Created
August 13, 2024 13:22
-
-
Save ImaginaryDevelopment/175b8b528c0b4f4c3b5a8ed3f23868b4 to your computer and use it in GitHub Desktop.
TreeDumper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// dump the directory structure that exists from cwd and down. ignoring most files | |
open System | |
let indentAmount = 2 | |
let dirIndent = "-" | |
let fileIndent = "+" | |
let interestingFiles = [ ".xml"; ".nupkg" ] | |
let blacklist = [ "bin"; "obj"; "packages"; "runtimes" ] | |
module Helpers = | |
let (|ValueString|_|) x = | |
if String.IsNullOrWhiteSpace x then | |
None | |
else | |
Some x | |
let isValueString = | |
function | |
| ValueString _ -> true | |
| _ -> false | |
let afterOpt (delim: string) (x: string) = | |
if not <| isValueString delim then | |
failwith "Delimiter must not be null or empty" | |
match x.IndexOf delim with | |
| i when i < 0 -> None | |
| i -> Some x.[i + delim.Length ..] | |
let afterOrSelf delim x = | |
afterOpt delim x |> Option.defaultValue x | |
let getFileCount d = | |
IO.Directory.GetFiles d | |
|> Seq.filter (fun f -> IO.File.Exists f) | |
|> Seq.length | |
open Helpers | |
module Option = | |
// swallow the error, make an option | |
let ofOk = | |
function | |
| Ok x -> Some x | |
| _ -> None | |
let tryResult f x = | |
try | |
f x |> Ok | |
with | |
| ex -> Error ex | |
let tryResult' f = tryResult f () | |
let enumerateFilesByExtension (extensions: string seq) (dir: string) : string seq = | |
System.IO.Directory.EnumerateFiles(dir, "*.*") | |
|> Seq.filter (fun x -> extensions |> Seq.exists x.EndsWith) | |
// assume something ending in .zip or .nupkg | |
let searchForPackage (dir: string) = | |
// System.IO.Directory.EnumerateFiles(dir, "*.xml|*.nupkg", System.IO.SearchOption.AllDirectories) | |
enumerateFilesByExtension interestingFiles dir | |
let containsBlacklistItem (path: string) = | |
blacklist | |
|> Seq.exists (fun bl -> path.Contains bl) // not accounting for case insensitivity | |
let trimEnclosing (enclosure: string) (path: string) = path |> afterOrSelf enclosure | |
type PathResult = Directory of string * (string list) * Result<int, exn> | |
// initial call will not catch if it fails, child calls however will be | |
let rec walkDirectories depth (path: string) = | |
[ for d in System.IO.Directory.EnumerateDirectories path do | |
let goForWalkies () = walkDirectories (depth + 1) d | |
// simple blacklisting does not account for hybrid words or path segmenting to properly respect the blacklist | |
if containsBlacklistItem d |> not then | |
let files: string list = | |
try | |
searchForPackage d | |
|> Seq.filter (containsBlacklistItem >> not) | |
|> Seq.map (trimEnclosing d) | |
|> List.ofSeq | |
with | |
| ex -> | |
eprintfn "Could not enumerate packages in '%s'" d | |
List.empty | |
yield (depth, PathResult.Directory(d, files, d |> tryResult getFileCount)) | |
match tryResult' goForWalkies with | |
| Ok items -> yield! items | |
| Error ex -> printfn $"Failed to descend into '%s{d}': '%s{ex.Message}'" ] | |
printfn "Starting in '%s'" Environment.CurrentDirectory | |
if IO.Directory.Exists Environment.CurrentDirectory | |
|> not then | |
eprintfn "Current directory doesn't exist" | |
let banner = "-------------------------" | |
let printBanner () = printfn "%s" banner | |
printBanner () | |
walkDirectories 0 Environment.CurrentDirectory | |
|> Seq.iter (fun (depth, pr) -> | |
let writeInfo indent p extra = printfn $"%s{indent}%s{p}%s{extra}" | |
match pr with | |
| PathResult.Directory (dir, files, countOpt) -> | |
let rep = String.replicate ((depth + 1) * indentAmount) | |
let indent = rep dirIndent | |
let c = | |
countOpt | |
|> Option.ofOk | |
|> Option.map string | |
|> Option.defaultValue "?" | |
|> sprintf "(%s)" | |
writeInfo indent (sprintf "%s%c" dir IO.Path.DirectorySeparatorChar) c | |
// writeInfo indent (sprintf "%s%c" dir '&') c | |
files | |
|> Seq.iter (fun f -> | |
let indent = rep fileIndent | |
let f = | |
if f.StartsWith IO.Path.DirectorySeparatorChar then | |
f[1..] | |
else f | |
writeInfo indent f null) | |
if depth = 0 then // for each root-level directory lets footer it | |
printBanner () | |
) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment