Repository: pydoit/doit

Issue:
Use natural order of task names when listed
```bash
$ doit list
buildhtml  Build HTML documentation.
buildpdf   Build PDF documentation.
publish    Publish documentation to FTP.
spell	   Spell document source files.
```
Tasks are today listed in alphabetical order.

As seen, it can look quite confusing and user would appreciate natural order:

```bash
$ doit list
spell	   Spell document source files.
buildhtml  Build HTML documentation.
buildpdf   Build PDF documentation.
publish    Publish documentation to FTP.
```

I guess, changing order of listed items is not likely to break anything and I would prefer do it this way by default. Author has good control of order of tasks definitions in dodo file.

If alphabetical order would be (sometime) required, we could add a key "listorder" into `DOIT_CONFIG`.

I had similar issue with Click https://github.com/pallets/click/issues/513 (and I managed to resolve it).


Discussion:
coveralls: 
[![Coverage Status](https://coveralls.io/builds/18799556/badge)](https://coveralls.io/builds/18799556)

Coverage increased (+0.0008%) to 99.744% when pulling **ce1be80ae54f53a780066da0592aa8f587cfdf22 on wmvanvliet:sorting** into **50f0c7eaa8084e3a54c88bfec02e477e419f4c12 on pydoit:master**.

schettino72: Oh. and it seems docs also need to be updated `cmd-other.rst`. Currently it has the wording:

```
Tasks are listed in alphabetical order, not by order of execution.
```

Instead it should mention both way are possible.

Patch:
diff --git a/AUTHORS b/AUTHORS
index 89e6941..045960b 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -24,3 +24,4 @@
  * Simon Mutch - https://github.com/smutch
  * Michael Milton - https://github.com/tmiguelt
  * Mike Pagel - https://github.com/moltob
+ * Marijn van Vliet - https://github.com/wmvanvliet
diff --git a/CHANGES b/CHANGES
index da0f984..42ffcbd 100644
--- a/CHANGES
+++ b/CHANGES
@@ -16,6 +16,7 @@ Changes
  - Fix #113: `tools.config_changed` deals with nested dictionaries.
              Using json instead of repr.
  - Fix #261: help organize command options in sections, and improve formatting.
+ - Fix #267: `doit list` now has a `--sort` parameter to determine the order in which the tasks are listed.
 
 
 0.31.1 (*2018-03-18*)
diff --git a/doc/cmd_other.rst b/doc/cmd_other.rst
index ff8d069..b9a2a3f 100644
--- a/doc/cmd_other.rst
+++ b/doc/cmd_other.rst
@@ -34,7 +34,8 @@ list
 ------
 
 *list* is used to show all tasks available in a *dodo* file.
-Tasks are listed in alphabetical order, not by order of execution.
+Tasks are listed in alphabetical order by default, but *--sort=definition* can
+be speficied to sort them in the order in which they appear in the `dodo` file.
 
 .. code-block:: console
 
diff --git a/doit/cmd_list.py b/doit/cmd_list.py
index 3473231..4652152 100644
--- a/doit/cmd_list.py
+++ b/doit/cmd_list.py
@@ -55,6 +55,18 @@ opt_template = {
     'help': "display entries with template"
     }
 
+opt_sort = {
+    'name': 'sort',
+    'short': '',
+    'long': 'sort',
+    'type': str,
+    'choices': [('name', 'sort by task name'),
+                ('definition', 'list tasks in the order they were defined')],
+    'default': 'name',
+    'help': ("choose the manner in which the task list is sorted. "
+             "[default: %(default)s]")
+    }
+
 
 class List(DoitCmdBase):
     doc_purpose = "list tasks from dodo file"
@@ -62,7 +74,8 @@ class List(DoitCmdBase):
     doc_description = None
 
     cmd_options = (opt_listall, opt_list_quiet, opt_list_status,
-                   opt_list_private, opt_list_dependencies, opt_template)
+                   opt_list_private, opt_list_dependencies, opt_template,
+                   opt_sort)
 
 
     STATUS_MAP = {'ignore': 'I', 'up-to-date': 'U', 'run': 'R', 'error': 'E'}
@@ -113,10 +126,9 @@ class List(DoitCmdBase):
         return print_list
 
 
-    def _execute(self, subtasks=False, quiet=True, status=False,
-                 private=False, list_deps=False, template=None, pos_args=None):
-        """List task generators, in the order they were defined.
-        """
+    def _execute(self, subtasks=False, quiet=True, status=False, private=False,
+                 list_deps=False, template=None, sort='name', pos_args=None):
+        """List task generators"""
         filter_tasks = pos_args
         # dict of all tasks
         tasks = dict([(t.name, t) for t in self.task_list])
@@ -144,7 +156,13 @@ class List(DoitCmdBase):
                 template = '{status} ' + template
         template += '\n'
 
+        # sort list of tasks
+        if sort == 'name':
+            print_list = sorted(print_list)
+        elif sort == 'definition':
+            pass  # task list is already sorted in order of definition
+
         # print list of tasks
-        for task in sorted(print_list):
+        for task in print_list:
             self._print_task(template, task, status, list_deps, tasks)
         return 0


Test patch:
diff --git a/tests/test_cmd_list.py b/tests/test_cmd_list.py
index 6b35eba..9df6942 100644
--- a/tests/test_cmd_list.py
+++ b/tests/test_cmd_list.py
@@ -163,3 +163,23 @@ class TestCmdList(object):
         cmd_list._execute()
         got = [line.strip() for line in output.getvalue().split('\n') if line]
         assert 't做' == got[0]
+
+    def testSortByName(self):
+        # by default, the task list should be ordered by name
+        task_list = list(tasks_sample())
+        output = StringIO()
+        cmd_list = CmdFactory(List, outstream=output, task_list=task_list)
+        cmd_list._execute()
+        got = [line.strip() for line in output.getvalue().split('\n') if line]
+        expected = ['g1', 't1', 't2', 't3']
+        assert expected == got
+
+    def testSortByDefinition(self):
+        # test sorting task list by order of definition
+        task_list = list(tasks_sample())
+        output = StringIO()
+        cmd_list = CmdFactory(List, outstream=output, task_list=task_list)
+        cmd_list._execute(sort='definition')
+        got = [line.strip() for line in output.getvalue().split('\n') if line]
+        expected = ['t1', 't2', 'g1', 't3']
+        assert expected == got