/
generate.go
187 lines (155 loc) · 5.33 KB
/
generate.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package cmd
import (
"fmt"
"io"
"math/rand/v2"
"path/filepath"
"strconv"
"github.com/meyermarcel/icm/configs"
"github.com/meyermarcel/icm/cont"
"github.com/meyermarcel/icm/data"
"github.com/spf13/cobra"
)
type countValue struct {
value int
}
func (r *countValue) String() string {
return strconv.Itoa(r.value)
}
func (r *countValue) Set(value string) error {
count, err := strconv.Atoi(value)
if err != nil {
return err
}
if count < 1 {
return fmt.Errorf("%d is not greater than 0", count)
}
r.value = count
return nil
}
func (*countValue) Type() string {
return "int"
}
type ownerValue struct {
value string
}
func (o *ownerValue) String() string {
return o.value
}
func (o *ownerValue) Set(value string) error {
if err := cont.IsOwnerCode(value); err != nil {
return err
}
o.value = value
return nil
}
func (*ownerValue) Type() string {
return "string"
}
type serialNumValue struct {
value int
}
func (r *serialNumValue) String() string {
return strconv.Itoa(r.value)
}
func (r *serialNumValue) Set(value string) error {
serialNum, err := strconv.Atoi(value)
if err != nil {
return err
}
if serialNum < 0 || serialNum > 999999 {
return fmt.Errorf("%d is not in range from 0 to 999999", serialNum)
}
r.value = serialNum
return nil
}
func (*serialNumValue) Type() string {
return "int"
}
func newGenerateCmd(writer, writerErr io.Writer, config *configs.Config, ownerDecoder data.OwnerDecoder, r *rand.Rand) *cobra.Command {
count := countValue{value: 1}
startValue := serialNumValue{}
endValue := serialNumValue{}
ownerValue := ownerValue{}
var excludeCheckDigit10 bool
var excludeErrorProneSerialNumbers bool
generateCmd := &cobra.Command{
Use: "generate",
Short: "Generate unique container numbers",
Long: `Generated container numbers are unique. Owners specified in
` + filepath.Join("$HOME", appDir, "data", "owner.csv") + `
are used. Owners can be updated by 'icm download-owners --help' command.
Equipment category ID 'U' is used for every generated container number.
For a custom owner code use the --owner-code flag.
For a custom serial number use the --start and --end flags and optionally the --count flag.
Using only the --count flag generates pseudo random serial numbers.
` + sepHelp,
Example: `icm generate
icm generate --count 10
# Generate container numbers with custom format
icm generate --count 10 --sep-owner-equip '' --sep-serial-check '-'
# Generate container numbers without error-prone serial numbers
icm generate --count 10 --exclude-check-digit-10 --exclude-error-prone-serial-numbers
# Generate container numbers within serial number range
icm generate --start 100500 --count 10
icm generate --start 100500 --end 100600
icm generate --start 100500 --end 100600 --owner ABC
# Generate CSV data set
icm generate --count 1000000 | icm validate`,
Args: cobra.NoArgs,
ValidArgsFunction: cobra.NoFileCompletions,
RunE: func(cmd *cobra.Command, _ []string) error {
config.Overwrite(cmd.Flags())
builder := cont.NewUniqueGeneratorBuilder(r).
Count(count.value).
ExcludeCheckDigit10(excludeCheckDigit10).
ExcludeErrorProneSerialNumbers(excludeErrorProneSerialNumbers)
if cmd.Flags().Changed("owner") {
builder.OwnerCodes([]string{ownerValue.value})
} else {
builder.OwnerCodes(ownerDecoder.GetAllOwnerCodes())
}
if cmd.Flags().Changed("start") {
builder.Start(startValue.value)
}
if cmd.Flags().Changed("end") {
builder.End(endValue.value)
}
generator, err := builder.Build()
if err != nil {
return err
}
for generator.Generate() {
cn := generator.ContNum()
_, err := io.WriteString(writer, fmt.Sprintf("%s%s%s%s%06d%s%d\n",
cn.OwnerCode, config.SepOE(),
string(cn.EquipCatID), config.SepES(),
cn.SerialNumber, config.SepSC(),
cn.CheckDigit))
writeErr(writerErr, err)
}
return nil
},
}
generateCmd.Flags().SortFlags = false
generateCmd.Flags().VarP(&count, "count", "c", "count of container numbers")
generateCmd.Flags().VarP(&startValue, "start", "s", "start of serial number range")
generateCmd.Flags().VarP(&endValue, "end", "e", "end of serial number range")
generateCmd.Flags().Var(&ownerValue, "owner", "custom owner code")
generateCmd.Flags().BoolVar(&excludeCheckDigit10, "exclude-check-digit-10", false, "exclude check digit 10")
generateCmd.Flags().BoolVar(&excludeErrorProneSerialNumbers, "exclude-transposition-errors", false,
"exclude possible transposition errors")
err := generateCmd.Flags().MarkDeprecated("exclude-transposition-errors", "use instead --exclude-error-prone-serial-numbers")
if err != nil {
return nil
}
generateCmd.Flags().BoolVar(&excludeErrorProneSerialNumbers, "exclude-error-prone-serial-numbers", false,
"exclude error-prone serial numbers. For example swapping the second 0 and first 1 of RCB U 001130 0 results in container number RCB U 010130 0 with a valid check digit 0")
generateCmd.Flags().String(configs.FlagNames.SepOE, configs.DefaultValues.SepOE,
"ABC(x)U1234560 (x) separates owner code and equipment category id")
generateCmd.Flags().String(configs.FlagNames.SepES, configs.DefaultValues.SepES,
"ABCU(x)1234560 (x) separates equipment category id and serial number")
generateCmd.Flags().String(configs.FlagNames.SepSC, configs.DefaultValues.SepSC,
"ABCU123456(x)0 (x) separates serial number and check digit")
return generateCmd
}