OptionTypeMapper.java

/**
 * Copyright (c) 2021, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.tools.autocompletion;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Tries to guess option argument type, to guide autocompletion.
 * Works based on option name or argument name.
 *
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at gmail.com>}
 */
public class OptionTypeMapper {

    public static class Key {

        public enum Type {
            OPTION_NAME,
            OPTION_ARG_NAME
        }

        private final String regex;

        private final Type type;

        public Key(String regex, Type type) {
            this.regex = Objects.requireNonNull(regex);
            this.type = Objects.requireNonNull(type);
        }

        public String getRegex() {
            return regex;
        }

        public Type getType() {
            return type;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Key key) {
                return regex.equals(key.regex) && type == key.type;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return Objects.hash(regex, type);
        }
    }

    private static final class Mapping {

        private final Key key;

        private final OptionType optionType;

        private Mapping(Key key, OptionType optionType) {
            this.key = key;
            this.optionType = optionType;
        }

        private Key getKey() {
            return key;
        }

        private OptionType getOptionType() {
            return optionType;
        }
    }

    private final List<Mapping> mappings = new ArrayList<>();
    private OptionType defaultType;

    public OptionTypeMapper add(Key key, OptionType optionType) {
        mappings.add(new Mapping(key, optionType));
        return this;
    }

    /**
     * Maps a regex on the option name to a specific type
     */
    public OptionTypeMapper addOptionNameMapping(String regex, OptionType optionType) {
        add(new Key(regex, Key.Type.OPTION_NAME), optionType);
        return this;
    }

    /**
     * Maps a regex on the argument name to a specific type
     */
    public OptionTypeMapper addArgNameMapping(String regex, OptionType optionType) {
        add(new Key(regex, Key.Type.OPTION_ARG_NAME), optionType);
        return this;
    }

    /**
     * Sets the type to be used as a fallback
     */
    public OptionTypeMapper setDefaultType(OptionType defaultType) {
        this.defaultType = defaultType;
        return this;
    }

    public void map(BashOption option) {
        if (!option.hasArg()) {
            return;
        }
        for (Mapping mapping : mappings) {
            switch (mapping.getKey().getType()) {
                case OPTION_NAME:
                    if (option.getName().matches(mapping.getKey().getRegex())) {
                        option.setType(mapping.getOptionType());
                    }
                    break;
                case OPTION_ARG_NAME:
                    option.getArgName().ifPresent(name -> {
                        if (name.matches(mapping.getKey().getRegex())) {
                            option.setType(mapping.getOptionType());
                        }
                    });
                    break;
                default:
                    throw new IllegalStateException();
            }
        }
        if (option.getType() == null) {
            option.setType(defaultType);
        }
    }

    public void map(BashCommand command) {
        for (BashOption option : command.getOptions()) {
            if (option.getType() == null) {
                map(option);
            }
        }
    }

    public void map(List<BashCommand> commands) {
        for (BashCommand command : commands) {
            map(command);
        }
    }

}