Skip to content

Commit eb9c956

Browse files
committed
replace: add {enr} and {rnr}. #322
1 parent 33f67be commit eb9c956

File tree

3 files changed

+128
-31
lines changed

3 files changed

+128
-31
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
- `csvtk fix`:
1414
- add a new flag `--na` to set content to fill. [#316](https://github.com/shenwei356/csvtk/issues/316)
1515
- `csvtk replace`:
16-
- add a new replacement symbol/placeholder: `{gnr}`, i.e., the group-specific record number. [#322](https://github.com/shenwei356/csvtk/issues/322)
16+
- add new replacements symbol/placeholder for group-specific numbering: `{gnr}`, `{enr}`, `{rnr`}. [#322](https://github.com/shenwei356/csvtk/issues/322)
1717
- [csvtk v0.33.0](https://github.com/shenwei356/csvtk/releases/tag/v0.33.0)
1818
[![Github Releases (by Release)](https://img.shields.io/github/downloads/shenwei356/csvtk/v0.33.0/total.svg)](https://github.com/shenwei356/csvtk/releases/tag/v0.33.0)
1919
- new command `csvtk comma`: make numbers more readable by adding commas. [#300](https://github.com/shenwei356/csvtk/issues/300)

csvtk/cmd/replace.go

+100-25
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,19 @@ more on: http://shenwei356.github.io/csvtk/usage/#replace
5656
Special replacement symbols:
5757
5858
{nr} Record number, starting from 1
59-
{gnr} Record number within a group (value of field -g/--gnr-field), starting from 1
59+
{gnr} Record number within a group, defined by value(s) of field(s) via -g/--group
60+
{enr} Enumeration numbering of groups, defined by value(s) of field(s) via -g/--group
61+
{rnr} Running numbering of groups, defined by value(s) of field(s) via -g/--group
62+
63+
Examples:
64+
group {nr} {gnr} {enr} {rnr}
65+
A 1 1 1 1
66+
A 2 2 1 1
67+
B 3 1 2 2
68+
B 4 2 2 2
69+
A 5 3 1 3
70+
C 6 1 3 4
71+
6072
{kv} Corresponding value of the key (captured variable $n) by key-value file,
6173
n can be specified by flag --key-capt-idx (default: 1)
6274
@@ -90,6 +102,13 @@ Special replacement symbols:
90102
nrWidth := getFlagPositiveInt(cmd, "nr-width")
91103
nrFormat := fmt.Sprintf("%%0%dd", nrWidth)
92104
startNum := getFlagNonNegativeInt(cmd, "start-num")
105+
startGNR := getFlagNonNegativeInt(cmd, "start-gnr")
106+
startENR := getFlagNonNegativeInt(cmd, "start-enr")
107+
startRNR := getFlagNonNegativeInt(cmd, "start-rnr")
108+
incrGNR := getFlagPositiveInt(cmd, "incr-gnr")
109+
incrENR := getFlagPositiveInt(cmd, "incr-enr")
110+
incrRNR := getFlagPositiveInt(cmd, "incr-rnr")
111+
93112
kvFileAllLeftColumnsAsValue := getFlagBool(cmd, "kv-file-all-left-columns-as-value")
94113

95114
var replaceWithNR bool
@@ -136,18 +155,22 @@ Special replacement symbols:
136155
if fieldStr == "" {
137156
checkError(fmt.Errorf("flag -f (--fields) needed"))
138157
}
139-
gnrFieldStr := getFlagString(cmd, "gnr-field")
140-
if reGNR.MatchString(replacement) {
141-
if gnrFieldStr == "" {
142-
checkError(fmt.Errorf(`flag -g (--gnr-field) needed for using "{gnr}" or "{NGR}"`))
158+
groups := getFlagStringSlice(cmd, "group")
159+
setGroup := len(groups) > 0
160+
groupCols := len(groups)
161+
162+
if reGNR.MatchString(replacement) || reENR.MatchString(replacement) || reRNR.MatchString(replacement) {
163+
if !setGroup {
164+
checkError(fmt.Errorf(`flag -g (--group) needed for using "{gnr}", "{enr}", and "{rnr}"`))
143165
}
144-
} else if gnrFieldStr != "" {
145-
checkError(fmt.Errorf(`flag -g (--gnr-field) given, but "{gnr}" or "{NGR}" not found in -r/--replacement: %s`, replacement))
146-
}
147-
if gnrFieldStr != "" && strings.Contains(gnrFieldStr, ",") {
148-
checkError(fmt.Errorf(`only one field should be given in flag -g (--gnr-field): %s`, gnrFieldStr))
166+
} else if setGroup {
167+
checkError(fmt.Errorf(`flag -g (--group) given, but "{gnr}", "{enr}", "{rnr}" not found in -r/--replacement: %s`, replacement))
149168
}
150-
replaceWithGNR := reGNR.MatchString(replacement) && gnrFieldStr != ""
169+
170+
replaceWithGNR := reGNR.MatchString(replacement) && setGroup
171+
replaceWithENR := reENR.MatchString(replacement) && setGroup
172+
replaceWithRNR := reRNR.MatchString(replacement) && setGroup
173+
_replaceWithXNR := replaceWithGNR || replaceWithENR || replaceWithRNR
151174

152175
fuzzyFields := getFlagBool(cmd, "fuzzy-fields")
153176

@@ -184,9 +207,10 @@ Special replacement symbols:
184207
}
185208

186209
_fieldStr := fieldStr
187-
if replaceWithGNR {
188-
_fieldStr += "," + gnrFieldStr
210+
if _replaceWithXNR {
211+
_fieldStr += "," + strings.Join(groups, ",")
189212
}
213+
190214
csvReader.Read(ReadOption{
191215
FieldStr: _fieldStr,
192216
FuzzyFields: fuzzyFields,
@@ -202,12 +226,21 @@ Special replacement symbols:
202226
var k string
203227
nr := startNum
204228

205-
var m map[string]int
229+
var group string
230+
groupColData := make([]string, groupCols)
231+
var mg map[string]int
206232
if replaceWithGNR {
207-
m = make(map[string]int)
233+
mg = make(map[string]int)
234+
}
235+
var me map[string]int
236+
if replaceWithENR {
237+
me = make(map[string]int)
208238
}
209-
var gnr int
210-
var group string
239+
240+
var iGroup, gnr, enr, rnr int
241+
iGroup = startENR - incrENR
242+
rnr = startRNR - incrRNR
243+
groupPre := "_shenwei356__"
211244

212245
var fields []int
213246

@@ -229,11 +262,33 @@ Special replacement symbols:
229262
}
230263
}
231264

232-
if replaceWithGNR {
233-
fields = record.Fields[:len(record.Fields)-1]
234-
group = record.All[len(record.Fields)-1]
235-
m[group]++
236-
gnr = m[group]
265+
if _replaceWithXNR {
266+
fields = record.Fields[:len(record.Fields)-groupCols]
267+
for i := 0; i < groupCols; i++ {
268+
groupColData[i] = record.All[record.Fields[len(record.Fields)-groupCols+i]-1]
269+
}
270+
group = strings.Join(groupColData, "_shenwei356_")
271+
272+
if replaceWithGNR {
273+
if _, ok = mg[group]; !ok {
274+
mg[group] = startGNR - incrGNR
275+
}
276+
mg[group] += incrGNR
277+
gnr = mg[group]
278+
}
279+
if replaceWithENR {
280+
if _, ok = me[group]; !ok {
281+
iGroup += incrENR
282+
me[group] = iGroup
283+
}
284+
enr = me[group]
285+
}
286+
if replaceWithRNR {
287+
if group != groupPre {
288+
rnr += incrRNR
289+
}
290+
groupPre = group
291+
}
237292
} else {
238293
fields = record.Fields
239294
}
@@ -243,6 +298,8 @@ Special replacement symbols:
243298

244299
r = replacement
245300

301+
r = reTab.ReplaceAllString(r, "\t")
302+
246303
if replaceWithNR {
247304
r = reNR.ReplaceAllString(r, fmt.Sprintf(nrFormat, nr))
248305
}
@@ -251,6 +308,14 @@ Special replacement symbols:
251308
r = reGNR.ReplaceAllString(r, fmt.Sprintf(nrFormat, gnr))
252309
}
253310

311+
if replaceWithENR {
312+
r = reENR.ReplaceAllString(r, fmt.Sprintf(nrFormat, enr))
313+
}
314+
315+
if replaceWithRNR {
316+
r = reRNR.ReplaceAllString(r, fmt.Sprintf(nrFormat, rnr))
317+
}
318+
254319
if replaceWithKV {
255320
founds = patternRegexp.FindAllStringSubmatch(record.All[i], -1)
256321
if len(founds) > 1 {
@@ -290,9 +355,9 @@ Special replacement symbols:
290355
func init() {
291356
RootCmd.AddCommand(replaceCmd)
292357
replaceCmd.Flags().StringP("fields", "f", "1", `select only these fields. e.g -f 1,2 or -f columnA,columnB`)
293-
replaceCmd.Flags().StringP("gnr-field", "g", "", `select a field for a group-specific record number {gnr}`)
358+
replaceCmd.Flags().StringSliceP("group", "g", []string{}, `select field(s) for group-specific record numbering, including {gnr}, {enr}, {rnr}. Please use the same field type (field number or column name) with the value of -f/--fields`)
294359
replaceCmd.Flags().BoolP("fuzzy-fields", "F", false, `using fuzzy fields, e.g., -F -f "*name" or -F -f "id123*"`)
295-
replaceCmd.Flags().StringP("pattern", "p", "", "search regular expression")
360+
replaceCmd.Flags().StringP("pattern", "p", ".*", "search regular expression")
296361
replaceCmd.Flags().StringP("replacement", "r", "",
297362
"replacement. supporting capture variables. "+
298363
" e.g. $1 represents the text of the first submatch. "+
@@ -305,11 +370,21 @@ func init() {
305370
replaceCmd.Flags().BoolP("keep-key", "K", false, "keep the key as value when no value found for the key")
306371
replaceCmd.Flags().IntP("key-capt-idx", "", 1, "capture variable index of key (1-based)")
307372
replaceCmd.Flags().StringP("key-miss-repl", "", "", "replacement for key with no corresponding value")
308-
replaceCmd.Flags().IntP("nr-width", "", 1, `minimum width for {nr} in flag -r/--replacement. e.g., formating "1" to "001" by --nr-width 3`)
373+
replaceCmd.Flags().IntP("nr-width", "", 1, `minimum width for {nr}, {gnr}, {enr}, {rnr} in flag -r/--replacement. e.g., formating "1" to "001" by --nr-width 3`)
309374
replaceCmd.Flags().IntP("start-num", "n", 1, `starting number when using {nr} in replacement`)
375+
replaceCmd.Flags().IntP("start-gnr", "", 1, `starting number when using {gnr} in replacement`)
376+
replaceCmd.Flags().IntP("start-enr", "", 1, `starting number when using {enr} in replacement`)
377+
replaceCmd.Flags().IntP("start-rnr", "", 1, `starting number when using {rnr} in replacement`)
378+
replaceCmd.Flags().IntP("incr-gnr", "", 1, `increment number when using {gnr} in replacement`)
379+
replaceCmd.Flags().IntP("incr-enr", "", 1, `increment number when using {enr} in replacement`)
380+
replaceCmd.Flags().IntP("incr-rnr", "", 1, `increment number when using {rnr} in replacement`)
381+
310382
replaceCmd.Flags().BoolP("kv-file-all-left-columns-as-value", "A", false, "treat all columns except 1th one as value for kv-file with more than 2 columns")
311383
}
312384

313385
var reNR = regexp.MustCompile(`\{(NR|nr)\}`)
314386
var reGNR = regexp.MustCompile(`\{(GNR|gnr)\}`)
387+
var reENR = regexp.MustCompile(`\{(ENR|enr)\}`)
388+
var reRNR = regexp.MustCompile(`\{(RNR|rnr)\}`)
315389
var reKV = regexp.MustCompile(`\{(KV|kv)\}`)
390+
var reTab = regexp.MustCompile(`\\t`)

doc/docs/usage.md

+27-5
Original file line numberDiff line numberDiff line change
@@ -3676,7 +3676,19 @@ more on: http://shenwei356.github.io/csvtk/usage/#replace
36763676
Special replacement symbols:
36773677
36783678
{nr} Record number, starting from 1
3679-
{gnr} Record number within a group (value of field -g/--gnr-field), starting from 1
3679+
{gnr} Record number within a group, defined by value(s) of field(s) via -g/--group
3680+
{enr} Enumeration numbering of groups, defined by value(s) of field(s) via -g/--group
3681+
{rnr} Running numbering of groups, defined by value(s) of field(s) via -g/--group
3682+
3683+
Examples:
3684+
group {nr} {gnr} {enr} {rnr}
3685+
A 1 1 1 1
3686+
A 2 2 1 1
3687+
B 3 1 2 2
3688+
B 4 2 2 2
3689+
A 5 3 1 3
3690+
C 6 1 3 4
3691+
36803692
{kv} Corresponding value of the key (captured variable $n) by key-value file,
36813693
n can be specified by flag --key-capt-idx (default: 1)
36823694
@@ -3687,25 +3699,35 @@ Flags:
36873699
-f, --fields string select only these fields. e.g -f 1,2 or -f columnA,columnB
36883700
(default "1")
36893701
-F, --fuzzy-fields using fuzzy fields, e.g., -F -f "*name" or -F -f "id123*"
3690-
-g, --gnr-field string select a field for a group-specific record number {gnr}
3702+
-g, --group strings select field(s) for group-specific record numbering,
3703+
including {gnr}, {enr}, {rnr}. Please use the same field
3704+
type (field number or column name) with the value of -f/--fields
36913705
-h, --help help for replace
36923706
-i, --ignore-case ignore case
3707+
--incr-enr int increment number when using {enr} in replacement (default 1)
3708+
--incr-gnr int increment number when using {gnr} in replacement (default 1)
3709+
--incr-rnr int increment number when using {rnr} in replacement (default 1)
36933710
-K, --keep-key keep the key as value when no value found for the key
36943711
--key-capt-idx int capture variable index of key (1-based) (default 1)
36953712
--key-miss-repl string replacement for key with no corresponding value
36963713
-k, --kv-file string tab-delimited key-value file for replacing key with value
36973714
when using "{kv}" in -r (--replacement)
36983715
-A, --kv-file-all-left-columns-as-value treat all columns except 1th one as value for kv-file with
36993716
more than 2 columns
3700-
--nr-width int minimum width for {nr} in flag -r/--replacement. e.g.,
3701-
formating "1" to "001" by --nr-width 3 (default 1)
3702-
-p, --pattern string search regular expression
3717+
--nr-width int minimum width for {nr}, {gnr}, {enr}, {rnr} in flag
3718+
-r/--replacement. e.g., formating "1" to "001" by --nr-width
3719+
3 (default 1)
3720+
-p, --pattern string search regular expression (default ".*")
37033721
-r, --replacement string replacement. supporting capture variables. e.g. $1
37043722
represents the text of the first submatch. ATTENTION: for
37053723
*nix OS, use SINGLE quote NOT double quotes or use the \
37063724
escape character. Record number is also supported by
37073725
"{nr}".use ${1} instead of $1 when {kv} given!
3726+
--start-enr int starting number when using {enr} in replacement (default 1)
3727+
--start-gnr int starting number when using {gnr} in replacement (default 1)
37083728
-n, --start-num int starting number when using {nr} in replacement (default 1)
3729+
--start-rnr int starting number when using {rnr} in replacement (default 1)
3730+
37093731
```
37103732

37113733
Examples

0 commit comments

Comments
 (0)