Command Line Parsing

- Implemented Arguments::extract_command to locate the command
  keyword in an argument list.
- Implemented Arguments::extract_sequence to locate and remove an ID
  sequence from an argument list.
- Added unit test for extract_sequence.
- Stubbed all Argument::extract_<object> methods.
- Simplified code in (soon to be obsolete) Sequence.cpp.
This commit is contained in:
Paul Beckingham 2011-05-28 12:09:25 -04:00
parent 8f85b0e194
commit 306f10b420
9 changed files with 291 additions and 46 deletions

View file

@ -30,6 +30,7 @@
#include <Context.h>
#include <Nibbler.h>
#include <text.h>
#include <util.h>
#include <Arguments.h>
extern Context context;
@ -45,23 +46,12 @@ Arguments::~Arguments ()
}
////////////////////////////////////////////////////////////////////////////////
void Arguments::capture (int argc, char** argv)
void Arguments::capture (int argc, const char** argv)
{
for (int i = 0; i < argc; ++i)
{
/*
if (i == 0)
{
std::string::size_type cal = context.program.find ("/cal");
if (context.program == "cal" ||
(cal != std::string::npos && context.program.length () == cal + 4))
this->push_back ("calendar");
}
else
*/
if (i > 0)
this->push_back (argv[i]);
}
}
////////////////////////////////////////////////////////////////////////////////
void Arguments::append_stdin ()
@ -241,3 +231,159 @@ std::string Arguments::combine ()
}
////////////////////////////////////////////////////////////////////////////////
// Given a vector of command keywords, scan all arguments and locate the first
// argument that matches a keyword.
bool Arguments::extract_command (
const std::vector <std::string>& keywords,
std::string& command)
{
std::vector <std::string>::iterator arg;
for (arg = this->begin (); arg != this->end (); ++arg)
{
std::vector <std::string> matches;
if (autoComplete (*arg, keywords, matches) == 1)
{
if (*arg != matches[0])
context.debug ("Arguments::extract_command keyword '" + *arg + "' --> '" + matches[0] + "'");
else
context.debug ("Arguments::extract_command keyword '" + *arg + "'");
command = matches[0];
return true;
}
}
return false;
}
////////////////////////////////////////////////////////////////////////////////
// A sequence can be:
//
// a single ID: 1
// a list of IDs: 1,3,5
// a list of IDs: 1 3 5
// a range: 5-10
// or a combination: 1,3,5-10 12
//
// If a sequence is followed by a non-number, then subsequent numbers are not
// interpreted as IDs. For example:
//
// 1 2 three 4
//
// The sequence is "1 2".
//
// The first number found in the command line is assumed to be a sequence. If
// there are two sequences, only the first is recognized, for example:
//
// 1,2 three 4,5
//
// The sequence is "1,2".
//
void Arguments::extract_sequence (std::vector <int>& sequence)
{
sequence.clear ();
std::vector <int> kill;
bool terminated = false;
for (int i = 0; i < this->size (); ++i)
{
if (!terminated)
{
bool something = false;
// The '--' argument shuts off all parsing - everything is an argument.
if ((*this)[i] == "--")
{
terminated = true;
}
else
{
if (isdigit ((*this)[i][0]))
{
std::vector <std::string> ranges;
split (ranges, (*this)[i], ',');
std::vector <std::string>::iterator it;
for (it = ranges.begin (); it != ranges.end (); ++it)
{
std::vector <std::string> range;
split (range, *it, '-');
if (range.size () == 1)
{
if (! digitsOnly (range[0]))
throw std::string ("Invalid ID in sequence.");
int id = (int)strtol (range[0].c_str (), NULL, 10);
sequence.push_back (id);
something = true;
}
else if (range.size () == 2)
{
if (! digitsOnly (range[0]) ||
! digitsOnly (range[1]))
throw std::string ("Invalid ID in range.");
int low = (int)strtol (range[0].c_str (), NULL, 10);
int high = (int)strtol (range[1].c_str (), NULL, 10);
if (low > high)
throw std::string ("Inverted sequence range high-low.");
if (high - low >= ARGUMENTS_SEQUENCE_MAX_RANGE)
throw std::string ("ID Range too large.");
for (int r = low; r <= high; ++r)
sequence.push_back (r);
something = true;
}
// Not a properly formed sequence, therefore probably text.
else
break;
}
}
// Once a sequence has been found, any non-numeric arguments effectively
// terminate sequence processing.
else if (sequence.size ())
terminated = true;
}
if (something)
kill.push_back (i);
}
}
// Now remove args in the kill list.
for (int k = 0; k < kill.size (); ++k)
this->erase (this->begin () + kill[k]);
}
////////////////////////////////////////////////////////////////////////////////
// TODO
void Arguments::extract_uuids (std::vector <std::string>& uuids)
{
uuids.clear ();
}
////////////////////////////////////////////////////////////////////////////////
// TODO
void Arguments::extract_filter ()
{
}
////////////////////////////////////////////////////////////////////////////////
// TODO
void Arguments::extract_modifications ()
{
}
////////////////////////////////////////////////////////////////////////////////
// TODO
void Arguments::extract_text ()
{
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -32,19 +32,28 @@
#include <string>
#include <File.h>
#define ARGUMENTS_SEQUENCE_MAX_RANGE 1000
class Arguments : public std::vector <std::string>
{
public:
Arguments ();
~Arguments ();
void capture (int, char**);
void capture (int, const char**);
void append_stdin ();
void rc_override (std::string&, File&, std::string&);
void get_data_location (std::string&);
void apply_overrides (std::string&);
void resolve_aliases ();
std::string combine ();
bool extract_command (const std::vector <std::string>&, std::string&);
void extract_sequence (std::vector <int>&);
void extract_uuids (std::vector <std::string>&);
void extract_filter ();
void extract_modifications ();
void extract_text ();
};
#endif

View file

@ -75,11 +75,12 @@ Context::~Context ()
}
////////////////////////////////////////////////////////////////////////////////
void Context::initialize (int argc, char** argv)
void Context::initialize (int argc, const char** argv)
{
Timer t ("Context::initialize");
// char** argv --> std::vector <std::string> Context::args.
// TODO Handle "cal" case here.
program = argv[0];
args.capture (argc, argv);
@ -154,7 +155,7 @@ int Context::run ()
std::string output;
try
{
parse (); // Parse command line.
parse (); // Parse command line. TODO Obsolete
rc = dispatch2 (output); // Dispatch to new command handlers.
if (rc)
rc = dispatch (output); // Dispatch to old command handlers.
@ -219,24 +220,15 @@ int Context::dispatch2 (std::string &out)
keywords.push_back (i->first);
// Autocomplete args against keywords.
std::vector <std::string>::iterator arg;
for (arg = args.begin (); arg != args.end (); ++arg)
std::string command;
if (args.extract_command (keywords, command))
{
std::vector <std::string> matches;
if (autoComplete (*arg, keywords, matches) == 1)
{
if (*arg != matches[0])
debug ("Context::dispatch2 parse keyword '" + *arg + "' --> '" + matches[0] + "'");
else
debug ("Context::dispatch2 parse keyword '" + *arg + "'");
Command* c = commands[matches[0]];
Command* c = commands[command];
if (c->displays_id ())
tdb.gc ();
return c->execute (commandLine, out);
}
}
// TODO When ::dispatch is eliminated, show usage on unrecognized command.
// commands["help"]->execute (commandLine, out);

View file

@ -54,7 +54,7 @@ public:
Context (const Context&);
Context& operator= (const Context&);
void initialize (int, char**); // all startup
void initialize (int, const char**); // all startup
int run ();
int dispatch2 (std::string&); // command handler dispatch
int dispatch (std::string&); // command handler dispatch

View file

@ -26,12 +26,9 @@
////////////////////////////////////////////////////////////////////////////////
#include <map>
#include <vector>
#include <string>
#include <algorithm>
#include <ctype.h>
#include <util.h>
#include <text.h>
#include <i18n.h>
#include <Context.h>
#include <Sequence.h>
@ -156,11 +153,7 @@ bool Sequence::validId (const std::string& input) const
if (input.length () == 0)
return false;
for (size_t i = 0; i < input.length (); ++i)
if (!isdigit (input[i]))
return false;
return true;
return digitsOnly (input);
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -63,7 +63,7 @@ int main (int argc, char** argv)
try
{
context.initialize (argc, argv);
context.initialize (argc, (const char**)argv);
status = context.run ();
}

1
test/.gitignore vendored
View file

@ -1,6 +1,7 @@
*.o
*.data
*.log
arguments.t
att.t
autocomplete.t
cmd.t

View file

@ -6,10 +6,11 @@ include_directories (${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/test
${TASK_INCLUDE_DIRS})
set (test_SRCS att.t autocomplete.t color.t config.t date.t directory.t dom.t
duration.t file.t filt.t i18n.t json.t list.t nibbler.t path.t
record.t rx.t seq.t subst.t t.benchmark.t t.t taskmod.t tdb.t
tdb2.t text.t uri.t util.t variant.t view.t json_test)
set (test_SRCS arguments.t att.t autocomplete.t color.t config.t date.t
directory.t dom.t duration.t file.t filt.t i18n.t json.t list.t
nibbler.t path.t record.t rx.t seq.t subst.t t.benchmark.t t.t
taskmod.t tdb.t tdb2.t text.t uri.t util.t variant.t view.t
json_test)
add_custom_target (test ./run_all DEPENDS ${test_SRCS}
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/test)

103
test/arguments.t.cpp Normal file
View file

@ -0,0 +1,103 @@
////////////////////////////////////////////////////////////////////////////////
// taskwarrior - a command line task list manager.
//
// Copyright 2006 - 2011, Paul Beckingham, Federico Hernandez.
// All rights reserved.
//
// 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.
//
// 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.
//
// You should have received a copy of the GNU General Public License along with
// this program; if not, write to the
//
// Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor,
// Boston, MA
// 02110-1301
// USA
//
////////////////////////////////////////////////////////////////////////////////
#include <Context.h>
#include <Arguments.h>
#include <text.h>
#include <test.h>
Context context;
////////////////////////////////////////////////////////////////////////////////
int main (int argc, char** argv)
{
UnitTest t (14);
const char* fake[] =
{
// task list proj:foo 1,3,5-10 12 pattern1 rc.name1=value1 rc.name2:value2 \
// 234 -- pattern2 n:v
"task", // context.program
"list", // command
"proj:foo", // n:v
"1,3,5-10", // sequence
"12", // sequence
"pattern1", // text
"rc.name1=value1", // n:v
"rc.name2:value2", // n:v
"234", // text
"--", // terminator
"pattern2", // text
"n:v", // text (due to terminator)
};
// void capture (int, char**);
Arguments a1;
a1.capture (12, &fake[0]);
t.is (a1.size (), (size_t)11, "11 arguments expected");
t.is (a1[0], "list", "Arguments properly strips argv[0]");
// std::string combine ();
t.is (a1.combine (),
"list proj:foo 1,3,5-10 12 pattern1 rc.name1=value1 rc.name2:value2 "
"234 -- pattern2 n:v",
"combine good");
// TODO void append_stdin ();
// TODO void rc_override (std::string&, File&, std::string&);
// TODO void get_data_location (std::string&);
// TODO void apply_overrides (std::string&);
// TODO void resolve_aliases ();
// TODO bool extract_command (const std::vector <std::string>&, std::string&);
// void extract_sequence (std::vector <int>&);
std::vector <int> sequence;
a1.extract_sequence (sequence);
size_t s = sequence.size ();
t.is (s, (size_t)9, "1,3,5-10 12 --> 1,3,5,6,7,8,9,10,12 == 9");
if (s > 0) t.is (sequence[0], 1, "sequence 1"); else t.fail ("sequence 1");
if (s > 1) t.is (sequence[1], 3, "sequence 3"); else t.fail ("sequence 3");
if (s > 2) t.is (sequence[2], 5, "sequence 5"); else t.fail ("sequence 5");
if (s > 3) t.is (sequence[3], 6, "sequence 6"); else t.fail ("sequence 6");
if (s > 4) t.is (sequence[4], 7, "sequence 7"); else t.fail ("sequence 7");
if (s > 5) t.is (sequence[5], 8, "sequence 8"); else t.fail ("sequence 8");
if (s > 6) t.is (sequence[6], 9, "sequence 9"); else t.fail ("sequence 9");
if (s > 7) t.is (sequence[7], 10, "sequence 10"); else t.fail ("sequence 10");
if (s > 8) t.is (sequence[8], 12, "sequence 12"); else t.fail ("sequence 12");
t.is (a1.size (), (size_t)9, "a1 - <sequence> = 9 args");
// TODO void extract_uuids (std::vector <std::string>&);
// TODO void extract_filter ();
// TODO void extract_modifications ();
// TODO void extract_text ();
return 0;
}
////////////////////////////////////////////////////////////////////////////////