예제 #1
0
// 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)
}
예제 #2
0
파일: serve.go 프로젝트: zhangzzl/ink
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())
		}
	}
}
예제 #3
0
파일: api.go 프로젝트: gnawux/ink
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
	})
}
예제 #4
0
파일: api.go 프로젝트: zhangzzl/ink
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
	})
}
예제 #5
0
파일: serve.go 프로젝트: gnawux/ink
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
		})
	}
}
예제 #6
0
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
}
예제 #7
0
파일: main.go 프로젝트: zhangzzl/ink
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)
}
예제 #8
0
파일: build.go 프로젝트: gnawux/ink
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)
}