Doing some IO in F# - 2
Tags: dotnet, fsharp, io, samples
Published: 2021-03-12 21:06:00

# Simple things in F#

This is the second post in Simple things in F# I felt the IO chapter is quite big so I split the `File` and `Directory`

> ***DISCLAIMER***: Remember that IO operations are never guaranteed so you will need to catch errors on your real code I will not discuss exception handling here since that's a complete different topic but keep it in mind please,

### Directory

The `Directory` class offers similar functionality to the `File` class it has Create and Delete methods as well as helpers to enumerate the directories

```fsharp
open System.IO

let path = Path.Combine(".", "my-dir")
let dirinfo = Directory.CreateDirectory(path)
printfn $"%s{dirinfo.Name} - %s{dirinfo.FullName}"
// my-dir - C:\Users\scyth\repos\blogpostdrafts\my-dir

// To Delete the directory
// use either dirinfo.Delete
// dirinfo.Delete()
// or Directory.Delete
Directory.Delete(dirinfo.FullName)
```

> If you try to delete a directory that is not empty you will get an exception, you can just pass a Boolean to recursively delete everything within that directory
> 
> ```fsharp
> Directory.Delete(dirinfo.FullName, true)
> // or
> dirinfo.Delete(true)
> ```

that is pretty nice and if you pay attention to the IDE hints, you'll see that `CreateDirectory` returns a DirectoryInfo instance which is another class that also has more less the same methods that Directory has, the I think is that `DirectoryInfo` operates on a specific directory instead of a general purpose class for directories

What if we want to get the files and directories on the current path?

```fsharp
open System.IO

let files =
    Directory.EnumerateFiles(".")
    |> Seq.fold (fun prev next -> $"{prev}\n\t{next}") ""

let directories =
    Directory.EnumerateDirectories(".")
    |> Seq.fold (fun prev next -> $"{prev}\n\t{next}") ""

printfn $"files:\t{files}\n directories:\t{directories}"
```
that will get you something like
```
files:
        .\README.md
        .\sample.log
        .\simple-things-in-fsharp-1.md
 directories:
        .\.git
        .\.ionide
        .\samples
        .\scripts
```
depending on your system of course, now this will only get you the path to the files or directories back if you want to operate with those you would need to that path and the `File` or `Directory` classes to do what you need but you can create a `DirectoryInfo` instance yourself

```fsharp
open System.IO

let dir = DirectoryInfo(".")

let dirs = dir.EnumerateDirectories()
let files = dir.EnumerateFiles()

printfn "directories: %s\nfiles: %s" (dirs.GetType().ToString()) (files.GetType().ToString())
// directories: System.IO.Enumeration.FileSystemEnumerable`1[System.IO.DirectoryInfo]
// files: System.IO.Enumeration.FileSystemEnumerable`1[System.IO.FileInfo]
```

If we want to recursively travel all the directories within a directory we can use `SearchOption.AllDirectories` which will get us a flat list of directories under the specified path

```fsharp
open System.IO

let dir = DirectoryInfo(".")
let directories = dir.GetDirectories("*.*", SearchOption.AllDirectories)
printfn "%A" directories
(*
[|C:\Users\scyth\repos\blogpostdrafts\.git;
  C:\Users\scyth\repos\blogpostdrafts\.ionide;
  C:\Users\scyth\repos\blogpostdrafts\samples;
  C:\Users\scyth\repos\blogpostdrafts\scripts;
  C:\Users\scyth\repos\blogpostdrafts\.git\hooks;
  C:\Users\scyth\repos\blogpostdrafts\.git\info;
  C:\Users\scyth\repos\blogpostdrafts\.git\logs;
  C:\Users\scyth\repos\blogpostdrafts\.git\objects;
  C:\Users\scyth\repos\blogpostdrafts\.git\refs;
  C:\Users\scyth\repos\blogpostdrafts\samples\samples2;
  C:\Users\scyth\repos\blogpostdrafts\.git\logs\refs;
  C:\Users\scyth\repos\blogpostdrafts\.git\objects\info;
  C:\Users\scyth\repos\blogpostdrafts\.git\objects\pack;
  C:\Users\scyth\repos\blogpostdrafts\.git\refs\heads;
  C:\Users\scyth\repos\blogpostdrafts\.git\refs\remotes;
  C:\Users\scyth\repos\blogpostdrafts\.git\refs\tags;
  C:\Users\scyth\repos\blogpostdrafts\samples\samples2\samples3;
  C:\Users\scyth\repos\blogpostdrafts\.git\logs\refs\heads;
  C:\Users\scyth\repos\blogpostdrafts\.git\logs\refs\remotes;
  C:\Users\scyth\repos\blogpostdrafts\.git\refs\remotes\origin;
  C:\Users\scyth\repos\blogpostdrafts\samples\samples2\samples3\samples4;
  C:\Users\scyth\repos\blogpostdrafts\.git\logs\refs\remotes\origin|]
*)
```
that's what gets printed in my case. You can play with the pattern to filter out unwanted directories

Let's now do a relatively common exercise create a tree like structure of the directory layout 

> ***NOTE***: Please keep in mind that this might be a naïve way to implement this and is not the best solution it's just a way to do it and always refer to a more experimented colleague when you want to do things like these

```fsharp
open System.IO

