001/**
002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.tools;
017
018import static java.util.Locale.ENGLISH;
019import static java.util.Locale.ROOT;
020import static java.util.Optional.ofNullable;
021import static java.util.stream.Collectors.joining;
022
023import java.io.File;
024import java.util.Locale;
025import java.util.Map;
026import java.util.stream.IntStream;
027import java.util.stream.Stream;
028
029public class AsciidocDocumentationGenerator extends DocBaseGenerator {
030
031    private final String levelPrefix;
032
033    private final Map<String, String> formats;
034
035    private final Map<String, String> attributes;
036
037    private final File templateDir;
038
039    private final File workDir;
040
041    private final String templateEngine;
042
043    private final String title;
044
045    private final String version;
046
047    // CHECKSTYLE:OFF - used by reflection so better to not create a wrapper
048    public AsciidocDocumentationGenerator(final File[] classes, final File output, final String title, final int level,
049            final Map<String, String> formats, final Map<String, String> attributes, final File templateDir,
050            final String templateEngine, final Object log, final File workDir, final String version,
051            final Locale locale) {
052        // CHECKSTYLE:ON
053        super(classes, locale, log, output);
054        this.title = title;
055        this.formats = formats;
056        this.attributes = attributes;
057        this.templateDir = templateDir;
058        this.templateEngine = templateEngine;
059        this.workDir = workDir;
060        this.version = version;
061        this.levelPrefix = IntStream.range(0, level).mapToObj(i -> "=").collect(joining(""));
062    }
063
064    @Override
065    public void doRun() {
066        final String doc = components()
067                .map(this::toAsciidoc)
068                .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
069                .toString();
070        write(output, doc);
071        log.info("Generated " + output.getAbsolutePath());
072        renderAdoc();
073    }
074
075    private String toAsciidoc(final ComponentDescription desc) {
076        final String partMarker = desc.getName();
077        return "//component_start:" + partMarker + "\n\n" + levelPrefix + " " + desc.getName() + "\n\n"
078                + desc.getDocumentation()
079                + (desc.getParameters().isEmpty() ? ""
080                        : ("//configuration_start\n\n" + levelPrefix + "= Configuration\n\n" + desc
081                                .parameters()
082                                .map(this::toAsciidoctor)
083                                .collect(joining("\n", "[cols=\"d,d,m,a,e,d\",options=\"header\"]\n"
084                                        + "|===\n|Display Name|Description|Default Value|Enabled If|Configuration Path|Configuration Type\n",
085                                        "\n|===\n\n//configuration_end\n\n"))))
086                + "//component_end:" + partMarker + "\n\n";
087    }
088
089    private String toAsciidoctor(final Param p) {
090        return "|" + p.getDisplayName() + '|' + p.getDocumentation() + '|' + p.getDefaultValue() + '|'
091                + doRenderConditions(p.getConditions()) + '|' + p.getFullPath() + '|' + p.getType();
092    }
093
094    private String doRenderConditions(final Conditions conditions) {
095        switch (conditions.getConditions().size()) {
096        case 0:
097            return "Always enabled";
098        case 1:
099            return renderCondition(conditions.getConditions().iterator().next());
100        default:
101            final String conds = conditions
102                    .getConditions()
103                    .stream()
104                    .map(this::renderCondition)
105                    .map(c -> "- " + c)
106                    .collect(joining("\n", "\n", "\n"));
107            switch (conditions.getOperator().toUpperCase(ROOT)) {
108            case "OR":
109                return "One of these conditions is meet:\n" + conds;
110            case "AND":
111            default:
112                return "All of the following conditions are met:\n" + conds;
113            }
114        }
115    }
116
117    private String renderCondition(final DocBaseGenerator.Condition condition) {
118        final String values =
119                Stream.of(condition.getValue().split(",")).map(v -> '`' + v + '`').collect(joining(" or "));
120        switch (ofNullable(condition.getStrategy()).orElse("default").toLowerCase(ROOT)) {
121        case "length":
122            if (condition.isNegate()) {
123                if (values.equals("`0`")) {
124                    return '`' + condition.getPath() + "` is not empty";
125                }
126                return "the length of `" + condition.getPath() + "` is not " + values;
127            }
128            if (values.equals("`0`")) {
129                return '`' + condition.getPath() + "` is empty";
130            }
131            return "the length of `" + condition.getPath() + "` is " + values;
132        case "contains":
133            if (condition.isNegate()) {
134                return '`' + condition.getPath() + "` does not contain " + values;
135            }
136            return '`' + condition.getPath() + "` contains " + values;
137        case "contains(lowercase=true)":
138            if (condition.isNegate()) {
139                return "the lowercase value of `" + condition.getPath() + "` does not contain " + values;
140            }
141            return "the lowercase value of `" + condition.getPath() + "` contains " + values;
142        case "default":
143        default:
144            if (condition.isNegate()) {
145                return '`' + condition.getPath() + "` is not equal to " + values;
146            }
147            return '`' + condition.getPath() + "` is equal to " + values;
148        }
149    }
150
151    private void renderAdoc() {
152        try (final AsciidoctorExecutor asciidoctorExecutor = new AsciidoctorExecutor()) {
153            ofNullable(formats).ifPresent(f -> f.forEach((format, output) -> {
154                switch (format.toLowerCase(ENGLISH)) {
155                case "html":
156                    asciidoctorExecutor
157                            .render(workDir, version, log, "html5", this.output, new File(output), title, attributes,
158                                    templateDir, templateEngine);
159                    break;
160                case "pdf":
161                    asciidoctorExecutor
162                            .render(workDir, version, log, "pdf", this.output, new File(output), title, attributes,
163                                    templateDir, templateEngine);
164                    break;
165                default:
166                    throw new IllegalArgumentException("unknown format: '" + format + "', supported: [html, pdf]");
167                }
168            }));
169        }
170    }
171}