pakabeiparser.cpp 8.58 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
/*
 * This file is part of the Chakra project

   Copyright (C) 2011 Lisa Vitolo <shainer@chakra-project.org>

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.
*/

#include "pakabeiparser.h"
#include <QDebug>

CommandLine::CommandLine(QStringList &args)
    : m_commands(args)
    , m_index(0)
{}

bool CommandLine::isSet(const QString& command)
{
    QString actualCom;
23

24 25 26
    if (command.isEmpty()) {
        return false;
    }
27

28 29 30 31 32
    /*
     * Handle options with a short command composed of the same letter
     * repeated twice
     */
    if (command.size() == 2) {
AlmAck's avatar
AlmAck committed
33
        actualCom = QString::fromLatin1("-") + command[0];
34
        m_requested.push_front(actualCom);
35

36 37 38 39
        if (m_commands.count(actualCom) == 2) {
            m_commands.removeAll(actualCom);
            return true;
        }
40

41 42
        return false;
    }
43

44 45 46
    if (command.size() == 1) {
        actualCom = "-" + command;
    } else {
Lisa's avatar
Lisa committed
47 48 49 50 51
        if (!command.startsWith("--")) {
            actualCom = "--" + command;
        } else {
            actualCom = command;
        }
52
    }
53

54
    m_index = m_commands.indexOf(actualCom);
55

56
    if (m_index != -1) {
57
        m_requested.push_front(actualCom);
58 59
        return true;
    }
60

61 62 63 64
    return false;
}

QString CommandLine::optArg()
65
{
66 67
    if (m_index < m_commands.size() - 1) {
        QString arg( m_commands[m_index + 1] );
68

69 70 71 72 73
        if (!arg.startsWith("-")) {
            m_commands.removeAt(m_index+1);
            return arg;
        }
    }
74

75 76 77 78 79 80
    return "";
}

QStringList CommandLine::args()
{
    QStringList result;
81

82 83 84
    foreach (const QString& c, m_commands) {
        if (!c.startsWith("-")) {
            result.append(c);
85
            m_commands.removeOne(c);
86 87
        }
    }
88

89 90 91
    return result;
}

92 93 94 95 96 97 98 99 100
void CommandLine::emptyCheck()
{
    foreach (const QString& com, m_commands) {
        if (!m_requested.contains(com)) {
            throw UnknownOptionException(com);
        }
    }
}

101 102 103
/*
 * Build our database of accepted operations and options with the right properties and associations
 */
104 105
PakabeiParser::PakabeiParser(const QStringList& args, const AkabeiAboutData& about)
    : CommandLineParser(args, about)
106 107 108 109 110 111 112 113 114 115 116 117 118
{
    typechars.insert("Q", APM::QueryOperationType);
    typechars.insert("R", APM::RemoveOperationType);
    typechars.insert("S", APM::SyncOperationType);
    typechars.insert("U", APM::UpgradeOperationType);
    typechars.insert("P", APM::PerformOperationType);
}

/*
 * I have to add the new operation to a different hash from the one used in the parser.
 * This is because acceptedOperations hashes a lot of operations under the same type (of course)
 * so after an operation is inserted, retrieving it to modify it in addConnectionWithOption is a bit complex.
 */
119
void PakabeiParser::addOperation(AkabeiOperation& op)
120
{
121 122 123
    foreach (APM::OptionName opt, generalOptions) {
        op.addConnectionWithOption(opt);
    }
124

Lisa's avatar
Lisa committed
125 126 127
    if (!op.commandLong().isEmpty()) {
        op.setCommandLong("--" + op.commandLong());
    }
128
    acceptedOperations.insertMulti(op.type(), op);
129 130
}

Lisa's avatar
Lisa committed
131
void PakabeiParser::addOption(APM::OptionName name, QString const& cmdShort, QString const& cmdLong, QString const& desc, QString const& argname)
132
{
Lisa's avatar
Lisa committed
133
    AkabeiOption opt(name, cmdShort, cmdLong, desc, argname);
134 135 136 137 138
    acceptedOptions.push_front(opt);
}

void PakabeiParser::connectOptionWithEverything(APM::OptionName option)
{
139
    generalOptions.push_front(option);
140 141 142 143 144
}

