vclog 1.0.0
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.
- data/COPYING +674 -0
- data/HISTORY +20 -0
- data/MANIFEST +32 -0
- data/README +38 -0
- data/RELEASE +11 -0
- data/bin/vclog +131 -0
- data/lib/vclog/changelog.rb +484 -0
- data/lib/vclog/core_ext.rb +106 -0
- data/lib/vclog/vcs/darcs.rb +83 -0
- data/lib/vclog/vcs/git.rb +54 -0
- data/lib/vclog/vcs/hg.rb +0 -0
- data/lib/vclog/vcs/svn.rb +58 -0
- data/lib/vclog/vcs.rb +82 -0
- data/meta/authors +1 -0
- data/meta/created +1 -0
- data/meta/description +1 -0
- data/meta/homepage +1 -0
- data/meta/license +1 -0
- data/meta/mailinglist +1 -0
- data/meta/package +1 -0
- data/meta/project +1 -0
- data/meta/repository +2 -0
- data/meta/requires +1 -0
- data/meta/sitemap +1 -0
- data/meta/summary +1 -0
- data/meta/title +1 -0
- data/meta/version +1 -0
- metadata +98 -0
data/HISTORY
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
= CHANGE HISTORY
|
2
|
+
|
3
|
+
== 1.0.0 / 2009-08-03
|
4
|
+
|
5
|
+
This is the first "production" release of VCLog.
|
6
|
+
|
7
|
+
* 2 Major Enhancements
|
8
|
+
|
9
|
+
* Improved command line interface.
|
10
|
+
* Added output option to save changelog.
|
11
|
+
|
12
|
+
|
13
|
+
== 0.1.0 / 2006-06-05
|
14
|
+
|
15
|
+
This is the initial version of vclog.
|
16
|
+
|
17
|
+
* 1 Major Enhancement
|
18
|
+
|
19
|
+
* Happy Birthday
|
20
|
+
|
data/MANIFEST
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
test
|
2
|
+
RELEASE
|
3
|
+
README
|
4
|
+
HISTORY
|
5
|
+
meta
|
6
|
+
meta/created
|
7
|
+
meta/repository
|
8
|
+
meta/homepage
|
9
|
+
meta/summary
|
10
|
+
meta/package
|
11
|
+
meta/title
|
12
|
+
meta/version
|
13
|
+
meta/license
|
14
|
+
meta/sitemap
|
15
|
+
meta/mailinglist
|
16
|
+
meta/authors
|
17
|
+
meta/requires
|
18
|
+
meta/project
|
19
|
+
meta/description
|
20
|
+
lib
|
21
|
+
lib/vclog
|
22
|
+
lib/vclog/changelog.rb
|
23
|
+
lib/vclog/core_ext.rb
|
24
|
+
lib/vclog/vcs
|
25
|
+
lib/vclog/vcs/darcs.rb
|
26
|
+
lib/vclog/vcs/git.rb
|
27
|
+
lib/vclog/vcs/svn.rb
|
28
|
+
lib/vclog/vcs/hg.rb
|
29
|
+
lib/vclog/vcs.rb
|
30
|
+
bin
|
31
|
+
bin/vclog
|
32
|
+
COPYING
|
data/README
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
= VCLog
|
2
|
+
|
3
|
+
* http://proutils.rubyforge.org
|
4
|
+
* http://proutils.rubyforge.org/vclog
|
5
|
+
|
6
|
+
|
7
|
+
== DESCRIPTION
|
8
|
+
|
9
|
+
VClog is an cross-VCS/SCM changelog generator.
|
10
|
+
|
11
|
+
|
12
|
+
== SYNOPSIS
|
13
|
+
|
14
|
+
VCLog generates changelogs. It supports a few different formats. The default
|
15
|
+
format is standard GNU text style. From a repository's root directory try:
|
16
|
+
|
17
|
+
$ vclog
|
18
|
+
|
19
|
+
To generate an XML formatted changelog use:
|
20
|
+
|
21
|
+
$ vclog --xml
|
22
|
+
|
23
|
+
See 'vclog --help' for more options.
|
24
|
+
|
25
|
+
|
26
|
+
== RELEASE NOTES
|
27
|
+
|
28
|
+
Please see RELEASE file.
|
29
|
+
|
30
|
+
|
31
|
+
== LICENSE & COPYRIGHT
|
32
|
+
|
33
|
+
VCLog
|
34
|
+
|
35
|
+
Copyright (c) 2008,2009 TigerOps.org
|
36
|
+
|
37
|
+
VCLog is distributed under the terms of the GPLv3 license.
|
38
|
+
|
data/RELEASE
ADDED
data/bin/vclog
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# vclog
|
4
|
+
#
|
5
|
+
# SYNOPSIS
|
6
|
+
#
|
7
|
+
# VCLog provides cross-vcs ChangeLogs. It works by
|
8
|
+
# parsing the native changelog a VCS system produces
|
9
|
+
# into a common model, which then can be used to
|
10
|
+
# produce Changelogs in a variety of formats.
|
11
|
+
#
|
12
|
+
# VCLog currently support SVN and Git. CVS, Darcs and
|
13
|
+
# Mercurial/Hg are in the works.
|
14
|
+
#
|
15
|
+
# EXAMPLES
|
16
|
+
#
|
17
|
+
# To produce a GNU-like changelog:
|
18
|
+
#
|
19
|
+
# $ vclog
|
20
|
+
#
|
21
|
+
# For XML format:
|
22
|
+
#
|
23
|
+
# $ vclog --xml
|
24
|
+
#
|
25
|
+
# Or for a micorformat-ish HTML:
|
26
|
+
#
|
27
|
+
# $ vclog --html
|
28
|
+
#
|
29
|
+
#To use the library programmatically, please see the API documentation.
|
30
|
+
#
|
31
|
+
# LICENSE
|
32
|
+
#
|
33
|
+
# VCLog Copyright (c) 2008 Tiger Ops
|
34
|
+
# VCLog is distributed under the terms of the GPLv3.
|
35
|
+
|
36
|
+
require 'vclog/vcs'
|
37
|
+
require 'getoptlong'
|
38
|
+
|
39
|
+
# TODO: rev option.
|
40
|
+
#
|
41
|
+
def vclog
|
42
|
+
|
43
|
+
opts = GetoptLong.new(
|
44
|
+
[ '--help' , '-h', GetoptLong::NO_ARGUMENT ],
|
45
|
+
[ '--debug', GetoptLong::NO_ARGUMENT ],
|
46
|
+
[ '--typed', GetoptLong::NO_ARGUMENT ],
|
47
|
+
[ '--rev' , GetoptLong::NO_ARGUMENT ],
|
48
|
+
[ '--gnu' , GetoptLong::NO_ARGUMENT ],
|
49
|
+
[ '--xml' , GetoptLong::NO_ARGUMENT ],
|
50
|
+
[ '--html' , GetoptLong::NO_ARGUMENT ],
|
51
|
+
[ '--rel' , GetoptLong::REQUIRED_ARGUMENT ],
|
52
|
+
[ '--style', GetoptLong::REQUIRED_ARGUMENT ],
|
53
|
+
[ '--output', '-o', GetoptLong::REQUIRED_ARGUMENT ]
|
54
|
+
)
|
55
|
+
|
56
|
+
format = :gnu
|
57
|
+
typed = false
|
58
|
+
rev = false
|
59
|
+
vers = nil
|
60
|
+
style = nil
|
61
|
+
output = nil
|
62
|
+
|
63
|
+
opts.each do |opt, arg|
|
64
|
+
case opt
|
65
|
+
when '--help'
|
66
|
+
puts "vclog [--gnu|--html|--xml|--rel] [--typed]"
|
67
|
+
exit
|
68
|
+
when '--debug'
|
69
|
+
$DEBUG = true
|
70
|
+
when '--typed'
|
71
|
+
typed = true
|
72
|
+
when '--rev'
|
73
|
+
rev = true
|
74
|
+
when '--xml'
|
75
|
+
format = :xml
|
76
|
+
when '--html'
|
77
|
+
format = :html
|
78
|
+
when '--rel'
|
79
|
+
format = :rel
|
80
|
+
vers = arg
|
81
|
+
when '--style'
|
82
|
+
style = arg
|
83
|
+
when '--output'
|
84
|
+
output = arg
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
vcs = VCLog::VCS.new
|
89
|
+
|
90
|
+
changelog = vcs.changelog
|
91
|
+
|
92
|
+
if typed
|
93
|
+
changelog = changelog.typed
|
94
|
+
end
|
95
|
+
|
96
|
+
case format
|
97
|
+
when :xml
|
98
|
+
log = changelog.format_xml(style) # xsl stylesheet url
|
99
|
+
when :html
|
100
|
+
log = changelog.format_html(style) # css stylesheet url
|
101
|
+
when :rel
|
102
|
+
if output && File.file?(output)
|
103
|
+
file = output
|
104
|
+
else
|
105
|
+
file = Dir.glob('change{s,log}{,.txt}', File::FNM_CASEFOLD).first
|
106
|
+
end
|
107
|
+
log = changelog.format_rel(file, vers)
|
108
|
+
else #:gnu
|
109
|
+
log = changelog.to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
if output
|
113
|
+
File.open(output, 'w') do |f|
|
114
|
+
f << log
|
115
|
+
end
|
116
|
+
else
|
117
|
+
puts log
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
begin
|
123
|
+
vclog
|
124
|
+
rescue => err
|
125
|
+
if $DEBUG
|
126
|
+
raise err
|
127
|
+
else
|
128
|
+
puts err.message
|
129
|
+
exit -1
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,484 @@
|
|
1
|
+
module VCLog
|
2
|
+
|
3
|
+
require 'vclog/core_ext'
|
4
|
+
|
5
|
+
# Supports output formats:
|
6
|
+
#
|
7
|
+
# xml
|
8
|
+
# html
|
9
|
+
# yaml
|
10
|
+
# json
|
11
|
+
# text
|
12
|
+
#
|
13
|
+
class Changelog
|
14
|
+
include Enumerable
|
15
|
+
|
16
|
+
DAY = 24*60*60
|
17
|
+
|
18
|
+
attr :changes
|
19
|
+
|
20
|
+
def initialize(changes=nil)
|
21
|
+
@changes = changes || []
|
22
|
+
end
|
23
|
+
|
24
|
+
def change(date, who, rev, note)
|
25
|
+
note, type = *split_note(note)
|
26
|
+
note = note.gsub(/^\s*?\n/m,'') # remove blank lines
|
27
|
+
@changes << Entry.new(:when=>date, :who=>who, :rev=>rev, :type=>type, :msg=>note)
|
28
|
+
end
|
29
|
+
|
30
|
+
def each(&block) ; @changes.each(&block) ; end
|
31
|
+
def empty? ; @changes.empty? ; end
|
32
|
+
def size ; @changes.size ; end
|
33
|
+
|
34
|
+
#
|
35
|
+
def <<(entry)
|
36
|
+
raise unless Entry===entry
|
37
|
+
@changes << entry
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return a new changelog with entries that have a specified type.
|
41
|
+
# TODO: Be able to specify which types to include or omit.
|
42
|
+
def typed
|
43
|
+
self.class.new(changes.select{ |e| e.type })
|
44
|
+
end
|
45
|
+
|
46
|
+
# Return a new changelog with entries occuring after the
|
47
|
+
# given date limit.
|
48
|
+
def after(date_limit)
|
49
|
+
after = changes.select{ |entry| entry.date > date_limit + DAY }
|
50
|
+
self.class.new(after)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Return a new changelog with entries occuring before the
|
54
|
+
# given date limit.
|
55
|
+
def before(date_limit)
|
56
|
+
before = changes.select{ |entry| entry.date <= date_limit + DAY }
|
57
|
+
self.class.new(before)
|
58
|
+
end
|
59
|
+
|
60
|
+
#
|
61
|
+
|
62
|
+
#
|
63
|
+
def by_type
|
64
|
+
mapped = {}
|
65
|
+
changes.each do |entry|
|
66
|
+
mapped[entry.type] ||= self.class.new
|
67
|
+
mapped[entry.type] << entry
|
68
|
+
end
|
69
|
+
mapped
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
def by_author
|
74
|
+
mapped = {}
|
75
|
+
changes.each do |entry|
|
76
|
+
mapped[entry.author] ||= self.class.new
|
77
|
+
mapped[entry.author] << entry
|
78
|
+
end
|
79
|
+
mapped
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
def by_date
|
84
|
+
mapped = {}
|
85
|
+
changes.each do |entry|
|
86
|
+
mapped[entry.date.strftime('%Y-%m-%d')] ||= self.class.new
|
87
|
+
mapped[entry.date.strftime('%Y-%m-%d')] << entry
|
88
|
+
end
|
89
|
+
mapped = mapped.to_a.sort{ |a,b| b[0] <=> a[0] }
|
90
|
+
mapped
|
91
|
+
end
|
92
|
+
|
93
|
+
#
|
94
|
+
#def by_date
|
95
|
+
# mapped = {}
|
96
|
+
# changes.each do |entry|
|
97
|
+
# mapped[entry.date.strftime('%Y-%m-%d')] ||= self.class.new
|
98
|
+
# mapped[entry.date.strftime('%Y-%m-%d')] << entry
|
99
|
+
# end
|
100
|
+
# mapped
|
101
|
+
#end
|
102
|
+
|
103
|
+
##################
|
104
|
+
# Output Formats #
|
105
|
+
##################
|
106
|
+
|
107
|
+
def format_yaml
|
108
|
+
require 'yaml'
|
109
|
+
changes.to_yaml
|
110
|
+
end
|
111
|
+
|
112
|
+
def format_json
|
113
|
+
require 'json'
|
114
|
+
changes.to_json
|
115
|
+
end
|
116
|
+
|
117
|
+
#
|
118
|
+
def format_gnu
|
119
|
+
x = []
|
120
|
+
by_date.each do |date, date_changes|
|
121
|
+
date_changes.by_author.each do |author, author_changes|
|
122
|
+
x << %[#{date} #{author}\n]
|
123
|
+
#author_changes = author_changes.sort{|a,b| b.date <=> a.date}
|
124
|
+
author_changes.each do |entry|
|
125
|
+
if entry.type
|
126
|
+
msg = "#{entry.message} [#{entry.type}]".tabto(10)
|
127
|
+
else
|
128
|
+
msg = "#{entry.message}".tabto(10)
|
129
|
+
end
|
130
|
+
msg[8] = '*'
|
131
|
+
x << msg
|
132
|
+
end
|
133
|
+
end
|
134
|
+
x << "\n"
|
135
|
+
end
|
136
|
+
return x.join("\n")
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
alias_method :to_s, :format_gnu
|
141
|
+
|
142
|
+
# Create an XML formated changelog.
|
143
|
+
# +xsl+ reference defaults to 'log.xsl'
|
144
|
+
def format_xml(xsl=nil)
|
145
|
+
xsl = 'log.xsl' if xsl.nil?
|
146
|
+
x = []
|
147
|
+
x << %[<?xml version="1.0"?>]
|
148
|
+
x << %[<?xml-stylesheet href="#{xsl}" type="text/xsl" ?>] if xsl
|
149
|
+
x << %[<log>]
|
150
|
+
changes.sort{|a,b| b.date <=> a.date}.each do |entry|
|
151
|
+
x << %[<entry>]
|
152
|
+
x << %[ <date>#{entry.date}</date>]
|
153
|
+
x << %[ <author>#{escxml(entry.author)}</author>]
|
154
|
+
x << %[ <revison>#{escxml(entry.revison)}</revison>]
|
155
|
+
x << %[ <type>#{escxml(entry.type)}</type>]
|
156
|
+
x << %[ <message>#{escxml(entry.message)}</message>]
|
157
|
+
x << %[</entry>]
|
158
|
+
end
|
159
|
+
x << %[</log>]
|
160
|
+
return x.join("\n")
|
161
|
+
end
|
162
|
+
|
163
|
+
# Create an HTML formated changelog.
|
164
|
+
# +css+ reference defaults to 'log.css'
|
165
|
+
def format_html(css=nil)
|
166
|
+
css = 'log.css' if css.nil?
|
167
|
+
x = []
|
168
|
+
x << %[<html>]
|
169
|
+
x << %[<head>]
|
170
|
+
x << %[ <title>Changelog</title>]
|
171
|
+
x << %[ <style>]
|
172
|
+
x << %[ body{font-family: sans-serif;}]
|
173
|
+
x << %[ #changelog{width:800px;margin:0 auto;}]
|
174
|
+
x << %[ li{padding: 10px;}]
|
175
|
+
x << %[ .date{font-weight: bold; color: gray; float: left; padding: 0 5px;}]
|
176
|
+
x << %[ .author{color: red;}]
|
177
|
+
x << %[ .message{padding: 5 0; font-weight: bold;}]
|
178
|
+
x << %[ .revison{font-size: 0.8em;}]
|
179
|
+
x << %[ </style>]
|
180
|
+
x << %[ <link rel="stylesheet" href="#{css}" type="text/css">] if css
|
181
|
+
x << %[</head>]
|
182
|
+
x << %[<body>]
|
183
|
+
x << %[ <div id="changelog">]
|
184
|
+
x << %[ <h1>ChangeLog</h1>]
|
185
|
+
x << %[ <ul class="log">]
|
186
|
+
changes.sort{|a,b| b.date <=> a.date}.each do |entry|
|
187
|
+
x << %[ <li class="entry">]
|
188
|
+
x << %[ <div class="date">#{entry.date}</div>]
|
189
|
+
x << %[ <div class="author">#{escxml(entry.author)}</div>]
|
190
|
+
x << %[ <div class="type">#{escxml(entry.type)}</div>]
|
191
|
+
x << %[ <div class="message">#{escxml(entry.message)}</div>]
|
192
|
+
x << %[ <div class="revison">##{escxml(entry.revison)}</div>]
|
193
|
+
x << %[ </li>]
|
194
|
+
end
|
195
|
+
x << %[ </ul>]
|
196
|
+
x << %[ </div>]
|
197
|
+
x << %[</body>]
|
198
|
+
x << %[</html>]
|
199
|
+
return x.join("\n")
|
200
|
+
end
|
201
|
+
|
202
|
+
#
|
203
|
+
def format_rel(file, current_version=nil, current_release=nil)
|
204
|
+
log = []
|
205
|
+
# collect releases already listed in changelog file
|
206
|
+
rels = releases(file)
|
207
|
+
# add current verion to release list (if given)
|
208
|
+
previous_version = rels[0][0]
|
209
|
+
if current_version < previous_version
|
210
|
+
raise ArgumentError, "Release version is less than previous version (#{previous_version})."
|
211
|
+
end
|
212
|
+
#case current_version
|
213
|
+
#when 'major'
|
214
|
+
# v = previous_verison.split(/\W/)
|
215
|
+
# v[0] = v[0].succ
|
216
|
+
# current_version = v.join('.')
|
217
|
+
#when 'minor'
|
218
|
+
# v = previous_verison.split(/\W/)
|
219
|
+
# v[1] = v[1].succ
|
220
|
+
# current_version = v.join('.')
|
221
|
+
# end
|
222
|
+
#end
|
223
|
+
rels << [current_version, current_release || Time.now]
|
224
|
+
# make sure all release date are Time objects
|
225
|
+
rels = rels.collect{ |v,d| [v, Time===d ? d : Time.parse(d)] }
|
226
|
+
# only uniq releases
|
227
|
+
rels = rels.uniq
|
228
|
+
# sort by release date
|
229
|
+
rels = rels.to_a.sort{ |a,b| a[1] <=> b[1] }
|
230
|
+
# organize into deltas
|
231
|
+
deltas, last = [], nil
|
232
|
+
rels.each do |rel|
|
233
|
+
deltas << [last, rel]
|
234
|
+
last = rel
|
235
|
+
end
|
236
|
+
# gather changes for each delta and build log
|
237
|
+
deltas.each do |gt, lt|
|
238
|
+
if gt
|
239
|
+
gt_vers, gt_date = *gt
|
240
|
+
lt_vers, lt_date = *lt
|
241
|
+
#gt_date = Time.parse(gt_date) unless Time===gt_date
|
242
|
+
#lt_date = Time.parse(lt_date) unless Time===lt_date
|
243
|
+
changes = after(gt_date).before(lt_date)
|
244
|
+
else
|
245
|
+
lt_vers, lt_date = *lt
|
246
|
+
#lt_date = Time.parse(lt_date) unless Time===lt_date
|
247
|
+
changes = before(lt_date)
|
248
|
+
end
|
249
|
+
reltext = changes.format_rel_types
|
250
|
+
unless reltext.strip.empty?
|
251
|
+
log << "== #{lt_vers} / #{lt_date.strftime('%Y-%m-%d')}\n\n#{reltext}"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
# reverse log order and make into document
|
255
|
+
log.reverse.join("\n")
|
256
|
+
end
|
257
|
+
|
258
|
+
#
|
259
|
+
def format_rel_types
|
260
|
+
groups = changes.group_by{ |e| e.type_number }
|
261
|
+
string = ""
|
262
|
+
5.times do |n|
|
263
|
+
entries = groups[n]
|
264
|
+
next if !entries
|
265
|
+
next if entries.empty?
|
266
|
+
string << "* #{entries.size} #{entries[0].type_phrase}\n\n"
|
267
|
+
entries.sort!{|a,b| a.date <=> b.date }
|
268
|
+
entries.each do |entry|
|
269
|
+
#string << "== #{date} #{who}\n\n" # no email :(
|
270
|
+
text = "#{entry.message} (##{entry.revison})"
|
271
|
+
text = text.tabto(6)
|
272
|
+
text[4] = '*'
|
273
|
+
#entry = entry.join(' ').tabto(6)
|
274
|
+
#entry[4] = '*'
|
275
|
+
string << text
|
276
|
+
string << "\n"
|
277
|
+
end
|
278
|
+
string << "\n"
|
279
|
+
end
|
280
|
+
string
|
281
|
+
end
|
282
|
+
|
283
|
+
#
|
284
|
+
def releases(file)
|
285
|
+
return [] unless file
|
286
|
+
clog = File.read(file)
|
287
|
+
tags = clog.scan(/^==(.*?)$/)
|
288
|
+
rels = tags.collect do |t|
|
289
|
+
parse_version_tag(t[0])
|
290
|
+
end
|
291
|
+
return rels
|
292
|
+
end
|
293
|
+
|
294
|
+
#
|
295
|
+
def parse_version_tag(tag)
|
296
|
+
version, date = *tag.split('/')
|
297
|
+
version, date = version.sub(/^\=+\s*/,'').strip, date.strip
|
298
|
+
return version, date
|
299
|
+
end
|
300
|
+
|
301
|
+
###################
|
302
|
+
# Save Chaqngelog #
|
303
|
+
###################
|
304
|
+
|
305
|
+
# Save changelog as file in specified format.
|
306
|
+
def save(file, format=:gnu, *args)
|
307
|
+
case format.to_sym
|
308
|
+
when :xml
|
309
|
+
text = format_xml
|
310
|
+
save_xsl(file)
|
311
|
+
when :html
|
312
|
+
text = format_html(*args)
|
313
|
+
when :rel
|
314
|
+
text = format_rel(file, *args)
|
315
|
+
when :yaml
|
316
|
+
text = format_yaml(file)
|
317
|
+
when :json
|
318
|
+
text = format_json(file)
|
319
|
+
else
|
320
|
+
text = format_gnu
|
321
|
+
end
|
322
|
+
|
323
|
+
FileUtils.mkdir_p(File.dirname(file))
|
324
|
+
|
325
|
+
different = true
|
326
|
+
if File.exist?(file)
|
327
|
+
different = (File.read(file) != text)
|
328
|
+
end
|
329
|
+
|
330
|
+
File.open(file, 'w') do |f|
|
331
|
+
f << text
|
332
|
+
end if different
|
333
|
+
end
|
334
|
+
|
335
|
+
private
|
336
|
+
|
337
|
+
#
|
338
|
+
def escxml(input)
|
339
|
+
result = input.to_s.dup
|
340
|
+
result.gsub!("&", "&")
|
341
|
+
result.gsub!("<", "<")
|
342
|
+
result.gsub!(">", ">")
|
343
|
+
result.gsub!("'", "'")
|
344
|
+
#result.gsub!("@", "&at;")
|
345
|
+
result.gsub!("\"", """)
|
346
|
+
return result
|
347
|
+
end
|
348
|
+
|
349
|
+
#
|
350
|
+
def save_xsl(file)
|
351
|
+
#xslfile = file.chomp(File.extname(file)) + '.xsl'
|
352
|
+
xslfile = File.join(File.dirname(file), 'log.xsl')
|
353
|
+
unless File.exist?(xslfile)
|
354
|
+
FileUtils.mkdir_p(File.dirname(xslfile))
|
355
|
+
File.open(xslfile, 'w') do |f|
|
356
|
+
f << DEFAULT_LOG_XSL
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
#
|
362
|
+
def split_note(note)
|
363
|
+
note = note.strip
|
364
|
+
if md = /\A.*?\[(.*?)\]\s*$/.match(note)
|
365
|
+
t = md[1].strip.downcase
|
366
|
+
n = note.sub(/\[#{md[1]}\]\s*$/, "")
|
367
|
+
else
|
368
|
+
n, t = note, nil
|
369
|
+
end
|
370
|
+
return n, t
|
371
|
+
end
|
372
|
+
|
373
|
+
# = Change Log Entry class
|
374
|
+
class Entry
|
375
|
+
include Comparable
|
376
|
+
|
377
|
+
attr_accessor :author
|
378
|
+
attr_accessor :date
|
379
|
+
attr_accessor :revison
|
380
|
+
attr_accessor :message
|
381
|
+
attr_accessor :type
|
382
|
+
|
383
|
+
def initialize(opts={})
|
384
|
+
@author = opts[:author] || opts[:who]
|
385
|
+
@date = opts[:date] || opts[:when]
|
386
|
+
@revison = opts[:revison] || opts[:rev]
|
387
|
+
@message = opts[:message] || opts[:msg]
|
388
|
+
@type = opts[:type]
|
389
|
+
end
|
390
|
+
|
391
|
+
#def clean_type(type)
|
392
|
+
# case type.to_s
|
393
|
+
# when 'maj', 'major' then :major
|
394
|
+
# when 'min', 'minor' then :minor
|
395
|
+
# when 'bug' then :bug
|
396
|
+
# when '' then :other
|
397
|
+
# else
|
398
|
+
# type.to_sym
|
399
|
+
# end
|
400
|
+
#end
|
401
|
+
|
402
|
+
#
|
403
|
+
def type_phrase
|
404
|
+
case type.to_s
|
405
|
+
when 'maj', 'major'
|
406
|
+
'Major Enhancements'
|
407
|
+
when 'min', 'minor'
|
408
|
+
'Minor Enhancements'
|
409
|
+
when 'bug'
|
410
|
+
'Bug Fixes'
|
411
|
+
when ''
|
412
|
+
'Further Enhancements'
|
413
|
+
else
|
414
|
+
"#{type.capitalize} Enhancements"
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
#
|
419
|
+
def type_number
|
420
|
+
case type.to_s.downcase
|
421
|
+
when 'maj', 'major'
|
422
|
+
0
|
423
|
+
when 'min', 'minor'
|
424
|
+
1
|
425
|
+
when 'bug'
|
426
|
+
2
|
427
|
+
when ''
|
428
|
+
4
|
429
|
+
else # other
|
430
|
+
3
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
#
|
435
|
+
def <=>(other)
|
436
|
+
other.date <=> date
|
437
|
+
end
|
438
|
+
|
439
|
+
def inspect
|
440
|
+
"#<Entry:#{object_id} #{date}>"
|
441
|
+
end
|
442
|
+
|
443
|
+
end #class Entry
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
DEFAULT_LOG_XSL = <<-END.tabto(0)
|
448
|
+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
|
449
|
+
|
450
|
+
<xsl:output cdata-section-elements="script"/>
|
451
|
+
|
452
|
+
<xsl:template match="/">
|
453
|
+
<html>
|
454
|
+
<head>
|
455
|
+
<title>Changelog</title>
|
456
|
+
<link REL='SHORTCUT ICON' HREF="../img/ruby-sm.png" />
|
457
|
+
<style>
|
458
|
+
td { font-family: sans-serif; padding: 0px 10px; }
|
459
|
+
</style>
|
460
|
+
</head>
|
461
|
+
<body>
|
462
|
+
<div class="container">
|
463
|
+
<h1>Changelog</h1>
|
464
|
+
<table style="width: 100%;">
|
465
|
+
<xsl:apply-templates />
|
466
|
+
</table>
|
467
|
+
</div>
|
468
|
+
</body>
|
469
|
+
</html>
|
470
|
+
</xsl:template>
|
471
|
+
|
472
|
+
<xsl:template match="entry">
|
473
|
+
<tr>
|
474
|
+
<td><b><pre><xsl:value-of select="message"/></pre></b></td>
|
475
|
+
<td><xsl:value-of select="author"/></td>
|
476
|
+
<td><xsl:value-of select="date"/></td>
|
477
|
+
</tr>
|
478
|
+
</xsl:template>
|
479
|
+
|
480
|
+
</xsl:stylesheet>
|
481
|
+
END
|
482
|
+
|
483
|
+
end
|
484
|
+
|