Initial QSfera import
This commit is contained in:
+311
@@ -0,0 +1,311 @@
|
||||
package z
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// SuperFlagHelp makes it really easy to generate command line `--help` output for a SuperFlag. For
|
||||
// example:
|
||||
//
|
||||
// const flagDefaults = `enabled=true; path=some/path;`
|
||||
//
|
||||
// var help string = z.NewSuperFlagHelp(flagDefaults).
|
||||
// Flag("enabled", "Turns on <something>.").
|
||||
// Flag("path", "The path to <something>.").
|
||||
// Flag("another", "Not present in defaults, but still included.").
|
||||
// String()
|
||||
//
|
||||
// The `help` string would then contain:
|
||||
//
|
||||
// enabled=true; Turns on <something>.
|
||||
// path=some/path; The path to <something>.
|
||||
// another=; Not present in defaults, but still included.
|
||||
//
|
||||
// All flags are sorted alphabetically for consistent `--help` output. Flags with default values are
|
||||
// placed at the top, and everything else goes under.
|
||||
type SuperFlagHelp struct {
|
||||
head string
|
||||
defaults *SuperFlag
|
||||
flags map[string]string
|
||||
}
|
||||
|
||||
func NewSuperFlagHelp(defaults string) *SuperFlagHelp {
|
||||
return &SuperFlagHelp{
|
||||
defaults: NewSuperFlag(defaults),
|
||||
flags: make(map[string]string, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SuperFlagHelp) Head(head string) *SuperFlagHelp {
|
||||
h.head = head
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *SuperFlagHelp) Flag(name, description string) *SuperFlagHelp {
|
||||
h.flags[name] = description
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *SuperFlagHelp) String() string {
|
||||
defaultLines := make([]string, 0)
|
||||
otherLines := make([]string, 0)
|
||||
for name, help := range h.flags {
|
||||
val, found := h.defaults.m[name]
|
||||
line := fmt.Sprintf(" %s=%s; %s\n", name, val, help)
|
||||
if found {
|
||||
defaultLines = append(defaultLines, line)
|
||||
} else {
|
||||
otherLines = append(otherLines, line)
|
||||
}
|
||||
}
|
||||
sort.Strings(defaultLines)
|
||||
sort.Strings(otherLines)
|
||||
dls := strings.Join(defaultLines, "")
|
||||
ols := strings.Join(otherLines, "")
|
||||
if len(h.defaults.m) == 0 && len(ols) == 0 {
|
||||
// remove last newline
|
||||
dls = dls[:len(dls)-1]
|
||||
}
|
||||
// remove last newline
|
||||
if len(h.defaults.m) == 0 && len(ols) > 1 {
|
||||
ols = ols[:len(ols)-1]
|
||||
}
|
||||
return h.head + "\n" + dls + ols
|
||||
}
|
||||
|
||||
func parseFlag(flag string) (map[string]string, error) {
|
||||
kvm := make(map[string]string)
|
||||
for _, kv := range strings.Split(flag, ";") {
|
||||
if strings.TrimSpace(kv) == "" {
|
||||
continue
|
||||
}
|
||||
// For a non-empty separator, 0 < len(splits) ≤ 2.
|
||||
splits := strings.SplitN(kv, "=", 2)
|
||||
k := strings.TrimSpace(splits[0])
|
||||
if len(splits) < 2 {
|
||||
return nil, fmt.Errorf("superflag: missing value for '%s' in flag: %s", k, flag)
|
||||
}
|
||||
k = strings.ToLower(k)
|
||||
k = strings.ReplaceAll(k, "_", "-")
|
||||
kvm[k] = strings.TrimSpace(splits[1])
|
||||
}
|
||||
return kvm, nil
|
||||
}
|
||||
|
||||
type SuperFlag struct {
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func NewSuperFlag(flag string) *SuperFlag {
|
||||
sf, err := newSuperFlagImpl(flag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return sf
|
||||
}
|
||||
|
||||
func newSuperFlagImpl(flag string) (*SuperFlag, error) {
|
||||
m, err := parseFlag(flag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SuperFlag{m}, nil
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) String() string {
|
||||
if sf == nil {
|
||||
return ""
|
||||
}
|
||||
kvs := make([]string, 0, len(sf.m))
|
||||
for k, v := range sf.m {
|
||||
kvs = append(kvs, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return strings.Join(kvs, "; ")
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) MergeAndCheckDefault(flag string) *SuperFlag {
|
||||
sf, err := sf.mergeAndCheckDefaultImpl(flag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return sf
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) mergeAndCheckDefaultImpl(flag string) (*SuperFlag, error) {
|
||||
if sf == nil {
|
||||
m, err := parseFlag(flag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SuperFlag{m}, nil
|
||||
}
|
||||
|
||||
src, err := parseFlag(flag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numKeys := len(sf.m)
|
||||
for k := range src {
|
||||
if _, ok := sf.m[k]; ok {
|
||||
numKeys--
|
||||
}
|
||||
}
|
||||
if numKeys != 0 {
|
||||
return nil, fmt.Errorf("superflag: found invalid options: %s.\nvalid options: %v", sf, flag)
|
||||
}
|
||||
for k, v := range src {
|
||||
if _, ok := sf.m[k]; !ok {
|
||||
sf.m[k] = v
|
||||
}
|
||||
}
|
||||
return sf, nil
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) Has(opt string) bool {
|
||||
val := sf.GetString(opt)
|
||||
return val != ""
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetDuration(opt string) time.Duration {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return time.Duration(0)
|
||||
}
|
||||
if strings.Contains(val, "d") {
|
||||
val = strings.Replace(val, "d", "", 1)
|
||||
days, err := strconv.ParseUint(val, 0, 64)
|
||||
if err != nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
return time.Hour * 24 * time.Duration(days)
|
||||
}
|
||||
d, err := time.ParseDuration(val)
|
||||
if err != nil {
|
||||
return time.Duration(0)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetBool(opt string) bool {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return false
|
||||
}
|
||||
b, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err,
|
||||
"Unable to parse %s as bool for key: %s. Options: %s\n",
|
||||
val, opt, sf)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetFloat64(opt string) float64 {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return 0
|
||||
}
|
||||
f, err := strconv.ParseFloat(val, 64)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err,
|
||||
"Unable to parse %s as float64 for key: %s. Options: %s\n",
|
||||
val, opt, sf)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetInt64(opt string) int64 {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return 0
|
||||
}
|
||||
i, err := strconv.ParseInt(val, 0, 64)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err,
|
||||
"Unable to parse %s as int64 for key: %s. Options: %s\n",
|
||||
val, opt, sf)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetUint64(opt string) uint64 {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return 0
|
||||
}
|
||||
u, err := strconv.ParseUint(val, 0, 64)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err,
|
||||
"Unable to parse %s as uint64 for key: %s. Options: %s\n",
|
||||
val, opt, sf)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetUint32(opt string) uint32 {
|
||||
val := sf.GetString(opt)
|
||||
if val == "" {
|
||||
return 0
|
||||
}
|
||||
u, err := strconv.ParseUint(val, 0, 32)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err,
|
||||
"Unable to parse %s as uint32 for key: %s. Options: %s\n",
|
||||
val, opt, sf)
|
||||
log.Fatalf("%+v", err)
|
||||
}
|
||||
return uint32(u)
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetString(opt string) string {
|
||||
if sf == nil {
|
||||
return ""
|
||||
}
|
||||
return sf.m[opt]
|
||||
}
|
||||
|
||||
func (sf *SuperFlag) GetPath(opt string) string {
|
||||
p := sf.GetString(opt)
|
||||
path, err := expandPath(p)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get path: %+v", err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// expandPath expands the paths containing ~ to /home/user. It also computes the absolute path
|
||||
// from the relative paths. For example: ~/abc/../cef will be transformed to /home/user/cef.
|
||||
func expandPath(path string) (string, error) {
|
||||
if len(path) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
if path[0] == '~' && (len(path) == 1 || os.IsPathSeparator(path[1])) {
|
||||
usr, err := user.Current()
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to get the home directory of the user")
|
||||
}
|
||||
path = filepath.Join(usr.HomeDir, path[1:])
|
||||
}
|
||||
|
||||
var err error
|
||||
path, err = filepath.Abs(path)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "Failed to generate absolute path")
|
||||
}
|
||||
return path, nil
|
||||
}
|
||||
Reference in New Issue
Block a user