Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

meta/refactor document lessons library #9

Merged
merged 4 commits into from
Feb 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
python = pkgs.python311;
};

lessonsDocumentation = lessonsLib.generateLessonsDocumentation {lessonsPath = ./lessons;};
lessonsDocumentation =
lessonsLib.generateLessonsDocumentation
{
lessonsPath = ./lessons;
lessonFile = "lesson.md";
};

site = pkgs.stdenvNoCC.mkDerivation {
name = "modules-lessons-site";
Expand Down
238 changes: 190 additions & 48 deletions lib/lessons.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@
lib,
...
}: rec {
/*
Creates an attrset where the key is the lesson directory name and the value is the path to the directory.

# Example

```nix
getLessons
{lessonsPath = ../lessons;}
=> {
"001-a-module" = <nix-store>/lessons/001-a-module;
"010-basic-types" = <nix-store>/lessons/010-basic-types;
}
```

# Type

```
getLessons :: Attrset -> Attrset
```

# Arguments

- [lessonsPath] The path to the lessons directory.
*/
getLessons = {lessonsPath ? ../lessons, ...}: (
lib.mapAttrs
(name: _: lib.path.append lessonsPath name)
Expand All @@ -14,6 +38,37 @@
)
);

/*
Return the extension of a file.

# Example

```nix
getFileExtension ./directory/eval.nix
=> "nix"
getFileExtension ./directory/run
=> ""
getFileExtension ./directory/archive.tar.xz
=> "tar.xz"
getFileExtension "./directory/eval.nix"
=> "nix"
getFileExtension "./directory/run"
=> ""
getFileExtension "./directory/archive.tar.xz"
=> "tar.xz"
```

# Type

```
getFileExtension :: Path -> String
getFileExtension :: String -> String
```

# Arguments

- [path] A path or string that contains a path to a file.
*/
getFileExtension = path: (
lib.concatStringsSep
"."
Expand All @@ -29,42 +84,132 @@
)
);

createLessonMetadata = name: value: let
/*
Like `match` but works on multiline strings.

Returns a list if the extended POSIX regular expression regex matches str precisely, otherwise returns null.
Each item in the list is a regex group.

# Example

```nix
multilineMatch
''(\[//]: # \(.*\..*\))''
''
In the `options.nix` file, we have declared boolean, enumeration, integer, and string options.

[//]: # (./options.nix)

In the `config.nix` file, we have declared values for all these options.

[//]: # (./config.nix)

In the `eval.nix` file, we evaluate our options and config and have it return the config values.
''
=> [ "[//]: # (./options.nix)" "[//]: # (./config.nix)" ]
```

# Type

```
multilineMatch :: String -> String -> [String]
```

# Arguments

- [regex] The regular expression.
- [input] The string to search.
*/
multilineMatch = regex: input: (
lib.flatten
(
builtins.filter
(elem: ! builtins.isNull elem)
(
builtins.map
(
lib.strings.match
regex
)
(
lib.splitString
"\n"
input
)
)
)
);

/*
Create a fenced code block with language identifier and file name given a file.

# Example

````nix
makeFencedCodeBlock ./eval.nix
=> ''
``` nix title="eval.nix"
let
a = 1;
in
a
```
''
````

# Type

```
makeFencedCodeBlock :: Path -> String
```

# Arguments

- [path] The file.
*/
makeFencedCodeBlock = file: ''
``` ${getFileExtension file} title="${builtins.baseNameOf file}"
${builtins.readFile file}
```
'';

/*
Given a lesson, create the metadata necessary to create the markdown documentation.
*/
createLessonMetadata = {lessonFile ? "lesson.md", ...}: name: value: let
lessonDir = name;
lessonPath = value;
rawLesson = builtins.readFile (lib.path.append lessonPath "lesson.md");
in rec {
outputParentDir = "lessons/" + name;
outputFilePath = outputParentDir + "/lesson.md";
linesToReplace = findStrings ''(\[//]: # \(.*\..*\))'' rawLesson;
rawLesson = builtins.readFile (lib.path.append lessonPath lessonFile);

commentLineMatch = ''(\[//]: # \(.*\..*\))'';
commentFileMatch = ''\[//]: # \(\./(.*)\)'';
linesToReplace = multilineMatch commentLineMatch rawLesson;
filesToSubstitute = (
lib.flatten
builtins.map
(
builtins.map
lib.path.append
lessonPath
)
(
lib.flatten
(
findStrings
''\[//]: # \(\./(.*)\)''
builtins.map
(
multilineMatch
commentFileMatch
)
linesToReplace
)
linesToReplace
)
);
textToSubstitute = (
builtins.map
(
file: let
fileExtension = getFileExtension file;
in ''
``` ${fileExtension} title="${file}"
${
builtins.readFile
(
lib.path.append lessonPath file
)
}
```
''
)
makeFencedCodeBlock
filesToSubstitute
);
in rec {
outputParentDir = "lessons/" + lessonDir;
outputFilePath = outputParentDir + "/" + lessonFile;
subsLesson = (
builtins.replaceStrings
[''[//]: # (evaluatedLesson)'']
Expand Down Expand Up @@ -94,32 +239,18 @@
.config;
};

lessonsToMetadata = {lessonsPath ? ../lessons, ...}: (
/*
Maps over all the lessons and generates metadata.
*/
lessonsToMetadata = args: (
lib.mapAttrs
createLessonMetadata
(getLessons {inherit lessonsPath;})
);

findStrings = regex: input: (
lib.flatten
(
builtins.filter
(elem: ! builtins.isNull elem)
(
builtins.map
(
lib.strings.match
regex
)
(
lib.splitString
"\n"
input
)
)
)
(createLessonMetadata args)
(getLessons args)
);

/*
Given a list of lesson metadata attrsets, copy the contents to the nix store.
*/
copyLessonsToNixStore = lessons:
pkgs.runCommand
"copy-module-lessons"
Expand All @@ -144,6 +275,12 @@
)
}
'';

/*
Primary function for building lesson documentation.

Is used when building the site.
*/
generateLessonsDocumentation = args: (
copyLessonsToNixStore
(
Expand All @@ -152,6 +289,11 @@
)
);

/*
Builds the lessons documentation and copies it to the needed location in the mkdocs directory.

Primary use is for developing with `mkdocs serve`.
*/
copyLessonsToSite =
pkgs.writeShellScriptBin
"copy-lessons-to-site"
Expand Down
Loading