Compare commits

...

9 Commits

Author SHA1 Message Date
10de418b6e changed bulkrename script to work work with filenames containing single quotation marks 2023-06-14 14:46:57 +02:00
929784da83 added -0 paramter for better compability with xargs 2023-03-20 11:07:09 +01:00
Timon Ringwald
e7a2a65eb6 show version 2022-08-29 13:45:28 +02:00
Timon Ringwald
75dce36679 README updated 2022-08-17 21:00:36 +02:00
Timon Ringwald
2f28cef4b5 updated README 2022-08-15 22:41:24 +02:00
Timon Ringwald
78e901eccf moved to git.milar.in 2022-08-03 21:34:47 +02:00
Timon Ringwald
b849cd47b1 improved README.md 2022-07-25 17:55:05 +02:00
Timon Ringwald
5bf2f81d98 added LICENSE.md 2022-07-25 01:50:52 +02:00
Timon Ringwald
50d1ac83b6 color attribute support 2022-05-22 22:39:41 +02:00
7 changed files with 251 additions and 87 deletions

29
.gitignore vendored
View File

@ -1 +1,30 @@
format
format_freebsd_386
format_linux_amd64
format_linux_arm
format_openbsd_amd64
format_linux_mips64
format_freebsd_arm
format_netbsd_arm64
format_windows_arm64
format_netbsd_386
format_freebsd_arm64
format_linux_386
format_openbsd_386
format_openbsd_mips64
format_netbsd_arm
format_linux_ppc64le
format_windows_386
format_linux_arm64
format_netbsd_amd64
format_windows_arm
format_linux_mips64le
format_openbsd_arm
format_windows_amd64
format_linux_riscv64
format_linux_ppc64
format_linux_mipsle
format_linux_mips
format_openbsd_arm64
format_freebsd_amd64
format_linux_s390x

19
LICENSE.md Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2022 Mila Ringwald
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

132
README.md
View File

