// parseIgnoreWalk is a helper function user by the Parse CLI. It walks the given // root path (traversing symbolic links if any) and calls walkFn only // on files that were not ignored by the given Matcher. // Note: It ignores any errors encountered during matching func parseIgnoreWalk(matcher parseignore.Matcher, root string, walkFn filepath.WalkFunc) ([]error, error) { var errors []error ignoresWalkFn := func(path string, info os.FileInfo, err error) error { // if root==path relPath="." which is ignored by legacyRule // hence the special handling of root if err != nil || root == path { return walkFn(path, info, err) } relPath, err := filepath.Rel(root, path) if err != nil { return err } exclude, err := matcher.Match(relPath, info) if err != nil { errors = append(errors, err) return nil } if exclude == parseignore.Exclude { if info.IsDir() { return filepath.SkipDir } return nil } return walkFn(path, info, err) } return errors, symwalk.Walk(root, ignoresWalkFn) }
func Watch() { // Listen watched file change event if watcher != nil { watcher.Close() } watcher, _ = fsnotify.NewWatcher() go func() { for { select { case event := <-watcher.Events: if event.Op == fsnotify.Write { // Handle when file change Log(event.Name) ParseGlobalConfigWrap(rootPath, true) Build() if conn != nil { if err := conn.WriteMessage(websocket.TextMessage, []byte("change")); err != nil { Warn(err.Error()) } } } case err := <-watcher.Errors: Warn(err.Error()) } } }() var dirs = []string{ filepath.Join(rootPath, "source"), filepath.Join(themePath, "bundle"), } var files = []string{ filepath.Join(rootPath, "config.yml"), filepath.Join(themePath), } for _, source := range dirs { symwalk.Walk(source, func(path string, f os.FileInfo, err error) error { if f.IsDir() { if err := watcher.Add(path); err != nil { Warn(err.Error()) } } return nil }) } for _, source := range files { if err := watcher.Add(source); err != nil { Warn(err.Error()) } } }
func UpdateArticleCache() { articleCache = make(map[string]interface{}, 0) symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" { fileName := strings.TrimSuffix(strings.ToLower(filepath.Base(path)), ".md") config, _ := ParseArticleConfig(path) md5Hex := md5.Sum([]byte(path)) id := hex.EncodeToString(md5Hex[:]) articleCache[string(id)] = map[string]interface{}{ "name": fileName, "path": path, "article": config, } } return nil }) }
func UpdateArticleCache() { articleCache = make(map[string]CacheArticleInfo, 0) symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" { fileName := strings.TrimPrefix(strings.TrimSuffix(strings.ToLower(path), ".md"), "template/source/") config, _ := ParseArticleConfig(path) id := hashPath(path) articleCache[string(id)] = CacheArticleInfo{ Name: fileName, Path: path, Date: ParseDate(config.Date), Article: config, } } return nil }) }
func Watch() { // Listen watched file change event if watcher != nil { watcher.Close() } watcher, _ = fsnotify.NewWatcher() go func() { for { select { case event := <-watcher.Events: if event.Op == fsnotify.Write { // Handle when file change fmt.Println(event.Name) Build() if conn != nil { if err := conn.WriteMessage(websocket.TextMessage, []byte("change")); err != nil { Warn(err.Error()) } } } case err := <-watcher.Errors: Warn(err.Error()) } } }() var dirs = []string{"source"} for _, source := range dirs { dirPath := filepath.Join(rootPath, source) symwalk.Walk(dirPath, func(path string, f os.FileInfo, err error) error { if f.IsDir() { if err := watcher.Add(path); err != nil { Warn(err.Error()) } } return nil }) } }
func (d *deployCmd) getSourceFiles( dirName string, suffixes map[string]struct{}, e *parsecli.Env, ) ([]string, []string, error) { ignoreFile := filepath.Join(e.Root, parseIgnore) content, err := ioutil.ReadFile(ignoreFile) if err != nil { if !os.IsNotExist(err) { return nil, nil, stackerr.Wrap(err) } content = nil } matcher, errors := parseIgnoreMatcher(content) if errors != nil && d.Verbose { fmt.Fprintf(e.Err, "Error compiling the parseignore file:\n%s\n", ignoreErrors(errors, e), ) } ignoredSet := make(map[string]struct{}) err = symwalk.Walk(dirName, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() { ignoredSet[path] = struct{}{} } return nil }) if err != nil { return nil, nil, stackerr.Wrap(err) } var selected []string errors, err = parseIgnoreWalk(matcher, dirName, func(path string, info os.FileInfo, err error) error { if err != nil { return err } ok := len(suffixes) == 0 if !ok { _, ok = suffixes[filepath.Ext(path)] } if ok && !info.IsDir() { selected = append(selected, path) delete(ignoredSet, path) } return nil }) if err != nil { return nil, nil, stackerr.Wrap(err) } if len(errors) != 0 && d.Verbose { fmt.Fprintf(e.Err, "Encountered the following errors while matching patterns:\n%s\n", ignoreErrors(errors, e), ) } var ignored []string for file := range ignoredSet { ignored = append(ignored, file) } sort.Strings(selected) sort.Strings(ignored) return selected, ignored, nil }
func Convert(c *cli.Context) { // Parse arguments var sourcePath, rootPath string args := c.Args() if len(args) > 0 { sourcePath = args[0] } else { Fatal("Please specify the posts source path") } if len(args) > 1 { rootPath = args[1] } else { rootPath = "." } // Check if path exist if !Exists(sourcePath) || !Exists(rootPath) { Fatal("Please specify valid path") } // Parse Jekyll/Hexo post file count := 0 symwalk.Walk(sourcePath, func(path string, f os.FileInfo, err error) error { fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" || fileExt == ".html" { // Read data from file data, err := ioutil.ReadFile(path) fileName := filepath.Base(path) Log("Converting " + fileName) if err != nil { Fatal(err.Error()) } // Split config and markdown var configStr, contentStr string content := strings.TrimSpace(string(data)) parseAry := strings.SplitN(content, "---", 3) parseLen := len(parseAry) if parseLen == 3 { // Jekyll configStr = parseAry[1] contentStr = parseAry[2] } else if parseLen == 2 { // Hexo configStr = parseAry[0] contentStr = parseAry[1] } // Parse config var article ArticleConfig if err = yaml.Unmarshal([]byte(configStr), &article); err != nil { Fatal(err.Error()) } tags := make(map[string]bool) for _, t := range article.Tags { tags[t] = true } for _, c := range article.Categories { if _, ok := tags[c]; !ok { article.Tags = append(article.Tags, c) } } if article.Author == "" { article.Author = "me" } // Convert date dateAry := strings.SplitN(article.Date, ".", 2) if len(dateAry) == 2 { article.Date = dateAry[0] } if len(article.Date) == 10 { article.Date = article.Date + " 00:00:00" } if len(article.Date) == 0 { article.Date = "1970-01-01 00:00:00" } article.Update = "" // Generate Config var inkConfig []byte if inkConfig, err = yaml.Marshal(article); err != nil { Fatal(err.Error()) } inkConfigStr := string(inkConfig) markdownStr := inkConfigStr + "\n\n---\n\n" + contentStr + "\n" targetName := "source/" + fileName if fileExt != ".md" { targetName = targetName + ".md" } ioutil.WriteFile(filepath.Join(rootPath, targetName), []byte(markdownStr), 0644) count++ } return nil }) fmt.Printf("\nConvert finish, total %v articles\n", count) }
func Build() { startTime := time.Now() var articles = make(Collections, 0) var tagMap = make(map[string]Collections) var archiveMap = make(map[string]Collections) // Parse config themePath = filepath.Join(rootPath, globalConfig.Site.Theme) publicPath = filepath.Join(rootPath, "public") sourcePath = filepath.Join(rootPath, "source") // Append all partial html var partialTpl string files, _ := filepath.Glob(filepath.Join(themePath, "*.html")) for _, path := range files { fileExt := strings.ToLower(filepath.Ext(path)) baseName := strings.ToLower(filepath.Base(path)) if fileExt == ".html" && strings.HasPrefix(baseName, "_") { html, err := ioutil.ReadFile(path) if err != nil { Fatal(err.Error()) } tplName := strings.TrimPrefix(baseName, "_") tplName = strings.TrimSuffix(tplName, ".html") htmlStr := "{{define \"" + tplName + "\"}}" + string(html) + "{{end}}" partialTpl += htmlStr } } // Compile template articleTpl = CompileTpl(filepath.Join(themePath, "article.html"), partialTpl, "article") pageTpl = CompileTpl(filepath.Join(themePath, "page.html"), partialTpl, "page") archiveTpl = CompileTpl(filepath.Join(themePath, "archive.html"), partialTpl, "archive") tagTpl = CompileTpl(filepath.Join(themePath, "tag.html"), partialTpl, "tag") // Clean public folder cleanPatterns := []string{"post", "tag", "images", "js", "css", "*.html", "favicon.ico", "robots.txt"} for _, pattern := range cleanPatterns { files, _ := filepath.Glob(filepath.Join(publicPath, pattern)) for _, path := range files { os.RemoveAll(path) } } // Find all .md to generate article symwalk.Walk(sourcePath, func(path string, info os.FileInfo, err error) error { fileExt := strings.ToLower(filepath.Ext(path)) if fileExt == ".md" { // Parse markdown data article := ParseArticle(path) if article == nil || article.Draft { return nil } // Generate page name fileName := strings.TrimSuffix(strings.ToLower(filepath.Base(path)), ".md") Log("Building " + fileName) // Genetate custom link unixTime := time.Unix(article.Date, 0) linkMap := map[string]string{ "{year}": unixTime.Format("2006"), "{month}": unixTime.Format("01"), "{day}": unixTime.Format("02"), "{title}": fileName, } var link string if globalConfig.Site.Link == "" { link = fileName + ".html" } else { link = globalConfig.Site.Link for key, val := range linkMap { link = strings.Replace(link, key, val, -1) } } directory := filepath.Dir(link) err := os.MkdirAll(filepath.Join(publicPath, directory), 0777) if err != nil { Fatal(err.Error()) } // Generate file path article.Link = link article.GlobalConfig = *globalConfig articles = append(articles, *article) // Get tags info for _, tag := range article.Tags { if _, ok := tagMap[tag]; !ok { tagMap[tag] = make(Collections, 0) } tagMap[tag] = append(tagMap[tag], *article) } // Get archive info dateYear := unixTime.Format("2006") if _, ok := archiveMap[dateYear]; !ok { archiveMap[dateYear] = make(Collections, 0) } articleInfo := ArticleInfo{ DetailDate: article.Date, Date: unixTime.Format("2006-01-02"), Title: article.Title, Link: article.Link, Top: article.Top, } archiveMap[dateYear] = append(archiveMap[dateYear], articleInfo) } return nil }) if len(articles) == 0 { Fatal("Must be have at least one article") } // Sort by date sort.Sort(articles) // Generate rss page wg.Add(1) go GenerateRSS(articles) // Render article wg.Add(1) go RenderArticles(articleTpl, articles) // Generate article list pages wg.Add(1) go RenderArticleList("", articles, "") // Generate article list pages by tag for tagName, articles := range tagMap { wg.Add(1) go RenderArticleList(filepath.Join("tag", tagName), articles, tagName) } // Generate archive page archives := make(Collections, 0) for year, articleInfos := range archiveMap { // Sort by date sort.Sort(articleInfos) archives = append(archives, Archive{ Year: year, Articles: articleInfos, }) } // Sort by year sort.Sort(archives) wg.Add(1) go RenderPage(archiveTpl, map[string]interface{}{ "Total": len(articles), "Archive": archives, "Site": globalConfig.Site, "I18n": globalConfig.I18n, }, filepath.Join(publicPath, "archive.html")) // Generate tag page tags := make(Collections, 0) for tagName, tagArticles := range tagMap { articleInfos := make(Collections, 0) for _, article := range tagArticles { articleValue := article.(Article) articleInfos = append(articleInfos, ArticleInfo{ DetailDate: articleValue.Date, Date: time.Unix(articleValue.Date, 0).Format("2006-01-02"), Title: articleValue.Title, Link: articleValue.Link, Top: articleValue.Top, }) } // Sort by date sort.Sort(articleInfos) tags = append(tags, Tag{ Name: tagName, Count: len(tagArticles), Articles: articleInfos, }) } // Sort by count sort.Sort(Collections(tags)) wg.Add(1) go RenderPage(tagTpl, map[string]interface{}{ "Total": len(articles), "Tag": tags, "Site": globalConfig.Site, "I18n": globalConfig.I18n, }, filepath.Join(publicPath, "tag.html")) // Generate other pages files, _ = filepath.Glob(filepath.Join(sourcePath, "*.html")) for _, path := range files { fileExt := strings.ToLower(filepath.Ext(path)) baseName := filepath.Base(path) if fileExt == ".html" && !strings.HasPrefix(baseName, "_") { htmlTpl := CompileTpl(path, partialTpl, baseName) relPath, _ := filepath.Rel(sourcePath, path) wg.Add(1) go RenderPage(htmlTpl, globalConfig, filepath.Join(publicPath, relPath)) } } // Copy static files Copy() wg.Wait() endTime := time.Now() usedTime := endTime.Sub(startTime) fmt.Printf("\nFinished to build in public folder (%v)\n", usedTime) }