-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56 from pinzolo/numeric
Numeric
- Loading branch information
Showing
13 changed files
with
788 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package main | ||
|
||
import ( | ||
"github.com/pinzolo/csvutil" | ||
) | ||
|
||
var cmdNumeric = &Command{ | ||
Run: runNumeric, | ||
UsageLine: "numeric [OPTIONS...] [FILE]", | ||
Short: "数値生成", | ||
Long: `DESCRIPTION | ||
指定した列にmin <= n < max となるランダムな数値を出力します。 | ||
ARGUMENTS | ||
FILE | ||
ソースとなる CSV ファイルのパスを指定します。 | ||
パスが指定されていない場合、標準入力が対象となりパイプでの使用ができます。 | ||
OPTIONS | ||
-w, --overwrite | ||
指定されたCSVファイルを実行結果で上書きします。 | ||
ファイルパスが渡されていない場合には無視されます。 | ||
-H, --no-header | ||
ソースとなるCSVの1行目をヘッダー列として扱いません。 | ||
-b, --backup | ||
処理が成功した場合に、指定されたCSVファイルをバックアップします。 | ||
--overwrite オプションと同時に使用されることを想定しているため、ファイルパスが渡されていない場合には無視されます。 | ||
-e, --encoding ENCODING | ||
ソースとなるCSVの文字エンコーディングを指定します。 | ||
このオプションが指定されていない場合、csvutil はUTF-8とみなして処理を行います。 | ||
UTF-8であった場合、BOMのあるなしは自動的に判別されます。 | ||
対応している値: | ||
sjis : Shift_JISとして扱います | ||
eucjp: EUC_JPとして扱います | ||
-oe, --output-encoding ENCODING | ||
出力するCSVの文字エンコーディングを指定します。 | ||
このオプションが指定されていない場合 --encoding オプションで指定されたエンコーディングとして出力します。 | ||
対応している値: | ||
utf8 : UTF-8として出力します(BOMは出力しません) | ||
utf8bom : UTF-8として出力します(BOMは出力します) | ||
sjis : Shift_JISとして出力します | ||
eucjp : EUC_JPとして出力します | ||
-c, --column COLUMN_SYMBOL | ||
対象となる列のシンボルを指定します。 | ||
列のシンボルとは列のインデックス(0開始)、もしくはヘッダーテキストです。 | ||
--no-header オプションが指定された場合、インデックスしか受け入れません。 | ||
-mx, --max NUMBER | ||
出力する数値の最大値を指定します。ただし、ここで指定した値は出力されません。 | ||
-mn, --min NUMBER | ||
出力する数値の最小値を指定します。 | ||
-d, --decimal | ||
出力する数値を小数として出力します。 | ||
-dd, --decimal-digit NUMBER | ||
出力する小数の有効桁数を指定します。値は正の整数でなければいけません。(初期値: 3) | ||
`, | ||
} | ||
|
||
type cmdNumericOption struct { | ||
csvutil.NumericOption | ||
Overwrite bool | ||
Backup bool | ||
} | ||
|
||
var numericOpt = cmdNumericOption{} | ||
|
||
func init() { | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Overwrite, "overwrite", false, "Overwrite to source.") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Overwrite, "w", false, "Overwrite to source.") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.NoHeader, "no-header", false, "Source file does not have header line.") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.NoHeader, "H", false, "Source file does not have header line.") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Backup, "backup", false, "Backup source file.") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Backup, "b", false, "Backup source file.") | ||
cmdNumeric.Flag.StringVar(&numericOpt.Encoding, "encoding", "utf8", "Encoding of source file") | ||
cmdNumeric.Flag.StringVar(&numericOpt.Encoding, "e", "utf8", "Encoding of source file") | ||
cmdNumeric.Flag.StringVar(&numericOpt.OutputEncoding, "output-encoding", "", "Encoding for output") | ||
cmdNumeric.Flag.StringVar(&numericOpt.OutputEncoding, "oe", "", "Encoding for output") | ||
cmdNumeric.Flag.StringVar(&numericOpt.Column, "column", "", "Target column symbol") | ||
cmdNumeric.Flag.StringVar(&numericOpt.Column, "c", "", "Target column symbol") | ||
cmdNumeric.Flag.IntVar(&numericOpt.Max, "max", 100, "Maximum value") | ||
cmdNumeric.Flag.IntVar(&numericOpt.Max, "mx", 100, "Maximum value") | ||
cmdNumeric.Flag.IntVar(&numericOpt.Min, "min", 0, "Minimum value") | ||
cmdNumeric.Flag.IntVar(&numericOpt.Min, "mn", 0, "Minmum value") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Decimal, "decimal", false, "Output decimal number") | ||
cmdNumeric.Flag.BoolVar(&numericOpt.Decimal, "d", false, "Output decimal number") | ||
cmdNumeric.Flag.IntVar(&numericOpt.DecimalDigit, "decimal-digit", 3, "Decimal digit number") | ||
cmdNumeric.Flag.IntVar(&numericOpt.DecimalDigit, "dd", 3, "Decimal digit number") | ||
} | ||
|
||
// runNumeric executes numeric command and return exit code. | ||
func runNumeric(args []string) int { | ||
success := false | ||
w, wf, r, rf, err := prepare(args, numericOpt.Overwrite) | ||
if wf != nil { | ||
defer wf(&success, numericOpt.Backup) | ||
} | ||
if rf != nil { | ||
defer rf() | ||
} | ||
if err != nil { | ||
return handleError(err) | ||
} | ||
|
||
err = csvutil.Numeric(r, w, numericOpt.NumericOption) | ||
if err != nil { | ||
return handleError(err) | ||
} | ||
|
||
success = true | ||
return 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
package main | ||
|
||
import ( | ||
"regexp" | ||
"testing" | ||
) | ||
|
||
func Test_runNumeric(t *testing.T) { | ||
numericOpt.Column = "名前" | ||
if c := runNumeric([]string{testFilePath("utf8.csv")}); c != 0 { | ||
t.Errorf("Invalid success exit code: %d", c) | ||
} | ||
numericOpt.Column = "" | ||
} | ||
|
||
func Test_runNumericOnNoFile(t *testing.T) { | ||
if c := runNumeric([]string{testFilePath("no-file.csv")}); c == 0 { | ||
t.Errorf("Invalid failed exit code: %d", c) | ||
} | ||
} | ||
|
||
func Test_runNumericOnFail(t *testing.T) { | ||
if c := runNumeric([]string{testFilePath("broken.csv")}); c == 0 { | ||
t.Errorf("Invalid failed exit code: %d", c) | ||
} | ||
} | ||
|
||
func Test_runNumericOnBackup(t *testing.T) { | ||
f, err := prepareWritingTest() | ||
defer f() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
numericOpt.Column = "名前" | ||
numericOpt.Overwrite = true | ||
numericOpt.Backup = true | ||
runNumeric([]string{tempFilePath()}) | ||
numericOpt.Backup = false | ||
numericOpt.Overwrite = false | ||
numericOpt.Column = "" | ||
if b, err := existsBackup(); err != nil || !b { | ||
t.Errorf("Failed backup") | ||
} | ||
} | ||
|
||
func Test_runNumericOnOverwrite(t *testing.T) { | ||
f, err := prepareWritingTest() | ||
defer f() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
numericOpt.Column = "名前" | ||
numericOpt.Overwrite = true | ||
runNumeric([]string{tempFilePath()}) | ||
numericOpt.Overwrite = false | ||
numericOpt.Column = "" | ||
c, err := overwriteContent() | ||
if err != nil { | ||
t.Error(err) | ||
} | ||
if len(c[0]) != 2 { | ||
t.Errorf("Overwrite failed. got %+v", c) | ||
} | ||
if c[0][0] != "名前" || c[0][1] != "個数" { | ||
t.Errorf("Overwrite failed. got %+v", c) | ||
} | ||
pat, err := regexp.Compile("^\\d+$") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if !pat.MatchString(c[1][0]) || c[1][1] != "1" { | ||
t.Errorf("Overwrite failed. got %+v", c) | ||
} | ||
if !pat.MatchString(c[2][0]) || c[2][1] != "2" { | ||
t.Errorf("Overwrite failed. got %+v", c) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
package csvutil | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"math" | ||
"math/rand" | ||
"strconv" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
// NumericOption is option holder for Numeric. | ||
type NumericOption struct { | ||
// Source file does not have header line. (default false) | ||
NoHeader bool | ||
// Encoding of source file. (default utf8) | ||
Encoding string | ||
// Encoding for output. | ||
OutputEncoding string | ||
// Target column symbol. | ||
Column string | ||
// Max value | ||
Max int | ||
// Min value | ||
Min int | ||
// Output decimal instead of integer | ||
Decimal bool | ||
// Digit of decimal | ||
DecimalDigit int | ||
} | ||
|
||
func (o NumericOption) validate() error { | ||
if o.Column == "" { | ||
return errors.New("no column") | ||
} | ||
if o.NoHeader { | ||
if !isDigit(o.Column) { | ||
return errors.New("not number column symbol") | ||
|
||
} | ||
} | ||
if o.Max <= o.Min { | ||
return errors.New("max should be greater than min") | ||
} | ||
if o.Decimal && o.DecimalDigit <= 0 { | ||
return errors.New("decimal digit is not positive") | ||
} | ||
return nil | ||
} | ||
|
||
func (o NumericOption) outputEncoding() string { | ||
if o.OutputEncoding != "" { | ||
return o.OutputEncoding | ||
} | ||
return o.Encoding | ||
} | ||
|
||
// Numeric overwrite value of given column by random numbers. | ||
func Numeric(r io.Reader, w io.Writer, o NumericOption) error { | ||
if err := o.validate(); err != nil { | ||
return errors.Wrap(err, "invalid option") | ||
} | ||
|
||
cr, bom := reader(r, o.Encoding) | ||
cw := writer(w, bom, o.outputEncoding()) | ||
defer cw.Flush() | ||
|
||
var col *column | ||
csvp := NewCSVProcessor(cr, cw) | ||
if o.NoHeader { | ||
csvp.SetPreBodyRead(func() error { | ||
col = newColumnWithIndex(o.Column, nil) | ||
return col.err | ||
}) | ||
} else { | ||
csvp.SetHeaderHanlder(func(hdr []string) ([]string, error) { | ||
col = newColumnWithIndex(o.Column, hdr) | ||
return hdr, col.err | ||
}) | ||
} | ||
csvp.SetRecordHandler(func(rec []string) ([]string, error) { | ||
rec[col.index] = fakeNumeric(o) | ||
return rec, nil | ||
}) | ||
|
||
return csvp.Process() | ||
} | ||
|
||
func fakeNumeric(o NumericOption) string { | ||
if o.Decimal { | ||
return fakeDecimal(o) | ||
} | ||
return fakeInteger(o) | ||
} | ||
|
||
func fakeDecimal(o NumericOption) string { | ||
coefficient := int(math.Pow10(o.DecimalDigit)) | ||
lim := (o.Max - o.Min) * coefficient | ||
n := rand.Intn(lim) + (o.Min * coefficient) | ||
nega := false | ||
if n < 0 { | ||
nega = true | ||
n = n * -1 | ||
} | ||
s := fmt.Sprintf("%0"+strconv.Itoa((o.DecimalDigit+1))+"d", n) | ||
l := len(s) | ||
s = s[:l-o.DecimalDigit] + "." + s[l-o.DecimalDigit:] | ||
if nega { | ||
return "-" + s | ||
} | ||
return s | ||
} | ||
|
||
func fakeInteger(o NumericOption) string { | ||
lim := o.Max - o.Min | ||
n := rand.Intn(lim) + o.Min | ||
return strconv.Itoa(n) | ||
} |
Oops, something went wrong.