DirTreeHtml (Polyglot)¶
In [ ]:
#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"
In [ ]:
#!import ../../lib/fsharp/Notebooks.dib
#!import ../../lib/fsharp/Testing.dib
In [ ]:
#!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
In [ ]:
#if !INTERACTIVE
open Lib
#endif
In [ ]:
open SpiralFileSystem.Operators
open Falco.Markup
In [ ]:
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 =
$"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body {{
background-color: #222;
color: #ccc;
}}
a {{
color: #777;
font-size: 15px;
}}
span {{
font-size: 11px;
}}
div > div {{
padding-left: 10px;
}}
details > div {{
padding-left: 19px;
}}
</style>
</head>
<body>
{root |> generateHtml |> renderNode}
</body>
</html>
"""
In [ ]:
//// test
let expected = """<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<style>
body {
background-color: #222;
color: #ccc;
}
a {
color: #777;
font-size: 15px;
}
span {
font-size: 11px;
}
div > div {
padding-left: 10px;
}
details > div {
padding-left: 19px;
}
</style>
</head>
<body>
<div><details open="true"><summary>📂 <a href="_.root">_.root</a><span> (10.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3">3</a><span> (6.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3/2">2</a><span> (3.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3/2/1">1</a><span> (1.00 B)</span></summary><div><div>📄 <a href="_.root/3/2/1/file.txt">file.txt</a><span> (1.00 B)</span></div></div></details><div>📄 <a href="_.root/3/2/file.txt">file.txt</a><span> (2.00 B)</span></div></div></details><div>📄 <a href="_.root/3/file.txt">file.txt</a><span> (3.00 B)</span></div></div></details><div>📄 <a href="_.root/file.txt">file.txt</a><span> (4.00 B)</span></div></div></details></div>
</body>
</html>
"""
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
"<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <style> body { background-color: #222; color: #ccc; } a { color: #777; font-size: 15px; } span { font-size: 11px; } div > div { padding-left: 10px; } details > div { padding-left: 19px; } </style> </head> <body> <div><details open="true"><summary>📂 <a href="_.root">_.root</a><span> (10.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3">3</a><span> (6.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3/2">2</a><span> (3.00 B)</span></summary><div><details open="true"><summary>📂 <a href="_.root/3/2/1">1</a><span> (1.00 B)</span></summary><div><div>📄 <a href="_.root/3/2/1/file.txt">file.txt</a><span> (1.00 B)</span></div></div></details><div>📄 <a href="_.root/3/2/file.txt">file.txt</a><span> (2.00 B)</span></div></div></details><div>📄 <a href="_.root/3/file.txt">file.txt</a><span> (3.00 B)</span></div></div></details><div>📄 <a href="_.root/file.txt">file.txt</a><span> (4.00 B)</span></div></div></details></div> </body> </html> "
Arguments¶
In [ ]:
[<RequireQualifiedAccess>]
type Arguments =
| [<Argu.ArguAttributes.ExactlyOnce>] Dir of string
| [<Argu.ArguAttributes.ExactlyOnce>] Html of string
interface Argu.IArgParserTemplate with
member s.Usage =
match s with
| Dir _ -> nameof Dir
| Html _ -> nameof Html
In [ ]:
//// test
Argu.ArgumentParser.Create<Arguments>().PrintUsage ()
"USAGE: dotnet-repl [--help] --dir <string> --html <string> OPTIONS: --dir <string> Dir --html <string> Html --help display this list of options. "
main¶
In [ ]:
let main args =
let argsMap = args |> Runtime.parseArgsMap<Arguments>
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
In [ ]:
//// 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"
0