001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2023-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.cli;
019
020import dev.enola.common.canonicalize.Canonicalizer;
021import dev.enola.common.context.TLC;
022import dev.enola.common.function.MoreStreams;
023import dev.enola.common.io.iri.URIs;
024import dev.enola.common.io.resource.DelegatingResource;
025import dev.enola.common.io.resource.ReadableResource;
026import dev.enola.common.io.resource.stream.GlobResolvers;
027import dev.enola.common.io.resource.stream.WritableResourcesProvider;
028
029import picocli.CommandLine;
030
031import java.io.IOException;
032import java.nio.file.Paths;
033
034@CommandLine.Command(
035        name = "canonicalize",
036        description = {
037            "Canonicalize (AKA normalize) resources",
038            "using e.g. RFC 8785 JSON Canonicalization Scheme (JCS) inspired approach",
039            "(but this implementation is currently not yet fully compliant with that RFC)"
040        })
041public class CanonicalizeCommand extends CommandWithResourceProvider {
042
043    // TODO Merge this completely with RosettaCommand ?!
044
045    @CommandLine.ArgGroup(multiplicity = "1")
046    CommandWithModel.LoadableModelURIs resources;
047
048    @CommandLine.ArgGroup CommandWithModel.Output output;
049
050    @CommandLine.Option(
051            names = {"--pretty"},
052            negatable = true,
053            required = true,
054            defaultValue = "false",
055            fallbackValue = "true",
056            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
057            description = "Whether to 'pretty print' (format) output")
058    boolean pretty;
059
060    private WritableResourcesProvider wrp;
061    private Canonicalizer canonicalizer;
062
063    @Override
064    public void run() throws Exception {
065        super.run();
066        try (var ctx = TLC.open().push(URIs.ContextKeys.BASE, Paths.get("").toUri())) {
067            wrp = new WritableResourcesProvider(rp);
068            canonicalizer = new Canonicalizer(rp);
069
070            var fgrp = new GlobResolvers();
071            for (var globIRI : resources.load) {
072                try (var stream = fgrp.get(globIRI)) {
073                    MoreStreams.forEach(stream, uri -> canonicalize(rp.getResource(uri)));
074                }
075            }
076        }
077    }
078
079    private void canonicalize(ReadableResource r) throws IOException {
080        var out = wrp.getWritableResource(CommandWithModel.Output.get(output), r.uri());
081        out = new DelegatingResource(out, r.mediaType().withoutParameters());
082        canonicalizer.canonicalize(r, out, pretty);
083    }
084}