github.com/mithrandie/csvq@v1.18.1/lib/option/flags.go (about)

     1  package option
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"runtime"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/mithrandie/go-text"
    14  	txjson "github.com/mithrandie/go-text/json"
    15  )
    16  
    17  const (
    18  	VariableSign            = "@"
    19  	FlagSign                = "@@"
    20  	EnvironmentVariableSign = "@%"
    21  	RuntimeInformationSign  = "@#"
    22  )
    23  const DelimitAutomatically = "SPACES"
    24  
    25  const (
    26  	RepositoryFlag               = "REPOSITORY"
    27  	TimezoneFlag                 = "TIMEZONE"
    28  	DatetimeFormatFlag           = "DATETIME_FORMAT"
    29  	AnsiQuotesFlag               = "ANSI_QUOTES"
    30  	StrictEqualFlag              = "STRICT_EQUAL"
    31  	WaitTimeoutFlag              = "WAIT_TIMEOUT"
    32  	ImportFormatFlag             = "IMPORT_FORMAT"
    33  	DelimiterFlag                = "DELIMITER"
    34  	AllowUnevenFieldsFlag        = "ALLOW_UNEVEN_FIELDS"
    35  	DelimiterPositionsFlag       = "DELIMITER_POSITIONS"
    36  	JsonQueryFlag                = "JSON_QUERY"
    37  	EncodingFlag                 = "ENCODING"
    38  	NoHeaderFlag                 = "NO_HEADER"
    39  	WithoutNullFlag              = "WITHOUT_NULL"
    40  	StripEndingLineBreakFlag     = "STRIP_ENDING_LINE_BREAK"
    41  	FormatFlag                   = "FORMAT"
    42  	ExportEncodingFlag           = "WRITE_ENCODING"
    43  	ExportDelimiterFlag          = "WRITE_DELIMITER"
    44  	ExportDelimiterPositionsFlag = "WRITE_DELIMITER_POSITIONS"
    45  	WithoutHeaderFlag            = "WITHOUT_HEADER"
    46  	LineBreakFlag                = "LINE_BREAK"
    47  	EncloseAllFlag               = "ENCLOSE_ALL"
    48  	JsonEscapeFlag               = "JSON_ESCAPE"
    49  	PrettyPrintFlag              = "PRETTY_PRINT"
    50  	ScientificNotationFlag       = "SCIENTIFIC_NOTATION"
    51  	EastAsianEncodingFlag        = "EAST_ASIAN_ENCODING"
    52  	CountDiacriticalSignFlag     = "COUNT_DIACRITICAL_SIGN"
    53  	CountFormatCodeFlag          = "COUNT_FORMAT_CODE"
    54  	ColorFlag                    = "COLOR"
    55  	QuietFlag                    = "QUIET"
    56  	LimitRecursion               = "LIMIT_RECURSION"
    57  	CPUFlag                      = "CPU"
    58  	StatsFlag                    = "STATS"
    59  )
    60  
    61  var FlagList = []string{
    62  	RepositoryFlag,
    63  	TimezoneFlag,
    64  	DatetimeFormatFlag,
    65  	AnsiQuotesFlag,
    66  	StrictEqualFlag,
    67  	WaitTimeoutFlag,
    68  	ImportFormatFlag,
    69  	DelimiterFlag,
    70  	AllowUnevenFieldsFlag,
    71  	DelimiterPositionsFlag,
    72  	JsonQueryFlag,
    73  	EncodingFlag,
    74  	NoHeaderFlag,
    75  	WithoutNullFlag,
    76  	StripEndingLineBreakFlag,
    77  	FormatFlag,
    78  	ExportEncodingFlag,
    79  	ExportDelimiterFlag,
    80  	ExportDelimiterPositionsFlag,
    81  	WithoutHeaderFlag,
    82  	LineBreakFlag,
    83  	EncloseAllFlag,
    84  	JsonEscapeFlag,
    85  	PrettyPrintFlag,
    86  	ScientificNotationFlag,
    87  	EastAsianEncodingFlag,
    88  	CountDiacriticalSignFlag,
    89  	CountFormatCodeFlag,
    90  	ColorFlag,
    91  	QuietFlag,
    92  	LimitRecursion,
    93  	CPUFlag,
    94  	StatsFlag,
    95  }
    96  
    97  type Format int
    98  
    99  const (
   100  	AutoSelect Format = -1 + iota
   101  	CSV
   102  	TSV
   103  	FIXED
   104  	JSON
   105  	JSONL
   106  	LTSV
   107  	GFM
   108  	ORG
   109  	BOX
   110  	TEXT
   111  )
   112  
   113  var FormatLiteral = map[Format]string{
   114  	CSV:   "CSV",
   115  	TSV:   "TSV",
   116  	FIXED: "FIXED",
   117  	JSON:  "JSON",
   118  	JSONL: "JSONL",
   119  	LTSV:  "LTSV",
   120  	GFM:   "GFM",
   121  	ORG:   "ORG",
   122  	BOX:   "BOX",
   123  	TEXT:  "TEXT",
   124  }
   125  
   126  func (f Format) String() string {
   127  	return FormatLiteral[f]
   128  }
   129  
   130  var ImportFormats = []Format{
   131  	CSV,
   132  	TSV,
   133  	FIXED,
   134  	JSON,
   135  	JSONL,
   136  	LTSV,
   137  }
   138  
   139  var JsonEscapeTypeLiteral = map[txjson.EscapeType]string{
   140  	txjson.Backslash:        "BACKSLASH",
   141  	txjson.HexDigits:        "HEX",
   142  	txjson.AllWithHexDigits: "HEXALL",
   143  }
   144  
   145  func JsonEscapeTypeToString(escapeType txjson.EscapeType) string {
   146  	return JsonEscapeTypeLiteral[escapeType]
   147  }
   148  
   149  const (
   150  	CsvExt      = ".csv"
   151  	TsvExt      = ".tsv"
   152  	JsonExt     = ".json"
   153  	JsonlExt    = ".jsonl"
   154  	LtsvExt     = ".ltsv"
   155  	GfmExt      = ".md"
   156  	OrgExt      = ".org"
   157  	SqlExt      = ".sql"
   158  	CsvqProcExt = ".cql"
   159  	TextExt     = ".txt"
   160  )
   161  
   162  type ImportOptions struct {
   163  	Format             Format
   164  	Delimiter          rune
   165  	AllowUnevenFields  bool
   166  	DelimiterPositions []int
   167  	SingleLine         bool
   168  	JsonQuery          string
   169  	Encoding           text.Encoding
   170  	NoHeader           bool
   171  	WithoutNull        bool
   172  }
   173  
   174  func (ops ImportOptions) Copy() ImportOptions {
   175  	var dp []int
   176  	if ops.DelimiterPositions != nil {
   177  		dp = make([]int, len(ops.DelimiterPositions))
   178  		copy(dp, ops.DelimiterPositions)
   179  	}
   180  
   181  	ret := ops
   182  	ret.DelimiterPositions = dp
   183  	return ret
   184  }
   185  
   186  func NewImportOptions() ImportOptions {
   187  	return ImportOptions{
   188  		Format:             CSV,
   189  		Delimiter:          ',',
   190  		AllowUnevenFields:  false,
   191  		DelimiterPositions: nil,
   192  		SingleLine:         false,
   193  		JsonQuery:          "",
   194  		Encoding:           text.AUTO,
   195  		NoHeader:           false,
   196  		WithoutNull:        false,
   197  	}
   198  }
   199  
   200  type ExportOptions struct {
   201  	StripEndingLineBreak bool
   202  	Format               Format
   203  	Encoding             text.Encoding
   204  	Delimiter            rune
   205  	DelimiterPositions   []int
   206  	SingleLine           bool
   207  	WithoutHeader        bool
   208  	LineBreak            text.LineBreak
   209  	EncloseAll           bool
   210  	JsonEscape           txjson.EscapeType
   211  	PrettyPrint          bool
   212  	ScientificNotation   bool
   213  
   214  	// For Calculation of String Width
   215  	EastAsianEncoding    bool
   216  	CountDiacriticalSign bool
   217  	CountFormatCode      bool
   218  
   219  	Color bool
   220  }
   221  
   222  func (ops ExportOptions) Copy() ExportOptions {
   223  	var dp []int
   224  	if ops.DelimiterPositions != nil {
   225  		dp = make([]int, len(ops.DelimiterPositions))
   226  		copy(dp, ops.DelimiterPositions)
   227  	}
   228  
   229  	ret := ops
   230  	ret.DelimiterPositions = dp
   231  	return ret
   232  }
   233  
   234  func NewExportOptions() ExportOptions {
   235  	return ExportOptions{
   236  		StripEndingLineBreak: false,
   237  		Format:               TEXT,
   238  		Encoding:             text.UTF8,
   239  		Delimiter:            ',',
   240  		DelimiterPositions:   nil,
   241  		SingleLine:           false,
   242  		WithoutHeader:        false,
   243  		LineBreak:            text.LF,
   244  		EncloseAll:           false,
   245  		JsonEscape:           txjson.Backslash,
   246  		PrettyPrint:          false,
   247  		ScientificNotation:   false,
   248  		EastAsianEncoding:    false,
   249  		CountDiacriticalSign: false,
   250  		CountFormatCode:      false,
   251  		Color:                false,
   252  	}
   253  }
   254  
   255  type Flags struct {
   256  	// Common Settings
   257  	Repository     string
   258  	Location       string
   259  	DatetimeFormat []string
   260  	AnsiQuotes     bool
   261  	StrictEqual    bool
   262  
   263  	WaitTimeout float64
   264  
   265  	// For Import
   266  	ImportOptions ImportOptions
   267  
   268  	// For Export
   269  	ExportOptions ExportOptions
   270  
   271  	// System Use
   272  	Quiet          bool
   273  	LimitRecursion int64
   274  	CPU            int
   275  	Stats          bool
   276  
   277  	defaultTimeLocation *time.Location
   278  }
   279  
   280  func GetDefaultNumberOfCPU() int {
   281  	n := runtime.NumCPU() / 2
   282  	if n < 1 {
   283  		n = 1
   284  	}
   285  	return n
   286  }
   287  
   288  func NewFlags(env *Environment) (*Flags, error) {
   289  	var datetimeFormat []string
   290  	var location = "Local"
   291  	var AnsiQuotes = false
   292  
   293  	if env != nil {
   294  		datetimeFormat = make([]string, 0, len(env.DatetimeFormat))
   295  		for _, v := range env.DatetimeFormat {
   296  			datetimeFormat = AppendStrIfNotExist(datetimeFormat, v)
   297  		}
   298  
   299  		if env.Timezone != nil {
   300  			location = *env.Timezone
   301  		}
   302  
   303  		if env.AnsiQuotes != nil {
   304  			AnsiQuotes = *env.AnsiQuotes
   305  		}
   306  	} else {
   307  		datetimeFormat = make([]string, 0, 4)
   308  	}
   309  
   310  	defaultTimeLocation, err := GetLocation(location)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	return &Flags{
   316  		Repository:          "",
   317  		Location:            location,
   318  		DatetimeFormat:      datetimeFormat,
   319  		AnsiQuotes:          AnsiQuotes,
   320  		StrictEqual:         false,
   321  		WaitTimeout:         10,
   322  		ImportOptions:       NewImportOptions(),
   323  		ExportOptions:       NewExportOptions(),
   324  		Quiet:               false,
   325  		LimitRecursion:      1000,
   326  		CPU:                 GetDefaultNumberOfCPU(),
   327  		Stats:               false,
   328  		defaultTimeLocation: defaultTimeLocation,
   329  	}, nil
   330  }
   331  
   332  func (f *Flags) GetTimeLocation() *time.Location {
   333  	return f.defaultTimeLocation
   334  }
   335  
   336  func (f *Flags) SetRepository(s string) error {
   337  	if len(s) < 1 {
   338  		f.Repository = ""
   339  		return nil
   340  	}
   341  
   342  	path, err := filepath.Abs(s)
   343  	if err != nil {
   344  		path = s
   345  	}
   346  
   347  	stat, err := os.Stat(path)
   348  	if err != nil {
   349  		return errors.New("repository does not exist")
   350  	}
   351  	if !stat.IsDir() {
   352  		return errors.New("repository must be a directory path")
   353  	}
   354  
   355  	f.Repository = path
   356  	return nil
   357  }
   358  
   359  func (f *Flags) SetLocation(s string) error {
   360  	if len(s) < 1 || strings.EqualFold(s, "Local") {
   361  		s = "Local"
   362  	} else if strings.EqualFold(s, "UTC") {
   363  		s = "UTC"
   364  	}
   365  
   366  	l, err := GetLocation(s)
   367  	if err != nil {
   368  		return err
   369  	}
   370  
   371  	f.Location = s
   372  	f.defaultTimeLocation = l
   373  	return nil
   374  }
   375  
   376  func (f *Flags) SetDatetimeFormat(s string) {
   377  	if len(s) < 1 {
   378  		return
   379  	}
   380  
   381  	var formats []string
   382  	if err := json.Unmarshal([]byte(s), &formats); err == nil {
   383  		for _, v := range formats {
   384  			f.DatetimeFormat = AppendStrIfNotExist(f.DatetimeFormat, v)
   385  		}
   386  	} else {
   387  		f.DatetimeFormat = append(f.DatetimeFormat, s)
   388  	}
   389  }
   390  
   391  func (f *Flags) SetAnsiQuotes(b bool) {
   392  	f.AnsiQuotes = b
   393  }
   394  
   395  func (f *Flags) SetStrictEqual(b bool) {
   396  	f.StrictEqual = b
   397  }
   398  
   399  func (f *Flags) SetWaitTimeout(t float64) {
   400  	if t < 0 {
   401  		t = 0
   402  	}
   403  
   404  	f.WaitTimeout = t
   405  	return
   406  }
   407  
   408  func (f *Flags) SetImportFormat(s string) error {
   409  	fm, _, err := ParseFormat(s, f.ExportOptions.JsonEscape)
   410  	if err != nil {
   411  		return errors.New("import format must be one of CSV|TSV|FIXED|JSON|JSONL|LTSV")
   412  	}
   413  
   414  	switch fm {
   415  	case CSV, TSV, FIXED, JSON, JSONL, LTSV:
   416  		f.ImportOptions.Format = fm
   417  		return nil
   418  	}
   419  
   420  	return errors.New("import format must be one of CSV|TSV|FIXED|JSON|JSONL|LTSV")
   421  }
   422  
   423  func (f *Flags) SetDelimiter(s string) error {
   424  	if len(s) < 1 {
   425  		return nil
   426  	}
   427  
   428  	delimiter, err := ParseDelimiter(s)
   429  	if err != nil {
   430  		return err
   431  	}
   432  
   433  	f.ImportOptions.Delimiter = delimiter
   434  	return nil
   435  }
   436  
   437  func (f *Flags) SetAllowUnevenFields(b bool) {
   438  	f.ImportOptions.AllowUnevenFields = b
   439  }
   440  
   441  func (f *Flags) SetDelimiterPositions(s string) error {
   442  	if len(s) < 1 {
   443  		return nil
   444  	}
   445  	delimiterPositions, singleLine, err := ParseDelimiterPositions(s)
   446  	if err != nil {
   447  		return err
   448  	}
   449  
   450  	f.ImportOptions.DelimiterPositions = delimiterPositions
   451  	f.ImportOptions.SingleLine = singleLine
   452  	return nil
   453  }
   454  
   455  func (f *Flags) SetJsonQuery(s string) {
   456  	f.ImportOptions.JsonQuery = TrimSpace(s)
   457  }
   458  
   459  func (f *Flags) SetEncoding(s string) error {
   460  	if len(s) < 1 {
   461  		return nil
   462  	}
   463  
   464  	encoding, err := ParseEncoding(s)
   465  	if err != nil {
   466  		return err
   467  	}
   468  
   469  	f.ImportOptions.Encoding = encoding
   470  	return nil
   471  }
   472  
   473  func (f *Flags) SetNoHeader(b bool) {
   474  	f.ImportOptions.NoHeader = b
   475  }
   476  
   477  func (f *Flags) SetWithoutNull(b bool) {
   478  	f.ImportOptions.WithoutNull = b
   479  }
   480  
   481  func (f *Flags) SetFormat(s string, outfile string, canOutputToPipe bool) error {
   482  	if len(s) < 1 {
   483  		if len(outfile) < 1 {
   484  			if canOutputToPipe {
   485  				f.ExportOptions.Format = CSV
   486  			} else {
   487  				f.ExportOptions.Format = TEXT
   488  			}
   489  			return nil
   490  		}
   491  
   492  		switch strings.ToLower(filepath.Ext(outfile)) {
   493  		case CsvExt:
   494  			f.ExportOptions.Format = CSV
   495  		case TsvExt:
   496  			f.ExportOptions.Format = TSV
   497  		case JsonExt:
   498  			f.ExportOptions.Format = JSON
   499  		case JsonlExt:
   500  			f.ExportOptions.Format = JSONL
   501  		case LtsvExt:
   502  			f.ExportOptions.Format = LTSV
   503  		case GfmExt:
   504  			f.ExportOptions.Format = GFM
   505  		case OrgExt:
   506  			f.ExportOptions.Format = ORG
   507  		default:
   508  			f.ExportOptions.Format = TEXT
   509  		}
   510  		return nil
   511  	}
   512  
   513  	fm, escape, err := ParseFormat(s, f.ExportOptions.JsonEscape)
   514  	if err != nil {
   515  		return err
   516  	}
   517  
   518  	f.ExportOptions.Format = fm
   519  	f.ExportOptions.JsonEscape = escape
   520  	return nil
   521  }
   522  
   523  func (f *Flags) SetWriteEncoding(s string) error {
   524  	if len(s) < 1 {
   525  		return nil
   526  	}
   527  
   528  	encoding, err := ParseEncoding(s)
   529  	if err != nil || encoding == text.AUTO {
   530  		return errors.New("write-encoding must be one of UTF8|UTF8M|UTF16|UTF16BE|UTF16LE|UTF16BEM|UTF16LEM|SJIS")
   531  	}
   532  
   533  	f.ExportOptions.Encoding = encoding
   534  	return nil
   535  }
   536  
   537  func (f *Flags) SetWriteDelimiter(s string) error {
   538  	if len(s) < 1 {
   539  		return nil
   540  	}
   541  
   542  	delimiter, err := ParseDelimiter(s)
   543  	if err != nil {
   544  		return errors.New("write-delimiter must be one character")
   545  	}
   546  
   547  	f.ExportOptions.Delimiter = delimiter
   548  	return nil
   549  }
   550  
   551  func (f *Flags) SetWriteDelimiterPositions(s string) error {
   552  	if len(s) < 1 {
   553  		return nil
   554  	}
   555  	delimiterPositions, singleLine, err := ParseDelimiterPositions(s)
   556  	if err != nil {
   557  		return errors.New(fmt.Sprintf("write-delimiter-positions must be %q or a JSON array of integers", DelimitAutomatically))
   558  	}
   559  
   560  	f.ExportOptions.DelimiterPositions = delimiterPositions
   561  	f.ExportOptions.SingleLine = singleLine
   562  	return nil
   563  }
   564  
   565  func (f *Flags) SetWithoutHeader(b bool) {
   566  	f.ExportOptions.WithoutHeader = b
   567  }
   568  
   569  func (f *Flags) SetLineBreak(s string) error {
   570  	if len(s) < 1 {
   571  		return nil
   572  	}
   573  
   574  	lb, err := ParseLineBreak(s)
   575  	if err != nil {
   576  		return err
   577  	}
   578  
   579  	f.ExportOptions.LineBreak = lb
   580  	return nil
   581  }
   582  
   583  func (f *Flags) SetJsonEscape(s string) error {
   584  	var escape txjson.EscapeType
   585  	var err error
   586  
   587  	if escape, err = ParseJsonEscapeType(s); err != nil {
   588  		return err
   589  	}
   590  
   591  	f.ExportOptions.JsonEscape = escape
   592  	return nil
   593  }
   594  
   595  func (f *Flags) SetPrettyPrint(b bool) {
   596  	f.ExportOptions.PrettyPrint = b
   597  }
   598  
   599  func (f *Flags) SetScientificNotation(b bool) {
   600  	f.ExportOptions.ScientificNotation = b
   601  }
   602  
   603  func (f *Flags) SetStripEndingLineBreak(b bool) {
   604  	f.ExportOptions.StripEndingLineBreak = b
   605  }
   606  
   607  func (f *Flags) SetEncloseAll(b bool) {
   608  	f.ExportOptions.EncloseAll = b
   609  }
   610  
   611  func (f *Flags) SetColor(b bool) {
   612  	f.ExportOptions.Color = b
   613  }
   614  
   615  func (f *Flags) SetEastAsianEncoding(b bool) {
   616  	f.ExportOptions.EastAsianEncoding = b
   617  }
   618  
   619  func (f *Flags) SetCountDiacriticalSign(b bool) {
   620  	f.ExportOptions.CountDiacriticalSign = b
   621  }
   622  
   623  func (f *Flags) SetCountFormatCode(b bool) {
   624  	f.ExportOptions.CountFormatCode = b
   625  }
   626  
   627  func (f *Flags) SetQuiet(b bool) {
   628  	f.Quiet = b
   629  }
   630  
   631  func (f *Flags) SetLimitRecursion(i int64) {
   632  	if i < 0 {
   633  		i = -1
   634  	}
   635  	f.LimitRecursion = i
   636  }
   637  
   638  func (f *Flags) SetCPU(i int) {
   639  	if i < 1 {
   640  		i = 1
   641  	}
   642  
   643  	if runtime.NumCPU() < i {
   644  		i = runtime.NumCPU()
   645  	}
   646  
   647  	f.CPU = i
   648  }
   649  
   650  func (f *Flags) SetStats(b bool) {
   651  	f.Stats = b
   652  }