rubycut-vclog 1.9.4

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.
Files changed (76) hide show
  1. data/.yardopts +7 -0
  2. data/History.md +249 -0
  3. data/License.txt +23 -0
  4. data/README.md +133 -0
  5. data/bin/vclog +6 -0
  6. data/bin/vclog-autotag +6 -0
  7. data/bin/vclog-bump +6 -0
  8. data/bin/vclog-formats +6 -0
  9. data/bin/vclog-news +6 -0
  10. data/bin/vclog-version +6 -0
  11. data/lib/vclog.rb +6 -0
  12. data/lib/vclog.yml +68 -0
  13. data/lib/vclog/adapters.rb +12 -0
  14. data/lib/vclog/adapters/abstract.rb +131 -0
  15. data/lib/vclog/adapters/darcs.rb +93 -0
  16. data/lib/vclog/adapters/git.rb +190 -0
  17. data/lib/vclog/adapters/hg.rb +129 -0
  18. data/lib/vclog/adapters/svn.rb +155 -0
  19. data/lib/vclog/change.rb +207 -0
  20. data/lib/vclog/change_point.rb +77 -0
  21. data/lib/vclog/changelog.rb +233 -0
  22. data/lib/vclog/cli.rb +8 -0
  23. data/lib/vclog/cli/abstract.rb +92 -0
  24. data/lib/vclog/cli/autotag.rb +36 -0
  25. data/lib/vclog/cli/bump.rb +29 -0
  26. data/lib/vclog/cli/formats.rb +28 -0
  27. data/lib/vclog/cli/log.rb +86 -0
  28. data/lib/vclog/cli/news.rb +29 -0
  29. data/lib/vclog/cli/version.rb +30 -0
  30. data/lib/vclog/config.rb +143 -0
  31. data/lib/vclog/core_ext.rb +11 -0
  32. data/lib/vclog/heuristics.rb +192 -0
  33. data/lib/vclog/heuristics/rule.rb +73 -0
  34. data/lib/vclog/heuristics/type.rb +29 -0
  35. data/lib/vclog/history_file.rb +69 -0
  36. data/lib/vclog/metadata.rb +16 -0
  37. data/lib/vclog/rc.rb +9 -0
  38. data/lib/vclog/release.rb +67 -0
  39. data/lib/vclog/repo.rb +298 -0
  40. data/lib/vclog/report.rb +200 -0
  41. data/lib/vclog/tag.rb +151 -0
  42. data/lib/vclog/templates/changelog.ansi.rb +35 -0
  43. data/lib/vclog/templates/changelog.atom.erb +52 -0
  44. data/lib/vclog/templates/changelog.gnu.rb +24 -0
  45. data/lib/vclog/templates/changelog.html.erb +49 -0
  46. data/lib/vclog/templates/changelog.json.rb +1 -0
  47. data/lib/vclog/templates/changelog.markdown.rb +30 -0
  48. data/lib/vclog/templates/changelog.rdoc.rb +30 -0
  49. data/lib/vclog/templates/changelog.rss.erb +54 -0
  50. data/lib/vclog/templates/changelog.xml.erb +28 -0
  51. data/lib/vclog/templates/changelog.xsl +34 -0
  52. data/lib/vclog/templates/changelog.yaml.rb +1 -0
  53. data/lib/vclog/templates/history.ansi.rb +57 -0
  54. data/lib/vclog/templates/history.atom.erb +84 -0
  55. data/lib/vclog/templates/history.gnu.rb +39 -0
  56. data/lib/vclog/templates/history.html.erb +60 -0
  57. data/lib/vclog/templates/history.json.rb +1 -0
  58. data/lib/vclog/templates/history.markdown.rb +38 -0
  59. data/lib/vclog/templates/history.rdoc.rb +36 -0
  60. data/lib/vclog/templates/history.rss.erb +84 -0
  61. data/lib/vclog/templates/history.xml.erb +43 -0
  62. data/lib/vclog/templates/history.yaml.rb +1 -0
  63. data/man/man1/index.txt +9 -0
  64. data/man/man1/vclog-autotag.1.ronn +29 -0
  65. data/man/man1/vclog-bump.1.ronn +21 -0
  66. data/man/man1/vclog-news.1.ronn +25 -0
  67. data/man/man1/vclog-version.1.ronn +14 -0
  68. data/man/man1/vclog.1.ronn +49 -0
  69. data/spec/feature_git_changes.rb +58 -0
  70. data/spec/feature_git_history.rb +58 -0
  71. data/spec/feature_hg_changes.rb +58 -0
  72. data/spec/feature_hg_history.rb +58 -0
  73. data/spec/featurettes/repo_creation.rb +64 -0
  74. data/spec/featurettes/shellout.rb +16 -0
  75. data/test/case_metadata.rb +10 -0
  76. metadata +265 -0