@ -1,13 +1,27 @@
# format
format greps lines from stdin, parses them via regex and reformats them according to a given format string
## Source code
## Input pattern
You can find the source code here: https://git.milar.in/milarin/format
## Installation
If you have Go installed, you can simply go install the program: `go install git.milar.in/milarin/format@latest`
There are pre-compiled executables for various platforms on the [repository](https://git.milar.in/milarin/format/releases).
## License
Distributed under the MIT License. See [LICENSE.md](https://git.milar.in/milarin/format/src/branch/main/LICENSE.md)
## Usage
### Input pattern
The input pattern describes the format in which the lines are parsed from stdin.
This pattern is a regular expression according to [Go's regexp spec](https://pkg.go.dev/regexp).
Be default, the input pattern will only be applied to every single line.
By default, the input pattern will only be applied to every single line.
When using multiline patterns, you can provide an amount of lines using the command line argument `-n` followed by an integer amount of lines.
Use subgroups for extracting specific parts of the input line.
@ -16,13 +30,15 @@ Provide your custom input pattern with the command line argument `-i '<pattern>'
The default value is `^.*?$` which simply matches the whole line.
## Output pattern
### Output pattern
The output pattern describes the format in which lines are generated for stdout using data from the input pattern.
Provide an output pattern via `-o '<pattern>'`.
The default value is `{0}` which always matches the full input pattern
### Capturing groups
#### Capturing groups
Use the `{<group_index>}` syntax to use a specific capturing group.
- `{0}` always matches the whole line.
@ -30,22 +46,39 @@ Use the `{<group_index>}` syntax to use a specific capturing group.
- `{2}` matches the second capturing group
- and so on
### Formatting
#### Coloring
When referencing capturing groups, you can add a specific format for some data types as well using a simplified printf syntax.
Coloring a reference to a capturing group can be done via `{1:<color>}`.
You can use them using this syntax: `{1:%d}`. It will parse the first capturing group into an integer to get rid of leading zeros. Additionally, you can provide a given amount of leading zeros via: `{1:%03d}` for a total length of 3 digits.
`<color>` can be one of the following values:
- `black`
- `red`
- `green`
- `yellow`
- `blue`
- `magenta`
- `cyan`
- `white`
Leaving the color argument empty results into the default color.
#### Formatting
When referencing capturing groups, you can add a specific format for some data types using a simplified printf syntax.
You can use them using this syntax: `{1::%d}`. It will parse the first capturing group into an integer to get rid of leading zeros. Additionally, you can provide a given amount of leading zeros via: `{1::%03d}` for a total length of 3 digits.
The same method can also be applied to `%f` to further format floating point values.
See a full list of formatting options at [Go's fmt spec](https://pkg.go.dev/fmt).
Currently only `%s`, `%d`, `%f` and `%g` are supported though.
### Mutators
#### Mutators
Mutators are a simple way of manipulating number values like integers and floats using a simple math-like expression
You can provide a mutator using the syntax: `{1:%d:+1}`. It will parse the first capturing group into an integer, adds 1 and then formats the result using the printf format `%d`.
You can provide a mutator using the syntax: `{1::%d:+1}`. It will parse the first capturing group into an integer, adds 1 and then formats the result using the printf format `%d`.
A mutator always consists of an operator and a value. In the example above `+` is the operator and `1` is the value.
@ -55,13 +88,13 @@ The following operators are supported for `%d`, `%f` and `%g` formats:
- `*`
- `/`
It is possible to add multiple mutators by just concatenating them: `{1:%d:*2+1}`.
It is possible to add multiple mutators by just concatenating them: `{1::%d:*2+1}`.
Multiple mutators will not follow any order of operations. They are simply applied from left to right!
**Multiple mutators will not follow any order of operations. They are simply applied from left to right!**
Furthermore you can reference caputring groups which will be parsed as the same type to apply its value. This is done via the following syntax: `{1:%d:+(2)}`. It will parse the first and second capturing group into integers and adds them.
Furthermore you can reference caputring groups which will be parsed as the same type to apply its value. This is done via the following syntax: `{1::%d:+(2)}`. It will parse the first and second capturing group into integers and adds them.
## Handling unmatched lines
### Handling unmatched lines
By default, lines which do not match the input pattern will be dropped.
Use `-k` to keep them unchanged. They will be copied into stdout.
@ -69,8 +102,10 @@ Use `-k` to keep them unchanged. They will be copied into stdout.
## Examples
### Copying
Copying is the default behavior of `format`
Input:
```
```txt
1
2
3
@ -83,7 +118,7 @@ format
```
Output:
```
```txt
1
2
3
@ -95,7 +130,7 @@ Output:
Only keep lines which contains an `i`
Input:
```
```txt
one
two
three
@ -114,7 +149,7 @@ format -i '.*i.*'
```
Output:
```
```txt
five
six
eight
@ -123,9 +158,10 @@ nine
### Removing leading zeros
Use printf syntax on a capturing group in the output pattern
Input:
```
```txt
001
002
003
@ -134,11 +170,11 @@ Input:
Command:
```sh
format -i '\d+' -o '{0:%d}'
format -i '\d+' -o '{0::%d}'
```
Output:
```
```txt
1
2
3
@ -148,7 +184,7 @@ Output:
### Extracting dates
Input:
```
```txt
2022-04-18
1970-01-01
2006-01-02
@ -156,22 +192,21 @@ Input:
Command:
```sh
format -i '(\d{4})-(\d{2})-(\d{2})' -o 'day: {3:%d} | month: {2:%d} | year: {1}'
format -i '(\d{4})-(\d{2})-(\d{2})' -o 'day: {3::%d} | month: {2::%d} | year: {1}'
```
Output:
```
```txt
day: 18 | month: 4 | year: 2022
day: 1 | month: 1 | year: 1970
day: 2 | month: 1 | year: 2006
```
### Applying multiple formats
Every format process can only apply a single pattern. Use `-k` to keep unmatched lines so the next format instance can apply another input pattern to them
Every `format` process can only apply a single pattern. Use `-k` to keep unmatched lines so the next `format` instance can apply another input pattern to them
Input:
```
```txt
2022-04-18
1970-01-01
02.01.2006
@ -180,11 +215,12 @@ Input:
Command:
```sh
format -i '(\d{4})-(\d{2})-(\d{2})' -o 'day: {3:%d} | month: {2:%d} | year: {1}' -k | format -i '(\d{2})\.(\d{2})\.(\d{4})' -o 'day: {1:%d} | month: {2:%d} | year: {3}' -k
format -i '(\d{4})-(\d{2})-(\d{2})' -o 'day: {3::%d} | month: {2::%d} | year: {1}' -k |
format -i '(\d{2})\.(\d{2})\.(\d{4})' -o 'day: {1::%d} | month: {2::%d} | year: {3}' -k
```
Output:
```
```txt
day: 18 | month: 4 | year: 2022
day: 1 | month: 1 | year: 1970
day: 2 | month: 1 | year: 2006
@ -192,9 +228,10 @@ day: 2 | month: 2 | year: 1962
```
### Parsing multi-line patterns
Use `-n` to change the amount of lines fed into the input pattern
Input:
```
```txt
year: 2022
month: 04
day: 18
@ -211,11 +248,11 @@ day: 02
Command:
```sh
format -n 3 -i '^year: (\d{4})\nmonth: (\d{2})\nday: (\d{2})$' -o 'day: {3:%d} | month: {2:%d} | year: {1}'
format -n 3 -i '^year: (\d{4})\nmonth: (\d{2})\nday: (\d{2})$' -o 'day: {3::%d} | month: {2::%d} | year: {1}'
```
Output:
```
```txt
day: 18 | month: 4 | year: 2022
day: 1 | month: 1 | year: 1970
day: 2 | month: 1 | year: 2006
@ -223,9 +260,10 @@ day: 2 | month: 2 | year: 1962
```
### Adding 2 values together
Use mutators to apply simple arithmetic on
Input:
```
```txt
5 7
3 2
10 152
@ -234,11 +272,11 @@ Input:
Command:
```sh
format -i '(-?\d+) (-?\d+(?:.\d+)?)' -o '{1} + {2} = {1:%g:+(2)}'
format -i '(-?\d+) (-?\d+(?:.\d+)?)' -o '{1} + {2} = {1::%g:+(2)}'
```
Output:
```
```txt
5 + 7 = 12
3 + 2 = 5
10 + 152 = 162
@ -247,10 +285,10 @@ Output:
### Bulk renaming files
Rename a bunch of files at once using format
Rename a bunch of files at once using `format`
Output of `ls`:
```
```txt
000.jpg
001.jpg
002.jpg
@ -258,11 +296,11 @@ Output of `ls`:
Command:
```sh
ls | format -i '(\d+)\.jpg' -o 'mv "{0}" "{1:%d:+1}.jpg"' | xargs -0 sh -c
ls | format -i '(\d+)\.jpg' -o 'mv "{0}" "{1::%d:+1}.jpg"' | xargs -0 sh -c
```
Output of `ls` afterwards:
```
```txt
1.jpg
2.jpg
3.jpg
@ -275,7 +313,7 @@ Content:
#!/usr/bin/env sh
if [ "$3" = "exec" ]; then
command ls | format -i "$1" -o "mv \"{0}\" \"$2\"" | xargs -0 -P 4 sh -c
command ls | format -0 -i "$1" -o "mv \"{0}\" \"$2\"" | xargs -0 -P 4 -I {} sh -c "{}"
else
command ls | format -i "$1" -o "mv \"{0}\" \"$2\""
echo
@ -291,7 +329,7 @@ There are a few things to consider using this script:
Example usage of this script:
Output of `ls`:
```
```txt
000.jpg
001.jpg
002.jpg
@ -299,26 +337,26 @@ Output of `ls`:
Command:
```sh
bulkrename '(\d+)\.jpg' '{1:%d:+1}.jpg'
bulkrename '(\d+)\.jpg' '{1::%d:+1}.jpg'
```
Output:
```
```txt
mv "000.jpg" "1.jpg"
mv "001.jpg" "2.jpg"
mv "002.jpg" "3.jpg"
execute commands with 'bulkrename (\d+)\.jpg {1:%d:+1}.jpg exec'
execute commands with 'bulkrename (\d+)\.jpg {1::%d:+1}.jpg exec'
```
Command:
```sh
bulkrename '(\d+)\.jpg' '{1:%d:+1}.jpg' exec
bulkrename '(\d+)\.jpg' '{1::%d:+1}.jpg' exec
```
Output of `ls` afterwards:
```
```txt
1.jpg
2.jpg
3.jpg
```
```

View File

@ -1,36 +1,85 @@
package main
import "github.com/fatih/color"
import (
"fmt"
"strings"
"github.com/fatih/color"
)
var colorCache = map[string]*color.Color{}
func makeColor(name string) (c *color.Color) {
func makeColor(colorSpec string) (c *color.Color) {
// caching
if c, ok := colorCache[name]; ok {
if c, ok := colorCache[colorSpec]; ok {
return c
}
defer func() { colorCache[name] = c }()
defer func() { colorCache[colorSpec] = c }()
splits := strings.Split(colorSpec, ",")
name := strings.TrimSpace(splits[0])
c = color.New()
switch name {
case "black":
return color.New(color.FgBlack)
c.Add(color.FgBlack)
case "red":
return color.New(color.FgRed)
c.Add(color.FgRed)
case "green":
return color.New(color.FgGreen)
c.Add(color.FgGreen)
case "yellow":
return color.New(color.FgYellow)
c.Add(color.FgYellow)
case "blue":
return color.New(color.FgBlue)
c.Add(color.FgBlue)
case "magenta":
return color.New(color.FgMagenta)
c.Add(color.FgMagenta)
case "cyan":
return color.New(color.FgCyan)
c.Add(color.FgCyan)
case "white":
return color.New(color.FgWhite)
c.Add(color.FgWhite)
case "":
return color.New()
default:
panic("unknown color name. valid color names: black, red, green, yellow, blue, magenta, cyan, white")
}
if len(splits) >= 2 {
attr := splits[1]
switch attr {
case "bold":
fmt.Println("bold")
c.Add(color.Bold)
case "faint":
fmt.Println("faint")
c.Add(color.Faint)
case "italic":
fmt.Println("italic")
c.Add(color.Italic)
case "underline":
fmt.Println("underline")
c.Add(color.Underline)
case "blink_slow":
fmt.Println("blink_slow")
c.Add(color.BlinkSlow)
case "blink_rapid":
fmt.Println("blink_rapid")
c.Add(color.BlinkRapid)
case "reverse_video":
fmt.Println("reverse_video")
c.Add(color.ReverseVideo)
case "concealed":
fmt.Println("concealed")
c.Add(color.Concealed)
case "crossed_out":
fmt.Println("crossed_out")
c.Add(color.CrossedOut)
case "":
fmt.Println("")
default:
panic("unknown attribute. valid attributes: bold, faint, italic, underline, blink_slow, blink_rapid, reverse_video, concealed, crossed_out")
}
}
return c
}

8
go.mod
View File

@ -1,9 +1,13 @@
module git.tordarus.net/Tordarus/format
module git.milar.in/milarin/format
go 1.18
require (
github.com/fatih/color v1.13.0 // indirect
git.milar.in/milarin/buildinfo v1.0.0
github.com/fatih/color v1.13.0
)
require (
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
golang.org/x/sys v0.0.0-20220517195934-5e4e11fc645e // indirect

2
go.sum
View File

@ -1,3 +1,5 @@
git.milar.in/milarin/buildinfo v1.0.0 h1:tw98GupUYl/0a/3aPGuezhE4wseycOSsbcLp70hy60U=
git.milar.in/milarin/buildinfo v1.0.0/go.mod h1:arI9ZoENOgcZcanv25k9y4dKDUhPp0buJrlVerGruas=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=

73
main.go
View File

@ -10,9 +10,11 @@ import (
"strconv"
"strings"
"unicode/utf8"
"git.milar.in/milarin/buildinfo"
)
var (
var ( //flags
// regex with sub groups
input = flag.String("i", `^(.|\n)*?$`, "input pattern")
@ -45,14 +47,30 @@ var (
// it may be useful to have a boolean flag for this behavior
lineParseAmount = flag.Int("n", 1, "amount of lines to feed into input pattern")
replacePattern = regexp.MustCompile(`\{(\d+)(?::(.*?))?(?::(.*?))?(?::(.*?))?\}`)
showVersion = flag.Bool("v", false, "show version and exit")
OutputNullByte = flag.Bool("0", false, "use nullbyte instead of newline as line separator for printing output")
)
var ( // globals
LineSeparator string = "\n"
replacePattern = regexp.MustCompile(`\{(\d+)(?::(.*?))?(?::(.*?))?(?::(.*?))?\}`)
numMutationPattern = regexp.MustCompile(`([+\-*/])(\d+|\((\d+)\))`)
)
func main() {
flag.Parse()
if *showVersion {
buildinfo.Print(buildinfo.Options{})
return
}
if *OutputNullByte {
LineSeparator = string(rune(0))
}
pattern, err := regexp.Compile(*input)
if err != nil {
panic(err)
@ -65,12 +83,12 @@ func main() {
if len(matches) == 0 {
if *keepUnmatched {
fmt.Println(line)
fmt.Print(line, LineSeparator)
}
continue
}
fmt.Println(replaceVars(escapedOutput, matches...))
fmt.Print(replaceVars(escapedOutput, matches...), LineSeparator)
}
}
@ -79,32 +97,15 @@ func readLines(r io.Reader) <-chan string {
go func(out chan<- string, source io.Reader) {
defer close(out)
r := bufio.NewReader(source)
for {
var line string
var err error
lines := make([]string, 0, *lineParseAmount)
for line, err = r.ReadString('\n'); ; line, err = r.ReadString('\n') {
if rn, size := utf8.DecodeLastRuneInString(line); rn == '\n' {
line = line[:len(line)-size]
}
lines = append(lines, line)
// stop reading as soon as lineParseAmount is reached or an error occured (most likely EOF)
if len(lines) == cap(lines) || err != nil {
break
}
}
linesCombined := strings.Join(lines, "\n")
line, err := ReadLine(r)
// use data as line if reading was successfull or EOF has been reached
// in the latter case: only use data if something could be read until EOF
if err == nil || err == io.EOF && linesCombined != "" {
out <- linesCombined
if err == nil || err == io.EOF && line != "" {
out <- line
}
if err != nil {
@ -116,8 +117,30 @@ func readLines(r io.Reader) <-chan string {
return ch
}
func ReadLine(r *bufio.Reader) (string, error) {
lines := make([]string, 0, *lineParseAmount)
var line string
var err error
for line, err = r.ReadString('\n'); ; line, err = r.ReadString('\n') {
if rn, size := utf8.DecodeLastRuneInString(line); rn == '\n' {
line = line[:len(line)-size]
}
lines = append(lines, line)
// stop reading as soon as lineParseAmount is reached or an error occured (most likely EOF)
if len(lines) == cap(lines) || err != nil {
break
}
}
linesCombined := strings.Join(lines, "\n")
return linesCombined, err
}
func replaceVars(format string, vars ...string) string {
replacements := replacePattern.FindAllStringSubmatch(format, -1)
replacements := replacePattern.FindAllStringSubmatch(format, -1) // TODO arguments do not change in outer loop (can be moved to main method)
for _, replacement := range replacements {
rplStr := replacement[0]