2016-08-13 12:49:04 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"log"
|
2016-09-06 20:05:18 +00:00
|
|
|
"path/filepath"
|
2016-08-13 12:49:04 +00:00
|
|
|
"strconv"
|
2016-11-06 14:06:25 +00:00
|
|
|
"strings"
|
2016-08-13 12:49:04 +00:00
|
|
|
"unicode"
|
|
|
|
"unicode/utf8"
|
2016-10-20 20:45:06 +00:00
|
|
|
|
2016-11-13 22:35:34 +00:00
|
|
|
"github.com/mattn/go-runewidth"
|
2016-08-13 12:49:04 +00:00
|
|
|
)
|
|
|
|
|
2016-09-06 20:05:18 +00:00
|
|
|
func isRoot(name string) bool { return filepath.Dir(name) == name }
|
2016-08-13 12:49:04 +00:00
|
|
|
|
2016-10-20 20:45:06 +00:00
|
|
|
func runeWidth(r rune) int {
|
2016-11-13 22:35:34 +00:00
|
|
|
w := runewidth.RuneWidth(r)
|
|
|
|
if w == 0 || (w == 2 && runewidth.IsAmbiguousWidth(r)) {
|
|
|
|
w = 1
|
2016-10-20 20:45:06 +00:00
|
|
|
}
|
2016-11-13 22:35:34 +00:00
|
|
|
return w
|
2016-10-20 20:45:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func runeSliceWidth(rs []rune) int {
|
|
|
|
w := 0
|
|
|
|
for _, r := range rs {
|
|
|
|
w += runeWidth(r)
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
}
|
|
|
|
|
|
|
|
func runeSliceWidthRange(rs []rune, beg int, end int) []rune {
|
|
|
|
curr := 0
|
|
|
|
b := 0
|
|
|
|
for i, r := range rs {
|
|
|
|
w := runeWidth(r)
|
|
|
|
switch {
|
|
|
|
case curr == beg:
|
|
|
|
b = i
|
|
|
|
case curr < beg && curr+w > beg:
|
|
|
|
b = i + 1
|
|
|
|
case curr == end:
|
|
|
|
return rs[b:i]
|
|
|
|
case curr > end:
|
|
|
|
return rs[b : i-1]
|
|
|
|
}
|
|
|
|
curr += w
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-11-06 14:06:25 +00:00
|
|
|
// This function splits the first word of a string delimited by whitespace from
|
|
|
|
// the rest. This is used to tokenize a string one by one without touching the
|
|
|
|
// rest. Whitespace on the left side of both the word and the rest are trimmed.
|
|
|
|
func splitWord(s string) (word, rest string) {
|
|
|
|
s = strings.TrimLeftFunc(s, unicode.IsSpace)
|
|
|
|
ind := len(s)
|
|
|
|
for i, c := range s {
|
|
|
|
if unicode.IsSpace(c) {
|
|
|
|
ind = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
word = s[0:ind]
|
|
|
|
rest = strings.TrimLeftFunc(s[ind:], unicode.IsSpace)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-08-14 19:56:55 +00:00
|
|
|
// This function converts a size in bytes to a human readable form. For this
|
|
|
|
// purpose metric suffixes are used (e.g. 1K = 1000). For values less than 10
|
|
|
|
// the first significant digit is shown, otherwise it is hidden. Numbers are
|
|
|
|
// always rounded down. For these reasons this function always show somewhat
|
|
|
|
// smaller values but it should be fine for most human beings.
|
2016-08-13 12:49:04 +00:00
|
|
|
func humanize(size int64) string {
|
|
|
|
if size < 1000 {
|
|
|
|
return fmt.Sprintf("%d", size)
|
|
|
|
}
|
|
|
|
|
|
|
|
suffix := []string{
|
|
|
|
"K", // kilo
|
|
|
|
"M", // mega
|
|
|
|
"G", // giga
|
|
|
|
"T", // tera
|
|
|
|
"P", // peta
|
|
|
|
"E", // exa
|
|
|
|
"Z", // zeta
|
|
|
|
"Y", // yotta
|
|
|
|
}
|
|
|
|
|
|
|
|
curr := float64(size) / 1000
|
|
|
|
for _, s := range suffix {
|
|
|
|
if curr < 10 {
|
2016-08-14 19:56:55 +00:00
|
|
|
return fmt.Sprintf("%.1f%s", curr-0.0499, s)
|
2016-08-13 12:49:04 +00:00
|
|
|
} else if curr < 1000 {
|
|
|
|
return fmt.Sprintf("%d%s", int(curr), s)
|
|
|
|
}
|
|
|
|
curr /= 1000
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// This function extracts numbers from a string and returns with the rest.
|
|
|
|
// It is used for numeric sorting of files when the file name consists of
|
|
|
|
// both digits and letters.
|
|
|
|
//
|
|
|
|
// For instance if your input is 'foo123bar456' you get a slice of number
|
|
|
|
// consisting of elements '123' and '456' and rest of the string as a slice
|
|
|
|
// consisting of elements 'foo' and 'bar'. The last return argument denotes
|
|
|
|
// whether or not the first partition is a number.
|
|
|
|
func extractNums(s string) (nums []int, rest []string, numFirst bool) {
|
|
|
|
var buf []rune
|
|
|
|
|
|
|
|
r, _ := utf8.DecodeRuneInString(s)
|
|
|
|
digit := unicode.IsDigit(r)
|
|
|
|
numFirst = digit
|
|
|
|
|
|
|
|
for i, c := range s {
|
|
|
|
if unicode.IsDigit(c) == digit {
|
|
|
|
buf = append(buf, c)
|
|
|
|
if i != len(s)-1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if digit {
|
|
|
|
i, err := strconv.Atoi(string(buf))
|
|
|
|
if err != nil {
|
|
|
|
// TODO: handle error
|
|
|
|
log.Printf("extracting numbers: %s", err)
|
|
|
|
}
|
|
|
|
nums = append(nums, i)
|
|
|
|
} else {
|
|
|
|
rest = append(rest, string(buf))
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = nil
|
|
|
|
buf = append(buf, c)
|
|
|
|
digit = !digit
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func min(a, b int) int {
|
|
|
|
if a < b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
func max(a, b int) int {
|
|
|
|
if a > b {
|
|
|
|
return a
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|
|
|
|
|
|
|
|
// We don't need no generic code
|
|
|
|
// We don't need no thought control
|
|
|
|
// No dark templates in compiler
|
|
|
|
// Haskell leave them kids alone
|
|
|
|
// Hey Bjarne leave them kids alone
|
|
|
|
// All in all it's just another brick in the code
|
|
|
|
// All in all you're just another brick in the code
|
|
|
|
//
|
|
|
|
// -- Pink Trolled --
|