Repository: mkdocs/mkdocs

Issue:
Navigation not in alphanumeric order (when pages config is automatic)
##### mkdocs Version:

0.14.0
##### Summary:

I have a problem with the table of content of my docs. I want that my chapters are in the correct order.
##### Steps To Reproduce:

In the docs folder there is the following structure:
- docs/
  - book/
    - Chapter 1/
      - something.md
    - Chapter 2/
      - something.md

...

Now when I build the docs the chapters won't be in the correct order.

The problem exists regardless of the theme.
##### Expected Results:
- book
  - Chapter 1
  - Chapter 2
  - Chapter 3
  - Chapter 4
  - Chapter 5
  - Chapter 6
  - Chapter 7
  - Chapter 8
  - Chapter 9
  - Chapter 10
  - Chapter 11
  - Chapter 12
##### Actual Results:
- book
  - Chapter 8
  - Chapter 6
  - Chapter 5
  - Chapter 2
  - Chapter 12
  - Chapter 9
  - Chapter 4
  - Chapter 7
  - Chapter 3
  - Chapter 1
  - Chapter 11
  - Chapter 10

Navigation not in alphanumeric order (when pages config is automatic)
##### mkdocs Version:

0.14.0
##### Summary:

I have a problem with the table of content of my docs. I want that my chapters are in the correct order.
##### Steps To Reproduce:

In the docs folder there is the following structure:
- docs/
  - book/
    - Chapter 1/
      - something.md
    - Chapter 2/
      - something.md

...

Now when I build the docs the chapters won't be in the correct order.

The problem exists regardless of the theme.
##### Expected Results:
- book
  - Chapter 1
  - Chapter 2
  - Chapter 3
  - Chapter 4
  - Chapter 5
  - Chapter 6
  - Chapter 7
  - Chapter 8
  - Chapter 9
  - Chapter 10
  - Chapter 11
  - Chapter 12
##### Actual Results:
- book
  - Chapter 8
  - Chapter 6
  - Chapter 5
  - Chapter 2
  - Chapter 12
  - Chapter 9
  - Chapter 4
  - Chapter 7
  - Chapter 3
  - Chapter 1
  - Chapter 11
  - Chapter 10



Discussion:
Is the repo public? Can you show us something so we can reproduce it?

I've done stuff like this before.  You can just break it up by numeric parts and string parts in a list and sort the list of lists instead of the list strings.  That is of course if mkdocs wants to support a numeric sort.

Anyways, some quick and dirty code.

``` python
from itertools import groupby

text = [
   "Chapter 99",
   "Chapter 100",
   "Chapter 3",
   "Chapter 33",
   "Chapter 4"
]

digit_text = []

for t in text:
    final_text = []
    for digit, g in groupby(t, lambda x: x.isdigit()):
        val = "".join(g)
        final_text.append(int(val) if digit else val)
    digit_text.append(final_text)

digit_text.sort()

for t in digit_text:
    print(''.join([str(x) for x in t]))

#### Output ####
# Chapter 3
# Chapter 4
# Chapter 33
# Chapter 99
# Chapter 100
```

On a second reading, I understand. This isn't to do with the table of contents, it is regarding the navigation. Subtle but important different.

We don't sort the files of you don't provide a pages config. They are shown in whatever order your file system gives them to us.

Here is a test-repo to reproduce my issue: 
https://github.com/CptLausebaer/test-mkdocs

