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] } } `