Skip to content

Commit 8f59020

Browse files
mishig25sgugger
andauthored
Course changes (#148)
* Add Question svelte component * Add `--not_python_module` flag * Escape svelte lcurlybrackets * Chore * Update escapre regex * Flag to disable frontmatter * Svelte import question component * Add more svelte escape compnents * Fix regex bug * make style * Add FrameworkSwtichCourse svelte * Add `fw` prop for svelte course * more css * More css * DocNotebookDropdonwn support single option * Fix tests * Tip is correctly escaped * Update src/doc_builder/build_doc.py Co-authored-by: Sylvain Gugger <[email protected]> * front format * Send chpater quiz event * toctree fw local Co-authored-by: Sylvain Gugger <[email protected]>
1 parent 6f68918 commit 8f59020

File tree

11 files changed

+248
-40
lines changed

11 files changed

+248
-40
lines changed

kit/src/lib/DocNotebookDropdown.svelte

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -44,44 +44,64 @@
4444
<svelte:window on:resize={onResize} />
4545

4646
<div class="flex space-x-1 {classNames}" bind:this={dropdownEl}>
47-
<Dropdown btnLabel="" classNames="colab-dropdown" noBtnClass useDeprecatedJS={false}>
48-
<slot slot="button">
47+
{#if googleColabOptions.length === 1}
48+
<a href={googleColabOptions[0].value} target="_blank">
4949
<img
5050
alt="Open In Colab"
5151
class="!m-0"
5252
src="https://colab.research.google.com/assets/colab-badge.svg"
5353
/>
54-
</slot>
55-
<slot slot="menu">
56-
{#each googleColabOptions as { label, value }}
57-
<DropdownEntry
58-
classNames="text-sm !no-underline"
59-
iconClassNames="text-gray-500"
60-
{label}
61-
onClick={() => onClick(value)}
62-
useDeprecatedJS={false}
54+
</a>
55+
{:else}
56+
<Dropdown btnLabel="" classNames="colab-dropdown" noBtnClass useDeprecatedJS={false}>
57+
<slot slot="button">
58+
<img
59+
alt="Open In Colab"
60+
class="!m-0"
61+
src="https://colab.research.google.com/assets/colab-badge.svg"
6362
/>
64-
{/each}
65-
</slot>
66-
</Dropdown>
67-
<Dropdown btnLabel="" classNames="colab-dropdown" noBtnClass useDeprecatedJS={false}>
68-
<slot slot="button">
63+
</slot>
64+
<slot slot="menu">
65+
{#each googleColabOptions as { label, value }}
66+
<DropdownEntry
67+
classNames="text-sm !no-underline"
68+
iconClassNames="text-gray-500"
69+
{label}
70+
onClick={() => onClick(value)}
71+
useDeprecatedJS={false}
72+
/>
73+
{/each}
74+
</slot>
75+
</Dropdown>
76+
{/if}
77+
{#if awsStudioOptions.length === 1}
78+
<a href={awsStudioOptions[0].value} target="_blank">
6979
<img
7080
alt="Open In Studio Lab"
7181
class="!m-0"
7282
src="https://studiolab.sagemaker.aws/studiolab.svg"
7383
/>
74-
</slot>
75-
<slot slot="menu">
76-
{#each awsStudioOptions as { label, value }}
77-
<DropdownEntry
78-
classNames="text-sm !no-underline"
79-
iconClassNames="text-gray-500"
80-
{label}
81-
onClick={() => onClick(value)}
82-
useDeprecatedJS={false}
84+
</a>
85+
{:else}
86+
<Dropdown btnLabel="" classNames="colab-dropdown" noBtnClass useDeprecatedJS={false}>
87+
<slot slot="button">
88+
<img
89+
alt="Open In Studio Lab"
90+
class="!m-0"
91+
src="https://studiolab.sagemaker.aws/studiolab.svg"
8392
/>
84-
{/each}
85-
</slot>
86-
</Dropdown>
93+
</slot>
94+
<slot slot="menu">
95+
{#each awsStudioOptions as { label, value }}
96+
<DropdownEntry
97+
classNames="text-sm !no-underline"
98+
iconClassNames="text-gray-500"
99+
{label}
100+
onClick={() => onClick(value)}
101+
useDeprecatedJS={false}
102+
/>
103+
{/each}
104+
</slot>
105+
</Dropdown>
106+
{/if}
87107
</div>

kit/src/lib/FrameworkContentBlock.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666
<svelte:window on:hashchange={onHashChange} />
6767

6868
<div class="border border-gray-200 rounded-xl px-4 relative" bind:this={containerEl}>
69-
<div class="flex h-[22px] mt-[-12.5px] justify-between leading-none" >
69+
<div class="flex h-[22px] mt-[-12.5px] justify-between leading-none">
7070
<div class="flex px-1 items-center space-x-1 bg-white dark:bg-gray-950">
7171
<svelte:component this={Icon} />
7272
<span>{label}</span>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<script lang="ts">
2+
import type { CourseFramework } from "./types";
3+
4+
import IconPytorch from "./IconPytorch.svelte";
5+
import IconTensorflow from "./IconTensorflow.svelte";
6+
7+
export let fw: CourseFramework;
8+
9+
const FRAMEWORKS = [
10+
{
11+
id: "pt",
12+
classNames: "bg-red-50 dark:bg-transparent text-red-600",
13+
icon: IconPytorch,
14+
name: "Pytorch"
15+
},
16+
{
17+
id: "tf",
18+
classNames: "bg-orange-50 dark:bg-transparent text-yellow-600",
19+
icon: IconTensorflow,
20+
name: "TensorFlow"
21+
}
22+
] as const;
23+
</script>
24+
25+
<div class="bg-white leading-none border border-gray-100 rounded-lg flex p-0.5 w-56 text-sm mb-4">
26+
{#each FRAMEWORKS as f, i}
27+
<a
28+
class="flex justify-center flex-1 py-1.5 px-2.5 focus:outline-none !no-underline
29+
rounded-{i ? 'r' : 'l'}
30+
{f.id === fw ? f.classNames : 'text-gray-500 filter grayscale'}"
31+
href="?fw={f.id}"
32+
>
33+
<svelte:component this={f.icon} classNames="mr-1.5" />
34+
{f.name}
35+
</a>
36+
{/each}
37+
</div>

kit/src/lib/Question.svelte

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<script lang="ts">
2+
import { onMount } from "svelte";
3+
4+
import { answers } from "./stores";
5+
6+
export let choices: Choice[];
7+
8+
interface Choice {
9+
text: string;
10+
explain: string;
11+
correct?: boolean;
12+
selected?: boolean;
13+
}
14+
15+
const id = randomId();
16+
17+
let isFalse: boolean = false;
18+
let isIncomplete: boolean = false;
19+
let selected: number[] = [];
20+
let submitted: number[] = [];
21+
22+
onMount(() => {
23+
$answers = { ...$answers, [id]: { correct: false } };
24+
});
25+
26+
function checkAnswer() {
27+
isFalse = false;
28+
isIncomplete = false;
29+
30+
for (let i = 0; i < choices.length; i++) {
31+
const c = choices[i];
32+
if (c.correct && !selected.includes(i)) {
33+
isIncomplete = true;
34+
} else if (!c.correct && selected.includes(i)) {
35+
isFalse = true;
36+
}
37+
}
38+
submitted = selected;
39+
$answers = { ...$answers, [id]: { correct: !isIncomplete && !isFalse } };
40+
const isChapterComplete = Object.values($answers).every(({ correct }) => correct);
41+
if (isChapterComplete) {
42+
const event = new Event("ChapterComplete");
43+
window.dispatchEvent(event);
44+
}
45+
}
46+
47+
function randomId(prefix = "_"): string {
48+
// Return a unique-ish random id string
49+
// Math.random should be unique because of its seeding algorithm.
50+
// Convert it to base 36 (numbers + letters), and grab the first 9 characters
51+
// after the decimal.
52+
return `${prefix}${Math.random().toString(36).substr(2, 9)}`;
53+
}
54+
</script>
55+
56+
<div>
57+
<form on:submit|preventDefault={() => checkAnswer()}>
58+
{#each choices as choice, index}
59+
<label class="block">
60+
<input
61+
autocomplete="off"
62+
bind:group={selected}
63+
class="form-input -mt-1.5 mr-2"
64+
name="choice"
65+
type="checkbox"
66+
value={index}
67+
/>
68+
{@html choice.text}
69+
</label>
70+
{#if submitted && submitted.includes(index)}
71+
<div
72+
class="alert alert-{!!choice.correct ? 'success' : 'error'} mt-1 mb-2.5 leading-normal"
73+
>
74+
<span class="font-bold">
75+
{!!choice.correct ? "Correct!" : "Incorrect."}
76+
</span>
77+
{@html choice.explain}
78+
</div>
79+
{/if}
80+
{/each}
81+
<div class="flex flex-row items-center mt-3">
82+
<button class="btn px-4 mr-4" type="submit" disabled={!selected.length}> Submit </button>
83+
84+
{#if submitted.length}
85+
<div class="font-semibold leading-snug">
86+
{#if isFalse}
87+
<span class="text-red-900 dark:text-red-200">
88+
Looks like at least one of your answers is wrong, try again!
89+
</span>
90+
{:else if isIncomplete}
91+
<span class="text-red-900 dark:text-red-200">
92+
You didn't select all the correct answers, there's more!
93+
</span>
94+
{:else}
95+
<span class="text-green-900 dark:text-green-200"> You got all the answers! </span>
96+
{/if}
97+
</div>
98+
{/if}
99+
</div>
100+
</form>
101+
</div>

kit/src/lib/stores.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ export function getFrameworkStore(key: Framework): Writable<FrameworkState> {
2424
}
2525
return frameworks[key];
2626
}
27+
28+
// used for Question.svelte
29+
export const answers = writable<{ [key: string]: { correct: boolean } }>({});

kit/src/lib/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
export type Group = "group1" | "group2";
22

33
export type Framework = "pytorch" | "tensorflow" | "jax";
4+
5+
export type CourseFramework = "pt" | "tf";

src/doc_builder/build_doc.py

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ def generate_frontmatter_in_text(text, file_name=None):
244244
Args:
245245
text (`str`): The text in which to convert the links.
246246
"""
247+
is_disabled = "<!-- DISABLE-FRONTMATTER-SECTIONS -->" in text
247248
text = text.split("\n")
248249
root = None
249250
is_inside_codeblock = False
@@ -270,6 +271,10 @@ def generate_frontmatter_in_text(text, file_name=None):
270271
node = FrontmatterNode(title, local)
271272
if header_level == 1:
272273
root = node
274+
# doc writers may choose to disable frontmatter generation
275+
# currenly used in Quiz sections of hf course
276+
if is_disabled:
277+
break
273278
else:
274279
if root is None:
275280
raise ValueError(
@@ -337,7 +342,16 @@ def build_notebooks(doc_folder, notebook_dir, package=None, mapping=None, page_i
337342
raise type(e)(f"There was an error when converting {file} to a notebook.\n" + e.args[0]) from e
338343

339344

340-
def build_doc(package_name, doc_folder, output_dir, clean=True, version="master", language="en", notebook_dir=None):
345+
def build_doc(
346+
package_name,
347+
doc_folder,
348+
output_dir,
349+
clean=True,
350+
version="master",
351+
language="en",
352+
notebook_dir=None,
353+
is_python_module=False,
354+
):
341355
"""
342356
Build the documentation of a package.
343357
@@ -352,19 +366,22 @@ def build_doc(package_name, doc_folder, output_dir, clean=True, version="master"
352366
language (`str`, *optional*, defaults to `"en"`): The language of the doc.
353367
notebook_dir (`str` or `os.PathLike`, *optional*):
354368
If provided, where to save the notebooks generated from the doc file with an [[open-in-colab]] marker.
369+
is_python_module (`bool`, *optional*, defaults to `False`):
370+
Whether the docs being built are for python module. (For example, HF Course is not a python module).
355371
"""
356372
page_info = {"version": version, "language": language, "package_name": package_name}
357373
if clean and Path(output_dir).exists():
358374
shutil.rmtree(output_dir)
359375

360376
read_doc_config(doc_folder)
361377

362-
package = importlib.import_module(package_name)
378+
package = importlib.import_module(package_name) if is_python_module else None
363379
anchors_mapping = build_mdx_files(package, doc_folder, output_dir, page_info)
364380
sphinx_refs = check_toc_integrity(doc_folder, output_dir)
365381
sphinx_refs.extend(convert_anchors_mapping_to_sphinx_format(anchors_mapping, package))
366-
build_sphinx_objects_ref(sphinx_refs, output_dir, page_info)
367-
resolve_links(output_dir, package, anchors_mapping, page_info)
382+
if is_python_module:
383+
build_sphinx_objects_ref(sphinx_refs, output_dir, page_info)
384+
resolve_links(output_dir, package, anchors_mapping, page_info)
368385
generate_frontmatter(output_dir)
369386

370387
if notebook_dir is not None:
@@ -395,6 +412,9 @@ def check_toc_integrity(doc_folder, output_dir):
395412
while len(toc) > 0:
396413
part = toc.pop(0)
397414
toc_sections.extend([sec["local"] for sec in part["sections"] if "local" in sec])
415+
for sec in part["sections"]:
416+
if "local_fw" in sec:
417+
toc_sections.extend(sec["local_fw"].values())
398418
# There should be one sphinx ref per page
399419
for sec in part["sections"]:
400420
if "local" in sec:

src/doc_builder/commands/build.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ def build_command(args):
6262
"the doc-builder package installed, so you need to run the command from inside the doc-builder repo."
6363
)
6464

65-
if args.version is None:
65+
if args.not_python_module:
66+
version = get_default_branch_name(args.path_to_docs)
67+
elif args.version is None:
6668
module = importlib.import_module(args.library_name)
6769
version = module.__version__
6870

@@ -86,6 +88,7 @@ def build_command(args):
8688
version=version,
8789
language=args.language,
8890
notebook_dir=args.notebook_dir,
91+
is_python_module=not args.not_python_module,
8992
)
9093

9194
# dev build should not update _versions.yml
@@ -112,7 +115,8 @@ def build_command(args):
112115
shutil.copy(f, dest)
113116

114117
# Move the objects.inv file at the root
115-
shutil.move(tmp_dir / "kit" / "src" / "routes" / "objects.inv", tmp_dir / "objects.inv")
118+
if not args.not_python_module:
119+
shutil.move(tmp_dir / "kit" / "src" / "routes" / "objects.inv", tmp_dir / "objects.inv")
116120

117121
# Build doc with node
118122
working_dir = str(tmp_dir / "kit")
@@ -143,7 +147,8 @@ def build_command(args):
143147
shutil.rmtree(output_path)
144148
shutil.copytree(tmp_dir / "kit" / "build", output_path)
145149
# Move the objects.inv file back
146-
shutil.move(tmp_dir / "objects.inv", output_path / "objects.inv")
150+
if not args.not_python_module:
151+
shutil.move(tmp_dir / "objects.inv", output_path / "objects.inv")
147152

148153

149154
def build_command_parser(subparsers=None):
@@ -170,6 +175,11 @@ def build_command_parser(subparsers=None):
170175
)
171176
parser.add_argument("--notebook_dir", type=str, help="Where to save the generated notebooks.", default=None)
172177
parser.add_argument("--html", action="store_true", help="Whether or not to build HTML files instead of MDX files.")
178+
parser.add_argument(
179+
"--not_python_module",
180+
action="store_true",
181+
help="Whether docs files do NOT have correspoding python module (like HF course & hub docs).",
182+
)
173183

174184
if subparsers is not None:
175185
parser.set_defaults(func=build_command)

0 commit comments

Comments
 (0)