Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
404 lines (366 sloc) 7.44 KB
// This code is under BSD license. See license-bsd.txt
package main
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"path/filepath"
"strings"
"unicode/utf8"
"github.com/yosssi/gohtml"
)
func must(err error, args ...interface{}) {
if err == nil {
return
}
if len(args) == 0 {
panic(err)
}
s := args[0].(string)
if len(args) > 1 {
args = args[1:]
s = fmt.Sprintf(s, args)
}
panic(s + " err: " + err.Error())
}
func panicIfErr(err error) {
if err != nil {
panic(err.Error())
}
}
func panicMsg(format string, args ...interface{}) {
s := fmt.Sprintf(format, args...)
fmt.Printf("%s\n", s)
panic(s)
}
// FmtArgs formats args as a string. First argument should be format string
// and the rest are arguments to the format
func FmtArgs(args ...interface{}) string {
if len(args) == 0 {
return ""
}
format := args[0].(string)
if len(args) == 1 {
return format
}
return fmt.Sprintf(format, args[1:]...)
}
func panicWithMsg(defaultMsg string, args ...interface{}) {
s := FmtArgs(args...)
if s == "" {
s = defaultMsg
}
fmt.Printf("%s\n", s)
panic(s)
}
func panicIf(cond bool, args ...interface{}) {
if !cond {
return
}
panicWithMsg("PanicIf: condition failed", args...)
}
func logIfError(err error) {
if err != nil {
fmt.Printf("%s\n", err)
}
}
// whitelisted characters valid in url
func validateRune(c rune) byte {
if c >= 'a' && c <= 'z' {
return byte(c)
}
if c >= '0' && c <= '9' {
return byte(c)
}
if c == '-' || c == '_' || c == '.' {
return byte(c)
}
if c == ' ' {
return '-'
}
return 0
}
func charCanRepeat(c byte) bool {
if c >= 'a' && c <= 'z' {
return true
}
if c >= '0' && c <= '9' {
return true
}
return false
}
// urlify generates safe url from tile by removing hazardous characters
func urlify(title string) string {
s := strings.TrimSpace(title)
s = strings.ToLower(s)
var res []byte
for _, r := range s {
c := validateRune(r)
if c == 0 {
continue
}
// eliminute duplicate consequitive characters
var prev byte
if len(res) > 0 {
prev = res[len(res)-1]
}
if c == prev && !charCanRepeat(c) {
continue
}
res = append(res, c)
}
s = string(res)
if len(s) > 128 {
s = s[:128]
}
return s
}
func trimEmptyLines(a []string) []string {
var res []string
// remove empty lines from beginning and duplicated empty lines
prevWasEmpty := true
for _, s := range a {
currIsEmpty := (len(s) == 0)
if currIsEmpty && prevWasEmpty {
continue
}
res = append(res, s)
prevWasEmpty = currIsEmpty
}
// remove empty lines from end
for len(res) > 0 {
lastIdx := len(res) - 1
if len(res[lastIdx]) != 0 {
break
}
res = res[:lastIdx]
}
return res
}
func lastLineEmpty(lines []string) bool {
if len(lines) == 0 {
return false
}
lastIdx := len(lines) - 1
line := lines[lastIdx]
return len(line) == 0
}
func removeLastLine(lines []string) []string {
lastIdx := len(lines) - 1
return lines[:lastIdx]
}
func findWordEnd(s string, start int) int {
for i := start; i < len(s); i++ {
c := s[i]
if c == ' ' {
return i + 1
}
}
return -1
}
// TODO: must not remove spaces from start
func collapseMultipleSpaces(s string) string {
for {
s2 := strings.Replace(s, " ", " ", -1)
if s2 == s {
return s
}
s = s2
}
}
// remove #tag from start and end
func removeHashTags(s string) (string, []string) {
var tags []string
defer func() {
for i, tag := range tags {
tags[i] = strings.ToLower(tag)
}
}()
// remove hashtags from start
for strings.HasPrefix(s, "#") {
idx := findWordEnd(s, 0)
if idx == -1 {
tags = append(tags, s[1:])
return "", tags
}
tags = append(tags, s[1:idx-1])
s = strings.TrimLeft(s[idx:], " ")
}
// remove hashtags from end
s = strings.TrimRight(s, " ")
for {
idx := strings.LastIndex(s, "#")
if idx == -1 {
return s, tags
}
// tag from the end must not have space after it
if -1 != findWordEnd(s, idx) {
return s, tags
}
// tag from the end must start at the beginning of line
// or be proceded by space
if idx > 0 && s[idx-1] != ' ' {
return s, tags
}
tags = append(tags, s[idx+1:])
s = strings.TrimRight(s[:idx], " ")
}
}
// there are no guarantees in life, but this should be pretty unique string
func genRandomString() string {
var a [20]byte
_, err := rand.Read(a[:])
if err == nil {
return hex.EncodeToString(a[:])
}
return fmt.Sprintf("__--##%d##--__", rand.Int63())
}
func dupStringArray(a []string) []string {
return append([]string{}, a...)
}
func reverseStringArray(a []string) {
n := len(a) / 2
for i := 0; i < n; i++ {
end := len(a) - i - 1
a[i], a[end] = a[end], a[i]
}
}
func sanitizeForFile(s string) string {
var res []byte
toRemove := "/\\#()[]{},?+.'\""
var prev rune
buf := make([]byte, 3)
for _, c := range s {
if strings.ContainsRune(toRemove, c) {
continue
}
switch c {
case ' ', '_':
c = '-'
}
if c == prev {
continue
}
prev = c
n := utf8.EncodeRune(buf, c)
for i := 0; i < n; i++ {
res = append(res, buf[i])
}
}
if len(res) > 32 {
res = res[:32]
}
s = string(res)
s = strings.Trim(s, "_- ")
s = strings.ToLower(s)
return s
}
func normalizeNewlines(d []byte) []byte {
// replace CR LF (windows) with LF (unix)
d = bytes.Replace(d, []byte{13, 10}, []byte{10}, -1)
// replace CF (mac) with LF (unix)
d = bytes.Replace(d, []byte{13}, []byte{10}, -1)
return d
}
// return first line of d and the rest
func bytesRemoveFirstLine(d []byte) (string, []byte) {
idx := bytes.IndexByte(d, 10)
panicIf(-1 == idx)
l := d[:idx]
return string(l), d[idx+1:]
}
func replaceExt(fileName, newExt string) string {
ext := filepath.Ext(fileName)
if ext == "" {
return fileName
}
n := len(fileName)
s := fileName[:n-len(ext)]
return s + newExt
}
// foo => Foo, BAR => Bar etc.
func capitalize(s string) string {
if len(s) == 0 {
return s
}
s = strings.ToLower(s)
return strings.ToUpper(s[0:1]) + s[1:]
}
func mkdirForFile(filePath string) error {
dir := filepath.Dir(filePath)
return os.MkdirAll(dir, 0755)
}
func copyFile(dst string, src string) error {
err := mkdirForFile(dst)
if err != nil {
return err
}
fin, err := os.Open(src)
if err != nil {
return err
}
defer fin.Close()
fout, err := os.Create(dst)
if err != nil {
return err
}
_, err = io.Copy(fout, fin)
err2 := fout.Close()
if err != nil || err2 != nil {
os.Remove(dst)
}
return err
}
func dirCopyRecur(dst string, src string, shouldSkipFile func(string) bool) (int, error) {
nFilesCopied := 0
dirsToVisit := []string{src}
for len(dirsToVisit) > 0 {
n := len(dirsToVisit)
dir := dirsToVisit[n-1]
dirsToVisit = dirsToVisit[:n-1]
fileInfos, err := ioutil.ReadDir(dir)
if err != nil {
return nFilesCopied, err
}
for _, fi := range fileInfos {
path := filepath.Join(dir, fi.Name())
if fi.IsDir() {
dirsToVisit = append(dirsToVisit, path)
continue
}
if shouldSkipFile != nil && shouldSkipFile(path) {
continue
}
dstPath := dst + path[len(src):]
err := copyFile(dstPath, path)
if err != nil {
return nFilesCopied, err
}
verbose("Copied %s => %s\n", path, dstPath)
nFilesCopied++
}
}
return nFilesCopied, nil
}
func prettyHTML(d []byte) []byte {
// TODO: disable for now as it messes up inline by adding padding e.g.
// around bold elements
if true {
return d
}
gohtml.Condense = true
s := string(d)
s = gohtml.Format(s)
return []byte(s)
}
func fileExists(path string) bool {
st, err := os.Stat(path)
if err != nil {
return false
}
return st.Mode().IsRegular()
}
You can’t perform that action at this time.