F# Console Application Template

Carl Nolan has posted another great online template:

 

If you are like me and often use console applications for a variety of purposes you would have found the F# template not much use (in fact a blank code file). As such I decided to put together a more complete Project Template that I could use.

The template can be found on the Visual Studio Gallery:

https://visualstudiogallery.msdn.microsoft.com/031891a9-06c3-47db-9c7d-8c9d4a32546a

When the template is installed you get the following template added to your F# folder when creating a new F# project:

image

The Project Template creates a Windows Console Application with the following components:

  • Program.fs: The application entry point defined
  • Arguments.fs: A simple command line parser placing all arguments into a dictionary
  • MyConsole.fs: A separate module for the custom console application code

The application entry point is quite simple:

module Program =   

     [<EntryPoint>]
     let Main(args) =

         MyConsole.Run args

         // main entry point return
         0

The custom console application handles the argument parsing, in addition to providing a TODO for the custom code:

module MyConsole =
        
     let Run args =

         // Define what arguments are expected
         let defs = [
             {ArgInfo.Command="a1"; Description="Argument 1"; Required=true };
             {ArgInfo.Command="a2"; Description="Argument 2"; Required=false } ]

         // Parse Arguments into a Dictionary
         let parsedArgs = Arguments.ParseArgs args defs
        
         // TODO add your code here
         Arguments.DisplayArgs parsedArgs
         Console.ReadLine() |> ignore

If one does not wish to use the argument parsing module the ArgInfo list can be removed. This list provides a simple means for ensuring that the start-up arguments are as expected. The returned parsed parameters is merely a Dictionary of the found arguments.

If one so desires one could map this over to the argument parsing provided in the FSharp PowerPack.

For completeness here is the argument parsing code:

module Arguments =

     // Type for simple argument checking
     type ArgInfo = { Command:string; Description:string; Required:bool }

     // Displays the help arguments
     let DisplayHelp (defs:ArgInfo list) =
         match defs with
         | [] -> Console.WriteLine "No help text defined."
         | _ ->
             Console.WriteLine "Command Arguments:"
             defs
             |> List.iter (fun def ->
                 let helpText = sprintf "-%s (Required=%b) : %s" def.Command def.Required def.Description
                 Console.WriteLine helpText )

     // Displays the found arguments
     let DisplayArgs (args:Dictionary<string, string>) =
         match args.Keys.Count with
         | 0 -> Console.WriteLine "No arguments found."
         | _ ->
             Console.WriteLine "Arguments Found:"
             for arg in args.Keys do
                 if String.IsNullOrEmpty(args.[arg]) then
                     Console.WriteLine (sprintf "-%s" arg)
                 else
                     Console.WriteLine (sprintf "-%s '%s'" arg args.[arg])

     // Parse the input arguments
     let ParseArgs (args:string array) (defs:ArgInfo list) =

         let parsedArgs = new Dictionary<string, string>()

         // Ensure help is supported if defintions provided
         let fullDefs =
             if not (List.exists (fun def -> String.Equals(def.Command, "help")) defs) then
                 {ArgInfo.Command="help"; Description="Display Help Text"; Required=false } :: defs
             else
                 defs

         // Report errors
         let reportError errorText =        
             DisplayArgs parsedArgs
             DisplayHelp fullDefs
             let errMessage = sprintf "Error occured: %A" errorText
             Console.Error.WriteLine errMessage
             Console.Error.Flush()
             Environment.Exit(1)

         // Capture variables
         let captureArg command value =
             match defs with
             | [] -> parsedArgs.Add(command, value)
             | _ ->                
                 if not (List.exists (fun def -> String.Equals(def.Command, command)) fullDefs) then
                     reportError (sprintf "Command '%s' Not in definition list." command)
                 else
                     parsedArgs.Add(command, value)            

         let (|IsCommand|_|) (command:string) =            
             let m = Regex.Match(command, "^(?:-{1,2}|\/)(?<command>.*)$", RegexOptions.IgnoreCase)
             if m.Success then Some(m.Groups.["command"].Value.ToLower()) else None

         let rec loop (argList:string list) =
             match argList with
             | [] -> ()
             | head::tail ->
                 match head with
                 | IsCommand command ->
                     match tail with
                     | [] -> captureArg command String.Empty
                     | iHead::iTail ->
                         match iHead with
                         | IsCommand iCommand ->
                             captureArg command String.Empty
                             loop tail
                         | _ ->
                             captureArg command iHead
                             loop iTail
                 | _ -> reportError (sprintf "Expected a command but got '%s'" head)
         loop (Array.toList args)
        
         // Look to see if help has been requested if not check for required
         if (parsedArgs.ContainsKey("help")) then
             DisplayHelp defs
         else
             defs
             |> List.filter (fun def -> def.Required)
             |> List.iter ( fun def ->
                 if not (parsedArgs.ContainsKey(def.Command)) then
                     reportError (sprintf "Command '%s' found but in argument list." def.Command))
                  
         parsedArgs

As always, hopefully you will find this template useful.

Written by Carl Nolan