defmodule Vsntool do import Vsntool.Util alias Vsntool.Plugin @vsntool_version Vsntool.MixProject.project()[:version] @options %{ "i" => "init", "bm" => "bump_major", "bi" => "bump_minor", "bp" => "bump_patch", "br" => "bump_rc", "r" => "release", "c" => "current", "l" => "last", "h" => "help", "v" => "--version" } @shortcuts Map.keys(@options) @next_commands ~w(next_major next_minor next_patch next_rc) @commands Map.values(@options) ++ @next_commands @kinds ~w(major minor patch) @env Mix.env() def main(args) do execute(args) rescue e in Vsntool.Flunk -> IO.write(:stderr, Exception.message(e) <> "\n") if @env != :test do System.halt(1) end end def execute([]), do: execute("current", []) def execute([command | args]) when command in @commands do execute(command, args) end def execute([shortcut | rest]) when shortcut in @shortcuts do execute([@options[shortcut] | rest]) end def execute(_), do: execute("usage", []) def execute("current", []) do vsn = case version_from_file!() do %{pre: ["dev"]} = vsn -> %{vsn | pre: [hash()]} _ -> version_from_git() end IO.puts(vsn) end def execute("bump_" <> kind, ["--dev"]) when kind in @kinds do do_bump(kind, ["dev"]) end def execute("bump_" <> kind, ["--rc"]) when kind in @kinds do do_bump(kind, ["rc.0"]) end def execute("bump_" <> kind, []) when kind in @kinds do do_bump(kind, []) end def execute("next_" <> kind, ["--dev"]) when kind in @kinds do do_next(kind, ["dev"]) end def execute("next_" <> kind, ["--rc"]) when kind in @kinds do do_next(kind, ["rc.0"]) end def execute("next_" <> kind, []) when kind in @kinds do do_next(kind, []) end def execute("next_rc", []) do assert_release_branch() vsn = version_from_file!() pre = case vsn.pre do ["dev"] -> ["rc", 0] _other -> n = rc_number(vsn) if n == nil do flunk("Need to be on a RC version number to bump, currently on #{vsn}") end ["rc", n + 1] end IO.puts(%{vsn | pre: pre}) end def execute("last", []) do IO.puts(version_from_file!()) end def execute("bump_rc", []) do assert_release_branch() vsn = version_from_file!() pre = case vsn.pre do ["dev"] -> ["rc", 0] _other -> n = rc_number(vsn) if n == nil do flunk("Need to be on a RC version number to bump, currently on #{vsn}") end ["rc", n + 1] end persist_version(%{vsn | pre: pre}) end def execute("release", []) do assert_release_branch() vsn = version_from_file!() if vsn.pre == [] do flunk("Not on a pre-release: #{vsn}") end vsn = %{vsn | pre: []} persist_version(vsn) end def execute("--version", []) do IO.puts(@vsntool_version) end def execute("help", []) do help_message = """ Usage: vsntool [options] Options: i, init Create a new VERSION file and initialize a git repository bm, bump_major Bump major version (with --dev option) bi, bump_minor Bump minor version (with --dev option) bp, bump_patch Bump patch version (with --dev option) next_major Print next major version after bump (with --dev or --rc) next_minor Print next minor version after bump (with --dev or --rc) next_patch Print next patch version after bump (with --dev or --rc) next_rc Print next RC version after bump_rc c, current Current version l, last Last released version h, help Display this help v, --version Display vsntool version """ IO.puts(help_message) end def execute("usage", []) do flunk( "Usage: vsntool (init|bump_major|bump_minor|bump_patch|next_major|next_minor|next_patch|next_rc|current|last|help)" ) end def execute("init", []) do execute("init", [System.get_env("VERSION", "0.0.1")]) end def execute("init", [initial_version]) do if File.exists?("VERSION") do flunk("This project already has a VERSION file") end if !File.exists?(".git") do IO.puts("Initialized git repository") shell("git init") end persist_version(Version.parse!(initial_version)) end def execute(command, args) do flunk("Invalid args given to #{command}: #{inspect(args)}") end def persist_version(vsn) do case shell("git tag -l #{vsn}") do "" -> call_hook("pre_persist", [to_string(vsn)]) File.write!("VERSION", to_string(vsn) <> "\n") Plugin.discover() |> Enum.map(fn {plugin, file} -> IO.puts("* plugin: #{inspect(plugin)} → #{file}") plugin.persist_version(vsn, file) end) shell("git add VERSION") shell("git commit -n -m 'Bump version to #{vsn}'") if vsn.pre != ["dev"] do shell("git tag -a '#{vsn_prefix()}#{vsn}' -m 'Tagged version #{vsn}'") end shell("git push") shell("git push --tags") IO.puts("Version bump to #{vsn} OK.") ^vsn -> flunk("There is already a git tag called #{vsn}") end end defp do_bump(kind, pre) do assert_release_branch() vsn = version_from_file!() git_vsn = version_from_git() if vsn == git_vsn && System.get_env("FORCE") != "true" do flunk("Current commit is already tagged (#{vsn})") end if vsn.pre != [] do flunk("Cannot bump when on prerelease (#{vsn})") end version = bump(String.to_atom(kind), vsn) version = %{version | pre: pre} persist_version(version) end defp do_next(kind, pre) do assert_release_branch() vsn = version_from_file!() git_vsn = version_from_git() if vsn == git_vsn && System.get_env("FORCE") != "true" do flunk("Current commit is already tagged (#{vsn})") end if vsn.pre != [] do flunk("Cannot bump when on prerelease (#{vsn})") end version = bump(String.to_atom(kind), vsn) version = %{version | pre: pre} IO.puts(version) end defp assert_release_branch() do if branch() not in vsn_branches() do flunk( "You need to be on branch #{Enum.join(vsn_branches(), " or ")} to bump versions (currently on #{branch()})" ) end end defp call_hook(name, args) do hookfile = Path.join([File.cwd!(), ".vsntool/hooks", name]) if File.exists?(hookfile) do {_, exitcode} = System.cmd(hookfile, args, into: IO.stream()) if exitcode != 0 do flunk("#{name} hook exited with code #{exitcode}") end end end end