Skip to content

Instantly share code, notes, and snippets.

@pnettto
Last active March 18, 2021 13:57
Show Gist options
  • Save pnettto/ca0adc0e8d5550cb34ce5144ec2b305f to your computer and use it in GitHub Desktop.
Save pnettto/ca0adc0e8d5550cb34ce5144ec2b305f to your computer and use it in GitHub Desktop.
"""
This script generates a sidebar structure for Docsify (https://docsify.js.org/)
projects. It's intended as a way to make the sidebar a little more
straight-forward but the result will probably need some re-arranging.
Usage:
- Download this file to your project's directory
- "cd" into that directory
- Run "python3 generate_sidebar.py"
The script will:
- Generate a sidebar with links to all files, recursively
- Generate an index file (prefix _i_) for each sub-folder, also accessible via
sidebar
Tip: On VSCode you can add the file .vscode/settings.json on the project's root
folder to hide the generated index files like so:
{
"files.exclude": {
"**/_i_*": true
}
}
Remember to reload the window or restart the editor afterwards.
Credits:
Initially based on indaviande's script (https://github.com/docsifyjs/docsify/issues/610)
"""
import os
import glob
import sys
def scan_dir(dir_path='.', level=0):
"""
Look inside each directory in the project to see if there's anything good to
add to the sidebar
"""
def make_display_name_from_path(path):
parts = path.split('/')
name = parts[-1]
name = name.split(".md")[0] # Remove .md extension
name = name.replace('-', ' ') # Add space instead of -
name = name.replace('_i_', '') # Always remove _i_ index flag
# Capitalize all words
# (Exclude some words from capitalization)
forbidden = ['a', 'on', 'to', 'and', 'with', 'how', 'at', 'the']
capitalized = ''
for word in name.split(' '):
if (word.lower() not in forbidden):
capitalized += word.capitalize()
else:
capitalized += word.lower()
capitalized += ' '
name = capitalized.strip()
return name
def create_dir_index_file(dir_path):
# Create index file name
dir_name = dir_path.split('/')[-1]
dir_index_file_path = dir_path.replace(
dir_name, '_i_' + dir_name) + '.md'
if (os.path.isfile(dir_index_file_path)):
# Clear existing file
open(dir_index_file_path, 'w').close()
# Compose the index file
index_file = open(dir_index_file_path, 'w')
# Write a link to parent index (skip root level)
if (level > 1):
parent_dir_path = os.path.split(dir_path)[0]
parent_dir_path_dirname = os.path.dirname(parent_dir_path)
parent_dir_path_basename = os.path.basename(parent_dir_path)
parent_dir_display_name = make_display_name_from_path(
parent_dir_path)
parent_index = f'{parent_dir_path_dirname}/_i_{parent_dir_path_basename}'
index_file.write(
f"**Go back:** [{parent_dir_display_name}]({parent_index})\n")
# Write a title
dir_display_name = make_display_name_from_path(dir_path)
index_file.write(f"# {dir_display_name}\n")
# Write a link for each entry in this directory
entries = [entry for entry in os.listdir(dir_path)]
entries = sorted(entries)
for entry_file_name in entries:
# Ignore entries starting with _ (so also _i_ for indexes) or .
if (any(i in entry_file_name[0] for i in ['_', '.'])):
continue
entry_path = dir_path + '/' + entry_file_name
entry_display_name = make_display_name_from_path(entry_path)
if os.path.isdir(entry_path):
entry_path = dir_path + '/_i_' + entry_file_name
index_file.write(f"- [{entry_display_name}]({entry_path})\n")
index_file.close()
def write_entry_in_sidebar(entry_path, index=False):
"""
Write the sidebar entry, on the right level
"""
# Max levels control
if level >= max_levels:
return
# Add prefix for index files
if index:
entry_file_name = entry_path.split('/')[-1]
entry_path = entry_path.replace(
entry_file_name, '_i_' + entry_file_name) + '.md'
# Open sidebar file for writing
sidebar_file = open('_sidebar.md', 'a')
# Write entry in the sidebar file
entry_display_name = make_display_name_from_path(entry_path)
sidebar_file.write(
f"{' ' * level}* [{entry_display_name}]({entry_path})\n")
# Save file
sidebar_file.close()
def remove_index_files(directory):
# Remove indexes in this directory
index_files = glob.glob(os.path.join(directory, '_i_*.md'))
for file_name in index_files:
try:
os.remove(file_name)
except:
print('Error while deleting file : ', file_name)
print(sys.exc_info()[0])
def execute():
# Remove all old index files
remove_index_files(dir_path)
if level == 0:
# Erase sidebar's content
open('_sidebar.md', 'w').close()
# Write the default header, if exists
if default_header:
sidebar_file = open('_sidebar.md', 'a')
sidebar_file.write(default_header)
sidebar_file.close()
entries = [entry for entry in os.listdir(dir_path)]
entries = sorted(entries)
sublevel = level + 1
if level > 0:
# Create folder index (skip root directory)
create_dir_index_file(dir_path)
for entry_file_name in entries:
# Ignore entries starting with _ (so also _i_ for indexes) or .
if (any(i in entry_file_name[0] for i in ['_', '.'])):
continue
# Compose full path for this entry
entry_path = dir_path + '/' + entry_file_name
if os.path.isfile(entry_path):
# Ignore files that are not markdown files
if '.md' not in entry_file_name:
continue
# Found suitable sidebar item, write it down
write_entry_in_sidebar(entry_path)
if os.path.isdir(entry_path):
# Create a higher lever entry for this directory
write_entry_in_sidebar(entry_path, index=True)
# Scan this directory to add the entries it contains
scan_dir(entry_path, sublevel)
execute()
# Define a section that is always going to be at the top of the sidebar. The
# format is regular Markdown. Example:
# default_header = '''
# * [Home](./home.md)
# * [Summary](./summary.md)
# '''
default_header = '''
'''
# Max levels
max_levels = 2
# Start process
scan_dir()
print('✅ All done!')
@indaviande
Copy link

