let squashStrArray (str_arg : string[]) = let array_len = str_arg.Length let rec inner_squash (inner_str_arg : string []) (build_up_list : string) idx = if idx = array_len then build_up_list else let build_up_list = inner_str_arg.[idx] + " " + build_up_list inner_squash inner_str_arg build_up_list (idx + 1) inner_squash str_arg "" 0
Category: Programming
I discovered a nice utility that accompanies Visual Studio; at least it is in Visual Studio 2015. It is called ilmerge. It specific purposes is to fold an executable along with any dependent DLLs into a new executable that requires no external dependencies. You might need this for those time when you want a single executable on, say, a remote system.
Here is a command that binds two .Net libraries, one a distributed Microsoft API and the other is part of my program.
ilmerge /log:ilmerge.log ` /lib:C:\Windows\Microsoft.NET\Framework\v4.0.30319 ` /target:winexe /targetplatform:v4 ` /out:FreeBytesC.exe ` /ndebug FreeBytes.exe ` FreeBytes.exe FSharp.Core.dll VolLib.dll
The library and application have been updated to be a little more flexible and take a parameter, so that the minimum free space does not have to be hard-coded into the program.
Here is VolLib, an F# library:
namespace Toa.volLib open System open System.Threading open System.Collections.Generic open System.Text open System.IO open Microsoft.Win32 type DiskFreeLevels = | DRIVE_OFF_LINE = -1L | GB1 = 1000000000L | GB5 = 5000000000L | GB10 = 10000000000L | GB20 = 20000000000L [<AutoOpen>] module volLib = let libInit = ref false let drive_list () = DriveInfo.GetDrives() // Takes a string representation of a drive, and pulls out its free space. let free_space drive = let di = DriveInfo(drive) if di.IsReady then int64(di.AvailableFreeSpace) else int64(-1) // Just convert bytes to gigabytes. let cvt_bytes_to_gb (in_bytes:int64) = int64(truncate((float(int64(in_bytes)) / float(1024L) / float(1024L)) / float(1024L))) let DRIVE_OFF_LINE = int64(-1) let GB1 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB1)) let GB5 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB5)) let GB10 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB10)) let GB20 = cvt_bytes_to_gb(int64(DiskFreeLevels.GB20)) // This is a pipeline function that takes a drive letter (string) and // returns the free GB from a DriveInfo structure. let cvt_drive_to_free_gb drive = let fs = free_space drive if fs > DRIVE_OFF_LINE then free_space drive |> cvt_bytes_to_gb else int64(-1) let cvt_gb_arg_to_int (arg:string) = let numeric_arg = arg.Substring(2, arg.Length - 4) int64(numeric_arg)
and the main application
(* A simple checker of Windows disks. Charles M. Norton 11/01/2016 Will be greatly improved over time. Modifications Charles M. Norton 11/1/2016 Initial version. *) open System open System.Text open System.Net.Mail open System.IO open System.Threading open Microsoft.VisualBasic.FileIO open System.Collections.Generic open Toa.volLib // local library let send_email msg = use msg = new MailMessage ("dbadmin@somewhere.sometown.us", @"dbadmin@town.arlington.ma.us", @"Disk Space Report\n", msg ) use client = new SmtpClient(@"webmail.somewhere.sometown.us") client.DeliveryMethod <- SmtpDeliveryMethod.Network client.Credentials <- new System.Net.NetworkCredential("dbadmin", "xxxxxx") client.Send msg let match_head = "--" let match_tail = "GB" let contains_match_constants (arg:string) = if arg.StartsWith match_head && arg.EndsWith match_tail then true else false let parse_min_gb_arg arg = if contains_match_constants arg then cvt_gb_arg_to_int arg else volLib.GB1 let no_disk = int64(-1) [<EntryPoint>] let main argv = let min_gb = if argv.Length > 0 then parse_min_gb_arg argv.[0] else volLib.GB1 let local_drive_list = drive_list () let check_file = "free_bytes_check.txt" let system_name = System.Environment.MachineName let system_drive = Path.GetPathRoot(Environment.SystemDirectory) let display_info = new List<string>() display_info.Add("System name " + system_name + "\n") let drive_list_len = local_drive_list.Length for idx = 0 to (drive_list_len - 1) do let drive_name = (Seq.item idx local_drive_list).Name let free_gb = cvt_drive_to_free_gb drive_name let display_row = "Volume: " + drive_name + "\n" + "Free Space: " + free_gb.ToString() + "GB\n" display_info.Add(display_row) let curr_time = DateTime.Now let gb_c = cvt_drive_to_free_gb system_drive let gb_d = cvt_drive_to_free_gb "D:\\" let gbs = display_info |> String.concat("\n") if not (File.Exists check_file) then use swH = new StreamWriter(check_file, true) swH.WriteLine(curr_time.ToString()) send_email gbs else let fAccessDate = File.GetLastAccessTime(check_file) let update_stamp = curr_time.Subtract(fAccessDate).Duration() < TimeSpan.FromDays(7.0) if not(update_stamp) then File.WriteAllText(check_file.ToString(), String.Empty) use swH = new StreamWriter(check_file, true) swH.WriteLine(curr_time.ToString()) send_email gbs if gb_c < min_gb || ((gb_d > no_disk) && gb_d < volLib.GB20) then printfn "Disk free space is below thresholds; send out warning email." send_email gbs else printfn "Disk free space is at or above thresholds. " printfn "All is well, at least for now."</pre> <pre> printfn "Sleeping 5 seconds to let you read." Thread.Sleep 5000 |> ignore 0 // return an integer exit code
So, here is a small, F# program to report on disk usage. This is from a real example on our Munis server.
(* A simple checker of Windows disks. Charles M. Norton 11/01/2016 Will be greatly improved over time. Modifications Charles M. Norton 11/1/2016 Initial version. *) open System open System.Text open System.Net.Mail open System.IO open System.Threading open Toa.volLib // local library let SendEmail msg = use msg = new MailMessage ( "dbadmin@town.arlington.ma.us", @"itadmin@town.arlington.ma.us", @"Munis Server Disk Space Report\n", msg ) use client = new SmtpClient(@"webmail.town.arlington.ma.us") client.DeliveryMethod client.Credentials client.Send msg let main argv = let localDriveList = driveList () let dn = (Seq.head(localDriveList)).Name let drive_c = (Seq.head(localDriveList)).Name let data_drive = (Seq.nth(1) localDriveList).Name let fs_c = freeSpace drive_c let fs_d = freeSpace data_drive let gb_c = cvtBytesToGB fs_c let gb_d = cvtBytesToGB fs_d let system_name = System.Environment.MachineName let strings = ["System name: "; system_name; "Volume "; drive_c; gb_c.ToString(); "GB\n"; "Volume"; data_drive; gb_d.ToString(); "GB\n"] let gbs = String.concat " " strings printfn "System name: %A: Drive C %f -- Data Drive %f" system_name gb_c gb_d if gb_c < volLib.GB5 || gb_d < volLib.GB5 then printfn "Disk free space is below thresholds; send out warning email." SendEmail gbs else printfn "Disk free space is at or above thresholds. All is well, at least for now." printfn "Sleeping 5 seconds to let you read." Thread.Sleep 5000 |> ignore 0 // return an integer exit code
I started building an F# library that could be used to hold housekeeping routines. This is the start of that library.
namespace Toa.volLib open System open System.Threading open System.Collections.Generic open System.Text open System.IO open Microsoft.Win32 type DiskFreeLevels = | GB1 = 1000000000L | GB5 = 5000000000L | GB10 = 10000000000L [<AutoOpen>] module volLib = let libInit = ref false let driveList () = DriveInfo.GetDrives() let freeSpace drive = let di = DriveInfo(drive) if di.IsReady then int64(di.AvailableFreeSpace) else int64(DiskFreeLevels.GB10) let cvtBytesToGB (inBytes:int64) = truncate((float(int64(inBytes)) / float(1024L) / float(1024L)) / float(1024L)) let GB1 = cvtBytesToGB(int64(DiskFreeLevels.GB1)) let GB5 = cvtBytesToGB(int64(DiskFreeLevels.GB5)) let GB10 = cvtBytesToGB(int64(DiskFreeLevels.GB10))
open System let readInput() = Seq.initInfinite (fun _ -> Console.ReadLine()) |> Seq.takeWhile (fun s -> not (s.Equals(""))) |> Seq.map int
There are a lot of ways to read information from the console, and with GUIs
having been around a long time, console I/O is not as big a deal as it
was years ago. For F#, this seems to be a good way to read integers from the command line, and then convert them from ASCII to int.
From a sequence, conversion is possible to an array or list.
If you create a sequence in Clojure, the sequence sticks around within the scope of its creation. That is, once a Clojure sequence is created and bound to a var, nothing special has to be done to make sure its contents stick around. This is not so with F#.
Take this program
open System open System.Collections.Generic open System.Text open System.IO #nowarn "40" let rec readLines () = seq { let line = Console.ReadLine() if not (line.Equals("")) then yield Int32.Parse(line) yield! readLines () } [<EntryPoint>] let main argv = let inSeq = readLines () inSeq |> Seq.length |> printfn "%d lines read" // This will keep it alive enough to read your output Console.ReadKey() |> ignore 0
Because of a sequences’ inherent laziness, the reading in of numbers does not begin until inSeq’s length is computed. Without caching inSeq, you will have to read the integers in again if you want to iterate the sequence, unless this change is made
let inSeq = readLines () |> Seq.cache
I got a very nice answer on SO to why a function was not executing.
open System open System.Collections.Generic open System.Text open System.IO #nowarn "40" let rec readlines () = seq { let line = Console.ReadLine() if not (line.Equals("")) then yield line yield! readlines () } [<EntryPoint>] let main argv = let inSeq = readlines () inSeq |> Seq.length |> printfn "%d lines read" // This will keep it alive enough to read your output Console.ReadKey() |> ignore 0
Got this example from here.
open System open System.Threading open System.Collections.Generic open System.Linq open System.Text open System.Threading.Tasks open System.IO open Microsoft.VisualBasic.FileIO let main argv = let data = [(Cats,4); (Dogs,5); (Mice,3); (Elephants,2)] let count = List.fold (fun acc (nm,x) acc+x) 0 data printfn "Total number of animals: %d" count
Even though I’ve used map and other functions in Clojure, I’ve forgotten the basics of an inline function. The value of 0 is assigned to nm, and x takes on the first row of data.
Yes, it is my favorite subject, which is transforming data in .csv files. Here’s reading data into a variable in F#.
open System open System.Threading open System.Collections.Generic open System.Linq open System.Text open System.Threading.Tasks open System.IO open Microsoft.VisualBasic.FileIO [<EntryPoint>] let main argv = let csv_fileH = new TextFieldParser(test1.csv) csv_fileH.TextFieldType = FieldType.Delimited |> ignore let x = csv_fileH.SetDelimiters(',') let csv_data = new List string[]() let eod = csv_fileH.EndOfData if not eod then let column_headings = csv_fileH.ReadFields() csv_data.Add(column_headings) |> ignore // Parentheses are needed after function definition to produce type bool. let read_rest_of_csv() = csv_data.Add(csv_fileH.ReadFields()) |> not csv_fileH.EndOfData while read_rest_of_csv() do ignore None 0 // return an integer exit code
This program is associated on stackoverflow with some very good answers to a question I asked.