diff options
author | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2019-09-02 13:46:59 +0900 |
commit | e0c6872cf40896c7be36b11dcc744620f10adf1d (patch) | |
tree | 60335e10d2f4354b0674ec22d7b53f0f8abee672 /indexing/zhmakeindex/main.go |
Initial commit
Diffstat (limited to 'indexing/zhmakeindex/main.go')
-rw-r--r-- | indexing/zhmakeindex/main.go | 234 |
1 files changed, 234 insertions, 0 deletions
diff --git a/indexing/zhmakeindex/main.go b/indexing/zhmakeindex/main.go new file mode 100644 index 0000000000..5ba585b4f4 --- /dev/null +++ b/indexing/zhmakeindex/main.go @@ -0,0 +1,234 @@ +// zhmakeindex: 带中文支持的 makeindex 实现 +package main // import "github.com/leo-liu/zhmakeindex" + +import ( + "flag" + "fmt" + "io" + "io/ioutil" + "log" + "os" + "path/filepath" + "runtime/pprof" + "strings" + + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/encoding/traditionalchinese" + "golang.org/x/text/encoding/unicode" + "golang.org/x/text/transform" +) + +var ( + ProgramAuthor = "刘海洋<leoliu.pku@gmail.com>" + // Build with option: -ldflags "-x main.Version ? -X main.Revision ?" + Version = "???" + Revision = "???" +) + +var debug = log.New(os.Stderr, "DEBUG: ", log.Lshortfile) + +func init() { + log.SetFlags(0) + log.SetPrefix("") +} + +func main() { + option := NewOptions() + option.parse() + + setupLog(option) + + log.Printf("zhmakeindex 版本:%s-%s\t作者:%s\n", Version, Revision, ProgramAuthor) + + if option.cpuprofile != "" { + f, err := os.Create(option.cpuprofile) + if err != nil { + log.Fatal(err) + } + if err := pprof.StartCPUProfile(f); err != nil { + log.Fatal("无法启动 CPU 性能调试:", err) + } + defer pprof.StopCPUProfile() + } + + if option.style != "" { + log.Printf("正在读取格式文件 %s……", option.style) + } + instyle, outstyle := NewStyles(&option.StyleOptions) + + in := NewInputIndex(&option.InputOptions, instyle) + log.Printf("合并后共 %d 项。\n", len(*in)) + + log.Println("正在排序……") + out := NewOutputIndex(in, &option.OutputOptions, outstyle) + + log.Println("正在输出……") + out.Output(&option.OutputOptions) + + if option.output != "" { + log.Printf("输出文件写入 %s\n", option.output) + } + if option.log != "" { + log.Printf("日志文件写入 %s\n", option.log) + } +} + +type Options struct { + InputOptions + OutputOptions + StyleOptions + encoding string + style_encoding string + log string + quiet bool + cpuprofile string +} + +type InputOptions struct { + compress bool + stdin bool + decoder transform.Transformer // 由 encoding 生成 + input []string +} + +type OutputOptions struct { + encoder transform.Transformer // 由 encoding 生成 + output string + sort string + page string + strict bool + disable_range bool +} + +type StyleOptions struct { + style string + style_decoder transform.Transformer // 由 style_encoding 生成 +} + +func NewOptions() *Options { + o := new(Options) + flag.BoolVar(&o.compress, "c", false, "忽略条目首尾空格") + flag.BoolVar(&o.stdin, "i", false, "从标准输入读取") + flag.StringVar(&o.output, "o", "", "输出文件") + flag.StringVar(&o.sort, "z", "pinyin", + "中文分组排序方式,可以使用 pinyin (reading)、bihua (stroke) 或 bushou (radical)") + // flag.StringVar(&o.page, "p", "", "设置起始页码") // 未实现 + flag.BoolVar(&o.quiet, "q", false, "静默模式,不输出错误信息") + flag.BoolVar(&o.disable_range, "r", false, "禁用自动生成页码区间") + flag.BoolVar(&o.strict, "strict", false, "严格区分不同 encapsulated 命令的页码") + flag.StringVar(&o.style, "s", "", "格式文件名") + flag.StringVar(&o.log, "t", "", "日志文件名") + flag.StringVar(&o.encoding, "enc", "utf-8", "读写索引文件的编码") + flag.StringVar(&o.style_encoding, "senc", "utf-8", "格式文件的编码") + flag.StringVar(&o.cpuprofile, "cpuprofile", "", "将 CPU 性能调试信息写入文件") + return o +} + +func (o *Options) parse() { + // 设置自定义帮助信息 + flag.Usage = Usage + + flag.Parse() + + // 整理输入文件 + o.input = flag.Args() + for i := range o.input { + o.input[i] = filepath.Clean(o.input[i]) + } + + // 错误的参数组合 + if len(o.input) > 0 && o.stdin { + log.Fatalln("不能同时从文件和标准输入流读取输入") + } else if len(o.input) == 0 && !o.stdin { + // 没有输入文件 + flag.Usage() + os.Exit(0) + } + // 不指定输出文件且不使用标准输入时,使用第一个输入文件的主文件名 + ".ind" 后缀 + if o.output == "" && !o.stdin { + o.output = stripExt(o.input[0]) + ".ind" + } + // 不指定输入文件且不使用标准输入时,使用第一个输入文件的主文件名 + ".ilg" 后缀 + if o.log == "" && !o.stdin { + o.log = stripExt(o.input[0]) + ".ilg" + } + // 不指定格式文件且只有一个输入文件时,尝试使用第一个输入文件的主文件名 + ".mst" 后缀 + if o.style == "" && len(o.input) == 1 { + // 这里只在当前目录下查找 .mst 文件而不调用 kpathsea 搜索,预先判断文件存在 + mst := stripExt(o.input[0]) + ".mst" + if _, err := os.Stat(mst); err == nil { + o.style = mst + } + } + + // 检查并设置 IO 编码 + encoding := checkEncoding(o.encoding) + o.encoder = encoding.NewEncoder() + o.decoder = encoding.NewDecoder() + + // 检查并设置格式文件编码 + styleEncoding := checkEncoding(o.style_encoding) + o.style_decoder = styleEncoding.NewDecoder() +} + +// 检查编码名,返回编码 +func checkEncoding(encodingName string) encoding.Encoding { + var encodingMap = map[string]encoding.Encoding{ + "utf-8": encoding.Nop, + "utf-16": unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM), + "gb18030": simplifiedchinese.GB18030, + "gbk": simplifiedchinese.GBK, + "big5": traditionalchinese.Big5, + } + encoding, ok := encodingMap[strings.ToLower(encodingName)] + if !ok { + log.Printf("编码 '%s' 是无效编码\n", encodingName) + var supported string + for enc := range encodingMap { + supported += enc + " " + } + log.Fatalf("支持的编码有(不区分大小写):%s\n", supported) + } + return encoding +} + +func setupLog(option *Options) { + var stderr io.Writer = os.Stderr + if option.quiet { + stderr = ioutil.Discard + } + if option.log == "" { + // 只使用标准错误流 + return + } + flog, err := os.Create(option.log) + if err != nil { + log.Fatalln(err) + } + log.SetOutput(io.MultiWriter(stderr, flog)) +} + +// 删除文件后缀名 +func stripExt(fpath string) string { + ext := filepath.Ext(fpath) + return fpath[:len(fpath)-len(ext)] +} + +// 帮助信息 +func Usage() { + fmt.Fprintln(os.Stderr, `用法: +zhmakeindex [-c] [-i] [-o <ind>] [-q] [-r] [-s <sty>] [-t <log>] + [-enc <enc>] [-senc <senc>] [-strict] [-z <sort>] + [<输入文件1> <输入文件2> ...]`) + fmt.Fprintln(os.Stderr, "\n中文索引处理程序") + fmt.Fprintf(os.Stderr, "\n %-10s %-5s %s\n", "选项", "默认值", "说明") + flag.VisitAll(func(f *flag.Flag) { + if f.Value.String() == "" { + fmt.Fprintf(os.Stderr, " -%-11s %-7s %s\n", f.Name, "无", f.Usage) + } else { + fmt.Fprintf(os.Stderr, " -%-11s %-8s %s\n", f.Name, f.DefValue, f.Usage) + } + }) + fmt.Fprintf(os.Stderr, "\n版本:%s-%s\t作者:%s\n", Version, Revision, ProgramAuthor) +} |