@@ -0,0 +1,73 @@
1
+ module VCLog
2
+
3
+ class Heuristics
4
+
5
+ # Defines a categorization rule for commits.
6
+ #
7
+ class Rule
8
+
9
+ # Initialize a new log rule.
10
+ #
11
+ # @param [Regexp] pattern
12
+ # An optional regular expression to match agains the commit message.
13
+ # If the pattern does not match the commit message than the rule
14
+ # does not apply.
15
+ #
16
+ # @yieldparam [Change]
17
+ # An encapsulation of the commit.
18
+ #
19
+ # @yieldreturn [Boolean]
20
+ # If the return value is +nil+ or +false+, the rule does not apply.
21
+ # If a rule does not apply then be sure not to alter the commit!
22
+ #
23
+ def initialize(pattern=nil, &block)
24
+ @pattern = pattern
25
+ @block = block
26
+ end
27
+
28
+ # Message pattern to match for the rule to apply.
29
+ attr :pattern
30
+
31
+ # Process the rule.
32
+ #
33
+ # @since 1.9.0
34
+ # If using a message pattern and the block takes two arguments
35
+ # then the first will be the commit object, not the message as
36
+ # was the case in older versions.
37
+ #
38
+ def call(commit)
39
+ if pattern
40
+ call_pattern(commit)
41
+ else
42
+ call_commit(commit)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ #
49
+ def call_pattern(commit)
50
+ if matchdata = @pattern.match(commit.message)
51
+ #case @block.arity
52
+ #when 0
53
+ # @block.call
54
+ #when 1
55
+ # @block.call(matchdata)
56
+ #else
57
+ @block.call(commit, matchdata)
58
+ #end
59
+ else
60
+ nil
61
+ end
62
+ end
63
+
64
+ #
65
+ def call_commit(commit)
66
+ @block.call(commit)
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,29 @@
1
+ module VCLog
2
+
3
+ class Heuristics
4
+
5
+ #
6
+ #
7
+ class Type
8
+ #
9
+ def initialize(type, level, label)
10
+ @type = type.to_sym
11
+ @level = level.to_i
12
+ @label = label.to_s
13
+ end
14
+
15
+ attr :type
16
+
17
+ attr :level
18
+
19
+ attr :label
20
+
21
+ #
22
+ def to_a
23
+ [type, level, label]
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,69 @@
1
+ module VCLog
2
+
3
+ # The HistoryFile class will parse a history into an array
4
+ # of release tags. Of course to do this, it assumes a specific
5
+ # file format.
6
+ #
7
+ class HistoryFile
8
+
9
+ FILE = '{HISTORY,HISTORY.*}'
10
+
11
+ LINE = /^[=#]/
12
+ VERS = /(\d+[._])+\d+/
13
+ DATE = /(\d+[-])+\d+/
14
+
15
+ # Alias for `File::FNM_CASEFOLD`.
16
+ CASEFOLD = File::FNM_CASEFOLD
17
+
18
+ # Release tags.
19
+ attr :tags
20
+
21
+ # Setup new HistoryFile instance.
22
+ def initialize(source=nil)
23
+ if File.file?(source)
24
+ @file = source
25
+ @root = File.dirname(source)
26
+ elsif File.directory?(source)
27
+ @file = Dir.glob(File.join(source,FILE), CASEFOLD).first
28
+ @root = source
29
+ else
30
+ @file = Dir.glob(FILE).first
31
+ @root = Dir.pwd
32
+ end
33
+ raise "no history file" unless @file
34
+
35
+ @tags = extract_tags
36
+ end
37
+
38
+ # Parse history file.
39
+ def extract_tags
40
+ tags = []
41
+ desc = ''
42
+ text = File.read(@file)
43
+ text.lines.each do |line|
44
+ if LINE =~ line
45
+ vers = (VERS.match(line) || [])[0]
46
+ date = (DATE.match(line) || [])[0]
47
+ next unless vers
48
+ tags << [vers, date, desc = '']
49
+ else
50
+ desc << line
51
+ end
52
+ end
53
+
54
+ tags.map do |vers, date, desc|
55
+ index = desc.index(/^Changes:/) || desc.index(/^\*/) || desc.size
56
+ desc = desc[0...index].strip.fold
57
+ #[vers, date, desc]
58
+ Tag.new(:name=>vers, :date=>date, :msg=>desc)
59
+ end
60
+ end
61
+
62
+ #
63
+ def news
64
+ tags.first.message
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,16 @@
1
+ module VCLog
2
+
3
+ def self.metadata
4
+ @metadata ||= (
5
+ require 'yaml'
6
+ YAML.load_file(File.dirname(__FILE__) + '/../vclog.yml')
7
+ )
8
+ end
9
+
10
+ def self.const_missing(name)
11
+ key = name.to_s.downcase
12
+ metadata.key?(key) ? metadata[key] : super(name)
13
+ end
14
+
15
+ end
16
+
data/lib/vclog/rc.rb ADDED
@@ -0,0 +1,9 @@
1
+ begin
2
+ require 'rc/api'
3
+
4
+ configure 'vclog' do |config|
5
+ VCLog.configure(&config) if config.profile?
6
+ end
7
+ rescue LoadError
8
+ end
9
+
@@ -0,0 +1,67 @@
1
+ module VCLog
2
+
3
+ # A Release encapsulate a collection of {Change} objects
4
+ # associated with a {Tag}.
5
+ #
6
+ class Release
7
+
8
+ # Tag object this release represents.
9
+ attr :tag
10
+
11
+ # Array of Change objects.
12
+ attr :changes
13
+
14
+ #
15
+ # New Release object.
16
+ #
17
+ # @param [Tag] tag
18
+ # A Tag object.
19
+ #
20
+ # @param [Array<Change>] changes
21
+ # An array of Change objects.
22
+ #
23
+ def initialize(tag, changes)
24
+ @tag = tag
25
+ @changes = changes
26
+ end
27
+
28
+ #
29
+ # Group +changes+ by type and sort by level.
30
+ #
31
+ # @return [Array<Array>]
32
+ # Returns an associative array of [type, changes].
33
+ #
34
+ def groups
35
+ @groups ||= (
36
+ changes.group_by{ |e| e.label }.sort{ |a,b| b[1][0].level <=> a[1][0].level }
37
+ )
38
+ end
39
+
40
+ #
41
+ # Compare release by tag.
42
+ #
43
+ # @param [Release] other
44
+ # Another release instance.
45
+ #
46
+ def <=>(other)
47
+ @tag <=> other.tag
48
+ end
49
+
50
+ #
51
+ # Convert Release to Hash.
52
+ #
53
+ # @todo Should +version+ be +name+?
54
+ #
55
+ def to_h
56
+ { 'version' => tag.name,
57
+ 'date' => tag.date,
58
+ 'message' => tag.message,
59
+ 'author' => tag.author,
60
+ 'id' => tag.id,
61
+ 'changes' => changes.map{|change| change.to_h}
62
+ }
63
+ end
64
+
65
+ end
66
+
67
+ end
data/lib/vclog/repo.rb ADDED
@@ -0,0 +1,298 @@
1
+ require 'vclog/core_ext'
2
+ require 'vclog/adapters'
3
+ require 'vclog/config'
4
+ require 'vclog/heuristics'
5
+ require 'vclog/history_file'
6
+ require 'vclog/changelog'
7
+ require 'vclog/tag'
8
+ require 'vclog/release'
9
+ require 'vclog/report'
10
+
11
+ module VCLog
12
+
13
+ # Encapsulate representaiton of the repository.
14
+ #
15
+ class Repo
16
+
17
+ #
18
+ # File glob used to find project root directory.
19
+ #
20
+ ROOT_GLOB = '{.ruby,.map/,.git/,.hg/,_darcs/,.svn/}'
21
+
22
+ #
23
+ # Project's root directory.
24
+ #
25
+ attr :root
26
+
27
+ #
28
+ # Options hash.
29
+ #
30
+ attr :options
31
+
32
+ #
33
+ # Setup new Repo instance.
34
+ #
35
+ def initialize(root, options={})
36
+ options[:root] = root if root
37
+
38
+ @config = Config.new(options)
39
+ @options = options
40
+
41
+ vcs_type = @config.vcs_type
42
+
43
+ raise ArgumentError, "Not a recognized version control system." unless vcs_type
44
+
45
+ @adapter = Adapters.const_get(vcs_type.capitalize).new(self)
46
+ end
47
+
48
+ #
49
+ # Returns instance of an Adapter subclass.
50
+ #
51
+ def adapter
52
+ @adapter
53
+ end
54
+
55
+ #
56
+ # Configuration.
57
+ #
58
+ def config
59
+ @config
60
+ end
61
+
62
+ #
63
+ # Project root directory.
64
+ #
65
+ def root
66
+ config.root
67
+ end
68
+
69
+ #
70
+ # Load heuristics script.
71
+ #
72
+ def heuristics
73
+ config.heuristics
74
+ end
75
+
76
+ #
77
+ # Check force option.
78
+ #
79
+ def force?
80
+ config.force?
81
+ end
82
+
83
+ #
84
+ # Access to Repo's HISTORY file.
85
+ #
86
+ def history_file
87
+ @history_file ||= HistoryFile.new(options[:history_file] || root)
88
+ end
89
+
90
+ #
91
+ # Read history file and make a commit tag for any release not already
92
+ # tagged. Unless the force option is set the user will be prompted for
93
+ # each new tag.
94
+ #
95
+ def autotag(prefix=nil)
96
+ history_file.tags.each do |tag|
97
+ label = "#{prefix}#{tag.name}"
98
+ if not adapter.tag?(label)
99
+ chg = adapter.change_by_date(tag.date)
100
+ if chg
101
+ if force? or ask_yn(new_tag_message(label, tag) + "\nCreate tag? [yN] ")
102
+ adapter.tag(chg.rev, label, tag.date, tag.message)
103
+ end
104
+ else
105
+ puts "No commit found for #{label} #{tag.date.strftime('%Y-%m-%d')}."
106
+ end
107
+ end
108
+ end
109
+ end
110
+
111
+ #
112
+ # List of all changes.
113
+ #
114
+ def changes
115
+ @changes ||= apply_heuristics(adapter.changes)
116
+ end
117
+
118
+ #
119
+ # List of all change points.
120
+ #
121
+ def change_points
122
+ @change_points ||= apply_heuristics(adapter.change_points)
123
+ end
124
+
125
+ #
126
+ # Apply heuristics to changes.
127
+ #
128
+ def apply_heuristics(changes)
129
+ changes.each do |change|
130
+ change.apply_heuristics(heuristics)
131
+ end
132
+ end
133
+
134
+ #
135
+ # Collect releases for the given set of +changes+.
136
+ #
137
+ # Releases are groups of changes segregated by tags. The release version,
138
+ # release date and release note are defined by hard tag commits.
139
+ #
140
+ # @param [Array<Change>] changes
141
+ # List of Change objects.
142
+ #
143
+ # @return [Array<Release>]
144
+ # List of Release objects.
145
+ #
146
+ def releases(changes)
147
+ rel = []
148
+ tags = self.tags.dup
149
+
150
+ #ver = repo.bump(version)
151
+
152
+ name = config.version || 'HEAD'
153
+ user = adapter.user
154
+ date = ::Time.now + (3600 * 24) # one day ahead
155
+
156
+ change = Change.new(:id=>'HEAD', :date=>date, :who=>user)
157
+
158
+ tags << Tag.new(:name=>name, :date=>date, :who=>user, :msg=>"Current Development", :commit=>change)
159
+
160
+ # TODO: Do we need to add a Time.now tag?
161
+ # add current verion to release list (if given)
162
+ #previous_version = tags[0].name
163
+ #if current_version < previous_version # TODO: need to use natural comparision
164
+ # raise ArgumentError, "Release version is less than previous version (#{previous_version})."
165
+ #end
166
+
167
+ # sort by release date
168
+ tags = tags.sort{ |a,b| a.date <=> b.date }
169
+
170
+ # organize into deltas
171
+ delta = []
172
+ last = nil
173
+ tags.each do |tag|
174
+ delta << [tag, [last, tag.commit.date]]
175
+ last = tag.commit.date
176
+ end
177
+
178
+ # gather changes for each delta
179
+ delta.each do |tag, (started, ended)|
180
+ if started
181
+ set = changes.select{ |c| c.date > started && c.date <= ended }
182
+ #gt_vers, gt_date = gt.name, gt.date
183
+ #lt_vers, lt_date = lt.name, lt.date
184
+ #gt_date = Time.parse(gt_date) unless Time===gt_date
185
+ #lt_date = Time.parse(lt_date) unless Time===lt_date
186
+ #log = changelog.after(gt).before(lt)
187
+ else
188
+ #lt_vers, lt_date = lt.name, lt.date
189
+ #lt_date = Time.parse(lt_date) unless Time===lt_date
190
+ #log = changelog.before(lt_date)
191
+ set = changes.select{ |c| c.date <= ended }
192
+ end
193
+ rel << Release.new(tag, set)
194
+ end
195
+ rel.sort
196
+ end
197
+
198
+ #
199
+ # Print a report with given options.
200
+ #
201
+ def report(options)
202
+ report = Report.new(self, options)
203
+ report.print
204
+ end
205
+
206
+ #
207
+ # Make an educated guess as to the next version number based on
208
+ # changes made since previous release.
209
+ #
210
+ # @return [String] version number
211
+ #
212
+ # @todo Allow configuration of version bump thresholds
213
+ #
214
+ def bump
215
+ last_release = releases(changes).first
216
+ max = last_release.changes.map{ |c| c.level }.max
217
+ if max > 1
218
+ bump_part('major')
219
+ elsif max >= 0
220
+ bump_part('minor')
221
+ else
222
+ bump_part('patch')
223
+ end
224
+ end
225
+
226
+ #
227
+ # Provides a bumped version number.
228
+ #
229
+ def bump_part(part=nil)
230
+ raise "bad version part - #{part}" unless ['major', 'minor', 'patch', 'build', ''].include?(part.to_s)
231
+
232
+ if tags.last
233
+ v = tags[-1].name # TODO: ensure the latest version
234
+ v = tags[-2].name if v == 'HEAD'
235
+ else
236
+ v = '0.0.0'
237
+ end
238
+
239
+ v = v.split(/\W/) # TODO: preserve split chars
240
+
241
+ case part.to_s
242
+ when 'major'
243
+ v[0] = v[0].succ
244
+ (1..(v.size-1)).each{ |i| v[i] = '0' }
245
+ v.join('.')
246
+ when 'minor'
247
+ v[1] = '0' unless v[1]
248
+ v[1] = v[1].succ
249
+ (2..(v.size-1)).each{ |i| v[i] = '0' }
250
+ v.join('.')
251
+ when 'patch'
252
+ v[1] = '0' unless v[1]
253
+ v[2] = '0' unless v[2]
254
+ v[2] = v[2].succ
255
+ (3..(v.size-1)).each{ |i| v[i] = '0' }
256
+ v.join('.')
257
+ else
258
+ v[-1] = '0' unless v[-1]
259
+ v[-1] = v[-1].succ
260
+ v.join('.')
261
+ end
262
+ end
263
+
264
+ #
265
+ # Delegate missing methods to SCM adapter.
266
+ #
267
+ def method_missing(s, *a, &b)
268
+ if adapter.respond_to?(s)
269
+ adapter.send(s, *a, &b)
270
+ else
271
+ super(s,*a,&b)
272
+ end
273
+ end
274
+
275
+ private
276
+
277
+ #
278
+ # Ask yes/no question.
279
+ #
280
+ def ask_yn(message)
281
+ case ask(message)
282
+ when 'y', 'Y', 'yes'
283
+ true
284
+ else
285
+ false
286
+ end
287
+ end
288
+
289
+ #
290
+ # Returns a String.
291
+ #
292
+ def new_tag_message(label, tag)
293
+ "#{label} / #{tag.date.strftime('%Y-%m-%d')}\n#{tag.message}"
294
+ end
295
+
296
+ end
297
+
298
+ end