A command-line tool written in Rust for pretty-printing CSV files grouped by a specified column or field.
brew tap matagus/tap
brew install shelveThe tap lives at matagus/homebrew-tap.
Its formula is bumped automatically whenever a release is published here, so
brew install shelve tracks the latest version without manual maintenance. To
build from main instead of a tagged release, use
brew install --HEAD matagus/tap/shelve.
cargo install shelveA simple command-line tool to pretty print CSV files grouped by a column
Usage: shelve [OPTIONS] [FILENAMES]...
Arguments:
[FILENAMES]...
Options:
-c, --column-number <COLUMN_NUMBER> Column number to group by [default: 1]
--no-headers Treat the first record as data instead of a header row
-d, --delimiter <DELIMITER> Field delimiter character (default: ',') [default: ,]
-h, --help Print help
-V, --version Print version
Note: This block is the verbatim output of
shelve --help. A CI step checks that it stays in sync with the binary; if you add or change a flag, update both the code and this section (or runscripts/update-readme-help.shto regenerate it).
By default shelve requires every row to have the same number of fields as
the header. A short or long row aborts the entire run with exit 1 after the
whole input has been read:
$ printf 'id,a,b\n1,x,y\n2,z\n' | shelve -c 1
Error: CSV error: record 2 (line: 3, byte: 13): found record with 2 fields, but the previous record has 3 fieldsThis catches malformed exports early. If your input has legitimate ragged
rows, pre-process it to pad or truncate fields before piping to shelve.
Given the following CSV file containing data about tasks and their status:
Task ID,Task Title,Status,Assignee,Priority
1,Implement feature A,In Progress,John Doe,High
2,Fix bug B,Done,Jane Doe,Low
3,Write tests for feature A,In Progress,John Doe,Medium
4,Refactor code,To Do,Jane Doe,High
5,Deploy to production A and B,To Do,John Doe,Low
6,Write missing documentation for feature A,Done,Peter Foo,Medium
7,Fix bug C,To Do,Alice Bar,High
8,Write tests for feature A,In Progress,John Doe,LowGrouping by the Status column (column number 3):
shelve -c 3 sample-files/tasks.csv
Done:
2, Fix bug B, Jane Doe, Low
6, Write missing documentation for feature A, Peter Foo, Medium
In Progress:
1, Implement feature A, John Doe, High
3, Write tests for feature A, John Doe, Medium
8, Write tests for feature A, John Doe, Low
To Do:
4, Refactor code, Jane Doe, High
5, Deploy to production A and B, John Doe, Low
7, Fix bug C, Alice Bar, HighGrouping by the Priority column (column number 5):
shelve -c 5 sample-files/tasks.csv
High:
1, Implement feature A, In Progress, John Doe
4, Refactor code, To Do, Jane Doe
7, Fix bug C, To Do, Alice Bar
Low:
2, Fix bug B, Done, Jane Doe
5, Deploy to production A and B, To Do, John Doe
8, Write tests for feature A, In Progress, John Doe
Medium:
3, Write tests for feature A, In Progress, John Doe
6, Write missing documentation for feature A, Done, Peter FooGrouping by the Assignee column (column number 4):
shelve -c 4 sample-files/tasks.csv
Alice Bar:
7, Fix bug C, To Do, High
Jane Doe:
2, Fix bug B, Done, Low
4, Refactor code, To Do, High
John Doe:
1, Implement feature A, In Progress, High
3, Write tests for feature A, In Progress, Medium
5, Deploy to production A and B, To Do, Low
8, Write tests for feature A, In Progress, Low
Peter Foo:
6, Write missing documentation for feature A, Done, MediumThe command can also read input from stdin:
cat sample-files/tasks.csv | shelve -c 5
High:
1, Implement feature A, In Progress, John Doe
4, Refactor code, To Do, Jane Doe
7, Fix bug C, To Do, Alice Bar
Low:
2, Fix bug B, Done, Jane Doe
5, Deploy to production A and B, To Do, John Doe
8, Write tests for feature A, In Progress, John Doe
Medium:
3, Write tests for feature A, In Progress, John Doe
6, Write missing documentation for feature A, Done, Peter FooOr reading multiple files at once:
shelve -c 5 sample-files/tasks.csv sample-files/more-tasks.csvIf your file has no header row, pass --no-headers so the first line is
grouped as data instead of being consumed as column names:
shelve --no-headers -c 1 sample-files/tasks.csvUse -d to change the field delimiter. For a TSV file:
shelve -d $'\t' -c 1 tests/inputs/tasks.tsvOutput:
Alice:
Deploy, 1
Refactor, 3
Bob:
Triage, 2
shelve reads the entire input into memory before printing. Expect peak RSS around 6Γ file size β inherent to grouping every record into a sorted map.
Embedded newlines in fields. A quoted CSV field that contains a newline is
written to stdout verbatim inside data rows. Group headers are the exception:
any \r\n, \r, or \n inside a group key is rendered as the two-character
escape \n so the header always occupies exactly one line and the structural
delimiter between groups stays intact.
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the MIT License - see the LICENSE file for details.
