├── Source ├── Bumpy.Tests │ ├── xunit.runner.json │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── packages.config │ ├── CommandParserTests.cs │ └── Bumpy.Tests.csproj ├── Bumpy.Core.Tests │ ├── xunit.runner.json │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── FileInfoExtensionTests.cs │ ├── packages.config │ ├── Config │ │ ├── ConfigProcessorTests.cs │ │ └── ConfigIOTests.cs │ ├── GlobTests.cs │ ├── BumpyVersionTests.cs │ ├── CommandTests.cs │ ├── Bumpy.Core.Tests.csproj │ └── VersionFunctionsTests.cs ├── tools │ └── packages.config ├── .bumpyconfig ├── Bumpy │ ├── packages.config │ ├── App.config │ ├── CommandType.cs │ ├── ParserException.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── CommandArguments.cs │ ├── Program.cs │ ├── Bumpy.csproj │ ├── CommandParser.cs │ └── CommandRunner.cs ├── .editorconfig ├── changelog.cake ├── NuSpec │ ├── Chocolatey │ │ ├── VERIFICATION.txt │ │ └── Bumpy.Portable.nuspec │ └── NuGet │ │ └── Bumpy.nuspec ├── Bumpy.Core │ ├── Bumpy.Core.csproj │ ├── Config │ │ ├── ConfigException.cs │ │ ├── BumpyConfigEntry.cs │ │ ├── ConfigProcessor.cs │ │ ├── Template.cs │ │ ├── LegacyConfigIO.cs │ │ └── ConfigIO.cs │ ├── ValidationExtensions.cs │ ├── Glob.cs │ ├── FileContent.cs │ ├── BumpyArguments.cs │ ├── FileInfoExtensions.cs │ ├── IFileUtil.cs │ ├── FileUtil.cs │ ├── BumpyVersion.cs │ ├── VersionFunctions.cs │ └── Command.cs ├── Bumpy.ruleset ├── github.cake ├── artifact.cake ├── build.cake ├── Bumpy.sln └── build.ps1 ├── .gitattributes ├── DEVELOPMENT.md ├── Jenkinsfile ├── LICENSE.txt ├── .gitignore ├── CHANGELOG.md └── README.md /Source/Bumpy.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } 4 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/xunit.runner.json: -------------------------------------------------------------------------------- 1 | { 2 | "methodDisplay": "method" 3 | } 4 | -------------------------------------------------------------------------------- /Source/tools/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Source/.bumpyconfig: -------------------------------------------------------------------------------- 1 | [Bumpy\Properties\AssemblyInfo.cs | assembly] 2 | 3 | [NuSpec\**\*.nuspec | nuspec] 4 | 5 | [build.cake | nuspec] 6 | regex = var (?version) = "(?.*)" 7 | -------------------------------------------------------------------------------- /Source/Bumpy/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Source/Bumpy/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Text (replace CRLF with LF) 2 | *.cs text diff=csharp 3 | *.config text 4 | *.csproj text 5 | *.sln text 6 | *.md text 7 | *.ruleset text 8 | *.cake text 9 | *.ps1 text 10 | -------------------------------------------------------------------------------- /Source/Bumpy/CommandType.cs: -------------------------------------------------------------------------------- 1 | namespace Bumpy 2 | { 3 | public enum CommandType 4 | { 5 | Help, 6 | List, 7 | New, 8 | Increment, 9 | IncrementOnly, 10 | Write, 11 | Assign, 12 | Label, 13 | Ensure 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Source/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | 7 | [*.cs] 8 | indent_size = 4 9 | insert_final_newline = true 10 | charset = utf-8-bom 11 | trim_trailing_whitespace = true 12 | 13 | [*.csproj] 14 | indent_size = 2 15 | 16 | [*.{ruleset,config,nuspec}] 17 | indent_size = 2 18 | 19 | [*.json] 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /Source/changelog.cake: -------------------------------------------------------------------------------- 1 | public void EnsureChangelog(FilePath changelog, string version) 2 | { 3 | var regex = new System.Text.RegularExpressions.Regex($"# {version}"); 4 | var text = System.IO.File.ReadAllText(changelog.FullPath); 5 | 6 | if (!regex.IsMatch(text)) 7 | { 8 | throw new InvalidOperationException($"Version {version} not mentioned in CHANGELOG!"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Source/Bumpy/ParserException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Bumpy 4 | { 5 | [Serializable] 6 | public sealed class ParserException : Exception 7 | { 8 | public ParserException(string message) 9 | : base(message) 10 | { 11 | } 12 | 13 | public ParserException(string message, Exception innerException) 14 | : base(message, innerException) 15 | { 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /DEVELOPMENT.md: -------------------------------------------------------------------------------- 1 | # Development 2 | 3 | ## Tools 4 | 5 | - Windows 6 | - Visual Studio 2017 7 | - .NET Framework 4.6.1 8 | - Chocolatey 9 | - NuGet 10 | 11 | ## Setup to Publish a Release 12 | 13 | - Register Chocolatey/NuGet API keys using `choco setApikey` and `nuget setApikey` 14 | - Register a [GitHub token](https://github.com/settings/tokens) using the environment variable `GITHUB_RELEASE_TOKEN` 15 | 16 | ## Run a Build 17 | 18 | - **Build artifacts:** `.\build.ps1` 19 | - **Publish artifacts:** `.\build.ps1 -target publish` 20 | -------------------------------------------------------------------------------- /Source/NuSpec/Chocolatey/VERIFICATION.txt: -------------------------------------------------------------------------------- 1 | VERIFICATION 2 | Verification is intended to assist the Chocolatey moderators and community 3 | in verifying that this package's contents are trustworthy. 4 | 5 | This package is published by Florian Winkelbauer (fwinkelbauer). 6 | The bumpy.exe binary contained in the Chocolatey package is identical to the 7 | binary found in the NuGet package published at nuget.org [1]. 8 | Both packages are also published on GitHub [2]. 9 | 10 | [1] https://www.nuget.org/packages/Bumpy/ 11 | [2] https://github.com/fwinkelbauer/Bumpy/releases 12 | -------------------------------------------------------------------------------- /Source/Bumpy.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Bumpy.Tests")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("Bumpy.Tests")] 9 | [assembly: AssemblyCopyright("Copyright © fwinkelbauer 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("cd9ae6dd-c13a-448a-8092-37b4efaab431")] 14 | [assembly: AssemblyVersion("1.0.0.0")] 15 | [assembly: AssemblyFileVersion("1.0.0.0")] 16 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | [assembly: AssemblyTitle("Bumpy.Core.Tests")] 5 | [assembly: AssemblyDescription("")] 6 | [assembly: AssemblyConfiguration("")] 7 | [assembly: AssemblyCompany("")] 8 | [assembly: AssemblyProduct("Bumpy.Core.Tests")] 9 | [assembly: AssemblyCopyright("Copyright © fwinkelbauer 2018")] 10 | [assembly: AssemblyTrademark("")] 11 | [assembly: AssemblyCulture("")] 12 | [assembly: ComVisible(false)] 13 | [assembly: Guid("f0bda27c-22cf-4a88-a045-d389f4c4c477")] 14 | [assembly: AssemblyVersion("1.0.0.0")] 15 | [assembly: AssemblyFileVersion("1.0.0.0")] 16 | -------------------------------------------------------------------------------- /Source/Bumpy/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: AssemblyTitle("Bumpy")] 6 | [assembly: AssemblyDescription("")] 7 | [assembly: AssemblyConfiguration("")] 8 | [assembly: AssemblyCompany("")] 9 | [assembly: AssemblyProduct("Bumpy")] 10 | [assembly: AssemblyCopyright("Copyright © fwinkelbauer 2018")] 11 | [assembly: AssemblyTrademark("")] 12 | [assembly: AssemblyCulture("")] 13 | [assembly: CLSCompliant(true)] 14 | [assembly: ComVisible(false)] 15 | [assembly: Guid("2115a19d-9ab9-4abc-8816-b9c49298e04e")] 16 | [assembly: AssemblyVersion("0.11.0.0")] 17 | [assembly: AssemblyFileVersion("0.11.0.0")] 18 | -------------------------------------------------------------------------------- /Source/Bumpy/CommandArguments.cs: -------------------------------------------------------------------------------- 1 | using Bumpy.Core; 2 | 3 | namespace Bumpy 4 | { 5 | public sealed class CommandArguments 6 | { 7 | public CommandArguments(CommandType cmdType, int position, string formattedNumber, string text, BumpyArguments arguments) 8 | { 9 | CmdType = cmdType; 10 | Position = position; 11 | FormattedNumber = formattedNumber; 12 | Text = text; 13 | Arguments = arguments; 14 | } 15 | 16 | public CommandType CmdType { get; } 17 | 18 | public int Position { get; } 19 | 20 | public string FormattedNumber { get; } 21 | 22 | public string Text { get; } 23 | 24 | public BumpyArguments Arguments { get; } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Bumpy.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | ..\Bumpy.ruleset 6 | 7 | 8 | 9 | bin\Debug\netstandard2.0\Bumpy.Core.xml 10 | 11 | 12 | 13 | bin\Release\netstandard2.0\Bumpy.Core.xml 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | triggers { 5 | pollSCM('') 6 | } 7 | 8 | options { 9 | buildDiscarder(logRotator(numToKeepStr: '100')) 10 | } 11 | 12 | stages { 13 | stage('Checkout') { 14 | steps { 15 | checkout scm 16 | } 17 | } 18 | 19 | stage('Build') { 20 | steps { 21 | powershell '''$ErrorActionPreference = "Stop" 22 | cd ./Source 23 | ./build.ps1''' 24 | } 25 | } 26 | 27 | stage('Archive') { 28 | steps { 29 | archiveArtifacts 'Artifacts/**/*.nupkg' 30 | } 31 | } 32 | } 33 | 34 | post { 35 | always { 36 | deleteDir() 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/FileInfoExtensionTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Xunit; 3 | 4 | namespace Bumpy.Core.Tests 5 | { 6 | public class FileInfoExtensionTests 7 | { 8 | [Fact] 9 | public void ToRelativePath_SimpleCheck() 10 | { 11 | var file = new FileInfo(@"C:\tmp\foo.txt"); 12 | var relativePath = file.ToRelativePath(file.Directory); 13 | 14 | Assert.Equal("foo.txt", relativePath); 15 | } 16 | 17 | [Fact] 18 | public void ToRelativePath_AdvancedCheck() 19 | { 20 | var file = new FileInfo(@"C:\project\source\foo.txt"); 21 | var directory = new DirectoryInfo("C:/"); 22 | var relativePath = file.ToRelativePath(directory); 23 | 24 | Assert.Equal(@"project\source\foo.txt", relativePath); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Source/Bumpy.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Source/Bumpy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Bumpy.Core; 3 | 4 | namespace Bumpy 5 | { 6 | public static class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | try 11 | { 12 | var command = new Command(new FileUtil(), Console.WriteLine); 13 | var commandArguments = new CommandParser().ParseArguments(args); 14 | var runner = new CommandRunner(command, commandArguments); 15 | runner.Execute(); 16 | } 17 | catch (Exception e) 18 | { 19 | Console.Error.WriteLine($"Error: {e.Message}"); 20 | Environment.ExitCode = 1; 21 | } 22 | 23 | #if DEBUG 24 | Console.WriteLine("Press ENTER to exit..."); 25 | Console.ReadLine(); 26 | #endif 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/ConfigException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Bumpy.Core.Config 4 | { 5 | /// 6 | /// A custom exception used for all Configuration classes. 7 | /// 8 | [Serializable] 9 | public sealed class ConfigException : Exception 10 | { 11 | /// 12 | /// Initializes a new instance of the class. 13 | /// 14 | /// The Exception message 15 | public ConfigException(string message) 16 | : base(message) 17 | { 18 | } 19 | 20 | /// 21 | /// Initializes a new instance of the class. 22 | /// 23 | /// The Exception message 24 | /// The inner Exception 25 | public ConfigException(string message, Exception innerException) 26 | : base(message, innerException) 27 | { 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Florian Winkelbauer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/ValidationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | 4 | namespace Bumpy.Core 5 | { 6 | /// 7 | /// A set of validation extensions. 8 | /// 9 | [DebuggerStepThrough] 10 | internal static class ValidationExtensions 11 | { 12 | /// 13 | /// Throws an ArgumentOutOfRangeException if a specified criteria is not met. 14 | /// 15 | /// The actual value to be checked 16 | /// A condition which should apply to the value 17 | /// The name of the value 18 | /// An error message that should be included if an Exception is thrown 19 | /// The specified value 20 | public static int ThrowIfOutOfRange(this int value, Func condition, string paramName, string message) 21 | { 22 | if (condition(value)) 23 | { 24 | throw new ArgumentOutOfRangeException(paramName, message); 25 | } 26 | 27 | return value; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Source/Bumpy.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Glob.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Bumpy.Core 4 | { 5 | /// 6 | /// A glob pattern class. 7 | /// 8 | public sealed class Glob 9 | { 10 | private readonly Regex _regex; 11 | 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The glob pattern 16 | public Glob(string searchPattern) 17 | { 18 | var unifiedPattern = searchPattern.Replace("\\", "/"); 19 | var escapedPattern = Regex.Escape(unifiedPattern) 20 | .Replace(@"\*", ".*") 21 | .Replace(@"\?", ".") 22 | .Replace("/.*.*/", "/(.*.*/)?") 23 | .Replace("/", @"[\\\/]"); 24 | 25 | _regex = new Regex($"{escapedPattern}$"); 26 | } 27 | 28 | /// 29 | /// Returns true if the input matches the given glob pattern. 30 | /// 31 | /// An input to check 32 | /// True if the input matches the given glob pattern, else false 33 | public bool IsMatch(string input) 34 | { 35 | return _regex.IsMatch(input); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/FileContent.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace Bumpy.Core 6 | { 7 | /// 8 | /// A class which encapsulates a file, its encoding and its content 9 | /// 10 | public sealed class FileContent 11 | { 12 | /// 13 | /// Initializes a new instance of the class. 14 | /// 15 | /// The underlying file 16 | /// The file content 17 | /// The encoding used to read the file content 18 | public FileContent(FileInfo file, IEnumerable lines, Encoding encoding) 19 | { 20 | File = file; 21 | Lines = lines; 22 | Encoding = encoding; 23 | } 24 | 25 | /// 26 | /// Gets the object. 27 | /// 28 | public FileInfo File { get; } 29 | 30 | /// 31 | /// Gets the file content. 32 | /// 33 | public IEnumerable Lines { get; } 34 | 35 | /// 36 | /// Gets the file encoding. 37 | /// 38 | public Encoding Encoding { get; } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/Config/ConfigProcessorTests.cs: -------------------------------------------------------------------------------- 1 | using Bumpy.Core.Config; 2 | using Xunit; 3 | 4 | namespace Bumpy.Core.Tests.Config 5 | { 6 | public class ConfigProcessorTests 7 | { 8 | [Fact] 9 | public void Process_GlobCannotBeEmpty() 10 | { 11 | Assert.Throws(() => ConfigProcessor.Process(new BumpyConfigEntry())); 12 | Assert.Throws(() => ConfigProcessor.Process(new BumpyConfigEntry { Glob = string.Empty })); 13 | Assert.Throws(() => ConfigProcessor.Process(new BumpyConfigEntry { Glob = null })); 14 | Assert.Throws(() => ConfigProcessor.Process(new BumpyConfigEntry { Glob = " " })); 15 | } 16 | 17 | [Fact] 18 | public void Process_ApplyTemplate() 19 | { 20 | var entry = new BumpyConfigEntry { Glob = "*.csproj" }; 21 | 22 | var newEntry = ConfigProcessor.Process(entry); 23 | 24 | Assert.False(string.IsNullOrWhiteSpace(newEntry.Regex)); 25 | } 26 | 27 | [Fact] 28 | public void Process_NoTemplateFound() 29 | { 30 | var entry = new BumpyConfigEntry { Glob = "unsupported.foo" }; 31 | 32 | Assert.Throws(() => ConfigProcessor.Process(entry)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/NuSpec/NuGet/Bumpy.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Bumpy 5 | Bumpy 6 | 0.11.0 7 | fwinkelbauer 8 | fwinkelbauer 9 | https://github.com/fwinkelbauer/Bumpy 10 | https://github.com/fwinkelbauer/Bumpy/blob/master/LICENSE.txt 11 | https://github.com/fwinkelbauer/Bumpy/blob/master/CHANGELOG.md 12 | false 13 | A tool to manipulate version information across multiple files. 14 | Bumpy is a tool to manipulate version information across multiple files found in the current working directory using a configuration file which consists of glob patterns and regular expressions. 15 | Copyright (c) 2018 Florian Winkelbauer 16 | Versioning Build AssemblyInfo NuSpec 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/BumpyArguments.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Bumpy.Core.Config; 3 | 4 | namespace Bumpy.Core 5 | { 6 | /// 7 | /// An arguments object which can be used to change the behavior 8 | /// of Bumpy's major commands. 9 | /// 10 | public sealed class BumpyArguments 11 | { 12 | /// 13 | /// Gets or sets the path to a configuration file (e.g.: ".bumpyconfig"). 14 | /// 15 | public FileInfo ConfigFile { get; set; } = new FileInfo(BumpyConfigEntry.DefaultConfigFile); 16 | 17 | /// 18 | /// Gets or sets the working directory (e.g. "."). 19 | /// 20 | public DirectoryInfo WorkingDirectory { get; set; } = new DirectoryInfo("."); 21 | 22 | /// 23 | /// Gets or sets a value indicating whether the "no operation" (preview) mode 24 | /// should be used. Enabling this flag prevents Bumpy's core operations 25 | /// from changing files on disk. 26 | /// 27 | public bool NoOperation { get; set; } 28 | 29 | /// 30 | /// Gets or sets the profile named used in Bumpy's core operations. 31 | /// Profiles can be defined in Bumpy's configuration file. 32 | /// 33 | public string Profile { get; set; } = BumpyConfigEntry.DefaultProfile; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/FileInfoExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace Bumpy.Core 5 | { 6 | /// 7 | /// A set of extension methods for objects. 8 | /// 9 | public static class FileInfoExtensions 10 | { 11 | /// 12 | /// Creates a string containing the relative path of a file. 13 | /// 14 | /// The file for which we want a relative path 15 | /// The directory on which the relative path is based on 16 | /// The relative path of a file 17 | public static string ToRelativePath(this FileInfo file, DirectoryInfo parent) 18 | { 19 | var fileName = file.FullName; 20 | var parentName = parent.FullName; 21 | 22 | if (!fileName.StartsWith(parentName, StringComparison.OrdinalIgnoreCase)) 23 | { 24 | throw new ArgumentException($"'{parentName}' is not a parent directory of '{fileName}'"); 25 | } 26 | 27 | int count = parentName.Length; 28 | 29 | if (!parentName.EndsWith("/", StringComparison.OrdinalIgnoreCase) 30 | && !parentName.EndsWith("\\", StringComparison.OrdinalIgnoreCase)) 31 | { 32 | count++; 33 | } 34 | 35 | return fileName.Substring(count); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/BumpyConfigEntry.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace Bumpy.Core.Config 4 | { 5 | /// 6 | /// A simple configuration class. 7 | /// 8 | public class BumpyConfigEntry 9 | { 10 | /// 11 | /// The name of a default Bumpy configuration file. 12 | /// 13 | public const string DefaultConfigFile = ".bumpyconfig"; 14 | 15 | /// 16 | /// The default profile in a Bumpy configuration file if the user does 17 | /// not specify a profile. 18 | /// 19 | public const string DefaultProfile = ""; 20 | 21 | /// 22 | /// Initializes a new instance of the class. 23 | /// 24 | public BumpyConfigEntry() 25 | { 26 | Glob = string.Empty; 27 | Profile = DefaultProfile; 28 | Encoding = new UTF8Encoding(false); 29 | Regex = string.Empty; 30 | } 31 | 32 | /// 33 | /// Gets or sets the glob pattern. 34 | /// 35 | public string Glob { get; set; } 36 | 37 | /// 38 | /// Gets or sets the profile. 39 | /// 40 | public string Profile { get; set; } 41 | 42 | /// 43 | /// Gets or sets the encoding. 44 | /// 45 | public Encoding Encoding { get; set; } 46 | 47 | /// 48 | /// Gets or sets the regular expression. 49 | /// 50 | public string Regex { get; set; } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/NuSpec/Chocolatey/Bumpy.Portable.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | bumpy.portable 5 | Bumpy 6 | 0.11.0 7 | fwinkelbauer 8 | fwinkelbauer 9 | https://github.com/fwinkelbauer/Bumpy 10 | https://github.com/fwinkelbauer/Bumpy 11 | https://github.com/fwinkelbauer/Bumpy 12 | https://github.com/fwinkelbauer/Bumpy/blob/master/CHANGELOG.md 13 | https://github.com/fwinkelbauer/Bumpy/issues 14 | https://github.com/fwinkelbauer/Bumpy/blob/master/LICENSE.txt 15 | false 16 | A tool to manipulate version information across multiple files. 17 | Bumpy is a tool to manipulate version information across multiple files found in the current working directory using a configuration file which consists of glob patterns and regular expressions. 18 | Copyright (c) 2018 Florian Winkelbauer 19 | Versioning Build AssemblyInfo NuSpec 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/ConfigProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace Bumpy.Core.Config 2 | { 3 | /// 4 | /// A postprocessor used to validate and enrich instances of 5 | /// 6 | /// 7 | public static class ConfigProcessor 8 | { 9 | /// 10 | /// Validates instances of . Will also enrich an instance 11 | /// with additional data. 12 | /// 13 | /// The object to validate and enrich 14 | /// The given object or a new enriched object 15 | public static BumpyConfigEntry Process(BumpyConfigEntry entry) 16 | { 17 | if (string.IsNullOrWhiteSpace(entry.Glob)) 18 | { 19 | throw new ConfigException("Glob cannot be empty"); 20 | } 21 | 22 | if (string.IsNullOrWhiteSpace(entry.Regex)) 23 | { 24 | return ApplyTemplate(entry); 25 | } 26 | 27 | return entry; 28 | } 29 | 30 | private static BumpyConfigEntry ApplyTemplate(BumpyConfigEntry entry) 31 | { 32 | if (Template.TryFindTemplate(entry.Glob, out var template)) 33 | { 34 | return new BumpyConfigEntry 35 | { 36 | Glob = entry.Glob, 37 | Profile = entry.Profile, 38 | Encoding = template.Encoding, 39 | Regex = template.Regex 40 | }; 41 | } 42 | 43 | throw new ConfigException($"Could not find a template for glob '{entry.Glob}'"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Source/github.cake: -------------------------------------------------------------------------------- 1 | #addin nuget:?package=Octokit&version=0.29.0 2 | 3 | public class GitHubSettings 4 | { 5 | public string Owner { get; set; } 6 | public string Repository { get; set; } 7 | public string Token { get; set; } 8 | public string GitTag { get; set; } 9 | public string TextBody { get; set; } 10 | public bool IsDraft { get; set; } 11 | public bool IsPrerelease { get; set; } 12 | } 13 | 14 | public class GitHubAsset 15 | { 16 | public GitHubAsset(FilePath artifact, string contentType) 17 | { 18 | Artifact = artifact; 19 | ContentType = contentType; 20 | } 21 | 22 | public FilePath Artifact { get; } 23 | public string ContentType { get; } 24 | } 25 | 26 | public void PublishGitHubReleaseWithArtifacts(GitHubSettings settings, params GitHubAsset[] assets) 27 | { 28 | var client = new Octokit.GitHubClient(new Octokit.ProductHeaderValue(settings.Repository)) 29 | { 30 | Credentials = new Octokit.Credentials(settings.Token) 31 | }; 32 | 33 | var newRelease = new Octokit.NewRelease(settings.GitTag) 34 | { 35 | Name = settings.GitTag, 36 | Body = settings.TextBody, 37 | Draft = settings.IsDraft, 38 | Prerelease = settings.IsPrerelease 39 | }; 40 | 41 | var publishedRelease = client.Repository.Release.Create(settings.Owner, settings.Repository, newRelease).Result; 42 | 43 | Information("Created GitHub release {0}", settings.GitTag); 44 | 45 | foreach (var asset in assets) 46 | { 47 | var fileName = asset.Artifact.GetFilename().FullPath; 48 | 49 | var assetUpload = new Octokit.ReleaseAssetUpload 50 | { 51 | FileName = fileName, 52 | ContentType = asset.ContentType, 53 | RawData = System.IO.File.OpenRead(asset.Artifact.FullPath) 54 | }; 55 | 56 | var publishedAsset = client.Repository.Release.UploadAsset(publishedRelease, assetUpload).Result; 57 | 58 | Information("Published artifact {0}", fileName); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/GlobTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Bumpy.Core.Tests 4 | { 5 | public class GlobTests 6 | { 7 | [Fact] 8 | public void Glob_WildcardMatch() 9 | { 10 | var glob = new Glob("**/Bumpy/**/AssemblyInfo.cs"); 11 | 12 | Assert.True(glob.IsMatch("./Source/Bumpy/Properties/AssemblyInfo.cs")); 13 | Assert.True(glob.IsMatch("./Bumpy/Properties/AssemblyInfo.cs")); 14 | Assert.True(glob.IsMatch("./Bumpy/AssemblyInfo.cs")); 15 | 16 | Assert.False(glob.IsMatch("./Source/Bumpy.Test/Properties/AssemblyInfo.cs")); 17 | Assert.False(glob.IsMatch("./Bumpy.Test/Properties/AssemblyInfo.cs")); 18 | } 19 | 20 | [Fact] 21 | public void Glob_AnotherWildcardMatch() 22 | { 23 | var glob = new Glob("**/AssemblyInfo.cs"); 24 | 25 | Assert.True(glob.IsMatch("./AssemblyInfo.cs")); 26 | Assert.True(glob.IsMatch("./Source/Bumpy/Properties/AssemblyInfo.cs")); 27 | 28 | Assert.False(glob.IsMatch("./FooAssemblyInfo.cs")); 29 | Assert.False(glob.IsMatch("./AssemblyInfo.cs.backup")); 30 | } 31 | 32 | [Fact] 33 | public void Glob_PreciseMatch() 34 | { 35 | var glob = new Glob("AssemblyInfo.cs"); 36 | 37 | Assert.True(glob.IsMatch("AssemblyInfo.cs")); 38 | Assert.True(glob.IsMatch("./Source/Bumpy/Properties/AssemblyInfo.cs")); 39 | Assert.True(glob.IsMatch("./FooAssemblyInfo.cs")); 40 | 41 | Assert.False(glob.IsMatch("./AssemblyInfo.cs.backup")); 42 | } 43 | 44 | [Fact] 45 | public void Glob_FileSeparatorMatch() 46 | { 47 | FileSeparatorTest(new Glob("Properties/AssemblyInfo.cs")); 48 | FileSeparatorTest(new Glob("Properties\\AssemblyInfo.cs")); 49 | } 50 | 51 | private void FileSeparatorTest(Glob glob) 52 | { 53 | Assert.True(glob.IsMatch("./Properties/AssemblyInfo.cs")); 54 | Assert.True(glob.IsMatch(".\\Properties\\AssemblyInfo.cs")); 55 | Assert.True(glob.IsMatch(@".\Properties\AssemblyInfo.cs")); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/IFileUtil.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using Bumpy.Core.Config; 5 | 6 | namespace Bumpy.Core 7 | { 8 | /// 9 | /// An abstraction for all file system based operations. 10 | /// 11 | public interface IFileUtil 12 | { 13 | /// 14 | /// Returns all files in a directory which match a glob pattern. 15 | /// 16 | /// The base directory 17 | /// The search glob pattern 18 | /// A list of files which match the glob pattern 19 | IEnumerable GetFiles(DirectoryInfo directory, Glob glob); 20 | 21 | /// 22 | /// Returns the content of a file. 23 | /// 24 | /// The file name 25 | /// The encoding with which the file should be read 26 | /// A representation of the file, it's encoding and the read content 27 | FileContent ReadFileContent(FileInfo file, Encoding encoding); 28 | 29 | /// 30 | /// Writes a file to disk. 31 | /// 32 | /// The file, it's encoding and content which should be written 33 | void WriteFileContent(FileContent fileContent); 34 | 35 | /// 36 | /// Creates a default config file for Bumpy. 37 | /// 38 | /// The location of the config file to create 39 | /// True if the file was created, false if it did already exist 40 | bool CreateConfigFile(FileInfo configFile); 41 | 42 | /// 43 | /// Reads and returns Bumpy's configuration from a file. 44 | /// 45 | /// The config file to read 46 | /// A profile which should filter the return set 47 | /// All read configuration of a given profile 48 | IEnumerable ReadConfigFile(FileInfo configFile, string profile); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Source/artifact.cake: -------------------------------------------------------------------------------- 1 | public DirectoryPath RepositoryHome = "."; 2 | 3 | void CleanArtifacts() 4 | { 5 | CleanDirectory($"{RepositoryHome}/Artifacts"); 6 | } 7 | 8 | void StoreBuildArtifacts(string projectName, string filePattern) 9 | { 10 | StoreBuildArtifacts(projectName, GetFiles(filePattern)); 11 | } 12 | 13 | void StoreBuildArtifacts(string projectName, IEnumerable filePaths) 14 | { 15 | var dir = $"{RepositoryHome}/Artifacts/Build/{projectName}"; 16 | 17 | Information("Copying artifacts to {0}", dir); 18 | EnsureDirectoryExists(dir); 19 | CopyFiles(filePaths, dir, true); 20 | } 21 | 22 | FilePath GetChocolateyArtifact(string packageId) 23 | { 24 | return GetFiles($"{RepositoryHome}/Artifacts/Chocolatey/{packageId}/*.nupkg").First(); 25 | } 26 | 27 | void PublishChocolateyArtifact(string packageId, string pushSource) 28 | { 29 | ChocolateyPush(GetChocolateyArtifact(packageId), new ChocolateyPushSettings { Source = pushSource }); 30 | } 31 | 32 | void PackChocolateyArtifacts(string nuspecPathPattern) 33 | { 34 | foreach (var nuspecPath in GetFiles(nuspecPathPattern)) 35 | { 36 | PackChocolateyArtifact(nuspecPath); 37 | } 38 | } 39 | 40 | void PackChocolateyArtifact(FilePath nuspecPath) 41 | { 42 | var dir = $"{RepositoryHome}/Artifacts/Chocolatey/{nuspecPath.GetFilenameWithoutExtension()}"; 43 | 44 | EnsureDirectoryExists(dir); 45 | ChocolateyPack(nuspecPath, new ChocolateyPackSettings { OutputDirectory = dir }); 46 | } 47 | 48 | FilePath GetNuGetArtifact(string packageId) 49 | { 50 | return GetFiles($"{RepositoryHome}/Artifacts/NuGet/{packageId}/*.nupkg").First(); 51 | } 52 | 53 | void PublishNuGetArtifact(string packageId, string pushSource) 54 | { 55 | var files = GetNuGetArtifact(packageId); 56 | 57 | NuGetPush(files, new NuGetPushSettings { Source = pushSource }); 58 | } 59 | 60 | void PackNuGetArtifacts(string nuspecPathPattern) 61 | { 62 | foreach (var nuspecPath in GetFiles(nuspecPathPattern)) 63 | { 64 | PackNuGetArtifact(nuspecPath); 65 | } 66 | } 67 | 68 | void PackNuGetArtifact(FilePath nuspecPath) 69 | { 70 | var dir = $"{RepositoryHome}/Artifacts/NuGet/{nuspecPath.GetFilenameWithoutExtension()}"; 71 | 72 | EnsureDirectoryExists(dir); 73 | NuGetPack(nuspecPath, new NuGetPackSettings { OutputDirectory = dir }); 74 | } 75 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/Template.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Bumpy.Core.Config 7 | { 8 | /// 9 | /// A template is used to provide default parameters for a 10 | /// instance for known file types. 11 | /// 12 | internal sealed class Template 13 | { 14 | private static readonly Dictionary Templates = new Dictionary() 15 | { 16 | { ".csproj", new Template(@"<(?[Vv]ersion)>(?\d+\.\d+\.\d+.*)<\/[Vv]ersion>", new UTF8Encoding(false)) }, 17 | { ".nuspec", new Template(@"<(?[Vv]ersion)>(?\d+\.\d+\.\d+.*)<\/[Vv]ersion>", new UTF8Encoding(false)) }, 18 | { "AssemblyInfo.cs", new Template(@"(?Assembly(File)?Version).*(?\d+\.\d+\.\d+\.\d+)", new UTF8Encoding(true)) }, 19 | { ".cs", new Template("(?(FILEVERSION|PRODUCTVERSION|FileVersion|ProductVersion))[\", ]*(?\\d+[\\.,]\\d+[\\.,]\\d+[\\.,]\\d+)", Encoding.GetEncoding(1200)) } 20 | }; 21 | 22 | private Template(string regularExpression, Encoding encoding) 23 | { 24 | Regex = regularExpression; 25 | Encoding = encoding; 26 | } 27 | 28 | /// 29 | /// Gets the regular expression. 30 | /// 31 | public string Regex { get; } 32 | 33 | /// 34 | /// Gets the encoding. 35 | /// 36 | public Encoding Encoding { get; } 37 | 38 | /// 39 | /// Tries to find a template based on the input text. 40 | /// 41 | /// The given text 42 | /// A template if one is registered for the given text 43 | /// True if a template was found, else false 44 | public static bool TryFindTemplate(string text, out Template template) 45 | { 46 | var matches = Templates.Where(t => text.EndsWith(t.Key, StringComparison.OrdinalIgnoreCase)).ToList(); 47 | template = null; 48 | 49 | if (matches.Any()) 50 | { 51 | template = matches[0].Value; 52 | return true; 53 | } 54 | 55 | return false; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Source/build.cake: -------------------------------------------------------------------------------- 1 | #load "artifact.cake" 2 | #load "changelog.cake" 3 | #load "github.cake" 4 | 5 | #tool nuget:?package=xunit.runner.console&version=2.3.1 6 | 7 | #addin nuget:?package=Cake.Bumpy&version=0.8.0 8 | 9 | var target = Argument("target", "Default"); 10 | var configuration = Argument("configuration", "Release"); 11 | var version = "0.11.0"; 12 | 13 | RepositoryHome = ".."; 14 | 15 | var gitHubSettings = new GitHubSettings 16 | { 17 | Owner = "fwinkelbauer", 18 | Repository = "Bumpy", 19 | Token = EnvironmentVariable("GITHUB_RELEASE_TOKEN"), 20 | GitTag = version, 21 | TextBody = "More information about this release can be found in the [changelog](https://github.com/fwinkelbauer/Bumpy/blob/master/CHANGELOG.md)", 22 | IsDraft = true, 23 | IsPrerelease = false 24 | }; 25 | 26 | Task("Clean").Does(() => 27 | { 28 | CleanArtifacts(); 29 | CleanDirectories($"Bumpy*/bin/{configuration}"); 30 | CleanDirectories($"Bumpy*/obj/{configuration}"); 31 | }); 32 | 33 | Task("Restore").Does(() => 34 | { 35 | NuGetRestore("Bumpy.sln"); 36 | }); 37 | 38 | Task("Build").Does(() => 39 | { 40 | MSBuild("Bumpy.sln", new MSBuildSettings { Configuration = configuration, WarningsAsError = true }); 41 | StoreBuildArtifacts("Bumpy", $"Bumpy/bin/{configuration}/**/*"); 42 | }); 43 | 44 | Task("Test").Does(() => 45 | { 46 | XUnit2($"*.Tests/bin/{configuration}/*.Tests.dll"); 47 | }); 48 | 49 | Task("CreatePackages").Does(() => 50 | { 51 | PackChocolateyArtifacts("NuSpec/Chocolatey/**/*.nuspec"); 52 | PackNuGetArtifacts("NuSpec/NuGet/**/*.nuspec"); 53 | }); 54 | 55 | Task("PushPackages").Does(() => 56 | { 57 | BumpyEnsure(); 58 | EnsureChangelog("../CHANGELOG.md", version); 59 | 60 | PublishChocolateyArtifact("Bumpy.Portable", "https://push.chocolatey.org/"); 61 | PublishNuGetArtifact("Bumpy", "https://www.nuget.org/api/v2/package"); 62 | 63 | var mime = "application/zip"; 64 | PublishGitHubReleaseWithArtifacts( 65 | gitHubSettings, 66 | new GitHubAsset(GetChocolateyArtifact("Bumpy.Portable"), mime), 67 | new GitHubAsset(GetNuGetArtifact("Bumpy"), mime)); 68 | }); 69 | 70 | Task("Default") 71 | .IsDependentOn("Clean") 72 | .IsDependentOn("Restore") 73 | .IsDependentOn("Build") 74 | .IsDependentOn("Test") 75 | .IsDependentOn("CreatePackages"); 76 | 77 | Task("Publish") 78 | .IsDependentOn("Clean") 79 | .IsDependentOn("Restore") 80 | .IsDependentOn("Build") 81 | .IsDependentOn("Test") 82 | .IsDependentOn("CreatePackages") 83 | .IsDependentOn("PushPackages"); 84 | 85 | RunTarget(target); 86 | -------------------------------------------------------------------------------- /Source/Bumpy.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27130.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bumpy", "Bumpy\Bumpy.csproj", "{2115A19D-9AB9-4ABC-8816-B9C49298E04E}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bumpy.Core", "Bumpy.Core\Bumpy.Core.csproj", "{03A499C5-3D23-48E0-9892-AACEA22620AA}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bumpy.Tests", "Bumpy.Tests\Bumpy.Tests.csproj", "{CD9AE6DD-C13A-448A-8092-37B4EFAAB431}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bumpy.Core.Tests", "Bumpy.Core.Tests\Bumpy.Core.Tests.csproj", "{F0BDA27C-22CF-4A88-A045-D389F4C4C477}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {2115A19D-9AB9-4ABC-8816-B9C49298E04E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {2115A19D-9AB9-4ABC-8816-B9C49298E04E}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {2115A19D-9AB9-4ABC-8816-B9C49298E04E}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {2115A19D-9AB9-4ABC-8816-B9C49298E04E}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {03A499C5-3D23-48E0-9892-AACEA22620AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {03A499C5-3D23-48E0-9892-AACEA22620AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {03A499C5-3D23-48E0-9892-AACEA22620AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {03A499C5-3D23-48E0-9892-AACEA22620AA}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {CD9AE6DD-C13A-448A-8092-37B4EFAAB431}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {CD9AE6DD-C13A-448A-8092-37B4EFAAB431}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {CD9AE6DD-C13A-448A-8092-37B4EFAAB431}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {CD9AE6DD-C13A-448A-8092-37B4EFAAB431}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {F0BDA27C-22CF-4A88-A045-D389F4C4C477}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {F0BDA27C-22CF-4A88-A045-D389F4C4C477}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {F0BDA27C-22CF-4A88-A045-D389F4C4C477}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {F0BDA27C-22CF-4A88-A045-D389F4C4C477}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {964686CA-4E4F-4F1F-97BE-F41884BB35FF} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/BumpyVersionTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Bumpy.Core.Tests 5 | { 6 | public class BumpyVersionTests 7 | { 8 | [Theory] 9 | [InlineData("3.0.0.0")] 10 | [InlineData("2018.01.01")] 11 | [InlineData("100.52.89.1024")] 12 | [InlineData("5.17.0-foo+bar")] 13 | [InlineData("5.17.0_final")] 14 | [InlineData("3,0,0,0")] 15 | public void ToString_ReturnsVersionString(string versionText) 16 | { 17 | var version = VersionFunctions.ParseVersion(versionText); 18 | 19 | Assert.Equal(versionText, version.ToString()); 20 | } 21 | 22 | [Fact] 23 | public void Constructor_ValidateInput() 24 | { 25 | Assert.Throws(() => new BumpyVersion(new int[] { }, new int[] { }, string.Empty, '.')); 26 | Assert.Throws(() => new BumpyVersion(new int[] { 2, 3 }, new int[] { 1 }, string.Empty, '.')); 27 | Assert.Throws(() => new BumpyVersion(new int[] { 2, -3 }, new int[] { 1, 1 }, string.Empty, '.')); 28 | Assert.Throws(() => new BumpyVersion(new int[] { 2, 3 }, new int[] { 1, -1 }, string.Empty, '.')); 29 | } 30 | 31 | [Theory] 32 | [InlineData("9.1.1")] 33 | [InlineData("1.9.1")] 34 | [InlineData("1.1.9")] 35 | [InlineData("01.1.1")] 36 | [InlineData("1.01.1")] 37 | [InlineData("1.1.01")] 38 | [InlineData("1.1.1-foo")] 39 | [InlineData("1,1,1")] 40 | public void Equals_DifferentState(string versionText) 41 | { 42 | var version = VersionFunctions.ParseVersion("1.1.1"); 43 | var otherVersion = VersionFunctions.ParseVersion(versionText); 44 | 45 | Assert.False(version.Equals(otherVersion)); 46 | Assert.False(otherVersion.Equals(version)); 47 | Assert.NotEqual(version.GetHashCode(), otherVersion.GetHashCode()); 48 | } 49 | 50 | [Theory] 51 | [InlineData("1.8.5")] 52 | [InlineData("0001.008.0005")] 53 | [InlineData("1.8.5-foo")] 54 | [InlineData("1,8,5")] 55 | public void Equals_SameState(string versionText) 56 | { 57 | var version = VersionFunctions.ParseVersion(versionText); 58 | var otherVersion = VersionFunctions.ParseVersion(versionText); 59 | 60 | Assert.True(version.Equals(otherVersion)); 61 | Assert.True(otherVersion.Equals(version)); 62 | Assert.Equal(version.GetHashCode(), otherVersion.GetHashCode()); 63 | } 64 | 65 | [Fact] 66 | public void Equals_WrongType() 67 | { 68 | var version = VersionFunctions.ParseVersion("1.1.1"); 69 | 70 | Assert.False(version.Equals(null)); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/Config/ConfigIOTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Bumpy.Core.Config; 5 | using Xunit; 6 | 7 | namespace Bumpy.Core.Tests.Config 8 | { 9 | public class ConfigIOTests 10 | { 11 | [Fact] 12 | public void ReadConfigFile_ParseDefaultConfig() 13 | { 14 | // We also make sure that the default "new" template is valid 15 | var entries = ReadConfig(ConfigIO.NewConfigFile()).ToList(); 16 | 17 | Assert.Equal(3, entries.Count); 18 | 19 | Assert.Equal("AssemblyInfo.cs", entries[0].Glob); 20 | Assert.Equal("assembly", entries[0].Profile); 21 | 22 | Assert.Equal("*.nuspec", entries[1].Glob); 23 | Assert.Equal("nuspec", entries[1].Profile); 24 | 25 | Assert.Equal("*.csproj", entries[2].Glob); 26 | Assert.Equal("nuspec", entries[2].Profile); 27 | } 28 | 29 | [Fact] 30 | public void ReadConfigFile_ParseCompleteEntry() 31 | { 32 | string configText = @" 33 | [AssemblyInfo.cs | my_profile] 34 | regex = some regex 35 | encoding = utf-8 36 | 37 | [*.rc] 38 | encoding = 1200 39 | "; 40 | var entries = ReadConfig(configText).ToList(); 41 | 42 | Assert.Equal(2, entries.Count); 43 | 44 | Assert.Equal("AssemblyInfo.cs", entries[0].Glob); 45 | Assert.Equal("my_profile", entries[0].Profile); 46 | Assert.Equal("Unicode (UTF-8)", entries[0].Encoding.EncodingName); 47 | Assert.Equal("some regex", entries[0].Regex); 48 | 49 | Assert.Equal(1200, entries[1].Encoding.CodePage); 50 | } 51 | 52 | [Fact] 53 | public void ReadConfigFile_InvalidSyntax() 54 | { 55 | string configText = @" 56 | [AssemblyInfo.cs | my_profile] 57 | regex 58 | "; 59 | Assert.Throws(() => ReadConfig(configText)); 60 | } 61 | 62 | [Fact] 63 | public void ReadConfigFile_UnrecognizedElement() 64 | { 65 | string configText = @" 66 | [AssemblyInfo.cs | my_profile] 67 | unknown_key = some value 68 | "; 69 | Assert.Throws(() => ReadConfig(configText)); 70 | } 71 | 72 | [Fact] 73 | public void ReadConfigFile_SameSectionTwice() 74 | { 75 | string configText = @" 76 | [AssemblyInfo.cs | some_profile] 77 | 78 | [AssemblyInfo.cs | some_profile]"; 79 | 80 | Assert.Throws(() => ReadConfig(configText)); 81 | } 82 | 83 | private IEnumerable ReadConfig(string configText) 84 | { 85 | return ConfigIO.ReadConfigFile(GetLines(configText)); 86 | } 87 | 88 | private IEnumerable GetLines(string configText) 89 | { 90 | using (var stream = new StringReader(configText)) 91 | { 92 | string line; 93 | while ((line = stream.ReadLine()) != null) 94 | { 95 | yield return line; 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/FileUtil.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using Bumpy.Core.Config; 7 | 8 | namespace Bumpy.Core 9 | { 10 | /// 11 | /// An interface implementation which uses the file system. 12 | /// 13 | public sealed class FileUtil : IFileUtil 14 | { 15 | private const string AllFilesPattern = "*"; 16 | 17 | /// 18 | public IEnumerable GetFiles(DirectoryInfo directory, Glob glob) 19 | { 20 | return directory 21 | .EnumerateFiles(AllFilesPattern, SearchOption.AllDirectories) 22 | .Where(f => glob.IsMatch(f.ToRelativePath(directory))); 23 | } 24 | 25 | /// 26 | public FileContent ReadFileContent(FileInfo file, Encoding encoding) 27 | { 28 | return new FileContent(file, File.ReadLines(file.FullName, encoding), encoding); 29 | } 30 | 31 | /// 32 | public void WriteFileContent(FileContent fileContent) 33 | { 34 | File.WriteAllLines(fileContent.File.FullName, fileContent.Lines, fileContent.Encoding); 35 | } 36 | 37 | /// 38 | public bool CreateConfigFile(FileInfo configFile) 39 | { 40 | if (configFile.Exists) 41 | { 42 | return false; 43 | } 44 | 45 | File.WriteAllText(configFile.FullName, ConfigIO.NewConfigFile()); 46 | 47 | return true; 48 | } 49 | 50 | /// 51 | public IEnumerable ReadConfigFile(FileInfo configFile, string profile) 52 | { 53 | var configEntries = ReadConfigFile(configFile); 54 | 55 | if (!profile.Equals(BumpyConfigEntry.DefaultProfile)) 56 | { 57 | configEntries = configEntries.Where(c => c.Profile == profile).ToList(); 58 | 59 | if (!configEntries.Any()) 60 | { 61 | throw new InvalidOperationException($"Profile '{profile}' does not exist"); 62 | } 63 | } 64 | 65 | // OrderBy is used to improve the formatting of all Bumpy commands 66 | return configEntries 67 | .Select(ConfigProcessor.Process) 68 | .OrderBy(c => c.Profile); 69 | } 70 | 71 | private IEnumerable ReadConfigFile(FileInfo configFile) 72 | { 73 | var lines = File.ReadLines(configFile.FullName).ToList(); 74 | 75 | try 76 | { 77 | return ConfigIO.ReadConfigFile(lines); 78 | } 79 | catch (ConfigException e) 80 | { 81 | // TODO fw When should I remove this? 82 | // If this is kept for a long period (e.g. using a legacy flag) consider a 83 | // decorator pattern for `ConfigIO` 84 | Console.WriteLine($"Could not parse '{configFile.Name}'. Error: {e.Message}"); 85 | Console.WriteLine("Trying legacy configuration format (prior to Bumpy 0.11.0)"); 86 | return LegacyConfigIO.ReadConfigFile(lines); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Source/Bumpy/Bumpy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {2115A19D-9AB9-4ABC-8816-B9C49298E04E} 8 | Exe 9 | Bumpy 10 | Bumpy 11 | v4.6.1 12 | 512 13 | true 14 | 15 | 16 | 17 | AnyCPU 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | true 26 | ..\Bumpy.ruleset 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | true 37 | ..\Bumpy.ruleset 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {03a499c5-3d23-48e0-9892-aacea22620aa} 66 | Bumpy.Core 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/LegacyConfigIO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Bumpy.Core.Config 6 | { 7 | /// 8 | /// The legacy configuration handler used in prior versions of Bumpy. 9 | /// 10 | internal static class LegacyConfigIO 11 | { 12 | private const string CommentPattern = "#"; 13 | private const string ProfileStartPattern = "["; 14 | private const string ProfileEndPattern = "]"; 15 | private const string EqualsPattern = "="; 16 | 17 | private const char MainSplit = '='; 18 | private const char EncodingSplit = '|'; 19 | 20 | /// 21 | /// Parses the content of a legacy configuration file into a set of objects. 22 | /// 23 | /// The content of a legacy configuration file 24 | /// The parsed objects containing the configuration 25 | public static IEnumerable ReadConfigFile(IEnumerable lines) 26 | { 27 | var profile = string.Empty; 28 | 29 | foreach (var line in lines) 30 | { 31 | var isComment = line.StartsWith(CommentPattern, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(line); 32 | var isProfile = line.StartsWith(ProfileStartPattern, StringComparison.Ordinal) && line.EndsWith(ProfileEndPattern, StringComparison.Ordinal); 33 | 34 | if (isComment) 35 | { 36 | continue; 37 | } 38 | else if (isProfile) 39 | { 40 | profile = line.Replace(ProfileStartPattern, string.Empty).Replace(ProfileEndPattern, string.Empty).Trim(); 41 | 42 | if (profile.Length == 0) 43 | { 44 | throw new ConfigException("A profile name cannot be empty"); 45 | } 46 | 47 | continue; 48 | } 49 | 50 | // Configuration line variants: 51 | // | = 52 | // | = 53 | // = 54 | var mainSplit = line.Split(MainSplit); 55 | var leftSplit = mainSplit[0].Split(EncodingSplit); 56 | 57 | if (mainSplit.Length == 1) 58 | { 59 | // Try to find template later 60 | yield return new BumpyConfigEntry 61 | { 62 | Glob = mainSplit[0], 63 | Profile = profile 64 | }; 65 | continue; 66 | } 67 | 68 | Encoding encoding = new UTF8Encoding(false); 69 | 70 | if (leftSplit.Length == 2) 71 | { 72 | var possibleEncoding = leftSplit[1].Trim(); 73 | bool isCodingPage = int.TryParse(possibleEncoding, out var codePage); 74 | 75 | if (isCodingPage) 76 | { 77 | encoding = Encoding.GetEncoding(codePage); 78 | } 79 | else 80 | { 81 | encoding = Encoding.GetEncoding(possibleEncoding); 82 | } 83 | } 84 | 85 | var searchPattern = leftSplit[0].Trim(); 86 | var regularExpression = string.Join(EqualsPattern, mainSplit, 1, mainSplit.Length - 1).Trim(); 87 | 88 | yield return new BumpyConfigEntry 89 | { 90 | Glob = searchPattern, 91 | Profile = profile, 92 | Encoding = encoding, 93 | Regex = regularExpression 94 | }; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | build/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studo 2015 cache/options directory 26 | .vs/ 27 | 28 | # MSTest test Results 29 | [Tt]est[Rr]esult*/ 30 | [Bb]uild[Ll]og.* 31 | 32 | # NUNIT 33 | *.VisualState.xml 34 | TestResult.xml 35 | 36 | # Build Results of an ATL Project 37 | [Dd]ebugPS/ 38 | [Rr]eleasePS/ 39 | dlldata.c 40 | 41 | *_i.c 42 | *_p.c 43 | *_i.h 44 | *.ilk 45 | *.meta 46 | *.obj 47 | *.pch 48 | *.pdb 49 | *.pgc 50 | *.pgd 51 | *.rsp 52 | *.sbr 53 | *.tlb 54 | *.tli 55 | *.tlh 56 | *.tmp 57 | *.tmp_proj 58 | *.log 59 | *.vspscc 60 | *.vssscc 61 | .builds 62 | *.pidb 63 | *.svclog 64 | *.scc 65 | 66 | # Chutzpah Test files 67 | _Chutzpah* 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | *.cachefile 76 | 77 | # Visual Studio profiler 78 | *.psess 79 | *.vsp 80 | *.vspx 81 | 82 | # TFS 2012 Local Workspace 83 | $tf/ 84 | 85 | # Guidance Automation Toolkit 86 | *.gpState 87 | 88 | # ReSharper is a .NET coding add-in 89 | _ReSharper*/ 90 | *.[Rr]e[Ss]harper 91 | *.DotSettings.user 92 | 93 | # JustCode is a .NET coding addin-in 94 | .JustCode 95 | 96 | # TeamCity is a build add-in 97 | _TeamCity* 98 | 99 | # DotCover is a Code Coverage Tool 100 | *.dotCover 101 | 102 | # NCrunch 103 | _NCrunch_* 104 | .*crunch*.local.xml 105 | 106 | # MightyMoose 107 | *.mm.* 108 | AutoTest.Net/ 109 | 110 | # Web workbench (sass) 111 | .sass-cache/ 112 | 113 | # Installshield output folder 114 | [Ee]xpress/ 115 | 116 | # DocProject is a documentation generator add-in 117 | DocProject/buildhelp/ 118 | DocProject/Help/*.HxT 119 | DocProject/Help/*.HxC 120 | DocProject/Help/*.hhc 121 | DocProject/Help/*.hhk 122 | DocProject/Help/*.hhp 123 | DocProject/Help/Html2 124 | DocProject/Help/html 125 | 126 | # Click-Once directory 127 | publish/ 128 | 129 | # Publish Web Output 130 | *.[Pp]ublish.xml 131 | *.azurePubxml 132 | # TODO: Comment the next line if you want to checkin your web deploy settings 133 | # but database connection strings (with potential passwords) will be unencrypted 134 | *.pubxml 135 | *.publishproj 136 | 137 | # NuGet Packages 138 | *.nupkg 139 | # The packages folder can be ignored because of Package Restore 140 | **/packages/* 141 | # except build/, which is used as an MSBuild target. 142 | !**/packages/build/ 143 | # Uncomment if necessary however generally it will be regenerated when needed 144 | #!**/packages/repositories.config 145 | 146 | # Windows Azure Build Output 147 | csx/ 148 | *.build.csdef 149 | 150 | # Windows Store app package directory 151 | AppPackages/ 152 | 153 | # Others 154 | *.[Cc]ache 155 | ClientBin/ 156 | [Ss]tyle[Cc]op.* 157 | ~$* 158 | *~ 159 | *.dbmdl 160 | *.dbproj.schemaview 161 | *.pfx 162 | *.publishsettings 163 | node_modules/ 164 | bower_components/ 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file 170 | # to a newer Visual Studio version. Backup files are not needed, 171 | # because we have git ;-) 172 | _UpgradeReport_Files/ 173 | Backup*/ 174 | UpgradeLog*.XML 175 | UpgradeLog*.htm 176 | 177 | # SQL Server files 178 | *.mdf 179 | *.ldf 180 | 181 | # Business Intelligence projects 182 | *.rdl.data 183 | *.bim.layout 184 | *.bim_*.settings 185 | 186 | # Microsoft Fakes 187 | FakesAssemblies/ 188 | 189 | # Node.js Tools for Visual Studio 190 | .ntvs_analysis.dat 191 | 192 | # Visual Studio 6 build log 193 | *.plg 194 | 195 | # Visual Studio 6 workspace options file 196 | *.opt 197 | 198 | # Build Related 199 | Artifacts/ 200 | **/tools/* 201 | !**/tools/packages.config 202 | -------------------------------------------------------------------------------- /Source/Bumpy/CommandParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Bumpy.Core; 6 | 7 | namespace Bumpy 8 | { 9 | public sealed class CommandParser 10 | { 11 | private CommandType _commandType; 12 | private int _position; 13 | private string _formattedNumber; 14 | private string _text; 15 | private BumpyArguments _arguments; 16 | 17 | public CommandParser() 18 | { 19 | _commandType = CommandType.Help; 20 | _position = -1; 21 | _formattedNumber = "-1"; 22 | _text = string.Empty; 23 | _arguments = new BumpyArguments(); 24 | } 25 | 26 | public CommandArguments ParseArguments(string[] args) 27 | { 28 | try 29 | { 30 | ParseCommand(new Queue(args)); 31 | } 32 | catch (ParserException) 33 | { 34 | throw; 35 | } 36 | catch (Exception e) 37 | { 38 | throw new ParserException("Invalid arguments. See 'bumpy help'.", e); 39 | } 40 | 41 | return new CommandArguments(_commandType, _position, _formattedNumber, _text, _arguments); 42 | } 43 | 44 | private void ParseCommand(Queue args) 45 | { 46 | if (!args.Any()) 47 | { 48 | return; 49 | } 50 | 51 | var cmd = args.Dequeue(); 52 | 53 | if (!Enum.TryParse(cmd, true, out _commandType)) 54 | { 55 | throw new ParserException($"Command '{cmd}' not recognized. See 'bumpy help'."); 56 | } 57 | 58 | if (_commandType == CommandType.Increment || _commandType == CommandType.IncrementOnly) 59 | { 60 | _position = Convert.ToInt32(args.Dequeue()); 61 | } 62 | else if (_commandType == CommandType.Write || _commandType == CommandType.Label) 63 | { 64 | _text = args.Dequeue(); 65 | } 66 | else if (_commandType == CommandType.Assign) 67 | { 68 | _position = Convert.ToInt32(args.Dequeue()); 69 | _formattedNumber = args.Dequeue(); 70 | } 71 | 72 | var shouldParseOptions = _commandType == CommandType.List 73 | || _commandType == CommandType.Increment 74 | || _commandType == CommandType.IncrementOnly 75 | || _commandType == CommandType.Write 76 | || _commandType == CommandType.Assign 77 | || _commandType == CommandType.Label 78 | || _commandType == CommandType.Ensure; 79 | 80 | if (!shouldParseOptions) 81 | { 82 | if (args.Any()) 83 | { 84 | throw new ParserException($"Command '{cmd}' does not accept additional arguments. See 'bumpy help'."); 85 | } 86 | 87 | return; 88 | } 89 | 90 | while (args.Any()) 91 | { 92 | ParseOptions(args); 93 | } 94 | } 95 | 96 | private void ParseOptions(Queue args) 97 | { 98 | string option = args.Dequeue(); 99 | 100 | if (option.Equals("-p")) 101 | { 102 | _arguments.Profile = args.Dequeue(); 103 | } 104 | else if (option.Equals("-d")) 105 | { 106 | _arguments.WorkingDirectory = new DirectoryInfo(args.Dequeue()); 107 | } 108 | else if (option.Equals("-c")) 109 | { 110 | _arguments.ConfigFile = new FileInfo(args.Dequeue()); 111 | } 112 | else if (option.Equals("-n")) 113 | { 114 | _arguments.NoOperation = true; 115 | } 116 | else 117 | { 118 | throw new ParserException($"Option '{option}' not recognized. See 'bumpy help'."); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /Source/Bumpy.Tests/CommandParserTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Bumpy.Core.Config; 3 | using Xunit; 4 | 5 | namespace Bumpy.Tests 6 | { 7 | public class CommandParserTests 8 | { 9 | [Fact] 10 | public void ParseArguments_Help() 11 | { 12 | var arguments = Parse("help"); 13 | 14 | Assert.Equal(CommandType.Help, arguments.CmdType); 15 | } 16 | 17 | [Fact] 18 | public void ParseArguments_New() 19 | { 20 | var arguments = Parse("new"); 21 | 22 | Assert.Equal(CommandType.New, arguments.CmdType); 23 | } 24 | 25 | [Fact] 26 | public void ParseArguments_List() 27 | { 28 | var arguments = Parse("list"); 29 | 30 | Assert.Equal(CommandType.List, arguments.CmdType); 31 | } 32 | 33 | [Fact] 34 | public void ParseArguments_Increment() 35 | { 36 | var arguments = Parse("increment 3"); 37 | 38 | Assert.Equal(CommandType.Increment, arguments.CmdType); 39 | Assert.Equal(3, arguments.Position); 40 | } 41 | 42 | [Fact] 43 | public void ParseArguments_IncrementOnly() 44 | { 45 | var arguments = Parse("incrementonly 3"); 46 | 47 | Assert.Equal(CommandType.IncrementOnly, arguments.CmdType); 48 | Assert.Equal(3, arguments.Position); 49 | } 50 | 51 | [Fact] 52 | public void ParseArguments_Assign() 53 | { 54 | var arguments = Parse("assign 2 007"); 55 | 56 | Assert.Equal(CommandType.Assign, arguments.CmdType); 57 | Assert.Equal(2, arguments.Position); 58 | Assert.Equal("007", arguments.FormattedNumber); 59 | } 60 | 61 | [Theory] 62 | [InlineData("label -beta", "-beta")] 63 | [InlineData("label ", "")] 64 | public void ParseArguments_Label(string args, string expectedLabel) 65 | { 66 | var arguments = Parse(args); 67 | 68 | Assert.Equal(CommandType.Label, arguments.CmdType); 69 | Assert.Equal(expectedLabel, arguments.Text); 70 | } 71 | 72 | [Fact] 73 | public void ParseArguments_Write() 74 | { 75 | var arguments = Parse("write 1.0.0"); 76 | 77 | Assert.Equal(CommandType.Write, arguments.CmdType); 78 | Assert.Equal("1.0.0", arguments.Text); 79 | } 80 | 81 | [Fact] 82 | public void ParseArguments_Ensure() 83 | { 84 | var arguments = Parse("ensure"); 85 | 86 | Assert.Equal(CommandType.Ensure, arguments.CmdType); 87 | } 88 | 89 | [Fact] 90 | public void ParseArguments_DefaultOptions() 91 | { 92 | var arguments = Parse("list"); 93 | 94 | Assert.Equal(string.Empty, arguments.Arguments.Profile); 95 | Assert.Equal(new FileInfo(BumpyConfigEntry.DefaultConfigFile).FullName, arguments.Arguments.ConfigFile.FullName); 96 | Assert.Equal(new DirectoryInfo(".").FullName, arguments.Arguments.WorkingDirectory.FullName); 97 | } 98 | 99 | [Fact] 100 | public void ParseArguments_CustomOptions() 101 | { 102 | var arguments = Parse("list -p bar -c foo.config -d foodir"); 103 | 104 | Assert.Equal("bar", arguments.Arguments.Profile); 105 | Assert.Equal(new FileInfo("foo.config").FullName, arguments.Arguments.ConfigFile.FullName); 106 | Assert.Equal(new DirectoryInfo("foodir").FullName, arguments.Arguments.WorkingDirectory.FullName); 107 | } 108 | 109 | [Fact] 110 | public void ParseArguments_Errors() 111 | { 112 | Assert.Throws(() => Parse("randomcommand")); 113 | Assert.Throws(() => Parse("increment")); 114 | Assert.Throws(() => Parse("increment notanumber")); 115 | Assert.Throws(() => Parse("incrementonly")); 116 | Assert.Throws(() => Parse("incrementonly notanumber")); 117 | Assert.Throws(() => Parse("assign")); 118 | Assert.Throws(() => Parse("assign notanumber")); 119 | Assert.Throws(() => Parse("write")); 120 | Assert.Throws(() => Parse("write -c -d")); 121 | Assert.Throws(() => Parse("help -p someprofile")); 122 | Assert.Throws(() => Parse("new -c foo.config -d foodir")); 123 | } 124 | 125 | private CommandArguments Parse(string args) 126 | { 127 | var parser = new CommandParser(); 128 | 129 | return parser.ParseArguments(args.Split(' ')); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/BumpyVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Bumpy.Core 6 | { 7 | /// 8 | /// A class which represents a version string. 9 | /// 10 | /// Examples: 11 | /// - 1.0.0.0 12 | /// - 1.2.3-beta 13 | /// - 1.2.3-beta+foo 14 | /// - 01.01.2018 15 | /// - 8,3,4,0 16 | /// 17 | public sealed class BumpyVersion 18 | { 19 | private readonly List _numbers; 20 | private readonly List _digits; 21 | 22 | /// 23 | /// Initializes a new instance of the class. 24 | /// 25 | /// A collection of version number parts, e.g. { 1, 23, 8025 } 26 | /// A collection of numbers describing the length of all members in the "numbers" collection, e.g. { 1, 2, 4 } 27 | /// The label suffix of a version, e.g. "-beta" 28 | /// The delimiter used to display a version string, e.g. "." -> "1.23.8025 29 | public BumpyVersion(IEnumerable numbers, IEnumerable digits, string label, char numberDelimiter) 30 | { 31 | _numbers = numbers.ToList(); 32 | _digits = digits.ToList(); 33 | Label = label; 34 | NumberDelimiter = numberDelimiter; 35 | 36 | if (_numbers.Count == 0) 37 | { 38 | throw new ArgumentException("Parameter must at least contain one element", nameof(numbers)); 39 | } 40 | 41 | if (_numbers.Count != _digits.Count) 42 | { 43 | throw new ArgumentException($"Length mismatch found in collections {nameof(numbers)} and {nameof(digits)}"); 44 | } 45 | 46 | for (int i = 0; i < _numbers.Count; i++) 47 | { 48 | _numbers[i].ThrowIfOutOfRange(n => n < 0, nameof(numbers), "Elements cannot be negative"); 49 | _digits[i].ThrowIfOutOfRange(n => n < 0, nameof(digits), "Elements cannot be negative"); 50 | } 51 | } 52 | 53 | /// 54 | /// Gets the numbers of a version, e.g. { 1, 24, 8025 }. 55 | /// 56 | public IList Numbers => new List(_numbers); 57 | 58 | /// 59 | /// Gets the digit count for each member of the Numbers collection. 60 | /// 61 | /// Example #1: 62 | /// - Numbers: { 1, 24, 8025 } 63 | /// - Digits: { 1, 2, 4 } 64 | /// - Results in version "1.24.8025" 65 | /// 66 | /// Example #2: 67 | /// - Numbers: { 1, 1, 2018 } 68 | /// - Digits: { 2, 2, 4 } 69 | /// - Results in version "01.01.2018" 70 | /// 71 | public IList Digits => new List(_digits); 72 | 73 | /// 74 | /// Gets the suffix version label, e.g. "-beta" or "". 75 | /// 76 | public string Label 77 | { 78 | get; 79 | } 80 | 81 | /// 82 | /// Gets the delimiter used to display the numbers of a version, e.g. "." -> "1.2.3". 83 | /// 84 | public char NumberDelimiter 85 | { 86 | get; 87 | } 88 | 89 | /// 90 | /// Creates a readable representation of a version. 91 | /// 92 | /// Examples: 93 | /// - 14.31.6532.4 94 | /// - 1.2.3-beta 95 | /// - 01.01.2018 96 | /// - 1,0,0,0 97 | /// 98 | /// A readable version string 99 | public override string ToString() 100 | { 101 | var formattedNumbers = new string[_numbers.Count]; 102 | 103 | for (int i = 0; i < _numbers.Count; i++) 104 | { 105 | formattedNumbers[i] = _numbers[i].ToString($"D{_digits[i]}"); 106 | } 107 | 108 | return string.Join(string.Empty + NumberDelimiter, formattedNumbers) + Label; 109 | } 110 | 111 | /// 112 | /// Checks if two objects are equal. 113 | /// 114 | /// The object to check 115 | /// True if all members are equal 116 | public override bool Equals(object obj) 117 | { 118 | if (obj is BumpyVersion version) 119 | { 120 | return version.ToString().Equals(ToString()); 121 | } 122 | 123 | return false; 124 | } 125 | 126 | /// 127 | /// Creates a hash code based on the object's state. 128 | /// 129 | /// A hash code 130 | public override int GetHashCode() 131 | { 132 | return ToString().GetHashCode(); 133 | } 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Source/Bumpy/CommandRunner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | using System.Text; 5 | using Bumpy.Core; 6 | using Bumpy.Core.Config; 7 | 8 | namespace Bumpy 9 | { 10 | public sealed class CommandRunner 11 | { 12 | private readonly Command _command; 13 | private readonly CommandArguments _arguments; 14 | 15 | public CommandRunner(Command command, CommandArguments arguments) 16 | { 17 | _command = command; 18 | _arguments = arguments; 19 | } 20 | 21 | public void Execute() 22 | { 23 | if (_arguments.CmdType == CommandType.List) 24 | { 25 | _command.List(_arguments.Arguments); 26 | } 27 | else if (_arguments.CmdType == CommandType.New) 28 | { 29 | _command.New(); 30 | } 31 | else if (_arguments.CmdType == CommandType.Increment) 32 | { 33 | _command.Increment(_arguments.Position, _arguments.Arguments); 34 | } 35 | else if (_arguments.CmdType == CommandType.IncrementOnly) 36 | { 37 | _command.IncrementOnly(_arguments.Position, _arguments.Arguments); 38 | } 39 | else if (_arguments.CmdType == CommandType.Write) 40 | { 41 | _command.Write(_arguments.Text, _arguments.Arguments); 42 | } 43 | else if (_arguments.CmdType == CommandType.Assign) 44 | { 45 | _command.Assign(_arguments.Position, _arguments.FormattedNumber, _arguments.Arguments); 46 | } 47 | else if (_arguments.CmdType == CommandType.Label) 48 | { 49 | _command.Label(_arguments.Text, _arguments.Arguments); 50 | } 51 | else if (_arguments.CmdType == CommandType.Ensure) 52 | { 53 | _command.Ensure(_arguments.Arguments); 54 | } 55 | else if (_arguments.CmdType == CommandType.Help) 56 | { 57 | Help(); 58 | } 59 | else 60 | { 61 | // This Exception is only thrown if we forget to extend 62 | // the 'Execute' method after introducing new commands. 63 | throw new NotImplementedException("Could not execute command"); 64 | } 65 | } 66 | 67 | private void Help() 68 | { 69 | var assembly = Assembly.GetExecutingAssembly(); 70 | var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location); 71 | 72 | var builder = new StringBuilder(); 73 | builder.AppendLine($"Bumpy v{versionInfo.FileVersion}"); 74 | builder.AppendLine("A tool to maintain version information accross multiple files found in the current working directory"); 75 | builder.AppendLine(); 76 | builder.AppendLine("Commands:"); 77 | builder.AppendLine(" help"); 78 | builder.AppendLine(" View all commands and options"); 79 | builder.AppendLine(" list"); 80 | builder.AppendLine(" Lists all versions"); 81 | builder.AppendLine(" new"); 82 | builder.AppendLine($" Creates a '{BumpyConfigEntry.DefaultConfigFile}' file if it does not exist"); 83 | builder.AppendLine(" increment (e.g. 'bumpy increment 3')"); 84 | builder.AppendLine(" Increments the specified component of each version"); 85 | builder.AppendLine(" incrementonly (e.g. 'bumpy incrementonly 3')"); 86 | builder.AppendLine(" Increments the specified component of each version, without updating following components"); 87 | builder.AppendLine(" write "); 88 | builder.AppendLine(" Overwrites a version with another version (e.g. 'bumpy write 1.0.0.0')"); 89 | builder.AppendLine(" assign (e.g. 'bumpy assign 3 99')"); 90 | builder.AppendLine(" Replaces the specified component of a version with a new number"); 91 | builder.AppendLine(" label "); 92 | builder.AppendLine(" Replaces the suffix text of a version (e.g. 'bumpy label \"-beta\"')"); 93 | builder.AppendLine(" ensure"); 94 | builder.AppendLine(" Checks that all versions in a profile are equal"); 95 | builder.AppendLine(); 96 | builder.AppendLine("Options: (only available for 'list', 'increment', 'incrementonly', 'write', 'assign', 'label' and 'ensure')"); 97 | builder.AppendLine(" -p "); 98 | builder.AppendLine(" Limit a command to a profile"); 99 | builder.AppendLine(" -d "); 100 | builder.AppendLine(" Run a command in a specific folder (the working directory is used by default)"); 101 | builder.AppendLine(" -c "); 102 | builder.AppendLine($" Alternative name/path of a configuration file (default: '{BumpyConfigEntry.DefaultConfigFile}')"); 103 | builder.AppendLine(" -n"); 104 | builder.AppendLine(" No operation: The specified command (e.g. increment) will not perform file changes"); 105 | 106 | Console.WriteLine(builder.ToString()); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 6 | 7 | ## Unreleased 8 | 9 | ### Added 10 | 11 | - The `ApplyTransformation` (the underlying method that is used by most Bumpy commands) is now exposed as a public method 12 | 13 | ### Changed 14 | 15 | - Project structure: 16 | - **Bumpy.Core** contains all basic features 17 | - **Bumpy** gives access to Bumpy.Core through a command line interface 18 | - Write errors to `stderr` 19 | - Only show Bumpy's version when calling the `help` command 20 | 21 | ### Fixed 22 | 23 | - [#39](https://github.com/fwinkelbauer/Bumpy/issues/39) A bug in the configuration parser. A section in `.bumpyconfig` must now be unique 24 | 25 | ## 0.11.0 - 2018-03-08 26 | 27 | ### Changed 28 | 29 | - **Breaking Change:** The configuration file format in `.bumpyconfig`. The old format is still supported, but a legacy warning is printed 30 | - Console output of all persisting commands (`increment`, `assign`, ...) to also display profiles 31 | - Console output of `ensure`. It will now print an overview of profiles with their version 32 | 33 | ## 0.10.0 - 2018-02-26 34 | 35 | ### Added 36 | 37 | - [#36](https://github.com/fwinkelbauer/Bumpy/issues/36) Ensure command 38 | - [#35](https://github.com/fwinkelbauer/Bumpy/issues/35) Templates for `.bumpyconfig` files 39 | - [#34](https://github.com/fwinkelbauer/Bumpy/issues/34) Regex capture group "marker" 40 | - [#31](https://github.com/fwinkelbauer/Bumpy/issues/31) "No-operation" flag which prevents commands such as `bumpy increment` to persist changes 41 | - [#28](https://github.com/fwinkelbauer/Bumpy/issues/28) Support for versions which use comma as a delimiter (e.g. Visual Studio C++ resource files contain versions such as "1,0,0,0") 42 | 43 | ### Changed 44 | 45 | - [#32](https://github.com/fwinkelbauer/Bumpy/issues/32) Bumpy now highlights files with no version information 46 | 47 | ## 0.9.0 - 2018-02-09 48 | 49 | ### Added 50 | 51 | - [#29](https://github.com/fwinkelbauer/Bumpy/issues/29) Support to change the postfix text of a version (e.g. "-beta001") 52 | 53 | ## 0.8.0 - 2018-02-06 54 | 55 | ### Added 56 | 57 | - [#26](https://github.com/fwinkelbauer/Bumpy/issues/26) Support for version formats with leading zeros (e.g. increment "2018.01.01" to "2018.01.02" instead of "2018.1.2") 58 | 59 | ### Changed 60 | 61 | - The glob implementation. My current approach relies on regex and is not fully compliant to any "glob standard". I might switch to an open source glob library if users start to have issues 62 | 63 | ### Fixed 64 | 65 | - A off-by-one bug when printing the lines of found versions 66 | - Some bugs which caused unnecessary regex and file write operations 67 | 68 | ## 0.7.0 - 2017-12-23 69 | 70 | ### Added 71 | 72 | - [#23](https://github.com/fwinkelbauer/Bumpy/issues/23) Support for code pages in `.bumpyconfig` 73 | - [#24](https://github.com/fwinkelbauer/Bumpy/issues/24) New command incrementonly. Thank you [@euronay](https://github.com/euronay) for your contribution! 74 | 75 | ### Fixed 76 | 77 | - [#22](https://github.com/fwinkelbauer/Bumpy/issues/22) A case in which `bumpy write` would perform in an inconsistent manner 78 | 79 | ## 0.6.0 - 2017-10-18 80 | 81 | ### Changed 82 | 83 | - **Breaking Change:** The command line interface to be similar to tools such as git or chocolatey. This change also introduces new options. See the updated `README` file for more information 84 | 85 | ### Removed 86 | 87 | - **Breaking Change:** The command to list profiles. Profiles are now shown when calling `bumpy list` 88 | 89 | ## 0.5.0 - 2017-09-20 90 | 91 | ### Removed 92 | 93 | - GitReleaseManager from the build script 94 | 95 | ### Changed 96 | 97 | - Adopted keepachangelog.com 98 | - The glob pattern implementation now relies on pure regex instead of relying on external NuGet packages 99 | - The pattern "\*\*\Foo.txt" will: 100 | - match files named "Foo.txt" 101 | - not match other files such as "SomeFoo.txt" 102 | 103 | ## 0.4.1 - 2017-08-24 104 | 105 | ### Fixed 106 | 107 | - The 0.4.0 packages were unlisted because they were missing a library 108 | 109 | ### Changed 110 | 111 | - [#19](https://github.com/fwinkelbauer/Bumpy/issues/19) **Breaking Change** Glob pattern: The re-introduction of glob patterns might break existing `.bumpyconfig` files. Please read the updated documentation! 112 | - [#17](https://github.com/fwinkelbauer/Bumpy/issues/17) Automation process to perform Chocolatey verification 113 | 114 | ## 0.3.0 - 2017-07-17 115 | 116 | ### Fixed 117 | 118 | - [#18](https://github.com/fwinkelbauer/Bumpy/issues/18) A bug when parsing configuration files with the '=' character in a regular expression 119 | 120 | ### Added 121 | 122 | - [#14](https://github.com/fwinkelbauer/Bumpy/issues/14) GitHub releases automation 123 | 124 | ## 0.2.1 - 2017-06-28 125 | 126 | ### Added 127 | 128 | - [#10](https://github.com/fwinkelbauer/Bumpy/issues/10) Missing information to the help command 129 | - [#12](https://github.com/fwinkelbauer/Bumpy/issues/12) Automation steps to publish packages 130 | 131 | ## 0.2.0 - 2017-06-27 132 | 133 | ### Added 134 | 135 | - [#12](https://github.com/fwinkelbauer/Bumpy/issues/2) Support other version formats 136 | - [#16](https://github.com/fwinkelbauer/Bumpy/issues/6) Encoding configuration 137 | 138 | ## 0.1.0 - 2017-06-19 139 | 140 | ### Added 141 | 142 | - Initial release 143 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/CommandTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using Bumpy.Core.Config; 7 | using NSubstitute; 8 | using Xunit; 9 | 10 | namespace Bumpy.Core.Tests 11 | { 12 | public class CommandTests 13 | { 14 | [Fact] 15 | public void New_CreatesFile() 16 | { 17 | var fileUtil = Substitute.For(); 18 | var command = CreateCommand(fileUtil); 19 | 20 | command.New(); 21 | 22 | fileUtil.Received().CreateConfigFile(Arg.Any()); 23 | } 24 | 25 | [Fact] 26 | public void List_NoVersion() 27 | { 28 | var fileUtil = CreateFileUtil(new[] { "no", "version", "here" }); 29 | var writeLine = Substitute.For>(); 30 | var command = CreateCommand(fileUtil, writeLine); 31 | 32 | command.List(); 33 | 34 | writeLine.Received().Invoke(@"foo.txt: no version found"); 35 | } 36 | 37 | [Fact] 38 | public void List_PrintVersions() 39 | { 40 | var fileUtil = CreateFileUtil(new[] { "some", "version", "here", "1.2.3", "foobar 0.25.0" }); 41 | var writeLine = Substitute.For>(); 42 | var command = CreateCommand(fileUtil, writeLine); 43 | 44 | command.List(); 45 | 46 | writeLine.Received().Invoke(@"foo.txt (4): 1.2.3"); 47 | writeLine.Received().Invoke(@"foo.txt (5): 0.25.0"); 48 | } 49 | 50 | [Fact] 51 | public void Ensure_Success() 52 | { 53 | var fileUtil = CreateFileUtil(new[] { "1.2.3", "1.2.3" }); 54 | var writeLine = Substitute.For>(); 55 | var command = CreateCommand(fileUtil, writeLine); 56 | 57 | command.Ensure(); 58 | 59 | writeLine.Received().Invoke("1.2.3"); 60 | } 61 | 62 | [Fact] 63 | public void Ensure_Error() 64 | { 65 | var fileUtil = CreateFileUtil(new[] { "1.2.3", "1.2.4" }); 66 | var command = CreateCommand(fileUtil); 67 | 68 | Assert.Throws(() => command.Ensure()); 69 | } 70 | 71 | [Fact] 72 | public void Increment_NoVersion() 73 | { 74 | var lines = new[] { "no", "version", "here" }; 75 | var fileUtil = CreateFileUtil(lines); 76 | var writeLine = Substitute.For>(); 77 | var command = CreateCommand(fileUtil, writeLine); 78 | 79 | command.Increment(2); 80 | 81 | fileUtil.DidNotReceive().WriteFileContent(Arg.Any()); 82 | writeLine.Received().Invoke(@"foo.txt: no version found"); 83 | } 84 | 85 | [Fact] 86 | public void Increment_IncrementVersions() 87 | { 88 | var fileUtil = CreateFileUtil(new[] { "some", "version", "here", "1.2.3", "foobar 0.25.0" }); 89 | var writeLine = Substitute.For>(); 90 | var command = CreateCommand(fileUtil, writeLine); 91 | 92 | command.Increment(2); 93 | 94 | var newLines = new[] { "some", "version", "here", "1.3.0", "foobar 0.26.0" }; 95 | fileUtil.Received().WriteFileContent(Arg.Is(f => f.Lines.SequenceEqual(newLines))); 96 | writeLine.Received().Invoke(@"foo.txt (4): 1.2.3 -> 1.3.0"); 97 | writeLine.Received().Invoke(@"foo.txt (5): 0.25.0 -> 0.26.0"); 98 | } 99 | 100 | [Fact] 101 | public void Increment_IncrementVersionsNoFileWrite() 102 | { 103 | var fileUtil = CreateFileUtil(new[] { "some", "version", "here", "1.2.3", "foobar 0.25.0" }); 104 | var writeLine = Substitute.For>(); 105 | var command = CreateCommand(fileUtil, writeLine); 106 | 107 | command.Increment(2, new BumpyArguments { NoOperation = true }); 108 | 109 | fileUtil.DidNotReceive().WriteFileContent(Arg.Any()); 110 | writeLine.Received().Invoke(@"foo.txt (4): 1.2.3 -> 1.3.0"); 111 | writeLine.Received().Invoke(@"foo.txt (5): 0.25.0 -> 0.26.0"); 112 | } 113 | 114 | [Fact] 115 | public void Assign_NoWrite() 116 | { 117 | var lines = new[] { "1.0.42" }; 118 | var fileUtil = CreateFileUtil(lines); 119 | var writeLine = Substitute.For>(); 120 | var command = CreateCommand(fileUtil, writeLine); 121 | 122 | command.Assign(3, "42"); 123 | 124 | fileUtil.DidNotReceive().WriteFileContent(Arg.Any()); 125 | writeLine.Received().Invoke(@"foo.txt (1): 1.0.42 -> 1.0.42"); 126 | } 127 | 128 | private IFileUtil CreateFileUtil(IEnumerable lines) 129 | { 130 | var fileUtil = Substitute.For(); 131 | 132 | fileUtil.ReadConfigFile(Arg.Any(), Arg.Any()).Returns(new[] 133 | { 134 | new BumpyConfigEntry 135 | { 136 | Glob = "*.txt", 137 | Regex = @"(?\d+\.\d+\.\d+)" 138 | } 139 | }); 140 | 141 | fileUtil.GetFiles(Arg.Any(), Arg.Any()).Returns(new[] 142 | { 143 | new FileInfo("foo.txt") 144 | }); 145 | 146 | fileUtil.ReadFileContent(Arg.Any(), Arg.Any()).Returns( 147 | new FileContent(new FileInfo("foo.txt"), lines, new UTF8Encoding(false))); 148 | 149 | return fileUtil; 150 | } 151 | 152 | private Command CreateCommand(IFileUtil fileUtil = null, Action writeLine = null) 153 | { 154 | fileUtil = fileUtil ?? Substitute.For(); 155 | writeLine = writeLine ?? (l => { }); 156 | 157 | return new Command(fileUtil, writeLine); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /Source/Bumpy.Tests/Bumpy.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {CD9AE6DD-C13A-448A-8092-37B4EFAAB431} 10 | Library 11 | Properties 12 | Bumpy.Tests 13 | Bumpy.Tests 14 | v4.6.1 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | ..\Bumpy.ruleset 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | true 38 | ..\Bumpy.ruleset 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll 51 | True 52 | 53 | 54 | ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll 55 | 56 | 57 | ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll 58 | 59 | 60 | ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | PreserveNewest 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | {03a499c5-3d23-48e0-9892-aacea22620aa} 81 | Bumpy.Core 82 | 83 | 84 | {2115a19d-9ab9-4abc-8816-b9c49298e04e} 85 | Bumpy 86 | 87 | 88 | 89 | 90 | 91 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/Config/ConfigIO.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace Bumpy.Core.Config 7 | { 8 | /// 9 | /// A class to load and create configuration files. 10 | /// 11 | public static class ConfigIO 12 | { 13 | private const string CommentPattern = "#"; 14 | private const string SectionStartPattern = "["; 15 | private const string SectionEndPattern = "]"; 16 | private const string EqualsPattern = "="; 17 | private const string ProfileSplit = "|"; 18 | 19 | /// 20 | /// Returns the content of a new configuration file. 21 | /// 22 | /// The content of a new configuration file. 23 | public static string NewConfigFile() 24 | { 25 | var builder = new StringBuilder(); 26 | builder.AppendLine("# Bumpy configuration file"); 27 | builder.AppendLine(); 28 | builder.AppendLine("# Bumpy tries to use a default configuration for a file type if a section is empty"); 29 | builder.AppendLine("[AssemblyInfo.cs | assembly]"); 30 | builder.AppendLine(); 31 | builder.AppendLine("[*.nuspec | nuspec]"); 32 | builder.AppendLine(); 33 | builder.AppendLine("[*.csproj | nuspec]"); 34 | 35 | return builder.ToString(); 36 | } 37 | 38 | /// 39 | /// Parses the content of a configuration file into a set of objects. 40 | /// 41 | /// The content of a configuration file 42 | /// The parsed objects containing the configuration 43 | public static IEnumerable ReadConfigFile(IEnumerable lines) 44 | { 45 | var iniContent = ReadIniContent(lines.ToList()); 46 | var configEntries = new List(); 47 | 48 | foreach (KeyValuePair> section in iniContent) 49 | { 50 | if (string.IsNullOrWhiteSpace(section.Key)) 51 | { 52 | continue; 53 | } 54 | 55 | var sectionSplit = section.Key.Split(ProfileSplit.ToCharArray(), 2); 56 | var configEntry = new BumpyConfigEntry(); 57 | configEntry.Glob = sectionSplit[0].Trim(); 58 | 59 | if (sectionSplit.Length == 2) 60 | { 61 | configEntry.Profile = sectionSplit[1].Trim(); 62 | } 63 | 64 | foreach (KeyValuePair iniKeyValue in section.Value) 65 | { 66 | if (iniKeyValue.Key.Equals("encoding", StringComparison.OrdinalIgnoreCase)) 67 | { 68 | configEntry.Encoding = GetEncoding(iniKeyValue.Value); 69 | } 70 | else if (iniKeyValue.Key.Equals("regex", StringComparison.OrdinalIgnoreCase)) 71 | { 72 | configEntry.Regex = iniKeyValue.Value; 73 | } 74 | else 75 | { 76 | throw new ConfigException($"Unrecognized element: '{iniKeyValue.Key}'"); 77 | } 78 | } 79 | 80 | configEntries.Add(configEntry); 81 | } 82 | 83 | return configEntries; 84 | } 85 | 86 | private static Dictionary> ReadIniContent(IList lines) 87 | { 88 | var iniContent = new Dictionary>(); 89 | var currentSection = string.Empty; 90 | 91 | for (int i = 0; i < lines.Count; i++) 92 | { 93 | var line = lines[i]; 94 | var isComment = line.StartsWith(CommentPattern, StringComparison.Ordinal) || string.IsNullOrWhiteSpace(line); 95 | var isSection = line.StartsWith(SectionStartPattern, StringComparison.Ordinal) && line.EndsWith(SectionEndPattern, StringComparison.Ordinal); 96 | 97 | if (isSection) 98 | { 99 | currentSection = line.Substring(1, line.Length - 2).Trim(); 100 | 101 | if (iniContent.ContainsKey(currentSection)) 102 | { 103 | throw new ConfigException($"Encountered ambiguous configuration for element '{currentSection}'"); 104 | } 105 | else 106 | { 107 | iniContent.Add(currentSection, new Dictionary()); 108 | } 109 | } 110 | else if (!isComment) 111 | { 112 | var lineSplit = line.Split(EqualsPattern.ToCharArray(), 2); 113 | 114 | if (lineSplit.Length != 2) 115 | { 116 | throw new ConfigException($"Invalid syntax in line {i + 1}. Expected format: 'key = value'"); 117 | } 118 | 119 | var key = lineSplit[0].Trim(); 120 | var value = lineSplit[1].Trim(); 121 | 122 | if (!iniContent[currentSection].ContainsKey(key)) 123 | { 124 | iniContent[currentSection].Add(key, value); 125 | } 126 | else 127 | { 128 | iniContent[currentSection][key] = value; 129 | } 130 | } 131 | } 132 | 133 | return iniContent; 134 | } 135 | 136 | private static Encoding GetEncoding(string encodingText) 137 | { 138 | bool isCodingPage = int.TryParse(encodingText, out var codePage); 139 | 140 | if (isCodingPage) 141 | { 142 | return Encoding.GetEncoding(codePage); 143 | } 144 | else 145 | { 146 | return Encoding.GetEncoding(encodingText); 147 | } 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/Bumpy.Core.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {F0BDA27C-22CF-4A88-A045-D389F4C4C477} 10 | Library 11 | Properties 12 | Bumpy.Core.Tests 13 | Bumpy.Core.Tests 14 | v4.6.1 15 | 512 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | true 28 | ..\Bumpy.ruleset 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | true 38 | ..\Bumpy.ruleset 39 | 40 | 41 | 42 | ..\packages\Castle.Core.4.2.0\lib\net45\Castle.Core.dll 43 | 44 | 45 | ..\packages\NSubstitute.3.1.0\lib\net46\NSubstitute.dll 46 | 47 | 48 | 49 | 50 | 51 | ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll 61 | True 62 | 63 | 64 | ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll 65 | 66 | 67 | ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll 68 | 69 | 70 | ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | PreserveNewest 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | {03a499c5-3d23-48e0-9892-aacea22620aa} 97 | Bumpy.Core 98 | 99 | 100 | 101 | 102 | 103 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Source/Bumpy.Core.Tests/VersionFunctionsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Bumpy.Core.Tests 5 | { 6 | public class VersionFunctionsTests 7 | { 8 | [Theory] 9 | [InlineData(1, "2.2.2.2", "3.0.0.0", true)] 10 | [InlineData(2, "2.2.2.2", "2.3.0.0", true)] 11 | [InlineData(3, "2.2.2.2", "2.2.3.0", true)] 12 | [InlineData(4, "2.2.2.2", "2.2.2.3", true)] 13 | [InlineData(4, "2.2.2.2+bar", "2.2.2.3+bar", true)] 14 | [InlineData(1, "2.2.2.2", "3.2.2.2", false)] 15 | [InlineData(2, "2.2.2.2", "2.3.2.2", false)] 16 | [InlineData(3, "2.2.2.2", "2.2.3.2", false)] 17 | [InlineData(4, "2.2.2.2", "2.2.2.3", false)] 18 | [InlineData(4, "2.2.2.2+bar", "2.2.2.3+bar", false)] 19 | [InlineData(4, "2.2.2.002", "2.2.2.003", false)] 20 | [InlineData(4, "2.2.2.09", "2.2.2.10", false)] 21 | [InlineData(3, "2.2.99.2", "2.2.100.2", false)] 22 | [InlineData(2, "2.2.99.2", "2.3.0.0", true)] 23 | [InlineData(1, "2,2,2,2", "3,0,0,0", true)] 24 | public void Increment_IncrementDifferentPositions(int position, string originalVersionText, string expectedVersionText, bool cascade) 25 | { 26 | var version = VersionFunctions.ParseVersion(originalVersionText); 27 | 28 | var newVersion = VersionFunctions.Increment(version, position, cascade); 29 | 30 | Assert.Equal(expectedVersionText, newVersion.ToString()); 31 | } 32 | 33 | [Fact] 34 | public void Increment_InvalidArgument() 35 | { 36 | var version = VersionFunctions.ParseVersion("2.2.2.2"); 37 | 38 | Assert.Throws(() => VersionFunctions.Increment(version, -1, true)); 39 | } 40 | 41 | [Theory] 42 | [InlineData(1, "1.1.1", "9.1.1", "9")] 43 | [InlineData(2, "1.1.1", "1.9.1", "9")] 44 | [InlineData(3, "1.1.1", "1.1.9", "9")] 45 | [InlineData(1, "1.1.1-foo", "9.1.1-foo", "9")] 46 | [InlineData(1, "1.1.1", "009.1.1", "009")] 47 | [InlineData(1, "1,1,1", "9,1,1", "9")] 48 | public void Assign_AssignPositions(int position, string originalVersionText, string expectedVersionText, string formattedNumber) 49 | { 50 | var version = VersionFunctions.ParseVersion(originalVersionText); 51 | 52 | var newVersion = VersionFunctions.Assign(version, position, formattedNumber); 53 | 54 | Assert.Equal(expectedVersionText, newVersion.ToString()); 55 | } 56 | 57 | [Fact] 58 | public void Assign_InvalidArgument() 59 | { 60 | var version = VersionFunctions.ParseVersion("2.2.2"); 61 | 62 | Assert.Throws(() => VersionFunctions.Assign(version, 0, "-1")); 63 | Assert.Throws(() => VersionFunctions.Assign(version, -1, "0")); 64 | Assert.Throws(() => VersionFunctions.Assign(version, 1, "not a number")); 65 | } 66 | 67 | [Theory] 68 | [InlineData("1.1.1", "1.1.1", "")] 69 | [InlineData("1.1.1", "1.1.1-beta", "-beta")] 70 | [InlineData("1.1.1-alpha", "1.1.1-beta", "-beta")] 71 | [InlineData("1.1.1-alpha", "1.1.1", "")] 72 | [InlineData("1,1,1", "1,1,1-beta", "-beta")] 73 | public void Label_ChangeLabel(string originalVersionText, string expectedVersionText, string versionLabel) 74 | { 75 | var version = VersionFunctions.ParseVersion(originalVersionText); 76 | 77 | var newVersion = VersionFunctions.Label(version, versionLabel); 78 | 79 | Assert.Equal(expectedVersionText, newVersion.ToString()); 80 | } 81 | 82 | [Theory] 83 | [InlineData("8")] 84 | [InlineData("100.5")] 85 | [InlineData("2018.01.01")] 86 | [InlineData("1.0.0.9")] 87 | [InlineData("1.0.0.9-foo")] 88 | [InlineData("1.100aBc")] 89 | [InlineData("18.5.3-beta03")] 90 | [InlineData("8.25.3-beta03+master0004")] 91 | [InlineData("8.5.43+bar")] 92 | [InlineData("1.0.0_final2")] 93 | [InlineData("1,0,0,9")] 94 | public void ParseVersionFromText_ValidText(string versionText) 95 | { 96 | var version = VersionFunctions.ParseVersion(versionText); 97 | 98 | Assert.Equal(versionText, version.ToString()); 99 | } 100 | 101 | [Fact] 102 | public void ParseVersionFromText_InvalidText() 103 | { 104 | Assert.Throws(() => VersionFunctions.ParseVersion("invalid.input")); 105 | } 106 | 107 | [Fact] 108 | public void TryParseVersionInText_Found() 109 | { 110 | var success = VersionFunctions.TryParseVersionInText("a1.2a", @"a(?\d\.\d)a", out var version, out var marker); 111 | 112 | Assert.True(success); 113 | Assert.Equal("1.2", version.ToString()); 114 | Assert.True(string.IsNullOrEmpty(marker)); 115 | } 116 | 117 | [Fact] 118 | public void TryParseVersionInText_InvalidRegexWithoutNamedGroup() 119 | { 120 | var success = VersionFunctions.TryParseVersionInText("100", @"\d", out var version, out var marker); 121 | 122 | Assert.False(success); 123 | Assert.Null(version); 124 | Assert.True(string.IsNullOrEmpty(marker)); 125 | } 126 | 127 | [Fact] 128 | public void TryParseVersionInText_InvalidRegexWithNamedGroup() 129 | { 130 | Assert.Throws(() => VersionFunctions.TryParseVersionInText("1.0//", @"(?1.0//)", out _, out _)); 131 | } 132 | 133 | [Fact] 134 | public void TryParseVersionInText_ValidRegexButTextIsNoVersion() 135 | { 136 | var success = VersionFunctions.TryParseVersionInText("something", @"a(?\d\.\d)a", out var version, out var marker); 137 | 138 | Assert.False(success); 139 | Assert.Null(version); 140 | Assert.True(string.IsNullOrEmpty(marker)); 141 | } 142 | 143 | [Fact] 144 | public void TryParseVersionInText_ValidMarker() 145 | { 146 | var success = VersionFunctions.TryParseVersionInText("foo 1.0", @"(?foo) (?1.0)", out var _, out var marker); 147 | 148 | Assert.True(success); 149 | Assert.Equal("foo", marker); 150 | } 151 | 152 | [Fact] 153 | public void EnsureExpectedVersion_Ok() 154 | { 155 | var expectedText = "a1.2a"; 156 | var version = VersionFunctions.ParseVersion("1.2"); 157 | 158 | var actualText = VersionFunctions.EnsureExpectedVersion(expectedText, @"a(?\d\.\d)a", version); 159 | 160 | Assert.Equal(expectedText, actualText); 161 | } 162 | 163 | [Fact] 164 | public void EnsureExpectedVersion_WrongText() 165 | { 166 | var version = VersionFunctions.ParseVersion("1.2"); 167 | 168 | Assert.Throws(() => VersionFunctions.EnsureExpectedVersion("not a number", @"(?\d\.\d)", version)); 169 | } 170 | 171 | [Fact] 172 | public void EnsureExpectedVersion_WrongRegex() 173 | { 174 | var version = VersionFunctions.ParseVersion("1.2"); 175 | 176 | Assert.Throws(() => VersionFunctions.EnsureExpectedVersion("1.2", "wrong regex", version)); 177 | } 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /Source/build.ps1: -------------------------------------------------------------------------------- 1 | ########################################################################## 2 | # This is the Cake bootstrapper script for PowerShell. 3 | # This file was downloaded from https://github.com/cake-build/resources 4 | # Feel free to change this file to fit your needs. 5 | ########################################################################## 6 | 7 | <# 8 | 9 | .SYNOPSIS 10 | This is a Powershell script to bootstrap a Cake build. 11 | 12 | .DESCRIPTION 13 | This Powershell script will download NuGet if missing, restore NuGet tools (including Cake) 14 | and execute your Cake build script with the parameters you provide. 15 | 16 | .PARAMETER Script 17 | The build script to execute. 18 | .PARAMETER Target 19 | The build script target to run. 20 | .PARAMETER Configuration 21 | The build configuration to use. 22 | .PARAMETER Verbosity 23 | Specifies the amount of information to be displayed. 24 | .PARAMETER Experimental 25 | Tells Cake to use the latest Roslyn release. 26 | .PARAMETER WhatIf 27 | Performs a dry run of the build script. 28 | No tasks will be executed. 29 | .PARAMETER Mono 30 | Tells Cake to use the Mono scripting engine. 31 | .PARAMETER SkipToolPackageRestore 32 | Skips restoring of packages. 33 | .PARAMETER ScriptArgs 34 | Remaining arguments are added here. 35 | 36 | .LINK 37 | https://cakebuild.net 38 | 39 | #> 40 | 41 | [CmdletBinding()] 42 | Param( 43 | [string]$Script = "build.cake", 44 | [string]$Target = "Default", 45 | [ValidateSet("Release", "Debug")] 46 | [string]$Configuration = "Release", 47 | [ValidateSet("Quiet", "Minimal", "Normal", "Verbose", "Diagnostic")] 48 | [string]$Verbosity = "Verbose", 49 | [switch]$Experimental, 50 | [Alias("DryRun","Noop")] 51 | [switch]$WhatIf, 52 | [switch]$Mono, 53 | [switch]$SkipToolPackageRestore, 54 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 55 | [string[]]$ScriptArgs 56 | ) 57 | 58 | [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null 59 | function MD5HashFile([string] $filePath) 60 | { 61 | if ([string]::IsNullOrEmpty($filePath) -or !(Test-Path $filePath -PathType Leaf)) 62 | { 63 | return $null 64 | } 65 | 66 | [System.IO.Stream] $file = $null; 67 | [System.Security.Cryptography.MD5] $md5 = $null; 68 | try 69 | { 70 | $md5 = [System.Security.Cryptography.MD5]::Create() 71 | $file = [System.IO.File]::OpenRead($filePath) 72 | return [System.BitConverter]::ToString($md5.ComputeHash($file)) 73 | } 74 | finally 75 | { 76 | if ($file -ne $null) 77 | { 78 | $file.Dispose() 79 | } 80 | } 81 | } 82 | 83 | Write-Host "Preparing to run build script..." 84 | 85 | if(!$PSScriptRoot){ 86 | $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent 87 | } 88 | 89 | $TOOLS_DIR = Join-Path $PSScriptRoot "tools" 90 | $ADDINS_DIR = Join-Path $TOOLS_DIR "Addins" 91 | $MODULES_DIR = Join-Path $TOOLS_DIR "Modules" 92 | $NUGET_EXE = Join-Path $TOOLS_DIR "nuget.exe" 93 | $CAKE_EXE = Join-Path $TOOLS_DIR "Cake/Cake.exe" 94 | $NUGET_URL = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe" 95 | $PACKAGES_CONFIG = Join-Path $TOOLS_DIR "packages.config" 96 | $PACKAGES_CONFIG_MD5 = Join-Path $TOOLS_DIR "packages.config.md5sum" 97 | $ADDINS_PACKAGES_CONFIG = Join-Path $ADDINS_DIR "packages.config" 98 | $MODULES_PACKAGES_CONFIG = Join-Path $MODULES_DIR "packages.config" 99 | 100 | # Should we use mono? 101 | $UseMono = ""; 102 | if($Mono.IsPresent) { 103 | Write-Verbose -Message "Using the Mono based scripting engine." 104 | $UseMono = "-mono" 105 | } 106 | 107 | # Should we use the new Roslyn? 108 | $UseExperimental = ""; 109 | if($Experimental.IsPresent -and !($Mono.IsPresent)) { 110 | Write-Verbose -Message "Using experimental version of Roslyn." 111 | $UseExperimental = "-experimental" 112 | } 113 | 114 | # Is this a dry run? 115 | $UseDryRun = ""; 116 | if($WhatIf.IsPresent) { 117 | $UseDryRun = "-dryrun" 118 | } 119 | 120 | # Make sure tools folder exists 121 | if ((Test-Path $PSScriptRoot) -and !(Test-Path $TOOLS_DIR)) { 122 | Write-Verbose -Message "Creating tools directory..." 123 | New-Item -Path $TOOLS_DIR -Type directory | out-null 124 | } 125 | 126 | # Make sure that packages.config exist. 127 | if (!(Test-Path $PACKAGES_CONFIG)) { 128 | Write-Verbose -Message "Downloading packages.config..." 129 | try { (New-Object System.Net.WebClient).DownloadFile("https://cakebuild.net/download/bootstrapper/packages", $PACKAGES_CONFIG) } catch { 130 | Throw "Could not download packages.config." 131 | } 132 | } 133 | 134 | # Try find NuGet.exe in path if not exists 135 | if (!(Test-Path $NUGET_EXE)) { 136 | Write-Verbose -Message "Trying to find nuget.exe in PATH..." 137 | $existingPaths = $Env:Path -Split ';' | Where-Object { (![string]::IsNullOrEmpty($_)) -and (Test-Path $_ -PathType Container) } 138 | $NUGET_EXE_IN_PATH = Get-ChildItem -Path $existingPaths -Filter "nuget.exe" | Select -First 1 139 | if ($NUGET_EXE_IN_PATH -ne $null -and (Test-Path $NUGET_EXE_IN_PATH.FullName)) { 140 | Write-Verbose -Message "Found in PATH at $($NUGET_EXE_IN_PATH.FullName)." 141 | $NUGET_EXE = $NUGET_EXE_IN_PATH.FullName 142 | } 143 | } 144 | 145 | # Try download NuGet.exe if not exists 146 | if (!(Test-Path $NUGET_EXE)) { 147 | Write-Verbose -Message "Downloading NuGet.exe..." 148 | try { 149 | (New-Object System.Net.WebClient).DownloadFile($NUGET_URL, $NUGET_EXE) 150 | } catch { 151 | Throw "Could not download NuGet.exe." 152 | } 153 | } 154 | 155 | # Save nuget.exe path to environment to be available to child processed 156 | $ENV:NUGET_EXE = $NUGET_EXE 157 | 158 | # Restore tools from NuGet? 159 | if(-Not $SkipToolPackageRestore.IsPresent) { 160 | Push-Location 161 | Set-Location $TOOLS_DIR 162 | 163 | # Check for changes in packages.config and remove installed tools if true. 164 | [string] $md5Hash = MD5HashFile($PACKAGES_CONFIG) 165 | if((!(Test-Path $PACKAGES_CONFIG_MD5)) -Or 166 | ($md5Hash -ne (Get-Content $PACKAGES_CONFIG_MD5 ))) { 167 | Write-Verbose -Message "Missing or changed package.config hash..." 168 | Remove-Item * -Recurse -Exclude packages.config,nuget.exe 169 | } 170 | 171 | Write-Verbose -Message "Restoring tools from NuGet..." 172 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$TOOLS_DIR`"" 173 | 174 | if ($LASTEXITCODE -ne 0) { 175 | Throw "An error occured while restoring NuGet tools." 176 | } 177 | else 178 | { 179 | $md5Hash | Out-File $PACKAGES_CONFIG_MD5 -Encoding "ASCII" 180 | } 181 | Write-Verbose -Message ($NuGetOutput | out-string) 182 | 183 | Pop-Location 184 | } 185 | 186 | # Restore addins from NuGet 187 | if (Test-Path $ADDINS_PACKAGES_CONFIG) { 188 | Push-Location 189 | Set-Location $ADDINS_DIR 190 | 191 | Write-Verbose -Message "Restoring addins from NuGet..." 192 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$ADDINS_DIR`"" 193 | 194 | if ($LASTEXITCODE -ne 0) { 195 | Throw "An error occured while restoring NuGet addins." 196 | } 197 | 198 | Write-Verbose -Message ($NuGetOutput | out-string) 199 | 200 | Pop-Location 201 | } 202 | 203 | # Restore modules from NuGet 204 | if (Test-Path $MODULES_PACKAGES_CONFIG) { 205 | Push-Location 206 | Set-Location $MODULES_DIR 207 | 208 | Write-Verbose -Message "Restoring modules from NuGet..." 209 | $NuGetOutput = Invoke-Expression "&`"$NUGET_EXE`" install -ExcludeVersion -OutputDirectory `"$MODULES_DIR`"" 210 | 211 | if ($LASTEXITCODE -ne 0) { 212 | Throw "An error occured while restoring NuGet modules." 213 | } 214 | 215 | Write-Verbose -Message ($NuGetOutput | out-string) 216 | 217 | Pop-Location 218 | } 219 | 220 | # Make sure that Cake has been installed. 221 | if (!(Test-Path $CAKE_EXE)) { 222 | Throw "Could not find Cake.exe at $CAKE_EXE" 223 | } 224 | 225 | # Start Cake 226 | Write-Host "Running build script..." 227 | Invoke-Expression "& `"$CAKE_EXE`" `"$Script`" -target=`"$Target`" -configuration=`"$Configuration`" -verbosity=`"$Verbosity`" $UseMono $UseDryRun $UseExperimental $ScriptArgs" 228 | exit $LASTEXITCODE 229 | -------------------------------------------------------------------------------- /Source/Bumpy.Core/VersionFunctions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Bumpy.Core 6 | { 7 | /// 8 | /// A set of functions which manipulate objects. 9 | /// 10 | public static class VersionFunctions 11 | { 12 | private const string VersionGroupName = "version"; 13 | private const string NumbersGroupName = "numbers"; 14 | private const string LabelGroupName = "label"; 15 | private const string MarkerGroupName = "marker"; 16 | 17 | private static readonly Regex BumpyRegex = new Regex(@"^(?\d+((\.|,)\d+)*)(?