@@ -2,6 +2,7 @@ local helpers = require "spec.helpers"
22local lfs = require " lfs"
33local mods = require " mods"
44
5+ local List = mods .List
56local Tree = helpers .Tree
67local fs = mods .fs
78local path = mods .path
@@ -401,6 +402,218 @@ describe("mods.fs", function()
401402 end )
402403 end )
403404
405+ describe (" dir()" , function ()
406+ it (" yields no items for an empty directory" , function ()
407+ local root = make_tmp_dir ()
408+ local ls = List ()
409+
410+ for name , tp in fs .dir (root ) do
411+ ls :append (name .. " :" .. tp )
412+ end
413+
414+ assert .is_true (ls :isempty ())
415+ assert .is_true (fs .rm (root , true ))
416+ end )
417+
418+ it (" yields direct child names and types" , function ()
419+ local root = make_tmp_dir ()
420+ local subdir = join (root , " sub" )
421+ local nested_dir = join (subdir , " deep" )
422+ local target = join (root , " data.txt" )
423+ local nested = join (nested_dir , " nested.txt" )
424+ local ls = List ()
425+
426+ assert .is_true (fs .mkdir (nested_dir , true ))
427+ assert .is_true (fs .write_text (target , " abc" ))
428+ assert .is_true (fs .write_text (nested , " xyz" ))
429+
430+ for name , tp in fs .dir (root ) do
431+ ls :append (name .. " :" .. tp )
432+ end
433+
434+ ls :sort ()
435+ assert .same ({ " data.txt:file" , " sub:directory" }, ls )
436+ assert .is_true (fs .rm (root , true ))
437+ end )
438+
439+ it (" supports hidden filtering" , function ()
440+ local root = make_tmp_dir ()
441+ local subdir = join (root , " sub" )
442+ local hidden_dir = join (root , " .hidden" )
443+ local nested = join (subdir , " nested.txt" )
444+ local hidden_nested = join (hidden_dir , " nested.txt" )
445+ local ls = List ()
446+
447+ assert .is_true (fs .mkdir (subdir ))
448+ assert .is_true (fs .mkdir (hidden_dir ))
449+ assert .is_true (fs .write_text (join (root , " data.txt" ), " abc" ))
450+ assert .is_true (fs .write_text (join (root , " .secret" ), " zzz" ))
451+ assert .is_true (fs .write_text (nested , " xyz" ))
452+ assert .is_true (fs .write_text (hidden_nested , " qqq" ))
453+
454+ local opts = { hidden = false , recursive = true }
455+ for name , tp in fs .dir (root , opts ) do
456+ ls :append (name .. " :" .. tp )
457+ end
458+
459+ ls :sort ()
460+ assert .same ({ " data.txt:file" , " nested.txt:file" , " sub:directory" }, ls )
461+ assert .is_true (fs .rm (root , true ))
462+ end )
463+
464+ it (" supports file type filtering" , function ()
465+ local root = make_tmp_dir ()
466+ local subdir = join (root , " sub" )
467+ local nested_dir = join (subdir , " deep" )
468+ local target = join (root , " data.txt" )
469+ local nested = join (nested_dir , " nested.txt" )
470+ local files = List ()
471+
472+ assert .is_true (fs .mkdir (nested_dir , true ))
473+ assert .is_true (fs .write_text (target , " abc" ))
474+ assert .is_true (fs .write_text (nested , " xyz" ))
475+
476+ local opts = { recursive = true , type = " file" }
477+ for name , tp in fs .dir (root , opts ) do
478+ files :append (name .. " :" .. tp )
479+ end
480+
481+ files :sort ()
482+ assert .same ({ " data.txt:file" , " nested.txt:file" }, files )
483+ assert .is_true (fs .rm (root , true ))
484+ end )
485+
486+ it (" supports directory type filtering" , function ()
487+ local root = make_tmp_dir ()
488+ local subdir = join (root , " sub" )
489+ local nested_dir = join (subdir , " deep" )
490+ local target = join (root , " data.txt" )
491+ local nested = join (nested_dir , " nested.txt" )
492+ local dirs = List ()
493+
494+ assert .is_true (fs .mkdir (nested_dir , true ))
495+ assert .is_true (fs .write_text (target , " abc" ))
496+ assert .is_true (fs .write_text (nested , " xyz" ))
497+
498+ local opts = { recursive = true , type = " directory" }
499+ for name , tp in fs .dir (root , opts ) do
500+ dirs :append (name .. " :" .. tp )
501+ end
502+
503+ dirs :sort ()
504+ assert .same ({ " deep:directory" , " sub:directory" }, dirs )
505+ assert .is_true (fs .rm (root , true ))
506+ end )
507+
508+ it (" fails for a missing path" , function ()
509+ local root = make_tmp_dir ()
510+ local missing = join (root , " missing" )
511+
512+ local iter , errmsg = fs .dir (missing )
513+ assert .is_nil (iter )
514+ assert .is_string (errmsg )
515+ assert .is_true (fs .rm (root , true ))
516+ end )
517+
518+ it (" fails for a non-directory path" , function ()
519+ local root = make_tmp_dir ()
520+ local target = join (root , " data.txt" )
521+
522+ assert .is_true (fs .write_text (target , " abc" ))
523+
524+ local iter , errmsg = fs .dir (target )
525+ assert .is_nil (iter )
526+ assert .is_string (errmsg )
527+ assert .is_true (fs .rm (root , true ))
528+ end )
529+
530+ if is_unix then
531+ it (" supports link type filtering" , function ()
532+ local root = make_tmp_dir ()
533+ local target = join (root , " target.txt" )
534+ local link = join (root , " linked.txt" )
535+ local links = List ()
536+
537+ assert .is_true (fs .write_text (target , " abc" ))
538+
539+ local ok = lfs .link (target , link , true )
540+ if not ok then
541+ assert .is_true (fs .rm (root , true ))
542+ return
543+ end
544+
545+ local opts = { type = " link" }
546+ for name , tp in fs .dir (root , opts ) do
547+ links :append (name .. " :" .. tp )
548+ end
549+
550+ assert .same ({ " linked.txt:link" }, links )
551+ assert .is_true (fs .rm (root , true ))
552+ end )
553+
554+ it (" follows symlinked directories when requested" , function ()
555+ local root = make_tmp_dir ()
556+ local target_dir = join (root , " target" )
557+ local nested = join (target_dir , " nested.txt" )
558+ local link_dir = join (root , " linked" )
559+ local ls = List ()
560+
561+ assert .is_true (fs .mkdir (target_dir , true ))
562+ assert .is_true (fs .write_text (nested , " abc" ))
563+
564+ local ok = lfs .link (target_dir , link_dir , true )
565+ if not ok then
566+ assert .is_true (fs .rm (root , true ))
567+ return
568+ end
569+
570+ local opts = { recursive = true , follow_links = true }
571+ for name , tp in fs .dir (root , opts ) do
572+ ls :append (name .. " :" .. tp )
573+ end
574+
575+ local function contains (xs , value )
576+ for i = 1 , # xs do
577+ if xs [i ] == value then
578+ return true
579+ end
580+ end
581+ return false
582+ end
583+
584+ ls :sort ()
585+ assert .is_true (# ls >= 3 )
586+ assert .is_true (contains (ls , " linked:link" ))
587+ assert .is_true (contains (ls , " nested.txt:file" ))
588+ assert .is_true (fs .rm (root , true ))
589+ end )
590+
591+ it (" includes broken symlinks and does not traverse them" , function ()
592+ local root = make_tmp_dir ()
593+ local target = join (root , " missing" )
594+ local link = join (root , " broken" )
595+ local ls = List ()
596+
597+ assert .is_true (lfs .link (target , link , true ))
598+
599+ local opts = { recursive = true }
600+ for name , tp in fs .dir (root , opts ) do
601+ ls :append (name .. " :" .. tp )
602+ end
603+
604+ assert .same ({ " broken:link" }, ls )
605+ assert .is_true (fs .rm (root , true ))
606+ end )
607+ end
608+
609+ it (" labels option validation errors with the function name" , function ()
610+ assert .has_error (function ()
611+ --- @diagnostic disable-next-line : assign-type-mismatch
612+ _ = fs .dir (cwd , { recursive = " yes" })
613+ end , " dir.opts.recursive: boolean expected, got string" )
614+ end )
615+ end )
616+
404617 describe (" mkdir()" , function ()
405618 it (" creates a directory without parent mode" , function ()
406619 local root = make_tmp_dir ()
@@ -855,6 +1068,7 @@ describe("mods.fs", function()
8551068 it (" errors on invalid argument types" , function ()
8561069 -- Argument #1 validation.
8571070 assert .has_error (function () fs .cp (false ) end , " bad argument #1 to 'cp' (string expected, got boolean)" )
1071+ assert .has_error (function () fs .dir (false ) end , " bad argument #1 to 'dir' (string expected, got boolean)" )
8581072 assert .has_error (function () fs .exists (true ) end , " bad argument #1 to 'exists' (string expected, got boolean)" )
8591073 assert .has_error (function () fs .getatime (false ) end , " bad argument #1 to 'getatime' (string expected, got boolean)" )
8601074 assert .has_error (function () fs .getctime (0 ) end , " bad argument #1 to 'getctime' (string expected, got number)" )
@@ -872,6 +1086,7 @@ describe("mods.fs", function()
8721086
8731087 -- Argument #2 validation.
8741088 assert .has_error (function () fs .cp (" a" ) end , " bad argument #2 to 'cp' (string expected, got no value)" )
1089+ assert .has_error (function () fs .dir (" src" , false ) end , " bad argument #2 to 'dir' (table expected, got boolean)" )
8751090 assert .has_error (function () fs .listdir (" src" , false ) end , " bad argument #2 to 'listdir' (table expected, got boolean)" )
8761091 assert .has_error (function () fs .mkdir (" tmp" , 1 ) end , " bad argument #2 to 'mkdir' (boolean expected, got number)" )
8771092 assert .has_error (function () fs .rm (" tmp" , 1 ) end , " bad argument #2 to 'rm' (boolean expected, got number)" )
@@ -881,11 +1096,15 @@ describe("mods.fs", function()
8811096
8821097 -- Option validation.
8831098
884- local hidden = { hidden = 1 }
885- local rec = { recursive = 1 }
886- local follow = { follow_links = 1 }
887- local tp = { type = 1 }
1099+ local hidden = { hidden = 1 }
1100+ local rec = { recursive = 1 }
1101+ local follow = { follow_links = 1 }
1102+ local tp = { type = 1 }
8881103
1104+ assert .has_error (function () fs .dir (" src" , follow ) end , " dir.opts.follow_links: boolean expected, got number" )
1105+ assert .has_error (function () fs .dir (" src" , hidden ) end , " dir.opts.hidden: boolean expected, got number" )
1106+ assert .has_error (function () fs .dir (" src" , rec ) end , " dir.opts.recursive: boolean expected, got number" )
1107+ assert .has_error (function () fs .dir (" src" , tp ) end , " dir.opts.type: string expected, got number" )
8891108 assert .has_error (function () fs .listdir (" src" , follow ) end , " listdir.opts.follow_links: boolean expected, got number" )
8901109 assert .has_error (function () fs .listdir (" src" , hidden ) end , " listdir.opts.hidden: boolean expected, got number" )
8911110 assert .has_error (function () fs .listdir (" src" , rec ) end , " listdir.opts.recursive: boolean expected, got number" )
0 commit comments