changi 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0d7fbfb1689d7effa8bed7d6ca6ca66073b6f31f
4
+ data.tar.gz: c436fe107d78b2479e66f578e0a92ba783eb3344
5
+ SHA512:
6
+ metadata.gz: c4e10adc77fde7170e214922b6a9845f17a0af8d4b039bb53944076e424287e126c4236ca66480da5ebf0b46f9f5534a4b1135f86929808c36cbd71ec886da23
7
+ data.tar.gz: 0e8c75ba30d82a81ff03acfd492db5f8f3cd4abb75628ce3f85a15450722b748f0ab7c9d58201d09ed26d75b5fe626b4889ab49bcd859e9e8e4a17c6b6274ebf
@@ -0,0 +1,29 @@
1
+ module Changi
2
+ class Changelog
3
+ def initialize config
4
+ @config = config
5
+ end
6
+
7
+ def new_entry
8
+ entry_set.new_entry
9
+ end
10
+
11
+ def render demo: false
12
+ release = demo ? Release.demo : Release.build
13
+ renderer = Renderer.new @config, entry_set, release
14
+ renderer.render
15
+ end
16
+
17
+ def update
18
+ updater = @config.updater.new
19
+ updater.update @config.changelog_path, render
20
+ entry_set.destroy_all
21
+ end
22
+
23
+ private
24
+
25
+ def entry_set
26
+ @entry_set ||= EntrySet.new @config
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module Changi
2
+ class Configuration
3
+ attr_accessor :changelog_path
4
+ attr_accessor :entries_path
5
+ attr_accessor :default_categories
6
+ attr_accessor :updater
7
+ attr_accessor :changelog_template
8
+
9
+ def self.default
10
+ new.tap do |config|
11
+ config.changelog_path = File.join Dir.pwd, 'changelog.md'
12
+ config.entries_path = File.join Dir.pwd, 'changelog'
13
+ config.default_categories = []
14
+ config.updater = Updater::PrependUpdater
15
+ config.changelog_template = <<-eod
16
+ # <%= release.version %>, <%= Time.now.strftime('%Y-%m-%d') %><%= release.notes and ", \#{release.notes}" %>
17
+ <% entry_set.entries_by_category.each do |category, entries| %>
18
+ ## <%= category %>
19
+
20
+ <%=
21
+ entries.map do |e|
22
+ if e.text.is_a?(Array)
23
+ e.text.map.with_index { |l, i| i == 0 ? "* \#{l}" : " \#{l}" }.join("\n")
24
+ else
25
+ "* \#{e.text.gsub("\n", ' ')}"
26
+ end
27
+ end.join("\n")
28
+ %>
29
+ <% end %>
30
+ eod
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,48 @@
1
+ require 'yaml'
2
+
3
+ module Changi
4
+ class Entry
5
+ include FlexibleAttributes
6
+
7
+ class << self
8
+ def build entry_set, yaml_path
9
+ new(entry_set, yaml_path).tap &:read_in_attributes
10
+ end
11
+
12
+ def load entry_set, yaml_path
13
+ new(entry_set, yaml_path).tap &:read_in_yaml
14
+ end
15
+ end
16
+
17
+ define_attribute :category, reader: Reader::CategoryReader
18
+ define_attribute :text, reader: Reader::MultilineReader, opts: { required: true }
19
+
20
+ attr_reader :entry_set
21
+
22
+ def initialize entry_set, path
23
+ @entry_set = entry_set
24
+ @path = path
25
+ end
26
+
27
+ def read_in_yaml
28
+ data = YAML.load_file @path
29
+ self.class.attribute_names.each { |name| send "#{name}=".to_sym, data[name] }
30
+ end
31
+
32
+ def save
33
+ File.open(@path, 'w') { |fd| fd.puts to_yaml }
34
+ end
35
+
36
+ def destroy
37
+ unless system "git rm '#{@path}' >/dev/null 2>&1"
38
+ FileUtils.rm_f @path
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def to_yaml
45
+ self.class.attribute_names.map { |name| [name, send(name.to_sym)] }.to_h.to_yaml
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,50 @@
1
+ require 'fileutils'
2
+
3
+ module Changi
4
+ class EntrySet
5
+ attr_reader :config
6
+
7
+ def initialize config
8
+ @config = config
9
+ FileUtils.mkdir_p @config.entries_path
10
+ end
11
+
12
+ def new_entry
13
+ entries << Entry.build(self, make_entry_path).tap(&:save)
14
+ end
15
+
16
+ def entries
17
+ @entries ||= yamls.map { |yml| Entry.load self, yml }
18
+ end
19
+
20
+ def entries_by_category
21
+ entries.group_by &:category
22
+ end
23
+
24
+ def previous_categories
25
+ entries.map &:category
26
+ end
27
+
28
+ def destroy_all
29
+ entries.each &:destroy
30
+ end
31
+
32
+ private
33
+
34
+ def yamls
35
+ Dir["#{@config.entries_path}/*yml"]
36
+ end
37
+
38
+ def make_entry_path
39
+ File.join @config.entries_path, make_entry_name
40
+ end
41
+
42
+ def make_entry_name
43
+ if git_branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`.strip
44
+ "#{Time.now.strftime('%y%m%d%H%M%S')}_from_#{git_branch}.yml"
45
+ else
46
+ "#{Time.now.strftime('%y%m%d%H%M%S')}.yml"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,33 @@
1
+ module Changi
2
+ module FlexibleAttributes
3
+ module ClassMethods
4
+ def attributes
5
+ @attributes ||= []
6
+ end
7
+
8
+ def attribute_names
9
+ attributes.map { |x| x[:name] }
10
+ end
11
+
12
+ def define_attribute name, reader: Reader::StringReader, opts: {}, &block
13
+ attr_accessor name.to_sym
14
+
15
+ attributes << {
16
+ name: name,
17
+ reader: (block_given? ? block : ->(*args) { reader.new.read *args }),
18
+ opts: opts
19
+ }
20
+ end
21
+ end
22
+
23
+ def self.included base
24
+ base.extend ClassMethods
25
+ end
26
+
27
+ def read_in_attributes
28
+ self.class.attributes.each do |data|
29
+ send "#{data[:name]}=".to_sym, data[:reader].call(data, self)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,24 @@
1
+ module Changi
2
+ module Reader
3
+ class CategoryReader < StringReader
4
+ def read attribute, owner
5
+ fail 'CategoryReader can only be used for Entry attributes.' unless owner.is_a? Entry
6
+
7
+ cli.say "#{attribute[:name].capitalize}:\n"
8
+ cli.choose do |menu|
9
+ menu.choices *categories(owner.entry_set)
10
+ menu.choice 'Other (create new)' do
11
+ cli.ask "Please insert #{attribute[:name]}:"
12
+ end
13
+ menu.choice('Abort') { exit }
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def categories entry_set
20
+ (entry_set.previous_categories + entry_set.config.default_categories).uniq
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,63 @@
1
+ require 'tempfile'
2
+
3
+ module Changi
4
+ module Reader
5
+ class MultilineReader
6
+ def read attribute, owner
7
+ tmpfile = Tempfile.new 'changi'
8
+
9
+ intro tmpfile, attribute
10
+
11
+ unless system "#{editor} '#{tmpfile.path.strip}'"
12
+ abort 'editor returned with non-zero exit status, abort'
13
+ end
14
+
15
+ read_and_strip(tmpfile).tap do |data|
16
+ if attribute[:opts][:required] && data.empty?
17
+ abort "required #{attribute[:name]} attribute empty, abort"
18
+ end
19
+ end
20
+ ensure
21
+ tmpfile.close
22
+ tmpfile.unlink
23
+ end
24
+
25
+ private
26
+
27
+ def intro tmpfile, attribute
28
+ tmpfile.puts
29
+ tmpfile.puts
30
+ tmpfile.puts "# Please enter #{attribute[:name]} above."
31
+ tmpfile.puts "# Lines starting with # will be ignored."
32
+ tmpfile.puts "# Empty tmpfile will abort the process." if attribute[:opts][:required]
33
+ tmpfile.sync
34
+ tmpfile.close
35
+ end
36
+
37
+ def read_and_strip tmpfile
38
+ tmpfile.open
39
+ tmpfile.read.strip.split("\n").reject { |x| x =~ /^\s*#/ }
40
+ .drop_while(&:empty?).reverse.drop_while(&:empty?).reverse
41
+ end
42
+
43
+ def editor
44
+ editor_tests.lazy.map(&:call).find { |e| !(e.nil? || e.empty?) }.tap do |e|
45
+ fail 'could not detect editor' unless e
46
+ end
47
+ end
48
+
49
+ def editor_tests
50
+ [
51
+ -> { ENV['EDITOR'] },
52
+ -> { `git config core.editor`.strip },
53
+ -> { editor_exists?('nano') && 'nano' },
54
+ -> { editor_exists?('vim') && 'vim' }
55
+ ]
56
+ end
57
+
58
+ def editor_exists? app
59
+ system "command -v #{app} >/dev/null 2>&1"
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ require 'highline'
2
+
3
+ module Changi
4
+ module Reader
5
+ class StringReader
6
+ def read attribute, owner
7
+ cli.ask("#{attribute[:name]}: ").tap do |x|
8
+ if attribute[:opts][:required] && [nil, ''].include?(x)
9
+ abort "required #{attribute[:name]} attribute empty, abort"
10
+ end
11
+ end
12
+ end
13
+
14
+ protected
15
+
16
+ def cli
17
+ @cli ||= HighLine.new
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ module Changi
2
+ class Release
3
+ include FlexibleAttributes
4
+
5
+ class << self
6
+ def build
7
+ new.tap &:read_in_attributes
8
+ end
9
+
10
+ def demo
11
+ new.tap do |inst|
12
+ attribute_names.each { |name| inst.send "#{name}=".to_sym, "<#{name}>" }
13
+ end
14
+ end
15
+ end
16
+
17
+ define_attribute :version
18
+ define_attribute :notes
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ require 'erb'
2
+
3
+ module Changi
4
+ class Renderer
5
+ attr_reader :entry_set
6
+ attr_reader :release
7
+
8
+ def initialize config, entry_set, release
9
+ @config = config
10
+ @entry_set = entry_set
11
+ @release = release
12
+ end
13
+
14
+ def render
15
+ ERB.new(@config.changelog_template).result binding
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ require 'changi'
2
+
3
+ namespace :changi do
4
+ desc 'Create a new changelog entry'
5
+ task :new do
6
+ Changi.changelog.new_entry
7
+ end
8
+
9
+ desc 'Show changelog for next release'
10
+ task :diff do
11
+ puts Changi.changelog.render demo: true
12
+ end
13
+
14
+ desc 'Update project changelog for release'
15
+ task :update do
16
+ Changi.changelog.update
17
+ puts 'Changelog updated, and changelog directory cleared.'
18
+ puts 'Check if there were manual changes to changelog.md and merge them before committing.'
19
+ end
20
+ end
@@ -0,0 +1,15 @@
1
+ module Changi
2
+ module Updater
3
+ class PrependUpdater
4
+ def update changelog_path, release_data
5
+ previous = File.read changelog_path if File.exists? changelog_path
6
+
7
+ File.open changelog_path, 'w' do |fd|
8
+ fd.puts release_data
9
+ fd.puts
10
+ fd.puts previous if previous
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/changi.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'changi/reader/string_reader'
2
+ require 'changi/reader/multiline_reader'
3
+ require 'changi/reader/category_reader'
4
+ require 'changi/updater/prepend_updater'
5
+ require 'changi/flexible_attributes'
6
+ require 'changi/changelog'
7
+ require 'changi/configuration'
8
+ require 'changi/entry'
9
+ require 'changi/entry_set'
10
+ require 'changi/release'
11
+ require 'changi/renderer'
12
+
13
+ module Changi
14
+ class << self
15
+ def configuration
16
+ @configuration ||= Configuration.default
17
+ end
18
+
19
+ def configure
20
+ yield configuration
21
+ end
22
+
23
+ def changelog
24
+ @changelog ||= Changelog.new configuration
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: changi
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Mobisol GmbH
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-05-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '10.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '10.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: highline
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ description: Manages a set of changelog entries for you and combines them to a changelog
42
+ file when you release.
43
+ email: dev@plugintheworld.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - lib/changi.rb
49
+ - lib/changi/changelog.rb
50
+ - lib/changi/configuration.rb
51
+ - lib/changi/entry.rb
52
+ - lib/changi/entry_set.rb
53
+ - lib/changi/flexible_attributes.rb
54
+ - lib/changi/reader/category_reader.rb
55
+ - lib/changi/reader/multiline_reader.rb
56
+ - lib/changi/reader/string_reader.rb
57
+ - lib/changi/release.rb
58
+ - lib/changi/renderer.rb
59
+ - lib/changi/tasks.rb
60
+ - lib/changi/updater/prepend_updater.rb
61
+ homepage: https://github.org/plugintheworld/changi
62
+ licenses:
63
+ - MIT
64
+ metadata: {}
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ version: '0'
79
+ requirements: []
80
+ rubyforge_project:
81
+ rubygems_version: 2.4.5.1
82
+ signing_key:
83
+ specification_version: 4
84
+ summary: Tiny library of changelog management rake tasks.
85
+ test_files: []
86
+ has_rdoc: