├── forge.toml ├── src ├── Forge.ProjectSystem │ ├── paket.references │ ├── Forge.ProjectSystem.fsproj │ ├── Constants.fs │ ├── XLinq.fs │ ├── Prelude.fs │ └── ResizeArray.fs ├── Forge.Core │ ├── paket.references │ ├── paket.template │ ├── Fake.fs │ ├── Alias.fs │ ├── ZipHelper.fs │ ├── Forge.Core.fsproj │ ├── Paket.fs │ ├── GacSearch.fs │ ├── Git.fs │ ├── TraceListener.fs │ ├── Environment.fs │ ├── Globbing.fs │ ├── TraceHelper.fs │ ├── ProcessHelper.fs │ ├── FileHelper.fs │ ├── Templates.fs │ └── ProjectManager.fs └── Forge │ ├── paket.references │ ├── runtimeconfig.template.json │ ├── Tools │ └── Paket │ │ └── paket.bootstrapper.exe │ ├── Forge.fsproj │ └── Program.fs ├── tests ├── Forge.Tests │ ├── paket.references │ ├── Main.fs │ ├── Forge.Tests.fsproj │ ├── common.fs │ └── Tests.fs ├── Forge.IntegrationTests │ ├── paket.references │ ├── Main.fs │ ├── Forge.IntegrationTests.fsproj │ ├── Helpers.fs │ └── Tests.fs └── TestCoverage.md ├── .paket ├── paket.exe └── paket.targets ├── global.json ├── Directory.Build.props ├── .config └── dotnet-tools.json ├── azure-pipelines.yml ├── paket.dependencies ├── .gitattributes ├── LICENSE.txt ├── .github └── ISSUE_TEMPLATE.md ├── RELEASE_NOTES.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── .gitignore ├── README.md ├── Forge.sln └── Reference.md /forge.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | test='fake RunTests' -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core -------------------------------------------------------------------------------- /tests/Forge.Tests/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Expecto -------------------------------------------------------------------------------- /tests/Forge.IntegrationTests/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core 2 | Expecto -------------------------------------------------------------------------------- /.paket/paket.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionide/Forge/HEAD/.paket/paket.exe -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "3.1.200" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Forge.Core/paket.references: -------------------------------------------------------------------------------- 1 | SharpZipLib 2 | FSharp.Core 3 | FParsec 4 | Nett -------------------------------------------------------------------------------- /src/Forge/paket.references: -------------------------------------------------------------------------------- 1 | Argu 2 | SharpZipLib 3 | FSharp.Core 4 | FParsec 5 | Nett -------------------------------------------------------------------------------- /src/Forge/runtimeconfig.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "rollForwardOnNoCandidateFx": 2 3 | } 4 | -------------------------------------------------------------------------------- /src/Forge/Tools/Paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ionide/Forge/HEAD/src/Forge/Tools/Paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /tests/Forge.Tests/Main.fs: -------------------------------------------------------------------------------- 1 | module Forge.Tests 2 | 3 | open Expecto 4 | 5 | [] 6 | let main argv = 7 | Tests.runTestsInAssembly defaultConfig argv 8 | -------------------------------------------------------------------------------- /tests/Forge.IntegrationTests/Main.fs: -------------------------------------------------------------------------------- 1 | module Forge.IntegrationTests 2 | 3 | open Expecto 4 | 5 | [] 6 | let main argv = 7 | Tests.runTestsInAssembly defaultConfig argv 8 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | $(NoWarn);FS2003 4 | false 5 | 6 | -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "fake-cli": { 6 | "version": "5.19.0", 7 | "commands": [ 8 | "fake" 9 | ] 10 | }, 11 | "paket": { 12 | "version": "5.241.6", 13 | "commands": [ 14 | "paket" 15 | ] 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/Forge.ProjectSystem.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Forge.Core/paket.template: -------------------------------------------------------------------------------- 1 | type project 2 | owners 3 | Krzysztof Cieslak 4 | authors 5 | Krzysztof Cieslak 6 | projectUrl 7 | http://github.com/ionide/Forge 8 | iconUrl 9 | https://raw.githubusercontent.com/ionide/Forge/master/docs/files/img/logo.png 10 | licenseUrl 11 | http://github.com/ionide/Forge/blob/master/LICENSE.txt 12 | requireLicenseAcceptance 13 | false 14 | copyright 15 | Copyright 2015-2019 16 | tags 17 | F#, fsharp, project system 18 | summary 19 | Forge is a build tool that provides tasks for creating, compiling, and testing F# projects 20 | description 21 | Forge is a build tool that provides tasks for creating, compiling, and testing F# projects 22 | 23 | 24 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | name: $(Rev:r) 2 | jobs: 3 | - job: Windows 4 | pool: 5 | vmImage: 'vs2017-win2016' 6 | steps: 7 | - script: dotnet tool install fake-cli --tool-path . 8 | displayName: Install FAKE 9 | - script: fake build 10 | displayName: Run Build 11 | - task: PublishBuildArtifacts@1 12 | inputs: 13 | pathtoPublish: out 14 | artifactName: Forge 15 | - job: Linux 16 | pool: 17 | vmImage: 'ubuntu-16.04' 18 | steps: 19 | - script: dotnet tool install fake-cli --tool-path . 20 | displayName: Install FAKE 21 | - script: ./fake build 22 | displayName: Run Build 23 | - task: PublishBuildArtifacts@1 24 | inputs: 25 | pathtoPublish: out 26 | artifactName: Forge -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | framework: netstandard2.0, netcoreapp2.1, netcoreapp3.1 2 | source https://nuget.org/api/v2 3 | 4 | nuget Argu 5 | nuget Expecto 6 | nuget FSharp.Core redirects: force 7 | nuget SharpZipLib 8 | nuget FParsec 9 | nuget Nett 10 | 11 | github fsharp/FAKE modules/Octokit/Octokit.fsx 12 | 13 | group Build 14 | source https://www.nuget.org/api/v2 15 | 16 | nuget Fake.Core.Target 17 | nuget Fake.Core.Process 18 | nuget Fake.DotNet.Cli 19 | nuget Fake.Core.ReleaseNotes 20 | nuget Fake.DotNet.AssemblyInfoFile 21 | nuget Fake.DotNet.Paket 22 | nuget Fake.Tools.Git 23 | nuget Fake.Core.Environment 24 | nuget Fake.Core.UserInput 25 | nuget Fake.IO.FileSystem 26 | nuget Fake.DotNet.MsBuild 27 | nuget Fake.Api.GitHub -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp text=auto eol=lf 6 | *.fs diff=csharp text=auto eol=lf 7 | *.fsi diff=csharp text=auto eol=lf 8 | *.fsx diff=csharp text=auto eol=lf 9 | *.sln text eol=crlf merge=union 10 | *.csproj merge=union 11 | *.vbproj merge=union 12 | *.fsproj merge=union 13 | *.dbproj merge=union 14 | 15 | # Standard to msysgit 16 | *.doc diff=astextplain 17 | *.DOC diff=astextplain 18 | *.docx diff=astextplain 19 | *.DOCX diff=astextplain 20 | *.dot diff=astextplain 21 | *.DOT diff=astextplain 22 | *.pdf diff=astextplain 23 | *.PDF diff=astextplain 24 | *.rtf diff=astextplain 25 | *.RTF diff=astextplain 26 | 27 | # Ensure that .sh files keep LF instead of CRLF so that shebang lines work 28 | *.sh eol=lf 29 | -------------------------------------------------------------------------------- /tests/Forge.Tests/Forge.Tests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | Exe 6 | 7 | 8 | 9 | Forge.Core.fsproj 10 | 11 | 12 | Forge.ProjectSystem.fsproj 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/Forge.IntegrationTests/Forge.IntegrationTests.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | Exe 6 | 7 | 8 | 9 | Forge.Core.fsproj 10 | 11 | 12 | Forge.ProjectSystem.fsproj 13 | 14 | 15 | Forge.fsproj 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Forge/Forge.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | Exe 6 | true 7 | 8 | 9 | 10 | Forge.Core 11 | 12 | 13 | Forge.ProjectSystem.fsproj 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Forge.Core/Fake.fs: -------------------------------------------------------------------------------- 1 | module Forge.Fake 2 | open Forge.ZipHelper 3 | open System.IO 4 | open System.Net 5 | 6 | let location = templatesLocation ".fake" 7 | 8 | let Copy folder = 9 | Directory.GetFiles location 10 | |> Seq.iter (fun x -> 11 | let fn = (folder Path.GetFileName x) 12 | if not ^ File.Exists fn then File.Copy (x, fn) ) 13 | 14 | let getFAKE () = 15 | match Directory.EnumerateFiles(getCwd(), "FAKE.exe", SearchOption.AllDirectories) |> Seq.tryHead with 16 | | Some f -> f 17 | | None -> fakeToolLocation "FAKE.exe" 18 | 19 | 20 | let Update () = 21 | use wc = new WebClient() 22 | let zip = fakeLocation "fake.zip" 23 | System.IO.Directory.CreateDirectory(fakeLocation) |> ignore 24 | printfn "Downloading FAKE..." 25 | wc.DownloadFile("https://www.nuget.org/api/v2/package/FAKE", zip ) 26 | Unzip fakeLocation zip 27 | 28 | 29 | let Run args = 30 | let f = getFAKE () 31 | if not ^ File.Exists f then Update () 32 | let args' = args |> String.concat " " 33 | run f args' ^ getCwd() 34 | 35 | -------------------------------------------------------------------------------- /src/Forge.Core/Alias.fs: -------------------------------------------------------------------------------- 1 | module Forge.Alias 2 | 3 | open System 4 | open System.IO 5 | open Nett 6 | open Prelude 7 | 8 | let defaultAliases = 9 | [ 10 | "build", "fake" 11 | "test", "fake Test" 12 | "release", "fake Release" 13 | "install", "paket install" 14 | "update", "paket update" 15 | ] |> Map.ofList 16 | 17 | let private merge original added = 18 | added 19 | |> Map.fold (fun s k v -> Map.add k v s) original 20 | 21 | 22 | let private parse path = 23 | if File.Exists path then 24 | let toml = Toml.ReadFile path 25 | let alias = toml.Get("alias") 26 | alias.Rows 27 | |> Seq.map (fun kv -> kv.Key, kv.Value.Get()) 28 | |> Map.ofSeq 29 | else 30 | Map.empty 31 | 32 | let private loadLocal () = 33 | let path = getCwd() "forge.toml" 34 | parse path 35 | 36 | let private loadGlobal () = 37 | let path = exeLocation "forge.toml" 38 | parse path 39 | 40 | let load () = 41 | let globals = loadGlobal () 42 | let locals = loadLocal () 43 | 44 | locals |> merge globals |> merge defaultAliases 45 | -------------------------------------------------------------------------------- /tests/TestCoverage.md: -------------------------------------------------------------------------------- 1 | # Test Coverage # 2 | 3 | ## Project System ## 4 | 5 | * [x] Parse - get all references 6 | * [x] Parse - build options 7 | * [x] Parse - add file to project 8 | * [x] Parse - add duplicate file to project 9 | * [x] Parse - remove file from project 10 | * [x] Parse - remove non-existing fie fom project 11 | * [x] Parse - order project file (moveUp) 12 | * [x] Parse - order project file (moveDown) 13 | * [x] Parse - add reference 14 | * [x] Parse - add existing reference 15 | * [x] Parse - remove reference 16 | * [x] Parse - remove non-existing reference 17 | * [x] Parse - move up 18 | * [x] Parse - move down 19 | * [x] Parse - add above (?) 20 | * [x] Parse - add below (?) 21 | * [ ] Parse - add dir 22 | * [ ] Parse - remove dir 23 | * [x] Parse - rename file 24 | * [ ] Parse - rename directory 25 | * [ ] Path Helper - normalize file name 26 | * [ ] Path Helper - get root 27 | * [ ] Path Helper - path is directory 28 | * [ ] Path Helper - get parent dir from path 29 | * [ ] Path Helper - remove parent dir 30 | * [ ] Path Helper - remove root 31 | * [ ] Path Helper - fixDir: add trailing slash if missing 32 | * [ ] Path Helper - dirOrder: (?) 33 | * [ ] Path Helper - treeOrder: (?) 34 | * [ ] Path Helper - checkFile: (?) 35 | 36 | ## Solution System ## 37 | 38 | * [ ] 39 | -------------------------------------------------------------------------------- /src/Forge.Core/ZipHelper.fs: -------------------------------------------------------------------------------- 1 | /// This module contains helper function to create and extract zip archives. 2 | module Forge.ZipHelper 3 | 4 | 5 | 6 | open System.IO 7 | open ICSharpCode.SharpZipLib.Zip 8 | open ICSharpCode.SharpZipLib.Core 9 | 10 | 11 | /// Unzips a file with the given file name. 12 | /// ## Parameters 13 | /// - `target` - The target directory. 14 | /// - `fileName` - The file name of the zip file. 15 | let Unzip target (fileName : string) = 16 | use zipFile = new ZipFile(fileName) 17 | for entry in zipFile do 18 | match entry with 19 | | :? ZipEntry as zipEntry -> 20 | let unzipPath = Path.Combine(target, zipEntry.Name) 21 | let directoryPath = Path.GetDirectoryName(unzipPath) 22 | // create directory if needed 23 | if directoryPath.Length > 0 then Directory.CreateDirectory(directoryPath) |> ignore 24 | // unzip the file 25 | let zipStream = zipFile.GetInputStream(zipEntry) 26 | let buffer = Array.create 32768 0uy 27 | if unzipPath.EndsWith "/" |> not then 28 | use unzippedFileStream = File.Create(unzipPath) 29 | StreamUtils.Copy(zipStream, unzippedFileStream, buffer) 30 | | _ -> () 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Expected Behavior 4 | 5 | 6 | 7 | ## Current Behavior 8 | 9 | 10 | 11 | ## Possible Solution 12 | 13 | 14 | 15 | ## Steps to Reproduce (for bugs) 16 | 17 | 18 | 1. 19 | 2. 20 | 3. 21 | 4. 22 | 23 | ## Context 24 | 25 | 26 | 27 | ## Your Environment 28 | 29 | * Version used: 30 | * Environment name and version (F#, .Net): 31 | * Server type and version: 32 | * Operating System and version: -------------------------------------------------------------------------------- /src/Forge.Core/Forge.Core.fsproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | Forge.ProjectSystem.fsproj 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/Forge.Core/Paket.fs: -------------------------------------------------------------------------------- 1 | module Forge.Paket 2 | 3 | open System.IO 4 | 5 | let location = templatesLocation ".paket" 6 | 7 | let getPaketLocation () = 8 | let local = getCwd() ".paket" 9 | if Directory.Exists local then local else paketLocation 10 | 11 | let getPaket () = 12 | getPaketLocation () "paket.exe" 13 | 14 | let copy folder = 15 | folder ".paket" |> Directory.CreateDirectory |> ignore 16 | Directory.GetFiles location 17 | |> Seq.iter (fun x -> 18 | let filename = Path.GetFileName x 19 | File.Copy (x, folder ".paket" filename, true) ) 20 | 21 | 22 | let Update () = 23 | let f = getPaketLocation () 24 | run (f "paket.bootstrapper.exe") "" f 25 | 26 | let Run args = 27 | let f = getPaket () 28 | if not ^ File.Exists f then Update () 29 | let args' = args |> String.concat " " 30 | run f args' ^ getCwd() 31 | 32 | let Init folder = 33 | let paketFolder = folder ".paket" 34 | if Directory.Exists paketFolder then 35 | if File.Exists (paketFolder "paket.exe") |> not then copy folder 36 | 37 | else 38 | copy folder 39 | 40 | if Directory.GetFiles folder |> Seq.exists (fun n -> n.EndsWith "paket.dependencies") |> not then 41 | Update () 42 | Run ["init"] 43 | let deps = folder "paket.dependencies" 44 | File.AppendAllText(deps, "\nframework: auto-detect\n") 45 | 46 | 47 | -------------------------------------------------------------------------------- /src/Forge.Core/GacSearch.fs: -------------------------------------------------------------------------------- 1 | #if INTERACTIVE 2 | #load "Prelude.fs" 3 | #load "Globbing.fs" 4 | #load "TraceListener.fs" 5 | #load "TraceHelper.fs" 6 | #load "FileHelper.fs" 7 | #load "TraceHelper.fs" 8 | open Forge.Prelude 9 | open Forge.FileHelper 10 | open Forge.TraceHelper 11 | #else 12 | module Forge.GacSearch 13 | #endif 14 | 15 | open System 16 | open System.IO 17 | open System.Text.RegularExpressions 18 | 19 | let private assemblyRoots = ["assembly"; @"Microsoft.NET\assembly"] 20 | let private gacDirs = ["GAC"; "GAC_32"; "GAC_64"; "GAC_MSIL"] 21 | let private policyRegex = Regex(@"^policy\.\d+\..+$", RegexOptions.IgnoreCase ||| RegexOptions.Compiled) 22 | 23 | let private subdirs d = Directory.EnumerateDirectories d 24 | 25 | let private getAssemblyDirs roots = 26 | roots 27 | |> Seq.map (combinePathsNoTrim SystemRoot) 28 | |> Seq.collect (fun dir -> gacDirs |> Seq.map (combinePathsNoTrim dir)) // potential root assembly dirs 29 | |> Seq.filter directoryExists 30 | |> Seq.collect subdirs // assembly dirs 31 | |> Seq.filter (fun x -> x.EndsWith(".resources") |> not) 32 | |> Seq.collect subdirs // assembly version dirs 33 | 34 | let private getAssemblyFiles dir = 35 | let strEqual s1 s2 = String.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase) 36 | let isAssemblyFile (f : FileInfo) = strEqual f.Extension ".dll" || strEqual f.Extension ".exe" 37 | let isNotPolicy (f : FileInfo) = policyRegex.IsMatch f.Name |> not 38 | DirectoryInfo dir 39 | |> filesInDir 40 | |> Seq.filter isAssemblyFile 41 | |> Seq.filter isNotPolicy 42 | 43 | let private tryGetAssemblyName (info : FileInfo) = 44 | try 45 | Some ^ System.Reflection.AssemblyName.GetAssemblyName info.FullName 46 | with 47 | | _ as ex -> 48 | log ex.Message 49 | None 50 | 51 | /// Looks for assemblies in GAC 52 | /// Returns AssemblyName instances for all found assemblies except resource and policy assemblies 53 | let searchGac () = 54 | assemblyRoots 55 | |> getAssemblyDirs 56 | |> Seq.collect getAssemblyFiles 57 | |> Seq.choose tryGetAssemblyName 58 | -------------------------------------------------------------------------------- /src/Forge.Core/Git.fs: -------------------------------------------------------------------------------- 1 | module Forge.Git 2 | 3 | open System 4 | 5 | /// Specifies a global timeout for git.exe - default is *no timeout* 6 | let mutable gitTimeOut = TimeSpan.MaxValue 7 | 8 | let private GitPath = @"[ProgramFiles]\Git\cmd\;[ProgramFilesX86]\Git\cmd\;[ProgramFiles]\Git\bin\;[ProgramFilesX86]\Git\bin\;" 9 | 10 | /// Tries to locate the git.exe via the eviroment variable "GIT". 11 | let gitPath = 12 | if isUnix then "git" else 13 | let ev = environVar "GIT" 14 | if not ^ String.isNullOrEmpty ev then ev else 15 | findPath "GitPath" GitPath "git.exe" 16 | 17 | /// Runs git.exe with the given command in the given repository directory. 18 | let runGitCommand repositoryDir command = 19 | let processResult = 20 | ExecProcessAndReturnMessages (fun info -> 21 | info.FileName <- gitPath 22 | info.WorkingDirectory <- repositoryDir 23 | info.Arguments <- command) gitTimeOut 24 | 25 | processResult.OK, processResult.Messages, String.toLines processResult.Errors 26 | 27 | /// [omit] 28 | let runGitCommandf fmt = Printf.ksprintf runGitCommand fmt 29 | 30 | /// Runs the git command and returns the first line of the result. 31 | let runSimpleGitCommand repositoryDir command = 32 | try 33 | let _, msg, errors = runGitCommand repositoryDir command 34 | let errorText = String.toLines msg + Environment.NewLine + errors 35 | if errorText.Contains "fatal: " then failwith errorText 36 | if msg.Count = 0 then "" else 37 | msg |> Seq.iter (logfn "%s") 38 | msg.[0] 39 | with 40 | | exn -> failwithf "Could not run \"git %s\".\r\nError: %s" command exn.Message 41 | 42 | /// Clones a single branch of a git repository. 43 | /// ## Parameters 44 | /// 45 | /// - `workingDir` - The working directory. 46 | /// - `repoUrl` - The URL to the origin. 47 | /// - `branchname` - Specifes the target branch. 48 | /// - `toPath` - Specifes the new target subfolder. 49 | let cloneSingleBranch workingDir repoUrl branchName toPath = 50 | sprintf "clone -b %s --single-branch %s %s" branchName repoUrl toPath 51 | |> runSimpleGitCommand workingDir 52 | |> trace 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Forge/Program.fs: -------------------------------------------------------------------------------- 1 | module Forge.App 2 | 3 | open System 4 | open Argu 5 | open Forge.Commands 6 | 7 | // Console Configuration 8 | Console.Title <- "Forge" 9 | Console.OutputEncoding <- System.Text.Encoding.UTF8 10 | 11 | let defaultForeground = Console.ForegroundColor 12 | let defaultBackground = Console.BackgroundColor 13 | 14 | let red = ConsoleColor.Red 15 | let darkRed = ConsoleColor.DarkRed 16 | let blue = ConsoleColor.Blue 17 | let darkBlue = ConsoleColor.DarkBlue 18 | let darkCyan = ConsoleColor.DarkCyan 19 | let cyan = ConsoleColor.Cyan 20 | let grey = ConsoleColor.Gray 21 | let darkGrey = ConsoleColor.DarkGray 22 | let darkGreen = ConsoleColor.DarkGreen 23 | let darkMagenta = ConsoleColor.DarkMagenta 24 | let green = ConsoleColor.Green 25 | let yellow = ConsoleColor.Yellow 26 | 27 | let parser = ArgumentParser.Create() 28 | 29 | let write color (msg:string) = 30 | Console.ForegroundColor <- color 31 | Console.Write msg 32 | Console.ForegroundColor <- defaultForeground 33 | 34 | let writeln color (msg:string) = 35 | Console.ForegroundColor <- color 36 | Console.WriteLine msg 37 | Console.ForegroundColor <- defaultForeground 38 | 39 | 40 | 41 | let highlight fcol bcol (msg:string) = 42 | Console.ForegroundColor <- fcol 43 | Console.BackgroundColor <- bcol 44 | Console.Write msg 45 | Console.ForegroundColor <- defaultForeground 46 | Console.BackgroundColor <- defaultBackground 47 | 48 | let highlightln fcol bcol (msg:string) = 49 | Console.ForegroundColor <- fcol 50 | Console.BackgroundColor <- bcol 51 | Console.WriteLine msg 52 | Console.ForegroundColor <- defaultForeground 53 | Console.BackgroundColor <- defaultBackground 54 | 55 | 56 | let (|ShouldPrompt|_|) (argv : string []) = 57 | let dirs = System.IO.Directory.EnumerateDirectories Environment.CurrentDirectory 58 | let files = System.IO.Directory.EnumerateFiles Environment.CurrentDirectory 59 | 60 | if argv.Length <> 0 && argv.[argv.Length - 1] = "--no-prompt" then None 61 | elif dirs |> Seq.isEmpty && files |> Seq.isEmpty then None 62 | elif dirs |> Seq.exists(fun d -> d.Contains ".git" || d.Contains ".paket" || d.Contains "packages") then None 63 | elif files |> Seq.exists(fun d -> d.Contains ".sln" || d.Contains ".gitignore" || d.Contains "paket.dependencies") then None 64 | else Some () 65 | 66 | [] 67 | let main argv = 68 | let k = 69 | match argv with 70 | | ShouldPrompt _ -> 71 | writeln yellow "\nForge should be run from solution/repository root. Please ensure you don't run it from folder containing other solutions" 72 | writeln yellow "\nDo You want to continue? [Y/n]" 73 | Console.ReadLine () 74 | | _ -> "" 75 | if k = "Y" || k = "" then 76 | runForge argv 77 | else 78 | 0 -------------------------------------------------------------------------------- /tests/Forge.IntegrationTests/Helpers.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Helpers 3 | 4 | open Forge.ProcessHelper 5 | open Forge.Environment 6 | open Expecto 7 | 8 | let () = Forge.Prelude.() 9 | 10 | let cwd = System.AppDomain.CurrentDomain.BaseDirectory 11 | 12 | let initTest dir args = 13 | let path = cwd ".." "Forge.exe" 14 | let dir = cwd dir 15 | Forge.FileHelper.cleanDir dir 16 | System.Environment.CurrentDirectory <- dir 17 | args |> List.iter (fun a -> 18 | let a = a + " --no-prompt" 19 | Forge.App.main (a.Split ' ') |> ignore) 20 | 21 | let runForgeWithOutput args = 22 | let sw = new System.IO.StringWriter() 23 | System.Console.SetOut(sw) 24 | args |> List.iter (fun a -> 25 | let a = a + " --no-prompt" 26 | Forge.App.main (a.Split ' ') |> ignore) 27 | let so = new System.IO.StreamWriter(System.Console.OpenStandardOutput()) 28 | so.AutoFlush <- true 29 | System.Console.SetOut(so) 30 | sw.ToString() 31 | 32 | 33 | let getPath file = 34 | cwd file 35 | 36 | let loadProject dir = 37 | let dir = cwd dir 38 | Forge.ProjectManager.Furnace.loadFsProject dir 39 | 40 | 41 | let makeAbsolute dir = cwd dir 42 | 43 | module Expect = 44 | let reference (ref : string) (proj : Forge.ProjectManager.ActiveState) = 45 | let res = 46 | proj.ProjectData.References 47 | |> Seq.map (fun r -> r.Include) 48 | 49 | Expect.contains res ref "should contain reference" 50 | 51 | let referenceProject (ref : string) (proj : Forge.ProjectManager.ActiveState) = 52 | let res = 53 | proj.ProjectData.ProjectReferences 54 | |> Seq.map (fun r -> r.Include) 55 | 56 | Expect.contains res ref "should contain project reference" 57 | 58 | 59 | let hasFile (file : string) (proj : Forge.ProjectManager.ActiveState) = 60 | let res = proj.ProjectData.SourceFiles.Files 61 | 62 | Expect.contains res file "should contain file" 63 | 64 | let hasName (name : string) (proj : Forge.ProjectManager.ActiveState) = 65 | let res = proj.ProjectData.Settings.Name.Data.Value 66 | 67 | Expect.equal res name "should have name" 68 | 69 | let notReference (ref : string) (proj : Forge.ProjectManager.ActiveState) = 70 | let res = 71 | proj.ProjectData.References 72 | |> Seq.map (fun r -> r.Include) 73 | 74 | Expect.equal (res |> Seq.tryFind ((=) ref)) None "shouln't contain reference" 75 | 76 | let notReferenceProject (ref : string) (proj : Forge.ProjectManager.ActiveState) = 77 | let res = 78 | proj.ProjectData.ProjectReferences 79 | |> Seq.map (fun r -> r.Include) 80 | Expect.equal (res |> Seq.tryFind ((=) ref)) None "shouln't contain project reference" 81 | 82 | let hasNotFile (file : string) (proj : Forge.ProjectManager.ActiveState) = 83 | let res = proj.ProjectData.SourceFiles.Files 84 | 85 | Expect.equal (res |> Seq.tryFind ((=) file)) None "shouln't contain file" 86 | -------------------------------------------------------------------------------- /RELEASE_NOTES.md: -------------------------------------------------------------------------------- 1 | ### 3.0.0-alpha001 - 2019-03-20 2 | * Port project to .Net Standard 2.0 and to .Net Core 2.1 3 | * Use `dotnet` global tool 4 | 5 | ### 2.2.0 - 2018-01-11 6 | * Add FAKE into build group instead Main group 7 | * Add list projects by folder command 8 | * Add checking validity of renamed files 9 | 10 | ### 2.1.0 - 2017-10-26 11 | * Handle call without and command 12 | * Fix add above/below 13 | * Resolve BadImageFormatException on Mono 14 | * Numbered templates 15 | * Added argument for CopyToOutputDirectory setting 16 | 17 | ### 2.0.0 - 2017-09-13 18 | * **This is not backward compatible change** 19 | * Update ProjectSystem to support SDK based project files 20 | * Update templates to use SDK based project files 21 | * Move templates to https://github.com/fsharp-editing/forge-templates 22 | * Update generated FAKE script 23 | * Infrastructure changes 24 | * Include `.vscode` folder 25 | * Remove interactive mode 26 | * Add support for solution level content in templates (`_content`) 27 | * Multiple small fixes 28 | 29 | ### 1.4.2 - 2017-06-03 30 | * Add support for `FORGE_TEMPLATE_DIR` environment variable 31 | * Fix error when adding a file to a project with no files 32 | 33 | ### 1.4.1 - 2017-03-16 34 | * Fix where path has spaces 35 | * Check validity of new project name and project directory 36 | * Paket.Init function replace Copy 37 | 38 | ### 1.4.0 - 2017-01-21 39 | * Change `update` into `update-version` 40 | 41 | ### 1.3.3 - 2017-01-05 42 | * Make build.sh executable and fix line endings 43 | 44 | ### 1.3.2 - 2016-12-28 45 | * Fix file rename command 46 | 47 | ### 1.3.1 - 2016-12-19 48 | * Fix bug with paths containing whitespaces and `new scaffold` command 49 | 50 | ### 1.3.0 - 2016-12-17 51 | * Add integration with ProjectScaffold 52 | 53 | ### 1.2.1 - 2016-11-28 54 | * Fix release 55 | 56 | ### 1.2.0 - 2016-11-26 57 | * Add aliases 58 | * Fix `new file` when wrong project name given. 59 | 60 | ### 1.1.3 - 2016-11-11 61 | * Add `FSharpTargetsPath` to Project System 62 | 63 | ### 1.1.2 - 2016-11-07 64 | * Add `new solution` command 65 | 66 | ### 1.0.1 - 2016-09-15 67 | * Update help 68 | 69 | ### 1.0.0 - 2016-09-11 70 | * Change command format 71 | * Lot of fixes 72 | * Can break stuff (or might not work) 73 | * Add new templates system 74 | 75 | ### 0.7.0 - 2016-02-22 76 | * Split into core and CLI 77 | * Multiple small fixes 78 | 79 | ### 0.6.0 - 2016-02-14 80 | * Rename to Forge 81 | 82 | ### 0.5.0 - 2016-02-10 83 | * Run local Paket or FAKE if they are present 84 | 85 | ### 0.4.0 - 2016-02-10 86 | * Change applications structure 87 | 88 | ### 0.3.4 - 2016-02-10 89 | * Handle wrong template name 90 | 91 | ### 0.3.3 - 2016-01-27 92 | * Ensure UTF-8 encoding on fsproj files 93 | 94 | ### 0.3.2 - 2016-01-22 95 | * FAKE installed by Paket unless --no-fake flag is sepcified 96 | 97 | ### 0.3.1 - 2016-01-21 98 | * Fix NullReferenceException when Ctr-d is entered in interactive mode 99 | 100 | ### 0.3.0 - 2016-01-19 101 | * Add file ordering command 102 | * Change syntax of new command 103 | * Initialize FAKE on new project creation 104 | 105 | ### 0.2.0 - 2016-01-17 106 | * Add file list command 107 | * Add reference commands 108 | 109 | ### 0.1.0 - 2016-01-17 110 | * Basic templating using F# generator templates 111 | * Paket integration 112 | * FAKE integration 113 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at lambda_factory@outlook.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Please take a moment to review this document in order to make the contribution process easy and effective for everyone involved! 4 | 5 | ## Using the issue tracker 6 | 7 | Use the issues tracker for: 8 | 9 | * [bug reports](#bug-reports) 10 | * [feature requests](#feature-requests) 11 | * [submitting pull requests](#pull-requests) 12 | 13 | Personal support request should be discussed on [F# Software Foundation Slack](https://fsharp.org/guides/slack/). 14 | 15 | ## Bug reports 16 | 17 | A bug is either a _demonstrable problem_ that is caused in Forge failing to provide the expected feature or indicate missing, unclear, or misleading documentation. Good bug reports are extremely helpful - thank you! 18 | ą 19 | Guidelines for bug reports: 20 | 21 | 1. **Use the GitHub issue search** — check if the issue has already been reported. 22 | 23 | 2. **Check if the issue has been fixed** — try to reproduce it using the `master` branch in the repository. 24 | 25 | 3. **Isolate and report the problem** — ideally create a reduced test case. 26 | 27 | Please try to be as detailed as possible in your report. Include information about 28 | your Operating System, as well as your `dotnet`. Please provide steps to 29 | reproduce the issue as well as the outcome you were expecting! All these details 30 | will help developers to fix any potential bugs. 31 | 32 | 33 | ## Feature requests 34 | 35 | Feature requests are welcome and should be discussed on issue tracker. But take a moment to find 36 | out whether your idea fits with the scope and aims of the project. It's up to *you* 37 | to make a strong case to convince the community of the merits of this feature. 38 | Please provide as much detail and context as possible. 39 | 40 | ## Pull requests 41 | 42 | Good pull requests - patches, improvements, new features - are a fantastic 43 | help. They should remain focused in scope and avoid containing unrelated 44 | commits. 45 | 46 | **IMPORTANT**: By submitting a patch, you agree that your work will be 47 | licensed under the license used by the project. 48 | 49 | If you have any large pull request in mind (e.g. implementing features, 50 | refactoring code, etc), **please ask first** otherwise you risk spending 51 | a lot of time working on something that the project's developers might 52 | not want to merge into the project. 53 | 54 | Please adhere to the coding conventions in the project (indentation, 55 | accurate comments, etc.). 56 | 57 | ## How to build and test a local version of Forge 58 | 59 | ### Prerequisites 60 | 61 | - [.NET Core 3.1](https://dotnet.microsoft.com/download) 62 | 63 | ### Building 64 | 65 | Fork, from the github interface https://github.com/ionide/forge 66 | - if you don't use a certificate for committing to github: 67 | ```bash 68 | git clone https://github.com/YOUR_GITHUB_USER/forge.git 69 | ``` 70 | - if you use a certificate for github authentication: 71 | ```bash 72 | git clone git@github.com:YOUR_GITHUB_USER/forge.git 73 | ``` 74 | 75 | #### First time build: 76 | ```bash 77 | cd ionide-forge-fsharp 78 | dotnet tool restore 79 | dotnet fake build 80 | ``` 81 | 82 | If `dotnet restore` gives the error `error MSB4126: The specified solution configuration "Debug|x64" is invalid`, there's a good chance you have the `Platform` environment variable set to "x64". Unset the variable and try the restore command again. 83 | 84 | You can also build project from VSCode with `Ctrl/Cmd + Shift + B`. 85 | 86 | #### Running Forge: 87 | 88 | ```bash 89 | dotnet run --project src/Forge 90 | ``` 91 | 92 | #### Running Tests 93 | 94 | ``` 95 | fake build -t Test 96 | ``` 97 | 98 | Or 99 | 100 | ``` 101 | dotnet run --project tests/Forge.Tests 102 | ``` 103 | 104 | #### Debugging 105 | 106 | Debugging Forge or Forge.Tests is possible with VSCode - choose appropriate target in VSCode debug panel and press `F5` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | PortabilityAnalysis*.html 2 | 3 | ## Ignore Visual Studio temporary files, build results, and 4 | ## files generated by popular Visual Studio add-ons. 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.sln.docstates 10 | 11 | # Xamarin Studio / monodevelop user-specific 12 | *.userprefs 13 | *.dll.mdb 14 | *.exe.mdb 15 | 16 | # Build results 17 | 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | build/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | src/*/[Bb]in/ 25 | src/*/[Oo]bj/ 26 | src/*/Scratch/ 27 | 28 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 29 | !packages/*/build/ 30 | 31 | # MSTest test Results 32 | [Tt]est[Rr]esult*/ 33 | [Bb]uild[Ll]og.* 34 | 35 | *_i.c 36 | *_p.c 37 | *.ilk 38 | *.meta 39 | *.obj 40 | *.pch 41 | *.pdb 42 | *.pgc 43 | *.pgd 44 | *.rsp 45 | *.sbr 46 | *.tlb 47 | *.tli 48 | *.tlh 49 | *.tmp 50 | *.tmp_proj 51 | *.log 52 | *.vspscc 53 | *.vssscc 54 | .builds 55 | *.pidb 56 | *.log 57 | *.scc 58 | 59 | # Visual C++ cache files 60 | ipch/ 61 | *.aps 62 | *.ncb 63 | *.opensdf 64 | *.sdf 65 | *.cachefile 66 | 67 | # Visual Studio profiler 68 | *.psess 69 | *.vsp 70 | *.vspx 71 | 72 | # Other Visual Studio data 73 | .vs/ 74 | 75 | # Guidance Automation Toolkit 76 | *.gpState 77 | 78 | # ReSharper is a .NET coding add-in 79 | _ReSharper*/ 80 | *.[Rr]e[Ss]harper 81 | 82 | # TeamCity is a build add-in 83 | _TeamCity* 84 | 85 | # DotCover is a Code Coverage Tool 86 | *.dotCover 87 | 88 | # NCrunch 89 | *.ncrunch* 90 | .*crunch*.local.xml 91 | 92 | # Installshield output folder 93 | [Ee]xpress/ 94 | 95 | # DocProject is a documentation generator add-in 96 | DocProject/buildhelp/ 97 | DocProject/Help/*.HxT 98 | DocProject/Help/*.HxC 99 | DocProject/Help/*.hhc 100 | DocProject/Help/*.hhk 101 | DocProject/Help/*.hhp 102 | DocProject/Help/Html2 103 | DocProject/Help/html 104 | 105 | # Click-Once directory 106 | publish/ 107 | 108 | # Publish Web Output 109 | *.Publish.xml 110 | 111 | # Enable nuget.exe in the .nuget folder (though normally executables are not tracked) 112 | !.nuget/NuGet.exe 113 | 114 | # Windows Azure Build Output 115 | csx 116 | *.build.csdef 117 | 118 | # Windows Store app package directory 119 | AppPackages/ 120 | 121 | # Others 122 | sql/ 123 | *.Cache 124 | ClientBin/ 125 | [Ss]tyle[Cc]op.* 126 | ~$* 127 | *~ 128 | *.dbmdl 129 | *.[Pp]ublish.xml 130 | *.pfx 131 | *.publishsettings 132 | 133 | # RIA/Silverlight projects 134 | Generated_Code/ 135 | 136 | # Backup & report files from converting an old project file to a newer 137 | # Visual Studio version. Backup files are not needed, because we have git ;-) 138 | _UpgradeReport_Files/ 139 | Backup*/ 140 | UpgradeLog*.XML 141 | UpgradeLog*.htm 142 | 143 | # SQL Server files 144 | App_Data/*.mdf 145 | App_Data/*.ldf 146 | 147 | 148 | #LightSwitch generated files 149 | GeneratedArtifacts/ 150 | _Pvt_Extensions/ 151 | ModelManifest.xml 152 | 153 | # ========================= 154 | # Windows detritus 155 | # ========================= 156 | 157 | # Windows image file caches 158 | Thumbs.db 159 | ehthumbs.db 160 | 161 | # Folder config file 162 | Desktop.ini 163 | 164 | # Recycle Bin used on file shares 165 | $RECYCLE.BIN/ 166 | 167 | # Mac desktop service store files 168 | .DS_Store 169 | 170 | # =================================================== 171 | # Exclude F# project specific directories and files 172 | # =================================================== 173 | 174 | # NuGet Packages Directory 175 | packages/ 176 | 177 | # Generated documentation folder 178 | docs/output/ 179 | docs/tools/XmlWriter 180 | *.svclog 181 | 182 | # Temp folder used for publishing docs 183 | temp/ 184 | 185 | # Test results produced by build 186 | TestResults.xml 187 | TestResult.xml 188 | 189 | # Nuget outputs 190 | nuget/*.nupkg 191 | release.cmd 192 | release.sh 193 | localpackages/ 194 | paket-files 195 | *.orig 196 | docs/content/license.md 197 | docs/content/release-notes.md 198 | .fake 199 | .vscode 200 | .ionide 201 | out/ 202 | AssemblyInfo.fs -------------------------------------------------------------------------------- /.paket/paket.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | true 7 | $(MSBuildThisFileDirectory) 8 | $(MSBuildThisFileDirectory)..\ 9 | $(PaketRootPath)paket.lock 10 | $(PaketRootPath)paket-files\paket.restore.cached 11 | /Library/Frameworks/Mono.framework/Commands/mono 12 | mono 13 | 14 | 15 | 16 | 17 | $(PaketRootPath)paket.exe 18 | $(PaketToolsPath)paket.exe 19 | "$(PaketExePath)" 20 | $(MonoPath) --runtime=v4.0.30319 "$(PaketExePath)" 21 | 22 | 23 | 24 | 25 | 26 | $(MSBuildProjectFullPath).paket.references 27 | 28 | 29 | 30 | 31 | $(MSBuildProjectDirectory)\$(MSBuildProjectName).paket.references 32 | 33 | 34 | 35 | 36 | $(MSBuildProjectDirectory)\paket.references 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | $(PaketCommand) restore --references-file "$(PaketReferences)" 49 | 50 | RestorePackages; $(BuildDependsOn); 51 | 52 | 53 | 54 | true 55 | 56 | 57 | 58 | $([System.IO.File]::ReadAllText('$(PaketRestoreCacheFile)')) 59 | $([System.IO.File]::ReadAllText('$(PaketLockFilePath)')) 60 | true 61 | false 62 | true 63 | 64 | 65 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/Forge.Core/TraceListener.fs: -------------------------------------------------------------------------------- 1 | [] 2 | /// Defines default listeners for build output traces 3 | module Forge.TraceListener 4 | 5 | open System 6 | 7 | /// Defines Tracing information for TraceListeners 8 | type TraceData = 9 | | StartMessage 10 | | ImportantMessage of msg:string 11 | | ErrorMessage of msg:string 12 | | WarningMessage of msg:string * newline:bool 13 | | LogMessage of msg:string * newline:bool 14 | | TraceMessage of msg:string * newLine:bool 15 | | FinishedMessage 16 | | OpenTag of tag:string * name:string 17 | | CloseTag of tag:string 18 | member x.NewLine = 19 | match x with 20 | | ImportantMessage _ 21 | | WarningMessage _ 22 | | ErrorMessage _ -> Some true 23 | | LogMessage (_, newLine) 24 | | TraceMessage (_, newLine) -> Some newLine 25 | | StartMessage 26 | | FinishedMessage 27 | | OpenTag _ 28 | | CloseTag _ -> None 29 | member x.Message = 30 | match x with 31 | | ImportantMessage text 32 | | ErrorMessage text 33 | | WarningMessage (text, _) 34 | | LogMessage (text, _) 35 | | TraceMessage (text, _) -> Some text 36 | | StartMessage 37 | | FinishedMessage 38 | | OpenTag _ 39 | | CloseTag _ -> None 40 | 41 | /// Defines a TraceListener interface 42 | type ITraceListener = 43 | abstract Write : TraceData -> unit 44 | 45 | /// A default color map which maps TracePriorities to ConsoleColors 46 | let colorMap traceData = 47 | match traceData with 48 | | ImportantMessage _ -> ConsoleColor.Yellow 49 | | ErrorMessage _ -> ConsoleColor.Red 50 | | WarningMessage _ -> ConsoleColor.DarkCyan 51 | | LogMessage _ -> ConsoleColor.Gray 52 | | TraceMessage _ -> ConsoleColor.Green 53 | | FinishedMessage -> ConsoleColor.White 54 | | _ -> ConsoleColor.Gray 55 | 56 | /// Implements a TraceListener for System.Console. 57 | /// ## Parameters 58 | /// - `importantMessagesToStdErr` - Defines whether to trace important messages to StdErr. 59 | /// - `colorMap` - A function which maps TracePriorities to ConsoleColors. 60 | type ConsoleTraceListener(importantMessagesToStdErr, colorMap) = 61 | 62 | let writeText toStdErr color newLine text = 63 | let curColor = Console.ForegroundColor 64 | try 65 | if curColor <> color then Console.ForegroundColor <- color 66 | let printer = 67 | match toStdErr, newLine with 68 | | true, true -> eprintfn 69 | | true, false -> eprintf 70 | | false, true -> printfn 71 | | false, false -> printf 72 | printer "%s" text 73 | finally 74 | if curColor <> color then Console.ForegroundColor <- curColor 75 | 76 | interface ITraceListener with 77 | /// Writes the given message to the Console. 78 | member this.Write msg = 79 | let color = colorMap msg 80 | match msg with 81 | | StartMessage -> () 82 | | OpenTag _ -> () 83 | | CloseTag _ -> () 84 | | ImportantMessage text 85 | | ErrorMessage text -> 86 | //writeText false color true text 87 | writeText importantMessagesToStdErr color true text 88 | | WarningMessage (text, newLine) 89 | | LogMessage (text, newLine) 90 | | TraceMessage (text, newLine) -> 91 | writeText false color newLine text 92 | | FinishedMessage -> () 93 | 94 | //// If we write the stderr on those build servers the build will fail. 95 | //let importantMessagesToStdErr = buildServer <> CCNet && buildServer <> AppVeyor 96 | 97 | /// The default TraceListener for Console. 98 | let defaultConsoleTraceListener = 99 | // ConsoleTraceListener(importantMessagesToStdErr, colorMap) 100 | ConsoleTraceListener(false, colorMap) 101 | 102 | 103 | /// A List with all registered listeners 104 | let listeners = new Collections.Generic.List() 105 | 106 | 107 | // register listeners 108 | listeners.Add defaultConsoleTraceListener 109 | 110 | 111 | /// Allows to post messages to all trace listeners 112 | let postMessage x = listeners.ForEach(fun listener -> listener.Write x) 113 | 114 | 115 | -------------------------------------------------------------------------------- /src/Forge.Core/Environment.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.Environment 3 | 4 | open System 5 | open System.IO 6 | open System.Diagnostics 7 | 8 | 9 | // Environment Helpers 10 | //======================================================= 11 | 12 | /// Retrieves the environment variable with the given name 13 | let environVar name = Environment.GetEnvironmentVariable name 14 | 15 | let environVarOrNone name = 16 | let var = environVar name 17 | if String.IsNullOrEmpty var then None 18 | else Some var 19 | 20 | /// Splits the entries of an environment variable and removes the empty ones. 21 | let splitEnvironVar name = 22 | let var = environVarOrNone name 23 | if var = None then [ ] 24 | else var.Value.Split [| Path.PathSeparator |] |> Array.toList 25 | 26 | /// The system root environment variable. Typically "C:\Windows" 27 | let SystemRoot = environVar "SystemRoot" 28 | 29 | /// Determines if the current system is an Unix system 30 | let isUnix = Environment.OSVersion.Platform = PlatformID.Unix 31 | 32 | /// Determines if the current system is a MacOs system 33 | let isMacOS = 34 | (Environment.OSVersion.Platform = PlatformID.MacOSX) || 35 | // osascript is the AppleScript interpreter on OS X 36 | File.Exists "/usr/bin/osascript" 37 | 38 | /// Determines if the current system is a Linux system 39 | let isLinux = int System.Environment.OSVersion.Platform |> fun p -> p=4 || p=6 || p=128 40 | 41 | /// Determines if the current system is a mono system 42 | /// Todo: Detect mono on windows 43 | let isMono = isLinux || isUnix || isMacOS 44 | 45 | let monoPath = 46 | if isMacOS && File.Exists "/Library/Frameworks/Mono.framework/Commands/mono" then 47 | "/Library/Frameworks/Mono.framework/Commands/mono" 48 | else 49 | "mono" 50 | 51 | /// The path of the "Program Files" folder - might be x64 on x64 machine 52 | let ProgramFiles = Environment.GetFolderPath Environment.SpecialFolder.ProgramFiles 53 | 54 | /// The path of Program Files (x86) 55 | /// It seems this covers all cases where PROCESSOR\_ARCHITECTURE may misreport and the case where the other variable 56 | /// PROCESSOR\_ARCHITEW6432 can be null 57 | let ProgramFilesX86 = 58 | let wow64 = environVar "PROCESSOR_ARCHITEW6432" 59 | let globalArch = environVar "PROCESSOR_ARCHITECTURE" 60 | match wow64, globalArch with 61 | | "AMD64", "AMD64" 62 | | null, "AMD64" 63 | | "x86", "AMD64" -> environVar "ProgramFiles(x86)" 64 | | _ -> environVar "ProgramFiles" 65 | |> fun detected -> if detected = null then @"C:\Program Files (x86)\" else detected 66 | 67 | 68 | /// Returns if the build parameter with the given name was set 69 | let inline hasBuildParam name = environVar name <> null 70 | 71 | /// Type alias for System.EnvironmentVariableTarget 72 | type EnvironTarget = EnvironmentVariableTarget 73 | 74 | /// Retrieves all environment variables from the given target 75 | let environVars target = 76 | [ for e in Environment.GetEnvironmentVariables target -> 77 | let e1 = e :?> Collections.DictionaryEntry 78 | e1.Key, e1.Value ] 79 | 80 | 81 | // Environment Config 82 | //==================================================== 83 | 84 | let exeLocation = 85 | try 86 | System.Reflection.Assembly.GetEntryAssembly().Location |> Path.GetDirectoryName 87 | with 88 | | _ -> "" 89 | 90 | let templatesLocation = 91 | match Environment.GetEnvironmentVariable "FORGE_TEMPLATE_DIR" with 92 | | null -> exeLocation "templates" 93 | | dir -> dir 94 | 95 | let getCwd () = System.Environment.CurrentDirectory 96 | let getPackagesDirectory () = getCwd() "packages" 97 | 98 | let paketLocation = exeLocation "Tools" "Paket" 99 | let fakeLocation = exeLocation "Tools" "FAKE" 100 | let fakeToolLocation = fakeLocation "tools" 101 | 102 | let filesLocation = templatesLocation ".files" 103 | let templateFile = templatesLocation "templates.json" 104 | 105 | let relative (path1 : string) (path2 : string) = 106 | let path1 = if Path.IsPathRooted path1 then path1 else getCwd() path1 107 | let path2 = if Path.IsPathRooted path2 then path2 else getCwd() path2 108 | 109 | let p1, p2 = Uri path1, Uri path2 110 | Uri.UnescapeDataString( 111 | p2.MakeRelativeUri(p1) 112 | .ToString() 113 | .Replace('/', Path.DirectorySeparatorChar) 114 | ) 115 | -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/Constants.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.Constants 3 | 4 | // Common Constants 5 | let [] Name = "Name" 6 | let [] None = "None" 7 | let [] Reference = "Reference" 8 | let [] PackageReference = "PackageReference" 9 | let [] DotNetCliToolReference = "DotNetCliToolReference" 10 | 11 | 12 | // Platform Constants 13 | 14 | let [] X86 = "x86" 15 | let [] X64 = "x64" 16 | let [] AnyCPU = "AnyCPU" 17 | let [] PlatformVar = "$(Platform)" 18 | 19 | // BuildAction Constants 20 | 21 | let [] Compile = "Compile" 22 | let [] Content = "Content" 23 | let [] Resource = "Resource" 24 | let [] EmbeddedResource = "EmbeddedResource" 25 | 26 | // CopyToOutputDirectory Constants 27 | let [] Never = "Never" 28 | let [] Always = "Always" 29 | let [] PreserveNewest = "PreserveNewest" 30 | 31 | // DebugType Constants 32 | 33 | let [] PdbOnly = "PdbOnly" 34 | let [] Full = "Full" 35 | 36 | 37 | // OutputType Constants 38 | let [] Exe = "Exe" 39 | let [] Winexe = "Winexe" 40 | let [] Library = "Library" 41 | let [] Module = "Module" 42 | 43 | 44 | // XML Attribute Name Constants 45 | 46 | let [] DefaultTargets = "DefaultTargets" 47 | let [] ToolsVersion = "ToolsVersion" 48 | let [] Sdk = "Sdk" 49 | 50 | let [] Include = "Include" 51 | let [] Condition = "Condition" 52 | 53 | // MSBuild XML Element Constants 54 | 55 | let [] Project = "Project" 56 | let [] ItemGroup = "ItemGroup" 57 | let [] PropertyGroup = "PropertyGroup" 58 | let [] ProjectReference = "ProjectReference" 59 | 60 | 61 | // XML Property Constants (found in PropertyGroups) 62 | 63 | let [] AssemblyName = "AssemblyName" 64 | let [] RootNamespace = "RootNamespace" 65 | let [] Configuration = "Configuration" 66 | let [] Platform = "Platform" 67 | let [] SchemaVersion = "SchemaVersion" 68 | let [] ProjectGuid = "ProjectGuid" 69 | let [] ProjectType = "ProjectType" 70 | let [] OutputType = "OutputType" 71 | let [] TargetFramework = "TargetFramework" 72 | let [] TargetFrameworks = "TargetFrameworks" 73 | let [] TargetFrameworkVersion = "TargetFrameworkVersion" 74 | let [] TargetFrameworkProfile = "TargetFrameworkProfile" 75 | let [] AutoGenerateBindingRedirects = "AutoGenerateBindingRedirects" 76 | let [] TargetFSharpCoreVersion = "TargetFSharpCoreVersion" 77 | let [] DebugSymbols = "DebugSymbols" 78 | let [] DebugType = "DebugType" 79 | let [] Optimize = "Optimize" 80 | let [] Tailcalls = "Tailcalls" 81 | let [] OutputPath = "OutputPath" 82 | let [] CompilationConstants = "DefineConstants" 83 | let [] WarningLevel = "WarningLevel" 84 | let [] PlatformTarget = "PlatformTarget" 85 | let [] DocumentationFile = "DocumentationFile" 86 | let [] Prefer32Bit = "Prefer32Bit" 87 | let [] OtherFlags = "OtherFlags" 88 | 89 | 90 | // XML Elements 91 | 92 | let [] CopyToOutputDirectory = "CopyToOutputDirectory" 93 | let [] HintPath = "HintPath" 94 | let [] Private = "Private" 95 | let [] SpecificVersion = "SpecificVersion" 96 | let [] Link = "Link" 97 | let [] Paket = "Paket" 98 | let [] FSharpTargetsPath = "FSharpTargetsPath" 99 | let [] Version = "Version" 100 | let [] PrivateAssets = "PrivateAssets" 101 | 102 | 103 | let [] XmlDecl = @"" 104 | let [] Xmlns = "http://schemas.microsoft.com/developer/msbuild/2003" 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Project is deprecated - read more in [#135](https://github.com/ionide/Forge/issues/135)] 2 | 3 | # Forge (F# Project Builder) 4 | 5 | Forge is a command line tool that provides tasks for creating and manipulation F# projects. For more documentation please visit [our wiki pages](https://github.com/ionide/Forge/wiki). 6 | 7 | 8 | Forge is part of Ionide tooling suite - You can support its development on [Open Collective](https://opencollective.com/ionide). 9 | 10 | [![open collective backers](https://img.shields.io/opencollective/backers/ionide.svg?color=blue)](https://opencollective.com/ionide) 11 | [![open collective sponsors](https://img.shields.io/opencollective/sponsors/ionide.svg?color=blue)](https://opencollective.com/ionide) 12 | 13 | [![Open Collective](https://opencollective.com/ionide/donate/button.png?color=blue)](https://opencollective.com/ionide) 14 | 15 | ## How to contribute 16 | 17 | *Imposter syndrome disclaimer*: I want your help. No really, I do. 18 | 19 | There might be a little voice inside that tells you you're not ready; that you need to do one more tutorial, or learn another framework, or write a few more blog posts before you can help me with this project. 20 | 21 | I assure you, that's not the case. 22 | 23 | This project has some clear Contribution Guidelines and expectations that you can [read here](https://github.com/ionide/forge/blob/master/CONTRIBUTING.md). 24 | 25 | The contribution guidelines outline the process that you'll need to follow to get a patch merged. By making expectations and process explicit, I hope it will make it easier for you to contribute. 26 | 27 | And you don't just have to write code. You can help out by writing documentation, tests, or even by giving feedback about this work. (And yes, that includes giving feedback about the contribution guidelines.) 28 | 29 | Thank you for contributing! 30 | 31 | ## Contributing and copyright 32 | 33 | The project is hosted on [GitHub](https://github.com/fsharp-editing/Forge) where you can [report issues](https://github.com/fsharp-editing/Forge/issues), fork 34 | the project and submit pull requests. 35 | 36 | The library is available under [unlicense](https://github.com/fsharp-editing/Forge/blob/master/LICENSE.md), which allows modification and redistribution for both commercial and non-commercial purposes. 37 | 38 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 39 | 40 | ## Maintainer(s) 41 | 42 | - [Krzysztof-Cieslak](https://github.com/Krzysztof-Cieslak) 43 | 44 | #### Past Maintainer(s) 45 | 46 | - [cloudRoutine](https://github.com/cloudRoutine/) 47 | - [@ReidNEvans](https://twitter.com/reidNEvans) 48 | 49 | ## Sponsors 50 | 51 | Forge couldn't be created without support of [Lambda Factory](https://lambdafactory.io). If your company would be interested in supporting development of Forge, or acquiring commercial support sent us email - lambda_factory@outlook.com 52 | 53 | Forge is part of Ionide tooling suite - You can also support its development on [Open Collective](https://opencollective.com/ionide). 54 | 55 | [![Open Collective](https://opencollective.com/ionide/donate/button.png?color=blue)](https://opencollective.com/ionide) 56 | 57 | ### Partners 58 | 59 |
60 | 61 | drawing 62 | 63 |
64 | 65 | ### Sponsors 66 | 67 | [Become a sponsor](https://opencollective.com/ionide) and get your logo on our README on Github, description in VSCode marketplace and on [ionide.io](http://ionide.io) with a link to your site. 68 | 69 |
70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /tests/Forge.Tests/common.fs: -------------------------------------------------------------------------------- 1 | module Common 2 | 3 | open Expecto 4 | 5 | 6 | 7 | let cleanup (text : string) = 8 | match text.Contains "\r\n" with 9 | | true -> text 10 | | false -> text.Replace("\n", "\r\n") 11 | 12 | module Expect = 13 | let shouldbetext expected actual = 14 | let cleanupExpected = cleanup expected 15 | let cleanupActual = cleanup actual 16 | 17 | Expect.equal cleanupActual cleanupExpected "should be same after cleanup" 18 | 19 | let equivalent expected actual = 20 | Expect.containsAll actual expected "should contain all" 21 | 22 | let hasLength' length xs = 23 | Expect.equal (xs |> Seq.length) length "should have given length" 24 | 25 | let astInput = 26 | """ 27 | 28 | 29 | Debug 30 | AnyCPU 31 | 2.0 32 | fbaf8c7b-4eda-493a-a7fe-4db25d15736f 33 | Library 34 | Test 35 | Test 36 | v4.5 37 | 4.3.0.0 38 | Test 39 | bin\Debug\Test.XML 40 | 41 | 42 | 43 | 44 | True 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | """ 57 | 58 | 59 | let projectWithoutFiles = """ 60 | 61 | 62 | 63 | 64 | True 65 | 66 | 67 | 68 | 69 | 70 | 71 | """ 72 | 73 | let projectWithFiles = """ 74 | 75 | 76 | 77 | 78 | True 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | """ 91 | 92 | let projectWithLinkedFiles = """ 93 | 94 | 95 | 96 | 97 | True 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | linked/ext/external.fs 113 | 114 | 115 | 116 | """ 117 | 118 | let netCoreProjectMultiTargetsNoFiles = """ 119 | 120 | 121 | Library 122 | net461;netstandard2.0;netcoreapp2.0 123 | 124 | 125 | """ -------------------------------------------------------------------------------- /Forge.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 14 3 | VisualStudioVersion = 14.0.25420.1 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".paket", ".paket", "{63297B98-5CED-492C-A5B7-A5B4F73CF142}" 6 | ProjectSection(SolutionItems) = preProject 7 | paket.dependencies = paket.dependencies 8 | paket.lock = paket.lock 9 | EndProjectSection 10 | EndProject 11 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "project", "project", "{BF60BC93-E09B-4E5F-9D85-95A519479D54}" 14 | ProjectSection(SolutionItems) = preProject 15 | .gitignore = .gitignore 16 | .travis.yml = .travis.yml 17 | appveyor.yml = appveyor.yml 18 | build.cmd = build.cmd 19 | build.fsx = build.fsx 20 | build.sh = build.sh 21 | build_docs.fsx = build_docs.fsx 22 | LICENSE.txt = LICENSE.txt 23 | README.md = README.md 24 | Reference.md = Reference.md 25 | RELEASE_NOTES.md = RELEASE_NOTES.md 26 | EndProjectSection 27 | EndProject 28 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{83F16175-43B1-4C90-A1EE-8E351C33435D}" 29 | ProjectSection(SolutionItems) = preProject 30 | docs\tools\generate.fsx = docs\tools\generate.fsx 31 | docs\tools\templates\template.cshtml = docs\tools\templates\template.cshtml 32 | EndProjectSection 33 | EndProject 34 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "content", "content", "{8E6D5255-776D-4B61-85F9-73C37AA1FB9A}" 35 | ProjectSection(SolutionItems) = preProject 36 | docs\content\index.fsx = docs\content\index.fsx 37 | docs\content\tutorial.fsx = docs\content\tutorial.fsx 38 | EndProjectSection 39 | EndProject 40 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{ED8079DD-2B06-4030-9F0F-DC548F98E1C4}" 41 | ProjectSection(SolutionItems) = preProject 42 | tests\TestCoverage.md = tests\TestCoverage.md 43 | EndProjectSection 44 | EndProject 45 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Forge.Tests", "tests\Forge.Tests\Forge.Tests.fsproj", "{FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F}" 46 | EndProject 47 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runners", "runners", "{0153573D-2FC1-4AFF-8D40-992ACF229E2F}" 48 | ProjectSection(SolutionItems) = preProject 49 | runners\forge.cmd = runners\forge.cmd 50 | runners\forge.sh = runners\forge.sh 51 | EndProjectSection 52 | EndProject 53 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Forge.Core", "src\Forge.Core\Forge.Core.fsproj", "{D28CE980-2040-4B62-ACA6-F07EB6B31920}" 54 | EndProject 55 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Forge", "src\Forge\Forge.fsproj", "{147B0E3C-C669-4666-8FBC-7F77CAC2FF36}" 56 | EndProject 57 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Forge.IntegrationTests", "tests\Forge.IntegrationTests\Forge.IntegrationTests.fsproj", "{43323D7C-A0B8-4451-ADD1-DAD8A062CB86}" 58 | EndProject 59 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Forge.ProjectSystem", "src\Forge.ProjectSystem\Forge.ProjectSystem.fsproj", "{9029BE0F-A72C-4281-9FD1-AA15F5722306}" 60 | EndProject 61 | Global 62 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 63 | Debug|Any CPU = Debug|Any CPU 64 | Release|Any CPU = Release|Any CPU 65 | EndGlobalSection 66 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 67 | {FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 68 | {FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F}.Debug|Any CPU.Build.0 = Debug|Any CPU 69 | {FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F}.Release|Any CPU.ActiveCfg = Release|Any CPU 70 | {FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F}.Release|Any CPU.Build.0 = Release|Any CPU 71 | {D28CE980-2040-4B62-ACA6-F07EB6B31920}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 72 | {D28CE980-2040-4B62-ACA6-F07EB6B31920}.Debug|Any CPU.Build.0 = Debug|Any CPU 73 | {D28CE980-2040-4B62-ACA6-F07EB6B31920}.Release|Any CPU.ActiveCfg = Release|Any CPU 74 | {D28CE980-2040-4B62-ACA6-F07EB6B31920}.Release|Any CPU.Build.0 = Release|Any CPU 75 | {147B0E3C-C669-4666-8FBC-7F77CAC2FF36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 76 | {147B0E3C-C669-4666-8FBC-7F77CAC2FF36}.Debug|Any CPU.Build.0 = Debug|Any CPU 77 | {147B0E3C-C669-4666-8FBC-7F77CAC2FF36}.Release|Any CPU.ActiveCfg = Release|Any CPU 78 | {147B0E3C-C669-4666-8FBC-7F77CAC2FF36}.Release|Any CPU.Build.0 = Release|Any CPU 79 | {43323D7C-A0B8-4451-ADD1-DAD8A062CB86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 80 | {43323D7C-A0B8-4451-ADD1-DAD8A062CB86}.Debug|Any CPU.Build.0 = Debug|Any CPU 81 | {43323D7C-A0B8-4451-ADD1-DAD8A062CB86}.Release|Any CPU.ActiveCfg = Release|Any CPU 82 | {43323D7C-A0B8-4451-ADD1-DAD8A062CB86}.Release|Any CPU.Build.0 = Release|Any CPU 83 | {9029BE0F-A72C-4281-9FD1-AA15F5722306}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 84 | {9029BE0F-A72C-4281-9FD1-AA15F5722306}.Debug|Any CPU.Build.0 = Debug|Any CPU 85 | {9029BE0F-A72C-4281-9FD1-AA15F5722306}.Release|Any CPU.ActiveCfg = Release|Any CPU 86 | {9029BE0F-A72C-4281-9FD1-AA15F5722306}.Release|Any CPU.Build.0 = Release|Any CPU 87 | EndGlobalSection 88 | GlobalSection(SolutionProperties) = preSolution 89 | HideSolutionNode = FALSE 90 | EndGlobalSection 91 | GlobalSection(NestedProjects) = preSolution 92 | {83F16175-43B1-4C90-A1EE-8E351C33435D} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 93 | {8E6D5255-776D-4B61-85F9-73C37AA1FB9A} = {A6A6AF7D-D6E3-442D-9B1E-58CC91879BE1} 94 | {FBAF8C7B-4EDA-493A-A7FE-4DB25D15736F} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} 95 | {43323D7C-A0B8-4451-ADD1-DAD8A062CB86} = {ED8079DD-2B06-4030-9F0F-DC548F98E1C4} 96 | EndGlobalSection 97 | EndGlobal 98 | -------------------------------------------------------------------------------- /Reference.md: -------------------------------------------------------------------------------- 1 | ## Solution File Reference 2 | 3 | [The differences between Solution build configurations and Project build configurations](http://jimmyscorner.com/archives/51/the-differences-between-solution-build-configurations-and-project-build-configurations) 4 | 5 | 6 | ## Visual Studio ProjectType GUIDs 7 | 8 | 9 | |:----------------------------------------------|:--------------------------------------:| 10 | | ASP.NET MVC 1 | {603C0E0B-DB56-11DC-BE95-000D561079B0} | 11 | | ASP.NET MVC 2 | {F85E285D-A4E0-4152-9332-AB1D724D3325} | 12 | | ASP.NET MVC 3 | {E53F8FEA-EAE0-44A6-8774-FFD645390401} | 13 | | ASP.NET MVC 4 | {E3E379DF-F4C6-4180-9B81-6769533ABE47} | 14 | | ASP.NET MVC 5 | {349C5851-65DF-11DA-9384-00065B846F21} | 15 | | C# | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} | 16 | | C++ | {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} | 17 | | Database | {A9ACE9BB-CECE-4E62-9AA4-C7E7C5BD2124} | 18 | | Database (other project types) | {4F174C21-8C12-11D0-8340-0000F80270F8} | 19 | | Deployment Cab | {3EA9E505-35AC-4774-B492-AD1749C4943A} | 20 | | Deployment Merge Module | {06A35CCD-C46D-44D5-987B-CF40FF872267} | 21 | | Deployment Setup | {978C614F-708E-4E1A-B201-565925725DBA} | 22 | | Deployment Smart Device Cab | {AB322303-2255-48EF-A496-5904EB18DA55} | 23 | | Distributed System | {F135691A-BF7E-435D-8960-F99683D2D49C} | 24 | | Dynamics 2012 AX C# in AOT | {BF6F8E12-879D-49E7-ADF0-5503146B24B8} | 25 | | F# | {F2A71F9B-5D33-465A-A702-920D77279786} | 26 | | J# | {E6FDF86B-F3D1-11D4-8576-0002A516ECE8} | 27 | | Legacy (2003) Smart Device (C#) | {20D4826A-C6FA-45DB-90F4-C717570B9F32} | 28 | | Legacy (2003) Smart Device (VB.NET) | {CB4CE8C6-1BDB-4DC7-A4D3-65A1999772F8} | 29 | | Model-View-Controller v2 (MVC 2) | {F85E285D-A4E0-4152-9332-AB1D724D3325} | 30 | | Model-View-Controller v3 (MVC 3) | {E53F8FEA-EAE0-44A6-8774-FFD645390401} | 31 | | Model-View-Controller v4 (MVC 4) | {E3E379DF-F4C6-4180-9B81-6769533ABE47} | 32 | | Model-View-Controller v5 (MVC 5) | {349C5851-65DF-11DA-9384-00065B846F21} | 33 | | Mono for Android | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF} | 34 | | MonoTouch | {6BC8ED88-2882-458C-8E55-DFD12B67127B} | 35 | | MonoTouch Binding | {F5B4F3BC-B597-4E2B-B552-EF5D8A32436F} | 36 | | Portable Class Library | {786C830F-07A1-408B-BD7F-6EE04809D6DB} | 37 | | SharePoint (C#) | {593B0543-81F6-4436-BA1E-4747859CAAE2} | 38 | | SharePoint (VB.NET) | {EC05E597-79D4-47f3-ADA0-324C4F7C7484} | 39 | | SharePoint Workflow | {F8810EC1-6754-47FC-A15F-DFABD2E3FA90} | 40 | | Silverlight | {A1591282-1198-4647-A2B1-27E5FF5F6F3B} | 41 | | Smart Device (C#) | {4D628B5B-2FBC-4AA6-8C16-197242AEB884} | 42 | | Smart Device (VB.NET) | {68B1623D-7FB9-47D8-8664-7ECEA3297D4F} | 43 | | Solution Folder | {2150E333-8FDC-42A3-9474-1A3956D46DE8} | 44 | | Test | {3AC096D0-A1C2-E12C-1390-A8335801FDAB} | 45 | | VB.NET | {F184B08F-C81C-45F6-A57F-5ABD9991F28F} | 46 | | Visual Database Tools | {C252FEB5-A946-4202-B1D4-9916A0590387} | 47 | | Visual Studio Extensibility (VSIX) | {82B43B9B-A64C-4715-B499-D71E9CA2BD60} | 48 | | Visual Studio Tools for Applications (VSTA) | {A860303F-1F3F-4691-B57E-529FC101A107} | 49 | | Visual Studio Tools for Office (VSTO) | {BAA0C2D2-18E2-41B9-852F-F413020CAA33} | 50 | | Web Application | {349C5851-65DF-11DA-9384-00065B846F21} | 51 | | Web Site | {E24C65DC-7377-472B-9ABA-BC803B73C61A} | 52 | | Windows (C#) | {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} | 53 | | Windows (VB.NET) | {F184B08F-C81C-45F6-A57F-5ABD9991F28F} | 54 | | Windows (Visual C++) | {8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942} | 55 | | Windows Communication Foundation (WCF) | {3D9AD99F-2412-4246-B90B-4EAA41C64699} | 56 | | Windows Phone 8/8.1 Blank/Hub/Webview App | {76F1466A-8B6D-4E39-A767-685A06062A39} | 57 | | Windows Phone 8/8.1 App (C#) | {C089C8C0-30E0-4E22-80C0-CE093F111A43} | 58 | | Windows Phone 8/8.1 App (VB.NET) | {DB03555F-0C8B-43BE-9FF9-57896B3C5E56} | 59 | | Windows Presentation Foundation (WPF) | {60DC8134-EBA5-43B8-BCC9-BB4BC16C2548} | 60 | | Windows Store (Metro) Apps & Components | {BC8A1FFA-BEE3-4634-8014-F334798102B3} | 61 | | Workflow (C#) | {14822709-B5A1-4724-98CA-57A101D1B079} | 62 | | Workflow (VB.NET) | {D59BE175-2ED0-4C54-BE3D-CDAA9F3214C8} | 63 | | Workflow Foundation | {32F31D43-81CC-4C15-9DE6-3FC5453562B6} | 64 | | Xamarin.Android | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF} | 65 | | Xamarin.iOS | {6BC8ED88-2882-458C-8E55-DFD12B67127B} | 66 | | XNA (Windows) | {6D335F3A-9D43-41b4-9D22-F6F17C4BE596} | 67 | | XNA (XBox) | {2DF5C3F4-5A5F-47a9-8E94-23B4456F55E2} | 68 | | XNA (Zune) | {D399B71A-8929-442a-A9AC-8BEC78BB2433} | 69 | -------------------------------------------------------------------------------- /src/Forge.Core/Globbing.fs: -------------------------------------------------------------------------------- 1 | /// This module contains a file pattern globbing implementation. 2 | module Forge.Globbing 3 | 4 | open System 5 | open System.IO 6 | open System.Text.RegularExpressions 7 | 8 | 9 | // Normalizes path for different OS 10 | let inline normalizePath (path : string) = 11 | path.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar) 12 | 13 | type private SearchOption = 14 | | Directory of path:string 15 | | Drive of driveName:string 16 | | Recursive 17 | | FilePattern of regexPattern:string 18 | 19 | let private checkSubDirs absolute (dir : string) root = 20 | if dir.Contains "*" then Directory.EnumerateDirectories (root, dir, SearchOption.TopDirectoryOnly) |> Seq.toList 21 | else 22 | let path = Path.Combine (root, dir) 23 | let di = if absolute then DirectoryInfo dir else DirectoryInfo path 24 | if di.Exists then [ di.FullName ] else [] 25 | 26 | let rec private buildPaths acc (input : SearchOption list) = 27 | match input with 28 | | [] -> acc 29 | | Directory name :: t -> 30 | let subDirs = acc |> List.collect (checkSubDirs false name) 31 | buildPaths subDirs t 32 | | Drive name :: t -> 33 | let subDirs = acc |> List.collect (checkSubDirs true name) 34 | buildPaths subDirs t 35 | | Recursive :: [] -> 36 | let dirs = 37 | acc 38 | |> Seq.collect (fun dir -> 39 | Directory.EnumerateFileSystemEntries(dir, "*", SearchOption.AllDirectories)) 40 | |> Seq.toList 41 | buildPaths (acc @ dirs) [] 42 | | Recursive :: t -> 43 | let dirs = 44 | acc 45 | |> Seq.collect (fun dir -> Directory.EnumerateDirectories(dir, "*", SearchOption.AllDirectories)) 46 | |> Seq.toList 47 | buildPaths (acc @ dirs) t 48 | | FilePattern pattern ::_ -> 49 | Seq.collect (fun dir -> 50 | if Directory.Exists (Path.Combine (dir, pattern)) 51 | then seq { yield Path.Combine (dir, pattern) } else 52 | try 53 | Directory.EnumerateFiles (dir, pattern) 54 | with 55 | | :? System.IO.PathTooLongException -> Array.toSeq [| |] 56 | ) acc |> Seq.toList 57 | 58 | let private driveRegex = Regex (@"^[A-Za-z]:$", RegexOptions.Compiled) 59 | 60 | let inline private normalizeOutputPath (p : string) = 61 | p.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar) 62 | .TrimEnd(Path.DirectorySeparatorChar) 63 | 64 | let internal getRoot (baseDirectory : string) (pattern : string) = 65 | let baseDirectory = normalizePath baseDirectory 66 | let normPattern = normalizePath pattern 67 | 68 | let patternParts = normPattern.Split ([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries) 69 | let patternPathParts = 70 | patternParts 71 | |> Array.takeWhile (fun p -> not ^ p.Contains "*") 72 | 73 | let globRoot = 74 | // If we did not find any "*", then drop the last bit (it is a file name, not a pattern) 75 | ( if patternPathParts.Length = patternParts.Length then 76 | patternPathParts.[0 .. patternPathParts.Length-2] 77 | else patternPathParts ) 78 | |> String.concat (Path.DirectorySeparatorChar.ToString()) 79 | 80 | let globRoot = 81 | // If we dropped "/" from the beginning of the path in the 'Split' call, put it back! 82 | if normPattern.StartsWith "/" then "/" + globRoot 83 | else globRoot 84 | 85 | if Path.IsPathRooted globRoot then globRoot 86 | else Path.Combine(baseDirectory, globRoot) 87 | 88 | let internal search (baseDir : string) (input : string) = 89 | let baseDir = normalizePath baseDir 90 | let input = normalizePath input 91 | let input = input.Replace (baseDir, "") 92 | 93 | let filePattern = Path.GetFileName input 94 | input.Split ([| '/'; '\\' |], StringSplitOptions.RemoveEmptyEntries) 95 | |> Array.map (function 96 | | "**" -> Recursive 97 | | a when a = filePattern -> FilePattern a 98 | | a when driveRegex.IsMatch a -> Directory(a + "\\") 99 | | a -> Directory a 100 | ) 101 | |> Array.toList 102 | |> buildPaths [ baseDir ] 103 | |> List.map normalizeOutputPath 104 | 105 | let internal compileGlobToRegex pattern = 106 | let pattern = normalizePath pattern 107 | 108 | let escapedPattern = (Regex.Escape pattern) 109 | let regexPattern = 110 | let xTOy = 111 | [ "dirwildcard" , (@"\\\*\\\*(/|\\\\)" , @"(.*(/|\\))?") 112 | "stardotstar" , (@"\\\*\\.\\\*" , @"([^\\/]*)" ) 113 | "wildcard" , (@"\\\*" , @"([^\\/]*)" ) 114 | ] |> List.map (fun (key, reg) -> 115 | let pattern, replace = reg 116 | let pattern = sprintf "(?<%s>%s)" key pattern 117 | key, (pattern, replace) 118 | ) 119 | let xTOyMap = xTOy |> Map.ofList 120 | let replacePattern = xTOy |> List.map (snd >> fst) |> String.concat "|" 121 | let replaced = Regex(replacePattern).Replace(escapedPattern, fun m -> 122 | let matched = xTOy |> Seq.map fst |> Seq.find (fun n -> 123 | m.Groups.[n].Success 124 | ) 125 | (xTOyMap |> Map.tryFind matched).Value |> snd 126 | ) 127 | "^" + replaced + "$" 128 | 129 | Regex regexPattern 130 | 131 | let globRegexCache = System.Collections.Concurrent.ConcurrentDictionary() 132 | 133 | let isMatch pattern path : bool = 134 | let path = normalizePath path 135 | 136 | let regex = 137 | let outRegex : ref = ref null 138 | if globRegexCache.TryGetValue (pattern, outRegex) then !outRegex else 139 | let compiled = compileGlobToRegex pattern 140 | globRegexCache.TryAdd(pattern, compiled) |> ignore 141 | compiled 142 | 143 | regex.IsMatch path 144 | 145 | -------------------------------------------------------------------------------- /src/Forge.Core/TraceHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | /// This module contains function which allow to trace build output 3 | module Forge.TraceHelper 4 | 5 | 6 | open System 7 | open System.IO 8 | open System.Reflection 9 | open System.Threading 10 | 11 | /// Defines if FAKE will use verbose tracing. 12 | /// This flag can be specified by setting the *verbose* build parameter. 13 | let mutable verbose = hasBuildParam "verbose" 14 | 15 | let private openTags = new ThreadLocal(fun _ -> []) 16 | 17 | /// Logs the specified string 18 | let log message = postMessage ^ LogMessage (message, true) 19 | 20 | /// Logs the specified message 21 | let logfn fmt = Printf.ksprintf log fmt 22 | 23 | /// Logs the specified message (without line break) 24 | let logf fmt = Printf.ksprintf (fun text -> postMessage ^ LogMessage (text, false)) fmt 25 | 26 | /// Logs the specified string if the verbose mode is activated. 27 | let logVerbosefn fmt = 28 | Printf.ksprintf (if verbose then log else ignore) fmt 29 | 30 | /// Writes a trace to the command line (in green) 31 | let trace message = postMessage ^ TraceMessage (message, true) 32 | 33 | /// Writes a message to the command line (in green) 34 | let tracefn fmt = Printf.ksprintf trace fmt 35 | 36 | /// Writes a message to the command line (in green) and without a line break 37 | let tracef fmt = Printf.ksprintf (fun text -> postMessage ^ TraceMessage (text, false)) fmt 38 | 39 | /// Writes a trace to the command line (in green) if the verbose mode is activated. 40 | let traceVerbose s = 41 | if verbose then trace s 42 | 43 | /// Writes a trace to stderr (in yellow) 44 | let traceImportant text = postMessage ^ ImportantMessage text 45 | 46 | /// Writes a trace to the command line (in yellow) 47 | let traceFAKE fmt = Printf.ksprintf (fun text -> postMessage ^ ImportantMessage text) fmt 48 | 49 | /// Writes a trace to the command line (in dark cyan) 50 | let traceWarning text = postMessage ^ WarningMessage (text, true) 51 | 52 | /// Traces an error (in red) 53 | let traceError error = postMessage ^ ErrorMessage error 54 | 55 | open Microsoft.FSharp.Core.Printf 56 | 57 | /// Converts an exception and its inner exceptions to a nice string. 58 | let exceptionAndInnersToString (ex:Exception) = 59 | let sb = Text.StringBuilder() 60 | let delimeter = String.replicate 50 "*" 61 | let nl = Environment.NewLine 62 | let rec printException (e:Exception) count = 63 | if (e :? TargetException && e.InnerException <> null) 64 | then printException (e.InnerException) count 65 | else 66 | if count = 1 then bprintf sb "Exception Message:%s%s%s" e.Message nl delimeter 67 | else bprintf sb "%s%s%d)Exception Message:%s%s%s" nl nl count e.Message nl delimeter 68 | bprintf sb "%sType: %s" nl (e.GetType().FullName) 69 | // Loop through the public properties of the exception object 70 | // and record their values. 71 | e.GetType().GetProperties() 72 | |> Array.iter (fun p -> 73 | // Do not log information for the InnerException or StackTrace. 74 | // This information is captured later in the process. 75 | if p.Name <> "InnerException" 76 | && p.Name <> "StackTrace" 77 | && p.Name <> "Message" 78 | && p.Name <> "Data" then 79 | try 80 | let value = p.GetValue(e, null) 81 | if value <> null then 82 | bprintf sb "%s%s: %s" nl p.Name (value.ToString()) 83 | with 84 | | e2 -> bprintf sb "%s%s: %s" nl p.Name e2.Message 85 | ) 86 | if e.StackTrace <> null then 87 | bprintf sb "%s%sStackTrace%s%s%s" nl nl nl delimeter nl 88 | bprintf sb "%s%s" nl e.StackTrace 89 | if e.InnerException <> null 90 | then printException e.InnerException (count+1) 91 | printException ex 1 92 | sb.ToString() 93 | 94 | /// Traces an exception details (in red) 95 | let traceException (ex:Exception) = exceptionAndInnersToString ex |> traceError 96 | 97 | /// Traces the EnvironmentVariables 98 | let TraceEnvironmentVariables() = 99 | [ EnvironTarget.Machine; EnvironTarget.Process; EnvironTarget.User ] 100 | |> Seq.iter (fun mode -> 101 | tracefn "Environment-Settings (%A):" mode 102 | environVars mode |> Seq.iter (tracefn " %A")) 103 | 104 | 105 | /// Traces a line 106 | let traceLine () = trace "---------------------------------------------------------------------" 107 | 108 | /// Traces a header 109 | let traceHeader name = 110 | trace "" 111 | traceLine () 112 | trace name 113 | traceLine () 114 | 115 | /// Traces the begin of the build 116 | let traceStartBuild() = postMessage StartMessage 117 | 118 | /// Traces the end of the build 119 | let traceEndBuild() = postMessage FinishedMessage 120 | 121 | /// Puts an opening tag on the internal tag stack 122 | let openTag tag = openTags.Value <- tag :: openTags.Value 123 | 124 | /// Removes an opening tag from the internal tag stack 125 | let closeTag tag = 126 | match openTags.Value with 127 | | x :: rest when x = tag -> openTags.Value <- rest 128 | | _ -> failwithf "Invalid tag structure. Trying to close %s tag but stack is %A" tag openTags 129 | CloseTag tag |> postMessage 130 | 131 | let closeAllOpenTags () = Seq.iter closeTag openTags.Value 132 | 133 | /// Traces the begin of a target 134 | let traceStartTarget name description dependencyString = 135 | openTag "target" 136 | OpenTag ("target", name) |> postMessage 137 | tracefn "Starting Target: %s %s" name dependencyString 138 | if description <> null then tracefn " %s" description 139 | // ReportProgressStart <| sprintf "Target: %s" name 140 | 141 | /// Traces the end of a target 142 | let traceEndTarget name = 143 | tracefn "Finished Target: %s" name 144 | closeTag "target" 145 | // ReportProgressFinish <| sprintf "Target: %s" name 146 | 147 | /// Traces the begin of a task 148 | let traceStartTask task _ = 149 | openTag "task" 150 | OpenTag ("task", task) |> postMessage 151 | // ReportProgressStart <| sprintf "Task: %s %s" task description 152 | 153 | /// Traces the end of a task 154 | let traceEndTask _ _ = 155 | closeTag "task" 156 | // ReportProgressFinish <| sprintf "Task: %s %s" task description 157 | 158 | let console = new ConsoleTraceListener(false, colorMap) :> ITraceListener 159 | 160 | /// Logs the given files with the message. 161 | let Log message files = files |> Seq.iter (log << sprintf "%s%s" message) 162 | 163 | 164 | -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/XLinq.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.XLinq 3 | open System.Runtime.CompilerServices 4 | open System.Xml.Linq 5 | 6 | 7 | 8 | let xattr (name:string) value = XAttribute (XName.Get name, value) 9 | 10 | 11 | let inline localName x = (^a:(member Name:XName) x).LocalName 12 | 13 | let inline private matchName (name:string) x = name = (localName x) 14 | 15 | /// Helper function to filter a seq of XElements by matching their local name against the provided string 16 | let inline private nameFilter name sqs = sqs |> Seq.filter ^ matchName name 17 | 18 | let inline private hasNamed name sqs = sqs |> Seq.exists ^ matchName name 19 | 20 | let inline private getNamed name sqs = sqs |> Seq.find ^ matchName name 21 | 22 | let inline private tryGetNamed name sqs = 23 | (None, sqs) ||> Seq.fold (fun acc elm -> 24 | match acc with 25 | | Some _ -> acc 26 | | None -> if matchName name elm then Some elm else None 27 | ) 28 | 29 | 30 | [] 31 | module XDoc = 32 | 33 | let elements (xdoc:#XDocument) = xdoc.Elements() 34 | 35 | let hasElement name (xdoc:#XDocument) = 36 | elements xdoc |> hasNamed name 37 | 38 | let getElement name (xdoc:#XDocument) = 39 | elements xdoc |> getNamed name 40 | 41 | let tryGetElement name (xdoc:#XDocument) = 42 | elements xdoc |> tryGetNamed name 43 | 44 | let getElements name (xdoc:#XDocument) = 45 | elements xdoc |> nameFilter name 46 | 47 | 48 | [] 49 | /// Functions for operating on XNodes 50 | module XNode = 51 | 52 | /// Returns a seq of XElements that precede the XNode 53 | let elementsBefore (node:#XNode) = node.ElementsBeforeSelf() 54 | 55 | /// Returns a seq of XElements that follow the XNode 56 | let elementsAfter (node:#XNode) = node.ElementsAfterSelf() 57 | 58 | /// Returns a seq of XElements that follow the XNode with a local name matching `name` 59 | let elementsAfterNamed (node:#XNode) name = 60 | elementsAfter node |> nameFilter name 61 | 62 | /// Returns a seq of XElements that precede the XNode with a local name matching `name` 63 | let elementsBeforeNamed (node:#XNode) name = 64 | elementsBefore node |> nameFilter name 65 | 66 | /// Returns seq of the XNode's ancestor XElements 67 | let ancestors (node:#XNode) = node.Ancestors() 68 | 69 | /// Returns the first ancestor of the XNode with a local name matching `name` 70 | let ancestorNamed (name:string) (node:#XNode) = 71 | ancestors node |> tryGetNamed name 72 | 73 | /// Returns all ancestors of the XNode with a local name matching `name` 74 | let ancestorsNamed (name:string) (node:#XNode) = 75 | ancestors node |> nameFilter name 76 | 77 | /// Insert a sibling XNode before `node` 78 | let addBefore (insert:#XNode) (node:#XNode) = 79 | node.AddBeforeSelf insert 80 | node 81 | 82 | /// Insert a sibling XNode after `node` 83 | let addAfter (insert:#XNode) (node:#XNode) = 84 | node.AddAfterSelf insert 85 | node 86 | 87 | 88 | let next (node:#XNode) = node.NextNode 89 | let previous (node:#XNode) = node.PreviousNode 90 | let parent (node:#XNode) = node.Parent 91 | 92 | let isBefore (target:#XNode) (node:#XNode) = node.IsBefore target 93 | let isAfter (target:#XNode) (node:#XNode) = node.IsAfter target 94 | 95 | 96 | [] 97 | /// Functions for operating on XContainers 98 | module XCont = 99 | 100 | let descendants (cont:#XContainer) = cont.Descendants() 101 | 102 | let descendantNamed name (cont:#XContainer) = 103 | descendants cont |> tryGetNamed name 104 | 105 | let descendantsNamed name (cont:#XContainer) = 106 | descendants cont |> nameFilter name 107 | 108 | let elements (cont:#XContainer) = cont.Elements() 109 | 110 | let hasElement name (cont:#XContainer) = 111 | elements cont |> hasNamed name 112 | 113 | let getElement name (cont:#XContainer) = 114 | elements cont |> getNamed name 115 | 116 | let tryGetElement name (cont:#XContainer) = 117 | elements cont |> tryGetNamed name 118 | 119 | let getElements name (cont:#XContainer) = 120 | elements cont |> nameFilter name 121 | 122 | let nodes (cont:#XContainer) = cont.Nodes() 123 | 124 | 125 | [] 126 | module XAttr = 127 | let value (xattr:XAttribute) = xattr.Value 128 | let parent (xattr:XAttribute) = xattr.Parent 129 | let previous (xattr:XAttribute) = xattr.PreviousAttribute 130 | let next (xattr:XAttribute) = xattr.NextAttribute 131 | 132 | [] 133 | /// Functions for operating on XElements 134 | module XElem = 135 | 136 | let inline isNamed name (xelem:#XElement) = 137 | matchName name xelem 138 | 139 | let inline notNamed name (xelem:#XElement) = 140 | not ^ matchName name xelem 141 | 142 | let create (name:string) (content:seq<'a>) = 143 | XElement (XName.Get name, Seq.toArray content) 144 | 145 | let value (xelem:#XElement) = xelem.Value 146 | 147 | let nodes (xelem:#XElement) = xelem.Nodes() 148 | 149 | let descendants (xelem:#XElement) = xelem.Descendants() 150 | 151 | let descendantNamed name (xelem:#XElement) = 152 | descendants xelem |> tryGetNamed name 153 | 154 | let descendantsNamed name (xelem:#XElement) = 155 | descendants xelem |> nameFilter name 156 | 157 | let elements (xelem:#XElement) = xelem.Elements() 158 | 159 | let hasElement name (xelem:#XElement) = 160 | elements xelem |> hasNamed name 161 | 162 | let getElement name (xelem:#XElement) = 163 | elements xelem |> getNamed name 164 | 165 | let getElementValue name (xelem:#XElement) = 166 | elements xelem |> getNamed name |> value 167 | 168 | let tryGetElement name (xelem:#XElement) = 169 | elements xelem |> tryGetNamed name 170 | 171 | let tryGetElementValue name (xelem:#XElement) = 172 | elements xelem |> tryGetNamed name |> Option.map value 173 | 174 | let getElements name (xelem:#XElement) = 175 | elements xelem |> nameFilter name 176 | 177 | let attributes (xelem:#XElement) = 178 | xelem.Attributes() 179 | 180 | let hasAttribute name (xelem:#XElement) = 181 | attributes xelem |> hasNamed name 182 | 183 | let getAttribute name (xelem:#XElement) = 184 | xelem.Attribute ^ XName.Get name 185 | 186 | let getAttributeValue name (xelem:#XElement) = 187 | xelem.Attribute ^ XName.Get name |> XAttr.value 188 | 189 | let tryGetAttribute name (xelem:#XElement) = 190 | attributes xelem |> tryGetNamed name 191 | 192 | let tryGetAttributeValue name (xelem:#XElement) = 193 | tryGetAttribute name xelem |> Option.map XAttr.value 194 | 195 | let setAttribute name value (xelem:#XElement) = 196 | xelem.SetAttributeValue(XName.Get name, value) 197 | xelem 198 | 199 | let addAttribute (xattr:#XAttribute) (xelem:#XElement) = 200 | xelem.Add xattr 201 | xelem 202 | 203 | let setElement name value (xelem:#XElement) = 204 | xelem.SetElementValue(XName.Get name, value) 205 | xelem 206 | 207 | let addElement (child:XElement) (parent:XElement) = 208 | parent.Add child 209 | parent 210 | 211 | let addElements (children:#seq) (parent:XElement) = 212 | parent.Add children 213 | parent 214 | 215 | /// Creates a new XElement and adds it as a child 216 | let inline addElem elmName value xelem = 217 | addElement (create elmName [value]) xelem 218 | 219 | 220 | 221 | 222 | [] 223 | type XLinqSeqExtensions = 224 | [] 225 | static member Ancestors (source:seq<#XNode>) name = source.Ancestors ^ XName.Get name 226 | 227 | [] 228 | static member AncestorsAndSelf (source:seq) name = source.AncestorsAndSelf ^ XName.Get name 229 | 230 | [] 231 | static member Attributes (source:seq) name = source.Attributes ^ XName.Get name 232 | 233 | [] 234 | static member Descendants (source:seq<#XContainer>) name = source.Descendants ^ XName.Get name 235 | 236 | [] 237 | static member DescendantsAndSelf (source:seq) name = source.DescendantsAndSelf ^ XName.Get name 238 | 239 | [] 240 | static member Elements (source:seq<#XContainer>) name = source.Elements ^ XName.Get name 241 | 242 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /src/Forge.Core/ProcessHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.ProcessHelper 3 | 4 | open System 5 | open System.Diagnostics 6 | open System.IO 7 | open System.Collections.Generic 8 | 9 | /// [omit] 10 | let startedProcesses = HashSet<_>() 11 | 12 | /// [omit] 13 | let start (proc : Process) = 14 | if isMono && proc.StartInfo.FileName.ToLowerInvariant().EndsWith(".exe") then 15 | proc.StartInfo.Arguments <- "--debug \"" + proc.StartInfo.FileName + "\" " + proc.StartInfo.Arguments 16 | proc.StartInfo.FileName <- monoPath 17 | 18 | ignore ^ proc.Start() 19 | ignore ^ startedProcesses.Add(proc.Id, proc.StartTime) 20 | 21 | /// [omit] 22 | let mutable redirectOutputToTrace = false 23 | 24 | /// [omit] 25 | let mutable enableProcessTracing = true 26 | 27 | /// A record type which captures console messages 28 | type ConsoleMessage = 29 | { IsError : bool 30 | Message : string 31 | Timestamp : DateTimeOffset } 32 | 33 | /// A process result including error code, message log and errors. 34 | type ProcessResult = 35 | { ExitCode : int 36 | Messages : ResizeArray 37 | Errors : ResizeArray } 38 | member x.OK = x.ExitCode = 0 39 | static member New exitCode messages errors = 40 | { ExitCode = exitCode 41 | Messages = messages 42 | Errors = errors } 43 | 44 | /// Arguments on the Mono executable 45 | let mutable monoArguments = "" 46 | 47 | /// Modifies the ProcessStartInfo according to the platform semantics 48 | let platformInfoAction (psi : ProcessStartInfo) = 49 | if isMono && psi.FileName.EndsWith ".exe" then 50 | psi.Arguments <- monoArguments + " \"" + psi.FileName + "\" " + psi.Arguments 51 | psi.FileName <- monoPath 52 | 53 | 54 | /// Runs the given process and returns the exit code. 55 | /// ## Parameters 56 | /// 57 | /// - `configProcessStartInfoF` - A function which overwrites the default ProcessStartInfo. 58 | /// - `timeOut` - The timeout for the process. 59 | /// - `silent` - If this flag is set then the process output is redirected to the given output functions `errorF` and `messageF`. 60 | /// - `errorF` - A function which will be called with the error log. 61 | /// - `messageF` - A function which will be called with the message log. 62 | let ExecProcessWithLambdas configProcessStartInfoF (timeOut : TimeSpan) silent errorF messageF = 63 | use proc = new Process() 64 | proc.StartInfo.UseShellExecute <- false 65 | configProcessStartInfoF proc.StartInfo 66 | platformInfoAction proc.StartInfo 67 | if not ^ String.isNullOrEmpty proc.StartInfo.WorkingDirectory then 68 | if not ^ Directory.Exists proc.StartInfo.WorkingDirectory then 69 | failwithf "Start of process %s failed. WorkingDir %s does not exist." proc.StartInfo.FileName 70 | proc.StartInfo.WorkingDirectory 71 | if silent then 72 | proc.StartInfo.RedirectStandardOutput <- true 73 | proc.StartInfo.RedirectStandardError <- true 74 | proc.ErrorDataReceived.Add(fun d -> 75 | if d.Data <> null then errorF d.Data) 76 | proc.OutputDataReceived.Add(fun d -> 77 | if d.Data <> null then messageF d.Data) 78 | try 79 | if enableProcessTracing && not ^ proc.StartInfo.FileName.EndsWith "fsi.exe" then 80 | tracefn "%s %s" proc.StartInfo.FileName proc.StartInfo.Arguments 81 | start proc 82 | with exn -> failwithf "Start of process %s failed. %s" proc.StartInfo.FileName exn.Message 83 | if silent then 84 | proc.BeginErrorReadLine () 85 | proc.BeginOutputReadLine () 86 | if timeOut = TimeSpan.MaxValue then proc.WaitForExit () 87 | elif not ^ proc.WaitForExit (int timeOut.TotalMilliseconds) then 88 | try 89 | proc.Kill() 90 | with exn -> 91 | traceError 92 | <| sprintf "Could not kill process %s %s after timeout." proc.StartInfo.FileName 93 | proc.StartInfo.Arguments 94 | failwithf "Process %s %s timed out." proc.StartInfo.FileName proc.StartInfo.Arguments 95 | proc.ExitCode 96 | 97 | 98 | /// Runs the given process and returns the process result. 99 | /// ## Parameters 100 | /// 101 | /// - `configProcessStartInfoF` - A function which overwrites the default ProcessStartInfo. 102 | /// - `timeOut` - The timeout for the process. 103 | let ExecProcessAndReturnMessages configProcessStartInfoF timeOut = 104 | let errors = ResizeArray<_>() 105 | let messages = ResizeArray<_>() 106 | let exitCode = ExecProcessWithLambdas configProcessStartInfoF timeOut true errors.Add messages.Add 107 | ProcessResult.New exitCode messages errors 108 | 109 | /// Runs the given process and returns the process result. 110 | /// ## Parameters 111 | /// 112 | /// - `configProcessStartInfoF` - A function which overwrites the default ProcessStartInfo. 113 | /// - `timeOut` - The timeout for the process. 114 | let ExecProcessRedirected configProcessStartInfoF timeOut = 115 | let messages = ref [] 116 | 117 | let appendMessage isError msg = 118 | messages := { IsError = isError 119 | Message = msg 120 | Timestamp = DateTimeOffset.UtcNow } :: !messages 121 | 122 | let exitCode = 123 | ExecProcessWithLambdas configProcessStartInfoF timeOut true (appendMessage true) (appendMessage false) 124 | (exitCode = 0, !messages |> List.rev |> Seq.ofList) 125 | 126 | 127 | /// Runs the given process and returns the exit code. 128 | /// ## Parameters 129 | /// 130 | /// - `configProcessStartInfoF` - A function which overwrites the default ProcessStartInfo. 131 | /// - `timeOut` - The timeout for the process. 132 | /// ## Sample 133 | /// 134 | /// let result = ExecProcess (fun info -> 135 | /// info.FileName <- "c:/MyProc.exe" 136 | /// info.WorkingDirectory <- "c:/workingDirectory" 137 | /// info.Arguments <- "-v") (TimeSpan.FromMinutes 5.0) 138 | /// 139 | /// if result <> 0 then failwithf "MyProc.exe returned with a non-zero exit code" 140 | let ExecProcess configProcessStartInfoF timeOut = 141 | ExecProcessWithLambdas configProcessStartInfoF timeOut redirectOutputToTrace traceError trace 142 | 143 | /// Runs the given process in an elevated context and returns the exit code. 144 | /// ## Parameters 145 | /// 146 | /// - `cmd` - The command which should be run in elavated context. 147 | /// - `args` - The process arguments. 148 | /// - `timeOut` - The timeout for the process. 149 | let ExecProcessElevated cmd args timeOut = 150 | timeOut 151 | |> ExecProcess (fun info -> 152 | info.Verb <- "runas" 153 | info.Arguments <- args 154 | info.FileName <- cmd 155 | info.UseShellExecute <- true 156 | ) 157 | 158 | /// Gets the list of valid directories included in the PATH environment variable. 159 | let pathDirectories = 160 | splitEnvironVar "PATH" 161 | |> Seq.map (fun value -> value.Trim()) 162 | |> Seq.filter (fun value -> String.isNotNullOrEmpty value) 163 | |> Seq.filter isValidPath 164 | 165 | /// Sets the environment Settings for the given startInfo. 166 | /// Existing values will be overriden. 167 | /// [omit] 168 | let setEnvironmentVariables (startInfo : ProcessStartInfo) environmentSettings = 169 | for key, value in environmentSettings do 170 | if startInfo.EnvironmentVariables.ContainsKey key then 171 | startInfo.EnvironmentVariables.[key] <- value 172 | else 173 | startInfo.EnvironmentVariables.Add(key, value) 174 | 175 | /// Runs the given process and returns true if the exit code was 0. 176 | /// [omit] 177 | let execProcess configProcessStartInfoF timeOut = ExecProcess configProcessStartInfoF timeOut = 0 178 | 179 | /// Starts the given process and returns immediatly. 180 | let fireAndForget configProcessStartInfoF = 181 | use proc = new Process () 182 | proc.StartInfo.UseShellExecute <- false 183 | configProcessStartInfoF proc.StartInfo 184 | try 185 | start proc 186 | with exn -> failwithf "Start of process %s failed. %s" proc.StartInfo.FileName exn.Message 187 | 188 | /// Runs the given process, waits for its completion and returns if it succeeded. 189 | let directExec configProcessStartInfoF = 190 | use proc = new Process () 191 | proc.StartInfo.UseShellExecute <- false 192 | configProcessStartInfoF proc.StartInfo 193 | try 194 | start proc 195 | with exn -> failwithf "Start of process %s failed. %s" proc.StartInfo.FileName exn.Message 196 | proc.WaitForExit () 197 | proc.ExitCode = 0 198 | 199 | /// Starts the given process and forgets about it. 200 | let StartProcess configProcessStartInfoF = 201 | use proc = new Process() 202 | proc.StartInfo.UseShellExecute <- false 203 | configProcessStartInfoF proc.StartInfo 204 | start proc 205 | 206 | 207 | let run cmd args dir = 208 | if execProcess( fun info -> 209 | info.FileName <- cmd 210 | if not ^ String.isNullOrWhiteSpace dir then 211 | info.WorkingDirectory <- dir 212 | info.Arguments <- args 213 | ) System.TimeSpan.MaxValue = false then 214 | traceError ^ sprintf "Error while running '%s' with args: %s" cmd args -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/Prelude.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.Prelude 3 | 4 | open System 5 | open System.IO 6 | open System.Diagnostics 7 | 8 | 9 | // Operators 10 | //======================== 11 | 12 | 13 | let (^) = (<|) 14 | 15 | let inline (|?|) (pred1:'a->bool) (pred2:'a->bool) = 16 | fun a -> pred1 a || pred2 a 17 | 18 | let inline (|&|) (pred1:'a->bool) (pred2:'a->bool) = 19 | fun a -> pred1 a && pred2 a 20 | 21 | /// Combines two path strings using Path.Combine 22 | let inline combinePaths path1 (path2 : string) = Path.Combine (path1, path2.TrimStart [| '\\'; '/' |]) 23 | let inline combinePathsNoTrim path1 path2 = Path.Combine (path1, path2) 24 | 25 | /// Combines two path strings using Path.Combine 26 | let inline (@@) path1 path2 = combinePaths path1 path2 27 | let inline () path1 path2 = combinePathsNoTrim path1 path2 28 | 29 | // String Helpers 30 | //===================================================== 31 | 32 | [] 33 | module String = 34 | 35 | let equalsIgnoreCase (str1:string) (str2:string) = 36 | String.Compare(str1,str2,StringComparison.OrdinalIgnoreCase) = 0 37 | /// Converts a sequence of strings to a string with delimiters 38 | let inline separated (delimiter: string) (items : string seq) = String.Join(delimiter, Array.ofSeq items) 39 | 40 | /// Returns if the string is null or empty 41 | let inline isNullOrEmpty value = String.IsNullOrEmpty value 42 | 43 | /// Returns if the string is not null or empty 44 | let inline isNotNullOrEmpty value = not ^ String.IsNullOrEmpty value 45 | 46 | /// Returns if the string is null or empty or completely whitespace 47 | let inline isNullOrWhiteSpace value = isNullOrEmpty value || value |> Seq.forall Char.IsWhiteSpace 48 | 49 | /// Converts a sequence of strings into a string separated with line ends 50 | let inline toLines text = separated Environment.NewLine text 51 | 52 | /// Checks whether the given text starts with the given prefix 53 | let startsWith (prefix: string) (text : string) = text.StartsWith prefix 54 | 55 | /// Checks whether the given text ends with the given suffix 56 | let endsWith (suffix: string) (text : string) = text.EndsWith suffix 57 | 58 | /// Determines whether the last character of the given 59 | /// matches Path.DirectorySeparatorChar. 60 | let endsWithSlash = endsWith (Path.DirectorySeparatorChar.ToString()) 61 | /// Reads a file as one text 62 | let inline readFileAsString file = File.ReadAllText file 63 | 64 | /// Replaces the given pattern in the given text with the replacement 65 | let inline replace (pattern : string) replacement (text : string) = text.Replace(pattern, replacement) 66 | 67 | /// Replaces the first occurrence of the pattern with the given replacement. 68 | let replaceFirst (pattern : string) replacement (text : string) = 69 | let pos = text.IndexOf pattern 70 | if pos < 0 then text 71 | else text.Remove(pos, pattern.Length).Insert(pos, replacement) 72 | 73 | /// Trims the given string with the DirectorySeparatorChar 74 | let inline trimSeparator (s : string) = s.TrimEnd Path.DirectorySeparatorChar 75 | 76 | /// Strips non-printable (control) characters from the string 77 | let inline stripControls (s : string) = s |> Seq.filter (fun c -> not (Char.IsControl(c))) |> String.Concat 78 | 79 | let takeUntil (c:char) (str:string) = 80 | match str.IndexOf c with 81 | | -1 -> str 82 | | num -> str.Substring(0,num) 83 | 84 | let inline private min3(a,b,c) = min a (min b c) 85 | let inline private distanceCalc (m, u) (n, v) = 86 | let d1 = Array.init n id 87 | let d0 = Array.create n 0 88 | for i=1 to m-1 do 89 | d0.[0] <- i 90 | let ui = u i 91 | for j=1 to n-1 do 92 | d0.[j] <- 1 + min3(d1.[j], d0.[j-1], d1.[j-1] + if ui = v j then -1 else 0) 93 | Array.blit d0 0 d1 0 n 94 | d0.[n-1] 95 | 96 | let editDistance (s: string) (t: string) = 97 | distanceCalc (s.Length, fun i -> s.[i]) (t.Length, fun i -> t.[i]) 98 | 99 | let containsAnyChars (str:string) chars = 100 | (false, str.ToCharArray()) 101 | ||> Array.fold (fun found strChar -> 102 | if found then true 103 | else Array.exists ((=) strChar) chars 104 | ) 105 | 106 | // Path/File Helpers 107 | //===================================================== 108 | 109 | /// Detects whether the given path does not contains invalid characters. 110 | let isValidPath (path:string) = 111 | not ^ String.containsAnyChars path [| yield! Path.GetInvalidPathChars() |] 112 | 113 | /// Detects whether the given fileName does not contains invalid characters. 114 | let isValidFileName (path:string) = 115 | not ^ String.containsAnyChars (Path.GetDirectoryName path) [| yield! Path.GetInvalidPathChars() |] && 116 | not ^ String.containsAnyChars (Path.GetFileName path) [| yield! Path.GetInvalidFileNameChars() |] 117 | 118 | // Process Helpers 119 | //===================================================== 120 | 121 | let prompt text = 122 | printfn text 123 | Console.Write "> " 124 | Console.ReadLine () |> String.stripControls 125 | 126 | let selectIndexOrString list s = 127 | try 128 | Seq.item (Int32.Parse(s) - 1) list 129 | with _ -> s 130 | 131 | let promptSelect text list = 132 | printfn text 133 | list |> Seq.iteri (fun i x -> printfn "%3i - %s" (i + 1) x) 134 | printfn "" 135 | Console.Write "> " 136 | Console.ReadLine () |> String.stripControls |> selectIndexOrString list 137 | 138 | let promptSelect2 text list = 139 | printfn text 140 | list |> Array.iteri (fun i (n, v) -> printfn "%3i - %s (%s)" (i + 1) n v) 141 | printfn "" 142 | Console.Write("> ") 143 | Console.ReadLine () |> String.stripControls |> selectIndexOrString (Array.map (fun (_, v) -> v) list) 144 | 145 | let promptCheck text checkF wrongInputMessage = 146 | let rec ask() = 147 | let x = prompt text 148 | if checkF x then x 149 | else 150 | printfn "%s" (wrongInputMessage x) 151 | ask() 152 | 153 | ask() 154 | 155 | let inline mapOpt (opt:'a option) mapfn (x:'b) = 156 | match opt with 157 | | None -> x 158 | | Some a -> mapfn a x 159 | 160 | let parseGuid (text: string) = 161 | let mutable g = Unchecked.defaultof 162 | if Guid.TryParse(text,&g) then Some g else None 163 | 164 | let parseBool (text: string) = 165 | let mutable b = Unchecked.defaultof 166 | if Boolean.TryParse(text,&b) then Some b else None 167 | 168 | [] 169 | module Option = 170 | 171 | /// Gets the value associated with the option or the supplied default value. 172 | let inline getOrElse v = function Some x -> x | None -> v 173 | 174 | /// Gets the value associated with the option or the supplied default value. 175 | let inline mapOrDefault mapfn v = 176 | function 177 | | Some x -> mapfn x 178 | | None -> v 179 | 180 | [] 181 | module Dict = 182 | open System.Collections.Generic 183 | 184 | let add key value (dict: Dictionary<_,_>) = 185 | dict.[key] <- value 186 | dict 187 | 188 | let remove (key: 'k) (dict: Dictionary<'k,_>) = 189 | dict.Remove key |> ignore 190 | dict 191 | 192 | let tryFind key (dict: Dictionary<'k, 'v>) = 193 | let mutable value = Unchecked.defaultof<_> 194 | if dict.TryGetValue (key, &value) then Some value 195 | else None 196 | 197 | let ofSeq (xs: ('k * 'v) seq) = 198 | let dict = Dictionary() 199 | for k, v in xs do dict.[k] <- v 200 | dict 201 | 202 | 203 | // COMPUTATION EXPRESSIONS 204 | //===================================== 205 | 206 | type MaybeBuilder () = 207 | [] 208 | member inline __.Return value: 'T option = Some value 209 | 210 | [] 211 | member inline __.ReturnFrom value: 'T option = value 212 | 213 | [] 214 | member inline __.Zero (): unit option = Some() 215 | 216 | [] 217 | member __.Delay (f: unit -> 'T option): 'T option = f () 218 | 219 | [] 220 | member inline __.Combine (r1, r2: 'T option): 'T option = 221 | match r1 with 222 | | None -> None 223 | | Some () -> r2 224 | 225 | [] 226 | member inline __.Bind (value, f: 'T -> 'U option): 'U option = Option.bind f value 227 | 228 | [] 229 | member __.Using (resource: ('T :> System.IDisposable), body: _ -> _ option): _ option = 230 | try body resource 231 | finally 232 | if not <| obj.ReferenceEquals (null, box resource) then 233 | resource.Dispose () 234 | 235 | [] 236 | member x.While (guard, body: _ option): _ option = 237 | if not ^ guard () then None else 238 | // OPTIMIZE: This could be simplified so we don't need to make calls to Bind and While. 239 | x.Bind (body, (fun () -> x.While (guard, body))) 240 | 241 | [] 242 | member x.For (sequence: seq<_>, body: 'T -> unit option): _ option = 243 | // OPTIMIZE: This could be simplified so we don't need to make calls to Using, While, Delay. 244 | x.Using (sequence.GetEnumerator (), fun enum -> 245 | x.While ( 246 | enum.MoveNext, 247 | x.Delay (fun () -> 248 | body enum.Current))) 249 | 250 | 251 | let maybe = MaybeBuilder() 252 | 253 | 254 | // ACTIVE PATTERNS 255 | //===================================== 256 | 257 | let (|InvariantEqual|_|) (str:string) arg = 258 | if String.Compare(str, arg, StringComparison.OrdinalIgnoreCase) = 0 259 | then Some () else None 260 | 261 | 262 | // Seq extension 263 | //==================================== 264 | 265 | module Seq = 266 | let duplicates xs = 267 | (Map.empty, xs) 268 | ||> Seq.scan (fun xs x -> 269 | match Map.tryFind x xs with 270 | | None -> Map.add x false xs 271 | | Some false -> Map.add x true xs 272 | | Some true -> xs) 273 | |> Seq.zip xs 274 | |> Seq.choose (fun (x, xs) -> 275 | match Map.tryFind x xs with 276 | | Some false -> Some x 277 | | None | Some true -> None) 278 | -------------------------------------------------------------------------------- /src/Forge.Core/FileHelper.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.FileHelper 3 | open System 4 | open System.IO 5 | open System.Collections.Generic 6 | open Forge.Globbing 7 | 8 | 9 | // File Helpers 10 | //===================================================== 11 | 12 | /// Gets the directory part of a filename. 13 | let inline directoryName fileName = Path.GetDirectoryName fileName 14 | 15 | /// Checks if the directory exists on disk. 16 | let directoryExists dir = Directory.Exists dir 17 | 18 | /// Ensure that directory chain exists. Create necessary directories if necessary. 19 | let inline ensureDirExists (dir : DirectoryInfo) = 20 | if not dir.Exists then dir.Create () 21 | 22 | /// Checks if the given directory exists. If not then this functions creates the directory. 23 | let inline ensureDirectory dir = DirectoryInfo dir |> ensureDirExists 24 | 25 | /// Creates a file if it does not exist. 26 | let createFile fileName = 27 | let file = FileInfo fileName 28 | if file.Exists then logfn "%s already exists." file.FullName else 29 | logfn "Creating %s" file.FullName 30 | let newFile = file.Create () 31 | newFile.Close () 32 | 33 | /// Deletes a file if it exists. 34 | let deleteFile fileName = 35 | let file = FileInfo fileName 36 | if not ^ file.Exists then logfn "%s does not exist." file.FullName else 37 | logfn "Deleting %s" file.FullName 38 | file.Delete () 39 | 40 | /// Renames the file to the target file name. 41 | let renameFile fileName target = (FileInfo fileName).MoveTo (getCwd() target) 42 | 43 | /// Renames the directory to the target directory name. 44 | let renameDir target dirName = (DirectoryInfo dirName).MoveTo target 45 | 46 | 47 | /// Gets the list of valid directories included in the PATH environment variable. 48 | let pathDirectories = 49 | splitEnvironVar "PATH" 50 | |> Seq.map (fun value -> value.Trim()) 51 | |> Seq.filter String.isNotNullOrEmpty 52 | |> Seq.filter isValidPath 53 | 54 | 55 | /// Searches the given directories for all occurrences of the given file name 56 | /// [omit] 57 | let tryFindFile dirs file = 58 | let files = 59 | dirs 60 | |> Seq.map (fun (path : string) -> 61 | let dir = 62 | path 63 | |> String.replace "[ProgramFiles]" ProgramFiles 64 | |> String.replace "[ProgramFilesX86]" ProgramFilesX86 65 | |> String.replace "[SystemRoot]" SystemRoot 66 | |> DirectoryInfo 67 | if not dir.Exists then "" else 68 | let fi = dir.FullName @@ file |> FileInfo 69 | if fi.Exists then fi.FullName else "" 70 | ) 71 | |> Seq.filter ((<>) "") 72 | |> Seq.cache 73 | if not ^ Seq.isEmpty files then Some ^ Seq.head files 74 | else None 75 | 76 | /// Searches the given directories for the given file, failing if not found. 77 | /// [omit] 78 | let findFile dirs file = 79 | match tryFindFile dirs file with 80 | | Some found -> found 81 | | None -> failwithf "%s not found in %A." file dirs 82 | 83 | /// Searches the current directory and the directories within the PATH 84 | /// environment variable for the given file. If successful returns the full 85 | /// path to the file. 86 | /// ## Parameters 87 | /// - `file` - The file to locate 88 | let tryFindFileOnPath (file : string) : string option = 89 | pathDirectories 90 | |> Seq.append [ "." ] 91 | |> fun path -> tryFindFile path file 92 | 93 | /// Returns the AppSettings for the key - Splitted on ; 94 | /// [omit] 95 | let appSettings (key : string) (fallbackValue : string) = 96 | let value = fallbackValue 97 | // let setting = 98 | // try 99 | // System.Configuration.ConfigurationManager.AppSettings.[key] 100 | // with exn -> "" 101 | // if not ^ String.isNullOrWhiteSpace setting then setting 102 | // else fallbackValue 103 | value.Split ([| ';' |], StringSplitOptions.RemoveEmptyEntries) 104 | 105 | 106 | /// Tries to find the tool via AppSettings. If no path has the right tool we are trying the PATH system variable. 107 | /// [omit] 108 | let tryFindPath settingsName fallbackValue tool = 109 | let paths = appSettings settingsName fallbackValue 110 | match tryFindFile paths tool with 111 | | Some path -> Some path 112 | | None -> tryFindFileOnPath tool 113 | 114 | /// Tries to find the tool via AppSettings. If no path has the right tool we are trying the PATH system variable. 115 | /// [omit] 116 | let findPath settingsName fallbackValue tool = 117 | match tryFindPath settingsName fallbackValue tool with 118 | | Some file -> file 119 | | None -> tool 120 | 121 | /// Internal representation of a file set. 122 | type FileIncludes = 123 | { BaseDirectory : string 124 | Includes : string list 125 | Excludes : string list } 126 | 127 | /// Adds the given pattern to the file includes 128 | member this.And pattern = { this with Includes = this.Includes @ [ pattern ] } 129 | 130 | /// Ignores files with the given pattern 131 | member this.ButNot pattern = { this with Excludes = pattern :: this.Excludes } 132 | 133 | /// Sets a directory as BaseDirectory. 134 | member this.SetBaseDirectory(dir : string) = { this with BaseDirectory = dir.TrimEnd(Path.DirectorySeparatorChar) } 135 | 136 | /// Checks if a particular file is matched 137 | member this.IsMatch (path : string) = 138 | let fullDir pattern = 139 | if Path.IsPathRooted pattern then pattern else 140 | System.IO.Path.Combine (this.BaseDirectory, pattern) 141 | 142 | let included = 143 | this.Includes 144 | |> Seq.exists (fun fileInclude -> 145 | Globbing.isMatch (fullDir fileInclude) path 146 | ) 147 | let excluded = 148 | this.Excludes 149 | |> Seq.exists (fun fileExclude -> 150 | Globbing.isMatch (fullDir fileExclude) path 151 | ) 152 | 153 | included && not excluded 154 | 155 | interface IEnumerable with 156 | 157 | member this.GetEnumerator() = 158 | let hashSet = HashSet<_> () 159 | 160 | let excludes = 161 | seq { for pattern in this.Excludes do 162 | yield! Globbing.search this.BaseDirectory pattern 163 | } |> Set.ofSeq 164 | 165 | let files = 166 | seq { for pattern in this.Includes do 167 | yield! Globbing.search this.BaseDirectory pattern 168 | } |> Seq.filter (fun x -> not ^ Set.contains x excludes) 169 | |> Seq.filter hashSet.Add 170 | 171 | files.GetEnumerator () 172 | 173 | member this.GetEnumerator () = (this :> seq).GetEnumerator() :> System.Collections.IEnumerator 174 | 175 | let private defaultBaseDir = Path.GetFullPath "." 176 | 177 | 178 | /// Include files 179 | let Include x = 180 | { BaseDirectory = defaultBaseDir 181 | Includes = [ x ] 182 | Excludes = [] } 183 | 184 | /// Sets a directory as baseDirectory for fileIncludes. 185 | let SetBaseDir (dir : string) (fileIncludes : FileIncludes) = fileIncludes.SetBaseDirectory dir 186 | 187 | /// Add Include operator 188 | let inline (++) (x : FileIncludes) pattern = x.And pattern 189 | 190 | /// Exclude operator 191 | let inline (--) (x : FileIncludes) pattern = x.ButNot pattern 192 | 193 | /// Includes a single pattern and scans the files - !! x = AllFilesMatching x 194 | let inline (!!) x = Include x 195 | 196 | /// Looks for a tool first in its default path, if not found in all subfolders of the root folder - returns the tool file name. 197 | let findToolInSubPath toolname defaultPath = 198 | try 199 | let tools = !! (defaultPath @@ "/**/" @@ toolname) 200 | if Seq.isEmpty tools then 201 | let root = !! ("./**/" @@ toolname) 202 | Seq.head root 203 | else 204 | Seq.head tools 205 | with 206 | | _ -> defaultPath @@ toolname 207 | 208 | /// Looks for a tool in all subfolders - returns the folder where the tool was found. 209 | let findToolFolderInSubPath toolname defaultPath = 210 | try 211 | let tools = !! ("./**/" @@ toolname) 212 | if Seq.isEmpty tools then defaultPath 213 | else 214 | let fi = FileInfo (Seq.head tools) 215 | fi.Directory.FullName 216 | with 217 | | _ -> defaultPath 218 | 219 | /// Gets all subdirectories of a given directory. 220 | let inline subDirectories (dir : DirectoryInfo) = dir.GetDirectories() 221 | 222 | /// Gets all files in the directory. 223 | let inline filesInDir (dir : DirectoryInfo) = dir.GetFiles() 224 | 225 | /// Performs the given actions on all files and subdirectories 226 | let rec recursively dirF fileF (dir : DirectoryInfo) = 227 | dir 228 | |> subDirectories 229 | |> Seq.iter (fun dir -> recursively dirF fileF dir; dirF dir) 230 | dir |> filesInDir |> Seq.iter fileF 231 | 232 | /// Finds all the files in the directory matching the search pattern. 233 | let filesInDirMatching pattern (dir : DirectoryInfo) = 234 | if dir.Exists then dir.GetFiles pattern 235 | else [||] 236 | 237 | /// Sets the directory readonly 238 | let setDirectoryReadOnly readOnly (dir : DirectoryInfo) = 239 | if dir.Exists then 240 | let isReadOnly = dir.Attributes &&& FileAttributes.ReadOnly = FileAttributes.ReadOnly 241 | if readOnly && (not isReadOnly) then dir.Attributes <- dir.Attributes ||| FileAttributes.ReadOnly 242 | if (not readOnly) && not isReadOnly then dir.Attributes <- dir.Attributes &&& (~~~FileAttributes.ReadOnly) 243 | 244 | /// Sets all files in the directory readonly. 245 | let setDirReadOnly readOnly dir = 246 | recursively (setDirectoryReadOnly readOnly) (fun file -> file.IsReadOnly <- readOnly) dir 247 | 248 | /// Sets all given files readonly. 249 | let setReadOnly readOnly (files : string seq) = 250 | files |> Seq.iter (fun file -> 251 | let fi = FileInfo file 252 | if fi.Exists then fi.IsReadOnly <- readOnly else 253 | file 254 | |> DirectoryInfo 255 | |> setDirectoryReadOnly readOnly 256 | ) 257 | 258 | /// Deletes a directory if it exists. 259 | let deleteDir path = 260 | let dir = DirectoryInfo path 261 | if not dir.Exists then printfn "%s does not exist." dir.FullName else 262 | // set all files readonly = false 263 | !!"/**/*.*" 264 | |> SetBaseDir dir.FullName 265 | |> setReadOnly false 266 | printfn "Deleting %s" dir.FullName 267 | dir.Delete true 268 | 269 | /// Creates a directory if it does not exist. 270 | let createDir path = 271 | let dir = DirectoryInfo path 272 | if dir.Exists then printfn "%s already exists." dir.FullName else 273 | printfn "Creating %s" dir.FullName 274 | dir.Create () 275 | 276 | /// Copies a directory recursivly. If the target directory does not exist, it will be created. 277 | /// ## Parameters 278 | /// 279 | /// - `target` - The target directory. 280 | /// - `source` - The source directory. 281 | /// - `filterFile` - A file filter predicate. 282 | let copyDir target source filterFile overwrite = 283 | createDir target 284 | Directory.GetFiles(source, "*.*", SearchOption.AllDirectories) 285 | |> Seq.filter filterFile 286 | |> Seq.iter (fun file -> 287 | let fi = file |> String.replaceFirst source "" |> String.trimSeparator 288 | let newFile = target @@ fi 289 | printfn "%s => %s" file newFile 290 | directoryName newFile |> ensureDirectory 291 | File.Copy (file, newFile, overwrite) 292 | ) |> ignore 293 | 294 | /// Cleans a directory by removing all files and sub-directories. 295 | let cleanDir path = 296 | let di = DirectoryInfo path 297 | if not di.Exists then createDir path else 298 | printfn "Deleting contents of %s" path 299 | // delete all files 300 | Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) 301 | |> Seq.iter (fun file -> 302 | let fi = FileInfo file 303 | fi.IsReadOnly <- false 304 | fi.Delete () 305 | ) 306 | // deletes all subdirectories 307 | let rec deleteDirs actDir = 308 | Directory.GetDirectories actDir |> Seq.iter deleteDirs 309 | Directory.Delete (actDir, true) 310 | Directory.GetDirectories path |> Seq.iter deleteDirs 311 | 312 | // set writeable 313 | File.SetAttributes (path, FileAttributes.Normal) 314 | -------------------------------------------------------------------------------- /src/Forge.Core/Templates.fs: -------------------------------------------------------------------------------- 1 | module Forge.Templates 2 | 3 | open Forge.Git 4 | open Forge.ProjectSystem 5 | open System.IO 6 | open System 7 | // open Mono.Unix.Native 8 | open FSharp.Data 9 | 10 | /// Result type for project comparisons. 11 | type ProjectComparison = 12 | { TemplateProjectFileName: string 13 | ProjectFileName: string 14 | MissingFiles: string seq 15 | DuplicateFiles: string seq 16 | UnorderedFiles: string seq } 17 | 18 | member this.HasErrors = 19 | not (Seq.isEmpty this.MissingFiles && 20 | Seq.isEmpty this.UnorderedFiles && 21 | Seq.isEmpty this.DuplicateFiles) 22 | 23 | /// Compares the given project files againts the template project and returns which files are missing. 24 | /// For F# projects it is also reporting unordered files. 25 | let findMissingFiles templateProject projects = 26 | let isFSharpProject file = file |> String.endsWith ".fsproj" 27 | 28 | let templateFiles = (FsProject.load templateProject).SourceFiles.Files 29 | let templateFilesSet = Set.ofSeq templateFiles 30 | 31 | projects 32 | |> Seq.map (fun fn -> 33 | let ps = FsProject.load fn 34 | let missingFiles = Set.difference templateFilesSet (Set.ofSeq ps.SourceFiles.Files) 35 | 36 | let duplicateFiles = 37 | Seq.duplicates ps.SourceFiles.Files 38 | 39 | let unorderedFiles = 40 | if not <| isFSharpProject templateProject then [] else 41 | if not <| Seq.isEmpty missingFiles then [] else 42 | let remainingFiles = ps.SourceFiles.Files |> List.filter (fun file -> Set.contains file templateFilesSet) 43 | if remainingFiles.Length <> templateFiles.Length then [] else 44 | 45 | templateFiles 46 | |> List.zip remainingFiles 47 | |> List.filter (fun (a,b) -> a <> b) 48 | |> List.map fst 49 | 50 | { TemplateProjectFileName = templateProject 51 | ProjectFileName = fn 52 | MissingFiles = missingFiles 53 | DuplicateFiles = duplicateFiles 54 | UnorderedFiles = unorderedFiles }) 55 | |> Seq.filter (fun pc -> pc.HasErrors) 56 | 57 | /// Compares the given projects to the template project and adds all missing files to the projects if needed. 58 | let FixMissingFiles templateProject projects = 59 | 60 | 61 | findMissingFiles templateProject projects 62 | |> Seq.iter (fun pc -> 63 | let addMissing project missingFile = 64 | printfn "Adding %s to %s" missingFile pc.ProjectFileName 65 | project |> FsProject.addSourceFile "" {SourceFile.Include = missingFile; Condition = None; OnBuild = BuildAction.Compile; Link = None; Copy = None; Paket = None} 66 | 67 | 68 | let project = FsProject.load pc.ProjectFileName 69 | if not ^ Seq.isEmpty pc.MissingFiles then 70 | pc.MissingFiles 71 | |> Seq.fold addMissing project 72 | |> FsProject.save pc.ProjectFileName ) 73 | 74 | /// It removes duplicate files from the project files. 75 | let RemoveDuplicateFiles projects = 76 | projects 77 | |> Seq.iter (fun fileName -> 78 | let project = FsProject.load fileName 79 | let duplicates = 80 | Seq.duplicates project.SourceFiles.Files 81 | if not ^ Seq.isEmpty duplicates then 82 | let newProject = project //TODO: .RemoveDuplicates() 83 | newProject |> FsProject.save fileName) 84 | 85 | /// Compares the given projects to the template project and adds all missing files to the projects if needed. 86 | /// It also removes duplicate files from the project files. 87 | let FixProjectFiles templateProject projects = 88 | FixMissingFiles templateProject projects 89 | RemoveDuplicateFiles projects 90 | 91 | /// Compares the given project files againts the template project and fails if any files are missing. 92 | /// For F# projects it is also reporting unordered files. 93 | let CompareProjectsTo templateProject projects = 94 | let errors = 95 | findMissingFiles templateProject projects 96 | |> Seq.map (fun pc -> 97 | seq { 98 | if Seq.isEmpty pc.MissingFiles |> not then 99 | yield sprintf "Missing files in %s:\r\n%s" pc.ProjectFileName (String.toLines pc.MissingFiles) 100 | if Seq.isEmpty pc.UnorderedFiles |> not then 101 | yield sprintf "Unordered files in %s:\r\n%s" pc.ProjectFileName (String.toLines pc.UnorderedFiles) 102 | if Seq.isEmpty pc.DuplicateFiles |> not then 103 | yield sprintf "Duplicate files in %s:\r\n%s" pc.ProjectFileName (String.toLines pc.DuplicateFiles)} 104 | |> String.toLines) 105 | |> String.toLines 106 | 107 | if String.isNotNullOrEmpty errors then failwith errors 108 | 109 | let Refresh () = 110 | printfn "Getting templates..." 111 | cleanDir templatesLocation 112 | cloneSingleBranch exeLocation "https://github.com/Ionide/Forge-templates.git" "netcoreTemplates" "templates" 113 | printfn "Templates refreshed..." 114 | 115 | let EnsureTemplatesExist () = 116 | if not ^ Directory.Exists templatesLocation then Refresh () 117 | 118 | let GetList () = 119 | if Directory.Exists templatesLocation then 120 | Directory.GetDirectories templatesLocation 121 | |> Seq.map Path.GetFileName 122 | |> Seq.filter (fun x -> not ^ x.StartsWith ".") 123 | else Seq.empty 124 | 125 | 126 | module Project = 127 | open System 128 | open System.IO 129 | open Forge.ProjectSystem 130 | 131 | let applicationNameToProjectName folder projectName = 132 | let applicationName = "ApplicationName" 133 | let files = Directory.GetFiles folder |> Seq.where (fun x -> x.Contains applicationName) 134 | files |> Seq.iter (fun x -> File.Move(x, x.Replace(applicationName, projectName))) 135 | 136 | let sed (find:string) replace folder = 137 | folder 138 | |> Directory.GetFiles 139 | |> Seq.iter (fun x -> 140 | let r = replace x 141 | let contents = File.ReadAllText(x).Replace(find, r) 142 | File.WriteAllText(x, contents)) 143 | 144 | let getProjects() = 145 | DirectoryInfo(getCwd()) |> filesInDirMatching "*.fsproj" 146 | 147 | 148 | let New projectName projectDir templateName paket fake vscode = 149 | EnsureTemplatesExist () 150 | 151 | let templates = GetList() 152 | 153 | let pathCheck path = 154 | try Path.GetFullPath path |> ignore; isValidPath path && not (String.IsNullOrWhiteSpace path) 155 | with _ -> false 156 | 157 | let projectName' = 158 | match projectName with 159 | | Some p -> p 160 | | None -> 161 | promptCheck 162 | "Enter project name:" 163 | pathCheck 164 | (sprintf "\"%s\" is not a valid project name.") 165 | let projectDir' = 166 | match projectDir with 167 | | Some p -> p 168 | | None -> 169 | promptCheck 170 | "Enter project directory (relative to working directory):" 171 | pathCheck 172 | (sprintf "\"%s\" is not a valid directory name.") 173 | let templateName' = 174 | match templateName with 175 | | Some p -> p 176 | | None -> (templates |> promptSelect "Choose a template:") 177 | let projectFolder = getCwd() projectDir' projectName' 178 | let vscodeDir = templatesLocation ".vscode" 179 | let vscodeDir' = getCwd() ".vscode" 180 | let templateDir = templatesLocation templateName' 181 | let contentDir = templateDir "_content" 182 | let gitignorePath = (templatesLocation ".vcsignore" ".gitignore") 183 | 184 | if pathCheck projectFolder |> not then 185 | printfn "\"%s\" is not a valid project folder." projectFolder 186 | elif templates |> Seq.contains templateName' |> not then 187 | printfn "Incorrect template number or name" 188 | else 189 | printfn "Generating %s project..." templateName' 190 | 191 | 192 | copyDir projectFolder templateDir (fun f -> f.Contains "_content" |> not) false //Copy project files 193 | applicationNameToProjectName projectFolder projectName' 194 | sed "<%= namespace %>" (fun _ -> projectName') projectFolder 195 | sed "<%= guid %>" (fun _ -> Guid.NewGuid().ToString()) projectFolder 196 | sed "<%= paketPath %>" (relative ^ getCwd()) projectFolder 197 | sed "<%= packagesPath %>" (relative ^ getPackagesDirectory()) projectFolder 198 | 199 | if Directory.Exists contentDir then 200 | copyDir (getCwd()) contentDir (fun _ -> true) false 201 | sed "<%= projectPath %>" (fun s -> "." (relative (projectFolder (projectName' + ".fsproj")) s)) (getCwd ()) 202 | 203 | if Directory.GetFiles (getCwd()) |> Seq.exists (fun n -> n.EndsWith ".gitignore") |> not then 204 | File.Copy(gitignorePath, (getCwd() ".gitignore"), false) 205 | 206 | if vscode then 207 | copyDir vscodeDir' vscodeDir (fun _ -> true) false 208 | 209 | if paket then 210 | Paket.Init ^ getCwd() 211 | 212 | Directory.GetFiles projectFolder 213 | |> Seq.tryFind (fun n -> n.EndsWith "paket.references") 214 | |> Option.iter (File.ReadAllLines >> Seq.iter (fun ref -> Paket.Run ["add"; ref; "--no-resolve"]) ) 215 | 216 | if fake then 217 | if paket then Paket.Run ["add"; "FAKE"; "--no-resolve"; "--group Build"] 218 | Fake.Copy ^ getCwd() 219 | let buildSh = getCwd() "build.sh" 220 | let ctn = File.ReadAllText buildSh 221 | let ctn = ctn.Replace("\r\n", "\n") 222 | File.WriteAllText(buildSh, ctn) 223 | // if isMono then 224 | // let perms = FilePermissions.S_IRWXU ||| FilePermissions.S_IRGRP ||| FilePermissions.S_IROTH // 0x744 225 | // Syscall.chmod(buildSh, perms) |> ignore 226 | 227 | if paket then Paket.Run ["install"] 228 | 229 | 230 | printfn "Done!" 231 | 232 | module File = 233 | open System 234 | open System.IO 235 | open Forge.ProjectSystem 236 | open Forge.Json 237 | open Json.JsonExtensions 238 | 239 | let getTemplates () = 240 | let value = System.IO.File.ReadAllText(templateFile) |> JsonValue.Parse 241 | value?Files |> JsonValue.AsArray |> Array.map (fun n -> 242 | n?name.ToString().Replace("\"","" ), 243 | n?value.ToString().Replace("\"","" ), 244 | n?extension.ToString().Replace("\"","" ) ) 245 | 246 | let sed (find:string) replace file = 247 | let r = replace file 248 | let contents = File.ReadAllText(file).Replace(find, r) 249 | File.WriteAllText(file, contents) 250 | 251 | 252 | let New fileName template project buildAction copy = 253 | EnsureTemplatesExist () 254 | 255 | let templates = getTemplates () 256 | let template' = 257 | match template with 258 | | Some t -> t 259 | | None -> (templates |> Array.map( fun (n,v,_) -> n,v) |> promptSelect2 "Choose a template:") 260 | let (_, value, ext) = templates |> Seq.find (fun (_,v,_) -> v = template') 261 | let oldFile = value + "." + ext 262 | let newFile = fileName + "." + ext 263 | let newFile' = (getCwd() newFile) 264 | let project' = 265 | match project with 266 | | Some p -> 267 | let p = if p |> String.endsWith ".fsproj" then p else p + ".fsproj" 268 | if File.Exists p then 269 | Some p 270 | else 271 | traceWarning "Provided project not found. Trying to find project file automatically." 272 | ProjectManager.Furnace.tryFindProject newFile' 273 | | None -> ProjectManager.Furnace.tryFindProject newFile' 274 | 275 | System.IO.File.Copy(filesLocation oldFile, newFile') 276 | match project' with 277 | | Some f -> 278 | ProjectManager.Furnace.loadFsProject f 279 | |> ProjectManager.Furnace.addSourceFile (newFile, None, buildAction, None, copy, None) 280 | |> ignore 281 | | None -> 282 | traceWarning "Project file not found, use `add file --project` to add file to project" 283 | 284 | sed "<%= namespace %>" (fun _ -> fileName.Split('\\', '/', '.') |> Seq.last ) newFile' 285 | sed "<%= guid %>" (fun _ -> System.Guid.NewGuid().ToString()) newFile' 286 | sed "<%= paketPath %>" (relative ^ getCwd()) newFile' 287 | sed "<%= packagesPath %>" (relative ^ getPackagesDirectory()) newFile' 288 | 289 | module Solution = 290 | let New name = 291 | EnsureTemplatesExist () 292 | let template = templatesLocation ".sln" "ApplicationName.sln" 293 | let newName = getCwd() (name + ".sln") 294 | File.Copy(template, newName, false) 295 | 296 | module Scaffold = 297 | let New () = 298 | EnsureTemplatesExist () 299 | printfn "Cloning project scaffold..." 300 | let whiteSpaceProtectedDir = ("\"" + getCwd() + "\"") 301 | cloneSingleBranch exeLocation "https://github.com/fsprojects/ProjectScaffold.git" "master" whiteSpaceProtectedDir 302 | () 303 | -------------------------------------------------------------------------------- /tests/Forge.Tests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open System 4 | open Forge 5 | open Forge.ProjectSystem 6 | open Forge.SolutionSystem 7 | open Common 8 | 9 | open Expecto 10 | 11 | [] 12 | let tests = 13 | testList "Unit Tests" [ 14 | testList "Prelude" [ 15 | testCase "relative - file target in different path than directory source" <| fun _ -> 16 | let relativePath = relative "test/Test/Test.fsproj" "src/Sample/" 17 | "should be OK" |> Expect.equal relativePath (".." ".." "test" "Test" "Test.fsproj") 18 | 19 | testCase "relative - file target in different path than file source" <| fun _ -> 20 | let relativePath = relative "test/Test/Test.fsproj" "src/Sample/Sample.fsproj" 21 | "should be OK" |> Expect.equal relativePath (".." ".." "test" "Test" "Test.fsproj") 22 | 23 | testCase "relative - directory target in different path than directory source" <| fun _ -> 24 | let relativePath = relative "test/Test" "src/Sample/" 25 | "should be OK" |> Expect.equal relativePath (".." ".." "test" "Test") 26 | 27 | testCase "relative - directory target in different path than file source" <| fun _ -> 28 | let relativePath = relative "test/Test" "src/Sample/Sample.fsproj" 29 | "should be OK" |> Expect.equal relativePath (".." ".." "test" "Test") 30 | 31 | testCase "relative - file target with shared ancestor as directory source" <| fun _ -> 32 | let relativePath = relative "test/Test" "src/Sample/Sample.fsproj" 33 | "should be OK" |> Expect.equal relativePath (".." ".." "test" "Test") 34 | 35 | testCase "relative - file target with shared ancestor as directory source 2" <| fun _ -> 36 | let relativePath = relative "src/Test/Test.fsproj" "src/Sample/" 37 | "should be OK" |> Expect.equal relativePath (".." "Test" "Test.fsproj") 38 | 39 | testCase "relative - file target with shared ancestor as file source" <| fun _ -> 40 | let relativePath = relative "src/Test/Test.fsproj" "src/Sample/Sample.fsproj" 41 | "should be OK" |> Expect.equal relativePath (".." "Test" "Test.fsproj") 42 | 43 | testCase "relative - directory target with shared ancestor as directory source" <| fun _ -> 44 | let relativePath = relative "src/Test" "src/Sample/" 45 | "should be OK" |> Expect.equal relativePath (".." "Test") 46 | 47 | testCase "relative - directory target with shared ancestor as file source" <| fun _ -> 48 | let relativePath = relative "src/Test" "src/Sample/Sample.fsproj" 49 | "should be OK" |> Expect.equal relativePath (".." "Test") 50 | 51 | testCase "relative - irectory target equal to directory source" <| fun _ -> 52 | let relativePath = relative "src/Test" "src/Test" 53 | "should be OK" |> Expect.equal relativePath "" 54 | 55 | testCase "relative - file target equal to file source" <| fun _ -> 56 | let relativePath = relative "src/Test/Test.fsproj" "src/Test/Test.fsproj" 57 | "should be OK" |> Expect.equal relativePath "" 58 | ] 59 | testList "Project System" [ 60 | testCase "parse - AST gets all project files" <| fun _ -> 61 | let projectFile = FsProject.parse astInput 62 | projectFile.SourceFiles.AllFiles() |> Expect.hasLength' 3 63 | 64 | testCase "parse - parse project files with nested folders and linked files" <| fun _ -> 65 | let pf = FsProject.parse projectWithLinkedFiles 66 | 67 | pf.SourceFiles.AllFiles() |> Expect.hasLength' 8 68 | pf.SourceFiles.Tree.["/"] |> Expect.equivalent ["FixProject.fs"; "App.config"; "a_file.fs"; "foo/"; "fldr/"; "linked/"] 69 | pf.SourceFiles.Tree.["foo/"] |> Expect.equivalent ["bar/"; "abc/"] 70 | pf.SourceFiles.Tree.["linked/"] |> Expect.equivalent ["ext/"] 71 | pf.SourceFiles.Tree.["linked/ext/"] |> Expect.equivalent ["external.fs"] 72 | Expect.equal pf.SourceFiles.Data.["linked/ext/external.fs"].Include "../foo/external.fs" "should be same" 73 | 74 | testCase "parse - AST gets all references" <| fun _ -> 75 | let projectFile = FsProject.parse astInput 76 | projectFile.References |> Expect.hasLength' 5 77 | 78 | testCase "parse - AST gets correct settings" <| fun _ -> 79 | let projectFile = FsProject.parse astInput 80 | let s = projectFile.Settings 81 | Expect.equal s.Configuration.Data (Some "Debug") "should be same" 82 | Expect.equal s.Platform.Data (Some "AnyCPU") "should be same" 83 | Expect.equal s.SchemaVersion.Data (Some "2.0") "should be same" 84 | Expect.equal s.ProjectGuid.Data (Some ^ System.Guid.Parse "fbaf8c7b-4eda-493a-a7fe-4db25d15736f") "should be same" 85 | Expect.equal s.OutputType.Data (Some OutputType.Library) "should be same" 86 | Expect.equal s.TargetFrameworkVersion.Data (Some "v4.5") "should be same" 87 | Expect.equal s.AssemblyName.Data (Some "Test") "should be same" 88 | Expect.equal s.DocumentationFile.Data (Some "bin\Debug\Test.XML") "should be same" 89 | 90 | testCase "parse - gets all multi target frameworks" <| fun _ -> 91 | let projectFile = FsProject.parse netCoreProjectMultiTargetsNoFiles 92 | let s = projectFile.Settings 93 | Expect.equal s.TargetFrameworks.Data (Some ["net461"; "netstandard2.0"; "netcoreapp2.0"]) "should be same" 94 | 95 | testCase "parse - ToXElem sets all multi target frameworks" <| fun _ -> 96 | let projectFile = FsProject.parse netCoreProjectMultiTargetsNoFiles 97 | let s = projectFile.Settings 98 | let settingsXml = projectFile.Settings.ToXElem() 99 | let targetFrameworks = settingsXml.Element (Xml.Linq.XName.Get "TargetFrameworks") 100 | Expect.equal targetFrameworks.Value "net461;netstandard2.0;netcoreapp2.0" "should be same" 101 | 102 | testCase "parse - add new file" <| fun _ -> 103 | let pf = FsProject.parse astInput 104 | let f = {SourceFile.Include = "Test.fsi"; Condition = None; OnBuild = BuildAction.Compile; Link = None; Copy = None; Paket = None} 105 | let pf' = FsProject.addSourceFile "/" f pf 106 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 4 107 | 108 | testCase "parse - add duplicate file" <| fun _ -> 109 | let pf = FsProject.parse astInput 110 | let f = {SourceFile.Include = "FixProject.fs"; Condition = None; OnBuild = BuildAction.Compile; Link = None; Copy = None; Paket = None} 111 | let pf' = FsProject.addSourceFile "/" f pf 112 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 3 113 | 114 | testCase "parse - remove file" <| fun _ -> 115 | let pf = FsProject.parse astInput 116 | let f = "FixProject.fs" 117 | let pf' = FsProject.removeSourceFile f pf 118 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 2 119 | 120 | testCase "parse - remove not existing file" <| fun _ -> 121 | let pf = FsProject.parse astInput 122 | let f = "FixProject2.fs" 123 | let pf' = FsProject.removeSourceFile f pf 124 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 3 125 | 126 | testCase "parse - order file" <| fun _ -> 127 | let pf = FsProject.parse astInput 128 | let pf' = pf |> FsProject.moveUp "a_file.fs" |> FsProject.moveUp "a_file.fs" 129 | let files = pf'.SourceFiles.AllFiles() 130 | Expect.equal (files |> Seq.head) "a_file.fs" "should be equal" 131 | files |> Expect.hasLength' 3 132 | 133 | testCase "parse - add reference" <| fun _ -> 134 | let pf = FsProject.parse astInput 135 | let r = {Reference.Empty with Include = "System"} 136 | let pf' = FsProject.addReference r pf 137 | pf'.References |> Expect.hasLength' 5 138 | 139 | testCase "parse - remove referenc" <| fun _ -> 140 | let pf = FsProject.parse astInput 141 | let r = {Reference.Empty with Include = "System"} 142 | let pf' = FsProject.removeReference r pf 143 | pf'.References |> Expect.hasLength' 4 144 | 145 | testCase "parse - remove not existing reference" <| fun _ -> 146 | let pf = FsProject.parse astInput 147 | let r = {Reference.Empty with Include = "System.Xml"} 148 | let pf' = FsProject.removeReference r pf 149 | pf'.References |> Expect.hasLength' 5 150 | 151 | testCase "parse - rename project" <| fun _ -> 152 | let pf = FsProject.parse astInput 153 | let pf' = FsProject.renameProject "TestRename" pf 154 | let s = pf'.Settings 155 | Expect.equal s.AssemblyName.Data (Some "TestRename") "should be same" 156 | Expect.equal s.RootNamespace.Data (Some "TestRename") "should be same" 157 | Expect.equal s.DocumentationFile.Data (Some "bin\Debug\TestRename.XML") "should be same" 158 | 159 | testCase "parse - rename file" <| fun _ -> 160 | let pf = FsProject.parse astInput 161 | let pf' = pf |> FsProject.renameFile "FixProject.fs" "renamed_file.fs" 162 | let files = pf'.SourceFiles.AllFiles() 163 | Expect.equal (files |> Seq.head) "renamed_file.fs" "should be same" 164 | files |> Expect.hasLength' 3 165 | 166 | testCase "parse - rename file invalid name" <| fun _ -> 167 | if System.IO.Path.GetInvalidFileNameChars().Length > 0 then 168 | let invalid = System.IO.Path.GetInvalidFileNameChars().[0].ToString() 169 | let pf = FsProject.parse astInput 170 | let pf' = pf |> FsProject.renameFile "FixProject.fs" ("invalid" + invalid + ".fs") 171 | let files = pf'.SourceFiles.AllFiles() 172 | Expect.equal (files |> Seq.head) "FixProject.fs" "should be same" 173 | files |> Expect.hasLength' 3 174 | 175 | testCase "parse - move up" <| fun _ -> 176 | let pf = FsProject.parse astInput 177 | let pf' = pf |> FsProject.moveUp "a_file.fs" 178 | let files = pf'.SourceFiles.AllFiles() 179 | Expect.equal (files |> Seq.last) "App.config" "should be same" 180 | files |> Expect.hasLength' 3 181 | 182 | testCase "parse - move down" <| fun _ -> 183 | let pf = FsProject.parse astInput 184 | let pf' = pf |> FsProject.moveDown "FixProject.fs" 185 | let files = pf'.SourceFiles.AllFiles() 186 | Expect.equal (files |> Seq.head) "App.config" "should be same" 187 | files |> Expect.hasLength' 3 188 | 189 | testCase "parse - add above" <| fun _ -> 190 | let pf = FsProject.parse astInput 191 | let f = {SourceFile.Include = "above.fs"; Condition = None; OnBuild = BuildAction.Compile; Link = None; Copy = None; Paket = None} 192 | let pf' = FsProject.addAbove "FixProject.fs" f pf 193 | let files = pf'.SourceFiles.AllFiles() 194 | Expect.equal (files |> Seq.head) "above.fs" "should be same" 195 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 4 196 | 197 | testCase "parse - add below" <| fun _ -> 198 | let pf = FsProject.parse astInput 199 | let f = {SourceFile.Include = "below.fs"; Condition = None; OnBuild = BuildAction.Compile; Link = None; Copy = None; Paket = None} 200 | let pf' = FsProject.addBelow "FixProject.fs" f pf 201 | let files = pf'.SourceFiles.AllFiles() 202 | Expect.equal (files |> Seq.item 1) "below.fs" "should be same" 203 | pf'.SourceFiles.AllFiles() |> Expect.hasLength' 4 204 | ] 205 | 206 | testList "SolutionSystem" [ 207 | testCase "addFolder - add folder to default solution" <| fun _ -> 208 | let folderName = "newFolder" 209 | let solution = Solution.Default |> Solution.addFolder folderName 210 | solution.Folders |> Expect.hasLength' 1 211 | 212 | testCase "addFolder - adding duplicated folder fails" <| fun _ -> 213 | let folderName = "newFolder" 214 | Expect.throws (fun () -> 215 | Solution.Default 216 | |> Solution.addFolder folderName 217 | |> Solution.addFolder folderName 218 | |> ignore) "should throw" 219 | 220 | testCase "addFolder - add folder with same name as a project fails" <| fun _ -> 221 | let projectName = "existingProject" 222 | let slnProj = 223 | { 224 | ProjectTypeGuid = Guid.NewGuid() 225 | Guid = Guid.NewGuid() 226 | Name = projectName 227 | Path = "projectPath" 228 | Dependecies = [] 229 | } 230 | let solution = { Solution.Default with Projects=[slnProj]} 231 | Expect.throws (fun () -> solution |> Solution.addFolder projectName |> ignore) "should throw" 232 | 233 | testCase "removeFolder - remove existing folder" <| fun _ -> 234 | let folderName = "aFolder" 235 | let solution = Solution.Default |> Solution.addFolder folderName 236 | let solution' = solution |> Solution.removeFolder folderName 237 | solution'.Folders |> Expect.hasLength' 0 238 | 239 | testCase "removeFolder - remove folder in NestedProjects" <| fun _ -> 240 | let projectGuid = Guid.NewGuid() 241 | let folderName = "folderName" 242 | let slnProj = 243 | { 244 | ProjectTypeGuid = projectGuid 245 | Guid = Guid.NewGuid() 246 | Name = "existingProject" 247 | Path = "projectPath" 248 | Dependecies = [] 249 | } 250 | 251 | let folderGuid = Guid.NewGuid() 252 | let slnFolder = 253 | { 254 | ProjectTypeGuid = FolderGuid 255 | Name = folderName 256 | Path = "folderPath" 257 | Guid = folderGuid 258 | SolutionItems = [] 259 | } 260 | let slnNestedProject = {Project = projectGuid; Parent = folderGuid} 261 | 262 | let solution = { Solution.Default with Projects=[slnProj]; NestedProjects=[slnNestedProject]; Folders=[slnFolder] } 263 | 264 | let solution' = solution |> Solution.removeFolder folderName 265 | solution'.NestedProjects |> Expect.hasLength' 0 266 | 267 | testCase "removeFolder - remove unexisting project" <| fun _ -> 268 | let solution = Solution.Default |> Solution.addFolder "aFolder" 269 | Expect.throws (fun () -> solution |> Solution.removeFolder "anotherFolder" |> ignore) "should throw" 270 | ] 271 | ] -------------------------------------------------------------------------------- /src/Forge.Core/ProjectManager.fs: -------------------------------------------------------------------------------- 1 | module Forge.ProjectManager 2 | 3 | open System 4 | open System.Text 5 | open System.IO 6 | open System.Xml 7 | open System.Xml.Linq 8 | open Forge 9 | open Forge.ProjectSystem 10 | open Forge.ProjectSystem.PathHelpers 11 | 12 | 13 | 14 | type ActiveState = 15 | { StoredXml : XElement seq 16 | ProjectData : FsProject 17 | ProjectPath : string 18 | ProjectFileName : string 19 | } 20 | 21 | // Maybe use a persistent vector here to allow timetravel/history & undo? 22 | 23 | 24 | let saveState (state:ActiveState) = 25 | File.WriteAllText( 26 | state.ProjectPath state.ProjectFileName + ".fsproj", 27 | state.ProjectData.ToXmlString state.StoredXml 28 | ) 29 | 30 | 31 | let updateProj projfn (state:ActiveState) = 32 | let state = { state with ProjectData = projfn state.ProjectData } 33 | saveState state 34 | state 35 | 36 | 37 | // The furnace is the internal workhorse that handles the orchestration of manipulating 38 | // the project and solution files, making changes to the file system, finding the source of 39 | // errors and surfacing them up to the user 40 | [] 41 | module Furnace = 42 | 43 | let loadFsProject (projectPath: string) = 44 | use reader = XmlReader.Create projectPath 45 | let xdoc = reader |> XDocument.Load 46 | // hold onto the xml content we're not using so it doesn't get lost 47 | let detritus = 48 | xdoc.Root |> XElem.elements 49 | |> Seq.filter (fun (xelem:XElement) -> 50 | xelem 51 | |>( XElem.notNamed Constants.Project 52 | |&| XElem.notNamed Constants.PropertyGroup 53 | |&| XElem.notNamed Constants.ItemGroup 54 | |&| XElem.notNamed Constants.ProjectReference 55 | |&| XElem.notNamed Constants.PackageReference 56 | |&| XElem.notNamed Constants.DotNetCliToolReference 57 | ) 58 | ) 59 | let proj = FsProject.fromXDoc xdoc 60 | 61 | let projectPath' = 62 | match Path.GetDirectoryName projectPath with 63 | | "" -> Environment.CurrentDirectory 64 | | p -> Environment.CurrentDirectory p 65 | // TODO - This is a bad way to deal with loading the configuration settings 66 | 67 | { StoredXml = List.ofSeq detritus 68 | ProjectData = proj 69 | ProjectPath = projectPath' 70 | ProjectFileName = Path.GetFileNameWithoutExtension projectPath 71 | } 72 | 73 | 74 | let addReference (includestr: string, condition: string option, hintPath: string option, name: string option, specificVersion: bool option, copy: bool option) (state: ActiveState) = 75 | let asmName = String.takeUntil ',' includestr 76 | let project = state.ProjectData 77 | let r = project.References |> ResizeArray.tryFind (fun refr -> 78 | (refr.Name.IsSome && refr.Name.Value = asmName) || 79 | (String.takeUntil ',' refr.Include = asmName ) 80 | ) 81 | let projectName = defaultArg project.Settings.Name.Data "fsproject" 82 | match r with 83 | | Some _ -> 84 | traceWarning ^ sprintf "'%s' already has a Reference for '%s'" projectName asmName 85 | state 86 | | None -> 87 | let reference = { 88 | Include = includestr 89 | Condition = condition 90 | HintPath = hintPath 91 | Name = name 92 | SpecificVersion = specificVersion 93 | CopyLocal = copy 94 | Paket = None 95 | } 96 | FsProject.addReference reference project |> ignore 97 | updateProj (FsProject.addReference reference) state 98 | 99 | 100 | let removeReference (refname:string) (state: ActiveState) = 101 | let project = state.ProjectData 102 | let r = project.References |> ResizeArray.tryFind (fun refr -> 103 | (refr.Name.IsSome && refr.Name.Value = refname) || 104 | (String.takeUntil ',' refr.Include = refname ) 105 | ) 106 | let projectName = defaultArg project.Settings.Name.Data "fsproject" 107 | match r with 108 | | None -> 109 | traceWarning ^ sprintf "'%s' does not contain a Reference for '%s'" projectName refname 110 | state 111 | | Some reference -> 112 | FsProject.removeReference reference project |> ignore 113 | updateProj (FsProject.removeReference reference) state 114 | 115 | let addProjectReference (path : string, name : string option, condition : string option, guid : Guid option, copyLocal : bool option) (state: ActiveState) = 116 | let path = if path.StartsWith "." then path else relative path (state.ProjectPath + Path.DirectorySeparatorChar.ToString()) 117 | let projRef = { 118 | Include = path 119 | Condition = condition 120 | Name = name 121 | CopyLocal = copyLocal 122 | Guid = guid 123 | } 124 | FsProject.addProjectReference projRef state.ProjectData |> ignore 125 | updateProj (FsProject.addProjectReference projRef) state 126 | 127 | let removeProjectReference (path : string) (state: ActiveState) = 128 | let project = state.ProjectData 129 | let path = if path.StartsWith "." then path else relative path (state.ProjectPath + Path.DirectorySeparatorChar.ToString()) 130 | let r = project.ProjectReferences |> ResizeArray.tryFind (fun refr -> refr.Include = path) 131 | 132 | let projectName = defaultArg project.Settings.Name.Data "fsproject" 133 | match r with 134 | | None -> 135 | traceWarning ^ sprintf "'%s' does not contain a project Reference for '%s'" projectName path 136 | state 137 | | Some reference -> 138 | FsProject.removeProjectReference reference project |> ignore 139 | updateProj (FsProject.removeProjectReference reference) state 140 | 141 | 142 | let moveUp (target: string) (state: ActiveState) = 143 | updateProj (FsProject.moveUp target) state 144 | 145 | 146 | let moveDown (target:string) (state: ActiveState) = 147 | updateProj (FsProject.moveDown target) state 148 | 149 | 150 | let addAbove (target: string, file: string, onBuild: BuildAction option, link: string option, copy: CopyToOutputDirectory option, condition: string option) (state: ActiveState) = 151 | let dir = getParentDir target 152 | let onBuild = defaultArg onBuild BuildAction.Compile 153 | let srcFile = 154 | { Include = dir file 155 | Condition = condition 156 | OnBuild = onBuild 157 | Link = link 158 | Copy = copy 159 | Paket = None 160 | } 161 | updateProj (FsProject.addAbove target srcFile) state 162 | 163 | 164 | let addBelow (target: string, file: string, onBuild: BuildAction option, link: string option, copy: CopyToOutputDirectory option, condition: string option) (state: ActiveState) = 165 | let dir = getParentDir target 166 | let onBuild = defaultArg onBuild BuildAction.Compile 167 | let srcFile = 168 | { Include = dir file 169 | Condition = condition 170 | OnBuild = onBuild 171 | Link = link 172 | Copy = copy 173 | Paket = None 174 | } 175 | updateProj (FsProject.addBelow target srcFile) state 176 | 177 | 178 | let addSourceFile (file: string, dir :string option, onBuild: BuildAction option, linkPath: string option, copy: CopyToOutputDirectory option, condition: string option) (state: ActiveState)= 179 | let dir = defaultArg dir "" 180 | let onBuild = defaultArg onBuild BuildAction.Compile 181 | let srcFile = 182 | { Include = dir file 183 | Condition = condition 184 | OnBuild = onBuild 185 | Link = linkPath 186 | Copy = copy 187 | Paket = None 188 | } 189 | updateProj (FsProject.addSourceFile dir srcFile) state 190 | 191 | 192 | let removeSourceFile (path:string) (state: ActiveState) = 193 | updateProj (FsProject.removeSourceFile path) state 194 | 195 | 196 | let deleteSourceFile (path:string) (state: ActiveState) = 197 | if not ^ File.Exists path then 198 | traceError ^ sprintf "Cannot Delete File - '%s' does not exist" path 199 | state 200 | else 201 | deleteFile path 202 | removeSourceFile path state 203 | 204 | 205 | let removeDirectory (path:string) (state: ActiveState) = 206 | updateProj (FsProject.removeDirectory path) state 207 | 208 | 209 | let deleteDirectory (path:string) (state: ActiveState) = 210 | if not ^ directoryExists path then 211 | traceError ^ sprintf "Cannot Delete Directory - '%s' does not exist" path 212 | state 213 | else 214 | deleteDir path 215 | removeDirectory path state 216 | 217 | 218 | let renameDirectory (path:string, newName:string) (state: ActiveState) = 219 | let fullOldPath = state.ProjectPath path 220 | let fullNewPath = state.ProjectPath newName 221 | 222 | let (|Physical|Virtual|None|) directory = 223 | let sourceFiles = 224 | state.ProjectData.SourceFiles.DirContents ^ normalizeFileName directory 225 | |> Seq.map (fun file -> state.ProjectData.SourceFiles.Data.[file]) 226 | |> List.ofSeq 227 | 228 | if sourceFiles.IsEmpty then None 229 | elif sourceFiles |> List.exists (fun f -> f.Link.IsNone) then Physical 230 | else Virtual 231 | 232 | let rename() = updateProj (FsProject.renameDir path newName) state 233 | 234 | match path with 235 | | Physical -> 236 | if directoryExists fullOldPath then 237 | renameDir fullNewPath fullOldPath 238 | rename() 239 | else 240 | traceError ^ sprintf "Cannot Rename Directory - directory '%s' does not exist on disk" fullOldPath 241 | state 242 | | Virtual -> rename() 243 | | None -> 244 | traceError ^ sprintf "Cannot Rename Directory - directory '%s' does not exist in the project" path 245 | state 246 | 247 | 248 | let renameSourceFile (path:string, newName:string) (state: ActiveState) = 249 | if not ^ isValidFileName newName then 250 | traceError ^ "Cannot Rename File - invalid name" 251 | state 252 | elif not ^ File.Exists path then 253 | traceError ^ sprintf "Cannot Rename File - '%s' does not exist" path 254 | state 255 | elif File.Exists newName then 256 | traceError ^ sprintf "Cannot Rename File - '%s' already exists" newName 257 | state 258 | elif Path.GetDirectoryName newName |> Directory.Exists |> not then 259 | traceError ^ sprintf "Cannot Rename File - '%s' does not exist" (Path.GetDirectoryName newName) 260 | state 261 | else 262 | renameFile path newName 263 | 264 | let dir = getParentDir path 265 | let name' = relative (getCwd() path) ((getCwd() dir |> Path.GetDirectoryName) + Path.DirectorySeparatorChar.ToString() ) 266 | let newName' = relative (getCwd() newName) ((getCwd() dir |> Path.GetDirectoryName) + Path.DirectorySeparatorChar.ToString() ) 267 | updateProj (FsProject.renameFile name' newName') state 268 | 269 | 270 | let listSourceFiles (filter: string option) (state: ActiveState) = 271 | let filterFn = 272 | match filter with 273 | | Some s -> (fun fileName -> (String.editDistance fileName s) < 5) 274 | | None -> (fun _ -> true) 275 | FsProject.listSourceFiles state.ProjectData 276 | |> List.filter filterFn 277 | |> List.iter trace 278 | state 279 | 280 | 281 | let listReferences (filter: string option) (state: ActiveState) = 282 | let filterFn = 283 | match filter with 284 | | Some s -> (fun fileName -> (String.editDistance fileName s) < 5) 285 | | None -> (fun _ -> true) 286 | FsProject.listReferences state.ProjectData 287 | |> List.filter filterFn 288 | |> List.iter trace 289 | state 290 | 291 | let listProjectReferences (filter: string option) (state: ActiveState) = 292 | let filterFn = 293 | match filter with 294 | | Some s -> (fun fileName -> (String.editDistance fileName s) < 5) 295 | | None -> (fun _ -> true) 296 | FsProject.listProjectReferences state.ProjectData 297 | |> List.filter filterFn 298 | |> List.iter trace 299 | state 300 | 301 | let listProjects (folder: string option) (filter: string option) = 302 | let filterFn = 303 | match filter with 304 | | Some s -> (fun fileName -> (String.editDistance fileName s) < 5) 305 | | None -> (fun _ -> true) 306 | let dir = 307 | match folder with 308 | | Some s -> s 309 | | None -> getCwd() + Path.DirectorySeparatorChar.ToString() 310 | Globbing.search dir "**/*proj" 311 | |> List.filter (fun s -> System.IO.Path.GetFileNameWithoutExtension(s) |> filterFn) 312 | |> List.map (fun s -> relative s dir) 313 | |> List.iter trace 314 | 315 | 316 | let rec tryFindProject dir = 317 | try 318 | let dir = 319 | try 320 | if (File.GetAttributes(dir).HasFlag FileAttributes.Directory |> not) then System.IO.Path.GetDirectoryName dir else dir 321 | with 322 | | _ -> System.IO.Path.GetDirectoryName dir 323 | 324 | match Globbing.search dir "*.fsproj" |> List.tryHead with 325 | | Some f -> Some f 326 | | None -> 327 | if dir = getCwd() then None 328 | else dir |> System.IO.Directory.GetParent |> fun n -> n.FullName |> tryFindProject 329 | with 330 | | _ -> None 331 | 332 | let renameProject (name:string, newName:string) (state: ActiveState) = 333 | let proj = tryFindProject name 334 | if proj.IsNone then 335 | traceError ^ sprintf "Cannot Rename Project - '%s' does not exist" name 336 | state 337 | else 338 | updateProj (FsProject.renameProject newName) state 339 | 340 | 341 | 342 | -------------------------------------------------------------------------------- /tests/Forge.IntegrationTests/Tests.fs: -------------------------------------------------------------------------------- 1 | module Tests 2 | 3 | open Expecto 4 | open Forge.Environment 5 | open Forge 6 | 7 | [] 8 | let tests = 9 | testSequenced <| testList "Integration test" [ 10 | testList "New file" [ 11 | testCase "Create new file giving path to fsproj" <| fun _ -> 12 | let dir = "new_file - path to fsproj" 13 | ["new project -n Sample --dir src -t console --no-paket" 14 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 15 | ] 16 | |> initTest dir 17 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 18 | project |> Expect.hasFile "Test.fs" 19 | 20 | testCase "Create new file without giving project name" <| fun _ -> 21 | let dir = "new_file - no path to project" 22 | ["new project -n Sample --dir src -t console --no-paket" 23 | "new file -n src/Sample/Test --template fs" 24 | ] 25 | |> initTest dir 26 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 27 | project |> Expect.hasFile "Test.fs" 28 | 29 | testCase "Create new file giving wrong project name" <| fun _ -> 30 | let dir = "new_file - wrong path to project" 31 | ["new project -n Sample --dir src -t console --no-paket" 32 | "new file -n src/Sample/Test --project ABC --template fs" 33 | ] 34 | |> initTest dir 35 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 36 | project |> Expect.hasFile "Test.fs" 37 | 38 | testCase "Create new file with copy-to-output" <| fun _ -> 39 | let dir = "new_file - copy" 40 | ["new project -n Sample --dir src -t console --no-paket" 41 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs --copy-to-output never" 42 | ] 43 | |> initTest dir 44 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 45 | project |> Expect.hasFile "Test.fs" 46 | ] 47 | testList "References" [ 48 | testCase "Add Reference" <| fun _ -> 49 | let dir = "references_add_ref" 50 | [ "new project -n Sample --dir src -t console --no-paket" 51 | "add reference -n System.Speech -p src/Sample/Sample.fsproj" ] 52 | |> initTest dir 53 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 54 | project |> Expect.reference "System.Speech" 55 | 56 | testCase "Remove Reference" <| fun _ -> 57 | let dir = "references_remove_ref" 58 | [ "new project -n Sample --dir src -t console --no-paket" 59 | "add reference -n System.Speech -p src/Sample/Sample.fsproj" 60 | "remove reference -n System.Speech -p src/Sample/Sample.fsproj" ] 61 | |> initTest dir 62 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 63 | project |> Expect.notReference "System.Speech" 64 | 65 | testCase "Add Reference - absolute path" <| fun _ -> 66 | let dir = "references_add_ref_Absolute_path" |> makeAbsolute 67 | let projectPath = dir "src" "Sample" "Sample.fsproj" 68 | [ "new project -n Sample --dir src -t console --no-paket" 69 | "add reference -n System.Speech -p " + projectPath] 70 | |> initTest dir 71 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 72 | project |> Expect.reference "System.Speech" 73 | 74 | testCase "Remove Reference - absolute path" <| fun _ -> 75 | let dir = "references_remove_ref_Absolute_path" |> makeAbsolute 76 | let projectPath = dir "src" "Sample" "Sample.fsproj" 77 | [ "new project -n Sample --dir src -t console --no-paket" 78 | "add reference -n System.Speech -p " + projectPath 79 | "remove reference -n System.Speech -p " + projectPath ] 80 | |> initTest dir 81 | let project = projectPath |> loadProject 82 | project |> Expect.notReference "System.Speech" 83 | ] 84 | testList "Project Reference" [ 85 | testCase "Add Project Reference" <| fun _ -> 86 | let dir = "project_references_add_ref" 87 | [ "new project -n Sample --dir src -t console --no-paket" 88 | "new project -n Test --dir test -t expecto --no-paket" 89 | "add project -p test/Test/Test.fsproj -n src/Sample/Sample.fsproj" ] 90 | |> initTest dir 91 | let project = dir "test" "Test" "Test.fsproj" |> loadProject 92 | project |> Expect.referenceProject (".." ".." "src" "Sample" "Sample.fsproj") 93 | 94 | testCase "Remove Project Reference" <| fun _ -> 95 | let dir = "project_references_remove_ref" 96 | [ "new project -n Sample --dir src -t console --no-paket" 97 | "new project -n Test --dir test -t expecto --no-paket" 98 | "add project -p test/Test/Test.fsproj -n src/Sample/Sample.fsproj" 99 | "remove project -p test/Test/Test.fsproj -n src/Sample/Sample.fsproj" ] 100 | |> initTest dir 101 | let project = dir "test" "Test" "Test.fsproj" |> loadProject 102 | project |> Expect.notReferenceProject (".." ".." "src" "Sample" "Sample.fsproj") 103 | 104 | testCase "Add Project Reference - absolute path" <| fun _ -> 105 | let dir = "project_references_add_ref_Absolute_path" |> makeAbsolute 106 | let path1 = dir "test" "Test" "Test.fsproj" 107 | let path2 = dir "src" "Sample" "Sample.fsproj" 108 | [ "new project -n Sample --dir src -t console --no-paket" 109 | "new project -n Test --dir test -t expecto --no-paket" 110 | sprintf "add project -p %s -n %s" path1 path2 ] 111 | |> initTest dir 112 | let project = path1 |> loadProject 113 | project |> Expect.referenceProject (".." ".." "src" "Sample" "Sample.fsproj") 114 | 115 | testCase "Remove Project Reference - absolute path" <| fun _ -> 116 | let dir = "project_references_remove_ref_Absolute_path" |> makeAbsolute 117 | let path1 = dir "test" "Test" "Test.fsproj" 118 | let path2 = dir "src" "Sample" "Sample.fsproj" 119 | [ "new project -n Sample --dir src -t console --no-paket" 120 | "new project -n Test --dir test -t expecto --no-paket" 121 | sprintf "add project -p %s -n %s" path1 path2 122 | sprintf "remove project -p %s -n %s" path1 path2 ] 123 | |> initTest dir 124 | let project = path1 |> loadProject 125 | project |> Expect.notReferenceProject (".." ".." "src" "Sample" "Sample.fsproj") 126 | ] 127 | testList "Add file" [ 128 | testCase "Add File" <| fun _ -> 129 | let dir = "file_add_file" 130 | [ "new project -n Sample --dir src -t console --no-paket" 131 | "add file -n src/Sample/Test.fs" ] 132 | |> initTest dir 133 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 134 | project |> Expect.hasFile "Test.fs" 135 | 136 | testCase "Add File - with project" <| fun _ -> 137 | let dir = "file_add_file_project" 138 | [ "new project -n Sample --dir src -t console --no-paket" 139 | "add file -p src/Sample/Sample.fsproj -n src/Sample/Test.fs " ] 140 | |> initTest dir 141 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 142 | project |> Expect.hasFile "Test.fs" 143 | 144 | testCase "Add File - absolute path" <| fun _ -> 145 | let dir = "file_add_file_absolute_path" |> makeAbsolute 146 | let p = dir "src" "Sample" "Test.fs" 147 | [ "new project -n Sample --dir src -t console --no-paket" 148 | sprintf "add file -n %s" p ] 149 | |> initTest dir 150 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 151 | project |> Expect.hasFile "Test.fs" 152 | 153 | testCase "Add File - with project, absolute path" <| fun _ -> 154 | let dir = "file_add_file_project_absolute_path" |> makeAbsolute 155 | let p = dir "src" "Sample" "Test.fs" 156 | let projectPath = dir "src" "Sample" "Sample.fsproj" 157 | [ "new project -n Sample --dir src -t console --no-paket" 158 | sprintf "add file -p %s -n %s " projectPath p ] 159 | |> initTest dir 160 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 161 | project |> Expect.hasFile "Test.fs" 162 | 163 | testCase "Add File - with project, absolute path, above" <| fun _ -> 164 | let dir = "file_add_file_project_absolute_path_above" |> makeAbsolute 165 | let p = dir "src" "Sample" "Test.fs" 166 | let projectPath = dir "src" "Sample" "Sample.fsproj" 167 | [ "new project -n Sample --dir src -t console --no-paket" 168 | sprintf "add file -p %s -n %s --above %s " projectPath p "Sample.fs" ] 169 | |> initTest dir 170 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 171 | project |> Expect.hasFile "Test.fs" 172 | 173 | testCase "Add File - with project, absolute path, below" <| fun _ -> 174 | let dir = "file_add_file_project_absolute_path_below" |> makeAbsolute 175 | let p = dir "src" "Sample" "Test.fs" 176 | let projectPath = dir "src" "Sample" "Sample.fsproj" 177 | [ "new project -n Sample --dir src -t console --no-paket" 178 | sprintf "add file -p %s -n %s --below %s " projectPath p "Sample.fs" ] 179 | |> initTest dir 180 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 181 | project |> Expect.hasFile "Test.fs" 182 | 183 | testCase "Add File - with project, absolute path, copy-to-output" <| fun _ -> 184 | let dir = "file_add_file_project_absolute_path_copy" |> makeAbsolute 185 | let p = dir "src" "Sample" "Test.fs" 186 | let projectPath = dir "src" "Sample" "Sample.fsproj" 187 | [ "new project -n Sample --dir src -t console --no-paket" 188 | sprintf "add file -p %s -n %s --copy-to-output always" projectPath p ] 189 | |> initTest dir 190 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 191 | project |> Expect.hasFile "Test.fs" 192 | ] 193 | testList "Remove file" [ 194 | testCase "Remove File" <| fun _ -> 195 | let dir = "file_remove_file" 196 | [ "new project -n Sample --dir src -t console --no-paket" 197 | "remove file -n src/Sample/Sample.fs" ] 198 | |> initTest dir 199 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 200 | project |> Expect.hasNotFile "Sample.fs" 201 | 202 | testCase "Remove File - with project" <| fun _ -> 203 | let dir = "file_remove_file_project" 204 | [ "new project -n Sample --dir src -t console --no-paket" 205 | "remove file -p src/Sample/Sample.fsproj -n src/Sample/Sample.fs " ] 206 | |> initTest dir 207 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 208 | project |> Expect.hasNotFile "Sample.fs" 209 | 210 | testCase "Remove File - absolute path" <| fun _ -> 211 | let dir = "file_remove_file_absolute_path" |> makeAbsolute 212 | let p = dir "src" "Sample" "Sample.fs" 213 | [ "new project -n Sample --dir src -t console --no-paket" 214 | sprintf "remove file -n %s" p ] 215 | |> initTest dir 216 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 217 | project |> Expect.hasNotFile "Sample.fs" 218 | 219 | testCase "Remove File - with project, absolute path" <| fun _ -> 220 | let dir = "file_remove_file_project_absolute_path" |> makeAbsolute 221 | let p = dir "src" "Sample" "Sample.fs" 222 | let projectPath = dir "src" "Sample" "Sample.fsproj" 223 | [ "new project -n Sample --dir src -t console --no-paket" 224 | sprintf "remove file -p %s -n %s " projectPath p ] 225 | |> initTest dir 226 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 227 | project |> Expect.hasNotFile "Sample.fs" 228 | ] 229 | testList "New file" [ 230 | testCase "Create new file giving path to fsproj" <| fun _ -> 231 | let dir = "new_file - path to fsproj" 232 | ["new project -n Sample --dir src -t console --no-paket" 233 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 234 | ] 235 | |> initTest dir 236 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 237 | project |> Expect.hasFile "Test.fs" 238 | 239 | testCase "Create new file without giving project name" <| fun _ -> 240 | let dir = "new_file - no path to project" 241 | ["new project -n Sample --dir src -t console --no-paket" 242 | "new file -n src/Sample/Test --template fs" 243 | ] 244 | |> initTest dir 245 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 246 | project |> Expect.hasFile "Test.fs" 247 | 248 | testCase "Create new file giving wrong project name" <| fun _ -> 249 | let dir = "new_file - wrong path to project" 250 | ["new project -n Sample --dir src -t console --no-paket" 251 | "new file -n src/Sample/Test --project ABC --template fs" 252 | ] 253 | |> initTest dir 254 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 255 | project |> Expect.hasFile "Test.fs" 256 | ] 257 | testList "ProjectScaffold" [ 258 | testCase "Create new scaffold" <| fun _ -> 259 | let dir = "new_scaffold" 260 | ["new scaffold"] 261 | |> initTest dir 262 | let path = getPath (dir "FSharp.ProjectScaffold.sln") 263 | Expect.isTrue (System.IO.File.Exists path) "should exist" 264 | 265 | testCase "Create new scaffold with spaces in folder" <| fun _ -> 266 | let dir = "new_scaffold with spaces" 267 | ["new scaffold"] 268 | |> initTest dir 269 | let path = getPath (dir "FSharp.ProjectScaffold.sln") 270 | Expect.isTrue (System.IO.File.Exists path) "should exist" 271 | ] 272 | testList "Rename file" [ 273 | testCase "Rename file changes name in fsproj" <| fun _ -> 274 | let dir = "rename_file" 275 | ["new project -n Sample --dir src -t console --no-paket" 276 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 277 | "rename file -n src/Sample/Test.fs -r src/Sample/Renamed.fs --project src/Sample/Sample.fsproj" 278 | ] 279 | |> initTest dir 280 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 281 | project |> Expect.hasFile "Renamed.fs" 282 | 283 | testCase "Rename file without project changes name in fsproj" <| fun _ -> 284 | let dir = "rename_file_no_project" 285 | ["new project -n Sample --dir src -t console --no-paket" 286 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 287 | "rename file -n src/Sample/Test.fs -r src/Sample/Renamed.fs" 288 | ] 289 | |> initTest dir 290 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 291 | project |> Expect.hasFile "Renamed.fs" 292 | 293 | testCase "Rename file nonexistent folder" <| fun _ -> 294 | let dir = "rename_file_nonexistent_folder" 295 | ["new project -n Sample --dir src -t console --no-paket" 296 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 297 | "rename file -n src/Sample/Test.fs -r src/Sample/Test/Renamed.fs" 298 | ] 299 | |> initTest dir 300 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 301 | project |> Expect.hasFile "Test.fs" 302 | 303 | testCase "Rename file existing file" <| fun _ -> 304 | let dir = "rename_file_existing_file" 305 | ["new project -n Sample --dir src -t console --no-paket" 306 | "new file -n src/Sample/Test --project src/Sample/Sample.fsproj --template fs" 307 | "rename file -n src/Sample/Test.fs -r src/Sample/Sample.fs" 308 | ] 309 | |> initTest dir 310 | let project = dir "src" "Sample" "Sample.fsproj" |> loadProject 311 | project |> Expect.hasFile "Test.fs" 312 | ] 313 | testList "List output" [ 314 | testCase "List files in fsproj" <| fun _ -> 315 | let s = "Sample.fs" + System.Environment.NewLine + "App.config" + System.Environment.NewLine 316 | let dir = "list_files" 317 | ["new project -n Sample --dir src -t console --no-paket"] 318 | |> initTest dir 319 | ["list files -p src/Sample/Sample.fsproj"] 320 | |> runForgeWithOutput 321 | |> Expecto.Flip.Expect.equal "should be equal" s 322 | 323 | testCase "List projects in folder" <| fun _ -> 324 | let s1 = "src" "Sample" "Sample.fsproj" 325 | let s2 = "src" "Sample2" "Sample2.fsproj" 326 | let s = s1 + System.Environment.NewLine + s2 + System.Environment.NewLine 327 | let dir = "list_projects" 328 | ["new project -n Sample --dir src -t console --no-paket" 329 | "new project -n Sample2 --dir src -t console --no-paket" 330 | ] 331 | |> initTest dir 332 | ["list projects --folder src"] 333 | |> runForgeWithOutput 334 | |> Expecto.Flip.Expect.equal "should be equal" s 335 | ] 336 | ] -------------------------------------------------------------------------------- /src/Forge.ProjectSystem/ResizeArray.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module Forge.ResizeArray 3 | 4 | /// 5 | /// Functional operators related to the System.Collections.Generic.List<T> type (called ResizeArray in F#). 6 | /// 7 | 8 | open System.Collections.Generic 9 | open LanguagePrimitives 10 | open OptimizedClosures 11 | 12 | 13 | /// Determines if a reference is a null reference, and if it is, throws an . 14 | let inline checkNonNull paramName arg = 15 | if isNull arg then 16 | if System.String.IsNullOrWhiteSpace paramName then 17 | raise ^ System.ArgumentNullException () 18 | else 19 | raise ^ System.ArgumentNullException paramName 20 | 21 | let keyNotFound (message : string) : 'T = 22 | if System.String.IsNullOrEmpty message then 23 | raise <| System.Collections.Generic.KeyNotFoundException () 24 | else 25 | raise <| System.Collections.Generic.KeyNotFoundException message 26 | 27 | 28 | let argOutOfRange (paramName : string) (message : string) : 'T = 29 | match System.String.IsNullOrEmpty paramName, System.String.IsNullOrEmpty message with 30 | | false, false -> 31 | raise <| System.ArgumentOutOfRangeException (paramName, message) 32 | | false, true -> 33 | raise <| System.ArgumentOutOfRangeException (paramName) 34 | | true, true -> 35 | raise <| System.ArgumentOutOfRangeException () 36 | | true, false -> 37 | raise <| System.ArgumentOutOfRangeException ("(Unspecified parameter)", message) 38 | 39 | /// Return the length of the collection. 40 | let inline length (resizeArray : ResizeArray<'T>) : int = 41 | resizeArray.Count 42 | 43 | /// Return true if the given array is empty, otherwise false. 44 | let inline isEmpty (resizeArray : ResizeArray<'T>) : bool = 45 | resizeArray.Count = 0 46 | 47 | /// Fetch an element from the collection. 48 | let inline get index (resizeArray : ResizeArray<'T>) : 'T = 49 | resizeArray.[index] 50 | 51 | /// Set the value of an element in the collection. 52 | let inline set index value (resizeArray : ResizeArray<'T>) : unit = 53 | resizeArray.[index] <- value 54 | 55 | /// Create a ResizeArray whose elements are all initially the given value. 56 | let create count value : ResizeArray<'T> = 57 | // Preconditions 58 | if count < 0 then 59 | invalidArg "count" "The number of elements may not be negative." 60 | 61 | let resizeArray = ResizeArray (count) 62 | for i = 0 to count - 1 do 63 | resizeArray.Add value 64 | resizeArray 65 | 66 | /// Create a ResizeArray by calling the given generator on each index. 67 | let init count initializer : ResizeArray<'T> = 68 | // Preconditions 69 | if count < 0 then 70 | invalidArg "count" "The number of elements may not be negative." 71 | 72 | let resizeArray = ResizeArray (count) 73 | for i = 0 to count - 1 do 74 | resizeArray.Add <| initializer count 75 | resizeArray 76 | 77 | /// Adds an object to the end of the ResizeArray. 78 | let inline add item (resizeArray : ResizeArray<'T>) = 79 | resizeArray.Add item 80 | resizeArray 81 | 82 | 83 | let inline insert index item (resizeArray : ResizeArray<'T>) = 84 | resizeArray.Insert(index, item) 85 | resizeArray 86 | 87 | 88 | let inline remove item (resizeArray : ResizeArray<'T>) = 89 | resizeArray.Remove item |> ignore 90 | resizeArray 91 | 92 | 93 | /// Determines whether an element is in the ResizeArray. 94 | let inline contains (value : 'T) (resizeArray : ResizeArray<'T>) : bool = 95 | // Preconditions 96 | checkNonNull "resizeArray" resizeArray 97 | 98 | resizeArray.Contains value 99 | 100 | /// Build a ResizeArray from the given sequence. 101 | let inline ofSeq (sequence : seq<'T>) : ResizeArray<'T> = 102 | ResizeArray (sequence) 103 | 104 | /// Build a ResizeArray from the given list. 105 | let ofList (list : 'T list) : ResizeArray<'T> = 106 | 107 | let len = list.Length 108 | let res = ResizeArray<_>(len) 109 | let rec add = function 110 | | [] -> () 111 | | e::l -> res.Add(e); add l 112 | add list 113 | res 114 | 115 | /// Build a ResizeArray from the given array. 116 | let inline ofArray (arr : 'T[]) : ResizeArray<'T> = 117 | ResizeArray (arr) 118 | 119 | 120 | 121 | /// Return a view of the ResizeArray as an enumerable object. 122 | let toSeq (resizeArray : ResizeArray<'T>) : seq<'T> = 123 | // Preconditions 124 | checkNonNull "resizeArray" resizeArray 125 | 126 | Seq.readonly resizeArray 127 | 128 | 129 | /// Build a list from the given ResizeArray. 130 | let toList (resizeArray : ResizeArray<'T>) : 'T list = 131 | // Preconditions 132 | checkNonNull "resizeArray" resizeArray 133 | 134 | let mutable res = [] 135 | for i = length resizeArray - 1 downto 0 do 136 | res <- resizeArray.[i] :: res 137 | res 138 | 139 | 140 | /// Return a fixed-length array containing the elements of the input ResizeArray. 141 | let inline toArray (resizeArray : ResizeArray<'T>) : 'T[] = 142 | resizeArray.ToArray () 143 | 144 | 145 | /// Sorts the elements of the ResizeArray by mutating the ResizeArray in-place. 146 | /// Elements are compared using Operators.compare. 147 | let inline sortInPlace<'T when 'T : comparison> (resizeArray : ResizeArray<'T>) : unit = 148 | resizeArray.Sort () 149 | 150 | 151 | /// Sort the elements using the key extractor and generic comparison on the keys. 152 | let inline sortInPlaceBy<'T, 'Key when 'Key : comparison> 153 | (projection : 'T -> 'Key) (resizeArray : ResizeArray<'T>) = 154 | resizeArray.Sort (fun x y -> 155 | compare (projection x) (projection y)) 156 | 157 | 158 | /// Sort the elements using the given comparison function. 159 | let inline sortInPlaceWith (comparer : 'T -> 'T -> int) (resizeArray : ResizeArray<'T>) : unit = 160 | resizeArray.Sort (comparer) 161 | 162 | 163 | /// Build a new ResizeArray that contains the elements of the given ResizeArray. 164 | let inline copy (resizeArray : ResizeArray<'T>) : ResizeArray<'T> = 165 | ResizeArray (resizeArray) 166 | 167 | 168 | /// Return an array containing the given element. 169 | let singleton value : ResizeArray<'T> = 170 | let resizeArray = ResizeArray () 171 | resizeArray.Add value 172 | resizeArray 173 | 174 | 175 | /// Build a new ResizeArray that contains the elements of each of the given sequence of ResizeArrays. 176 | let concat (resizeArrays : seq>) : ResizeArray<'T> = 177 | // Preconditions 178 | checkNonNull "resizeArrays" resizeArrays 179 | 180 | let flattened = ResizeArray () 181 | for resizeArray in resizeArrays do 182 | flattened.AddRange resizeArray 183 | flattened 184 | 185 | 186 | /// Build a new ResizeArray that contains the elements of the first ResizeArray followed by 187 | /// the elements of the second ResizeArray. 188 | let append (resizeArray1 : ResizeArray<'T>) (resizeArray2 : ResizeArray<'T>) : ResizeArray<'T> = 189 | // Preconditions 190 | checkNonNull "resizeArray1" resizeArray1 191 | checkNonNull "resizeArray2" resizeArray2 192 | 193 | let combined = ResizeArray (resizeArray1.Count + resizeArray2.Count) 194 | combined.AddRange resizeArray1 195 | combined.AddRange resizeArray2 196 | combined 197 | 198 | 199 | /// Return a new ResizeArray with the elements in reverse order. 200 | let rev (resizeArray : ResizeArray<'T>) : ResizeArray<'T> = 201 | // Preconditions 202 | checkNonNull "resizeArray" resizeArray 203 | 204 | let len = length resizeArray 205 | let result = ResizeArray (len) 206 | for i = len - 1 downto 0 do 207 | result.Add resizeArray.[i] 208 | result 209 | 210 | 211 | 212 | /// Test if any element of the array satisfies the given predicate. 213 | /// If the input function is f and the elements are i0...iN 214 | /// then computes p i0 or ... or p iN. 215 | let inline exists (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : bool = 216 | // Preconditions 217 | checkNonNull "resizeArray" resizeArray 218 | 219 | resizeArray.Exists (System.Predicate predicate) 220 | 221 | 222 | /// Test if all elements of the array satisfy the given predicate. 223 | /// If the input function is f and the elements are i0...iN and "j0...jN" 224 | /// then computes p i0 && ... && p iN. 225 | let inline forall (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : bool = 226 | // Preconditions 227 | checkNonNull "resizeArray" resizeArray 228 | resizeArray.TrueForAll (System.Predicate predicate) 229 | 230 | 231 | 232 | /// Return a new collection containing only the elements of the collection 233 | /// for which the given predicate returns true. 234 | let inline filter (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : ResizeArray<'T> = 235 | // Preconditions 236 | checkNonNull "resizeArray" resizeArray 237 | resizeArray.FindAll (System.Predicate predicate) 238 | 239 | 240 | /// 241 | /// Apply the given function to each element of the array. Return 242 | /// the array comprised of the results "x" for each element where 243 | /// the function returns Some(x). 244 | /// 245 | let choose (chooser : 'T -> 'U option) (resizeArray : ResizeArray<'T>) : ResizeArray<'U> = 246 | // Preconditions 247 | checkNonNull "resizeArray" resizeArray 248 | 249 | // OPTIMIZATION : If the input list is empty return immediately. 250 | if isEmpty resizeArray then 251 | ResizeArray () 252 | else 253 | let result = ResizeArray () 254 | let count = resizeArray.Count 255 | 256 | for i = 0 to count - 1 do 257 | match chooser resizeArray.[i] with 258 | | None -> () 259 | | Some value -> 260 | result.Add value 261 | 262 | result 263 | 264 | 265 | /// 266 | /// Return the first element for which the given function returns true. 267 | /// Return None if no such element exists. 268 | /// 269 | let tryFind (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : 'T option = 270 | // Preconditions 271 | checkNonNull "resizeArray" resizeArray 272 | match resizeArray.FindIndex ^ System.Predicate predicate with 273 | | -1 -> None 274 | | index -> Some resizeArray.[index] 275 | 276 | 277 | /// 278 | /// Return the first element for which the given function returns true. 279 | /// Raise KeyNotFoundException if no such element exists. 280 | /// 281 | let find (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : 'T = 282 | // Preconditions 283 | checkNonNull "resizeArray" resizeArray 284 | match resizeArray.FindIndex (System.Predicate predicate) with 285 | | -1 -> raise <| System.Collections.Generic.KeyNotFoundException () 286 | | index -> resizeArray.[index] 287 | 288 | 289 | /// Return the index of the first element in the array 290 | /// that satisfies the given predicate. 291 | let tryFindIndex (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : int option = 292 | // Preconditions 293 | checkNonNull "resizeArray" resizeArray 294 | match resizeArray.FindIndex ^ System.Predicate predicate with 295 | | -1 -> None 296 | | index -> Some index 297 | 298 | 299 | /// 300 | /// Return the index of the first element in the array 301 | /// that satisfies the given predicate. Raise KeyNotFoundException if 302 | /// none of the elements satisfy the predicate. 303 | /// 304 | let findIndex (predicate : 'T -> bool) (resizeArray : ResizeArray<'T>) : int = 305 | // Preconditions 306 | checkNonNull "resizeArray" resizeArray 307 | match resizeArray.FindIndex ^ System.Predicate predicate with 308 | | -1 -> raise <| System.Collections.Generic.KeyNotFoundException () 309 | | index -> index 310 | 311 | 312 | let indexOf elem (resizeArray : ResizeArray<'T>) = 313 | resizeArray.FindIndex ^ System.Predicate ((=) elem) 314 | 315 | 316 | /// Return the index of the first element in the array 317 | /// that satisfies the given predicate. 318 | let tryFindIndexi predicate (resizeArray : ResizeArray<'T>) : int option = 319 | // Preconditions 320 | checkNonNull "resizeArray" resizeArray 321 | 322 | let predicate = FSharpFunc<_,_,_>.Adapt predicate 323 | 324 | let lastIndex = length resizeArray - 1 325 | let mutable index = -1 326 | let mutable foundMatch = false 327 | while index < lastIndex && not foundMatch do 328 | let i = index + 1 329 | index <- i 330 | foundMatch <- predicate.Invoke (i, resizeArray.[i]) 331 | 332 | if foundMatch then 333 | Some index 334 | else None 335 | 336 | /// 337 | /// Return the index of the first element in the array 338 | /// that satisfies the given predicate. Raise KeyNotFoundException if 339 | /// none of the elements satisfy the predicate. 340 | /// 341 | let findIndexi predicate (resizeArray : ResizeArray<'T>) : int = 342 | // Preconditions 343 | checkNonNull "resizeArray" resizeArray 344 | 345 | match tryFindIndexi predicate resizeArray with 346 | | Some index -> 347 | index 348 | | None -> 349 | keyNotFound "An element satisfying the predicate was not found in the collection." 350 | 351 | /// 352 | /// Applies the given function to successive elements, returning the first 353 | /// result where function returns Some(x) for some x. If the function 354 | /// never returns Some(x), returns None. 355 | /// 356 | let tryPick (picker : 'T -> 'U option) (resizeArray : ResizeArray<'T>) : 'U option = 357 | // Preconditions 358 | checkNonNull "resizeArray" resizeArray 359 | 360 | let count = resizeArray.Count 361 | let mutable result = None 362 | let mutable index = 0 363 | 364 | while index < count && Option.isNone result do 365 | result <- picker resizeArray.[index] 366 | index <- index + 1 367 | result 368 | 369 | 370 | let swap (index1:int) (index2:int) (resizeArray : ResizeArray<'T>) = 371 | checkNonNull "resizeArray" resizeArray 372 | if index1 < 0 || index1 > resizeArray.Count then 373 | argOutOfRange "index1" (sprintf "'%i' - is outside the range of the resizeArray" index1) 374 | if index2 < 0 || index2 > resizeArray.Count then 375 | argOutOfRange "index1" (sprintf "'%i' - is outside the range of the resizeArray" index2) 376 | 377 | let t1,t2 = resizeArray.[index1], resizeArray.[index2] 378 | resizeArray.[index1] <- t2 379 | resizeArray.[index2] <- t1 380 | resizeArray 381 | 382 | /// 383 | /// Applies the given function to successive elements, returning the first 384 | /// result where function returns Some(x) for some x. If the function 385 | /// never returns Some(x), raises KeyNotFoundException. 386 | /// 387 | let pick (picker : 'T -> 'U option) (resizeArray : ResizeArray<'T>) : 'U = 388 | // Preconditions 389 | checkNonNull "resizeArray" resizeArray 390 | 391 | let count = resizeArray.Count 392 | let mutable result = None 393 | let mutable index = 0 394 | 395 | while index < count && Option.isNone result do 396 | result <- picker resizeArray.[index] 397 | index <- index + 1 398 | 399 | match result with 400 | | Some result -> 401 | result 402 | | None -> 403 | // TODO : Return a better error message 404 | //keyNotFound "" 405 | raise <| System.Collections.Generic.KeyNotFoundException () 406 | 407 | /// Apply the given function to each element of the array. 408 | let iter (action : 'T -> unit) (resizeArray : ResizeArray<'T>) : unit = 409 | // Preconditions 410 | checkNonNull "resizeArray" resizeArray 411 | 412 | let count = resizeArray.Count 413 | for i = 0 to count - 1 do 414 | action resizeArray.[i] 415 | 416 | /// Apply the given function to each element of the array. The integer passed to the 417 | /// function indicates the index of element. 418 | let iteri (action : int -> 'T -> unit) (resizeArray : ResizeArray<'T>) : unit = 419 | // Preconditions 420 | checkNonNull "resizeArray" resizeArray 421 | 422 | let action = FSharpFunc<_,_,_>.Adapt action 423 | 424 | let count = resizeArray.Count 425 | for i = 0 to count - 1 do 426 | action.Invoke (i, resizeArray.[i]) 427 | 428 | 429 | 430 | /// 431 | /// Build a new array whose elements are the results of applying the given function 432 | /// to each of the elements of the array. 433 | /// 434 | /// 435 | /// 436 | /// 437 | let inline map (mapping : 'T -> 'U) (resizeArray : ResizeArray<'T>) : ResizeArray<'U> = 438 | // Preconditions 439 | checkNonNull "resizeArray" resizeArray 440 | 441 | let len = length resizeArray 442 | let res = ResizeArray<_>(len) 443 | for i = 0 to len - 1 do 444 | res.Add(mapping resizeArray.[i]) 445 | res 446 | 447 | 448 | 449 | /// 450 | /// Split the collection into two collections, containing the elements for which 451 | /// the given predicate returns true and false respectively. 452 | /// 453 | /// 454 | /// 455 | /// 456 | let partition predicate (resizeArray : ResizeArray<'T>) : ResizeArray<'T> * ResizeArray<'T> = 457 | // Preconditions 458 | checkNonNull "resizeArray" resizeArray 459 | 460 | let trueResults = ResizeArray () 461 | let falseResults = ResizeArray () 462 | 463 | let len = length resizeArray 464 | for i = 0 to len - 1 do 465 | let el = resizeArray.[i] 466 | if predicate el then 467 | trueResults.Add el 468 | else 469 | falseResults.Add el 470 | 471 | trueResults, falseResults 472 | 473 | 474 | /// Returns the greatest of all elements of the ResizeArray, compared via Operators.max on the function result. 475 | /// The input ResizeArray. 476 | /// The maximum element. 477 | /// Thrown when is empty. 478 | let inline max (resizeArray : ResizeArray<'T>) = 479 | // Preconditions 480 | checkNonNull "resizeArray" resizeArray 481 | if resizeArray.Count = 0 then 482 | invalidArg "resizeArray" "The input collection is empty." 483 | 484 | let mutable acc = resizeArray.[0] 485 | for i = 1 to resizeArray.Count - 1 do 486 | let curr = resizeArray.[i] 487 | if curr > acc then 488 | acc <- curr 489 | acc 490 | --------------------------------------------------------------------------------