├── lib ├── dolumns.dart └── src │ └── dolumns_base.dart ├── analysis_options.yaml ├── .github └── workflows │ └── dart.yml ├── .gitignore ├── pubspec.yaml ├── CHANGELOG.md ├── example └── dolumns_example.dart ├── LICENSE ├── README.md └── test └── dolumns_test.dart /lib/dolumns.dart: -------------------------------------------------------------------------------- 1 | /// Format text output into columns. 2 | /// 3 | library dolumns; 4 | 5 | export 'src/dolumns_base.dart'; 6 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | strong-mode: 5 | implicit-casts: false 6 | implicit-dynamic: false 7 | 8 | linter: 9 | rules: 10 | - always_put_control_body_on_new_line 11 | - curly_braces_in_flow_control_structures 12 | - prefer_final_locals 13 | -------------------------------------------------------------------------------- /.github/workflows/dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | container: 10 | image: dart:2.19 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Install dependencies 14 | run: dart pub get 15 | - name: Run tests 16 | run: dart test 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Files and directories created by pub 2 | .dart_tool/ 3 | .packages 4 | # Remove the following pattern if you wish to check in your lock file 5 | pubspec.lock 6 | 7 | # Conventional directory for build outputs 8 | build/ 9 | 10 | # Directory created by dartdoc 11 | doc/api/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dolumns 2 | description: A Dart library for formatting text output into columns. Useful for printing tabular data in a terminal. 3 | version: 2.0.1 4 | homepage: https://github.com/maks/dolumns 5 | 6 | environment: 7 | sdk: ">=2.18.0 <3.0.0" 8 | 9 | dev_dependencies: 10 | pedantic: ^1.11.1 11 | test: ^1.24.2 12 | 13 | topics: 14 | - formatting 15 | - cli 16 | - strings -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.1 2 | 3 | - Update dependencies 4 | - Add topics 5 | 6 | ## 2.0.0 7 | 8 | - Null-safety stable release 🎉 9 | 10 | ## 2.0.0-nullsafety.0 11 | 12 | - Null-safety pre-release 🎉 13 | 14 | ## 1.2.0 15 | 16 | - header support (thanks @snorkelbuckle) 17 | 18 | ## 1.0.1+1 19 | 20 | - Improve description in pubspec 21 | 22 | ## 1.0.1 23 | 24 | - Allow specifying a custom column splitter string 25 | - Improve documentation 26 | 27 | ## 1.0.0 28 | 29 | - Initial version 30 | -------------------------------------------------------------------------------- /example/dolumns_example.dart: -------------------------------------------------------------------------------- 1 | import 'package:dolumns/dolumns.dart'; 2 | 3 | void main() { 4 | print('<>\n'); 5 | final columns = dolumnify([ 6 | ['provider', '4.0.2'], 7 | ['http', '0.12.0+4'], 8 | ['shared_preferences', '0.5.6+1'], 9 | ['sentry', '3.0.1'], 10 | ], columnSplitter: ' | '); 11 | print(columns); 12 | 13 | print('\n<>\n'); 14 | final columns2 = dolumnify([ 15 | ['PACKAGES', 'VERSION'], 16 | ['provider', '4.0.2'], 17 | ['http', '0.12.0+4'], 18 | ['shared_preferences', '0.5.6+1'], 19 | ['sentry', '3.0.1'], 20 | ], columnSplitter: ' | ', headerIncluded: true, headerSeparator: '='); 21 | print(columns2); 22 | 23 | print('\n<
>\n'); 24 | final columns3 = dolumnify([ 25 | ['PACKAGES', 'VERSION'], 26 | ['http', '0.12.0+4'], 27 | ['shared_preferences', '0.5.6+1'], 28 | ], columnSplitter: ' | ', headerIncluded: true, headerSeparator: '/\\'); 29 | print(columns3); 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020 Maksim Lin. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of copyright holder nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A library for formatting text output into columns. 2 | 3 | ![Dart CI](https://github.com/maks/dolumns/workflows/Dart%20CI/badge.svg) 4 | 5 | ## Usage 6 | 7 | A simple usage example: 8 | 9 | ```dart 10 | import 'package:dolumns/dolumns.dart'; 11 | 12 | main() { 13 | final columns = dolumnify([ 14 | ['provider', '4.0.2'], 15 | ['http', '0.12.0+4'], 16 | ['shared_preferences', '0.5.6+1'], 17 | ['sentry', '3.0.1'], 18 | ]); 19 | print(columns); 20 | } 21 | ``` 22 | 23 | will output: 24 | ``` 25 | provider 4.0.2 26 | http 0.12.0+4 27 | shared_preferences 0.5.6+1 28 | sentry 3.0.1 29 | ``` 30 | 31 | You can also supply a custom column splitter, `(...], columnSplitter: ' | ');` will give: 32 | 33 | ``` 34 | provider | 4.0.2 35 | http | 0.12.0+4 36 | shared_preferences | 0.5.6+1 37 | sentry | 3.0.1 38 | ``` 39 | 40 | Columns with headers are possible with a custom header separator, provide the header in the first list item: 41 | 42 | ```dart 43 | import 'package:dolumns/dolumns.dart'; 44 | 45 | main() { 46 | final columns = dolumnify([ 47 | ['PACKAGES', 'VERSION'], 48 | ['provider', '4.0.2'], 49 | ['http', '0.12.0+4'], 50 | ['shared_preferences', '0.5.6+1'], 51 | ['sentry', '3.0.1'], 52 | ], columnSplitter: ' | ', headerIncluded: true, headerSeparator: '='); 53 | print(columns); 54 | } 55 | ``` 56 | 57 | will output: 58 | 59 | ``` 60 | PACKAGES | VERSION 61 | ============================= 62 | provider | 4.0.2 63 | http | 0.12.0+4 64 | shared_preferences | 0.5.6+1 65 | sentry | 3.0.1 66 | ``` 67 | 68 | see `dolumns_example.dart` example file for full details. 69 | 70 | 71 | Note you can pass in objects of any type, dolumn will call `toString()` on every object passed in. 72 | 73 | API inspired by the [columinfy](https://github.com/timoxley/columnify) npm package. 74 | 75 | ## Features and bugs 76 | 77 | Please file feature requests and bugs at the [issue tracker][tracker]. 78 | 79 | [tracker]: http://github.com/maks/dolumns/issues 80 | -------------------------------------------------------------------------------- /lib/src/dolumns_base.dart: -------------------------------------------------------------------------------- 1 | /// toString() will called on every object passed in 2 | String dolumnify( 3 | List> data, { 4 | String columnSplitter = ' ', 5 | bool headerIncluded = false, 6 | String headerSeparator = ' ', 7 | }) { 8 | final columnLengths = columnMaxLengths(data); 9 | final lines = []; 10 | for (var index = 0; index < data.length; index++) { 11 | final paddedItems = 12 | data[index].mapIndex((f, i) => f.toString().padRight(columnLengths[i])); 13 | lines.add(paddedItems.join(columnSplitter)); 14 | 15 | if ((index == 0) && (headerIncluded == true)) { 16 | final headerSeparatorLine = data[index].mapIndex((f, i) => headerSeparator 17 | .padRight(columnLengths[i], headerSeparator) 18 | .truncate(size: columnLengths[i])); 19 | final headerSeparatorJoiner = 20 | headerSeparator.padRight(columnSplitter.length, headerSeparator); 21 | lines.add(headerSeparatorLine.join(headerSeparatorJoiner)); 22 | } 23 | } 24 | return lines.join('\n'); 25 | } 26 | 27 | List columnMaxLengths(List> data) { 28 | if (data.isEmpty) { 29 | return []; 30 | } 31 | final maxColumnLengths = List.filled(data[0].length, 0); 32 | for (final row in data) { 33 | final columnCount = row.length; 34 | 35 | for (final column in _range(columnCount)) { 36 | final itemLength = row[column].toString().length; 37 | maxColumnLengths[column] = (itemLength > maxColumnLengths[column]) 38 | ? itemLength 39 | : maxColumnLengths[column]; 40 | } 41 | } 42 | return maxColumnLengths; 43 | } 44 | 45 | Iterable _range(int max) => Iterable.generate(max).toList(); 46 | 47 | /// from https://stackoverflow.com/a/59447850/85472 48 | extension ExtendedIterable on Iterable { 49 | /// Like Iterable.map but callback have index as second argument 50 | Iterable mapIndex(T Function(E, int) f) { 51 | var i = 0; 52 | return map((e) => f(e, i++)); 53 | } 54 | 55 | void forEachIndex(void Function(E, int) f) { 56 | var i = 0; 57 | forEach((e) => f(e, i++)); 58 | } 59 | } 60 | 61 | extension DolumnsStringParser on String { 62 | String truncate({int size = 10}) { 63 | // returns truncated string 64 | // the max returned length of string is 'size' 65 | // if size is 0 or less or size greater than string length, then returns empty string 66 | return (size <= 0) || (size > length) ? '' : '${substring(0, size)}'; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/dolumns_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:dolumns/dolumns.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | void main() { 5 | group('format simple lists', () { 6 | test('correct column max lengths from data', () { 7 | final columnmaxLengths = columnMaxLengths([ 8 | ['http', '0.12.0+4'], 9 | ['shared_preferences', '0.5.6+1'], 10 | ['sentry', '3.0.1'], 11 | ]); 12 | expect(columnmaxLengths, [18, 8]); 13 | }); 14 | 15 | test('format single entry', () { 16 | final columns = dolumnify([ 17 | ['provider', '4.0.2'], 18 | ]); 19 | expect(columns, 'provider 4.0.2'); 20 | }); 21 | 22 | test('format multiple entries', () { 23 | final columns = dolumnify([ 24 | ['http', '0.12.0+4'], 25 | ['shared_preferences', '0.5.6+1'], 26 | ['sentry', '3.0.1'], 27 | ]); 28 | expect(columns, 'http 0.12.0+4\nshared_preferences 0.5.6+1 \nsentry 3.0.1 '); 29 | }); 30 | 31 | test('custom column splitter', () { 32 | final columns = dolumnify([ 33 | ['http', '0.12.0+4'], 34 | ['shared_preferences', '0.5.6+1'], 35 | ], columnSplitter: ' - '); 36 | expect(columns, 'http - 0.12.0+4\nshared_preferences - 0.5.6+1 '); 37 | }); 38 | 39 | test('using headers', () { 40 | final columns = dolumnify([ 41 | ['PACKAGES', 'VERSION'], 42 | ['http', '0.12.0+4'], 43 | ['shared_preferences', '0.5.6+1'], 44 | ], columnSplitter: ' - ', headerIncluded: true); 45 | expect(columns, 46 | 'PACKAGES - VERSION \n \nhttp - 0.12.0+4\nshared_preferences - 0.5.6+1 '); 47 | }); 48 | 49 | test('using headers with custom separator', () { 50 | final columns = dolumnify([ 51 | ['PACKAGES', 'VERSION'], 52 | ['http', '0.12.0+4'], 53 | ['shared_preferences', '0.5.6+1'], 54 | ], columnSplitter: ' | ', headerIncluded: true, headerSeparator: '='); 55 | expect(columns, 56 | 'PACKAGES | VERSION \n=============================\nhttp | 0.12.0+4\nshared_preferences | 0.5.6+1 '); 57 | }); 58 | 59 | test('using headers with custom multi-character separator', () { 60 | final columns = dolumnify([ 61 | ['PACKAGES', 'VERSION'], 62 | ['http', '0.12.0+4'], 63 | ['shared_preferences', '0.5.6+1'], 64 | ], columnSplitter: ' | ', headerIncluded: true, headerSeparator: '/\\'); 65 | expect(columns, 66 | 'PACKAGES | VERSION \n/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\/\\\nhttp | 0.12.0+4\nshared_preferences | 0.5.6+1 '); 67 | }); 68 | }); 69 | } 70 | --------------------------------------------------------------------------------