Artifact
966e7d983bd7174e2505b9f693fe93e1a4a794de94addcbaad710e24a7088c87:
- File
cmd/tome/op_typst.go
— part of check-in
[8b960f6f4a]
at
2024-12-15 09:00:37
on branch trunk
— transform markdown into typst (work in progress)
(user:
dnc
size: 3500)
// Copyright (C) David N. Cohen
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package main
import (
"errors"
"flag"
"fmt"
"io"
"log"
"os"
"golang.org/x/text/transform"
"src.d10.dev/tome/internal/tome"
"src.d10.dev/tome/typst"
)
func init() {
cmd.Operation("typst",
"typst [-suffix=.typ] [DIRECTORY ...]",
"writes notes to file in typst format",
opTypst,
)
}
func opTypst() (err error) {
defer func() {
if err != nil {
log.Printf("opTypst failed: %v", err)
}
}()
md := flag.Bool("md", true, "convert markdown headers into typst headers")
outfile := flag.String("o", "", "filename where to write typst markup")
flag.Parse()
if len(Suffix) == 0 {
Suffix = []string{".typ"}
if *md {
Suffix = append(Suffix, ".md")
}
}
arg := flag.Args()
if len(arg) == 0 {
wd, err := os.Getwd()
if err != nil {
return err
}
arg = append(arg, wd) // default find content in current directory
}
filenames := []string{}
for i := range arg {
names, err := tome.FindSuffix(arg[i], Suffix...)
if err != nil {
return fmt.Errorf("failed to find content in directory (%q): %w", arg[i], err)
}
sortAlphaWithFirst(names, "README.*")
// overall list preserves directory order of args
filenames = append(filenames, names...)
}
out := os.Stdout
log.Printf("outfile (%q)", *outfile)
if *outfile != "" {
out, err = os.OpenFile(*outfile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return fmt.Errorf("cannot open output file (%q): %w", *outfile, err)
}
defer func() {
err = errors.Join(err, out.Close())
}()
}
// one-time definitions at top of typ file
_, err = fmt.Fprint(out, header)
if err != nil {
return fmt.Errorf("failed to write typst header: %w", err)
}
for _, name := range filenames {
f, err := os.Open(name)
if err != nil {
return err
}
log.Printf("transforming file %q", name)
var xform []transform.Transformer
if *md {
xform = append(xform, typst.NewMd(name)...)
}
// last
xform = append(xform, typst.New(name))
// avoid transform.Chain because it has a fixed dst buffer size, so we'd fail with ErrShortDst
// actually problem with chain was probably misunderstanding of how ErrShortDst works, it probably would work as intended
// var pipeline io.Writer = out
// for _, x := range xform {
// tw := transform.NewWriter(pipeline, x)
// defer tw.Close()
// pipeline = tw
// }
pipeline := transform.NewWriter(out, transform.Chain(xform...))
defer pipeline.Close()
_, err = io.Copy(pipeline, f)
if err != nil {
return err
}
}
return nil
}
const header = `
// style
#show link: underline
#show link: set text(blue)
// prevent broken links from breaking compile
#show link: it => {
if type(it.dest) != label {
return it
}
if query(it.dest).len() > 0 {
it
} else {
text(fill: red)[#it.body]
}
}
`