Skip to content

Commit

Permalink
Support --files0-from
Browse files Browse the repository at this point in the history
  • Loading branch information
timdp committed Apr 27, 2018
1 parent da0fe2f commit 452bdda
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 99 deletions.
16 changes: 11 additions & 5 deletions README.md
Expand Up @@ -31,14 +31,15 @@ Without any options, `lwc` will count the number of lines, words, and bytes
in standard input, and write them to standard output. Contrary to `wc`, it will
also update standard output while it is still counting.

The following [`wc` options](https://en.wikipedia.org/wiki/Wc_(Unix)) are
currently supported:
All the standard [`wc` options](https://en.wikipedia.org/wiki/Wc_(Unix)) are
supported:

- `--lines` or `-l`
- `--words` or `-w`
- `--chars` or `-m`
- `--bytes` or `-c`
- `--max-line-length` or `-L`
- `--files0-from=F`
- `--help`
- `--version`

Expand All @@ -60,10 +61,15 @@ Run a slow command and count the number of bytes logged:
slow-command | lwc --bytes
```

## TODO
## Caveats

- Support `--files0-from`
- Add tests
- The `--lines` option is currently implemented differently from `wc`'s. Where
`wc` will count the number of newline characters, `lwc` will count the actual
number of lines. Hence, if there is no newline at the end of its input, `lwc`
will still count the line, while `wc` won't.

- While `lwc` is pretty fast, you'll still get better performance out of `wc`.
Benchmarks will be added at some point, but it's currently not a priority.

## JavaScript Version

Expand Down
77 changes: 57 additions & 20 deletions internal/app/lwc/config.go
@@ -1,6 +1,7 @@
package lwc

import (
"bufio"
"fmt"
"time"

Expand All @@ -16,6 +17,7 @@ type Config struct {
Chars bool
Bytes bool
MaxLineLength bool
Files0From string
Interval time.Duration
Help bool
Version bool
Expand All @@ -27,30 +29,65 @@ func (c *Config) PrintUsage() {
c.g.PrintUsage(lwcutil.GetStdout())
}

func BuildConfig(args []string) Config {
func NewConfig(args []string) *Config {
intervalMs := DEFAULT_INTERVAL
g := getopt.New()
var config Config
config.g = g
g.FlagLong(&config.Lines, "lines", 'l', "print the newline counts")
g.FlagLong(&config.Words, "words", 'w', "print the word counts")
g.FlagLong(&config.Chars, "chars", 'm', "print the character counts")
g.FlagLong(&config.Bytes, "bytes", 'c', "print the byte counts")
g.FlagLong(&config.MaxLineLength, "max-line-length", 'L', "print the maximum display width")
g.FlagLong(&intervalMs, "interval", 'i',
var c Config
c.g = getopt.New()
c.g.FlagLong(&c.Lines, "lines", 'l', "print the newline counts")
c.g.FlagLong(&c.Words, "words", 'w', "print the word counts")
c.g.FlagLong(&c.Chars, "chars", 'm', "print the character counts")
c.g.FlagLong(&c.Bytes, "bytes", 'c', "print the byte counts")
c.g.FlagLong(&c.MaxLineLength, "max-line-length", 'L', "print the maximum display width")
c.g.FlagLong(&c.Files0From, "files0-from", 0, "read input from the files specified by NUL-terminated names in file F")
c.g.FlagLong(&intervalMs, "interval", 'i',
fmt.Sprintf("set update interval in ms (default %d ms)", DEFAULT_INTERVAL))
g.FlagLong(&config.Help, "help", 'h', "display this help and exit")
g.FlagLong(&config.Version, "version", 'V', "output version information and exit")
g.Parse(args)
c.g.FlagLong(&c.Help, "help", 'h', "display this help and exit")
c.g.FlagLong(&c.Version, "version", 'V', "output version information and exit")
c.g.Parse(args)
if intervalMs < 0 {
lwcutil.Fatal("Update interval cannot be negative")
}
config.Interval = time.Duration(intervalMs) * time.Millisecond
config.Files = g.Args()
if !(config.Lines || config.Words || config.Chars || config.Bytes) {
config.Lines = true
config.Words = true
config.Bytes = true
c.Interval = time.Duration(intervalMs) * time.Millisecond
c.Files = c.g.Args()
if !(c.Lines || c.Words || c.Chars || c.Bytes || c.MaxLineLength) {
c.Lines = true
c.Words = true
c.Bytes = true
}
return &c
}

func (config *Config) Processors() []Processor {
var temp [5]Processor
i := 0
if config.Lines {
temp[i] = Processor{bufio.ScanLines, ScanCount}
i++
}
if config.Words {
temp[i] = Processor{bufio.ScanWords, ScanCount}
i++
}
if config.Chars {
temp[i] = Processor{bufio.ScanRunes, ScanCount}
i++
}
if config.Bytes {
temp[i] = Processor{bufio.ScanBytes, ScanCount}
i++
}
if config.MaxLineLength {
temp[i] = Processor{bufio.ScanLines, ScanMaxLength}
i++
}
return temp[0:i]
}

func (config *Config) FilesChan() *chan string {
if config.Files0From != "" {
reader := lwcutil.OpenFile(config.Files0From)
return lwcutil.NewFilesChanFromReader(reader, byte(0))
} else {
return lwcutil.NewFilesChanFromSlice(config.Files)
}
return config
}
40 changes: 33 additions & 7 deletions internal/app/lwc/config_test.go
Expand Up @@ -19,6 +19,7 @@ var configTests = []configTest{
[]string{},
Config{
true, true, false, true, false,
"",
time.Duration(DEFAULT_INTERVAL) * time.Millisecond,
false, false,
[]string{},
Expand All @@ -29,6 +30,7 @@ var configTests = []configTest{
[]string{"-w", "--lines"},
Config{
true, true, false, false, false,
"",
time.Duration(DEFAULT_INTERVAL) * time.Millisecond,
false, false,
[]string{},
Expand All @@ -39,6 +41,7 @@ var configTests = []configTest{
[]string{"foo"},
Config{
true, true, false, true, false,
"",
time.Duration(DEFAULT_INTERVAL) * time.Millisecond,
false, false,
[]string{"foo"},
Expand All @@ -49,6 +52,7 @@ var configTests = []configTest{
[]string{"--", "/path/to/file"},
Config{
true, true, false, true, false,
"",
time.Duration(DEFAULT_INTERVAL) * time.Millisecond,
false, false,
[]string{"/path/to/file"},
Expand All @@ -59,6 +63,7 @@ var configTests = []configTest{
[]string{"--max-line-length", "--bytes", "/etc/passwd", "/etc/group"},
Config{
false, false, false, true, true,
"",
time.Duration(DEFAULT_INTERVAL) * time.Millisecond,
false, false,
[]string{"/etc/passwd", "/etc/group"},
Expand All @@ -69,6 +74,7 @@ var configTests = []configTest{
[]string{"-i", "5000"},
Config{
true, true, false, true, false,
"",
time.Duration(5000) * time.Millisecond,
false, false,
[]string{},
Expand All @@ -79,6 +85,7 @@ var configTests = []configTest{
[]string{"--interval=2000"},
Config{
true, true, false, true, false,
"",
time.Duration(2000) * time.Millisecond,
false, false,
[]string{},
Expand All @@ -89,6 +96,7 @@ var configTests = []configTest{
[]string{"--interval", "3000"},
Config{
true, true, false, true, false,
"",
time.Duration(3000) * time.Millisecond,
false, false,
[]string{},
Expand All @@ -99,6 +107,7 @@ var configTests = []configTest{
[]string{"-i", "0"},
Config{
true, true, false, true, false,
"",
time.Duration(0),
false, false,
[]string{},
Expand All @@ -109,27 +118,44 @@ var configTests = []configTest{

func TestBuildConfig(t *testing.T) {
for i, test := range configTests {
actual := BuildConfig(append([]string{"lwc"}, test.args...))
// Clear getopt Set because we don't want to compare it
actual := NewConfig(append([]string{"lwc"}, test.args...))
// Unref getopt to make comparison work
actual.g = nil
if !reflect.DeepEqual(test.expected, actual) {
t.Errorf("Test #%d failed: expecting %#v, got %#v", i, test.expected, actual)
if !reflect.DeepEqual(test.expected, *actual) {
t.Errorf("Test #%d failed: expecting config %#v, got %#v", i, test.expected, actual)
}
}
}

func TestNegativeUpdateIntervalError(t *testing.T) {
BuildConfig([]string{"lwc", "--interval", "-1"})
NewConfig([]string{"lwc", "--interval", "-1"})
if lwcutil.LastError != "Update interval cannot be negative" {
t.Errorf("Expecting update interval error, got %#v", lwcutil.LastError)
}
}

func TestPrintUsage(t *testing.T) {
config := BuildConfig([]string{"lwc"})
config.PrintUsage()
c := NewConfig([]string{"lwc"})
c.PrintUsage()
out := string(lwcutil.FlushStdoutBuffer())
if !strings.HasPrefix(out, "Usage: lwc ") {
t.Errorf("Expecting usage information, got %#v", out)
}
}

func TestConfigProcessors(t *testing.T) {
config := Config{
true, true, true, true, true,
"",
time.Millisecond,
false, false,
[]string{},
nil,
}
actualProcs := config.Processors()
actualCount := len(actualProcs)
expectedCount := 5
if expectedCount != actualCount {
t.Fatalf("Expecting %d processors, got %d", expectedCount, actualCount)
}
}
10 changes: 5 additions & 5 deletions internal/app/lwc/output.go
Expand Up @@ -13,7 +13,7 @@ const CARRIAGE_RETURN byte = 13
const LINE_FEED byte = 10
const SPACE byte = 32

func FormatCounts(counts *[]uint64, label string, cr bool, lf bool) *bytes.Buffer {
func FormatCounts(counts *[]uint64, name string, cr bool, lf bool) *bytes.Buffer {
buf := new(bytes.Buffer)
if cr {
buf.WriteByte(CARRIAGE_RETURN)
Expand All @@ -23,18 +23,18 @@ func FormatCounts(counts *[]uint64, label string, cr bool, lf bool) *bytes.Buffe
buf.WriteByte(SPACE)
buf.WriteString(fmt.Sprintf(COUNT_FORMAT, (*counts)[i]))
}
if label != "" {
if name != "" {
buf.WriteByte(SPACE)
buf.WriteString(label)
buf.WriteString(name)
}
if lf {
buf.WriteByte(LINE_FEED)
}
return buf
}

func PrintCounts(counts *[]uint64, label string, cr bool, lf bool) {
lwcutil.GetStdout().Write(FormatCounts(counts, label, cr, lf).Bytes())
func PrintCounts(counts *[]uint64, name string, cr bool, lf bool) {
lwcutil.GetStdout().Write(FormatCounts(counts, name, cr, lf).Bytes())
}

func PollCounts(name string, counts *[]uint64, interval time.Duration, done chan bool) {
Expand Down
11 changes: 10 additions & 1 deletion internal/app/lwc/output_test.go
Expand Up @@ -94,11 +94,20 @@ func TestFormatCounts(t *testing.T) {
}
}

func TestPrintCounts(t *testing.T) {
func TestPrintNamedCounts(t *testing.T) {
PrintCounts(&[]uint64{1, 2, 3}, "file", true, true)
actual := string(lwcutil.FlushStdoutBuffer())
expected := "\r 1 2 3 file\n"
if expected != actual {
t.Errorf("Expecting %#v, got %#v", expected, actual)
}
}

func TestPrintStdinCounts(t *testing.T) {
PrintCounts(&[]uint64{1, 2, 3}, "", true, true)
actual := string(lwcutil.FlushStdoutBuffer())
expected := "\r 1 2 3\n"
if expected != actual {
t.Errorf("Expecting %#v, got %#v", expected, actual)
}
}

0 comments on commit 452bdda

Please sign in to comment.