#!meta {"kernelInfo":{"defaultKernelName":"spiral","items":[{"aliases":[],"name":"spiral"}]}} #!markdown # DirTreeHtml (Polyglot) #!fsharp #r @"../../../../../../../.nuget/packages/fsharp.control.asyncseq/3.2.1/lib/netstandard2.1/FSharp.Control.AsyncSeq.dll" #r @"../../../../../../../.nuget/packages/system.reactive/6.0.1-preview.1/lib/net6.0/System.Reactive.dll" #r @"../../../../../../../.nuget/packages/system.reactive.linq/6.0.1-preview.1/lib/netstandard2.0/System.Reactive.Linq.dll" #r @"../../../../../../../.nuget/packages/argu/6.2.4/lib/netstandard2.0/Argu.dll" #r @"../../../../../../../.nuget/packages/falco.markup/1.1.1/lib/netstandard2.0/Falco.Markup.dll" #!fsharp #!import ../../lib/fsharp/Notebooks.dib #!import ../../lib/fsharp/Testing.dib #!fsharp #!import ../../lib/fsharp/Common.fs #!import ../../lib/fsharp/CommonFSharp.fs #!import ../../lib/fsharp/Async.fs #!import ../../lib/fsharp/AsyncSeq.fs #!import ../../lib/fsharp/Runtime.fs #!import ../../lib/fsharp/FileSystem.fs #!fsharp #if !INTERACTIVE open Lib #endif #!fsharp open SpiralFileSystem.Operators open Falco.Markup #!fsharp type FileSystemNode = | File of string * string * int64 | Folder of string * string * FileSystemNode list | Root of FileSystemNode list let rec scanDirectory isRoot (basePath : string) (path : string) = let relativePath = path |> SpiralSm.replace basePath "" |> SpiralSm.replace "\\" "/" |> SpiralSm.replace "//" "/" |> SpiralSm.trim_start [| '/' |] let directories = path |> System.IO.Directory.GetDirectories |> Array.toList |> List.sort |> List.map (scanDirectory false basePath) let files = path |> System.IO.Directory.GetFiles |> Array.toList |> List.sort |> List.map (fun f -> File (System.IO.Path.GetFileName f, relativePath, System.IO.FileInfo(f).Length)) let children = directories @ files if isRoot then Root children else Folder (path |> System.IO.Path.GetFileName, relativePath, children) let rec generateHtml fsNode = let sizeLabel size = match float size with | size when size > 1024.0 * 1024.0 -> $"%.2f{size / 1024.0 / 1024.0} MB" | size when size > 1024.0 -> $"%.2f{size / 1024.0} KB" | size -> $"%.2f{size} B" match fsNode with | File (fileName, relativePath, size) -> Elem.div [] [ Text.raw "📄 " Elem.a [ Attr.href $"""{relativePath}{if relativePath = "" then "" else "/"}{fileName}""" ] [ Text.raw fileName ] Elem.span [] [ Text.raw $" ({size |> sizeLabel})" ] ] | Folder (folderName, relativePath, children) -> let size = let rec loop children = children |> List.sumBy (function | File (_, _, size) -> size | Folder (_, _, children) | Root children -> loop children ) loop children Elem.details [ Attr.open' "true" ] [ Elem.summary [] [ Text.raw "📂 " Elem.a [ Attr.href relativePath ] [ Text.raw folderName ] Elem.span [] [ Text.raw $" ({size |> sizeLabel})" ] ] Elem.div [] [ yield! children |> List.map generateHtml ] ] | Root children -> Elem.div [] [ yield! children |> List.map generateHtml ] let generateHtmlForFileSystem root = $""" {root |> generateHtml |> renderNode} """ #!fsharp //// test let expected = """
📂 _.root (10.00 B)
📂 3 (6.00 B)
📂 2 (3.00 B)
📂 1 (1.00 B)
📄 file.txt (1.00 B)
📄 file.txt (2.00 B)
📄 file.txt (3.00 B)
📄 file.txt (4.00 B)
""" let struct (tempFolder, disposable) = expected |> SpiralCrypto.hash_text |> SpiralFileSystem.create_temp_dir' let rec loop d n = async { if n >= 0 then tempFolder d |> System.IO.Directory.CreateDirectory |> ignore do! n |> string |> String.replicate (n + 1) |> SpiralFileSystem.write_all_text_async (tempFolder d $"file.txt") do! loop $"{d}/{n}" (n - 1) } loop "_.root" 3 |> Async.RunSynchronously let html = scanDirectory true tempFolder tempFolder |> generateHtmlForFileSystem html |> _assertEqual expected disposable.Dispose () html |> Microsoft.DotNet.Interactive.Formatting.Html.ToHtmlContent #!markdown ## Arguments #!fsharp [] type Arguments = | [] Dir of string | [] Html of string interface Argu.IArgParserTemplate with member s.Usage = match s with | Dir _ -> nameof Dir | Html _ -> nameof Html #!fsharp //// test Argu.ArgumentParser.Create().PrintUsage () #!markdown ## main #!fsharp let main args = let argsMap = args |> Runtime.parseArgsMap let dir = match argsMap.[nameof Arguments.Dir] with | [ Arguments.Dir dir ] -> Some dir | _ -> None |> Option.get let htmlPath = match argsMap.[nameof Arguments.Html] with | [ Arguments.Html html ] -> Some html | _ -> None |> Option.get let fileSystem = scanDirectory true dir dir let html = generateHtmlForFileSystem fileSystem html |> SpiralFileSystem.write_all_text_async htmlPath |> Async.runWithTimeout 30000 |> function | Some () -> 0 | None -> 1 #!fsharp //// test let args = System.Environment.GetEnvironmentVariable "ARGS" |> SpiralRuntime.split_args |> Result.toArray |> Array.collect id match args with | [||] -> 0 | args -> if main args = 0 then 0 else failwith "main failed"