11import { existsSync , readdirSync , readFileSync } from "fs" ;
22import { join } from "path" ;
33
4- import { CategoryType , RawSnippetType , SnippetType } from "../src/types" ;
4+ import { RawSnippetType , SnippetType } from "../src/types" ;
55import { isCorrectType } from "../src/utils/objectUtils" ;
66import { raise } from "../src/utils/raise" ;
77import { reverseSlugify , slugify } from "../src/utils/slugify" ;
88
9- const crlfRegex = / \r \n / gm;
9+ interface ParseLanguageResponse {
10+ name : string ;
11+ icon : string ;
12+ categories : Array < {
13+ name : string ;
14+ snippets : Array < SnippetType > ;
15+ } > ;
16+ subLanguages : ParseLanguageResponse [ ] ;
17+ }
18+
19+ interface ParseCategoryResponse {
20+ name : string ;
21+ snippets : Array < SnippetType > ;
22+ }
23+
1024const propertyRegex = / ^ \s + ( [ a - z A - Z ] + ) : \s * ( .+ ) / ;
11- const headerEndCodeStartRegex = / ^ \s * - - - \s * ` ` ` .* \n / ;
25+ const headerEndCodeStartRegex = / ^ \s * - - - \s * ` ` ` .* \r ? \ n/ ;
1226const codeRegex = / ^ ( .+ ) ` ` ` / s;
1327
14- let errored = false ;
28+ let errored : boolean = false ;
1529
1630function parseSnippet (
17- snippetPath : string ,
31+ path : string ,
1832 name : string ,
1933 text : string
2034) : SnippetType | null {
21- if ( crlfRegex . exec ( text ) !== null ) {
22- return raise (
23- "Found CRLF line endings instead of LF line endings" ,
24- snippetPath
25- ) ;
26- }
27- let cursor = 0 ;
35+ let cursor : number = 0 ;
2836
2937 const fromCursor = ( ) => text . substring ( cursor ) ;
30-
3138 if ( ! fromCursor ( ) . trim ( ) . startsWith ( "---" ) ) {
32- return raise ( "Missing header start delimiter '---'" , snippetPath ) ;
39+ return raise ( "Missing header start delimiter '---'" , path ) ;
3340 }
41+
3442 cursor += 3 ;
3543
3644 const properties = { } ;
3745
38- let match ;
46+ let match : string [ ] | null ;
3947 while ( ( match = propertyRegex . exec ( fromCursor ( ) ) ) !== null ) {
4048 cursor += match [ 0 ] . length ;
4149 properties [ match [ 1 ] . toLowerCase ( ) ] = match [ 2 ] ;
@@ -49,26 +57,28 @@ function parseSnippet(
4957 "tags" ,
5058 ] )
5159 ) {
52- return raise ( "Invalid properties" , snippetPath ) ;
60+ return raise ( "Invalid properties" , path ) ;
5361 }
5462
5563 if ( slugify ( properties . title ) !== name ) {
5664 return raise (
5765 `slugifyed 'title' property doesn't match snippet file name` ,
58- snippetPath
66+ path
5967 ) ;
6068 }
6169
6270 match = headerEndCodeStartRegex . exec ( fromCursor ( ) ) ;
6371 if ( match === null ) {
64- return raise ( "Missing header end '---' or code start '```'" , snippetPath ) ;
72+ return raise ( "Missing header end '---' or code start '```'" , path ) ;
6573 }
74+
6675 cursor += match [ 0 ] . length ;
6776
6877 match = codeRegex . exec ( fromCursor ( ) ) ;
6978 if ( match === null ) {
70- return raise ( "Missing code block end '```'" , snippetPath ) ;
79+ return raise ( "Missing code block end '```'" , path ) ;
7180 }
81+
7282 const code : string = match [ 1 ] ;
7383
7484 return {
@@ -83,56 +93,100 @@ function parseSnippet(
8393 . split ( "," )
8494 . map ( ( contributor ) => contributor . trim ( ) )
8595 . filter ( ( contributor ) => contributor ) ,
86- code,
96+ code : code . replace ( / \r \n / g , "\n" ) ,
8797 } ;
8898}
8999
90- const snippetPath = "snippets/" ;
91- export function parseAllSnippets ( ) {
92- const snippets = { } ;
100+ function parseCategory ( path : string , name : string ) : ParseCategoryResponse {
101+ const snippets : SnippetType [ ] = [ ] ;
93102
94- for ( const language of readdirSync ( snippetPath ) ) {
95- const languagePath = join ( snippetPath , language ) ;
96- const languageIconPath = join ( languagePath , "icon.svg" ) ;
103+ for ( const snippet of readdirSync ( path ) ) {
104+ const snippetPath = join ( path , snippet ) ;
105+ const snippetContent = readFileSync ( snippetPath ) . toString ( ) ;
106+ const snippetFileName = snippet . slice ( 0 , - 3 ) ;
97107
98- if ( ! existsSync ( languageIconPath ) ) {
108+ const snippetData = parseSnippet (
109+ snippetPath ,
110+ snippetFileName ,
111+ snippetContent
112+ ) ;
113+ if ( ! snippetData ) {
99114 errored = true ;
100- raise ( `icon for '${ language } ' is missing` ) ;
101115 continue ;
102116 }
117+ snippets . push ( snippetData ) ;
118+ }
119+
120+ return {
121+ name : reverseSlugify ( name ) ,
122+ snippets,
123+ } ;
124+ }
125+
126+ function parseLanguage (
127+ path : string ,
128+ name : string ,
129+ subLanguageOf : string | null = null
130+ ) : ParseLanguageResponse | null {
131+ const iconPath = join ( path , "icon.svg" ) ;
103132
104- const categories : CategoryType [ ] = [ ] ;
105-
106- for ( const category of readdirSync ( languagePath ) ) {
107- if ( category === "icon.svg" ) continue ;
108-
109- const categoryPath = join ( languagePath , category ) ;
110- const categorySnippets : SnippetType [ ] = [ ] ;
111-
112- for ( const snippet of readdirSync ( categoryPath ) ) {
113- const snippetPath = join ( categoryPath , snippet ) ;
114- const snippetContent = readFileSync ( snippetPath ) . toString ( ) ;
115- const snippetFileName = snippet . slice ( 0 , - 3 ) ;
116- const snippetData = parseSnippet (
117- snippetPath ,
118- snippetFileName ,
119- snippetContent
120- ) ;
121- if ( snippetData === null ) {
122- errored = true ;
123- continue ;
124- }
125- categorySnippets . push ( snippetData ) ;
133+ if ( ! existsSync ( iconPath ) ) {
134+ return raise (
135+ `icon for '${ subLanguageOf ? `${ subLanguageOf } /` : "" } ${ name } ' is missing`
136+ ) ;
137+ }
138+
139+ const subLanguages : Array < ParseLanguageResponse > = [ ] ;
140+ const categories : Array < ParseCategoryResponse > = [ ] ;
141+
142+ for ( const category of readdirSync ( path ) ) {
143+ if ( category === "icon.svg" ) continue ;
144+ const categoryPath = join ( path , category ) ;
145+
146+ if ( category . startsWith ( "[" ) && category . endsWith ( "]" ) ) {
147+ if ( subLanguageOf !== null ) {
148+ return raise ( "Cannot have more than two level of language nesting" ) ;
126149 }
127150
128- categories . push ( {
129- categoryName : reverseSlugify ( category ) ,
130- snippets : categorySnippets ,
131- } ) ;
151+ const parsedLanguage = parseLanguage (
152+ categoryPath ,
153+ category . slice ( 1 , - 1 ) ,
154+ name
155+ ) ;
156+ if ( ! parsedLanguage ) {
157+ errored = true ;
158+ continue ;
159+ }
160+ subLanguages . push ( parsedLanguage ) ;
161+ } else {
162+ categories . push ( parseCategory ( categoryPath , category ) ) ;
132163 }
164+ }
133165
134- snippets [ language ] = categories ;
166+ return {
167+ name : reverseSlugify ( name ) ,
168+ icon : iconPath ,
169+ categories,
170+ subLanguages,
171+ } ;
172+ }
173+
174+ export function parseAllSnippets ( ) {
175+ const snippetPath = "snippets/" ;
176+
177+ const languages : ParseLanguageResponse [ ] = [ ] ;
178+ for ( const language of readdirSync ( snippetPath ) ) {
179+ const languagePath = join ( snippetPath , language ) ;
180+ const parsedLanguage = parseLanguage ( languagePath , language ) ;
181+ if ( ! parsedLanguage ) {
182+ errored = true ;
183+ continue ;
184+ }
185+ languages . push ( parsedLanguage ) ;
135186 }
136187
137- return [ errored , snippets ] ;
188+ return {
189+ errored,
190+ languages,
191+ } ;
138192}
0 commit comments