Repository: fniessink/next-action

Issue:
Option to write out a default configuration file

Discussion:

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d6fd7c7..7768ac2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 ### Added
 
 - Option to not read a configuration file. Closes #71.
+- Option to write a default configuration file. Closes #68.
 
 ## [0.7.0] - 2018-05-23
 
diff --git a/README.md b/README.md
index 8ff4108..762ffb6 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ optional arguments:
   -c <config.cfg>, --config-file <config.cfg>
                         filename of configuration file to read (default: ~/.next-action.cfg)
   -C, --no-config-file  don't read the configuration file
+  --write-config-file   generate a sample configuration file and exit
   -f <todo.txt>, --file <todo.txt>
                         filename of todo.txt file to read; can be '-' to read from standard input; argument can be
                         repeated to read tasks from multiple todo.txt files (default: ~/todo.txt)
@@ -121,7 +122,20 @@ Note again that completed tasks and task with a future creation date are never s
 
 ### Configuring *Next-action*
 
-Instead of specifying which todo.txt files to read on the command-line, you can also configure this in a configuration file. By default, *Next-action* tries to read a file called [.next-action.cfg](https://raw.githubusercontent.com/fniessink/next-action/master/docs/.next-action.cfg) in your home folder, but you can tell it to read another configuration file:
+In addition to specifying options on the command-line, you can also configure options in a configuration file. By default, *Next-action* tries to read a file called [.next-action.cfg](https://raw.githubusercontent.com/fniessink/next-action/master/docs/.next-action.cfg) in your home folder.
+
+To get started, you can tell *Next-action* to generate a configuration file with the default options:
+
+```console
+$ next-action --write-config-file
+# Configuration file for Next-action. Edit the settings below as you like.
+file: ~/todo.txt
+number: 1
+```
+
+To make this the configuration that *Next-action* reads by default, redirect the output to `~/.next-action.cfg` like this: `next-action --write-config-file > ~/.next-action.cfg`.
+
+If you want to use a configuration file that is not in the default location (`~/.next-action.cfg`), you'll need to explicitly tell *Next-action* its location:
 
 ```console
 $ next-action --config-file docs/.next-action.cfg
@@ -175,9 +189,9 @@ To run the unit tests:
 
 ```console
 $ python -m unittest
-.......................................................................................................................
+........................................................................................................................
 ----------------------------------------------------------------------
-Ran 119 tests in 0.168s
+Ran 120 tests in 0.346s
 
 OK
 ```
diff --git a/docs/update_readme.py b/docs/update_readme.py
index 5333ee9..b8de9af 100644
--- a/docs/update_readme.py
+++ b/docs/update_readme.py
@@ -19,7 +19,7 @@ def update_readme():
             elif line.startswith("$ "):
                 print(line)
                 command = line[2:].split(" ")
-                if command[0] == "next-action":
+                if command[0] == "next-action" and "--write-config-file" not in command:
                     command.extend(["--config", "docs/.next-action.cfg"])
                 command_output = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                                 check=True, universal_newlines=True)
diff --git a/next_action/arguments/parser.py b/next_action/arguments/parser.py
index 3c349f6..604915c 100644
--- a/next_action/arguments/parser.py
+++ b/next_action/arguments/parser.py
@@ -4,6 +4,8 @@ import argparse
 import sys
 from typing import List
 
+import yaml
+
 import next_action
 from .config import read_config_file, validate_config_file
 
@@ -32,6 +34,8 @@ class NextActionArgumentParser(argparse.ArgumentParser):
             help="filename of configuration file to read (default: %(default)s)")
         config_file.add_argument(
             "-C", "--no-config-file", help="don't read the configuration file", action="store_true")
+        config_file.add_argument(
+            "--write-config-file", help="generate a sample configuration file and exit", action="store_true")
         self.add_argument(
             "-f", "--file", action="append", metavar="<todo.txt>", default=default_filenames[:], type=str,
             help="filename of todo.txt file to read; can be '-' to read from standard input; argument can be "
@@ -70,6 +74,8 @@ class NextActionArgumentParser(argparse.ArgumentParser):
         self.parse_remaining_args(remaining, namespace)
         if not namespace.no_config_file:
             self.process_config_file(namespace)
+        if namespace.write_config_file:
+            self.write_config_file()
         return namespace
 
     def parse_remaining_args(self, remaining: List[str], namespace: argparse.Namespace) -> None:
@@ -102,6 +108,13 @@ class NextActionArgumentParser(argparse.ArgumentParser):
             number = sys.maxsize if config.get("all", False) else config.get("number", 1)
             setattr(namespace, "number", number)
 
+    def write_config_file(self) -> None:
+        """ Generate a configuration file on standard out and exi. """
+        intro = "# Configuration file for Next-action. Edit the settings below as you like.\n"
+        config = yaml.dump(dict(file="~/todo.txt", number=1), default_flow_style=False)
+        sys.stdout.write(intro + config)
+        self.exit()
+
     def arguments_not_specified(self, namespace: argparse.Namespace, *arguments: str) -> bool:
         """ Return whether the arguments were not specified on the command line. """
         return all([getattr(namespace, argument) == self.get_default(argument) for argument in arguments])


Test patch:
diff --git a/tests/unittests/arguments/test_config.py b/tests/unittests/arguments/test_config.py
index e5bae6c..775e336 100644
--- a/tests/unittests/arguments/test_config.py
+++ b/tests/unittests/arguments/test_config.py
@@ -88,6 +88,16 @@ class ConfigFileTest(unittest.TestCase):
         parse_arguments()
         self.assertEqual([], mock_file_open.call_args_list)
 
+    @patch.object(sys, "argv", ["next-action", "--write-config-file"])
+    @patch.object(config, "open", mock_open(read_data=""))
+    @patch.object(sys.stdout, "write")
+    def test_write_config(self, mock_stdout_write):
+        """ Test that a config file can be written to stdout. """
+        self.assertRaises(SystemExit, parse_arguments)
+        expected = "# Configuration file for Next-action. Edit the settings below as you like.\n"
+        expected += "file: ~/todo.txt\nnumber: 1\n"
+        self.assertEqual([call(expected)], mock_stdout_write.call_args_list)
+
 
 class FilenameTest(unittest.TestCase):
     """ Unit tests for the config file parameter. """
diff --git a/tests/unittests/test_cli.py b/tests/unittests/test_cli.py
index 754fe21..0eca016 100644
--- a/tests/unittests/test_cli.py
+++ b/tests/unittests/test_cli.py
@@ -77,6 +77,7 @@ optional arguments:
   -c <config.cfg>, --config-file <config.cfg>
                         filename of configuration file to read (default: ~/.next-action.cfg)
   -C, --no-config-file  don't read the configuration file
+  --write-config-file   generate a sample configuration file and exit
   -f <todo.txt>, --file <todo.txt>
                         filename of todo.txt file to read; can be '-' to read from standard input; argument can be
                         repeated to read tasks from multiple todo.txt files (default: ~/todo.txt)