Subversion Repositories mkgmap

Rev

Rev 2498 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * Copyright (C) 2013.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */

package uk.me.parabola.mkgmap.main;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import uk.me.parabola.imgfmt.ExitException;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.mkgmap.scan.TokType;
import uk.me.parabola.mkgmap.scan.Token;
import uk.me.parabola.mkgmap.scan.TokenScanner;


/**
 * All the information contained in the options help file is held here.
 *
 * @author Steve Ratcliffe
 */

public class HelpOptions {

        private final List<HelpOptionItem> list = new ArrayList<HelpOptionItem>();
        private final Map<String, HelpOptionItem> options = new HashMap<String, HelpOptionItem>();

        /**
         * Read the file given.
         * @param stream An opened file stream to the help file.
         */

        public void parse(InputStream stream) {
                InputStreamReader r = null;
                try {
                        r = new InputStreamReader(stream, "utf-8");
                        parse(r);
                } catch (UnsupportedEncodingException e) {
                        // java guarantees utf-8 support, so this can't happen in normal circumstances
                        throw new ExitException("unsupported encoding", e);
                } finally {
                        Utils.closeFile(r);
                }
        }

        /**
         * Read the given input stream and parse it.
         */

        private void parse(InputStreamReader r) {
                TokenScanner scan = new TokenScanner("options", r);
                scan.setCommentChar(null);  // turn off comment processing which we don't use
                scan.setExtraWordChars("-");

                while (!scan.isEndOfFile()) {
                        scan.skipSpace();
                        Token tok = scan.peekToken();

                        if (isOption(tok)) {
                                parseOpt(scan);
                        } else {
                                parseSection(scan);
                        }
                }
        }

        /**
         * Parse a section of text between the options.
         * This text starts in column 1.
         *
         * @param scan The token stream.
         */

        private void parseSection(TokenScanner scan) {
                HelpOptionItem item = new HelpOptionItem();

                boolean para = false;
                while (!scan.isEndOfFile()) {

                        Token tok = scan.peekToken();
                        if (tok.isType(TokType.TEXT) && tok.getValue().startsWith("-")) {
                                break;
                        }

                        if (para) {
                                item.addDescriptionLine("");
                                para = false;
                        }

                        if (tok.isType(TokType.EOL)) {
                                scan.skipSpace();
                                para = true;
                        } else {
                                item.addDescriptionLine(scan.readLine());
                        }
                }

                list.add(item);
        }

        /**
         * Parse a single option. An option can have several names (eg short and long form, or
         * a deprecated version and a new name), and it is followed by a description that is
         * indented by 4 spaces. There can be blank lines within the description.
         *
         * @param scan The token stream.
         */

        private void parseOpt(TokenScanner scan) {
                HelpOptionItem item = new HelpOptionItem();

                while (!scan.isEndOfFile()) {
                        String optname = scan.nextValue();

                        if (optname.startsWith("--")) {
                                optname = optname.substring(2);
                        } else {
                                optname = optname.substring(1);
                        }

                        readMeta(scan, item, optname);

                        Token next = scan.peekToken();
                        if (next.getType() != TokType.TEXT || !next.getValue().startsWith("-")) {
                                parseDescription(scan, next, item);
                                break;
                        }
                }

                // Add a reference for each name
                for (String optname : item.getOptionNames())
                        options.put(optname, item);

                // Add the ordered list
                list.add(item);
        }

        /**
         * Read the meta variable for an option that takes an argument.
         * @param scan The token stream.
         * @param item The current option item.
         * @param optname The name of the option.
         */

        private void readMeta(TokenScanner scan, HelpOptionItem item, String optname) {
                String meta = null;
                boolean old = false;
                boolean removed = false;

                while (!scan.isEndOfFile()) {
                        Token tok = scan.nextRawToken();

                        if (tok.isType(TokType.EOL)) break;

                        if (meta == null && (isSym(tok, "=") || tok.isWhiteSpace()))
                                meta = scan.nextWord();
                        else if (isSym(tok, "#")) {
                                String cmd = scan.nextWord();
                                if ("old".equals(cmd)) {
                                        old = true;
                                } else if ("removed".equals(cmd)) {
                                        removed = true;
                                }
                        }
                }

                HelpOption opt = new HelpOption(optname, meta);
                opt.setOld(old);
                opt.setRemoved(removed);

                item.addOption(opt);
        }

        private boolean isSym(Token tok, String sym) {
                return tok.isType(TokType.SYMBOL) && tok.getValue().equals(sym);
        }

        /**
         * Parse the description of a particular option. This is not for text that occurs between options.
         *
         * @param scan The token stream.
         * @param next The next token in the stream, it has not been removed from the stream yet.
         * @param item The current option item. The description is added here.
         */

        private void parseDescription(TokenScanner scan, Token next, HelpOptionItem item) {
                boolean para = false;
                Token tok = next;
                while (!scan.isEndOfFile()) {

                        if (tok.isType(TokType.TEXT)) {
                                break;

                        } else if (tok.isType(TokType.SPACE)) {
                                if (para) {
                                        item.addDescriptionLine("");
                                        para = false;
                                }

                                tok = scan.nextRawToken();
                                String val = tok.getValue();
                                String line = "";
                                if (val.length() > 4)
                                        line = val.substring(4);

                                tok = scan.peekToken();
                                if (tok.isValue("#")) {
                                        parseCommands(scan, item);
                                } else {
                                        line += scan.readLine();
                                        item.addDescriptionLine(line);
                                }
                        } else if (tok.isType(TokType.EOL)) {
                                scan.nextRawToken();
                                para = true;
                        } else {
                                assert false : "unexpected";
                        }

                        tok = scan.peekToken();
                }
        }

        /**
         * Read and interpret the commands.
         * Commands follow the option description.
         *
         * @param scan Input stream
         * @param item The current option.
         */

        private void parseCommands(TokenScanner scan, HelpOptionItem item) {
                scan.validateNext("#");

                while (!scan.isEndOfFile()) {
                        Token tok = scan.peekToken();

                        if (tok.isType(TokType.EOL)) {
                                return;
                        } else if (tok.isType(TokType.TEXT)) {
                                String cmd = scan.nextWord();

                                if ("default".equals(cmd)) {
                                        scan.validateNext(":");
                                        String defaultValue = scan.nextWord();
                                        if (item.isBoolean()) {
                                                if ("on".equals(defaultValue))
                                                        item.setDefaultValue("");
                                                else
                                                        item.setDefaultValue(null);
                                        } else {
                                                item.setDefaultValue(defaultValue);
                                        }
                                }
                        } else {
                                scan.nextRawToken();
                        }
                }
        }

        /**
         * Get a list of the long option names.
         */

        public Set<String> getOptionNameSet() {
                Set<String> set = new HashSet<String>();
                for (HelpOptionItem item : list) {
                        set.addAll(item.getOptionNames());
                }
                return set;
        }

        public HelpOptionItem getItemByName(String name) {
                return options.get(name);
        }

        public HelpOption getOptionByName(String name) {
                HelpOptionItem item = getItemByName(name);
                if (item == null)
                        return null;
                return item.getOptionForName(name);
        }

        private boolean isOption(Token tok) {
                return tok.isText() && tok.getValue().startsWith("-");
        }
}