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 static dev.enola.core.thing.ListThingService.ENOLA_ROOT_LIST_THINGS;
021
022import com.google.protobuf.InvalidProtocolBufferException;
023
024import dev.enola.common.function.CheckedPredicate;
025import dev.enola.core.meta.docgen.Options;
026import dev.enola.core.proto.EnolaServiceGrpc.EnolaServiceBlockingStub;
027import dev.enola.core.proto.GetThingRequest;
028import dev.enola.model.Datatypes;
029import dev.enola.thing.gen.markdown.MarkdownSiteGenerator;
030import dev.enola.thing.message.MoreThings;
031import dev.enola.thing.message.ProtoThingMetadataProvider;
032import dev.enola.thing.proto.Thing;
033import dev.enola.thing.template.Templates;
034import dev.enola.web.EnolaThingProvider;
035
036import picocli.CommandLine;
037import picocli.CommandLine.Command;
038import picocli.CommandLine.Option;
039
040import java.io.IOException;
041import java.net.URI;
042import java.util.Collection;
043import java.util.function.Function;
044import java.util.stream.Collectors;
045
046@Command(name = "docgen", description = "Generate Markdown Documentation")
047public class DocGenCommand extends CommandWithModelAndOutput {
048
049    // TODO Replace "docgen" completely with separate class GenCommand subcommands
050
051    @Option(
052            names = {"--diagram", "-d"},
053            required = true,
054            defaultValue = "Mermaid",
055            description =
056                    "Type of diagrams to generate (${COMPLETION-CANDIDATES}; default:"
057                            + " ${DEFAULT-VALUE})")
058    Options.DiagramType diagram;
059
060    @Option(
061            names = {"--variables", "-var"},
062            required = true,
063            defaultValue = "Mustache",
064            description =
065                    "Type of variables to generate for IRI Templates (${COMPLETION-CANDIDATES};"
066                            + " default: ${DEFAULT-VALUE})")
067    Templates.Format variables;
068
069    @Option(
070            names = {"--header", "-h"},
071            required = true,
072            defaultValue = "string:%23%20Models%0A",
073            showDefaultValue = CommandLine.Help.Visibility.ALWAYS,
074            description = "URI of Markdown header (e.g. file: or string:<URL-encoded> etc.)")
075    URI headerURI;
076
077    @Option(
078            names = {"--index", "-i"},
079            negatable = true,
080            required = true,
081            defaultValue = "true",
082            fallbackValue = "true",
083            description = "Whether index.md should be generated")
084    boolean generateIndexFile;
085
086    @Override
087    public void run(EnolaServiceBlockingStub service) throws Exception {
088        multipleMDDocsForThings(service, generateIndexFile);
089    }
090
091    private void multipleMDDocsForThings(
092            EnolaServiceBlockingStub service, boolean generateIndexFile) throws Exception {
093        var mdp = getMetadataProvider(new EnolaThingProvider(service));
094        var pmdp = new ProtoThingMetadataProvider(mdp);
095        var mdsg =
096                new MarkdownSiteGenerator(
097                        Output.get(output), rp, mdp, pmdp, Datatypes.DTR, variables);
098
099        var things = getThings(service, ENOLA_ROOT_LIST_THINGS);
100
101        // TODO This works, but is not efficient if there were a HUGE amount of Things and MDs...
102        var map = things.stream().collect(Collectors.toMap(Thing::getIri, Function.identity()));
103        CheckedPredicate<String, IOException> isDocumentedIRI =
104                iri -> templateService.get(iri) != null;
105        mdsg.generate(things, map::get, isDocumentedIRI, templateService, generateIndexFile, true);
106    }
107
108    private Collection<Thing> getThings(EnolaServiceBlockingStub service, String iri)
109            throws InvalidProtocolBufferException {
110        var request = GetThingRequest.newBuilder().setIri(iri).build();
111        var response = service.getThing(request);
112        return MoreThings.fromAny(response.getThing());
113    }
114}