Dan Cojocaru
4 years ago
commit
fc03ea8820
9 changed files with 497 additions and 0 deletions
@ -0,0 +1,9 @@ |
|||||||
|
# Files and directories created by pub. |
||||||
|
.dart_tool/ |
||||||
|
.packages |
||||||
|
|
||||||
|
# Conventional directory for build output. |
||||||
|
build/ |
||||||
|
|
||||||
|
# VS Code |
||||||
|
.vscode |
@ -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/** |
@ -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); |
||||||
|
} |
@ -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)]; |
||||||
|
} |
@ -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 {} |
@ -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" |
@ -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…
Reference in new issue