void PakabeiParser::parse()
{
    CommandLine commandLine(m_argv);
145

146 147
    m_type = APM::NoType;
    QSet<QString> found;
148

149 150 151 152 153 154 155 156 157 158
    /*
     * Manages the "special" operations separately
     */
    if (commandLine.isSet("V") || commandLine.isSet("version")) {
        CommandLineUtils::printVersion(m_data);
    } else if (commandLine.isSet("license")) {
        CommandLineUtils::printLicenses(m_data);
    } else if (commandLine.isSet("authors")) {
        CommandLineUtils::printAuthors(m_data);
    } else if (commandLine.isSet("h") || commandLine.isSet("help")) {
Lisa's avatar
Lisa committed
159
        CommandLineUtils::printHelpMessage(acceptedOperations, acceptedOptions, m_data);
160
    }
161

162 163 164 165 166 167
    for (QHash<QString, APM::OperationType>::iterator it = typechars.begin(); it != typechars.end(); it++) {
        if (commandLine.isSet(it.key())) {
            if (m_type != APM::NoType) {
                throw MultipleTypesException();
            }
            m_type = it.value();
168

169 170 171
            if (m_type == APM::QueryOperationType) {
                found << "Q";
            }
172

173 174 175 176
            /*
            * We look only for the acceptable operations
            * all the others, if present, will be spotted later
            */
177
            foreach (AkabeiOperation op, acceptedOperations.values(m_type)) {
178

179 180 181
               /*
                * Saves up the commands for later inspection
                */
182
                if (commandLine.isSet(op.commandShort()) || commandLine.isSet(op.commandLong())) {
183
                    m_operations.append( op.name() );
184
                    found << op.commandShort();
185

186 187 188 189
                    if (m_operations.size() > 1) {
                        m_operations.removeOne(APM::Remove);
                        m_operations.removeOne(APM::Install);
                    }
190 191
                }
            }
192

193 194
        }
    }
195

196 197 198
    if (m_operations.isEmpty()) {
        throw MissingOperationException();
    }
199

200 201
    QSet<APM::OptionName> goodOptions = supportedOptions();
    bool freeArgsRequired = areArgsRequired();
202 203 204

    foreach (AkabeiOption opt, acceptedOptions) {
        QString commandName = (opt.commandShort().isEmpty()) ? opt.commandLong() : opt.commandShort();
205

206
        /* Checks only the options that aren't already been recognized as operations (some of them share the same short command) */
207
        if ((!found.contains(commandName.toUtf8()) && commandLine.isSet(opt.commandShort())) || commandLine.isSet(opt.commandLong())) {
208

209
            /* If at least one operation supports the option, it's fine */
210
            if (!goodOptions.contains( opt.name() )) {
211 212
                throw OptionOutOfContextException(commandName);
            }
213

214 215 216 217 218
            /*
             * Gets arguments associated with the option if required
             */
            if (opt.hasArgs()) {
                QString optarg = commandLine.optArg();
219

220 221 222
                if (optarg.isEmpty()) {
                    throw OptionArgExpectedException(commandName);
                }
223

224 225
                opt.addArguments(optarg);
            }
226

227 228 229
            m_options.insert(opt.name(), opt);
        }
    }
230

231 232 233 234
    /*
     * Gets all the other arguments around
     */
    m_freeArgs = commandLine.args();
235

236 237 238 239
    /*
     * Exception: -Sy without args just updates the databases, while
     * -Sy with args first updates and then installs the packages
     */
Lukas Appelhans's avatar
Lukas Appelhans committed
240
    if (!m_freeArgs.isEmpty() && (m_operations.contains(APM::UpdateDatabases) || m_operations.contains(APM::ForceUpdateDatabases))) {
241
        m_operations.append(APM::Install);
242
    }
243 244

    /*if (!m_freeArgs.isEmpty() && !freeArgsRequired()) {
245
        throw ArgumentsOutOfContextException();
246 247
    }*///NOTE: We don't throw this anymore, as we could have arguments, but not needed... e.g. akabei files can have -p /path/to/package OR an argument

248
    if (m_freeArgs.isEmpty() && freeArgsRequired) {
249 250
        throw ExpectedArgumentsException();
    }
251

252
    commandLine.emptyCheck();
253 254 255
}

/*
256
 * Arguments are required if at least one operation requires them.
257
 */
258
bool PakabeiParser::areArgsRequired()
259
{
260
    bool req = false;
261

262 263 264
    foreach (const AkabeiOperation& operation, acceptedOperations.values()) {
        if (m_operations.contains( operation.name() )) {
            req = req || operation.hasFreeArgs();
265 266
        }
    }
267

268 269
    return req;
}
270

271 272 273 274 275 276 277
/*
 * Returns a set of all the supported options. An option is supported if at least one
 * operation accepts it.
 */
QSet< APM::OptionName > PakabeiParser::supportedOptions()
{
    QSet<APM::OptionName> supported;
278

279 280 281 282 283
    foreach (const AkabeiOperation& operation, acceptedOperations.values()) {
        if (m_operations.contains( operation.name() )) {
            supported += QSet<APM::OptionName>::fromList( operation.options() );
        }
    }
284

285
    return supported;
286 287 288 289 290 291 292 293 294
}

APM::OperationType PakabeiParser::type()
{
    return m_type;
}

QList<APM::OperationName> PakabeiParser::operations()
{
295
    return m_operations;
296 297 298 299 300 301 302 303 304 305 306 307 308
}

QStringList PakabeiParser::args()
{
    return m_freeArgs;
}

QHash< APM::OptionName, QStringList > PakabeiParser::options()
{
    QHash<APM::OptionName, QStringList> result;
    foreach (APM::OptionName key, m_options.keys()) {
        result.insert(key, m_options[key].arguments());
    }
309

310 311
    return result;
}