bluecloth 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. data/ChangeLog +629 -0
  2. data/LICENSE +27 -0
  3. data/LICENSE.discount +47 -0
  4. data/README +71 -0
  5. data/Rakefile +319 -0
  6. data/Rakefile.local +63 -0
  7. data/bin/bluecloth +84 -0
  8. data/ext/VERSION +1 -0
  9. data/ext/amalloc.h +29 -0
  10. data/ext/bluecloth.c +373 -0
  11. data/ext/config.h +47 -0
  12. data/ext/cstring.h +73 -0
  13. data/ext/docheader.c +43 -0
  14. data/ext/extconf.rb +45 -0
  15. data/ext/generate.c +1387 -0
  16. data/ext/markdown.c +939 -0
  17. data/ext/markdown.h +135 -0
  18. data/ext/mkdio.c +241 -0
  19. data/ext/mkdio.h +66 -0
  20. data/ext/resource.c +169 -0
  21. data/ext/version.c +28 -0
  22. data/lib/bluecloth.rb +148 -0
  23. data/rake/191_compat.rb +26 -0
  24. data/rake/dependencies.rb +76 -0
  25. data/rake/helpers.rb +412 -0
  26. data/rake/manual.rb +782 -0
  27. data/rake/packaging.rb +116 -0
  28. data/rake/publishing.rb +321 -0
  29. data/rake/rdoc.rb +40 -0
  30. data/rake/style.rb +62 -0
  31. data/rake/svn.rb +639 -0
  32. data/rake/testing.rb +204 -0
  33. data/rake/verifytask.rb +64 -0
  34. data/rake/win32.rb +186 -0
  35. data/spec/bluecloth/101_changes_spec.rb +141 -0
  36. data/spec/bluecloth/autolinks_spec.rb +49 -0
  37. data/spec/bluecloth/blockquotes_spec.rb +143 -0
  38. data/spec/bluecloth/code_spans_spec.rb +164 -0
  39. data/spec/bluecloth/emphasis_spec.rb +164 -0
  40. data/spec/bluecloth/entities_spec.rb +65 -0
  41. data/spec/bluecloth/hrules_spec.rb +90 -0
  42. data/spec/bluecloth/images_spec.rb +92 -0
  43. data/spec/bluecloth/inline_html_spec.rb +238 -0
  44. data/spec/bluecloth/links_spec.rb +171 -0
  45. data/spec/bluecloth/lists_spec.rb +294 -0
  46. data/spec/bluecloth/paragraphs_spec.rb +75 -0
  47. data/spec/bluecloth/titles_spec.rb +305 -0
  48. data/spec/bluecloth_spec.rb +209 -0
  49. data/spec/bugfix_spec.rb +123 -0
  50. data/spec/contributions_spec.rb +85 -0
  51. data/spec/data/antsugar.txt +34 -0
  52. data/spec/data/markdowntest/Amps and angle encoding.html +17 -0
  53. data/spec/data/markdowntest/Amps and angle encoding.text +21 -0
  54. data/spec/data/markdowntest/Auto links.html +18 -0
  55. data/spec/data/markdowntest/Auto links.text +13 -0
  56. data/spec/data/markdowntest/Backslash escapes.html +118 -0
  57. data/spec/data/markdowntest/Backslash escapes.text +120 -0
  58. data/spec/data/markdowntest/Blockquotes with code blocks.html +15 -0
  59. data/spec/data/markdowntest/Blockquotes with code blocks.text +11 -0
  60. data/spec/data/markdowntest/Code Blocks.html +18 -0
  61. data/spec/data/markdowntest/Code Blocks.text +14 -0
  62. data/spec/data/markdowntest/Code Spans.html +5 -0
  63. data/spec/data/markdowntest/Code Spans.text +5 -0
  64. data/spec/data/markdowntest/Hard-wrapped paragraphs with list-like lines.html +8 -0
  65. data/spec/data/markdowntest/Hard-wrapped paragraphs with list-like lines.text +8 -0
  66. data/spec/data/markdowntest/Horizontal rules.html +71 -0
  67. data/spec/data/markdowntest/Horizontal rules.text +67 -0
  68. data/spec/data/markdowntest/Inline HTML (Advanced).html +15 -0
  69. data/spec/data/markdowntest/Inline HTML (Advanced).text +15 -0
  70. data/spec/data/markdowntest/Inline HTML (Simple).html +72 -0
  71. data/spec/data/markdowntest/Inline HTML (Simple).text +69 -0
  72. data/spec/data/markdowntest/Inline HTML comments.html +13 -0
  73. data/spec/data/markdowntest/Inline HTML comments.text +13 -0
  74. data/spec/data/markdowntest/Links, inline style.html +11 -0
  75. data/spec/data/markdowntest/Links, inline style.text +12 -0
  76. data/spec/data/markdowntest/Links, reference style.html +52 -0
  77. data/spec/data/markdowntest/Links, reference style.text +71 -0
  78. data/spec/data/markdowntest/Links, shortcut references.html +9 -0
  79. data/spec/data/markdowntest/Links, shortcut references.text +20 -0
  80. data/spec/data/markdowntest/Literal quotes in titles.html +3 -0
  81. data/spec/data/markdowntest/Literal quotes in titles.text +7 -0
  82. data/spec/data/markdowntest/Markdown Documentation - Basics.html +314 -0
  83. data/spec/data/markdowntest/Markdown Documentation - Basics.text +306 -0
  84. data/spec/data/markdowntest/Markdown Documentation - Syntax.html +942 -0
  85. data/spec/data/markdowntest/Markdown Documentation - Syntax.text +888 -0
  86. data/spec/data/markdowntest/Nested blockquotes.html +9 -0
  87. data/spec/data/markdowntest/Nested blockquotes.text +5 -0
  88. data/spec/data/markdowntest/Ordered and unordered lists.html +148 -0
  89. data/spec/data/markdowntest/Ordered and unordered lists.text +131 -0
  90. data/spec/data/markdowntest/Strong and em together.html +7 -0
  91. data/spec/data/markdowntest/Strong and em together.text +7 -0
  92. data/spec/data/markdowntest/Tabs.html +25 -0
  93. data/spec/data/markdowntest/Tabs.text +21 -0
  94. data/spec/data/markdowntest/Tidyness.html +8 -0
  95. data/spec/data/markdowntest/Tidyness.text +5 -0
  96. data/spec/data/ml-announce.txt +17 -0
  97. data/spec/data/re-overflow.txt +67 -0
  98. data/spec/data/re-overflow2.txt +281 -0
  99. data/spec/lib/constants.rb +5 -0
  100. data/spec/lib/helpers.rb +137 -0
  101. data/spec/lib/matchers.rb +235 -0
  102. data/spec/markdowntest_spec.rb +76 -0
  103. metadata +305 -0
