@@ -4,6 +4,7 @@ const util = require('util');
44const yaml = require ( 'js-yaml' ) ;
55const { findIndex } = require ( 'lodash' ) ;
66const readDirP = require ( 'readdirp' ) ;
7+ const stringSimilarity = require ( 'string-similarity' ) ;
78
89const { curriculum : curriculumLangs } =
910 require ( '../shared/config/i18n' ) . availableLangs ;
@@ -176,7 +177,10 @@ const walk = (root, target, options, cb) => {
176177 } ) ;
177178} ;
178179
179- exports . getChallengesForLang = async function getChallengesForLang ( lang ) {
180+ exports . getChallengesForLang = async function getChallengesForLang (
181+ lang ,
182+ filters
183+ ) {
180184 const invalidLang = ! curriculumLangs . includes ( lang ) ;
181185 if ( invalidLang )
182186 throw Error ( `${ lang } is not a accepted language.
@@ -192,11 +196,86 @@ Accepted languages are ${curriculumLangs.join(', ')}`);
192196 { type : 'directories' , depth : 0 } ,
193197 buildSuperBlocks
194198 ) ;
195- const cb = ( file , curriculum ) => buildChallenges ( file , curriculum , lang ) ;
199+
200+ const superBlocks = Object . keys ( curriculum ) ;
201+ const blocksWithParent = Object . entries ( curriculum ) . flatMap (
202+ ( [ key , superBlock ] ) => {
203+ const blocks = Object . entries ( superBlock . blocks ) ;
204+ return blocks . map ( ( [ block , blockData ] ) => ( {
205+ block,
206+ blockData,
207+ superBlock : key
208+ } ) ) ;
209+ }
210+ ) ;
211+
212+ const blocks = blocksWithParent . map ( ( { block } ) => block ) ;
213+
214+ let filteredCurriculum = curriculum ;
215+ const updatedFilters = { ...filters } ;
216+ if ( filters ?. superBlock ) {
217+ const target = stringSimilarity . findBestMatch (
218+ filters . superBlock ,
219+ superBlocks
220+ ) . bestMatch . target ;
221+
222+ console . log ( 'superBlock being tested:' , target ) ;
223+
224+ filteredCurriculum = {
225+ [ target ] : curriculum [ target ]
226+ } ;
227+ updatedFilters . superBlock = target ;
228+ } else if ( filters ?. block ) {
229+ const target = stringSimilarity . findBestMatch ( filters . block , blocks )
230+ . bestMatch . target ;
231+
232+ console . log ( 'block being tested:' , target ) ;
233+ const targetBlock = blocksWithParent . find ( ( { block } ) => block === target ) ;
234+
235+ filteredCurriculum = {
236+ [ targetBlock . superBlock ] : {
237+ blocks : {
238+ [ targetBlock . block ] : targetBlock . blockData
239+ }
240+ }
241+ } ;
242+ updatedFilters . block = targetBlock . block ;
243+ } else if ( filters ?. challengeId ) {
244+ const blocksWithMeta = blocksWithParent . filter (
245+ ( { blockData } ) => blockData . meta
246+ ) ;
247+ const container = blocksWithMeta . filter ( ( { block, blockData } ) => {
248+ return blockData . meta . challengeOrder . some (
249+ ( { id } ) => id === filters . challengeId
250+ ) ;
251+ } ) ;
252+
253+ if ( container . length === 0 ) {
254+ throw new Error ( `No block found with challengeId ${ filters . challengeId } ` ) ;
255+ }
256+ if ( container . length > 1 ) {
257+ throw new Error (
258+ `Multiple blocks found with challengeId ${ filters . challengeId } `
259+ ) ;
260+ }
261+ const targetBlock = container [ 0 ] ;
262+ filteredCurriculum = {
263+ [ targetBlock . superBlock ] : {
264+ blocks : {
265+ [ targetBlock . block ] : targetBlock . blockData
266+ }
267+ }
268+ } ;
269+ updatedFilters . block = targetBlock . block ;
270+ updatedFilters . superBlock = targetBlock . superBlock ;
271+ }
272+
273+ const cb = ( file , curriculum ) =>
274+ buildChallenges ( file , curriculum , lang , updatedFilters ) ;
196275 // fill the scaffold with the challenges
197276 return walk (
198277 root ,
199- curriculum ,
278+ filteredCurriculum ,
200279 { type : 'files' , fileFilter : [ '*.md' , '*.yml' ] } ,
201280 cb
202281 ) ;
@@ -250,11 +329,17 @@ async function buildSuperBlocks({ path, fullPath }, curriculum) {
250329 return walk ( fullPath , curriculum , { depth : 1 , type : 'directories' } , cb ) ;
251330}
252331
253- async function buildChallenges ( { path : filePath } , curriculum , lang ) {
332+ async function buildChallenges ( { path : filePath } , curriculum , lang , filters ) {
254333 // path is relative to getChallengesDirForLang(lang)
255334 const block = getBlockNameFromPath ( filePath ) ;
335+ if ( filters ?. block && block !== filters . block ) {
336+ return ;
337+ }
256338 const superBlockDir = getBaseDir ( filePath ) ;
257339 const superBlock = getSuperBlockFromDir ( superBlockDir ) ;
340+ if ( filters ?. superBlock && superBlock !== filters . superBlock ) {
341+ return ;
342+ }
258343 let challengeBlock ;
259344
260345 // TODO: this try block and process exit can all go once errors terminate the
@@ -286,6 +371,9 @@ async function buildChallenges({ path: filePath }, curriculum, lang) {
286371 ? await parseCert ( englishPath )
287372 : await createChallenge ( filePath , meta ) ;
288373
374+ // this builds the entire block, even if we only want one challenge, which is
375+ // inefficient, but finding the next challenge without building the whole
376+ // block is fiddly.
289377 challengeBlock . challenges = [ ...challengeBlock . challenges , challenge ] ;
290378}
291379
0 commit comments