Commit 3594d6f1 authored by Matthias Simon's avatar Matthias Simon
Browse files

Add t3s export for writing sources as an ETSI DOCX document.

The writer uses the version-5 paragraph styles so headings, notes, examples, and code match the working chapters.

Co-authored-by: default avatarCursor <cursoragent@cursor.com>
parent 1ba95a4a
Loading
Loading
Loading
Loading
+39 −11
Changes for tools/HANDOVER.md: 39 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ flowchart LR
  model --> extract["extract.TTCN3"]
  model -.-> grammar["extract.Grammar later"]
  model -.-> lint["lint later"]
  model -.-> docxOut["doc/docx Write later"]
  model --> docxOut["doc/docx Write"]
  model -.-> htmlOut["doc/html Write later"]
  model --> mdOut["doc/md Write"]
```
@@ -47,10 +47,10 @@ flowchart LR
|---|---|---|
| `internal/doc` | model | |
| `internal/doc/md` | `Read`, `Write` | |
| `internal/doc/docx` | `Read` | `Write` |
| `internal/doc/docx` | `Read`, `Write` | |
| `internal/doc/html` | | `Read`, `Write` |
| `internal/extract` | `TTCN3` | `Grammar`, keywords, modifiers |
| `cmd/t3s` | `extract examples`, `import` | |
| `cmd/t3s` | `extract examples`, `import`, `export` | |
| `internal/lint` | | yes |

Package names avoid the word "format", because the README uses it for
@@ -249,10 +249,11 @@ These will surface once the slice runs on all of `../src`:
## Later slices on the same model

- **Command line.** [`cmd/t3s`](cmd/t3s) implements
  `t3s extract examples -o dir [path...]` and
  `t3s import -o dir [-f] file.docx...`.
  `t3s extract examples -o dir [path...]`,
  `t3s import -o dir [-f] file.docx...`, and
  `t3s export -o file.docx [path...]`.
  `extract examples` writes `extract.TTCN3` results into the directory.
  Further commands wait on formatting, grammar, lint, and DOCX export.
  Further commands wait on formatting, grammar, and lint.
- **TTCN-3 parse and format** use `github.com/nokia/ntt`:
  - Parse `Code.Lines` with `ttcn3/syntax.Parse`, which accepts declaration
    and statement fragments.
@@ -269,11 +270,8 @@ These will surface once the slice runs on all of `../src`:
    README's lint rule. The start symbol is the first non-lexical
    production.
  - Keywords and modifiers come from the grammar's token literals.
- **DOCX and HTML writers** map `Section.Number`, `Note` (`NO`), titled
  examples (`EX`), and code (`PL`) to ETSI styles, and apply the meta rules
  above. The ETSI DOCX template is still needed; [`../assets`](../assets)
  holds screenshots only. EBNF production names inside code become links to
  their definitions.
- **HTML writer** is still open. The DOCX writer is implemented; see the
  export slice below. [`../assets`](../assets) holds screenshots only.
- **DOCX import** is implemented. See the slice below.

## DOCX import slice
@@ -343,3 +341,33 @@ the meta (`example="title"`).
- Images are comments, not figures. The model still has no image node.
- A second unnumbered top-level heading, such as "History" at the end of a
  chapter file, is numbered from 1000.

## DOCX export slice

`docx.Write` renders a `doc.Document` as one DOCX file.
`t3s export -o file.docx [path...]` reads Markdown the same way as
`extract examples` and writes that file. Duplicate clause numbers are an
error, as they are for extraction.

Styles, numbering, fonts, and the theme are copied from
`19-basic-program-statements.docx` in Version_5_Content. Page size and margins
match that file. Settings keep its tab stop, hyphenation, and compatibility
mode. Tracked changes are not turned on.

| Model | DOCX |
|---|---|
| Heading | `Heading` *level* from the clause depth, then the number, a tab, and the title |
| Paragraph | `Normal` |
| Bullet list | `B1+` |
| Ordered list | `BN`: `1)`, then `a.`, `i.` |
| Note | `NO`. The label is `NOTE:` or `NOTE n:`, then a tab |
| Example | `EX` caption `EXAMPLE n: title`, counted from 1 in each clause, then the code |
| Code | `PL`, one paragraph per line. `test` code is omitted |
| Quote | `Block Text` |
| Table | black borders, header `TAH`, body `TAL` |
| Inline code | bold Courier New, at the body size |

TTCN-3 keywords in `PL` are bold and comments are italic. EBNF and syntax
nonterminals are italic; a use of a defined name is a hyperlink to its first
definition. An empty paragraph separates adjacent code blocks so they stay
separate listings. HTML comments are left out.
+8 −5
Changes for tools/README.md: 8 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -13,14 +13,16 @@ The command is `t3s` (`cmd/t3s`).
```text
t3s extract examples -o dir [path...]
t3s import -o dir [-f] file.docx...
t3s help [extract|import]
t3s export -o file.docx [path...]
t3s help [extract|import|export]
t3s version
```

`extract examples` reads Markdown in clause order and writes one `.ttcn3` file
per TTCN-3 code block. With no paths it reads `./src`. `import` writes one
canonical Markdown file per top-level section of each DOCX file. It refuses
to overwrite an existing file unless `-f` is given.
to overwrite an existing file unless `-f` is given. `export` writes one DOCX
document. With no paths it reads `./src`.

## Features

@@ -28,7 +30,7 @@ to overwrite an existing file unless `-f` is given.
- [x] Extract TTCN-3 code blocks
- [ ] Extract TTCN-3 grammar
- [ ] Code Linting
- [ ] DOCX export
- [x] DOCX export
- [x] DOCX import

### Extract TTCN-3 code blocks
@@ -55,9 +57,10 @@ to overwrite an existing file unless `-f` is given.
### DOCX Export

Compile and export markdown documents into a coherent DOCX document that adheres
to ETSI requirements.
to ETSI requirements. `t3s export -o file.docx [path...]` reads Markdown in
clause order and writes that document. With no paths it reads `./src`.

- ETSI template in @assets
- Paragraph styles from the version-5 working documents (`Heading`, `Normal`, `PL`, `EX`, `NO`, `B1+`, `BN`)
- code blocks have syntax highlighting
- references to EBNF productions are hyperlinks referring to their definition.
- Every example has its title preceding using the "EX"-Style. E.g. `EXAMPLE 1: assignment notation`
+61 −0
Changes for tools/cmd/t3s/export.go: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
package main

import (
	"flag"
	"fmt"
	"io"
	"os"
	"path/filepath"

	"tools/internal/doc/docx"
)

func cmdExport(args []string, stdout, stderr io.Writer) int {
	var out string
	fs := flag.NewFlagSet("t3s export", flag.ContinueOnError)
	fs.SetOutput(io.Discard)
	fs.StringVar(&out, "o", "", "DOCX file to write")
	if code, ok := parseFlags(fs, args, stdout, stderr, exportHelp); !ok {
		return code
	}
	if out == "" {
		fmt.Fprintln(stderr, "t3s: -o is required")
		fmt.Fprint(stderr, exportHelp)
		return 2
	}
	if fi, err := os.Stat(out); err == nil && fi.IsDir() {
		fmt.Fprintf(stderr, "t3s: %s: is a directory\n", out)
		fmt.Fprint(stderr, exportHelp)
		return 2
	} else if err != nil && !os.IsNotExist(err) {
		fmt.Fprintln(stderr, err)
		return 1
	}
	d, err := readMarkdown(fs.Args())
	if err != nil {
		return report(stderr, err, exportHelp)
	}
	if dir := filepath.Dir(out); dir != "" && dir != "." {
		if err := os.MkdirAll(dir, 0o755); err != nil {
			fmt.Fprintln(stderr, err)
			return 1
		}
	}
	f, err := os.Create(out)
	if err != nil {
		fmt.Fprintln(stderr, err)
		return 1
	}
	if err := docx.Write(f, d); err != nil {
		f.Close()
		os.Remove(out)
		fmt.Fprintln(stderr, err)
		return 1
	}
	if err := f.Close(); err != nil {
		os.Remove(out)
		fmt.Fprintln(stderr, err)
		return 1
	}
	return 0
}
+19 −2
Changes for tools/cmd/t3s/main.go: 19 added lines, 2 removed lines.
Original line number Diff line number Diff line
// Command t3s extracts TTCN-3 examples and imports ETSI DOCX files.
// Command t3s extracts TTCN-3 examples and converts ETSI documents.
package main

import (
@@ -28,6 +28,8 @@ func run(args []string, stdout, stderr io.Writer) int {
		return cmdExtract(args[1:], stdout, stderr)
	case "import":
		return cmdImport(args[1:], stdout, stderr)
	case "export":
		return cmdExport(args[1:], stdout, stderr)
	default:
		fmt.Fprintf(stderr, "t3s: unknown command %q\n", args[0])
		fmt.Fprint(stderr, rootHelp)
@@ -43,6 +45,8 @@ func help(args []string, stdout, stderr io.Writer) int {
			topic = extractHelp
		case "import":
			topic = importHelp
		case "export":
			topic = exportHelp
		default:
			fmt.Fprintf(stderr, "t3s: unknown help topic %q\n", args[0])
			fmt.Fprint(stderr, rootHelp)
@@ -66,7 +70,8 @@ const rootHelp = `t3s — tools for the TTCN-3 standard sources
Usage:
  t3s extract examples -o dir [path...]
  t3s import -o dir [-f] file.docx...
  t3s help [extract|import]
  t3s export -o file.docx [path...]
  t3s help [extract|import|export]
  t3s version

`
@@ -83,6 +88,18 @@ in that directory, not in subdirectories. Files are read in clause order.

`

const exportHelp = `t3s export -o file.docx [path...]

Write one DOCX document.

  -o file
    	DOCX file to write (required)

With no paths, t3s reads ./src. A directory contributes the .md files
in that directory, not in subdirectories. Files are read in clause order.

`

const importHelp = `t3s import -o dir [-f] file.docx...

Write one canonical Markdown file per top-level section.
Loading