mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-09-06 17:37:21 +02:00
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:
parent
8f85b0e194
commit
306f10b420
9 changed files with 291 additions and 46 deletions
|
@ -30,6 +30,7 @@
|
|||
#include <Context.h>
|
||||
#include <Nibbler.h>
|
||||
#include <text.h>
|
||||
#include <util.h>
|
||||
#include <Arguments.h>
|
||||
|
||||
extern Context context;
|
||||
|
@ -45,22 +46,11 @@ 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]);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -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 ()
|
||||
{
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,23 +220,14 @@ 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[command];
|
||||
if (c->displays_id ())
|
||||
tdb.gc ();
|
||||
|
||||
Command* c = commands[matches[0]];
|
||||
if (c->displays_id ())
|
||||
tdb.gc ();
|
||||
|
||||
return c->execute (commandLine, out);
|
||||
}
|
||||
return c->execute (commandLine, out);
|
||||
}
|
||||
|
||||
// TODO When ::dispatch is eliminated, show usage on unrecognized command.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
1
test/.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
*.o
|
||||
*.data
|
||||
*.log
|
||||
arguments.t
|
||||
att.t
|
||||
autocomplete.t
|
||||
cmd.t
|
||||
|
|
|
@ -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
103
test/arguments.t.cpp
Normal 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;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue