go-vise

Constrained Size Output Virtual Machine
Info | Log | Files | Refs | README | LICENSE

main.go (6518B)


      1 package main
      2 
      3 import (
      4 	"context"
      5 	"flag"
      6 	"fmt"
      7 	"io"
      8 	"os"
      9 	"path"
     10 	"strings"
     11 
     12 	fsdb "git.defalsify.org/vise.git/db/fs"
     13 	"git.defalsify.org/vise.git/debug"
     14 	"git.defalsify.org/vise.git/lang"
     15 	"git.defalsify.org/vise.git/logging"
     16 	"git.defalsify.org/vise.git/resource"
     17 )
     18 
     19 var (
     20 	logg         = logging.NewVanilla()
     21 	writeDomains = []string{
     22 		resource.PoDomain,
     23 		resource.TemplateKeyPoDomain,
     24 		resource.MenuKeyPoDomain,
     25 	}
     26 	writeDomainReady = make(map[string]bool)
     27 )
     28 
     29 type translator struct {
     30 	langs   []lang.Language
     31 	ctx     context.Context
     32 	rs      resource.Resource
     33 	newline bool
     34 	d       string
     35 }
     36 
     37 func newTranslator(ctx context.Context, rs resource.Resource, outPath string, newline bool) *translator {
     38 	return &translator{
     39 		ctx:     ctx,
     40 		rs:      rs,
     41 		d:       outPath,
     42 		newline: newline,
     43 	}
     44 }
     45 
     46 func (tr *translator) ensureFileNameFor(ln lang.Language, domain string) (string, error) {
     47 	fileName := domain + ".po"
     48 	p := path.Join(tr.d, ln.Code)
     49 	err := os.MkdirAll(p, 0700)
     50 	if err != nil {
     51 		return "", err
     52 	}
     53 	return path.Join(p, fileName), nil
     54 }
     55 
     56 // skip default*.po for translations other than default
     57 func (tr *translator) writersFor(ln lang.Language) ([]io.WriteCloser, error) {
     58 	var r []io.WriteCloser
     59 	_, ready := writeDomainReady[ln.Code]
     60 	for _, v := range writeDomains {
     61 		fp, err := tr.ensureFileNameFor(ln, v)
     62 		if err != nil {
     63 			return r, err
     64 		}
     65 		if !ready {
     66 			w, err := os.OpenFile(fp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
     67 			if err != nil {
     68 				return r, err
     69 			}
     70 			s := fmt.Sprintf(`msgid ""
     71 msgstr ""
     72 	"Content-Type: text/plain; charset=UTF-8\n"
     73 	"Language: %s\n"
     74 
     75 `, ln.Code)
     76 			_, err = w.Write([]byte(s))
     77 			if err != nil {
     78 				return r, err
     79 			}
     80 			w.Close()
     81 		}
     82 		w, err := os.OpenFile(fp, os.O_WRONLY|os.O_APPEND, 0644)
     83 		logg.DebugCtxf(tr.ctx, "writer", "fp", fp)
     84 		if err != nil {
     85 			return r, err
     86 		}
     87 		r = append(r, w)
     88 	}
     89 	writeDomainReady[ln.Code] = true
     90 	return r, nil
     91 }
     92 
     93 func (tr *translator) writeTranslation(w io.Writer, sym string, msgid string, msgstr string) error {
     94 	s := fmt.Sprintf(`#: vise_node.%s
     95 msgid ""
     96 %s
     97 msgstr ""
     98 %s
     99 
    100 `, sym, msgid, msgstr)
    101 	_, err := w.Write([]byte(s))
    102 	if err != nil {
    103 		return err
    104 	}
    105 	return nil
    106 }
    107 
    108 func (tr *translator) closeWriters(writers []io.WriteCloser) {
    109 	for _, w := range writers {
    110 		w.Close()
    111 	}
    112 }
    113 
    114 // TODO: DRY; merge with menuFunc
    115 func (tr *translator) nodeFunc(node *debug.Node) error {
    116 	var def string
    117 	for i, ln := range tr.langs {
    118 		var s string
    119 		ww, err := tr.writersFor(ln)
    120 		defer tr.closeWriters(ww)
    121 		if err != nil {
    122 			return fmt.Errorf("failed writers for lang '%s': %v", ln.Code, err)
    123 		}
    124 		ctx := context.WithValue(tr.ctx, "Language", ln)
    125 		r, err := tr.rs.GetTemplate(ctx, node.Name)
    126 		if err == nil {
    127 			logg.TraceCtxf(tr.ctx, "template found", "lang", ln, "node", node.Name)
    128 			for i, v := range strings.Split(r, "\n") {
    129 				if i > 0 {
    130 					if tr.newline {
    131 						s += fmt.Sprintf("\t\"\\n\"\n")
    132 					}
    133 				}
    134 				s += fmt.Sprintf("\t\"%s\"\n", v)
    135 			}
    136 			if def == "" {
    137 				def = fmt.Sprintf("\t\"%s\"\n", node.Name)
    138 				err = tr.writeTranslation(ww[1], node.Name, def, s)
    139 			}
    140 			if i == 0 {
    141 				def = s
    142 			}
    143 			err = tr.writeTranslation(ww[0], node.Name, def, s)
    144 			if err != nil {
    145 				return err
    146 			}
    147 		} else {
    148 			logg.DebugCtxf(tr.ctx, "no template found", "node", node.Name, "lang", ln)
    149 		}
    150 	}
    151 	return nil
    152 }
    153 
    154 // TODO: drop the multiline gen
    155 func (tr *translator) menuFunc(sym string) error {
    156 	var def string
    157 	for i, ln := range tr.langs {
    158 		var s string
    159 		ww, err := tr.writersFor(ln)
    160 		defer tr.closeWriters(ww)
    161 		if err != nil {
    162 			return fmt.Errorf("failed writers for lang '%s': %v", ln.Code, err)
    163 		}
    164 		ctx := context.WithValue(tr.ctx, "Language", ln)
    165 		r, err := tr.rs.GetMenu(ctx, sym)
    166 		if err == nil {
    167 			logg.TraceCtxf(tr.ctx, "menu found", "lang", ln, "menu", sym)
    168 			for i, v := range strings.Split(r, "\n") {
    169 				if i > 0 {
    170 					if tr.newline {
    171 						s += fmt.Sprintf("\t\"\\n\"\n")
    172 					}
    173 				}
    174 				s += fmt.Sprintf("\t\"%s\"\n", v)
    175 			}
    176 			if def == "" {
    177 				def = fmt.Sprintf("\t\"%s\"\n", sym)
    178 				err = tr.writeTranslation(ww[2], sym, def, s)
    179 			}
    180 			if i == 0 {
    181 				def = s
    182 			}
    183 			err = tr.writeTranslation(ww[0], sym, def, s)
    184 			if err != nil {
    185 				return err
    186 			}
    187 		} else {
    188 			logg.DebugCtxf(tr.ctx, "no menu found", "menu", sym, "lang", ln)
    189 		}
    190 	}
    191 	return nil
    192 }
    193 
    194 func (tr *translator) AddLang(ln lang.Language) error {
    195 	var err error
    196 	tr.langs = append(tr.langs, ln)
    197 	return err
    198 }
    199 
    200 type langVar struct {
    201 	v []lang.Language
    202 }
    203 
    204 func (lv *langVar) Set(s string) error {
    205 	v, err := lang.LanguageFromCode(s)
    206 	if err != nil {
    207 		return err
    208 	}
    209 	lv.v = append(lv.v, v)
    210 	return err
    211 }
    212 
    213 func (lv *langVar) String() string {
    214 	var s []string
    215 	for _, v := range lv.v {
    216 		s = append(s, v.Code)
    217 	}
    218 	return strings.Join(s, ",")
    219 }
    220 
    221 func (lv *langVar) Langs() []lang.Language {
    222 	return lv.v
    223 }
    224 
    225 func main() {
    226 	var dir string
    227 	var outDir string
    228 	var root string
    229 	var newline bool
    230 	var langs langVar
    231 
    232 	flag.StringVar(&dir, "d", ".", "node resource dir to read from")
    233 	flag.StringVar(&outDir, "o", "locale", "output directory")
    234 	flag.StringVar(&root, "root", "root", "entry point symbol")
    235 	flag.BoolVar(&newline, "newline", false, "insert newlines in multiline strings")
    236 	flag.Var(&langs, "l", "process for language")
    237 	flag.Parse()
    238 
    239 	fmt.Fprintf(os.Stderr, "starting session at symbol '%s' using resource dir: %s\n", root, dir)
    240 
    241 	err := os.MkdirAll(outDir, 0700)
    242 	if err != nil {
    243 		fmt.Fprintf(os.Stderr, "output dir create error: %v", err)
    244 		os.Exit(1)
    245 	}
    246 
    247 	ctx := context.Background()
    248 	rsStore := fsdb.NewFsDb()
    249 	err = rsStore.Connect(ctx, dir)
    250 	if err != nil {
    251 		fmt.Fprintf(os.Stderr, "resource db connect error: %v", err)
    252 		os.Exit(1)
    253 	}
    254 
    255 	rs := resource.NewDbResource(rsStore)
    256 
    257 	tr := newTranslator(ctx, rs, outDir, newline)
    258 	for _, ln := range langs.Langs() {
    259 		logg.DebugCtxf(ctx, "lang", "lang", ln)
    260 		err = tr.AddLang(ln)
    261 		if err != nil {
    262 			fmt.Fprintf(os.Stderr, "add language failed for %s: %v", ln.Code, err)
    263 			os.Exit(1)
    264 		}
    265 	}
    266 
    267 	nm := debug.NewNodeMap(root)
    268 	err = nm.Run(ctx, rs)
    269 	if err != nil {
    270 		fmt.Fprintf(os.Stderr, "node tree process fail: %v", err)
    271 		os.Exit(1)
    272 	}
    273 
    274 	for k, v := range debug.NodeIndex {
    275 		err = tr.nodeFunc(&v)
    276 		if err != nil {
    277 			fmt.Fprintf(os.Stderr, "translate process error for node %s: %v", k, err)
    278 			os.Exit(1)
    279 		}
    280 	}
    281 
    282 	for k, _ := range debug.MenuIndex {
    283 		logg.Tracef("processing menu", "sym", k)
    284 		err = tr.menuFunc(k)
    285 		if err != nil {
    286 			fmt.Fprintf(os.Stderr, "translate process error for menu %s: %v", k, err)
    287 			os.Exit(1)
    288 		}
    289 	}
    290 
    291 }