First kennson version
This commit is contained in:
commit
fc03ea8820
9 changed files with 497 additions and 0 deletions
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
# Files and directories created by pub.
|
||||
.dart_tool/
|
||||
.packages
|
||||
|
||||
# Conventional directory for build output.
|
||||
build/
|
||||
|
||||
# VS Code
|
||||
.vscode
|
5
CHANGELOG.md
Normal file
5
CHANGELOG.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.0
|
||||
|
||||
- Initial version.
|
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# kennson
|
||||
|
||||
A terminal utility to pretty print JSON.
|
16
analysis_options.yaml
Normal file
16
analysis_options.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Defines a default set of lint rules enforced for projects at Google. For
|
||||
# details and rationale, see
|
||||
# https://github.com/dart-lang/pedantic#enabled-lints.
|
||||
|
||||
include: package:pedantic/analysis_options.yaml
|
||||
|
||||
# For lint rules and documentation, see http://dart-lang.github.io/linter/lints.
|
||||
|
||||
# Uncomment to specify additional rules.
|
||||
# linter:
|
||||
# rules:
|
||||
# - camel_case_types
|
||||
|
||||
# analyzer:
|
||||
# exclude:
|
||||
# - path/to/excluded/files/**
|
138
bin/ansi_format.dart
Normal file
138
bin/ansi_format.dart
Normal file
|
@ -0,0 +1,138 @@
|
|||
const String ESC = '\x1b';
|
||||
const String CSI = '$ESC[';
|
||||
|
||||
class TextFormat {
|
||||
final List<TextFormat> children;
|
||||
|
||||
const TextFormat({required this.children});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return children.join('');
|
||||
}
|
||||
}
|
||||
|
||||
class ANSIColorText extends TextFormat {
|
||||
final ANSIColor? foreground;
|
||||
final ANSIColor? background;
|
||||
|
||||
ANSIColorText({this.foreground, this.background, required List<TextFormat> children}) : super(children: children);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var result = '';
|
||||
for (final child in children) {
|
||||
result += foreground?.enabler ?? '';
|
||||
result += background?.backgroundEnabler ?? '';
|
||||
result += child.toString();
|
||||
}
|
||||
result += foreground?.disabler ?? '';
|
||||
result += background?.backgroundDisabler ?? '';
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class ANSIFormat extends TextFormat {
|
||||
final bool? bold;
|
||||
final bool? italic;
|
||||
final bool? underline;
|
||||
final bool? reverseColor;
|
||||
|
||||
ANSIFormat({this.bold, this.italic, this.underline, this.reverseColor, required List<TextFormat> children}) : super(children: children);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
var result = '';
|
||||
for (final child in children) {
|
||||
if (bold != null) {
|
||||
result += bold! ? ANSIBold().enabler : ANSIBold().disabler;
|
||||
}
|
||||
if (italic != null) {
|
||||
result += italic! ? ANSIItalic().enabler : ANSIItalic().disabler;
|
||||
}
|
||||
if (underline != null) {
|
||||
result += underline! ? ANSIUnderline().enabler : ANSIUnderline().disabler;
|
||||
}
|
||||
if (reverseColor != null) {
|
||||
result += reverseColor! ? ANSIReverseVideo().enabler : ANSIReverseVideo().disabler;
|
||||
}
|
||||
result += child.toString();
|
||||
}
|
||||
if (bold != null) {
|
||||
result += bold! ? ANSIBold().disabler : ANSIBold().enabler;
|
||||
}
|
||||
if (italic != null) {
|
||||
result += italic! ? ANSIItalic().disabler : ANSIItalic().enabler;
|
||||
}
|
||||
if (underline != null) {
|
||||
result += underline! ? ANSIUnderline().disabler : ANSIUnderline().enabler;
|
||||
}
|
||||
if (reverseColor != null) {
|
||||
result += reverseColor! ? ANSIReverseVideo().disabler : ANSIReverseVideo().enabler;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class Text extends TextFormat {
|
||||
final String value;
|
||||
const Text(this.value) : super(children: const []);
|
||||
@override
|
||||
String toString() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
class ANSIEscape {
|
||||
final String enabler;
|
||||
final String disabler;
|
||||
|
||||
const ANSIEscape({required this.enabler, required this.disabler});
|
||||
}
|
||||
|
||||
class ANSIBold extends ANSIEscape {
|
||||
const ANSIBold() : super(enabler: '${CSI}1m', disabler: '${CSI}22m');
|
||||
}
|
||||
|
||||
class ANSIItalic extends ANSIEscape {
|
||||
const ANSIItalic() : super(enabler: '${CSI}3m', disabler: '${CSI}23m');
|
||||
}
|
||||
|
||||
class ANSIUnderline extends ANSIEscape {
|
||||
const ANSIUnderline() : super(enabler: '${CSI}4m', disabler: '${CSI}24m');
|
||||
}
|
||||
|
||||
class ANSIReverseVideo extends ANSIEscape {
|
||||
const ANSIReverseVideo() : super(enabler: '${CSI}7m', disabler: '${CSI}27m');
|
||||
}
|
||||
|
||||
class ANSIColor extends ANSIEscape {
|
||||
const ANSIColor.index(int index, [bool bright = false]) : super(enabler: '$CSI${bright ? 9 : 3}${index}m', disabler: '${CSI}39m');
|
||||
const ANSIColor.value(String value) : super(enabler: '$CSI${value}m', disabler: '${CSI}39m');
|
||||
String get backgroundEnabler {
|
||||
final noCSI = enabler.substring(CSI.length);
|
||||
final nom = noCSI.substring(0, noCSI.length - 1);
|
||||
final number = int.parse(nom);
|
||||
return '$CSI${number + 10}m';
|
||||
}
|
||||
final String backgroundDisabler = '${CSI}49m';
|
||||
}
|
||||
|
||||
class ANSIColors {
|
||||
static const ANSIColor black = ANSIColor.index(0);
|
||||
static const ANSIColor red = ANSIColor.index(1);
|
||||
static const ANSIColor green = ANSIColor.index(2);
|
||||
static const ANSIColor yellow = ANSIColor.index(3);
|
||||
static const ANSIColor blue = ANSIColor.index(4);
|
||||
static const ANSIColor magenta = ANSIColor.index(5);
|
||||
static const ANSIColor cyan = ANSIColor.index(6);
|
||||
static const ANSIColor white = ANSIColor.index(7);
|
||||
static const ANSIColor brightBlack = ANSIColor.index(0, true);
|
||||
static const ANSIColor brightRed = ANSIColor.index(1, true);
|
||||
static const ANSIColor brightGreen = ANSIColor.index(2, true);
|
||||
static const ANSIColor brightYellow = ANSIColor.index(3, true);
|
||||
static const ANSIColor brightBlue = ANSIColor.index(4, true);
|
||||
static const ANSIColor brightMagenta = ANSIColor.index(5, true);
|
||||
static const ANSIColor brightCyan = ANSIColor.index(6, true);
|
||||
static const ANSIColor brightWhite = ANSIColor.index(7, true);
|
||||
}
|
105
bin/kennson.dart
Normal file
105
bin/kennson.dart
Normal file
|
@ -0,0 +1,105 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:json_path/json_path.dart';
|
||||
import 'package:rfc_6901/rfc_6901.dart';
|
||||
|
||||
import 'ppjson.dart';
|
||||
|
||||
void main(List<String> arguments) {
|
||||
final parser = ArgParser()
|
||||
..addOption('file', abbr: 'f', help: 'Read JSON from file instead of stdin', valueHelp: 'filename')
|
||||
..addOption('input', help: 'Read input as parameter instead of stdin', valueHelp: 'json input')
|
||||
..addOption('jsonpath', aliases: ['path'], help: 'Display only the matches of the JSON document', valueHelp: 'JSONPath query')
|
||||
..addOption('jsonpointer', aliases: ['pointer'], abbr: 'p', help: 'Display only the matches of the JSON pointer')
|
||||
..addOption('indent', abbr: 'i', help: 'Set space indentation level (prefix with t for tab indentation)', defaultsTo: '2')
|
||||
..addOption('max-depth', abbr: 'm', help: 'Specify maximum nesting before stopping printing');
|
||||
// ..addFlag('force-color', help: "Output using colors even when the environment doesn't allow them", hide: true);
|
||||
final ArgResults parsedArgs;
|
||||
try {
|
||||
parsedArgs = parser.parse(arguments);
|
||||
}
|
||||
on ArgParserException catch(e) {
|
||||
print(e.message);
|
||||
print('');
|
||||
print(parser.usage);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Get indent
|
||||
final indentArg = parsedArgs['indent'] as String;
|
||||
final indent = indentArg[0] == 't' ? '\t' * int.parse(indentArg.substring(1)) : ' ' * int.parse(indentArg);
|
||||
|
||||
// Get max depth
|
||||
final maxDepthArg = parsedArgs['max-depth'] as String?;
|
||||
final maxDepth = int.tryParse(maxDepthArg ?? '');
|
||||
|
||||
// Get JSONPath
|
||||
final jsonPath = parsedArgs['jsonpath'] as String?;
|
||||
|
||||
// Get JSON Pointer
|
||||
final jsonPointer = parsedArgs['jsonpointer'] as String?;
|
||||
|
||||
// Read
|
||||
String jsonInput;
|
||||
if (parsedArgs['file'] != null) {
|
||||
jsonInput = File(parsedArgs['file']).readAsStringSync();
|
||||
}
|
||||
else if (parsedArgs['input'] != null) {
|
||||
jsonInput = parsedArgs['input'];
|
||||
}
|
||||
else {
|
||||
jsonInput = '';
|
||||
while (true) {
|
||||
final line = stdin.readLineSync(retainNewlines: true);
|
||||
if (line != null) {
|
||||
jsonInput += line;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse
|
||||
final decodedData;
|
||||
try {
|
||||
decodedData = jsonDecode(jsonInput);
|
||||
}
|
||||
catch(_) {
|
||||
stderr.writeln('Unable to parse JSON input.');
|
||||
exit(1);
|
||||
}
|
||||
final List<dynamic> data;
|
||||
try {
|
||||
data = jsonPath != null ? followJsonPath(decodedData, jsonPath) : jsonPointer != null ? followJsonPointer(decodedData, jsonPointer) : [decodedData];
|
||||
}
|
||||
catch (e) {
|
||||
stderr.writeln('JSONPath Error: $e');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Print
|
||||
if (data.isEmpty) {
|
||||
stderr.writeln('No match');
|
||||
}
|
||||
else if (data.length == 1) {
|
||||
stdout.write(prettyPrintJson(data[0], indent, maxDepth));
|
||||
}
|
||||
else {
|
||||
for (final match in data) {
|
||||
print(prettyPrintJson(match, indent, maxDepth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<dynamic> followJsonPath(dynamic object, String jsonPath) {
|
||||
final path = JsonPath(jsonPath);
|
||||
return path.read(object).map((e) => e.value).toList(growable: false);
|
||||
}
|
||||
|
||||
List<dynamic> followJsonPointer(dynamic object, String jsonPointer) {
|
||||
final pointer = JsonPointer(jsonPointer);
|
||||
return [pointer.read(object)];
|
||||
}
|
158
bin/ppjson.dart
Normal file
158
bin/ppjson.dart
Normal file
|
@ -0,0 +1,158 @@
|
|||
import 'ansi_format.dart';
|
||||
|
||||
String prettyPrintJson(dynamic object, String indent, [int? maxDepth]) {
|
||||
var result = '';
|
||||
final stack = <_PrettyPrintStackObject>[];
|
||||
stack.add(_PrettyPrintStackObject(object, 0));
|
||||
while (stack.isNotEmpty) {
|
||||
final elem = stack.removeLast();
|
||||
final obj = elem.object is _PrettyPrintTrailingComma ? elem.object.object : elem.object;
|
||||
final trailingComma = elem.object is _PrettyPrintTrailingComma;
|
||||
var suppressTrailingComma = false;
|
||||
|
||||
result += indent * elem.level;
|
||||
String formatObject(dynamic obj) {
|
||||
if (obj is Map) {
|
||||
String startStr;
|
||||
if (obj.isEmpty) {
|
||||
startStr = '{}';
|
||||
}
|
||||
else if (elem.level == maxDepth) {
|
||||
startStr = '{...}';
|
||||
}
|
||||
else {
|
||||
startStr = '{';
|
||||
suppressTrailingComma = true;
|
||||
}
|
||||
final thisResult = ANSIColorText(
|
||||
foreground: ANSIColors.blue,
|
||||
children: [Text(startStr),],
|
||||
).toString();
|
||||
if (elem.level != maxDepth && obj.isNotEmpty) {
|
||||
stack.add(_PrettyPrintStackObject(trailingComma ? _PrettyPrintTrailingComma(_PrettyPrintMapEnd()) : _PrettyPrintMapEnd(), elem.level));
|
||||
final entries = obj.entries;
|
||||
stack.add(_PrettyPrintStackObject(entries.last, elem.level + 1));
|
||||
stack.addAll(entries.toList(growable: false).reversed.skip(1).map((e) => _PrettyPrintStackObject(_PrettyPrintTrailingComma(e), elem.level + 1)));
|
||||
}
|
||||
return thisResult;
|
||||
}
|
||||
else if (obj is List) {
|
||||
String startStr;
|
||||
if (obj.isEmpty) {
|
||||
startStr = '[]';
|
||||
}
|
||||
else if (elem.level == maxDepth) {
|
||||
startStr = '[...]';
|
||||
}
|
||||
else {
|
||||
startStr = '[';
|
||||
suppressTrailingComma = true;
|
||||
}
|
||||
final thisResult = ANSIColorText(
|
||||
foreground: ANSIColors.cyan,
|
||||
children: [Text(startStr),],
|
||||
).toString();
|
||||
if (elem.level != maxDepth && obj.isNotEmpty) {
|
||||
stack.add(_PrettyPrintStackObject(trailingComma ? _PrettyPrintTrailingComma(_PrettyPrintArrayEnd()) : _PrettyPrintArrayEnd(), elem.level));
|
||||
stack.add(_PrettyPrintStackObject(obj.last, elem.level + 1));
|
||||
stack.addAll(obj.reversed.skip(1).map((o) => _PrettyPrintStackObject(_PrettyPrintTrailingComma(o), elem.level + 1)));
|
||||
}
|
||||
return thisResult;
|
||||
}
|
||||
else if (obj is MapEntry) {
|
||||
return TextFormat(
|
||||
children: [
|
||||
ANSIColorText(
|
||||
foreground: ANSIColors.brightYellow,
|
||||
children: [Text('"${obj.key}"'),]
|
||||
),
|
||||
Text(': '),
|
||||
Text(formatObject(obj.value)),
|
||||
],
|
||||
).toString();
|
||||
}
|
||||
else if (obj is String) {
|
||||
return ANSIColorText(
|
||||
foreground: ANSIColors.magenta,
|
||||
children: [Text('"$obj"'),],
|
||||
).toString();
|
||||
}
|
||||
else if (obj is double || obj is int) {
|
||||
return ANSIColorText(
|
||||
foreground: ANSIColors.brightCyan,
|
||||
children: [Text(obj.toString()),],
|
||||
).toString();
|
||||
}
|
||||
else if (obj == true) {
|
||||
return ANSIFormat(
|
||||
underline: true,
|
||||
children: [
|
||||
ANSIColorText(
|
||||
foreground: ANSIColors.green,
|
||||
children: [Text(obj.toString()),],
|
||||
),
|
||||
],
|
||||
).toString();
|
||||
}
|
||||
else if (obj == false) {
|
||||
return ANSIFormat(
|
||||
underline: true,
|
||||
children: [
|
||||
ANSIColorText(
|
||||
foreground: ANSIColors.red,
|
||||
children: [Text(obj.toString()),],
|
||||
),
|
||||
],
|
||||
).toString();
|
||||
}
|
||||
else if (obj == null) {
|
||||
return ANSIFormat(
|
||||
underline: true,
|
||||
children: [
|
||||
ANSIColorText(
|
||||
foreground: ANSIColors.brightBlue,
|
||||
children: [Text('null'),],
|
||||
),
|
||||
],
|
||||
).toString();
|
||||
}
|
||||
else if (obj is _PrettyPrintMapEnd) {
|
||||
return ANSIColorText(
|
||||
foreground: ANSIColors.blue,
|
||||
children: [Text('}'),],
|
||||
).toString();
|
||||
}
|
||||
else if (obj is _PrettyPrintArrayEnd) {
|
||||
return ANSIColorText(
|
||||
foreground: ANSIColors.cyan,
|
||||
children: [Text(']'),],
|
||||
).toString();
|
||||
}
|
||||
else {
|
||||
throw Exception('Unexpected object of unknown type: $obj ${obj.runtimeType}');
|
||||
}
|
||||
}
|
||||
result += formatObject(obj);
|
||||
|
||||
if (trailingComma && !suppressTrailingComma) {
|
||||
result += ',';
|
||||
}
|
||||
|
||||
result += '\n';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
class _PrettyPrintStackObject {
|
||||
final dynamic object;
|
||||
final int level;
|
||||
_PrettyPrintStackObject(this.object, this.level);
|
||||
}
|
||||
|
||||
class _PrettyPrintTrailingComma {
|
||||
final dynamic object;
|
||||
_PrettyPrintTrailingComma(this.object);
|
||||
}
|
||||
|
||||
class _PrettyPrintMapEnd {}
|
||||
class _PrettyPrintArrayEnd {}
|
47
pubspec.lock
Normal file
47
pubspec.lock
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
args:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: args
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
json_path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: json_path
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
pedantic:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: pedantic
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
petitparser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
rfc_6901:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: rfc_6901
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.0"
|
||||
sdks:
|
||||
dart: ">=2.13.0 <3.0.0"
|
16
pubspec.yaml
Normal file
16
pubspec.yaml
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: kennson
|
||||
description: A simple command-line application.
|
||||
version: 1.0.0
|
||||
# homepage: https://www.example.com
|
||||
|
||||
environment:
|
||||
sdk: '>=2.12.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
args: ^2.2.0
|
||||
json_path: ^0.3.0
|
||||
rfc_6901: ^0.1.0
|
||||
# path: ^1.8.0
|
||||
|
||||
dev_dependencies:
|
||||
pedantic: ^1.10.0
|
Loading…
Add table
Reference in a new issue