revision 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 94dfd966e2e44d185a37be41bb1fe745455f0e6d
4
+ data.tar.gz: cf971a767a20dbc24696adff7bbf634e821e8ea0
5
+ SHA512:
6
+ metadata.gz: 7e5ec6a596855ad4f50c5ee1307c9cfac3e4c7fe4918f6e4c03b00879c8e7824caa1675bea87d9ac4b0601b165bf6f6b8e1dd2c7e5e95b31e3f6450fe759d994
7
+ data.tar.gz: 6b71b8ed50740a488904d66b35e52bdcfb14dd5ec5bfcd71cef1cd387e94cdbfd8816584a60fec3342e6133820c108a53cf299dc6964e03207b205fa28a08440
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ Gemfile.lock
13
+
14
+ README.html
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.2
5
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ #git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in revision.gemspec
6
+ gemspec
data/README.org ADDED
@@ -0,0 +1,269 @@
1
+ #+TITLE: Revision management gem
2
+ #+AUTHOR: Cormac Cannon
3
+ #+EMAIL: cormac.cannon@neuromoddevices.com
4
+ #+LANGUAGE: en
5
+
6
+ # Generic properties
7
+ # :eval never-export
8
+ # ... Prevent auto evaluation of blocks on export. Evaluate them interactively instead
9
+ # :results verbatim
10
+ # ... Output results verbatim rather than autodetecting (which usually ends up tabular). Override per script using :results tabular
11
+ #+PROPERTY: header-args :results verbatim :noweb yes :exports both :eval never-export
12
+ # #+PROPERTY: header-args :results verbatim :noweb yes :exports both
13
+
14
+ # HTML EXPORT SETUP
15
+
16
+ # 1. Auto-export to html on save (N.B. This doesn't work?)
17
+ # This can be done per-session by calling 'toggle-org-html-export-on-save'
18
+ # Local variables:
19
+ # eval: (add-hook 'after-save-hook 'org-html-export-to-html t t)
20
+ # End:
21
+
22
+ # 2. Apply a theme
23
+ #+SETUPFILE: org-themes/theme-bigblow.setup
24
+ # or alternatively #+SETUPFILE: theme-readtheorg.setup
25
+
26
+ * Overview
27
+
28
+ This gem automates revision management for source projects. The tool is language agnostic (used for C, ruby and matlab projects, to date -- though you're
29
+ probably better off using bundler for ruby projects). It supports per-project configuration using a yaml-format file called =releasables.yaml= located at
30
+ the project root.
31
+
32
+ It currently supports the following functionality
33
+ - Manage 3-component revision IDs embedded natively in source file
34
+ - Embeds/updates/extracts changelog in source file header/footer comments
35
+ - Automatically prompts for a changelog entry each time a revision identifier is incremented
36
+ - Optionally commits and tags changes to a git repo after an update to the revision ID
37
+ - Builds and archives projects in zip format (including release notes and arbitrary release artefacts defined
38
+
39
+ * Installation
40
+
41
+ #+BEGIN_NOTE
42
+ The gem hasn't been uploaded to rubygems.org as yet.
43
+ #+END_NOTE
44
+
45
+ ** From rubygems.org
46
+
47
+ #+BEGIN_SRC sh
48
+ gem install revision
49
+ #+END_SRC
50
+
51
+ ** From source
52
+
53
+ *** Checkout
54
+
55
+ #+BEGIN_SRC sh
56
+ gem install bundler
57
+ git clone git@git.nmd.ie:gem/revision
58
+ cd revision
59
+ #+END_SRC
60
+
61
+ *** Install
62
+
63
+ #+BEGIN_SRC sh
64
+ bundle install
65
+ bundle exec rake install
66
+ #+END_SRC
67
+
68
+ #+RESULTS:
69
+ #+begin_example
70
+ Using rake 10.5.0
71
+ Using bundler 1.16.0
72
+ Using coderay 1.1.2
73
+ Using diff-lcs 1.3
74
+ Using git 1.3.0
75
+ Using method_source 0.9.0
76
+ Using pry 0.11.3
77
+ Using rubyzip 1.2.1
78
+ Using thor 0.19.4
79
+ Using revision 1.0.0 from source at `.`
80
+ Using rspec-support 3.7.0
81
+ Using rspec-core 3.7.0
82
+ Using rspec-expectations 3.7.0
83
+ Using rspec-mocks 3.7.0
84
+ Using rspec 3.7.0
85
+ Bundle complete! 5 Gemfile dependencies, 15 gems now installed.
86
+ Use `bundle info [gemname]` to see where a bundled gem is installed.
87
+ revision 1.0.0 built to pkg/revision-1.0.0.gem.
88
+ revision (1.0.0) installed.
89
+ #+end_example
90
+
91
+ * Usage
92
+
93
+ ** Supported Commands
94
+ Run the executable with no arguments to get usage instructions in your console window...
95
+
96
+ #+BEGIN_SRC sh
97
+ revision
98
+ #+END_SRC
99
+
100
+ #+RESULTS:
101
+ #+begin_example
102
+ Loading releasable definitions from /home/cormacc/nextcloud/nmd/gem/revision/releasables.yaml ...
103
+ Commands:
104
+ revision archive # Archive releasables
105
+ revision changelog # Display change log on stdout
106
+ revision help [COMMAND] # Describe available commands or one specific command
107
+ revision info # Display info for all defined releasables
108
+ revision major # Increment major revision index
109
+ revision minor # Increment minor revision index
110
+ revision patch # Increment patch revision index
111
+ revision tag # Commit the current revision to a local git repo ...
112
+
113
+ Options:
114
+ [--dryrun], [--no-dryrun]
115
+ [--id=ID]
116
+
117
+ #+end_example
118
+
119
+ #+BEGIN_NOTE
120
+ The tool can be run from any subfolder of a project root -- it will traverse the tree until it finds
121
+ an ancestor containing =releasables.yaml= OR can go no further (in which case it throws an error).
122
+ #+END_NOTE
123
+
124
+ *** Operating on multiple releasables
125
+
126
+ A single =releasables.yaml= file can define multiple releasables, either implicitly (via inclusion) or explicitly
127
+ (see [[Configuration]] section below for examples). In this context, the ~info~ and ~archive~ commands
128
+ will operate on all defined releasables, whereas the remaining commands will require the releasable
129
+ to be specified using the ~--id=~ option, e.g.
130
+
131
+ #+BEGIN_SRC sh
132
+ revision minor --id=firmware
133
+ #+END_SRC
134
+
135
+ ** Configuration
136
+
137
+ YAML syntax is used for the configuration file. The [[Syntax]] and [[Examples]] sections below should provide sufficient
138
+ introduction to the limit subset of language features required to use this tool, however further info
139
+ may be found at the following links:
140
+
141
+ - http://docs.ansible.com/ansible/latest/YAMLSyntax.html
142
+ - http://www.yaml.org/start.html
143
+
144
+ *** Syntax
145
+
146
+ The =releasables.yaml= file should contain a top level ~:releasables~ node.
147
+ Under this, you can create a list (or YAML /sequence/) of releasable definitions.
148
+
149
+ Each sequence item begins with a ~-~ and contains either a link to a folder containing its own =releasables.yaml=
150
+ defining one or more releasables ...
151
+
152
+ #+BEGIN_SRC yaml
153
+ - :folder: relative/path/to/a/releasable/folder
154
+ #+END_SRC
155
+
156
+ ... or a single inline releasable definition.
157
+
158
+ #+BEGIN_NOTE
159
+ The lines beginning with '#' are explanatory comments
160
+ #+END_NOTE
161
+
162
+ #+BEGIN_SRC yaml
163
+ - :id: my_releasable
164
+ :revision:
165
+ # Source file containing the revision identifier
166
+ # This will also include changelog entries, embedded as comments
167
+ :src: lib/revision/version.rb
168
+ # Regex matching the source revision identifier. Must contain the following named capture groups
169
+ # - major, minor, patch :: Numeric (uint) sequences representing the three revision ID components
170
+ # - sep1, sep2 :: the characters separating the revision components
171
+ # - prefix, postfix :: sufficient syntactic context to match the revision ID uniquely
172
+ # N.B. this regex matches the version ID from the standard bundler gem skeleton,
173
+ # e.g. VERSION = "1.1.0"
174
+ :regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
175
+ # Comment char for the project language -- prefixed to each line of changelog entries
176
+ # Quotes only necessary here to prevent # being interpreted as the beginning of a YAML comment
177
+ :comment_prefix: "#"
178
+ # Sequence of build steps -- each item should be a valid shell command, prefixed with the YAML sequence item token, '- '
179
+ :build_steps:
180
+ - bundle exec rake install
181
+ # Sequence defining the files (build artefacts) to package in the release archive.
182
+ # Each artefact definition must include a :src: key/value pair.
183
+ # An optional :dest: value may be provided to rename the file during packaging, or just (as in the first entry below)
184
+ # to flatten the folder structure.
185
+ # Any <VER> (or <REV>) in the :src: or :dest: placeholders wil be replaced with the current revision ID
186
+ # The revision archive will also include the revision history extracted as a text file
187
+ :artefacts:
188
+ # A binary artefact -- the
189
+ - :src: pkg/revision-<VER>.gem
190
+ :dest: revision-<VER>.gem
191
+ # This document -- the :dest: value defaults to duplicating :src: if not specified
192
+ - :src: README.org
193
+ #+END_SRC
194
+
195
+ **** TODO (or at least consider) add support for overridable defaults
196
+ e.g. via a =.releasables= configuration file in the user home dir.
197
+ Though this would be bad for collaborative development as the config file would live outside source control.
198
+ Possibly useful to support inclusion of a controlled configuration file instead? Primarily to define
199
+ a revision regex and comment prefix for a group of related releasables....
200
+
201
+ *** Examples
202
+ **** C project
203
+
204
+ #+BEGIN_NOTE
205
+ The ~:regex:~ and ~:comment_prefix:~ keys are absent in the C example below. This project started life as
206
+ managing some embedded C projects, and the default values reflect this.
207
+ #+END_NOTE
208
+
209
+ #+BEGIN_SRC yaml
210
+ :releasables:
211
+ - :id: mbt_cd_firmware
212
+ :revision:
213
+ :src: src/FirmwareRevision.c
214
+ :build_steps:
215
+ - make --jobs=8 -f Makefile CONF=bootloadable
216
+ :artefacts:
217
+ - :src: dist/bootloadable/production/firmware.production.hex
218
+ :dest: mbt_cd_firmware_v<REV>.bootloadable.hex
219
+ - :src: dist/default/production/firmware.production.hex
220
+ :dest: mbt_cd_firmware_v<REV>.standalone.hex
221
+ #+END_SRC
222
+
223
+ **** Ruby project
224
+
225
+ #+BEGIN_SRC yaml
226
+ :releasables:
227
+ - :id: revision
228
+ :revision:
229
+ :src: lib/revision/version.rb
230
+ :regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
231
+ :comment_prefix: "#"
232
+ :build_steps:
233
+ - bundle exec rake install
234
+ :artefacts:
235
+ - :src: pkg/revision-<VER>.gem
236
+ #+END_SRC
237
+
238
+ *** Heirarchical project
239
+ Rather than defining the releasable parameters inline, a =releasables.yaml= list entry can contain a (relative or absolute) link to another folder containing it's own =releasables.yaml=.
240
+
241
+ i.e assuming the earlier examples were in folders =examples/c= and =examples/ruby= relative to a common root, a separate =releasables.yaml=
242
+ at that root could include them as follows...
243
+
244
+ #+BEGIN_SRC yaml
245
+ :releasables:
246
+ - :folder: examples/c
247
+ - :folder: examples/ruby
248
+ #+END_SRC
249
+
250
+ **** TODO consider supporting a higher-level aggregate revision ID
251
+
252
+ #+BEGIN_SRC yaml
253
+ :revision:
254
+ :src: release_log.txt
255
+ :releasables:
256
+ - :folder: examples/c
257
+ - :folder: examples/ruby
258
+ #+END_SRC
259
+
260
+ * Development
261
+
262
+ After checking out the repo, run =bin/setup= to install dependencies. Then, run =rake spec= to run the tests. You can also run =bin/console= for an interactive prompt that will allow you to experiment.
263
+
264
+ To install this gem onto your local machine, run =bundle exec rake install=. To release a new version, update the version number in =version.rb=, and then run =bundle exec rake release=, which will create a git tag for the version, push git commits and tags, and push the =.gem= file to [rubygems.org](https://rubygems.org).
265
+
266
+
267
+ * Contributing
268
+
269
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/revision.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "revision"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/revision ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'revision'
4
+
5
+ Revision::CLI.start(ARGV)
@@ -0,0 +1,101 @@
1
+ require 'thor'
2
+
3
+ require_relative 'releasable'
4
+ require_relative 'info'
5
+ require_relative 'errors'
6
+
7
+ module Revision
8
+ class CLI < Thor
9
+ class_option :dryrun, :type => :boolean, :default =>false
10
+ class_option :id, :default => nil #Initialized after loading definition
11
+
12
+ def initialize(*args)
13
+ super
14
+ #TODO Update this to traverse up the folder heirarchy until we find a releasables.yaml
15
+ wd = Dir.getwd
16
+ loop do
17
+ begin
18
+ @releasables = Releasable.from_folder(wd)
19
+ break
20
+ rescue Errors::NoDefinition
21
+ break if wd == File.expand_path('..',wd)
22
+ puts "No releasable found in #{wd}, trying parent..."
23
+ wd = File.expand_path('..',wd)
24
+ next
25
+ end
26
+ end
27
+ raise 'No definition file found in this directory or its ancestors' if @releasables.nil? || @releasables.empty?
28
+ @id = options[:id] || @releasables.keys[0]
29
+ end
30
+
31
+ desc "info", 'Display info for all defined releasables'
32
+ def info
33
+ puts "Found #{@releasables.values.length} releasables"
34
+ puts ''
35
+ puts @releasables.values.map {|r| r.to_s}.join("\n\n")
36
+ puts ''
37
+ puts "Default releasable ID: #{@id}"
38
+ end
39
+
40
+ desc "patch", "Increment patch revision index"
41
+ def patch
42
+ do_increment('patch')
43
+ end
44
+
45
+ desc "minor", "Increment minor revision index"
46
+ def minor
47
+ do_increment('minor')
48
+ end
49
+
50
+ desc "major", "Increment major revision index"
51
+ def major
52
+ do_increment('major')
53
+ end
54
+
55
+ desc "archive", "Archive releasables"
56
+ def archive
57
+ selected = options[:id].nil? ? @releasables.values : [@releasables[options[:id]]]
58
+ puts "Archiving #{selected.length} releasables..."
59
+ selected.each do |r|
60
+ r.build
61
+ r.archive
62
+ end
63
+ end
64
+
65
+ desc "changelog", "Display change log on stdout"
66
+ def changelog
67
+ select_one.output_changelog($stdout)
68
+ end
69
+
70
+ desc "tag", "Commit the current revision to a local git repo and add a version tag"
71
+ def tag
72
+ select_one.tag
73
+ end
74
+
75
+ private
76
+
77
+ def select_one
78
+ raise "Please specify one of #{@releasables.keys}" if options[:id].nil? && @releasables.keys.length > 1
79
+ @releasables[@id]
80
+ end
81
+
82
+ def do_increment(type)
83
+ r = select_one
84
+ increment_method = "#{type}_increment!"
85
+ say "Incrementing #{r.revision} to #{r.revision.public_send(increment_method)}"
86
+ options[:dryrun] ? r.revision.write_to_file : r.revision.write!
87
+ say ""
88
+ say "The automatic commit / tag step assumes you're only checking in changes to existing files"
89
+ say "You can answer 'n' at the prompt and use 'revision tag' to generate a commit with the latest changelog entry and an associated tag after manually adding any new files to the repo"
90
+ say ""
91
+ if ask("Commit changes to existing files and add a Git tag (y/N)?").upcase=='Y'
92
+ r.tag
93
+ if ask("Push changes/tag to origin (Y/n)?").upcase=='N' || !r.push
94
+ say "To push from the command line, type 'git push --tags' at a shell prompt"
95
+ end
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,10 @@
1
+ module Revision
2
+ module Errors
3
+ class Base < StandardError; end
4
+ class NoDefinition < Base
5
+ def initialize(root)
6
+ super("No definition file (#{Releasable::CONFIG_FILE_NAME}) found at root #{root}")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,137 @@
1
+ require_relative 'releasable'
2
+ require_relative 'string_case'
3
+
4
+
5
+ class Revision::Info
6
+ DEFAULT_REGEX = /(?<prefix>\s*Info\s+const\s+\S+_REVISION\s*=\s*\{\s*\{\s*)(?<major>\d+)(?<sep1>\s*,\s*)(?<minor>\d+)(?<sep2>\s*,\s*)(?<patch>\d+)(?<postfix>\s*\}\s*\};\s*)/
7
+ DEFAULT_COMMENT_PREFIX = ' *'.freeze
8
+ CHANGELOG_START_TAG = '<BEGIN CHANGELOG>'.freeze
9
+ CHANGELOG_END_TAG = '<END CHANGELOG>'.freeze
10
+ CHANGELOG_START = /.*#{CHANGELOG_START_TAG}.*/
11
+ CHANGELOG_END = /.*#{CHANGELOG_END_TAG}.*/
12
+
13
+ attr_accessor :major, :minor, :patch
14
+ attr_accessor :src
15
+ attr_accessor :regex
16
+ attr_accessor :comment_prefix
17
+
18
+ def initialize(file, regex: nil, comment_prefix: nil)
19
+ @src=file
20
+ @regex = regex.nil? ? DEFAULT_REGEX : /#{regex}/
21
+ @comment_prefix = comment_prefix || DEFAULT_COMMENT_PREFIX
22
+ matched = false
23
+ File.open(@src).each_line do |line|
24
+ if line =~ @regex
25
+ @major = Regexp.last_match[:major].to_i
26
+ @minor = Regexp.last_match[:minor].to_i
27
+ @patch = Regexp.last_match[:patch].to_i
28
+ matched = true
29
+ break
30
+ end
31
+ end
32
+ raise "Failed to match against #{@regex}" unless matched
33
+ end
34
+
35
+ def patch_increment!
36
+ @patch += 1
37
+ self
38
+ end
39
+
40
+ def minor_increment!
41
+ @minor += 1
42
+ @patch = 0
43
+ self
44
+ end
45
+
46
+ def major_increment!
47
+ @major += 1
48
+ @minor = 0
49
+ @patch = 0
50
+ self
51
+ end
52
+
53
+ def new_changelog_placeholder
54
+ placeholder = [@comment_prefix,CHANGELOG_START_TAG,CHANGELOG_END_TAG].join("\n#{@comment_prefix} ")
55
+ # Handle C block comments as a special case for legacy project support
56
+ # Could generalise the comment definition, but feels like unnecessary complexity...
57
+ if @src =~ /\.(c|cpp|h|hpp)$/ && !@comment_prefix =~ /#{Regexp.escape('//')}/
58
+ placeholder = "/**\n${placeholder}\n*/"
59
+ end
60
+ placeholder + "\n"
61
+ end
62
+
63
+ def write(output_file_name)
64
+
65
+ ref_info = self.class.new(@src, regex: @regex)
66
+ raise 'No revision identifiers incremented' if ref_info.to_s == self.to_s
67
+
68
+ entry = get_changelog_entry
69
+
70
+ text = File.read(@src)
71
+ text.gsub!(@regex) { |match| "#{$~[:prefix]}#{@major}#{$~[:sep1]}#{@minor}#{$~[:sep2]}#{@patch}#{$~[:postfix]}" }
72
+
73
+ #Insert start/end tags if not present
74
+ text += new_changelog unless text.match(CHANGELOG_START)
75
+
76
+ text.gsub!(CHANGELOG_START) { |match| [match, format_changelog_entry(entry)].join("\n") }
77
+
78
+ File.open(output_file_name, 'w') { |f| f.write(text) }
79
+ end
80
+
81
+ def write!
82
+ write(@src)
83
+ end
84
+
85
+ def to_s
86
+ "#{@major}.#{@minor}.#{@patch}"
87
+ end
88
+
89
+ def strip_comment_prefix(line)
90
+ line.gsub(/^\s*#{Regexp.escape(@comment_prefix)}\s?/,'')
91
+ end
92
+
93
+ def changelog
94
+ in_changelog = false
95
+ File.open(@src).each_line do |line|
96
+ if in_changelog
97
+ break if line =~ CHANGELOG_END
98
+ yield strip_comment_prefix(line.chomp)
99
+ else
100
+ in_changelog = line =~ CHANGELOG_START
101
+ end
102
+ end
103
+ end
104
+
105
+ def last_changelog_entry
106
+ in_entry = false
107
+ lines = []
108
+ changelog do |line|
109
+ if line.length > 0
110
+ in_entry = true
111
+ lines << line
112
+ else
113
+ break if in_entry
114
+ end
115
+ end
116
+ lines
117
+ end
118
+
119
+ # Prefixes the entry with an empty line, then prefixes each line with comment chars
120
+ # and converts the line entries to a single string
121
+ def format_changelog_entry(entry_lines)
122
+ entry_lines.unshift('').map { |line| "#{@comment_prefix} #{line}"}.join("\n")
123
+ end
124
+
125
+ def get_changelog_entry
126
+ entry_lines = []
127
+ entry_lines << "Version #{self} (#{Time.now.strftime("%d %b %Y")})"
128
+ puts('Changelog entry (one item per line / empty line to end)')
129
+ puts('N.B. Git commit entry will use revision ID and first line of entry')
130
+ while line = $stdin.readline.strip
131
+ break if line.length == 0
132
+ entry_lines << "- #{line}"
133
+ end
134
+ entry_lines
135
+ end
136
+
137
+ end
@@ -0,0 +1,152 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+ require 'zip'
4
+ require 'git'
5
+
6
+ module Revision
7
+
8
+ class Releasable
9
+
10
+ BUILD_DIR_BASE_NAME = "dist"
11
+ BUILD_CONFIGURATION_DEFAULT = "default"
12
+ BUILD_TARGET_DEFAULT = "production"
13
+ # RELATIVE_PATH_TO_BOOTLOADER = File.join("..","bootloader")
14
+ RELATIVE_PATH_TO_BOOTLOADER = "bootloader"
15
+
16
+ CONFIG_FILE_NAME = 'releasables.yaml'.freeze
17
+
18
+ REVISION_PLACEHOLDER = /<REV>|<VER>/
19
+
20
+ attr_reader :root, :id, :revision, :build_steps, :artefacts
21
+
22
+ # Load a file in yaml format containing one or more releasable definitions
23
+ # @param root [String] An optional root directory argument
24
+ # @return [Hash] Contents of the yaml file
25
+ def self.load_definitions(root: nil)
26
+ root ||= Dir.getwd
27
+ config_file = File.join(root, CONFIG_FILE_NAME)
28
+ raise Errors::NoDefinition.new(root) unless File.exist?(config_file)
29
+ puts "Loading releasable definitions from #{config_file} ..."
30
+ YAML.load_file(config_file)
31
+ end
32
+
33
+ # Instantiate the Releasables defined in releasables.yaml
34
+ def self.from_folder(root = Dir.getwd)
35
+ config = load_definitions(:root=>root)
36
+
37
+ releasables = {}
38
+ config[:releasables].each do |config_entry|
39
+ if config_entry[:folder]
40
+ #Load entries from a nested releasable definition file
41
+ releasables = releasables.merge(from_folder(File.join(root, config_entry[:folder])))
42
+ else
43
+ r = new(root: root, config: config_entry)
44
+ releasables[r.id] = r
45
+ end
46
+ end
47
+ releasables
48
+ end
49
+
50
+ def initialize(root: nil, config: {})
51
+
52
+ root ||= Dir.getwd
53
+ @root = Pathname.new(root).realpath
54
+ @id = config[:id] || File.basename(@root)
55
+ @revision = Info.new(File.join(@root,config[:revision][:src]), regex: config[:revision][:regex], comment_prefix: config[:revision][:comment_prefix])
56
+ @build_steps = config[:build_steps]
57
+ @artefacts = config[:artefacts]
58
+ @artefacts.each { |a| a[:dest] ||= a[:src] }
59
+ end
60
+
61
+ def to_s
62
+ <<~EOT
63
+ #{@id} v#{@revision} @ #{@root}
64
+
65
+ Build pipeline:
66
+ - #{@build_steps.join("\n - ")}
67
+
68
+ Build artefacts:
69
+ #{artefacts.map{ |a| "- #{a[:src]}\n => #{a[:dest]}" }.join("\n")}
70
+ EOT
71
+ end
72
+
73
+ def build
74
+ puts "Executing #{@build_steps.length} build steps..."
75
+ Dir.chdir(@root) do
76
+ @build_steps.each_with_index do |step, index|
77
+ puts "... (#{index+1}/#{@build_steps.length}) #{step}"
78
+ system(step)
79
+ puts "WARNING: build step #{index}: #{step} exit status #{$?.exitstatus}" unless $?.exitstatus.zero?
80
+ end
81
+ end
82
+ end
83
+
84
+ def tag
85
+ Dir.chdir(@root) do
86
+ tag_id = "v#{revision}"
87
+ changelog_entry = @revision.last_changelog_entry
88
+ #Insert a blank line between the revision header and release notes, as per git commit best practice
89
+ commit_lines = ["#{tag_id} #{changelog_entry[1]}", '']
90
+ if changelog_entry.length > 2
91
+ commit_lines << "Also..."
92
+ commit_lines += changelog_entry[2..-1]
93
+ end
94
+ commit_message = commit_lines.join("\n")
95
+ g = Git.init
96
+ puts "Committing..."
97
+ puts commit_message
98
+ g.commit_all(commit_message)
99
+ puts "Tagging as #{tag_id}"
100
+ g.add_tag(tag_id)
101
+ end
102
+ end
103
+
104
+ def push
105
+ pushed = false
106
+ Dir.chdir(@root) do
107
+ g = Git.init
108
+ begin
109
+ g.push('origin', g.current_branch, tags: true)
110
+ pushed = true
111
+ rescue GitExecuteError => e
112
+ puts "ERROR :: Cannot push to origin :: #{e}"
113
+ end
114
+ end
115
+ pushed
116
+ end
117
+
118
+ def archive_name
119
+ "#{@id}_v#{@revision}.zip"
120
+ end
121
+
122
+ def changelog_name
123
+ "#{@id}_revision_history_v#{@revision}.txt"
124
+ end
125
+
126
+ def archive
127
+ puts "Packaging #{@artefacts.length} build artefacts as #{archive_name}..."
128
+ if File.exist?(archive_name)
129
+ puts "... deleting existing archive"
130
+ File.delete(archive_name)
131
+ end
132
+ Zip::File.open(archive_name, Zip::File::CREATE) do |zipfile|
133
+ @artefacts.each_with_index do |a, index|
134
+ src = a[:src].gsub(REVISION_PLACEHOLDER, @revision.to_s)
135
+ dest = a[:dest].gsub(REVISION_PLACEHOLDER, @revision.to_s)
136
+ puts "... (#{index+1}/#{@artefacts.length}) #{src} => #{dest}"
137
+ zipfile.add(dest, File.join(@root, src))
138
+ end
139
+ puts "... embedding revision history as #{changelog_name} "
140
+ zipfile.get_output_stream(changelog_name) { |os| output_changelog(os)}
141
+ end
142
+ end
143
+
144
+ def output_changelog(output_stream)
145
+ output_stream.puts "Revision history for #{@id} version #{@revision}"
146
+ output_stream.puts ""
147
+ @revision.changelog {|line| output_stream.puts(line)}
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,36 @@
1
+ class String
2
+ # ruby mutation methods have the expectation to return self if a mutation occurred, nil otherwise. (see http://www.ruby-doc.org/core-1.9.3/String.html#method-i-gsub-21)
3
+ def to_underscore!
4
+ gsub!(/::/, '/')
5
+ gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
6
+ gsub!(/([a-z\d])([A-Z])/,'\1_\2')
7
+ tr!("-", "_")
8
+ end
9
+
10
+ ##
11
+ # Converts _SnakeCase_ to _snake_case_
12
+ def to_snake_case!
13
+ to_underscore!
14
+ downcase!
15
+ end
16
+
17
+ ##
18
+ # Converts _ScreamingSnakeCase to _SCREAMING_SNAKE_CASE_
19
+ def to_screaming_snake_case!
20
+ to_underscore!
21
+ upcase!
22
+ end
23
+
24
+ def to_underscore
25
+ dup.tap { |s| s.to_underscore! }
26
+ end
27
+
28
+ def to_snake_case
29
+ dup.tap { |s| s.to_snake_case! }
30
+ end
31
+
32
+ def to_screaming_snake_case
33
+ dup.tap { |s| s.to_screaming_snake_case! }
34
+ end
35
+
36
+ end
@@ -0,0 +1,45 @@
1
+ # Defines the revision ID for the revision gem
2
+ module Revision
3
+ VERSION = "1.1.4"
4
+ end
5
+
6
+ # <BEGIN CHANGELOG>
7
+ #
8
+ # Version 1.1.4 (14 Dec 2017)
9
+ # - Minor message body reformatting
10
+ #
11
+ # Version 1.1.3 (14 Dec 2017)
12
+ # - Eliminated duplication of version ID in commit message body
13
+ #
14
+ # Version 1.1.2 (14 Dec 2017)
15
+ # - Removed redundant ':: ' from commit message headline
16
+ #
17
+ # Version 1.1.1 (14 Dec 2017)
18
+ # - Added git connection failure handling
19
+ # - Revision commit message now includes first line of changelog entry
20
+ # - Updated configuration syntax for consistency (:revision: :file: -> :revision: :src:)
21
+ #
22
+ # Version 1.1.0 (13 Dec 2017)
23
+ # - Updated to optionally push tags to the repo
24
+ #
25
+ # Version 1.0.1 (13 Dec 2017)
26
+ # - Corrected revision placeholder handling when archiving build artefacts
27
+ # - Added proper high-level usage documentation
28
+ #
29
+ # Version 1.0.0 (12 Dec 2017)
30
+ # - First fully functional release with new config file
31
+ #
32
+ # Version 0.1.4 (12 Dec 2017)
33
+ # - boo
34
+ # - hoo
35
+ # - hoo
36
+ #
37
+ # Version 0.1.3 (12 Dec 2017)
38
+ # - boo
39
+ #
40
+ # Version 0.1.2 (12 Dec 2017)
41
+ # - wahoo!
42
+ #
43
+ # Version 0.1.1 (12 Dec 2017)
44
+ # - Wahey!
45
+ # <END CHANGELOG>
data/lib/revision.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "revision/version"
2
+
3
+ module Revision
4
+ require 'revision/cli'
5
+ require 'revision/errors'
6
+ end
data/releasables.yaml ADDED
@@ -0,0 +1,10 @@
1
+ :releasables:
2
+ - :id: revision
3
+ :revision:
4
+ :src: lib/revision/version.rb
5
+ :regex: (?<prefix>VERSION = ")(?<major>\d+)(?<sep1>\.)(?<minor>\d+)(?<sep2>\.)(?<patch>\d+)(?<postfix>")
6
+ :comment_prefix: "#"
7
+ :build_steps:
8
+ - bundle exec rake install
9
+ :artefacts:
10
+ - :src: pkg/revision-<VER>.gem
data/revision.gemspec ADDED
@@ -0,0 +1,40 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "revision/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "revision"
7
+ spec.version = Revision::VERSION
8
+ spec.authors = ["Cormac Cannon"]
9
+ spec.email = ["cormac.cannon@neuromoddevices.com"]
10
+
11
+ spec.summary = %q{Language-agnostic revision management tool}
12
+ spec.description = %q{Updates project revision identifiers in software source files and associated change log. Can also build and package project archives as a zip and optionally commit, tag and push to a Git repo.}
13
+ # spec.homepage = "TBC"
14
+
15
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
16
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ # if spec.respond_to?(:metadata)
18
+ # spec.metadata["allowed_push_host"] = "http://gems.nmd.ie"
19
+ # else
20
+ # raise "RubyGems 2.0 or newer is required to protect against " \
21
+ # "public gem pushes."
22
+ # end
23
+
24
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
25
+ f.match(%r{^(test|spec|features)/})
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{^#{spec.bindir}/}) { |f| File.basename(f) }
29
+
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_runtime_dependency 'thor', '~> 0.19.1'
33
+ spec.add_runtime_dependency 'rubyzip'
34
+ spec.add_runtime_dependency 'git'
35
+
36
+ spec.add_development_dependency "bundler", "~> 1.16"
37
+ spec.add_development_dependency "rake", "~> 10.0"
38
+ spec.add_development_dependency "rspec", "~> 3.0"
39
+ spec.add_development_dependency "pry"
40
+ end
metadata ADDED
@@ -0,0 +1,162 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: revision
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Cormac Cannon
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.19.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.19.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubyzip
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: git
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.16'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.16'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Updates project revision identifiers in software source files and associated
112
+ change log. Can also build and package project archives as a zip and optionally
113
+ commit, tag and push to a Git repo.
114
+ email:
115
+ - cormac.cannon@neuromoddevices.com
116
+ executables:
117
+ - revision
118
+ extensions: []
119
+ extra_rdoc_files: []
120
+ files:
121
+ - ".gitignore"
122
+ - ".rspec"
123
+ - ".travis.yml"
124
+ - Gemfile
125
+ - README.org
126
+ - Rakefile
127
+ - bin/console
128
+ - bin/setup
129
+ - exe/revision
130
+ - lib/revision.rb
131
+ - lib/revision/cli.rb
132
+ - lib/revision/errors.rb
133
+ - lib/revision/info.rb
134
+ - lib/revision/releasable.rb
135
+ - lib/revision/string_case.rb
136
+ - lib/revision/version.rb
137
+ - releasables.yaml
138
+ - revision.gemspec
139
+ homepage:
140
+ licenses: []
141
+ metadata: {}
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: '0'
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubyforge_project:
158
+ rubygems_version: 2.6.13
159
+ signing_key:
160
+ specification_version: 4
161
+ summary: Language-agnostic revision management tool
162
+ test_files: []