diff --git a/src/Arguments.cpp b/src/Arguments.cpp index 7774f7b71..8cd497b30 100644 --- a/src/Arguments.cpp +++ b/src/Arguments.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include 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 & keywords, + std::string& command) +{ + std::vector ::iterator arg; + for (arg = this->begin (); arg != this->end (); ++arg) + { + std::vector 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 & sequence) +{ + sequence.clear (); + std::vector 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 ranges; + split (ranges, (*this)[i], ','); + + std::vector ::iterator it; + for (it = ranges.begin (); it != ranges.end (); ++it) + { + std::vector 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 & uuids) +{ + uuids.clear (); + +} + +//////////////////////////////////////////////////////////////////////////////// +// TODO +void Arguments::extract_filter () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// TODO +void Arguments::extract_modifications () +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// TODO +void Arguments::extract_text () +{ +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/src/Arguments.h b/src/Arguments.h index ad44d7c8e..2adea6052 100644 --- a/src/Arguments.h +++ b/src/Arguments.h @@ -32,19 +32,28 @@ #include #include +#define ARGUMENTS_SEQUENCE_MAX_RANGE 1000 + class Arguments : public std::vector { 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&); + void extract_sequence (std::vector &); + void extract_uuids (std::vector &); + void extract_filter (); + void extract_modifications (); + void extract_text (); }; #endif diff --git a/src/Context.cpp b/src/Context.cpp index 270a7eca1..a60188ac2 100644 --- a/src/Context.cpp +++ b/src/Context.cpp @@ -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 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 ::iterator arg; - for (arg = args.begin (); arg != args.end (); ++arg) + std::string command; + if (args.extract_command (keywords, command)) { - std::vector 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. diff --git a/src/Context.h b/src/Context.h index 0db731da3..0e32ae55b 100644 --- a/src/Context.h +++ b/src/Context.h @@ -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 diff --git a/src/Sequence.cpp b/src/Sequence.cpp index ad3a51135..949e4d5ff 100644 --- a/src/Sequence.cpp +++ b/src/Sequence.cpp @@ -26,12 +26,9 @@ //////////////////////////////////////////////////////////////////////////////// #include +#include #include -#include -#include -#include #include -#include #include #include @@ -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); } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/main.cpp b/src/main.cpp index b797a4f64..2f8e18569 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -63,7 +63,7 @@ int main (int argc, char** argv) try { - context.initialize (argc, argv); + context.initialize (argc, (const char**)argv); status = context.run (); } diff --git a/test/.gitignore b/test/.gitignore index 33c6f6bba..6b611cc1a 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,6 +1,7 @@ *.o *.data *.log +arguments.t att.t autocomplete.t cmd.t diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5f049d899..c80059fc4 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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) diff --git a/test/arguments.t.cpp b/test/arguments.t.cpp new file mode 100644 index 000000000..2d7e0ab49 --- /dev/null +++ b/test/arguments.t.cpp @@ -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 +#include +#include +#include + +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&); + + // void extract_sequence (std::vector &); + std::vector 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 - = 9 args"); + + // TODO void extract_uuids (std::vector &); + // TODO void extract_filter (); + // TODO void extract_modifications (); + // TODO void extract_text (); + + return 0; +} + +//////////////////////////////////////////////////////////////////////////////// +