generate a hierarchical markdown list whose elements are links to files in my git repository?

Viewed 623

I am trying to create a git hook which generates a readme.md file that contains an "index" in which only the .c files, as part of my class assignments, are linked. The directory tree looks like this

$ tree
├── assignment1
│   ├── linear_lists.c
|   |── circular_lists.c
|   |── main.c
├── assignment2
│   ├── stacks.c
│   ├── main.c
| ...
└── readme.md

So far I have only figured out how to generate the code for a single leveled list

* [assignment1/linear_lists.c](https://raw.githubusercontent.com/me/myrepo/main/assignment1/linear_lists.c)
* [assignment1/circular_lists.c](/https://raw.githubusercontent.com/me/myrepo/main/assignment1/circular_lists.c)
...
* [assignment2/stacks.c](https://raw.githubusercontent.com/me/myrepo/assignment2/stacks.c)
...

using :

find . -name "*.c" | sed "s|^\./\(.*\)|* \[\1\]($url/$repo/$branch/\1)|" >> readme.md

I want to represent the above as a two-level markdown list by basically generating the following code (using only Unix utilties if possible)

* assignment1 <!-- name of parent -->
    + [linear_lists.c](same link as above)
    + [circular_lists.c](same link as above)
...
* assignment2
    + [stacks.c](same link as above)
...

Is this possible ? Any help is appreciated.

2 Answers

Here's an awk script to convert find output to a Markdown list of lists with links.

Use as:

find DIR -type f \( -name '*.[ch]' -o -name '*.cpp' \) |
sort -t '/' -k 1 -k 2 |
awk -v base=BASE -f path/to/script

where

  • DIR is . or dirname, for a compact result avoid '/'s
  • the sort step is optional
  • BASE is a path prefix, e.g. 'https://example.org/pub/'
BEGIN {
    FS = "/"
    levelindent = 4
    split("*+-*+-*+-*+-*+-*+-",liststyle,//)
}
{
    sub(/^\.\//,"",$0)
    depth = NF-1
    filename = $(NF)
    path = $0; sub(/[^/]*$/,"",path)

    ## print each path part on first dir entry
    accpath = ""
    for ( d = 1; d <= depth; d++ ) {
        accpath = accpath $(d) "/"
        if ( ! seen[accpath]++ )
            printf("%*s %s %s\n", levelindent*(d-1), "", \
                liststyle[d], $(d) "/")
    }

    printf("%*s %s [%s](%s)\n", levelindent*(depth), "", \
        liststyle[depth+1], filename, base path filename)
}

Each pathname ($0) is stripped of any leading ./ and split by / as field separator (FS) using the number of fields (NF) to determine directory depth (NF-1), and filename ($(NF)). A path is made by stripping off the filename.

Each dir in path ($1 through $(NF-1)) - on first encounter only - and filename is output as a Markdown list item, each leaf node as a Markdown link concatenating base (from command line), path, and filename.

Indentation is handled using the printf width flag (the * in %*s) to output a space-filled field of specified width, varying with the directory depth.

EDIT: support for file-less dirs, added explanation

Output:

 * week/
     + assignment1/
         - [linear_lists.c](/pub/week/assignment1/linear_lists.c)
         - [main.c](/pub/week/assignment1/main.c)
         - supp/
             * [supp.c](/pub/week/assignment1/supp/supp.c)
             * extra/
                 + [extra.h](/pub/week/assignment1/supp/extra/extra.h)
                 + [extra.c](/pub/week/assignment1/supp/extra/extra.c)
         - [circular_lists.c](/pub/week/assignment1/circular_lists.c)
     + assignment2/
         - [stacks.c](/pub/week/assignment2/stacks.c)
         - [stacks.h](/pub/week/assignment2/stacks.h)
         - [main.c](/pub/week/assignment2/main.c)
         - extra/
             * [extra.c](/pub/week/assignment2/extra/extra.c)

Would you please try the following:

#!/bin/bash

prefix="https://raw.githubusercontent.com/me/myrepo/main"
for d in */; do                 # find directories in the current directory
    d=${d%/}                    # remove a trailing slash
    printf "* %s\n" "$d"        # print the name of parent
    for f in "$d"/*; do         # find files in each directory
        f=${f#*/}               # remove dirname to extract the filename
        printf "    + [%s](%s)\n" "$f" "$prefix/$d/$f"
                                # print tha name of child and the link
    done
done > readme.md

Generated readme.md:

* assignment1
    + [circular_lists.c](https://raw.githubusercontent.com/me/myrepo/main/assignment1/circular_lists.c)
    + [linear_lists.c](https://raw.githubusercontent.com/me/myrepo/main/assignment1/linear_lists.c)
    + [main.c](https://raw.githubusercontent.com/me/myrepo/main/assignment1/main.c)
* assignment2
    + [main.c](https://raw.githubusercontent.com/me/myrepo/main/assignment2/main.c)
    + [stacks.c](https://raw.githubusercontent.com/me/myrepo/main/assignment2/stacks.c)

Please note the script above is specialized for the two-level directory tree. If we need more levels, we need to modify the script.

Related