Created
May 10, 2016 00:23
-
-
Save TeaDrivenDev/a1e73d25096f5771b30d9ea69134dad3 to your computer and use it in GitHub Desktop.
F# script to add the files for a new F# problem to an .fsproj file
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
(* | |
I am using Visual Studio to solve the F# problems on exercism.io, and I found it tedious | |
to always add the same files (test file, new code file, readme, plus a folder for better | |
organization) after each `exercism fetch`, so I automated that. | |
As it is, this assumes that the .fsproj file is in `exercism\fsharp`. When run, it compares | |
the subdirectories present with the folders in the .fsproj file, and for any that are | |
missing in the project file, it adds the `*Test(s).fs` and `README.md` files as well as | |
creating a new .fs file for the problem solution. It also adds a `module` line with the | |
respective file name (which is correct most of the time; only occasionally, the module the | |
test file will attempt to open has a different name). | |
Add this to the F# project, Alt-Enter it after every `exercism fetch`, and you should be | |
ready to go for solving the next problem without fiddling with the project. | |
*) | |
#r "System.Xml.Linq" | |
open System.IO | |
open System.Xml.Linq | |
let asFst second first = first, second | |
let asSnd first second = first, second | |
let xname = XName.Get | |
let elements (element : XElement) = element.Elements() | |
let elementsNamed (name : string) (element : XElement) = | |
element.Elements() | |
|> Seq.filter (fun element -> element.Name.LocalName = name) | |
let directory = __SOURCE_DIRECTORY__ | |
// This may need to be adjusted. | |
let projectFileName = "ExercismFsharp.fsproj" | |
let project = Path.Combine [| directory; projectFileName |] | |
let loadDocument (fileName : string) = (XDocument.Load fileName).Root | |
let getCodeFilesItemGroup doc = | |
doc | |
|> elementsNamed "ItemGroup" | |
|> Seq.find (fun itemGroup -> | |
itemGroup | |
|> elementsNamed "Compile" | |
|> Seq.tryHead | |
|> function | |
| Some node -> | |
// This assumes that the first 'Compile' node of the ItemGroup containing the .fs | |
// files will be that for AssemblyInfo.fs. If that isn't already the case, it is | |
// easily achieved by manually editing the .fsproj file. | |
node.Attribute(xname "Include").Value = "AssemblyInfo.fs" | |
| None -> false) | |
let directoriesToExclude = | |
// All subdirectories of the project directory that are *not* exercism problem diretories | |
[ ".paket"; ".vs"; "_NCrunch_ExercismFsharp"; "bin"; "obj"; "packages" ] | |
let getSubDirectories exclude directory = | |
let exclude = exclude |> Set.ofList | |
Directory.GetDirectories directory | |
|> Seq.map Path.GetFileName | |
|> Seq.filter (not << exclude.Contains) | |
let getProblemDirectories (itemGroup : XElement) = | |
itemGroup | |
|> elementsNamed "Compile" | |
|> Seq.choose (fun element -> | |
match element.Attribute(xname "Include").Value.Split '\\' with | |
| [| directory; _ |] -> Some directory | |
| _ -> None) | |
|> Seq.distinct | |
let getNewProblems problemsInProject problemsOnDisk = | |
Set.ofSeq problemsOnDisk - Set.ofSeq problemsInProject | |
let createCodeFile problemDirectory fileName = | |
[ sprintf "module %s" (Path.GetFileNameWithoutExtension fileName); "" ] | |
|> asSnd (Path.Combine [| directory; problemDirectory; fileName |]) | |
|> File.WriteAllLines | |
let getCodeFileName problemDirectory = | |
let testFileName = | |
// These are what I have come across so far for test file names. | |
[| "*Test.fs"; "*Tests.fs" |] | |
|> Array.collect (fun pattern -> | |
Directory.GetFiles(Path.Combine [| directory; problemDirectory |], pattern)) | |
|> Array.head | |
|> Path.GetFileName | |
let codeFileName = | |
let testFileName = Path.GetFileNameWithoutExtension testFileName | |
let offset = | |
match testFileName with | |
| name when name.EndsWith "Tests" -> 5 | |
| _ -> 4 | |
testFileName.Substring(0, testFileName.Length - offset) + ".fs" | |
codeFileName, testFileName | |
let addProblemFiles (itemGroup : XElement) (``namespace`` : XNamespace) problemDirectory fileNames = | |
fileNames | |
|> List.map (asFst "Compile") | |
|> List.append [ "README.md", "None" ] | |
|> List.iter (fun (fileName, tag) -> | |
let element = ``namespace`` + tag |> XElement | |
[| problemDirectory; fileName |] | |
|> Path.Combine | |
|> asSnd (xname "Include") | |
|> XAttribute | |
|> element.Add | |
itemGroup.Add element) | |
let projectFilePath = Path.Combine [| directory; projectFileName |] | |
let projectDocument = loadDocument projectFilePath | |
let ``namespace`` = projectDocument.GetDefaultNamespace() | |
let itemGroup = getCodeFilesItemGroup projectDocument | |
let problemsInProject = getProblemDirectories itemGroup | |
let problemsOnDisk = getSubDirectories directoriesToExclude directory | |
let newProblems = getNewProblems problemsInProject problemsOnDisk | |
for problem in newProblems do | |
let codeFileName, testFileName = getCodeFileName problem | |
createCodeFile problem codeFileName | |
addProblemFiles itemGroup ``namespace`` problem [ codeFileName; testFileName ] | |
projectDocument.Save projectFilePath |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment