1
1
import fs from 'node:fs' ;
2
2
import path from 'node:path' ;
3
+ import glob from 'tiny-glob/sync.js' ;
3
4
import { transform } from './markdown.js' ;
4
5
5
6
const text_files = new Set ( [
@@ -22,175 +23,169 @@ function json(file) {
22
23
return JSON . parse ( fs . readFileSync ( file , 'utf-8' ) ) ;
23
24
}
24
25
25
- export function get_index ( ) {
26
- const parts = [ ] ;
27
-
28
- /** @type {import('$lib/types').ExerciseRaw | null } */
29
- let last_exercise = null ;
30
-
31
- let last_part_meta = null ;
32
- let last_chapter_meta = null ;
33
-
34
- for ( const part of fs . readdirSync ( 'content/tutorial' ) ) {
35
- if ( ! / ^ \d { 2 } - / . test ( part ) ) continue ;
36
-
37
- const part_meta = {
38
- ...json ( `content/tutorial/${ part } /meta.json` ) ,
39
- slug : part
40
- } ;
41
-
42
- const chapters = [ ] ;
43
-
44
- for ( const chapter of fs . readdirSync ( `content/tutorial/${ part } ` ) ) {
45
- if ( ! / ^ \d { 2 } - / . test ( chapter ) ) continue ;
46
-
47
- const chapter_meta = {
48
- ...json ( `content/tutorial/${ part } /${ chapter } /meta.json` ) ,
49
- slug : chapter
50
- } ;
51
-
52
- const exercises = [ ] ;
53
-
54
- for ( const exercise of fs . readdirSync ( `content/tutorial/${ part } /${ chapter } ` ) ) {
55
- if ( ! / ^ \d { 2 } - / . test ( exercise ) ) continue ;
56
-
57
- const dir = `content/tutorial/${ part } /${ chapter } /${ exercise } ` ;
58
- if ( ! fs . statSync ( dir ) . isDirectory ( ) ) continue ;
59
-
60
- const text = fs . readFileSync ( `${ dir } /README.md` , 'utf-8' ) ;
61
- const { frontmatter, markdown } = extract_frontmatter ( text , dir ) ;
62
- const { title, path = '/' , focus } = frontmatter ;
63
- const slug = exercise . slice ( 3 ) ;
64
- const meta = fs . existsSync ( `${ dir } /meta.json` ) ? json ( `${ dir } /meta.json` ) : { } ;
65
-
66
- if ( last_exercise ) {
67
- last_exercise . next = {
68
- slug,
69
- title :
70
- last_part_meta !== part_meta
71
- ? part_meta . title
72
- : last_chapter_meta !== chapter_meta
73
- ? chapter_meta . title
74
- : title
75
- } ;
76
- }
77
-
78
- exercises . push (
79
- ( last_exercise = {
80
- slug : exercise . slice ( 3 ) ,
81
- title,
82
- path,
83
- focus,
84
- markdown,
85
- dir,
86
- prev : last_exercise ? { slug : last_exercise . slug } : null ,
87
- meta,
88
- next : null
89
- } )
90
- ) ;
26
+ /** @param {string } dir */
27
+ function is_valid ( dir ) {
28
+ return / ^ \d { 2 } - / . test ( dir ) ;
29
+ }
91
30
92
- last_chapter_meta = chapter_meta ;
93
- last_part_meta = part_meta ;
94
- }
31
+ /**
32
+ * @returns {import('$lib/types').PartStub[] }
33
+ */
34
+ export function get_index ( ) {
35
+ const parts = fs . readdirSync ( 'content/tutorial' ) . filter ( is_valid ) ;
95
36
96
- chapters . push ( {
97
- meta : {
98
- ...part_meta ,
99
- ...chapter_meta
100
- } ,
101
- exercises
102
- } ) ;
103
- }
37
+ return parts . map ( ( part ) => {
38
+ const chapters = fs . readdirSync ( `content/tutorial/${ part } ` ) . filter ( is_valid ) ;
104
39
105
- parts . push ( {
40
+ return {
106
41
slug : part ,
107
- meta : part_meta ,
108
- chapters
109
- } ) ;
110
- }
111
-
112
- return parts ;
42
+ title : json ( `content/tutorial/${ part } /meta.json` ) . title ,
43
+ chapters : chapters . map ( ( chapter ) => {
44
+ const exercises = fs . readdirSync ( `content/tutorial/${ part } /${ chapter } ` ) . filter ( is_valid ) ;
45
+
46
+ return {
47
+ slug : chapter ,
48
+ title : json ( `content/tutorial/${ part } /${ chapter } /meta.json` ) . title ,
49
+ exercises : exercises . map ( ( exercise ) => {
50
+ const dir = `content/tutorial/${ part } /${ chapter } /${ exercise } ` ;
51
+
52
+ const text = fs . readFileSync ( `${ dir } /README.md` , 'utf-8' ) ;
53
+ const { frontmatter } = extract_frontmatter ( text , dir ) ;
54
+ const { title } = frontmatter ;
55
+
56
+ return {
57
+ slug : exercise . slice ( 3 ) ,
58
+ title
59
+ } ;
60
+ } )
61
+ } ;
62
+ } )
63
+ } ;
64
+ } ) ;
113
65
}
114
66
115
67
/**
116
68
* @param {string } slug
117
69
* @returns {import('$lib/types').Exercise | undefined }
118
70
*/
119
71
export function get_exercise ( slug ) {
120
- const index = get_index ( ) ;
72
+ const exercises = glob ( '[0-9][0-9]-*/[0-9][0-9]-*/[0-9][0-9]-*/README.md' , {
73
+ cwd : 'content/tutorial'
74
+ } ) ;
121
75
122
- for ( let i = 0 ; i < index . length ; i += 1 ) {
123
- const part = index [ i ] ;
76
+ /** @type { string[] } */
77
+ const chain = [ ] ;
124
78
125
- /** @type {string[] } */
126
- const chain = [ ] ;
79
+ for ( let i = 0 ; i < exercises . length ; i += 1 ) {
80
+ const file = exercises [ i ] ;
81
+ const [ part_dir , chapter_dir , exercise_dir ] = file . split ( '/' ) ;
82
+ const exercise_slug = exercise_dir . slice ( 3 ) ;
127
83
128
- for ( const chapter of part . chapters ) {
129
- for ( const exercise of chapter . exercises ) {
130
- if ( fs . existsSync ( `${ exercise . dir } /app-a` ) ) {
131
- chain . length = 0 ;
132
- chain . push ( `${ exercise . dir } /app-a` ) ;
133
- }
84
+ const dir = `content/tutorial/${ part_dir } /${ chapter_dir } /${ exercise_dir } ` ;
85
+
86
+ if ( fs . existsSync ( `${ dir } /app-a` ) ) {
87
+ chain . length = 0 ;
88
+ chain . push ( `${ dir } /app-a` ) ;
89
+ }
90
+
91
+ if ( exercise_slug === slug ) {
92
+ const a = {
93
+ ...walk ( 'content/tutorial/common' , {
94
+ exclude : [ 'node_modules' , 'static/tutorial' , 'static/svelte-logo-mask.svg' ]
95
+ } ) ,
96
+ ...walk ( `content/tutorial/${ part_dir } /common` )
97
+ } ;
98
+
99
+ for ( const dir of chain ) {
100
+ Object . assign ( a , walk ( dir ) ) ;
101
+ }
134
102
135
- if ( exercise . slug === slug ) {
136
- const a = {
137
- ...walk ( 'content/tutorial/common' , {
138
- exclude : [ 'node_modules' , 'static/tutorial' , 'static/svelte-logo-mask.svg' ]
139
- } ) ,
140
- ...walk ( `content/tutorial/${ part . slug } /common` )
141
- } ;
142
-
143
- for ( const dir of chain ) {
144
- Object . assign ( a , walk ( dir ) ) ;
145
- }
146
-
147
- const b = walk ( `${ exercise . dir } /app-b` ) ;
148
-
149
- const scope = chapter . meta . scope ?? part . meta . scope ;
150
- const filenames = new Set (
151
- Object . keys ( a )
152
- . filter (
153
- ( filename ) => filename . startsWith ( scope . prefix ) && a [ filename ] . type === 'file'
154
- )
155
- . map ( ( filename ) => filename . slice ( scope . prefix . length ) )
156
- ) ;
157
-
158
- return {
159
- part : {
160
- slug : part . meta . slug ,
161
- title : part . meta . title ,
162
- index : i
163
- } ,
164
- chapter : {
165
- slug : chapter . meta . slug ,
166
- title : chapter . meta . title
167
- } ,
168
- scope,
169
- focus : exercise . focus ?? chapter . meta . focus ?? part . meta . focus ,
170
- title : exercise . title ,
171
- path : exercise . path ,
172
- slug : exercise . slug ,
173
- prev : exercise . prev ,
174
- next : exercise . next ,
175
- dir : exercise . dir ,
176
- editing_constraints : {
177
- create : exercise . meta . editing_constraints ?. create ?? [ ] ,
178
- remove : exercise . meta . editing_constraints ?. remove ?? [ ]
179
- } ,
180
- html : transform ( exercise . markdown , {
181
- codespan : ( text ) =>
182
- filenames . size > 1 && filenames . has ( text )
183
- ? `<code data-file="${ scope . prefix + text } ">${ text } </code>`
184
- : `<code>${ text } </code>`
185
- } ) ,
186
- a,
187
- b
188
- } ;
103
+ const b = walk ( `${ dir } /app-b` ) ;
104
+
105
+ const part_meta = json ( `content/tutorial/${ part_dir } /meta.json` ) ;
106
+ const chapter_meta = json ( `content/tutorial/${ part_dir } /${ chapter_dir } /meta.json` ) ;
107
+
108
+ const exercise_meta_file = `content/tutorial/${ part_dir } /${ chapter_dir } /${ exercise_dir } /meta.json` ;
109
+ const exercise_meta = fs . existsSync ( exercise_meta_file ) ? json ( exercise_meta_file ) : { } ;
110
+
111
+ const scope = chapter_meta . scope ?? part_meta . scope ;
112
+ const filenames = new Set (
113
+ Object . keys ( a )
114
+ . filter ( ( filename ) => filename . startsWith ( scope . prefix ) && a [ filename ] . type === 'file' )
115
+ . map ( ( filename ) => filename . slice ( scope . prefix . length ) )
116
+ ) ;
117
+
118
+ const text = fs . readFileSync ( `${ dir } /README.md` , 'utf-8' ) ;
119
+ const { frontmatter, markdown } = extract_frontmatter ( text , dir ) ;
120
+ const { title, path = '/' , focus } = frontmatter ;
121
+
122
+ const prev_slug = exercises [ i - 1 ] ?. split ( '/' ) [ 2 ] . slice ( 3 ) ;
123
+ const prev = prev_slug
124
+ ? {
125
+ slug : prev_slug
126
+ }
127
+ : null ;
128
+
129
+ let next = null ;
130
+
131
+ const next_exercise = exercises [ i + 1 ] ;
132
+
133
+ if ( next_exercise ) {
134
+ /** @type {string } */
135
+ let title ;
136
+
137
+ const dirs = next_exercise . split ( '/' ) ;
138
+ if ( dirs [ 0 ] !== part_dir ) {
139
+ console . log ( { dirs, part_dir, next_exercise } ) ;
140
+ title = json ( `content/tutorial/${ dirs [ 0 ] } /meta.json` ) . title ;
141
+ } else if ( dirs [ 1 ] !== chapter_dir ) {
142
+ title = json ( `content/tutorial/${ dirs [ 0 ] } /${ dirs [ 1 ] } /meta.json` ) . title ;
143
+ } else {
144
+ title = extract_frontmatter (
145
+ fs . readFileSync ( `content/tutorial/${ next_exercise } ` , 'utf-8' ) ,
146
+ next_exercise
147
+ ) . frontmatter . title ;
189
148
}
190
149
191
- chain . push ( `${ exercise . dir } /app-b` ) ;
150
+ next = {
151
+ slug : next_exercise . split ( '/' ) [ 2 ] . slice ( 3 ) ,
152
+ title
153
+ } ;
192
154
}
155
+
156
+ return {
157
+ part : {
158
+ slug : part_dir ,
159
+ title : `Part ${ part_dir . slice ( 1 , 2 ) } `
160
+ } ,
161
+ chapter : {
162
+ slug : chapter_dir ,
163
+ title : chapter_meta . title
164
+ } ,
165
+ scope,
166
+ focus : focus ?? chapter_meta . focus ?? part_meta . focus ,
167
+ title,
168
+ path,
169
+ slug : exercise_slug ,
170
+ prev,
171
+ next,
172
+ dir,
173
+ editing_constraints : {
174
+ create : exercise_meta . editing_constraints ?. create ?? [ ] ,
175
+ remove : exercise_meta . editing_constraints ?. remove ?? [ ]
176
+ } ,
177
+ html : transform ( markdown , {
178
+ codespan : ( text ) =>
179
+ filenames . size > 1 && filenames . has ( text )
180
+ ? `<code data-file="${ scope . prefix + text } ">${ text } </code>`
181
+ : `<code>${ text } </code>`
182
+ } ) ,
183
+ a,
184
+ b
185
+ } ;
193
186
}
187
+
188
+ chain . push ( `${ dir } /app-b` ) ;
194
189
}
195
190
}
196
191
0 commit comments