// first let's define our data type that will 
type Hierarchy =
    { Name: string
      Files: string list
      Directories: Hierarchy list }


let getFiles (dir: DirectoryInfo) =
    dir.EnumerateFiles()
    |> Seq.map (fun file -> file.FullName)
    |> List.ofSeq

let getDirectories (dir: DirectoryInfo) =
    dir.EnumerateDirectories()
    |> Seq.filter (fun dir -> not (dir.Name.StartsWith(".")))
    |> List.ofSeq

/// we'll define a recursive function that will take a `List<DirectoryInfo>`
/// and returns a `List<Hierarchy>`
let rec getDirHierarchy (directories: DirectoryInfo list) =
    directories
    |> List.map
        (fun dir ->
            { Name = dir.FullName
              Files = getFiles dir
              Directories = getDirHierarchy (getDirectories dir) })

let getHierarchy (path: string) =
    let dir = DirectoryInfo(path)

    { Name = dir.FullName
      Files = getFiles dir
      Directories = getDirHierarchy (getDirectories dir) }

let hierarchy = getHierarchy "."
printfn $"%A{hierarchy}"
(*
{ Name = "C:\Users\scyth\repos\blogpostdrafts"
  Files =
   [C:\Users\scyth\repos\blogpostdrafts\README.md;
    C:\Users\scyth\repos\blogpostdrafts\sample.log;
    C:\Users\scyth\repos\blogpostdrafts\simple-things-in-fsharp-1.md;
    C:\Users\scyth\repos\blogpostdrafts\simple-things-in-fsharp-2.md]
  Directories =
   [{ Name = "C:\Users\scyth\repos\blogpostdrafts\samples"
      Files = [C:\Users\scyth\repos\blogpostdrafts\samples\moved.txt]
      Directories =
       [{ Name = "C:\Users\scyth\repos\blogpostdrafts\samples\samples2"
          Files =
           [C:\Users\scyth\repos\blogpostdrafts\samples\samples2\moved2.txt]
          Directories =
           [{ Name =
               "C:\Users\scyth\repos\blogpostdrafts\samples\samples2\samples3"
              Files = []
              Directories =
               [{ Name =
                   "C:\Users\scyth\repos\blogpostdrafts\samples\samples2\samples3\samples4"
                  Files = []
                  Directories = [] }] }] }] };
    { Name = "C:\Users\scyth\repos\blogpostdrafts\scripts"
      Files =
       [C:\Users\scyth\repos\blogpostdrafts\scripts\http.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-2.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-3.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-4.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-5.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-6.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-7.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1-8.fsx;
        C:\Users\scyth\repos\blogpostdrafts\scripts\stif-1.fsx]
      Directories = [] }] }
*)
```

to move a directory and all of it's contents simply use the `DirectoryInfo.MoveTo` or `Directory.Move` but please keep in mind that `MoveTo` is a method of an instance of `DirectoryInfo`

```fsharp
open System.IO

Directory.Move("./samples", "./samples2")

// check the directory was moved before running the last bits

let dir = DirectoryInfo("./samples2")
// move it back to it's original place
dir.MoveTo("./samples")
```

So, That's it! I think with this we can close the IO chapter these are common operations of the IO namespace but keep in mind this already exists [here](https://docs.microsoft.com/en-us/dotnet/standard/io/common-i-o-tasks) but since most samples are in C# you might feel intimidated or feel that you are not doing in an *F# idiomatic way* I'd say that you should not worry about it remember that your code runs in `.NET` so don't feel afraid to us existing libraries from other languages such as C# and VB. Feel f


As always feel free to comment below or to ping me in twitter 😁