image to display the issue:
![mkdocs_screenshot_nav](https://cloud.githubusercontent.com/assets/2578782/8202121/4515ba90-14d7-11e5-86e0-27c03494ebf8.png)

I  wasn't sure if table of contents is the right name of it, but I hope the image will show what I mean. 
I mean the section on the left.

So the only way to achieve a alphanumeric order is to declare all files in the mkdocs.yml at the pages section?

> So the only way to achieve a alphanumeric order is to declare all files in the mkdocs.yml at the pages section?

Yeah, that is the only way to do it at the moment.

I would be happy to sort it, it is easy to do for most cases but I have no idea how sorting will work in some languages like Chinese. However, I guess we could improve the situation for English etc. and it wouldn't be any worse for the rest.

This look interesting - https://github.com/jtauber/pyuca

This is where we need to sort it in MkDocs. https://github.com/mkdocs/mkdocs/blob/0.14.0/mkdocs/config/config_options.py#L393

My initial reaction is that it should be an error to assume that files should be sorted alphanumerically (or in any particular order). In some use cases alphanumeric sorting might be wrong. I think that if a user wants a specific order, then the only way to achieve that should be by explicitly defining that order in the pages section of the config as it works now. 

If any sorting is added to the auto-populated pages, then I would suggest that it be turned off by default.  Of course, what would require an additional setting to turn it on. And sorting should never run on a manually defined pages config (as it is now).

Alternatively, this could be addressed via the future Plugin API (see #206). Via a "post-config" hook (or whatever is would be called--after config is validated), a user could define their own sort function which would sort the pages in the config according to their needs.

That said, looking at the source code, is appears that there is sorting already of the files within a directory, but not of the directories which contain those files (see https://github.com/mkdocs/mkdocs/blob/0.14.0/mkdocs/config/config_options.py#L332). Given @CptLausebaer's file structure, he needs the directories to be sorted as well.

FWIW, I see no problem explicitly defining order. If you don't, where does "Table of Contents" come relative to "Index" relative to "Appendix A"? Is "Chapter 1" before or after "Introduction"?

Given that, it never even occurred to me to accept an as-it-came-from-disc order.

Yeah, I see value in sorting  properly as it will provide a consistent order anywhere. Otherwise I guess it could randomly change over multiple builds and platforms.

I would guess that alphanumeric is the most sensible default choice. Users that need a specific ordering should define it themselves. The order as-it-came-from-disc isn't obvious and as @waylan pointed out, we already sort files but don't sort directories. So, I'd consider this a bug even. We should sort or not sort them both the same way.

So, initially, I think we should do the sort on directories like we do with files. We can then later look at improving this or perhaps making it optional - but I think any other sorting should be delegated to a plugin as that starts to get quite specific.

I can live with that.

Is the repo public? Can you show us something so we can reproduce it?

I've done stuff like this before.  You can just break it up by numeric parts and string parts in a list and sort the list of lists instead of the list strings.  That is of course if mkdocs wants to support a numeric sort.

Anyways, some quick and dirty code.

``` python
from itertools import groupby

text = [
   "Chapter 99",
   "Chapter 100",
   "Chapter 3",
   "Chapter 33",
   "Chapter 4"
]

digit_text = []

for t in text:
    final_text = []
    for digit, g in groupby(t, lambda x: x.isdigit()):
        val = "".join(g)
        final_text.append(int(val) if digit else val)
    digit_text.append(final_text)

digit_text.sort()

for t in digit_text:
    print(''.join([str(x) for x in t]))

#### Output ####
# Chapter 3
# Chapter 4
# Chapter 33
# Chapter 99
# Chapter 100
```

On a second reading, I understand. This isn't to do with the table of contents, it is regarding the navigation. Subtle but important different.

We don't sort the files of you don't provide a pages config. They are shown in whatever order your file system gives them to us.

Here is a test-repo to reproduce my issue: 
https://github.com/CptLausebaer/test-mkdocs

image to display the issue:
![mkdocs_screenshot_nav](https://cloud.githubusercontent.com/assets/2578782/8202121/4515ba90-14d7-11e5-86e0-27c03494ebf8.png)

I  wasn't sure if table of contents is the right name of it, but I hope the image will show what I mean. 
I mean the section on the left.

So the only way to achieve a alphanumeric order is to declare all files in the mkdocs.yml at the pages section?

> So the only way to achieve a alphanumeric order is to declare all files in the mkdocs.yml at the pages section?

Yeah, that is the only way to do it at the moment.

I would be happy to sort it, it is easy to do for most cases but I have no idea how sorting will work in some languages like Chinese. However, I guess we could improve the situation for English etc. and it wouldn't be any worse for the rest.

This look interesting - https://github.com/jtauber/pyuca

This is where we need to sort it in MkDocs. https://github.com/mkdocs/mkdocs/blob/0.14.0/mkdocs/config/config_options.py#L393

My initial reaction is that it should be an error to assume that files should be sorted alphanumerically (or in any particular order). In some use cases alphanumeric sorting might be wrong. I think that if a user wants a specific order, then the only way to achieve that should be by explicitly defining that order in the pages section of the config as it works now. 

If any sorting is added to the auto-populated pages, then I would suggest that it be turned off by default.  Of course, what would require an additional setting to turn it on. And sorting should never run on a manually defined pages config (as it is now).

Alternatively, this could be addressed via the future Plugin API (see #206). Via a "post-config" hook (or whatever is would be called--after config is validated), a user could define their own sort function which would sort the pages in the config according to their needs.

That said, looking at the source code, is appears that there is sorting already of the files within a directory, but not of the directories which contain those files (see https://github.com/mkdocs/mkdocs/blob/0.14.0/mkdocs/config/config_options.py#L332). Given @CptLausebaer's file structure, he needs the directories to be sorted as well.

FWIW, I see no problem explicitly defining order. If you don't, where does "Table of Contents" come relative to "Index" relative to "Appendix A"? Is "Chapter 1" before or after "Introduction"?

Given that, it never even occurred to me to accept an as-it-came-from-disc order.

Yeah, I see value in sorting  properly as it will provide a consistent order anywhere. Otherwise I guess it could randomly change over multiple builds and platforms.

I would guess that alphanumeric is the most sensible default choice. Users that need a specific ordering should define it themselves. The order as-it-came-from-disc isn't obvious and as @waylan pointed out, we already sort files but don't sort directories. So, I'd consider this a bug even. We should sort or not sort them both the same way.

So, initially, I think we should do the sort on directories like we do with files. We can then later look at improving this or perhaps making it optional - but I think any other sorting should be delegated to a plugin as that starts to get quite specific.

I can live with that.


Resolving patch:
diff --git a/mkdocs/config/config_options.py b/mkdocs/config/config_options.py
--- a/mkdocs/config/config_options.py
+++ b/mkdocs/config/config_options.py
@@ -328,7 +328,8 @@ def walk_docs_dir(self, docs_dir):
         if self.file_match is None:
             raise StopIteration
 
-        for (dirpath, _, filenames) in os.walk(docs_dir):
+        for (dirpath, dirs, filenames) in os.walk(docs_dir):
+            dirs.sort()
             for filename in sorted(filenames):
                 fullpath = os.path.join(dirpath, filename)
                 relpath = os.path.normpath(os.path.relpath(fullpath, docs_dir))


Test patch:
diff --git a/mkdocs/tests/config/config_tests.py b/mkdocs/tests/config/config_tests.py
--- a/mkdocs/tests/config/config_tests.py
+++ b/mkdocs/tests/config/config_tests.py
@@ -146,11 +146,16 @@ def test_default_pages_nested(self):
         tmp_dir = tempfile.mkdtemp()
         try:
             open(os.path.join(tmp_dir, 'index.md'), 'w').close()
+            open(os.path.join(tmp_dir, 'getting-started.md'), 'w').close()
             open(os.path.join(tmp_dir, 'about.md'), 'w').close()
-            os.makedirs(os.path.join(tmp_dir, 'sub'))
-            open(os.path.join(tmp_dir, 'sub', 'sub.md'), 'w').close()
-            os.makedirs(os.path.join(tmp_dir, 'sub', 'sub2'))
-            open(os.path.join(tmp_dir, 'sub', 'sub2', 'sub2.md'), 'w').close()
+            os.makedirs(os.path.join(tmp_dir, 'subA'))
+            open(os.path.join(tmp_dir, 'subA', 'index.md'), 'w').close()
+            os.makedirs(os.path.join(tmp_dir, 'subA', 'subA1'))
+            open(os.path.join(tmp_dir, 'subA', 'subA1', 'index.md'), 'w').close()
+            os.makedirs(os.path.join(tmp_dir, 'subC'))
+            open(os.path.join(tmp_dir, 'subC', 'index.md'), 'w').close()
+            os.makedirs(os.path.join(tmp_dir, 'subB'))
+            open(os.path.join(tmp_dir, 'subB', 'index.md'), 'w').close()
             conf = config.Config(schema=config.DEFAULT_SCHEMA)
             conf.load_dict({
                 'site_name': 'Example',
@@ -160,11 +165,18 @@ def test_default_pages_nested(self):
             self.assertEqual([
                 'index.md',
                 'about.md',
-                {'Sub': [
-                    os.path.join('sub', 'sub.md'),
-                    {'Sub2': [
-                        os.path.join('sub', 'sub2', 'sub2.md'),
+                'getting-started.md',
+                {'subA': [
+                    os.path.join('subA', 'index.md'),
+                    {'subA1': [
+                        os.path.join('subA', 'subA1', 'index.md')
                     ]}
+                ]},
+                {'subB': [
+                    os.path.join('subB', 'index.md')
+                ]},
+                {'subC': [
+                    os.path.join('subC', 'index.md')
                 ]}
             ], conf['pages'])
         finally: