func (cfg *config) init() *app.OptionsError { if !version.SemVerValid(cfg.Version) { return &app.OptionsError{Field: "version", Message: locale.Sprintf(locale.ErrInvalidFormat)} } if len(cfg.Inputs) == 0 { return &app.OptionsError{Field: "inputs", Message: locale.Sprintf(locale.ErrRequired)} } if cfg.Output == nil { return &app.OptionsError{Field: "output", Message: locale.Sprintf(locale.ErrRequired)} } for i, opt := range cfg.Inputs { if err := opt.Init(); err != nil { index := strconv.Itoa(i) err.Field = "inputs[" + index + "]." + err.Field return err } opt.SyntaxLog = erro // 语法错误输出到 erro 中 } if err := cfg.Output.Init(); err != nil { err.Field = "outputs." + err.Field return err } return nil }
// 真正的程序入口,main 主要是作参数的处理。 func run() { start := time.Now() path, err := getConfigFile() if err != nil { erro.Println(err) return } cfg, err := loadConfig(path) if err != nil { erro.Println(err) return } // 比较版本号兼容问题 compatible, err := version.SemVerCompatible(app.Version, cfg.Version) if err != nil { erro.Println(err) return } if !compatible { erro.Println(locale.Sprintf(locale.VersionInCompatible)) return } // 分析文档内容 docs := doc.New() wg := &sync.WaitGroup{} for _, opt := range cfg.Inputs { wg.Add(1) go func(o *input.Options) { if err := input.Parse(docs, o); err != nil { erro.Println(err) } wg.Done() }(opt) } wg.Wait() if len(docs.Title) == 0 { docs.Title = app.DefaultTitle } // 输出内容 cfg.Output.Elapsed = time.Now().Sub(start) if err := output.Render(docs, cfg.Output); err != nil { erro.Println(err) return } info.Println(locale.Sprintf(locale.Complete, cfg.Output.Dir, time.Now().Sub(start))) }
func (b *block) EndFunc(l *lexer) ([]rune, bool) { switch b.Type { case blockTypeString: return b.endString(l) case blockTypeMComment: return b.endMComments(l) case blockTypeSComment: return b.endSComments(l) default: panic(locale.Sprintf(locale.ErrInvalidBlockType, b.Type)) } }
// Render 渲染 docs 的内容,具体的渲染参数由 o 指定。 func Render(docs *doc.Doc, o *Options) error { switch o.Type { case "html": return renderHTML(docs, o) case "html+": return renderHTMLPlus(docs, o) case "json": return renderJSON(docs, o) default: return &app.OptionsError{Field: "Type", Message: locale.Sprintf(locale.ErrInvalidOutputType)} } }
// DetectDirLang 检测指定目录下的语言类型。 // // 检测依据为根据扩展名来做统计,数量最大且被支持的获胜。 // 不会分析子目录。 func DetectDirLang(dir string) (string, error) { fs, err := ioutil.ReadDir(dir) if err != nil { return "", err } // langsMap 记录每个支持的语言对应的文件数量 langsMap := make(map[string]int, len(fs)) for _, f := range fs { // 遍历所有的文件 if f.IsDir() { continue } ext := strings.ToLower(filepath.Ext(f.Name())) lang := getLangByExt(ext) if len(lang) > 0 { langsMap[lang]++ } } if len(langsMap) == 0 { return "", errors.New(locale.Sprintf(locale.ErrNotFoundSupportedLang)) } lang := "" cnt := 0 for k, v := range langsMap { if v >= cnt { lang = k cnt = v } } if len(lang) > 0 { return lang, nil } return "", errors.New(locale.Sprintf(locale.ErrNotFoundSupportedLang)) }
// Init 检测 Options 变量是否符合要求 func (opt *Options) Init() *app.OptionsError { if len(opt.Dir) == 0 { return &app.OptionsError{Field: "dir", Message: locale.Sprintf(locale.ErrRequired)} } if !utils.FileExists(opt.Dir) { return &app.OptionsError{Field: "dir", Message: locale.Sprintf(locale.ErrDirNotExists)} } if len(opt.Lang) == 0 { return &app.OptionsError{Field: "lang", Message: locale.Sprintf(locale.ErrRequired)} } if !langIsSupported(opt.Lang) { return &app.OptionsError{Field: "lang", Message: locale.Sprintf(locale.ErrUnsupportedInputLang, opt.Lang)} } if len(opt.Exts) > 0 { exts := make([]string, 0, len(opt.Exts)) for _, ext := range opt.Exts { if len(ext) == 0 { continue } if ext[0] != '.' { ext = "." + ext } exts = append(exts, ext) } opt.Exts = exts } else { opt.Exts = langExts[opt.Lang] } return nil }
// 分析 path 指向的文件,并将内容写入到 docs 中。 func parseFile(docs *doc.Doc, path string, blocks []blocker, synerrLog *log.Logger) { data, err := ioutil.ReadFile(path) if err != nil && synerrLog != nil { synerrLog.Println(&app.SyntaxError{Message: err.Error(), File: path}) return } l := &lexer{data: data} var block blocker wg := sync.WaitGroup{} defer wg.Wait() for { if l.atEOF() { return } if block == nil { block = l.block(blocks) if block == nil { // 没有匹配的 block 了 return } } ln := l.lineNumber() + 1 // 记录当前的行号,顺便调整为行号起始行号为 1 rs, ok := block.EndFunc(l) if !ok && synerrLog != nil { synerrLog.Println(&app.SyntaxError{Line: ln, File: path, Message: locale.Sprintf(locale.ErrNotFoundEndFlag)}) return } block = nil if len(rs) < miniSize { continue } wg.Add(1) go func(rs []rune, ln int) { if err := docs.Scan(rs); err != nil && synerrLog != nil { err.Line += ln err.File = path synerrLog.Println(err) } wg.Done() }(rs, ln) } // end for }
// Init 对 Options 作一些初始化操作。 func (o *Options) Init() *app.OptionsError { if len(o.Dir) == 0 { return &app.OptionsError{Field: "dir", Message: locale.Sprintf(locale.ErrRequired)} } if len(o.Type) == 0 { return &app.OptionsError{Field: "type", Message: locale.Sprintf(locale.ErrRequired)} } if !utils.FileExists(o.Dir) { if err := os.MkdirAll(o.Dir, os.ModePerm); err != nil { msg := locale.Sprintf(locale.ErrMkdirError, err) return &app.OptionsError{Field: "dir", Message: msg} } } if !isSuppertedType(o.Type) { return &app.OptionsError{Field: "type", Message: locale.Sprintf(locale.ErrInvalidFormat)} } // 只有 html 和 html+ 才需要判断模板文件是否存在 if o.Type == "html" || o.Type == "html+" { if len(o.Template) > 0 && !utils.FileExists(o.Template) { msg := locale.Sprintf(locale.ErrTemplateNotExists) return &app.OptionsError{Field: "template", Message: msg} } } // 调试模式,必须得有模板和端口 if o.Type == "html+" { if len(o.Template) == 0 { return &app.OptionsError{Field: "template", Message: locale.Sprintf(locale.ErrRequired)} } if len(o.Port) == 0 { return &app.OptionsError{Field: "port", Message: locale.Sprintf(locale.ErrRequired)} } if o.Port[0] != ':' { o.Port = ":" + o.Port } } return nil }
// Parse 分析源代码,获取相应的文档内容。 func Parse(docs *doc.Doc, o *Options) error { blocks, found := langs[o.Lang] if !found { return errors.New(locale.Sprintf(locale.ErrUnsupportedInputLang, o.Lang)) } paths, err := recursivePath(o) if err != nil { return err } wg := sync.WaitGroup{} defer wg.Wait() for _, path := range paths { wg.Add(1) go func(path string) { parseFile(docs, path, blocks, o.SyntaxLog) wg.Done() }(path) } return nil }
// 构建一个语法错误的信息。 func (l *lexer) syntaxError(format string, v ...interface{}) *app.SyntaxError { return &app.SyntaxError{ Line: l.lineNumber(), Message: locale.Sprintf(format, v...), } }
func (err *OptionsError) Error() string { return locale.Sprintf(locale.OptionsError, ConfigFilename, err.Field, err.Message) }
func (err *SyntaxError) Error() string { return locale.Sprintf(locale.SyntaxError, err.File, err.Line, err.Message) }
func main() { tag, err := locale.Init() if err != nil { warn.Println(err) info.Println("无法获取系统语言,使用默认的本化语言:", app.DefaultLocale) tag, err = language.Parse(app.DefaultLocale) if err != nil { erro.Println(err) return } } locale.SetLocale(tag) h := flag.Bool("h", false, locale.Sprintf(locale.FlagHUsage)) v := flag.Bool("v", false, locale.Sprintf(locale.FlagVUsage)) l := flag.Bool("l", false, locale.Sprintf(locale.FlagLUsage)) g := flag.Bool("g", false, locale.Sprintf(locale.FlagGUsage)) pprofType := flag.String("pprof", "", locale.Sprintf(locale.FlagPprofUsage)) flag.Usage = usage flag.Parse() switch { case *h: flag.Usage() return case *v: locale.Printf(locale.FlagVersionBuildWith, app.Name, app.Version, runtime.Version()) return case *l: locale.Printf(locale.FlagSupportedLangs, input.Langs()) return case *g: path, err := getConfigFile() if err != nil { erro.Println(err) return } if err = genConfigFile(path); err != nil { erro.Println(err) return } info.Println(locale.Sprintf(locale.FlagConfigWritedSuccess, path)) return } // 指定了 pprof 参数 if len(*pprofType) > 0 { profile := filepath.Join("./", app.Profile) f, err := os.Create(profile) if err != nil { // 不能创建文件,则忽略 pprof 相关操作 warn.Println(err) goto RUN } defer func() { if err = f.Close(); err != nil { erro.Println(err) return } info.Println(locale.Sprintf(locale.FlagPprofWritedSuccess, profile)) }() switch strings.ToLower(*pprofType) { case "mem": defer func() { if err = pprof.Lookup("heap").WriteTo(f, 1); err != nil { warn.Println(err) } }() case "cpu": if err := pprof.StartCPUProfile(f); err != nil { warn.Println(err) } defer pprof.StopCPUProfile() default: erro.Println(locale.Sprintf(locale.FlagInvalidPprrof)) return } } RUN: run() }