@@ -0,0 +1,62 @@
1
+ #
2
+ # Style Fixup Rake Tasks
3
+ # $Id: style.rb 10 2008-07-18 15:52:48Z deveiant $
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+
9
+
10
+ ### Coding style checks and fixes
11
+ namespace :style do
12
+
13
+ BLANK_LINE = /^\s*$/
14
+ GOOD_INDENT = /^(\t\s*)?\S/
15
+
16
+ # A list of the files that have legitimate leading whitespace, etc.
17
+ PROBLEM_FILES = [ SPECDIR + 'config_spec.rb' ]
18
+
19
+ desc "Check source files for inconsistent indent and fix them"
20
+ task :fix_indent do
21
+ files = LIB_FILES + SPEC_FILES
22
+
23
+ badfiles = Hash.new {|h,k| h[k] = [] }
24
+
25
+ trace "Checking files for indentation"
26
+ files.each do |file|
27
+ if PROBLEM_FILES.include?( file )
28
+ trace " skipping problem file #{file}..."
29
+ next
30
+ end
31
+
32
+ trace " #{file}"
33
+ linecount = 0
34
+ file.each_line do |line|
35
+ linecount += 1
36
+
37
+ # Skip blank lines
38
+ next if line =~ BLANK_LINE
39
+
40
+ # If there's a line with incorrect indent, note it and skip to the
41
+ # next file
42
+ if line !~ GOOD_INDENT
43
+ trace " Bad line %d: %p" % [ linecount, line ]
44
+ badfiles[file] << [ linecount, line ]
45
+ end
46
+ end
47
+ end
48
+
49
+ if badfiles.empty?
50
+ log "No indentation problems found."
51
+ else
52
+ log "Found incorrect indent in #{badfiles.length} files:\n "
53
+ badfiles.each do |file, badlines|
54
+ log " #{file}:\n" +
55
+ " " + badlines.collect {|badline| "%5d: %p" % badline }.join( "\n " )
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+
@@ -0,0 +1,639 @@
1
+ #
2
+ # Subversion Rake Tasks
3
+ # $Id: svn.rb 95 2009-04-02 15:21:01Z deveiant $
4
+ #
5
+ # Authors:
6
+ # * Michael Granger <ged@FaerieMUD.org>
7
+ #
8
+
9
+ require 'pp'
10
+ require 'yaml'
11
+ require 'date'
12
+ require 'time'
13
+ require 'abbrev'
14
+
15
+ # Strftime format for tags/releases
16
+ TAG_TIMESTAMP_FORMAT = '%Y%m%d-%H%M%S'
17
+ TAG_TIMESTAMP_PATTERN = /\d{4}\d{2}\d{2}-\d{6}/
18
+
19
+ RELEASE_VERSION_PATTERN = /\d+\.\d+\.\d+/
20
+
21
+ DEFAULT_EDITOR = 'vi'
22
+ DEFAULT_KEYWORDS = %w[Date Rev Author URL Id]
23
+ KEYWORDED_FILEDIRS = %w[applets spec bin etc ext experiments examples lib misc docs]
24
+ KEYWORDED_FILEPATTERN = /
25
+ ^(?:
26
+ (?:meta)?rakefile.* # Rakefiles
27
+ |
28
+ .*\.(?:rb|c|h|js|html|css|template|erb|page) # Source file extensions
29
+ |
30
+ readme|install|todo
31
+ )$/ix
32
+
33
+ COMMIT_MSG_FILE = 'commit-msg.txt'
34
+
35
+ SVN_TRUNK_DIR = 'trunk' unless defined?( SVN_TRUNK_DIR )
36
+ SVN_RELEASES_DIR = 'branches' unless defined?( SVN_RELEASES_DIR )
37
+ SVN_BRANCHES_DIR = 'branches' unless defined?( SVN_BRANCHES_DIR )
38
+ SVN_TAGS_DIR = 'tags' unless defined?( SVN_TAGS_DIR )
39
+
40
+ FILE_INDENT = " " * 12
41
+ LOG_INDENT = " " * 3
42
+
43
+
44
+
45
+ ###
46
+ ### Subversion-specific Helpers
47
+ ###
48
+
49
+ ### Return a new tag for the given time
50
+ def make_new_tag( time=Time.now )
51
+ return time.strftime( TAG_TIMESTAMP_FORMAT )
52
+ end
53
+
54
+
55
+ ### Get the subversion information for the current working directory as
56
+ ### a hash.
57
+ def get_svn_info( dir='.' )
58
+ return {} unless File.directory?( File.join(dir, '.svn') )
59
+ info = IO.read( '|-' ) or exec 'svn', 'info', dir.to_s
60
+ return YAML.load( info ) # 'svn info' outputs valid YAML! Yay!
61
+ rescue NotImplementedError
62
+ trace "No fork(), proceeding without svn info..."
63
+ return {}
64
+ end
65
+
66
+
67
+ ### Get a list of the objects registered with subversion under the specified directory and
68
+ ### return them as an Array of Pathame objects.
69
+ def get_svn_filelist( dir='.' )
70
+ list = IO.read( '|-' ) or exec 'svn', 'st', '-v', '--ignore-externals', dir
71
+
72
+ # Split into lines, filter out the unknowns, and grab the filenames as Pathnames
73
+ # :FIXME: This will break if we ever put in a file with spaces in its name. This
74
+ # will likely be the least of our worries if we do so, however, so it's not worth
75
+ # the additional complexity to make it handle that case. If we do need that, there's
76
+ # always the --xml output for 'svn st'...
77
+ return list.split( $/ ).
78
+ reject {|line| line =~ /^(\?|(\s*|--- .*)$)/ }.
79
+ collect {|fn| Pathname(fn[/\S+$/]) }
80
+ end
81
+
82
+ ### Return the URL to the repository root for the specified +dir+.
83
+ def get_svn_repo_root( dir='.' )
84
+ info = get_svn_info( dir )
85
+ return info['Repository Root']
86
+ end
87
+
88
+
89
+ ### Return the Subversion URL to the given +dir+.
90
+ def get_svn_url( dir='.' )
91
+ info = get_svn_info( dir )
92
+ return info['URL']
93
+ end
94
+
95
+
96
+ ### Return the path of the specified +dir+ under the svn root of the
97
+ ### checkout.
98
+ def get_svn_path( dir='.' )
99
+ root = get_svn_repo_root( dir )
100
+ url = get_svn_url( dir )
101
+
102
+ return url.sub( root + '/', '' )
103
+ end
104
+
105
+
106
+ ### Return the keywords for the specified array of +files+ as a Hash keyed by filename.
107
+ def get_svn_keyword_map( *files )
108
+ files.flatten!
109
+ files.push( '.' ) if files.empty?
110
+
111
+ cmd = ['svn', 'pg', 'svn:keywords', *files]
112
+
113
+ # trace "Executing: svn pg svn:keywords " + files.join(' ')
114
+ output = IO.read( '|-' ) or exec( 'svn', 'pg', 'svn:keywords', *files )
115
+
116
+ kwmap = {}
117
+ output.split( "\n" ).each do |line|
118
+ next if line !~ /\s+-\s+/
119
+ path, keywords = line.split( /\s+-\s+/, 2 )
120
+ kwmap[ path ] = keywords.split
121
+ end
122
+
123
+ return kwmap
124
+ end
125
+
126
+
127
+ ### Return the latest revision number of the specified +dir+ as an Integer.
128
+ def get_svn_rev( dir='.' )
129
+ info = get_svn_info( dir )
130
+ return info['Revision']
131
+ end
132
+
133
+
134
+ ### Return the latest revision number of the specified +dir+ as an Integer.
135
+ def get_last_changed_rev( dir='.' )
136
+ info = get_svn_info( dir )
137
+ return info['Last Changed Rev']
138
+ end
139
+
140
+
141
+ ### Return a list of the entries at the specified Subversion url. If
142
+ ### no +url+ is specified, it will default to the list in the URL
143
+ ### corresponding to the current working directory.
144
+ def svn_ls( url=nil )
145
+ url ||= get_svn_url()
146
+ list = IO.read( '|-' ) or exec 'svn', 'ls', url
147
+
148
+ trace 'svn ls of %s: %p' % [url, list] if $trace
149
+
150
+ return [] if list.nil? || list.empty?
151
+ return list.split( $INPUT_RECORD_SEPARATOR )
152
+ end
153
+
154
+
155
+ ### Return the URL of the latest timestamp in the tags directory.
156
+ def get_latest_svn_timestamp_tag
157
+ rooturl = get_svn_repo_root()
158
+ tagsurl = rooturl + "/#{SVN_TAGS_DIR}"
159
+
160
+ tags = svn_ls( tagsurl ).grep( TAG_TIMESTAMP_PATTERN ).sort
161
+ return nil if tags.nil? || tags.empty?
162
+ return tagsurl + '/' + tags.last
163
+ end
164
+
165
+
166
+ ### Get a subversion diff of the specified targets and return it. If no targets are
167
+ ### specified, the current directory will be diffed instead.
168
+ def get_svn_diff( *targets )
169
+ targets << BASEDIR if targets.empty?
170
+ trace "Getting svn diff for targets: %p" % [targets]
171
+ log = IO.read( '|-' ) or exec 'svn', 'diff', *(targets.flatten)
172
+
173
+ return log
174
+ end
175
+
176
+
177
+ ### Get a subversion status as an Array of tuples of the form:
178
+ ### [ <status>, <path> ]
179
+ def get_svn_status( *targets )
180
+ targets << BASEDIR if targets.empty?
181
+ trace "Getting svn status for targets: %p" % [targets]
182
+ status = IO.read( '|-' ) or exec 'svn', 'st', '--ignore-externals', *(targets.flatten)
183
+ entries = status.split( /\n/ ).
184
+ select {|line| line !~ /^(\s*|--- .*)$/ }.
185
+ collect do |line|
186
+ flag, path = line.strip.split( /\s+/, 2 )
187
+ [ flag, Pathname.new(path) ]
188
+ end
189
+
190
+ return entries
191
+ end
192
+
193
+
194
+ ### Return the URL of the latest timestamp in the tags directory.
195
+ def get_latest_release_tag
196
+ rooturl = get_svn_repo_root()
197
+ releaseurl = rooturl + "/#{SVN_RELEASES_DIR}"
198
+
199
+ tags = svn_ls( releaseurl ).grep( RELEASE_VERSION_PATTERN ).sort_by do |tag|
200
+ tag[RELEASE_VERSION_PATTERN].split('.').collect {|i| Integer(i) }
201
+ end
202
+ return nil if tags.empty?
203
+
204
+ return releaseurl + '/' + tags.last
205
+ end
206
+
207
+
208
+ ### Return the names of all existing branches.
209
+ def get_branch_names
210
+ rooturl = get_svn_repo_root()
211
+ branchesurl = rooturl + "/#{SVN_BRANCHES_DIR}"
212
+
213
+ return svn_ls( branchesurl )
214
+ end
215
+
216
+
217
+ ### Extract a diff from the specified subversion working +dir+ and return it.
218
+ def make_svn_commit_log( dir='.' )
219
+ diff = IO.read( '|-' ) or exec 'svn', 'diff'
220
+ fail "No differences." if diff.empty?
221
+
222
+ return diff
223
+ end
224
+
225
+
226
+ ### Extract the svn log from the specified subversion working +dir+,
227
+ ### starting from rev +start+ and ending with rev +finish+, and return it.
228
+ def make_svn_log( dir='.', start='PREV', finish='HEAD' )
229
+ trace "svn log -r#{start}:#{finish} #{dir}"
230
+ log = IO.read( '|-' ) or exec 'svn', 'log', "-r#{start}:#{finish}", dir
231
+ fail "No log between #{start} and #{finish}." if log.empty?
232
+
233
+ return log
234
+ end
235
+
236
+
237
+ ### Extract the verbose XML svn log from the specified subversion working +dir+,
238
+ ### starting from rev +start+ and ending with rev +finish+, and return it.
239
+ def make_xml_svn_log( dir='.', start='PREV', finish='HEAD' )
240
+ trace "svn log --xml --verbose -r#{start}:#{finish} #{dir}"
241
+ log = IO.read( '|-' ) or exec 'svn', 'log', '--verbose', '--xml', "-r#{start}:#{finish}", dir
242
+ fail "No log between #{start} and #{finish}." if log.empty?
243
+
244
+ return log
245
+ end
246
+
247
+
248
+ ### Create a changelog from the subversion log of the specified +dir+ and return it.
249
+ def make_svn_changelog( dir='.' )
250
+ require 'xml/libxml'
251
+
252
+ changelog = ''
253
+ path_prefix = '/' + get_svn_path( dir ) + '/'
254
+
255
+ xmllog = make_xml_svn_log( dir, 0 )
256
+
257
+ parser = XML::Parser.string( xmllog )
258
+ root = parser.parse.root
259
+ root.find( '//log/logentry' ).to_a.reverse.each do |entry|
260
+ trace "Making a changelog entry for r%s" % [ entry['revision'] ]
261
+
262
+ added = []
263
+ deleted = []
264
+ changed = []
265
+
266
+ entry.find( 'paths/path').each do |path|
267
+ pathname = path.content
268
+ pathname.sub!( path_prefix , '' ) if pathname.count('/') > 1
269
+
270
+ case path['action']
271
+ when 'A', 'R'
272
+ if path['copyfrom-path']
273
+ verb = path['action'] == 'A' ? 'renamed' : 'copied'
274
+ added << "%s\n#{FILE_INDENT}-> #{verb} from %s@r%s" % [
275
+ pathname,
276
+ path['copyfrom-path'],
277
+ path['copyfrom-rev'],
278
+ ]
279
+ else
280
+ added << "%s (new)" % [ pathname ]
281
+ end
282
+
283
+ when 'M'
284
+ changed << pathname
285
+
286
+ when 'D'
287
+ deleted << pathname
288
+
289
+ else
290
+ log "Unknown action %p in rev %d" % [ path['action'], entry['revision'] ]
291
+ end
292
+
293
+ end
294
+
295
+ date = Time.parse( entry.find_first('date').content )
296
+
297
+ # cvs2svn doesn't set 'author'
298
+ author = 'unknown'
299
+ if entry.find_first( 'author' )
300
+ author = entry.find_first( 'author' ).content
301
+ end
302
+
303
+ msg = entry.find_first( 'msg' ).content
304
+ rev = entry['revision']
305
+
306
+ changelog << "-- #{date.rfc2822} by #{author} (r#{rev}) -----\n"
307
+ changelog << " Added: " << humanize_file_list(added) << "\n" unless added.empty?
308
+ changelog << " Changed: " << humanize_file_list(changed) << "\n" unless changed.empty?
309
+ changelog << " Deleted: " << humanize_file_list(deleted) << "\n" unless deleted.empty?
310
+ changelog << "\n"
311
+
312
+ indent = msg[/^(\s*)/] + LOG_INDENT
313
+
314
+ changelog << indent << msg.strip.gsub(/\n\s*/m, "\n#{indent}")
315
+ changelog << "\n\n\n"
316
+ end
317
+
318
+ return changelog
319
+ end
320
+
321
+
322
+ ### Returns a human-scannable file list by joining and truncating the list if it's too long.
323
+ def humanize_file_list( list, indent=FILE_INDENT )
324
+ listtext = list[0..5].join( "\n#{indent}" )
325
+ if list.length > 5
326
+ listtext << " (and %d other/s)" % [ list.length - 5 ]
327
+ end
328
+
329
+ return listtext
330
+ end
331
+
332
+
333
+ ### Add the list of +pathnames+ to the svn:ignore list.
334
+ def svn_ignore_files( *pathnames )
335
+ pathnames.flatten!
336
+
337
+ map = pathnames.inject({}) do |map,path|
338
+ map[ path.dirname ] ||= []
339
+ map[ path.dirname ] << path.basename
340
+ map
341
+ end
342
+
343
+ trace "Ignoring %d files in %d directories." % [ pathnames.length, map.length ]
344
+
345
+ map.each do |dir, files|
346
+ trace " %s: %p" % [ dir, files ]
347
+ io = open( '|-' ) or exec 'svn', 'pg', 'svn:ignore', dir
348
+ ignorelist = io.read.strip
349
+ ignorelist << "\n" << files.join("\n")
350
+ system 'svn', 'ps', 'svn:ignore', ignorelist, dir
351
+ end
352
+ end
353
+
354
+
355
+ ### Delete the files in the given +filelist+ after confirming with the user.
356
+ def delete_extra_files( filelist )
357
+ description = humanize_file_list( filelist, ' ' )
358
+ log "Files to delete:\n ", description
359
+ ask_for_confirmation( "Really delete them?", false ) do
360
+ filelist.each do |f|
361
+ rm_rf( f, :verbose => true )
362
+ end
363
+ end
364
+ end
365
+
366
+
367
+
368
+ ###
369
+ ### Tasks
370
+ ###
371
+
372
+ desc "Subversion tasks"
373
+ namespace :svn do
374
+
375
+ desc "Copy the HEAD revision of the current #{SVN_TRUNK_DIR}/ to #{SVN_TAGS_DIR}/ with a " +
376
+ "current timestamp."
377
+ task :tag do
378
+ svninfo = get_svn_info()
379
+ tag = make_new_tag()
380
+ svntrunk = svninfo['Repository Root'] + "/#{SVN_TRUNK_DIR}"
381
+ svntagdir = svninfo['Repository Root'] + "/#{SVN_TAGS_DIR}"
382
+ svntag = svntagdir + '/' + tag
383
+
384
+ desc = "Tagging trunk as #{svntag}"
385
+ ask_for_confirmation( desc ) do
386
+ msg = prompt_with_default( "Commit log: ", "Tagging for code push" )
387
+ run 'svn', 'cp', '-m', msg, svntrunk, svntag
388
+ end
389
+ end
390
+
391
+
392
+ desc "Copy the HEAD revision of the current #{SVN_TRUNK_DIR}/ to #{SVN_BRANCHES_DIR} with a " +
393
+ "user-specified name."
394
+ task :branch, [:name] do |task, args|
395
+ unless args.name
396
+ args.name = prompt( "Branch name" ) or abort
397
+ end
398
+
399
+ svninfo = get_svn_info()
400
+ svntrunk = Pathname.new( svninfo['Repository Root'] ) + SVN_TRUNK_DIR
401
+ svnbranchdir = Pathname.new( svninfo['Repository Root'] ) + SVN_BRANCHES_DIR
402
+ svnbranch = svnbranchdir + args.name
403
+
404
+ desc = "Making a new branch: #{svnbranch}"
405
+ ask_for_confirmation( desc ) do
406
+ msg = prompt_with_default( "Commit log: ", "Making a '#{args.name}' branch" )
407
+ run 'svn', 'cp', '-m', msg, svntrunk, svnbranch
408
+ ask_for_confirmation( "Switch to the new branch?", false ) do
409
+ run 'svn', 'sw', svnbranch
410
+ end
411
+ end
412
+ end
413
+
414
+
415
+ desc "Switch the working copy to the named branch"
416
+ task :switch, [:name] do |task, args|
417
+ branches = get_branch_names().collect {|name| name.chomp('/') }
418
+
419
+ unless args.name
420
+ log "Branches are:\n" + branches.collect {|br| " #{br}" }.join( "\n" )
421
+
422
+ begin
423
+ oldproc = Readline.completion_proc
424
+ abbrev = branches.abbrev
425
+ Readline.completion_proc = lambda{|string| abbrev[string] }
426
+
427
+ name = prompt( "Branch to switch to" ) or abort
428
+ args.with_defaults( :name => name )
429
+ ensure
430
+ Readline.completion_proc = oldproc unless oldproc.nil?
431
+ end
432
+ end
433
+
434
+ svninfo = get_svn_info()
435
+ abort "Branch '#{args.name}' does not exist" unless branches.include?( args.name )
436
+ branchuri = Pathname.new( svninfo['Repository Root'] ) + SVN_BRANCHES_DIR + args.name
437
+ run 'svn', 'sw', branchuri
438
+ end
439
+ task :sw => :switch
440
+
441
+
442
+ desc "Switch to the trunk if the working copy isn't there already."
443
+ task :trunk do
444
+ svninfo = get_svn_info()
445
+ svntrunk = Pathname.new( svninfo['Repository Root'] ) + SVN_TRUNK_DIR
446
+
447
+ if svninfo['URL'] != svntrunk.to_s
448
+ log "Switching to #{svntrunk}"
449
+ run 'svn', 'sw', svntrunk
450
+ else
451
+ log "You are already on trunk (#{svntrunk})"
452
+ end
453
+ end
454
+
455
+
456
+ desc "Copy the most recent tag to #{SVN_RELEASES_DIR}/#{PKG_VERSION}"
457
+ task :release do
458
+ last_tag = get_latest_svn_timestamp_tag()
459
+ svninfo = get_svn_info()
460
+ svnroot = Pathname.new( svninfo['Repository Root'] )
461
+ svntrunk = svnroot + SVN_TRUNK_DIR
462
+ svnrel = svnroot + SVN_RELEASES_DIR
463
+ release = PKG_VERSION
464
+ svnrelease = svnrel + release
465
+
466
+ unless svn_ls( svnrel.dirname ).include?( svnrel.basename.to_s + '/' )
467
+ log "Releases path #{svnrel} does not exist."
468
+ ask_for_confirmation( "To continue I'll need to create it." ) do
469
+ run 'svn', 'mkdir', svnrel, '-m', 'Creating releases/ directory'
470
+ end
471
+ else
472
+ trace "Found release dir #{svnrel}"
473
+ end
474
+
475
+ releases = svn_ls( svnrel ).collect {|name| name.sub(%r{/$}, '') }
476
+ trace "Releases: %p" % [releases]
477
+ if releases.include?( release )
478
+ error "Version #{release} already has a branch (#{svnrelease}). Did you mean " +
479
+ "to increment the version in #{VERSION_FILE}?"
480
+ fail
481
+ else
482
+ trace "No #{release} version currently exists"
483
+ end
484
+
485
+ desc = "Tagging trunk as #{svnrelease}..."
486
+ ask_for_confirmation( desc ) do
487
+ msg = prompt_with_default( "Commit log: ", "Branching for release" )
488
+ run 'svn', 'cp', '-m', msg, svntrunk, svnrelease
489
+ end
490
+ end
491
+
492
+ ### Task for debugging the #get_target_args helper
493
+ task :show_targets do
494
+ $stdout.puts "Targets from ARGV (%p): %p" % [ARGV, get_target_args()]
495
+ end
496
+
497
+
498
+ desc "Generate a commit log"
499
+ task :commitlog => [COMMIT_MSG_FILE]
500
+
501
+ desc "Show the (pre-edited) commit log for the current directory"
502
+ task :show_commitlog do
503
+ puts make_svn_commit_log()
504
+ end
505
+
506
+
507
+ file COMMIT_MSG_FILE do
508
+ diff = make_svn_commit_log()
509
+
510
+ File.open( COMMIT_MSG_FILE, File::WRONLY|File::EXCL|File::CREAT ) do |fh|
511
+ fh.print( diff )
512
+ end
513
+
514
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
515
+ system editor, COMMIT_MSG_FILE
516
+ unless $?.success?
517
+ fail "Editor exited uncleanly."
518
+ end
519
+ end
520
+
521
+
522
+ desc "Update from Subversion"
523
+ task :update do
524
+ run 'svn', 'up', '--ignore-externals'
525
+ end
526
+
527
+
528
+ desc "Add/ignore any files that are unknown in the working copy"
529
+ task :newfiles do
530
+ log "Checking for new files..."
531
+ entries = get_svn_status()
532
+
533
+ unless entries.empty?
534
+ files_to_add = []
535
+ files_to_ignore = []
536
+ files_to_delete = []
537
+
538
+ entries.find_all {|entry| entry[0] == '?'}.each do |entry|
539
+ action = prompt_with_default( " #{entry[1]}: (a)dd, (i)gnore, (s)kip (d)elete", 's' )
540
+ case action
541
+ when 'a'
542
+ files_to_add << entry[1]
543
+ when 'i'
544
+ files_to_ignore << entry[1]
545
+ when 'd'
546
+ files_to_delete << entry[1]
547
+ end
548
+ end
549
+
550
+ unless files_to_add.empty?
551
+ run 'svn', 'add', *files_to_add
552
+ end
553
+
554
+ unless files_to_ignore.empty?
555
+ svn_ignore_files( *files_to_ignore )
556
+ end
557
+
558
+ unless files_to_delete.empty?
559
+ delete_extra_files( files_to_delete )
560
+ end
561
+ end
562
+ end
563
+ task :add => :newfiles
564
+
565
+
566
+ desc "Check in all the changes in your current working copy"
567
+ task :checkin => ['svn:update', 'svn:newfiles', 'test', 'svn:fix_keywords', COMMIT_MSG_FILE] do
568
+ targets = get_target_args()
569
+ $stderr.puts '---', File.read( COMMIT_MSG_FILE ), '---'
570
+ ask_for_confirmation( "Continue with checkin?" ) do
571
+ run 'svn', 'ci', '-F', COMMIT_MSG_FILE, targets
572
+ rm_f COMMIT_MSG_FILE
573
+ end
574
+ end
575
+ task :commit => :checkin
576
+ task :ci => :checkin
577
+
578
+
579
+ task :clean do
580
+ rm_f COMMIT_MSG_FILE
581
+ end
582
+
583
+
584
+ desc "Check and fix any missing keywords for any files in the project which need them"
585
+ task :fix_keywords do
586
+ log "Checking subversion keywords..."
587
+ paths = get_svn_filelist( BASEDIR ).
588
+ select {|path| path.file? && path.to_s =~ KEYWORDED_FILEPATTERN }
589
+
590
+ trace "Looking at %d paths for keywords:\n %p" % [paths.length, paths]
591
+ kwmap = get_svn_keyword_map( paths )
592
+
593
+ buf = ''
594
+ PP.pp( kwmap, buf, 132 )
595
+ trace "keyword map is: %s" % [buf]
596
+
597
+ files_needing_fixups = paths.find_all do |path|
598
+ (kwmap[path.to_s] & DEFAULT_KEYWORDS) != DEFAULT_KEYWORDS
599
+ end
600
+
601
+ unless files_needing_fixups.empty?
602
+ $stderr.puts "Files needing keyword fixes: ",
603
+ files_needing_fixups.collect {|f|
604
+ " %s: %s" % [f, kwmap[f] ? kwmap[f].join(' ') : "(no keywords)"]
605
+ }
606
+ ask_for_confirmation( "Will add default keywords to these files." ) do
607
+ run 'svn', 'ps', 'svn:keywords', DEFAULT_KEYWORDS.join(' '), *files_needing_fixups
608
+ end
609
+ else
610
+ log "Keywords are all up to date."
611
+ end
612
+ end
613
+
614
+
615
+ task :debug_helpers do
616
+ methods = [
617
+ :get_last_changed_rev,
618
+ :get_latest_release_tag,
619
+ :get_latest_svn_timestamp_tag,
620
+ :get_svn_diff,
621
+ :get_svn_filelist,
622
+ :get_svn_info,
623
+ :get_svn_keyword_map,
624
+ :get_svn_path,
625
+ :get_svn_repo_root,
626
+ :get_svn_rev,
627
+ :get_svn_status,
628
+ :get_svn_url,
629
+ :svn_ls,
630
+ ]
631
+ maxlen = methods.collect {|sym| sym.to_s.length }.max
632
+
633
+ methods.each do |meth|
634
+ res = send( meth )
635
+ puts "%*s => %p" % [ maxlen, colorize(meth.to_s, :cyan), res ]
636
+ end
637
+ end
638
+ end
639
+