diff --git a/file-util.go b/file-util.go new file mode 100644 index 0000000..c6fb788 --- /dev/null +++ b/file-util.go @@ -0,0 +1,116 @@ +// file_util.go +package utils + +import ( + "errors" + "fmt" + "io" + "os" + "strconv" + "strings" +) + +func FileSize(filePath string) (size int64) { + info, err := os.Stat(filePath) + if err == nil { + size = info.Size() + } else { + size = -1 + } + return +} + +func IsRegularFile(filePath string) bool { + if filePath != "" { + info, err := os.Stat(filePath) + // return (err == nil || os.IsExist(err)) && info.Mode().IsRegular() + return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsRegular() + } + return false +} + +func IsDirectory(filePath string) bool { + if filePath != "" { + info, err := os.Stat(filePath) + return (err == nil || errors.Is(err, os.ErrExist)) && info.Mode().IsDir() + } + return false +} + +func IsSymLink(filePath string) bool { + if filePath != "" { + info, err := os.Stat(filePath) + return (err == nil || errors.Is(err, os.ErrExist)) && (info.Mode()&os.ModeSymlink != 0) + } + return false +} + +func IsSymLinkByDirEntry(e os.DirEntry) bool { + info, _ := e.Info() + return info.Mode()&os.ModeSymlink != 0 +} + +func StreamIsTerminal(fh *os.File) bool { + var isTerminal bool = false + if fh != nil { + fi, _ := fh.Stat() + isTerminal = (fi.Mode() & os.ModeCharDevice) != 0 + } + return isTerminal +} + +/* + * Ispirata a https://stackoverflow.com/a/50741908 + */ +func MoveFile(sourcePath, destPath string) (int64, error) { + var size int64 = 0 + + err := os.Rename(sourcePath, destPath) + if err != nil { + inputFile, err := os.Open(sourcePath) + if err != nil { + return 0, fmt.Errorf("Couldn't open source file: %s", err) + } + outputFile, err := os.Create(destPath) + if err != nil { + inputFile.Close() + return 0, fmt.Errorf("Couldn't open dest file: %s", err) + } + defer outputFile.Close() + size, err = io.Copy(outputFile, inputFile) + inputFile.Close() + if err != nil { + return 0, fmt.Errorf("Writing to output file failed: %s", err) + } + // The copy was successful, so now delete the original file + err = os.Remove(sourcePath) + if err != nil { + return 0, fmt.Errorf("Failed removing original file: %s", err) + } + } + return size, nil +} + +func ParseMemSize(s string) (size int64, err error) { + const MULTIPLES = "BKMGTP" + // s = strings.TrimSpace(s) + if len(s) != 0 { + s = strings.ToUpper(s) + s = strings.ReplaceAll(s, ".", "") + s = strings.ReplaceAll(s, ",", "") + s = strings.ReplaceAll(s, " ", "") + pos := strings.IndexAny(s, MULTIPLES) + if pos >= 0 { + size, err = strconv.ParseInt(s[0:pos], 10, 64) + mul := strings.IndexByte(MULTIPLES, s[pos]) + for ; mul > 0; mul-- { + size = size * 1_000 + } + } else { + size, err = strconv.ParseInt(s, 10, 64) + } + } else { + err = errors.New("Empty string") + } + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4fae332 --- /dev/null +++ b/go.mod @@ -0,0 +1,6 @@ +module portale-stac.it/packages/utils + +go 1.21 + +require golang.org/x/text v0.3.7 + diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1f78e03 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/gzip-util.go b/gzip-util.go new file mode 100644 index 0000000..a39ed49 --- /dev/null +++ b/gzip-util.go @@ -0,0 +1,36 @@ +// gzip-util.go +package utils + +import ( + "bufio" + "compress/gzip" + "fmt" + "io" + "strings" +) + +const GZIP_SUFFIX = ".gz" + +func CreateGzipReader(in io.Reader) (reader *bufio.Reader, err error) { + inflate, err := gzip.NewReader(in) + if err != nil { + fmt.Errorf("Can't inflate gzipped input stream: %v", err) + } else { + reader = bufio.NewReader(inflate) + } + return +} + +func CreateGzipFile(in io.Reader, fileName string) (reader *bufio.Reader, err error) { + if len(fileName) > 0 && strings.HasSuffix(fileName, GZIP_SUFFIX) { + inflate, err := gzip.NewReader(in) + if err != nil { + fmt.Errorf("Can't inflate gzipped input stream %#v: %v", fileName, err) + } else { + reader = bufio.NewReader(inflate) + } + } else { + reader = bufio.NewReader(in) + } + return +} diff --git a/misc-util.go b/misc-util.go new file mode 100644 index 0000000..2a85d1a --- /dev/null +++ b/misc-util.go @@ -0,0 +1,154 @@ +package utils + +import ( + "fmt" + "math/rand" + + "os" + "strconv" + "time" + + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +func NewEnglishPrinter() *message.Printer { + return message.NewPrinter(language.English) +} + +func ExitErrorf(rc int, format string, args ...interface{}) { + fmt.Fprintf(os.Stderr, format+"\n", args...) + os.Exit(rc) +} + +func ExitMessagef(rc int, format string, args ...interface{}) { + fmt.Printf(format+"\n", args...) + os.Exit(rc) +} + +func OnStringIndex(index uint, values ...string) (value string) { + if index >= 0 && index < uint(len(values)) { + value = values[index] + } + return +} + +func OnEmpty(s string, altValues ...string) (value string) { + if len(s) == 0 { + for _, altValue := range altValues { + if len(altValue) > 0 { + value = altValue + } + } + } else { + value = s + } + return s +} + +func OnCond(cond bool, trueValue, falseValue string) string { + if cond { + return trueValue + } else { + return falseValue + } +} + +func OnCondIface(cond bool, trueValue, falseValue interface{}) interface{} { + if cond { + return trueValue + } else { + return falseValue + } +} + +func OnCondInt(cond bool, trueValue, falseValue int) int { + if cond { + return trueValue + } else { + return falseValue + } +} + +func OnCondInt64(cond bool, trueValue, falseValue int64) int64 { + if cond { + return trueValue + } else { + return falseValue + } +} + +func OnSuccess(cond bool) string { + return OnCond(cond, "SUCCEEDED", "FAILED") +} + +func OnOk(cond bool) string { + return OnCond(cond, "OK", "KO") +} + +func RandBool() bool { + rand.Seed(time.Now().UnixNano()) + return rand.Intn(2) > 0 +} + +func S2Uint32(s string, defaultValue uint32) uint32 { + var res uint32 + v, err := strconv.ParseUint(s, 10, 32) + if err == nil { + res = uint32(v) + } else { + res = defaultValue + } + return res +} + +func S2Int32(s string, defaultValue int32) int32 { + var res int32 + v, err := strconv.ParseUint(s, 10, 32) + if err == nil { + res = int32(v) + } else { + res = defaultValue + } + return res +} + +func RemoveQuotes(s string) string { + l := len(s) + if l >= 2 { + if (s[0] == '"' && s[l-1] == '"') || (s[0] == '\'' && s[l-1] == '\'') { + s = s[1 : l-1] + } + } + return s +} + +// Credits: https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ +func ByteCountSI(b int64) string { + const unit = 1000 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", + float64(b)/float64(div), "kMGTPE"[exp]) +} + +// Credits: https://yourbasic.org/golang/formatting-byte-size-to-human-readable-format/ +func ByteCountIEC(b int64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +} diff --git a/os-util.go b/os-util.go new file mode 100644 index 0000000..53d020c --- /dev/null +++ b/os-util.go @@ -0,0 +1,169 @@ +// os-util.go +package utils + +import ( + "fmt" + "os" + "os/user" + "path" + "path/filepath" + "strings" +) + +func ExpandPath(sourcePath string) (expandedPath string, err error) { + if strings.HasPrefix(sourcePath, "~") { + var home, userName, remainder string + + slashPos := strings.IndexRune(sourcePath, '/') + if slashPos > 0 { + userName = sourcePath[1:slashPos] + remainder = sourcePath[slashPos:] + } else { + userName = sourcePath[1:] + } + + if len(userName) == 0 { + home, err = os.UserHomeDir() + if err != nil { + return + } + } else { + var userInfo *user.User + userInfo, err = user.Lookup(userName) + if err != nil { + return + } + home = userInfo.HomeDir + } + expandedPath = os.ExpandEnv(path.Join(home, remainder)) + } else { + expandedPath = os.ExpandEnv(sourcePath) + } + return +} + +func ExpandPathV(sourcePath string, maps ...map[string]string) (expandedPath string, err error) { + var normalizedPath string + if strings.HasPrefix(sourcePath, "~") { + var home, userName, remainder string + + slashPos := strings.IndexRune(sourcePath, '/') + if slashPos > 0 { + userName = sourcePath[1:slashPos] + remainder = sourcePath[slashPos:] + } else { + userName = sourcePath[1:] + } + + if len(userName) == 0 { + home, err = os.UserHomeDir() + if err != nil { + return + } + } else { + var userInfo *user.User + userInfo, err = user.Lookup(userName) + if err != nil { + return + } + home = userInfo.HomeDir + } + normalizedPath = path.Join(home, remainder) + } else { + normalizedPath = sourcePath + } + + expandedPath = os.Expand(normalizedPath, func(name string) (value string) { + var ok bool + for _, vars := range maps { + if value, ok = vars[name]; ok { + break + } + } + if !ok { + value = os.Getenv(name) + } + return + }) + return +} + +func ExpandRealPath(sourcePath string) (expandedRealPath string, err error) { + var expandedPath string + if expandedPath, err = ExpandPath(sourcePath); err == nil { + if expandedPath, err = filepath.Abs(expandedPath); err == nil { + expandedRealPath, err = filepath.EvalSymlinks(expandedPath) + } + } + return +} + +func GetProgramDirPath() (dir string, err error) { + var exe string + exe, err = os.Executable() + if err == nil { + exe, err = filepath.EvalSymlinks(exe) + if err == nil { + exe, err = filepath.Abs(exe) + if err == nil { + dir = filepath.Dir(exe) + } + } + } + return +} + +func GetProgramPath() (exec string, err error) { + var tmpExec string + tmpExec, err = os.Executable() + if err == nil { + tmpExec, err = filepath.EvalSymlinks(tmpExec) + if err == nil { + exec, err = filepath.Abs(tmpExec) + } + } + return +} + +func MakeParentDir(filePath string) (dir string, err error) { + dir = filepath.Dir(filePath) + if !IsDirectory(dir) { + if err1 := os.MkdirAll(dir, 0755); err1 != nil { + err = fmt.Errorf("Can't make parent path of %#v: %v", filePath, err1) + } + } + return +} + +func ExpandFilePathAndMakeParent(specPath string) (expandedPath, expandedParent string, err error) { + expandedPath, err = ExpandPath(specPath) + if err == nil { + expandedParent, err = MakeParentDir(expandedPath) + } + return +} + +func ExpandFilePathAndMakeParentV(specPath string, maps ...map[string]string) (expandedPath, expandedParent string, err error) { + expandedPath, err = ExpandPathV(specPath, maps...) + if err == nil { + expandedParent, err = MakeParentDir(expandedPath) + } + return +} + +func MakeDir(dirPath string) (err error) { + if !IsDirectory(dirPath) { + if err1 := os.MkdirAll(dirPath, 0755); err1 != nil { + err = fmt.Errorf("Can't make directory path %#v: %v", dirPath, err1) + } + } + return +} + +func ExpandDirPathAndMakeParent(specPath string) (expandedPath string, err error) { + expandedPath, err = ExpandPath(specPath) + if err == nil { + err = MakeDir(expandedPath) + } + return +} diff --git a/string.go b/string.go new file mode 100644 index 0000000..e5f1889 --- /dev/null +++ b/string.go @@ -0,0 +1,222 @@ +// string +package utils + +import ( + "fmt" + "strings" +) + +func PadStringRight(s string, pad string, size int, suffix string) (paddedString string) { + if len(pad) == 0 { + pad = " " + } + space := size - len(s) + if space > 0 { + paddedString = s + strings.Repeat(pad, space) + } else { + paddedString = s + } + paddedString += ": %v" + return +} + +func UnescapeSpecialChars(source string, charSet, specialChars string) string { + var buf strings.Builder + var escape bool + for _, c := range source { + if c == '\\' { + if escape { + buf.WriteByte('\\') + escape = false + } else { + escape = true + } + } else { + if escape { + if index := strings.IndexRune(charSet, c); index >= 0 { + if index < len(specialChars) { + buf.WriteByte(specialChars[index]) + } + } + escape = false + } else { + buf.WriteRune(c) + } + } + } + return buf.String() +} + +func UnescapeBlanks(source string) string { + return UnescapeSpecialChars(source, "nrt", "\n\r\t") +} + +func EscapeSpecialChars(source string, charSet string) string { + var buf strings.Builder + for _, c := range source { + if strings.IndexRune(charSet, c) >= 0 { + buf.WriteByte(92) // 92 = backslash + } + buf.WriteRune(c) + } + return buf.String() +} + +func EscapeShellSpecialChars(source string) string { + return EscapeSpecialChars(source, "!`$\\()#~&*;") +} + +func SplitPair(source, sep string) (left, right string) { + parts := strings.SplitN(source, sep, 2) + left = strings.TrimSpace(parts[0]) + if len(parts) > 1 { + right = strings.TrimSpace(parts[1]) + } + return +} + +func StartsWith(s string, aliases ...string) bool { + startsWith := false + for _, alias := range aliases { + if startsWith = strings.HasPrefix(alias, s); startsWith { + break + } + } + return startsWith +} + +func JoinStringMap(m map[string]string, kvSep, itemSep string) string { + var sb strings.Builder + for key, value := range m { + if sb.Len() > 0 { + sb.WriteString(itemSep) + } + sb.WriteString(key) + sb.WriteString(kvSep) + sb.WriteString(value) + } + return sb.String() +} + +func JoinMap(m map[string]any, kvSep, itemSep string) string { + var sb strings.Builder + for key, value := range m { + if sb.Len() > 0 { + sb.WriteString(itemSep) + } + sb.WriteString(key) + sb.WriteString(kvSep) + if s, ok := value.(fmt.Stringer); ok { + sb.WriteString(s.String()) + } else { + sb.WriteString(fmt.Sprintf("%v", value)) + } + } + return sb.String() +} + +func SplitByRune(s string, sep rune, maxParts int) (parts []string) { + return ScopedSplitByRune(s, sep, `'"`, `'"`, maxParts) + // var capacity int = 0 + // var sb strings.Builder + // if maxParts < 0 { + // for _, b := range s { + // if b == sep { + // capacity++ + // } + // } + // capacity++ + // } else { + // capacity = max(1, maxParts) + // } + // parts = make([]string, 0, capacity) + + // count := 0 + // quote := rune(0) + // for i, r := range s { + // if r == sep && quote == rune(0) { + // if len(parts) < capacity { + // count++ + // if count == maxParts { + // sb.WriteString(s[i:]) + // } + // parts = append(parts, sb.String()) + // sb.Reset() + // if count == maxParts { + // break + // } + // } + // } else if r == '\'' || r == '"' { + // if quote == r { + // quote = rune(0) + // } else { + // quote = r + // } + // sb.WriteRune(r) + // } else { + // sb.WriteRune(r) + // } + // } + // if maxParts < 0 { + // parts = append(parts, sb.String()) + // } else { + // for ; count < maxParts; count++ { + // parts = append(parts, sb.String()) + // sb.Reset() + // } + // } + // return parts +} + +func ScopedSplitByRune(s string, sep rune, scopeOpeners, scopeClosers string, maxParts int) (parts []string) { + var capacity int = 0 + var sb strings.Builder + + if len(scopeClosers) != len(scopeOpeners) { + panic("scope openers and closers must have the same length") + } + + if maxParts < 0 { + capacity = strings.Count(s, string(sep)) + 1 + } else { + capacity = max(1, maxParts) + } + + parts = make([]string, 0, capacity) + + count := 0 + scopeIndex := -1 + for i, r := range s { + if r == sep && scopeIndex < 0 { + if len(parts) < capacity { + count++ + if count == maxParts { + sb.WriteString(s[i:]) + } + parts = append(parts, sb.String()) + sb.Reset() + if count == maxParts { + break + } + } + } else { + if scopeIndex < 0 { + if quoteIndex := strings.IndexRune(scopeOpeners, r); quoteIndex >= 0 { + scopeIndex = quoteIndex + } + } else if quoteIndex := strings.IndexRune(scopeClosers, r); quoteIndex >= 0 { + scopeIndex = -1 + } + sb.WriteRune(r) + } + } + if maxParts < 0 { + parts = append(parts, sb.String()) + } else { + for ; count < maxParts; count++ { + parts = append(parts, sb.String()) + sb.Reset() + } + } + return parts +} diff --git a/text-util.go b/text-util.go new file mode 100644 index 0000000..96e76c9 --- /dev/null +++ b/text-util.go @@ -0,0 +1,283 @@ +// text-util.go +package utils + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "os/user" + "sort" + "strings" + "time" +) + +type TextTemplate struct { + varMap map[string]string + UserName string + Uid string + ProgramDir string + HomeDir string + env map[string]string +} + +func NewTextTemplate(args ...string) (instance *TextTemplate) { + instance = &TextTemplate{} + user, err := user.Current() + if err == nil { + instance.UserName = user.Username + instance.HomeDir = user.HomeDir + instance.Uid = user.Uid + } + + instance.ProgramDir, _ = GetProgramDirPath() + instance.varMap = make(map[string]string, 5+len(args)) + instance.initVarMap() + + for _, arg := range args { + if len(arg) == 0 { + break + } + parts := strings.SplitN(arg, ":", 2) + if len(parts) == 2 { + instance.varMap[parts[0]] = parts[1] + } else { + instance.varMap[parts[0]] = "" + } + } + + instance.env = make(map[string]string) + for _, v := range os.Environ() { + parts := strings.SplitN(v, "=", 2) + instance.env[parts[0]] = parts[1] + } + return +} + +func (self *TextTemplate) initVarMap() { + self.varMap["dt"] = "Current timestamp as YYYY-MM-DD_HH-MM-SS" + self.varMap["ymd"] = "Current date as YYYY-MM-DD" + self.varMap["hm"] = "Current time as HH-MM" + self.varMap["h"] = "Current time as HH" + self.varMap["hms"] = "Current time as HH-MM-SS" + self.varMap["month"] = "Month name" + self.varMap["month-num"] = "Month as number (mm)" + self.varMap["week-day"] = "Week day name" + self.varMap["week-day-num"] = "Week day as number" + self.varMap["yesterday"] = "Yesterday date" + self.varMap["uid"] = self.Uid + self.varMap["username"] = self.UserName + self.varMap["home"] = self.HomeDir + self.varMap["progdir"] = self.ProgramDir + self.varMap["hostname"], _ = os.Hostname() +} + +func (self *TextTemplate) AddVar(name, value string) { + self.varMap[name] = value +} + +// func MakeVarMap() map[string]string { +// return map[string]string{ +// "ymd": "Current date as YYYY-MM-DD", +// "hms": "Current time as HH-MM-SS", +// // "host": "Hostname", +// "uid": "UID", +// "username": "Username", +// "home": "Home directory", +// } +// } + +func (self *TextTemplate) updateVarMap() { + var now = time.Now() + + self.varMap["ymd"] = fmt.Sprintf("%04d-%02d-%02d", now.Year(), now.Month(), now.Day()) + self.varMap["hms"] = fmt.Sprintf("%02d-%02d-%02d", now.Hour(), now.Minute(), now.Second()) + self.varMap["hm"] = fmt.Sprintf("%02d-%02d", now.Hour(), now.Minute()) + self.varMap["h"] = fmt.Sprintf("%02d", now.Hour()) + self.varMap["month"] = now.Format("January") + self.varMap["month-num"] = fmt.Sprintf("%02d", now.Month()) + self.varMap["week-day-num"] = fmt.Sprintf("%d", 1+now.Weekday()) + self.varMap["week-day"] = now.Format("Monday") + self.varMap["dt"] = self.varMap["ymd"] + "_" + self.varMap["hms"] + + yday := now.AddDate(0, 0, -1) + self.varMap["yesterday"] = fmt.Sprintf("%04d-%02d-%02d", yday.Year(), yday.Month(), yday.Day()) + // self.varMap["uid"] = strconv.Itoa(int(self.Uid)) + // self.varMap["uid"] = self.Uid + // self.varMap["username"] = self.UserName + // // self.varMap["host"] = self.Hostname + // self.varMap["home"] = self.HomeDir +} + +func (self *TextTemplate) Expand(template string) string { + + self.updateVarMap() + + result := os.Expand(template, func(name string) string { + value, ok := self.varMap[name] + if ok { + return value + } else { + return "" + } + }) + + return result +} + +func (self *TextTemplate) ExpandEnv(template string) (result string) { + if len(template) > 0 { + self.updateVarMap() + result = os.Expand(template, func(name string) string { + value, ok := self.varMap[name] + if ok { + return value + } else { + return "${" + name + "}" + } + }) + + // for k, v := range self.varMap { + // self.env[k] = v + // } + + result = os.ExpandEnv(result) + // result = os.Expand(result, func(name string) string { + // value, ok := self.env[name] + // if ok { + // return value + // } else { + // return "" + // } + // }) + } + return result +} + +func (self *TextTemplate) Value(key string) (value string, ok bool) { + value, ok = self.varMap[key] + return +} + +func (self *TextTemplate) String() string { + var sb strings.Builder + + keys := make([]string, 0, len(self.varMap)) + for k := range self.varMap { + keys = append(keys, k) + } + sort.Strings(keys) + + for _, k := range keys { + sb.WriteString(fmt.Sprintf("%s=%#v\n", k, self.varMap[k])) + } + // for k, v := range self.varMap { + // sb.WriteString(fmt.Sprintf("%s=%#v\n", k, v)) + // } + return sb.String() +} + +func ParseParamPairsFile(sourceFile string, separator string, hook func(line int, name, value string) (err error)) (err error) { + var source io.Reader + if source, err = os.Open(sourceFile); err == nil { + err = ParseParamPairs(source, separator, hook) + } + return +} + +func ParseParamPairs(source io.Reader, separator string, hook func(line int, name, value string) (err error)) (err error) { + var content, sep, name, value []byte + var sb strings.Builder + + if content, err = ioutil.ReadAll(source); err != nil { + return + } + + if len(separator) == 0 { + sep = []byte{' '} + } else { + sep = []byte(separator) + } + for lineCount, rawLine := range bytes.Split(content, []byte{'\n'}) { + rawLine = bytes.TrimSpace(rawLine) + if len(rawLine) == 0 || rawLine[0] == '#' { + continue + } + parts := bytes.SplitN(rawLine, sep, 2) + name = bytes.TrimSpace(parts[0]) + if len(parts) > 1 { + value = bytes.TrimSpace(parts[1]) + } else { + value = []byte{} + } + quoteOn := false + escMode := false + sb.Reset() + for _, c := range value { + switch c { + case '"': + if escMode { + sb.WriteByte(c) + escMode = false + } else { + quoteOn = !quoteOn + } + case '\\': + if escMode { + sb.WriteByte(c) + escMode = false + } else { + escMode = true + } + case 'n': + if escMode { + sb.WriteByte('\n') + escMode = false + } else { + sb.WriteByte(c) + } + case 'r': + if escMode { + sb.WriteByte('\r') + escMode = false + } else { + sb.WriteByte(c) + } + case 't': + if escMode { + sb.WriteByte('\t') + escMode = false + } else { + sb.WriteByte(c) + } + case '#': + if quoteOn { + sb.WriteByte(c) + } else if escMode { + sb.WriteByte(c) + escMode = false + } else { + goto endloop + } + default: + if escMode { + escMode = false + } + sb.WriteByte(c) + } + } + endloop: + if err == nil && quoteOn { + err = errors.New("unbalanced quotes") + break + } + if err == nil { + if err = hook(lineCount, string(name), sb.String()); err != nil { + break + } + } + } + return +} diff --git a/time.go b/time.go new file mode 100644 index 0000000..70822be --- /dev/null +++ b/time.go @@ -0,0 +1,69 @@ +package utils + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +func StringTimestamp2GoTime(s string) (ts time.Time) { + secs, err := strconv.ParseInt(s, 10, 64) + if err == nil { + ts = time.Unix(secs, 0) + } + return +} + +func StringSeconds2GoDuration(s string) (secs time.Duration) { + if len(s) > 0 { + if s[len(s)-1] != 's' { + s += "s" + } + secs, _ = time.ParseDuration(s) + } + return +} + +// Convert the spec string to a number of seconds. +// spec is a number, optionally followed by a time unit among 's'(seconds), +// 'm'(minutes), 'h'(hours), and 'd'(days) +// If the time unit is omitted, defaultUnit is used. +// Empty spec returns 0 seconds. +func ParseTimeInterval(spec string, defaultUnit byte) (interval int64, err error) { + var u byte + var intVal int + + spec = strings.TrimSpace(spec) + if len(spec) == 0 { + //err = errors.New(iface.ErrEmptyValue) + return + } + spec = strings.ToLower(spec) + u = spec[len(spec)-1] + if u >= '0' && u <= '9' { + u = 'm' + } else { + spec = spec[0 : len(spec)-1] + } + if intVal, err = strconv.Atoi(spec); err != nil { + return + // } else if intVal <= 0 { + // err = errors.New(iface.ErrMustBePositive) + // return + } + + switch u { + case 's': + interval = int64(intVal) + case 'm': + interval = int64(intVal) * 60 + case 'h': + interval = int64(intVal) * 3600 + case 'd': + interval = int64(intVal) * 86400 + default: + err = fmt.Errorf("invalid time unit %c", u) + } + return +} diff --git a/tty-util.go b/tty-util.go new file mode 100644 index 0000000..a7eff9e --- /dev/null +++ b/tty-util.go @@ -0,0 +1,204 @@ +package utils + +import ( + "os" +) + +const STDIN uint = 0 +const STDOUT uint = 1 +const STDERR uint = 2 + +const ( + NONE = iota + BLACK + RED + GREEN + BLUE + BROWN + // YELLOW + CYAN + MAGENTA + WHITE +) + +type TTYContext struct { + isTTY [3]bool +} + +func (self *TTYContext) Init() { + for i, _ := range self.isTTY { + self.isTTY[i] = StreamIsTerminal(getStream(uint(i))) + } +} + +func (self *TTYContext) IsTTY(fd uint) bool { + if fd == STDERR || fd == STDOUT { + return self.isTTY[fd] + } + return false +} + +func (self *TTYContext) Clean() { + for i, _ := range self.isTTY { + self.isTTY[i] = false + } +} + +func (self *TTYContext) Bold(fd uint) string { + if self.isTTY[fd] { + return "\x1b[1m" + } + return "" +} + +func (self *TTYContext) BoldOff(fd uint) string { + if self.isTTY[fd] { + return "\x1b[22m" + } + return "" +} + +func (self *TTYContext) Underline(fd uint) string { + if self.isTTY[fd] { + return "\x1b[4m" + } + return "" +} + +func (self *TTYContext) UnderlineOff(fd uint) string { + if self.isTTY[fd] { + return "\x1b[24m" + } + return "" +} + +func (self *TTYContext) Italic(fd uint) string { + if self.isTTY[fd] { + return "\x1b[3m" + } + return "" +} + +func (self *TTYContext) ItalicOff(fd uint) string { + if self.isTTY[fd] { + return "\x1b[23m" + } + return "" +} + +func (self *TTYContext) BlackFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[30m" + } + return "" +} + +func (self *TTYContext) RedFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[31m" + } + return "" +} + +func (self *TTYContext) GreenFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[32m" + } + return "" +} + +func (self *TTYContext) BrownFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[33m" + } + return "" +} + +func (self *TTYContext) BlueFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[34m" + } + return "" +} + +func (self *TTYContext) MagentaFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[35m" + } + return "" +} + +func (self *TTYContext) CyanFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[36m" + } + return "" +} + +func (self *TTYContext) WhiteFg(fd uint) string { + if self.isTTY[fd] { + return "\x1b[37m" + } + return "" +} + +func (self *TTYContext) FgColor(fd uint, n uint) string { + var color string + switch n { + case BLACK: + color = self.BlackFg(fd) + case WHITE: + color = self.WhiteFg(fd) + case RED: + color = self.RedFg(fd) + case GREEN: + color = self.GreenFg(fd) + case BLUE: + color = self.BlueFg(fd) + case BROWN: + color = self.BrownFg(fd) + case CYAN: + color = self.CyanFg(fd) + case MAGENTA: + color = self.MagentaFg(fd) + } + return color +} + +// func (self *TTYContext) BgColor(fd uint, n int) string { +// var color string +// switch n { +// case BLACK: +// color = self.BlackBg(fd) +// case WHITE: +// color = self.WhiteBg(fd) +// case RED: +// color = self.RedBg(fd) +// case GREEN: +// color = self.GreenBg(fd) +// case CYAN: +// color = self.CyanBg(fd) +// case MAGENTA: +// color = self.MagentaBg(fd) +// } +// return color +// } + +func (self *TTYContext) Reset(fd uint) string { + if StreamIsTerminal(getStream(fd)) { + return "\x1b[0m" + } + return "" +} + +func getStream(fd uint) *os.File { + var s *os.File = nil + + switch fd { + case STDOUT: + s = os.Stdout + case STDERR: + s = os.Stderr + } + return s +}