- Added feature #800, adding a new command 'columns' that lists all the columns
  available for custom reports, and includes their formatting options (thanks
  to T. Charles Yun).
This commit is contained in:
Paul Beckingham 2011-07-16 13:08:23 -04:00
parent 3a5370ddf1
commit 27a04b29f5
28 changed files with 372 additions and 64 deletions

View file

@ -82,6 +82,9 @@
their descriptions.
+ Added feature #779, which uses more relevant and consistent terms on the
'burndown' charts.
+ Added feature #800, adding a new command 'columns' that lists all the columns
available for custom reports, and includes their formatting options (thanks
to T. Charles Yun).
# Tracked Bugs, sorted by ID.
+ Fixed bug #403, which disambiguates certain commands involving numbers.

1
NEWS
View file

@ -23,6 +23,7 @@ New Features in taskwarrior 2.0.0
- Filtering now available on most read-only commands.
- The done, delete, start and stop commands now allow modification to the
task and annotations.
- New 'columns' command to list the supported columns and formats.
Please refer to the ChangeLog file for full details. There are too many to
list here.

View file

@ -152,6 +152,12 @@ std::string Date::toISO ()
return iso.str ();
}
////////////////////////////////////////////////////////////////////////////////
double Date::toJulian ()
{
return (mT / 86400.0) + 2440587.5;
}
////////////////////////////////////////////////////////////////////////////////
void Date::toEpoch (time_t& epoch)
{

View file

@ -49,6 +49,7 @@ public:
time_t toEpoch ();
std::string toEpochString ();
std::string toISO ();
double toJulian ();
void toMDY (int&, int&, int&);
const std::string toString (const std::string& format = "m/d/Y") const;

View file

@ -111,7 +111,7 @@ std::string ViewTask::render (std::vector <Task>& data, std::vector <int>& seque
for (i = _columns.begin (); i != _columns.end (); ++i)
{
// Headers factor in to width calculations.
int global_min = utf8_length ((*i)->getLabel ());
int global_min = utf8_length ((*i)->label ());
int global_ideal = global_min;
for (unsigned int s = 0; s < sequence.size (); ++s)

View file

@ -115,7 +115,7 @@ std::string ViewText::render ()
for (unsigned int col = 0; col < _columns.size (); ++col)
{
// Headers factor in to width calculations.
int global_min = utf8_length (_columns[col]->getLabel ());
int global_min = utf8_length (_columns[col]->label ());
int global_ideal = global_min;
for (unsigned int row = 0; row < _data.size (); ++row)

View file

@ -43,8 +43,22 @@ ColumnDate::ColumnDate ()
{
_name = "";
_type = "date";
_style = "default";
_style = "formatted";
_label = "";
_styles.push_back ("formatted");
_styles.push_back ("julian");
_styles.push_back ("epoch");
_styles.push_back ("iso");
_styles.push_back ("age");
Date now;
now -= 125; // So that "age" is non-zero.
_examples.push_back (now.toString (context.config.get ("dateformat")));
_examples.push_back (format (now.toJulian (), 13, 12));
_examples.push_back (now.toEpochString ());
_examples.push_back (now.toISO ());
_examples.push_back (Duration (Date () - now).formatCompact ());
}
////////////////////////////////////////////////////////////////////////////////
@ -68,7 +82,8 @@ void ColumnDate::measure (Task& task, int& minimum, int& maximum)
{
Date date ((time_t) strtol (task.get (_name).c_str (), NULL, 10));
if (_style == "default")
if (_style == "default" ||
_style == "formatted")
{
// Determine the output date format, which uses a hierarchy of definitions.
// rc.report.<report>.dateformat
@ -84,9 +99,7 @@ void ColumnDate::measure (Task& task, int& minimum, int& maximum)
}
else if (_style == "julian")
{
// (JD 2440587.5) × 86400
double julian = (date.toEpoch () / 86400.0) + 2440587.5;
minimum = maximum = format (julian, 13, 12).length ();
minimum = maximum = format (date.toJulian (), 13, 12).length ();
}
else if (_style == "epoch")
{
@ -115,7 +128,8 @@ void ColumnDate::render (
{
if (task.has (_name))
{
if (_style == "default")
if (_style == "default" ||
_style == "formatted")
{
// Determine the output date format, which uses a hierarchy of definitions.
// rc.report.<report>.dateformat
@ -135,13 +149,11 @@ void ColumnDate::render (
}
else if (_style == "julian")
{
double julian = (Date ((time_t) strtol (task.get (_name).c_str (), NULL, 10))
.toEpoch () / 86400.0) + 2440587.5;
lines.push_back (
color.colorize (
rightJustify (
format (julian, 13, 12), width)));
format (Date ((time_t) strtol (task.get (_name).c_str (), NULL, 10))
.toJulian (), 13, 12), width)));
}
else if (_style == "epoch")
{
@ -169,18 +181,6 @@ void ColumnDate::render (
rightJustify (
Duration (now - date).formatCompact (), width)));
}
else if (_style == "short")
{
}
else if (_style == "active")
{
}
else if (_style == "countdown")
{
}
else if (_style == "remaining")
{
}
}
}

View file

@ -41,8 +41,16 @@ ColumnDepends::ColumnDepends ()
{
_name = "depends";
_type = "string";
_style = "default";
_style = "list";
_label = STRING_COLUMN_LABEL_DEP;
_styles.push_back ("list");
_styles.push_back ("count");
_styles.push_back ("indicator");
_examples.push_back ("1 2 10");
_examples.push_back ("[3]");
_examples.push_back (context.config.get ("dependency.indicator"));
}
////////////////////////////////////////////////////////////////////////////////
@ -76,7 +84,8 @@ void ColumnDepends::measure (Task& task, int& minimum, int& maximum)
if (_style == "indicator") minimum = maximum = context.config.get ("dependency.indicator").length ();
else if (_style == "count") minimum = maximum = 2 + format ((int) blocking.size ()).length ();
else if (_style == "default")
else if (_style == "default" ||
_style == "list")
{
minimum = maximum = 0;
if (task.has ("depends"))
@ -129,7 +138,8 @@ void ColumnDepends::render (
color.colorize (
rightJustify ("[" + format ((int)blocking.size ()) + "]", width)));
}
else if (_style == "default")
else if (_style == "default" ||
_style == "list")
{
std::vector <int> blocking_ids;
std::vector <Task>::iterator t;

View file

@ -42,8 +42,35 @@ ColumnDescription::ColumnDescription ()
{
_name = "description";
_type = "string";
_style = "default";
_style = "combined";
_label = STRING_COLUMN_LABEL_DESC;
_styles.push_back ("combined");
_styles.push_back ("desc");
_styles.push_back ("oneline");
_styles.push_back ("truncated");
_styles.push_back ("count");
std::string t = Date ().toString (context.config.get ("dateformat"));
std::string d = STRING_COLUMN_EXAMPLES_DESC;
std::string a1 = STRING_COLUMN_EXAMPLES_ANNO1;
std::string a2 = STRING_COLUMN_EXAMPLES_ANNO2;
std::string a3 = STRING_COLUMN_EXAMPLES_ANNO3;
std::string a4 = STRING_COLUMN_EXAMPLES_ANNO4;
_examples.push_back (d
+ "\n " + t + " " + a1
+ "\n " + t + " " + a2
+ "\n " + t + " " + a3
+ "\n " + t + " " + a4);
_examples.push_back (d);
_examples.push_back (d
+ " " + t + " " + a1
+ " " + t + " " + a2
+ " " + t + " " + a3
+ " " + t + " " + a4);
_examples.push_back (d.substr (0, 20) + "...");
_examples.push_back (d + " [4]");
}
////////////////////////////////////////////////////////////////////////////////
@ -66,7 +93,8 @@ void ColumnDescription::measure (Task& task, int& minimum, int& maximum)
// The text
// <indent> <date> <anno>
// ...
if (_style == "default")
if (_style == "default" ||
_style == "combined")
{
int indent = context.config.getInteger ("indent.annotation");
std::string format = context.config.get ("dateformat.annotation");
@ -149,7 +177,8 @@ void ColumnDescription::render (
// This is a description
// <date> <anno>
// ...
if (_style == "default")
if (_style == "default" ||
_style == "combined")
{
int indent = context.config.getInteger ("indent.annotation");

View file

@ -42,6 +42,12 @@ ColumnDue::ColumnDue ()
{
_name = "due";
_label = STRING_COLUMN_LABEL_DUE;
_styles.push_back ("countdown");
Date now;
now += 125;
_examples.push_back (Duration (now - Date ()).formatCompact ());
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -40,8 +40,12 @@ ColumnID::ColumnID ()
{
_name = "id";
_type = "number";
_style = "default";
_style = "number";
_label = STRING_COLUMN_LABEL_ID;
_styles.push_back ("number");
_examples.push_back ("123");
}
////////////////////////////////////////////////////////////////////////////////
@ -69,7 +73,8 @@ void ColumnID::measure (Task& task, int& minimum, int& maximum)
minimum = maximum = length;
if (_style != "default")
if (_style != "default" &&
_style != "number")
throw format (STRING_COLUMN_BAD_FORMAT, _name, _style);
}

View file

@ -39,8 +39,14 @@ ColumnPriority::ColumnPriority ()
{
_name = "priority";
_type = "string";
_style = "default";
_style = "short";
_label = STRING_COLUMN_LABEL_PRI;
_styles.push_back ("short");
_styles.push_back ("long");
_examples.push_back ("H");
_examples.push_back ("High");
}
////////////////////////////////////////////////////////////////////////////////
@ -86,7 +92,8 @@ void ColumnPriority::measure (Task& task, int& minimum, int& maximum)
else if (priority == "M") minimum = maximum = 6;
else if (priority == "L") minimum = maximum = 3;
}
else if (_style != "default")
else if (_style != "default" &&
_style != "short")
throw format (STRING_COLUMN_BAD_FORMAT, "priority", _style);
}

View file

@ -39,8 +39,14 @@ ColumnProject::ColumnProject ()
{
_name = "project";
_type = "string";
_style = "default";
_style = "full";
_label = STRING_COLUMN_LABEL_PROJECT;
_styles.push_back ("full");
_styles.push_back ("parent");
_examples.push_back (STRING_COLUMN_EXAMPLES_PROJ);
_examples.push_back (STRING_COLUMN_EXAMPLES_PAR);
}
////////////////////////////////////////////////////////////////////////////////
@ -66,7 +72,8 @@ void ColumnProject::measure (Task& task, int& minimum, int& maximum)
if (period != std::string::npos)
project = project.substr (0, period);
}
else if (_style != "default")
else if (_style != "default" &&
_style != "full")
throw format (STRING_COLUMN_BAD_FORMAT, _name, _style);
minimum = longestWord (project);

View file

@ -39,8 +39,14 @@ ColumnRecur::ColumnRecur ()
{
_name = "recur";
_type = "string";
_style = "default";
_style = "duration";
_label = STRING_COLUMN_LABEL_RECUR;
_styles.push_back ("duration");
_styles.push_back ("indicator");
_examples.push_back ("weekly");
_examples.push_back (context.config.get ("recurrence.indicator"));
}
////////////////////////////////////////////////////////////////////////////////
@ -69,7 +75,8 @@ void ColumnRecur::setStyle (const std::string& value)
// Set the minimum and maximum widths for the value.
void ColumnRecur::measure (Task& task, int& minimum, int& maximum)
{
if (_style == "default")
if (_style == "default" ||
_style == "duration")
{
minimum = maximum = task.get ("recur").length ();
}
@ -89,7 +96,8 @@ void ColumnRecur::render (
int width,
Color& color)
{
if (_style == "default")
if (_style == "default" ||
_style == "duration")
{
lines.push_back (color.colorize (rightJustify (task.get ("recur"), width)));
}

View file

@ -39,6 +39,10 @@ ColumnStart::ColumnStart ()
{
_name = "start";
_label = STRING_COLUMN_LABEL_STARTED;
_styles.push_back ("active");
_examples.push_back (context.config.get ("active.indicator"));
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -39,8 +39,14 @@ ColumnStatus::ColumnStatus ()
{
_name = "status";
_type = "string";
_style = "default";
_style = "long";
_label = STRING_COLUMN_LABEL_STATUS;
_styles.push_back ("long");
_styles.push_back ("short");
_examples.push_back (STRING_COLUMN_LABEL_STAT_PE);
_examples.push_back (STRING_COLUMN_LABEL_STAT_P);
}
////////////////////////////////////////////////////////////////////////////////
@ -71,7 +77,8 @@ void ColumnStatus::measure (Task& task, int& minimum, int& maximum)
{
Task::status status = task.getStatus ();
if (_style == "default")
if (_style == "default" ||
_style == "long")
{
if (status == Task::pending ||
status == Task::deleted ||
@ -101,7 +108,8 @@ void ColumnStatus::render (
Task::status status = task.getStatus ();
std::string value;
if (_style == "default")
if (_style == "default" ||
_style == "long")
{
if (status == Task::pending) value = STRING_COLUMN_LABEL_STAT_PE;
else if (status == Task::completed) value = STRING_COLUMN_LABEL_STAT_CO;

View file

@ -39,8 +39,18 @@ ColumnString::ColumnString ()
{
_name = "string";
_type = "string";
_style = "default";
_style = "left";
_label = "";
_styles.push_back ("left");
_styles.push_back ("right");
_styles.push_back ("left_fixed");
_styles.push_back ("right_fixed");
_styles.push_back ("Hello (wrapped) ");
_styles.push_back (" Hello (wrapped)");
_styles.push_back ("Hello (no-wrap) ");
_styles.push_back (" Hello (no-wrap)");
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -40,8 +40,16 @@ ColumnTags::ColumnTags ()
{
_name = "tags";
_type = "string";
_style = "default";
_style = "list";
_label = STRING_COLUMN_LABEL_TAGS;
_styles.push_back ("list");
_styles.push_back ("indicator");
_styles.push_back ("count");
_examples.push_back (STRING_COLUMN_EXAMPLES_TAGS);
_examples.push_back (context.config.get ("tag.indicator"));
_examples.push_back ("[2]");
}
////////////////////////////////////////////////////////////////////////////////
@ -78,7 +86,8 @@ void ColumnTags::measure (Task& task, int& minimum, int& maximum)
if (_style == "indicator") minimum = maximum = context.config.get ("tag.indicator").length ();
else if (_style == "count") minimum = maximum = 3;
else if (_style == "default")
else if (_style == "default" ||
_style == "list")
{
std::string tags = task.get (_name);
minimum = 0;
@ -122,7 +131,8 @@ void ColumnTags::render (
color.colorize (
rightJustify ("[" + format ((int)all.size ()) + "]", width)));
}
else if (_style == "default")
else if (_style == "default" ||
_style == "list")
{
std::replace (tags.begin (), tags.end (), ',', ' ');
std::vector <std::string> all;

View file

@ -40,8 +40,14 @@ ColumnUUID::ColumnUUID ()
{
_name = "uuid";
_type = "string";
_style = "default";
_style = "long";
_label = STRING_COLUMN_LABEL_UUID;
_styles.push_back ("long");
_styles.push_back ("short");
_examples.push_back ("f30cb9c3-3fc0-483f-bfb2-3bf134f00694");
_examples.push_back ("34f00694");
}
////////////////////////////////////////////////////////////////////////////////
@ -59,7 +65,7 @@ bool ColumnUUID::validate (std::string& value)
// Set the minimum and maximum widths for the value.
void ColumnUUID::measure (Task&, int& minimum, int& maximum)
{
if (_style == "default") minimum = maximum = 36;
if (_style == "default" || _style == "long") minimum = maximum = 36;
else if (_style == "short") minimum = maximum = 8;
else
throw format (STRING_COLUMN_BAD_FORMAT, _name, _style);
@ -74,7 +80,8 @@ void ColumnUUID::render (
{
// f30cb9c3-3fc0-483f-bfb2-3bf134f00694 default
// 34f00694 short
if (_style == "default")
if (_style == "default" ||
_style == "long")
lines.push_back (color.colorize (leftJustify (task.get (_name), width)));
else if (_style == "short")

View file

@ -39,8 +39,14 @@ ColumnUrgency::ColumnUrgency ()
{
_name = "urgency";
_type = "number";
_style = "default";
_style = "real";
_label = STRING_COLUMN_LABEL_URGENCY;
_styles.push_back ("real");
_styles.push_back ("integer");
_examples.push_back ("4.6");
_examples.push_back ("4");
}
////////////////////////////////////////////////////////////////////////////////
@ -52,9 +58,17 @@ ColumnUrgency::~ColumnUrgency ()
// Set the minimum and maximum widths for the value.
void ColumnUrgency::measure (Task& task, int& minimum, int& maximum)
{
if (_style == "default" ||
_style == "real")
{
minimum = maximum = format (task.urgency (), 4, 3).length ();
}
else if (_style == "integer")
{
minimum = maximum = format ((int)task.urgency ()).length ();
}
if (_style != "default")
else
throw format (STRING_COLUMN_BAD_FORMAT, _name, _style);
}
@ -65,10 +79,21 @@ void ColumnUrgency::render (
int width,
Color& color)
{
if (_style == "default" ||
_style == "real")
{
lines.push_back (
color.colorize (
rightJustify (
format (task.urgency (), 4, 3), width)));
}
else if (_style == "integer")
{
lines.push_back (
color.colorize (
rightJustify (
format ((int)task.urgency ()), width)));
}
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -45,9 +45,11 @@ public:
bool operator== (const Column&) const; // TODO Is this necessary?
~Column ();
std::string getStyle () { return _style; }
std::string getLabel () { return _label; }
std::string style () const { return _style; }
std::string label () const { return _label; }
std::string type () const { return _type; }
std::vector <std::string> styles () const { return _styles; }
std::vector <std::string> examples () const { return _examples; }
virtual void setStyle (const std::string& value) { _style = value; }
virtual void setLabel (const std::string& value) { _label = value; }
@ -66,6 +68,8 @@ protected:
std::string _style;
std::string _label;
std::string _report;
std::vector <std::string> _styles;
std::vector <std::string> _examples;
};
#endif

View file

@ -13,6 +13,7 @@ set (commands_SRCS Command.cpp Command.h
CmdCalendar.cpp CmdCalendar.h
CmdCommands.cpp CmdCommands.h
CmdColor.cpp CmdColor.h
CmdColumns.cpp CmdColumns.h
CmdConfig.cpp CmdConfig.h
CmdCount.cpp CmdCount.h
CmdCustom.cpp CmdCustom.h

View file

@ -0,0 +1,97 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#define L10N // Localization complete.
#include <algorithm>
#include <Context.h>
#include <ViewText.h>
#include <Color.h>
#include <text.h>
#include <i18n.h>
#include <main.h>
#include <CmdColumns.h>
extern Context context;
////////////////////////////////////////////////////////////////////////////////
CmdColumns::CmdColumns ()
{
_keyword = "columns";
_usage = "task columns";
_description = STRING_CMD_COLUMNS_USAGE;
_read_only = true;
_displays_id = false;
}
////////////////////////////////////////////////////////////////////////////////
int CmdColumns::execute (std::string& output)
{
// Include all columns in the table.
std::vector <std::string> names;
std::map <std::string, Column*>::const_iterator col;
for (col = context.columns.begin (); col != context.columns.end (); ++col)
names.push_back (col->first);
std::sort (names.begin (), names.end ());
// Render a list of project names from the map.
ViewText formats;
formats.width (context.getWidth ());
formats.add (Column::factory ("string", STRING_COLUMN_LABEL_COLUMN));
formats.add (Column::factory ("string", STRING_COLUMN_LABEL_STYLES));
formats.add (Column::factory ("string", STRING_COLUMN_LABEL_EXAMPLES));
Color alternate (context.config.get ("color.alternate"));
formats.colorOdd (alternate);
formats.intraColorOdd (alternate);
std::vector <std::string>::iterator name;
for (name = names.begin (); name != names.end (); ++name)
{
const std::vector <std::string> styles = context.columns[*name]->styles ();
const std::vector <std::string> examples = context.columns[*name]->examples ();
for (unsigned int i = 0; i < styles.size (); ++i)
{
int row = formats.addRow ();
formats.set (row, 0, i == 0 ? *name : "");
formats.set (row, 1, styles[i] + (i == 0 ? "*" : ""));
formats.set (row, 2, i < examples.size () ? examples[i] : "");
}
}
output = optionalBlankLine ()
+ formats.render ()
+ "\n"
+ STRING_CMD_COLUMNS_NOTE
+ "\n";
return 0;
}
////////////////////////////////////////////////////////////////////////////////

42
src/commands/CmdColumns.h Normal file
View file

@ -0,0 +1,42 @@
////////////////////////////////////////////////////////////////////////////////
// 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
//
////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDED_CMDCOLUMNS
#define INCLUDED_CMDCOLUMNS
#define L10N // Localization complete.
#include <string>
#include <Command.h>
class CmdColumns : public Command
{
public:
CmdColumns ();
int execute (std::string&);
};
#endif
////////////////////////////////////////////////////////////////////////////////

View file

@ -43,6 +43,7 @@
#include <CmdBurndown.h>
#include <CmdCalendar.h>
#include <CmdColor.h>
#include <CmdColumns.h>
#include <CmdCommands.h>
#include <CmdConfig.h>
#include <CmdCount.h>
@ -102,6 +103,7 @@ void Command::factory (std::map <std::string, Command*>& all)
c = new CmdBurndownWeekly (); all[c->keyword ()] = c;
c = new CmdCalendar (); all[c->keyword ()] = c;
c = new CmdColor (); all[c->keyword ()] = c;
c = new CmdColumns (); all[c->keyword ()] = c;
c = new CmdCompletionCommands (); all[c->keyword ()] = c;
c = new CmdCompletionConfig (); all[c->keyword ()] = c;
c = new CmdCompletionIds (); all[c->keyword ()] = c;

View file

@ -155,6 +155,19 @@
#define STRING_COLUMN_LABEL_FG "Foreground color"
#define STRING_COLUMN_LABEL_BG "Background color"
#define STRING_COLUMN_LABEL_DATE "Date"
#define STRING_COLUMN_LABEL_COLUMN "Columns"
#define STRING_COLUMN_LABEL_STYLES "Supported Formats"
#define STRING_COLUMN_LABEL_EXAMPLES "Example"
// Column Examples
#define STRING_COLUMN_EXAMPLES_TAGS "home @chore"
#define STRING_COLUMN_EXAMPLES_PROJ "home.garden"
#define STRING_COLUMN_EXAMPLES_PAR "home"
#define STRING_COLUMN_EXAMPLES_DESC "Move your clothes down on to the lower peg"
#define STRING_COLUMN_EXAMPLES_ANNO1 "Immediately before your lunch"
#define STRING_COLUMN_EXAMPLES_ANNO2 "If you are playing in the match this afternoon"
#define STRING_COLUMN_EXAMPLES_ANNO3 "Before you write your letter home"
#define STRING_COLUMN_EXAMPLES_ANNO4 "If you're not getting your hair cut"
// commands/Cmd*
#define STRING_CMD_CONFLICT "Custom report '{1}' conflicts with built-in task command."
@ -287,6 +300,8 @@
#define STRING_CMD_ANNO_DONE "Annotated {1} '{2}'"
#define STRING_CMD_ANNO_SUMMARY "Annotated {1} task."
#define STRING_CMD_ANNO_SUMMARY_N "Annotated {1} tasks."
#define STRING_CMD_COLUMNS_USAGE "Displays supported columns and styles."
#define STRING_CMD_COLUMNS_NOTE "* Means default format, and therefore optionsl. For example, 'due' and 'due.formatted' are equivalent."
// Config
#define STRING_CONFIG_OVERNEST "Configuration file nested to more than 10 levels deep - this has to be a mistake."