indaviande commented Apr 10, 2020

That's great!
I don't know anything in Python, could you add:

  1. the possibility to scan only .md files/or maybe possibility to exclude some selected Folders & files ?
  2. And also have a section in the sidebar.md which won't be deleted after re-generation (so that I can keep - [Home] at the start of my sidebar

@pnettto
Copy link
Author

pnettto commented Apr 11, 2020

Hey @indaviande, glad to know this was useful for someone! :)

The script is actually already only accounting for .md files. Did you perhaps notice a glitch?

I added a way to build a section that is always going to show at the top of the sidebar file. Just add the entries you want there (in Markdown) and it should it work. Let me know! :)

@indaviande
Copy link

Thanks for this additional feature!
Any chance the first level files could be indented with a Level 1 instead of no indentation ?
Like an option to select the starting indentation.

Lastly, do you know how we can run this script through Netlify every time the docsify is generated?

@pnettto
Copy link
Author

pnettto commented Apr 15, 2020

Hey! Do you mean that the initial indentation of the level 0 would be actually level 1? What about the default entries (in my example Home and Summary)?

About the Netlify question, it must be possible but I don't have enough knowledge about that to share with you. Nevertheless when you manage to make that work could you share the solution here?

@Soneji
Copy link

Soneji commented Mar 13, 2021

Hey @brisa-pedronetto
Thank you for this script, it's amazing!

I was wondering if I could have some help because I am running into an issue where the script is using ./folder/file.md when I want it to use /folder/file.md. Is there any way to do this?

This is an issue because once I navigate to ./folder/file.md and then click on another file in the same subdirectory it takes me to ./folder/file2.md which leads me to website.com/folder/folder/file2.md which leads to a 404

Any help would be appreciated. Thank you!!

@pnettto
Copy link
Author

pnettto commented Mar 18, 2021

Hello @Soneji, glad you found it useful :) I'm a little busy at the moment so I won't be able to dive into it (i.e. create a project to test and fiddle with it, etc), but I'd recommend taking a look at the function "write_entry_in_sidebar", since I think that's where the issue is. Good luck! :)

@Soneji
Copy link

Soneji commented Mar 18, 2021

Thank you! I ended up writing my own script based on the original (just like yours), which finds all markdown files recursively and puts them in the sidebar (just with one level of depth). If anybody wants it:

#!/usr/bin/env python3

import glob
import os

PATH = "."

files = []

for x in os.walk(PATH):
    for y in glob.glob(os.path.join(x[0], '*.md')):
        files.append(y)
        files[-1] = files[-1].replace(".", "", 1)

sidebar_file = open('_sidebar.md', 'w')
name = "Home"
file = "/"
sidebar_file.write(f"* [{name}]({file})\n")


try:
    files.remove("/README.md")
    files.remove("/_sidebar.md")
    # put any other files you want to remove here
except:
    pass

files.sort()

for file in files:
    if ".md" in file:

        name = file[1:-3]
        name = name.replace("/", "'s ")
        name = name.replace("_", " ")
        name = name.replace("-", " ")

        file = file.replace(" ", "%20")

        sidebar_file.write(f"* [{name}]({file})\n")

sidebar_file.close()

print("============================================================")
print("Sidebar:")
print("============================================================")
try:
    os.system("cat _sidebar.md")
except:
    print("Unable to `cat _sidebar.md`")
print("============================================================")

@pnettto
Copy link
Author

pnettto commented Mar 18, 2021

@Soneji That's so cool! Thanks for sharing :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment