From dc7fda1eed240d416dad44e929dad5708f89fd57 Mon Sep 17 00:00:00 2001 From: mdnapo Date: Sat, 28 Sep 2024 13:29:53 +0200 Subject: [PATCH] Add JSON path to CLI and refactored --- .../JsonParserTests.cs | 5 +- DevDisciples.Json.Parser/JsonParser.cs | 2 +- .../Controllers/TransformController.cs | 4 +- .../GlobalExceptionHandler.cs | 2 +- ...{PrettifyRequest.cs => BeautifyRequest.cs} | 2 +- .../src/app/app.routes.ts | 4 +- .../beautify.component.html} | 2 +- .../beautify.component.scss} | 0 .../beautify.component.spec.ts} | 12 ++--- .../beautify.component.ts} | 10 ++-- .../src/app/json-transform.service.ts | 4 +- .../src/app/layout/layout.component.html | 2 +- DevDisciples.Json.Tools.CLI/CommandOptions.cs | 2 +- .../Extensions/CommandExtensions.cs | 5 +- .../{IOHandler.cs => InputOutputHandler.cs} | 20 ++++---- .../{InputOptions.cs => InputSource.cs} | 2 +- .../Json2CSharpCommand.cs | 6 +-- ... => JsonBeautifyCommand.CommandOptions.cs} | 2 +- ...onBeautifyCommand.CommandOptionsBinder.cs} | 2 +- ...ttifyCommand.cs => JsonBeautifyCommand.cs} | 13 +++--- .../JsonPathCommand.CommandOptions.cs | 9 ++++ .../JsonPathCommand.CommandOptionsBinder.cs | 16 +++++++ .../JsonPathCommand.cs | 32 +++++++++++++ .../JsonUglifyCommand.cs | 6 +-- DevDisciples.Json.Tools.CLI/RootCommand.cs | 3 +- .../SharedCommandOptions.cs | 4 +- .../scripts/publish_as_tool.sh | 0 .../Json2CSharpTranslator.ClassTranslation.cs | 2 + .../Json2CSharpTranslator.cs | 21 +++++---- DevDisciples.Json.Tools/JsonFormatter.cs | 15 +++--- .../JsonPath.Interpreter.cs | 46 +++++++++---------- DevDisciples.Json.Tools/JsonPath.Parser.cs | 4 +- DevDisciples.Json.Tools/JsonPath.Syntax.cs | 1 - DevDisciples.Parsing/Lexer.Rule.cs | 4 +- DevDisciples.Parsing/Lexer.cs | 2 +- DevDisciples.Parsing/ParserContext.cs | 6 +-- DevDisciples.Parsing/ParsingException.cs | 16 ------- DevDisciples.Parsing/Report.cs | 8 ++-- DevDisciples.Parsing/SyntaxException.cs | 16 +++++++ 39 files changed, 188 insertions(+), 124 deletions(-) rename DevDisciples.Json.Tools.API/Requests/{PrettifyRequest.cs => BeautifyRequest.cs} (72%) rename DevDisciples.Json.Tools.App/src/app/{prettify/prettify.component.html => beautify/beautify.component.html} (89%) rename DevDisciples.Json.Tools.App/src/app/{prettify/prettify.component.scss => beautify/beautify.component.scss} (100%) rename DevDisciples.Json.Tools.App/src/app/{prettify/prettify.component.spec.ts => beautify/beautify.component.spec.ts} (52%) rename DevDisciples.Json.Tools.App/src/app/{prettify/prettify.component.ts => beautify/beautify.component.ts} (86%) rename DevDisciples.Json.Tools.CLI/{IOHandler.cs => InputOutputHandler.cs} (78%) rename DevDisciples.Json.Tools.CLI/{InputOptions.cs => InputSource.cs} (80%) rename DevDisciples.Json.Tools.CLI/{JsonPrettifyCommand.CommandOptions.cs => JsonBeautifyCommand.CommandOptions.cs} (75%) rename DevDisciples.Json.Tools.CLI/{JsonPrettifyCommand.CommandOptionsBinder.cs => JsonBeautifyCommand.CommandOptionsBinder.cs} (89%) rename DevDisciples.Json.Tools.CLI/{JsonPrettifyCommand.cs => JsonBeautifyCommand.cs} (60%) create mode 100644 DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptions.cs create mode 100644 DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptionsBinder.cs create mode 100644 DevDisciples.Json.Tools.CLI/JsonPathCommand.cs mode change 100644 => 100755 DevDisciples.Json.Tools.CLI/scripts/publish_as_tool.sh delete mode 100644 DevDisciples.Parsing/ParsingException.cs create mode 100644 DevDisciples.Parsing/SyntaxException.cs diff --git a/DevDisciples.Json.Parser.Tests/JsonParserTests.cs b/DevDisciples.Json.Parser.Tests/JsonParserTests.cs index 8c9bdf8..4ba0207 100644 --- a/DevDisciples.Json.Parser.Tests/JsonParserTests.cs +++ b/DevDisciples.Json.Parser.Tests/JsonParserTests.cs @@ -1,4 +1,5 @@ using DevDisciples.Json.Parser.Syntax; +using DevDisciples.Parsing.Extensions; namespace DevDisciples.Json.Parser.Tests; @@ -110,7 +111,7 @@ public class JsonParserTests var node = JsonParser.Parse(nameof(Can_parse_an_object_with_one_entry), source); Assert.That(node is JsonObjectSyntax { Properties.Length: 1 }); - var @object = (JsonObjectSyntax)node; + var @object = node.As(); Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name")); Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" }); } @@ -124,7 +125,7 @@ public class JsonParserTests Assert.Multiple(() => { Assert.That(node is JsonObjectSyntax { Properties.Length: 2 }); - var @object = (JsonObjectSyntax)node; + var @object = node.As(); Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "first_name")); Assert.That(@object.Properties.First(property => property.Key.Lexeme == "first_name").Value is JsonStringSyntax { Value: "John" }); Assert.That(@object.Properties.Any(property => property.Key.Lexeme == "last_name")); diff --git a/DevDisciples.Json.Parser/JsonParser.cs b/DevDisciples.Json.Parser/JsonParser.cs index 83f3110..017b069 100644 --- a/DevDisciples.Json.Parser/JsonParser.cs +++ b/DevDisciples.Json.Parser/JsonParser.cs @@ -33,7 +33,7 @@ public static partial class JsonParser if (ctx.Match(JsonToken.True) || ctx.Match(JsonToken.False)) return BoolExpression(ctx); - throw Report.Error(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'"); + throw Report.SyntaxException(ctx.Current, $"Expected a JSON expression, got '{ctx.Current.Lexeme}'"); } private static IJsonSyntax ArrayExpression(ParserContext ctx) diff --git a/DevDisciples.Json.Tools.API/Controllers/TransformController.cs b/DevDisciples.Json.Tools.API/Controllers/TransformController.cs index 1d0ac0f..e1a1f3d 100644 --- a/DevDisciples.Json.Tools.API/Controllers/TransformController.cs +++ b/DevDisciples.Json.Tools.API/Controllers/TransformController.cs @@ -17,8 +17,8 @@ public class TransformController : ControllerBase return Ok(new { Result = result }); } - [HttpPost("prettify")] - public IActionResult Prettify([FromBody] PrettifyRequest request) + [HttpPost("beautify")] + public IActionResult Beautify([FromBody] BeautifyRequest request) { var context = new JsonFormatter.Context { Beautify = true, IndentSize = request.IndentSize }; diff --git a/DevDisciples.Json.Tools.API/GlobalExceptionHandler.cs b/DevDisciples.Json.Tools.API/GlobalExceptionHandler.cs index 0a54070..e55066c 100644 --- a/DevDisciples.Json.Tools.API/GlobalExceptionHandler.cs +++ b/DevDisciples.Json.Tools.API/GlobalExceptionHandler.cs @@ -23,7 +23,7 @@ public class GlobalExceptionHandler : IExceptionHandler { switch (exception) { - case ParsingException: + case SyntaxException: _logger.LogError(exception, "An exception occurred: {Message}", exception.Message); var problem = new ProblemDetails diff --git a/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs b/DevDisciples.Json.Tools.API/Requests/BeautifyRequest.cs similarity index 72% rename from DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs rename to DevDisciples.Json.Tools.API/Requests/BeautifyRequest.cs index dbd8875..0478cb9 100644 --- a/DevDisciples.Json.Tools.API/Requests/PrettifyRequest.cs +++ b/DevDisciples.Json.Tools.API/Requests/BeautifyRequest.cs @@ -1,6 +1,6 @@ namespace DevDisciples.Json.Tools.API.Requests; -public class PrettifyRequest : TransformRequest +public class BeautifyRequest : TransformRequest { public int IndentSize { get; init; } = JsonFormatter.Context.DefaultIndentSize; } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.App/src/app/app.routes.ts b/DevDisciples.Json.Tools.App/src/app/app.routes.ts index 132ba24..99fcf6f 100644 --- a/DevDisciples.Json.Tools.App/src/app/app.routes.ts +++ b/DevDisciples.Json.Tools.App/src/app/app.routes.ts @@ -1,13 +1,13 @@ import { Routes } from '@angular/router'; import {HomeComponent} from "./home/home.component"; -import {PrettifyComponent} from "./prettify/prettify.component"; +import {BeautifyComponent} from "./beautify/beautify.component"; import {UglifyComponent} from "./uglify/uglify.component"; import {Json2CsharpComponent} from "./json2csharp/json2-csharp.component"; import {JsonPathComponent} from "./json-path/json-path.component"; export const routes: Routes = [ { path: 'home', component: HomeComponent }, - { path: 'prettify', component: PrettifyComponent }, + { path: 'beautify', component: BeautifyComponent }, { path: 'uglify', component: UglifyComponent }, { path: 'json2csharp', component: Json2CsharpComponent }, { path: 'jsonpath', component: JsonPathComponent }, diff --git a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html b/DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.html similarity index 89% rename from DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html rename to DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.html index f4491c3..b6312ae 100644 --- a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.html +++ b/DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.html @@ -1,4 +1,4 @@ -

JSON Prettify

+

JSON Beautify

{ - let component: PrettifyComponent; - let fixture: ComponentFixture; +describe('BeautifyComponent', () => { + let component: BeautifyComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [PrettifyComponent] + imports: [BeautifyComponent] }) .compileComponents(); - fixture = TestBed.createComponent(PrettifyComponent); + fixture = TestBed.createComponent(BeautifyComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.ts b/DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.ts similarity index 86% rename from DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.ts rename to DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.ts index 48358e6..cfd7bc7 100644 --- a/DevDisciples.Json.Tools.App/src/app/prettify/prettify.component.ts +++ b/DevDisciples.Json.Tools.App/src/app/beautify/beautify.component.ts @@ -12,17 +12,17 @@ import { import {BehaviorSubject, debounceTime} from "rxjs"; @Component({ - selector: 'app-prettify', + selector: 'app-beautify', standalone: true, imports: [ EditorComponent, FormsModule, InputOutputComponent ], - templateUrl: './prettify.component.html', - styleUrl: './prettify.component.scss' + templateUrl: './beautify.component.html', + styleUrl: './beautify.component.scss' }) -export class PrettifyComponent implements OnInit { +export class BeautifyComponent implements OnInit { // input: string = ; $input: BehaviorSubject = new BehaviorSubject(GenerateDefaultJsonObjectString()); inputOptions = MonacoJsonConfig; @@ -43,7 +43,7 @@ export class PrettifyComponent implements OnInit { update(input: string): void { this.service - .prettify(input) + .beautify(input) .subscribe({ next: response => { console.log(response); diff --git a/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts b/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts index 5f8dcae..e47bfd5 100644 --- a/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts +++ b/DevDisciples.Json.Tools.App/src/app/json-transform.service.ts @@ -11,8 +11,8 @@ export class JsonTransformService { constructor(private http: HttpClient) { } - public prettify(source: string): Observable> { - return this.http.post(`${this.url}/api/transform/prettify`, {Source: source}, {observe: "response"}); + public beautify(source: string): Observable> { + return this.http.post(`${this.url}/api/transform/beautify`, {Source: source}, {observe: "response"}); } public uglify(source: string): Observable> { diff --git a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html index 32aa4f2..5f9d846 100644 --- a/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html +++ b/DevDisciples.Json.Tools.App/src/app/layout/layout.component.html @@ -3,7 +3,7 @@ Menu Home - Prettify + Beautify Uglify JSON to C# JSON Path diff --git a/DevDisciples.Json.Tools.CLI/CommandOptions.cs b/DevDisciples.Json.Tools.CLI/CommandOptions.cs index ac581ca..f925ec1 100644 --- a/DevDisciples.Json.Tools.CLI/CommandOptions.cs +++ b/DevDisciples.Json.Tools.CLI/CommandOptions.cs @@ -2,7 +2,7 @@ namespace DevDisciples.Json.Tools.CLI; public class CommandOptions { - public InputOptions Input { get; set; } + public InputSource Input { get; set; } public FileInfo? InputFile { get; set; } public string? InputArgument { get; set; } public OutputOptions Output { get; set; } diff --git a/DevDisciples.Json.Tools.CLI/Extensions/CommandExtensions.cs b/DevDisciples.Json.Tools.CLI/Extensions/CommandExtensions.cs index b3646ed..80d8caf 100644 --- a/DevDisciples.Json.Tools.CLI/Extensions/CommandExtensions.cs +++ b/DevDisciples.Json.Tools.CLI/Extensions/CommandExtensions.cs @@ -4,13 +4,12 @@ namespace DevDisciples.Json.Tools.CLI.Extensions; public static class CommandExtensions { - public static void AddIOCommandOptions(this Command command) + public static void AddInputOutputCommandOptions(this Command command) { command.AddOption(SharedCommandOptions.InputOption); command.AddOption(SharedCommandOptions.InputFileOption); + command.AddArgument(SharedCommandOptions.InputArgument); command.AddOption(SharedCommandOptions.OutputOption); command.AddOption(SharedCommandOptions.OutputFileOption); - - command.AddArgument(SharedCommandOptions.InputArgument); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/IOHandler.cs b/DevDisciples.Json.Tools.CLI/InputOutputHandler.cs similarity index 78% rename from DevDisciples.Json.Tools.CLI/IOHandler.cs rename to DevDisciples.Json.Tools.CLI/InputOutputHandler.cs index 5133402..15645c8 100644 --- a/DevDisciples.Json.Tools.CLI/IOHandler.cs +++ b/DevDisciples.Json.Tools.CLI/InputOutputHandler.cs @@ -2,28 +2,28 @@ namespace DevDisciples.Json.Tools.CLI; -public static class IOHandler +public static class InputOutputHandler { - public static async Task HandleInput(InputOptions input, string? inputArgument, FileInfo? inputFile) + public static async Task HandleInput(InputSource input, string? inputArgument, FileInfo? inputFile) { string json; switch (input) { - case InputOptions.s: - case InputOptions.std: - case InputOptions.stdin: + case InputSource.s: + case InputSource.std: + case InputSource.stdin: json = inputArgument ?? string.Empty; break; - case InputOptions.c: - case InputOptions.clip: - case InputOptions.clipboard: + case InputSource.c: + case InputSource.clip: + case InputSource.clipboard: json = await ClipboardService.GetTextAsync() ?? string.Empty; break; - case InputOptions.f: - case InputOptions.file: + case InputSource.f: + case InputSource.file: if (inputFile is null) throw new ArgumentException("Input file was not specified."); diff --git a/DevDisciples.Json.Tools.CLI/InputOptions.cs b/DevDisciples.Json.Tools.CLI/InputSource.cs similarity index 80% rename from DevDisciples.Json.Tools.CLI/InputOptions.cs rename to DevDisciples.Json.Tools.CLI/InputSource.cs index f610416..9b6af24 100644 --- a/DevDisciples.Json.Tools.CLI/InputOptions.cs +++ b/DevDisciples.Json.Tools.CLI/InputSource.cs @@ -1,6 +1,6 @@ namespace DevDisciples.Json.Tools.CLI; -public enum InputOptions +public enum InputSource { // Standard input stdin, diff --git a/DevDisciples.Json.Tools.CLI/Json2CSharpCommand.cs b/DevDisciples.Json.Tools.CLI/Json2CSharpCommand.cs index 1cb9202..f010651 100644 --- a/DevDisciples.Json.Tools.CLI/Json2CSharpCommand.cs +++ b/DevDisciples.Json.Tools.CLI/Json2CSharpCommand.cs @@ -23,7 +23,7 @@ public partial class Json2CSharpCommand : Command AddAlias("2cs"); AddAlias("2c#"); - this.AddIOCommandOptions(); + this.AddInputOutputCommandOptions(); AddOption(RootClassNameOption); AddOption(NamespaceOption); @@ -32,7 +32,7 @@ public partial class Json2CSharpCommand : Command private static async Task ExecuteAsync(CommandOptions options) { - var json = await IOHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); + var json = await InputOutputHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); var output = Json2CSharpTranslator.Translate(json, new() { @@ -40,6 +40,6 @@ public partial class Json2CSharpCommand : Command Namespace = options.Namespace ?? Json2CSharpTranslator.Context.DefaultNamespace }); - await IOHandler.HandleOutput(options.Output, options.OutputFile, output); + await InputOutputHandler.HandleOutput(options.Output, options.OutputFile, output); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptions.cs b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptions.cs similarity index 75% rename from DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptions.cs rename to DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptions.cs index 48cd598..d6f69f3 100644 --- a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptions.cs +++ b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptions.cs @@ -1,6 +1,6 @@ namespace DevDisciples.Json.Tools.CLI; -public partial class JsonPrettifyCommand +public partial class JsonBeautifyCommand { public class CommandOptions : CLI.CommandOptions { diff --git a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptionsBinder.cs b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptionsBinder.cs similarity index 89% rename from DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptionsBinder.cs rename to DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptionsBinder.cs index 8b14d7b..e7ef00c 100644 --- a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.CommandOptionsBinder.cs +++ b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.CommandOptionsBinder.cs @@ -3,7 +3,7 @@ using DevDisciples.Json.Tools.CLI.Extensions; namespace DevDisciples.Json.Tools.CLI; -public partial class JsonPrettifyCommand +public partial class JsonBeautifyCommand { public class CommandOptionsBinder : BinderBase { diff --git a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.cs b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.cs similarity index 60% rename from DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.cs rename to DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.cs index 797ec29..cbaa7f7 100644 --- a/DevDisciples.Json.Tools.CLI/JsonPrettifyCommand.cs +++ b/DevDisciples.Json.Tools.CLI/JsonBeautifyCommand.cs @@ -3,23 +3,22 @@ using DevDisciples.Json.Tools.CLI.Extensions; namespace DevDisciples.Json.Tools.CLI; -public partial class JsonPrettifyCommand : Command +public partial class JsonBeautifyCommand : Command { private static readonly Option IndentSizeOption = new(aliases: ["--indent", "--is"], description: "The indent size", getDefaultValue: () => 2); - public JsonPrettifyCommand() : base("prettify", "Prettify JSON") + public JsonBeautifyCommand() : base("beautify", "Beautify JSON") { - AddAlias("p"); - this.AddIOCommandOptions(); + AddAlias("b"); + this.AddInputOutputCommandOptions(); AddOption(IndentSizeOption); - this.SetHandler(ExecuteAsync, new CommandOptionsBinder()); } private static async Task ExecuteAsync(CommandOptions options) { - var json = await IOHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); + var json = await InputOutputHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); var output = JsonFormatter.Format(json, new() { @@ -27,6 +26,6 @@ public partial class JsonPrettifyCommand : Command IndentSize = options.IndentSize }); - await IOHandler.HandleOutput(options.Output, options.OutputFile, output); + await InputOutputHandler.HandleOutput(options.Output, options.OutputFile, output); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptions.cs b/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptions.cs new file mode 100644 index 0000000..6e33780 --- /dev/null +++ b/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptions.cs @@ -0,0 +1,9 @@ +namespace DevDisciples.Json.Tools.CLI; + +public partial class JsonPathCommand +{ + public class CommandOptions : CLI.CommandOptions + { + public string PathExpression { get; init; } + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptionsBinder.cs b/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptionsBinder.cs new file mode 100644 index 0000000..f563320 --- /dev/null +++ b/DevDisciples.Json.Tools.CLI/JsonPathCommand.CommandOptionsBinder.cs @@ -0,0 +1,16 @@ +using System.CommandLine.Binding; +using DevDisciples.Json.Tools.CLI.Extensions; + +namespace DevDisciples.Json.Tools.CLI; + +public partial class JsonPathCommand +{ + public class CommandOptionsBinder : BinderBase + { + protected override CommandOptions GetBoundValue(BindingContext context) => + new CommandOptions + { + PathExpression = context.ParseResult.GetValueForOption(PathExpressionOption) + }.WithCommonBindings(context); + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/JsonPathCommand.cs b/DevDisciples.Json.Tools.CLI/JsonPathCommand.cs new file mode 100644 index 0000000..4badeee --- /dev/null +++ b/DevDisciples.Json.Tools.CLI/JsonPathCommand.cs @@ -0,0 +1,32 @@ +using System.CommandLine; +using DevDisciples.Json.Tools.CLI.Extensions; + +namespace DevDisciples.Json.Tools.CLI; + +public partial class JsonPathCommand : Command +{ + private static readonly Option PathExpressionOption = + new( + aliases: ["--expr", "-e"], + description: "The path expression", getDefaultValue: () => "$" + ) { IsRequired = true }; + + public JsonPathCommand() : base("path", "Evaluate a JSON path expression") + { + AddAlias("p"); + this.AddInputOutputCommandOptions(); + this.AddOption(PathExpressionOption); + this.SetHandler(ExecuteAsync, new CommandOptionsBinder()); + } + + private static async Task ExecuteAsync(CommandOptions options) + { + var json = await InputOutputHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); + + var output = JsonPath.Interpreter.Evaluate(json, options.PathExpression); + + var beautifiedOutput = JsonFormatter.Format(output, new() { Beautify = true }); + + await InputOutputHandler.HandleOutput(options.Output, options.OutputFile, beautifiedOutput); + } +} \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/JsonUglifyCommand.cs b/DevDisciples.Json.Tools.CLI/JsonUglifyCommand.cs index 04aaba7..4e70217 100644 --- a/DevDisciples.Json.Tools.CLI/JsonUglifyCommand.cs +++ b/DevDisciples.Json.Tools.CLI/JsonUglifyCommand.cs @@ -8,16 +8,16 @@ public class JsonUglifyCommand : Command public JsonUglifyCommand() : base("uglify", "Uglify JSON") { AddAlias("u"); - this.AddIOCommandOptions(); + this.AddInputOutputCommandOptions(); this.SetHandler(ExecuteAsync, new CommandOptionsBinder()); } private static async Task ExecuteAsync(CommandOptions options) { - var json = await IOHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); + var json = await InputOutputHandler.HandleInput(options.Input, options.InputArgument, options.InputFile); var output = JsonFormatter.Format(json, new() { Beautify = false }); - await IOHandler.HandleOutput(options.Output, options.OutputFile, output); + await InputOutputHandler.HandleOutput(options.Output, options.OutputFile, output); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/RootCommand.cs b/DevDisciples.Json.Tools.CLI/RootCommand.cs index 113cd72..f0b3558 100644 --- a/DevDisciples.Json.Tools.CLI/RootCommand.cs +++ b/DevDisciples.Json.Tools.CLI/RootCommand.cs @@ -7,7 +7,8 @@ public class RootCommand : System.CommandLine.RootCommand public RootCommand() : base("A JSON transform CLI tool by DevDisciples.") { AddCommand(new JsonUglifyCommand()); - AddCommand(new JsonPrettifyCommand()); + AddCommand(new JsonBeautifyCommand()); AddCommand(new Json2CSharpCommand()); + AddCommand(new JsonPathCommand()); } } \ No newline at end of file diff --git a/DevDisciples.Json.Tools.CLI/SharedCommandOptions.cs b/DevDisciples.Json.Tools.CLI/SharedCommandOptions.cs index 0937047..8a9b0ad 100644 --- a/DevDisciples.Json.Tools.CLI/SharedCommandOptions.cs +++ b/DevDisciples.Json.Tools.CLI/SharedCommandOptions.cs @@ -4,8 +4,8 @@ namespace DevDisciples.Json.Tools.CLI; public static class SharedCommandOptions { - public static readonly Option InputOption = - new(aliases: ["--input", "-i"], getDefaultValue: () => InputOptions.stdin) { IsRequired = false }; + public static readonly Option InputOption = + new(aliases: ["--input", "-i"], getDefaultValue: () => InputSource.stdin) { IsRequired = false }; public static readonly Argument InputArgument = new(name: "input", description: "The input argument.", getDefaultValue: () => default); diff --git a/DevDisciples.Json.Tools.CLI/scripts/publish_as_tool.sh b/DevDisciples.Json.Tools.CLI/scripts/publish_as_tool.sh old mode 100644 new mode 100755 diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.ClassTranslation.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.ClassTranslation.cs index ea830a8..4714d7f 100644 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.ClassTranslation.cs +++ b/DevDisciples.Json.Tools/Json2CSharpTranslator.ClassTranslation.cs @@ -11,6 +11,8 @@ public static partial class Json2CSharpTranslator public void Translate(Context context) { + if (Properties.Count == 0) return; + context.Builder.Append($"public class {Name.Pascalize()}\n"); context.Builder.Append("{\n"); diff --git a/DevDisciples.Json.Tools/Json2CSharpTranslator.cs b/DevDisciples.Json.Tools/Json2CSharpTranslator.cs index 68a3375..a8c4776 100644 --- a/DevDisciples.Json.Tools/Json2CSharpTranslator.cs +++ b/DevDisciples.Json.Tools/Json2CSharpTranslator.cs @@ -1,6 +1,7 @@ using DevDisciples.Json.Parser; using DevDisciples.Json.Parser.Syntax; using DevDisciples.Parsing; +using DevDisciples.Parsing.Extensions; using Humanizer; namespace DevDisciples.Json.Tools; @@ -19,10 +20,10 @@ public static partial class Json2CSharpTranslator Visitors.Register(Null); } - public static string Translate(string source, Context? context = null) + public static string Translate(string input, Context? context = null) { - if (JsonParser.Parse("", source) is not JsonObjectSyntax root) - throw new ParsingException("Expected a JSON object."); + if (JsonParser.Parse("", input) is not JsonObjectSyntax root) + throw new SyntaxException("Expected a JSON object."); context ??= new(); context.CurrentName = context.RootClassName; @@ -41,7 +42,7 @@ public static partial class Json2CSharpTranslator private static ITranslation Object(IJsonSyntax visitee, Context context) { - var @object = (JsonObjectSyntax)visitee; + var @object = visitee.As(); var @class = new ClassTranslation { Name = context.CurrentName, @@ -76,10 +77,14 @@ public static partial class Json2CSharpTranslator private static ITranslation Array(IJsonSyntax visitee, Context context) { - var array = (JsonArraySyntax)visitee; + var array = visitee.As(); var type = "object"; - if (array.Elements.All(e => e is JsonObjectSyntax)) + if (array.Elements.Length == 0) + { + type = "object"; + } + else if (array.Elements.All(e => e is JsonObjectSyntax)) { context.Classes.Add(SquashObjects(context.CurrentName, array.Elements, context)); type = context.Classes.Last().Name; @@ -106,7 +111,7 @@ public static partial class Json2CSharpTranslator private static ITranslation String(IJsonSyntax visitee, Context context) { - var @string = (JsonStringSyntax)visitee; + var @string = visitee.As(); var type = DateTime.TryParse(@string.Value, out _) ? "DateTime" : "string"; return new PropertyTranslation @@ -120,7 +125,7 @@ public static partial class Json2CSharpTranslator { return new PropertyTranslation { - Type = ((JsonNumberSyntax)visitee).Token.Lexeme.Contains('.') ? "double" : "int", + Type = visitee.As().Token.Lexeme.Contains('.') ? "double" : "int", Name = context.CurrentName, }; } diff --git a/DevDisciples.Json.Tools/JsonFormatter.cs b/DevDisciples.Json.Tools/JsonFormatter.cs index 5946153..81f700b 100644 --- a/DevDisciples.Json.Tools/JsonFormatter.cs +++ b/DevDisciples.Json.Tools/JsonFormatter.cs @@ -1,6 +1,7 @@ using DevDisciples.Json.Parser; using DevDisciples.Json.Parser.Syntax; using DevDisciples.Parsing; +using DevDisciples.Parsing.Extensions; namespace DevDisciples.Json.Tools; @@ -18,9 +19,9 @@ public static partial class JsonFormatter Visitors.Register(PrintNull); } - public static string Format(string source, Context? context) + public static string Format(string input, Context? context) { - var node = JsonParser.Parse("", source); + var node = JsonParser.Parse("", input); return Format(node, context); } @@ -33,7 +34,7 @@ public static partial class JsonFormatter private static void PrintArray(IJsonSyntax visitee, Context context) { - var array = (JsonArraySyntax)visitee; + var array = visitee.As(); context.Builder.Append($"[{context.NewLine}"); context.IncrementDepth(); @@ -52,7 +53,7 @@ public static partial class JsonFormatter private static void PrintObject(IJsonSyntax visitee, Context context) { - var @object = (JsonObjectSyntax)visitee; + var @object = visitee.As(); context.Builder.Append($"{{{context.NewLine}"); context.IncrementDepth(); @@ -72,19 +73,19 @@ public static partial class JsonFormatter private static void PrintString(IJsonSyntax visitee, Context context) { - var @string = (JsonStringSyntax)visitee; + var @string = visitee.As(); context.Builder.Append($"\"{@string.Token.Lexeme}\""); } private static void PrintNumber(IJsonSyntax visitee, Context context) { - var number = (JsonNumberSyntax)visitee; + var number = visitee.As(); context.Builder.Append($"{number.Value}"); } private static void PrintBool(IJsonSyntax visitee, Context context) { - var @bool = (JsonBoolSyntax)visitee; + var @bool = visitee.As(); context.Builder.Append($"{@bool.Value.ToString().ToLower()}"); } diff --git a/DevDisciples.Json.Tools/JsonPath.Interpreter.cs b/DevDisciples.Json.Tools/JsonPath.Interpreter.cs index 511e814..8abdfa7 100644 --- a/DevDisciples.Json.Tools/JsonPath.Interpreter.cs +++ b/DevDisciples.Json.Tools/JsonPath.Interpreter.cs @@ -13,21 +13,21 @@ public static partial class JsonPath static Interpreter() { - Visitors.Register(WildCardExpression); - Visitors.Register(PropertyAccessorExpression); - Visitors.Register(PropertyExpression); - Visitors.Register(ArrayIndexExpression); - Visitors.Register(ArrayIndexListExpression); - Visitors.Register(ObjectIndexExpression); - Visitors.Register(ObjectIndexListExpression); + Visitors.Register(WildCard); + Visitors.Register(PropertyAccessor); + Visitors.Register(Property); + Visitors.Register(ArrayIndex); + Visitors.Register(ArrayIndexList); + Visitors.Register(ObjectIndex); + Visitors.Register(ObjectIndexList); } - public static IJsonSyntax Evaluate(string source, string path) + public static IJsonSyntax Evaluate(string input, string path) { - var root = JsonParser.Parse("", source); + var root = JsonParser.Parse("", input); if (root is not JsonArraySyntax && root is not JsonObjectSyntax) - throw Report.Error(root.Token, "Expected a JSON array or object."); + throw Report.SyntaxException(root.Token, "Expected a JSON array or object."); var expressions = Parser.Parse(path); @@ -41,7 +41,7 @@ public static partial class JsonPath private static void Evaluate(IJsonPathSyntax node, Context context) => Visitors[node.GetType()](node, context); - private static void WildCardExpression(IJsonPathSyntax visitee, Context context) + private static void WildCard(IJsonPathSyntax visitee, Context context) { switch (context.Target) { @@ -57,17 +57,17 @@ public static partial class JsonPath default: var syntax = visitee.As(); - throw Report.Error(syntax.Token, "Invalid target for '*'."); + throw Report.SyntaxException(syntax.Token, "Invalid target for '*'."); } } - private static void PropertyAccessorExpression(IJsonPathSyntax visitee, Context context) + private static void PropertyAccessor(IJsonPathSyntax visitee, Context context) { var accessor = visitee.As(); Evaluate(accessor.Getter, context); } - private static void PropertyExpression(IJsonPathSyntax visitee, Context context) + private static void Property(IJsonPathSyntax visitee, Context context) { var property = visitee.As(); @@ -90,10 +90,10 @@ public static partial class JsonPath } } - private static void ArrayIndexExpression(IJsonPathSyntax visitee, Context context) + private static void ArrayIndex(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonArraySyntax array) - throw Report.Error(context.Target.Token, "Integer indexes are only allowed on arrays."); + throw Report.SyntaxException(context.Target.Token, "Integer indexes are only allowed on arrays."); var index = visitee.As(); @@ -102,15 +102,15 @@ public static partial class JsonPath if (value >= 0 && value < array.Elements.Length) context.Target = array.Elements[value]; - else throw Report.Error(index.Token, "Index out of range."); + else throw Report.SyntaxException(index.Token, "Index out of range."); } - private static void ArrayIndexListExpression(IJsonPathSyntax visitee, Context context) + private static void ArrayIndexList(IJsonPathSyntax visitee, Context context) { var indices = visitee.As(); if (context.Target is not JsonArraySyntax array) - throw Report.Error(indices.Token, "Integer indices are only allowed on arrays."); + throw Report.SyntaxException(indices.Token, "Integer indices are only allowed on arrays."); var list = new List(); @@ -125,10 +125,10 @@ public static partial class JsonPath context.Target = new JsonArraySyntax(array.Token, list.ToArray()); } - private static void ObjectIndexExpression(IJsonPathSyntax visitee, Context context) + private static void ObjectIndex(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonObjectSyntax @object) - throw Report.Error(context.Target.Token, "String indices are only allowed on objects."); + throw Report.SyntaxException(context.Target.Token, "String indices are only allowed on objects."); var index = visitee.As(); @@ -142,10 +142,10 @@ public static partial class JsonPath context.Target = new JsonNullSyntax(@object.Token); } - private static void ObjectIndexListExpression(IJsonPathSyntax visitee, Context context) + private static void ObjectIndexList(IJsonPathSyntax visitee, Context context) { if (context.Target is not JsonObjectSyntax @object) - throw Report.Error(context.Target.Token, "Index strings are only allowed on objects."); + throw Report.SyntaxException(context.Target.Token, "Index strings are only allowed on objects."); var indices = visitee.As(); diff --git a/DevDisciples.Json.Tools/JsonPath.Parser.cs b/DevDisciples.Json.Tools/JsonPath.Parser.cs index 3126a32..78b5820 100644 --- a/DevDisciples.Json.Tools/JsonPath.Parser.cs +++ b/DevDisciples.Json.Tools/JsonPath.Parser.cs @@ -8,7 +8,7 @@ public static partial class JsonPath { public static List Parse(string path) { - var pathTokens = Lexer.Default.Lex("", path); + var pathTokens = Lexer.Default.Lex("", path); var context = new Context(pathTokens.ToArray()); context.Match(JsonPathToken.DollarSign); // Match the '$' eagerly. This will make it optional by default. @@ -28,7 +28,7 @@ public static partial class JsonPath if (context.Match(JsonPathToken.LeftBracket)) return IndexAccessorExpression(context); - throw context.Error("Invalid expression."); + throw context.Error($"Invalid expression '{context.Current.Lexeme}'."); } private static IJsonPathSyntax PropertyAccessorExpression(Context context) diff --git a/DevDisciples.Json.Tools/JsonPath.Syntax.cs b/DevDisciples.Json.Tools/JsonPath.Syntax.cs index d6e281d..b68869f 100644 --- a/DevDisciples.Json.Tools/JsonPath.Syntax.cs +++ b/DevDisciples.Json.Tools/JsonPath.Syntax.cs @@ -6,7 +6,6 @@ namespace DevDisciples.Json.Tools; public static partial class JsonPath { - public interface IJsonPathSyntax { public Lexer.Token Token { get; } diff --git a/DevDisciples.Parsing/Lexer.Rule.cs b/DevDisciples.Parsing/Lexer.Rule.cs index ca62fa3..f5af759 100644 --- a/DevDisciples.Parsing/Lexer.Rule.cs +++ b/DevDisciples.Parsing/Lexer.Rule.cs @@ -73,7 +73,7 @@ public abstract partial class Lexer where TToken : Enum if (src.Ended()) { - throw new ParsingException( + throw new SyntaxException( $"[line: {src.Line}, column: {src.Column}] Unterminated string near '{src.Last}'." ); } @@ -117,7 +117,7 @@ public abstract partial class Lexer where TToken : Enum if (src.Ended()) { - throw new ParsingException( + throw new SyntaxException( $"[line: {src.Line}, column: {src.Column}] Unterminated string near '{src.Last}'." ); } diff --git a/DevDisciples.Parsing/Lexer.cs b/DevDisciples.Parsing/Lexer.cs index 83dd486..1b032aa 100644 --- a/DevDisciples.Parsing/Lexer.cs +++ b/DevDisciples.Parsing/Lexer.cs @@ -25,7 +25,7 @@ public abstract partial class Lexer where TToken : Enum if (!matched) { - Report.Halt(ctx.Source, $"Unexpected character '{ctx.Source.Current}'."); + Report.SyntaxHalt(ctx.Source, $"Unexpected character '{ctx.Source.Current}'."); } } diff --git a/DevDisciples.Parsing/ParserContext.cs b/DevDisciples.Parsing/ParserContext.cs index e8200fd..2b7a716 100644 --- a/DevDisciples.Parsing/ParserContext.cs +++ b/DevDisciples.Parsing/ParserContext.cs @@ -91,11 +91,11 @@ public class ParserContext : ParsableStream.Token> where T public Exception Error(string message) { - return new ParsingException(Report.FormatMessage(Current, message)); + return new SyntaxException(Report.FormatMessage(Current, message)); } public Exception Error(Lexer.Token token, string message) { - return new ParsingException(Report.FormatMessage(token, message)); + return new SyntaxException(Report.FormatMessage(token, message)); } -} \ No newline at end of file +} diff --git a/DevDisciples.Parsing/ParsingException.cs b/DevDisciples.Parsing/ParsingException.cs deleted file mode 100644 index 1f13796..0000000 --- a/DevDisciples.Parsing/ParsingException.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace DevDisciples.Parsing; - -public class ParsingException : Exception -{ - public ParsingException() - { - } - - public ParsingException(string? message) : base(message) - { - } - - public ParsingException(string? message, Exception? innerException) : base(message, innerException) - { - } -} \ No newline at end of file diff --git a/DevDisciples.Parsing/Report.cs b/DevDisciples.Parsing/Report.cs index 3a260a5..b4a2bdf 100644 --- a/DevDisciples.Parsing/Report.cs +++ b/DevDisciples.Parsing/Report.cs @@ -2,14 +2,14 @@ public static class Report { - public static ParsingException Error(ISourceLocation token, string message) + public static SyntaxException SyntaxException(ISourceLocation token, string message) { - return new ParsingException(FormatMessage(token, message)); + return new SyntaxException(FormatMessage(token, message)); } - public static void Halt(ISourceLocation token, string message) + public static void SyntaxHalt(ISourceLocation token, string message) { - throw new ParsingException(FormatMessage(token, message)); + throw new SyntaxException(FormatMessage(token, message)); } public static string FormatMessage(ISourceLocation token, string msg) diff --git a/DevDisciples.Parsing/SyntaxException.cs b/DevDisciples.Parsing/SyntaxException.cs new file mode 100644 index 0000000..2757e48 --- /dev/null +++ b/DevDisciples.Parsing/SyntaxException.cs @@ -0,0 +1,16 @@ +namespace DevDisciples.Parsing; + +public class SyntaxException : Exception +{ + public SyntaxException() + { + } + + public SyntaxException(string? message) : base(message) + { + } + + public SyntaxException(string? message, Exception? innerException) : base(message, innerException) + { + } +} \ No newline at end of file