bluecloth 2.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.
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,28 @@
1
+ #include "config.h"
2
+
3
+ char markdown_version[] = VERSION
4
+ #if DL_TAG_EXTENSION
5
+ " DL_TAG"
6
+ #endif
7
+ #if PANDOC_HEADER
8
+ " HEADER"
9
+ #endif
10
+ #if 4 != 4
11
+ " TAB=4"
12
+ #endif
13
+ #if USE_AMALLOC
14
+ " DEBUG"
15
+ #endif
16
+ #if SUPERSCRIPT
17
+ " SUPERSCRIPT"
18
+ #endif
19
+ #if RELAXED_EMPHASIS
20
+ " RELAXED"
21
+ #endif
22
+ #if DIV_QUOTE
23
+ " DIV"
24
+ #endif
25
+ #if ALPHA_LIST
26
+ " AL"
27
+ #endif
28
+ ;
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/ruby
2
+
3
+ #
4
+ # Bluecloth is a Ruby implementation of Markdown, a text-to-HTML conversion
5
+ # tool.
6
+ #
7
+ # == Authors
8
+ #
9
+ # * Michael Granger <ged@FaerieMUD.org>
10
+ #
11
+ # == Contributors
12
+ #
13
+ # * Martin Chase <stillflame@FaerieMUD.org> - Peer review, helpful suggestions
14
+ # * Florian Gross <flgr@ccan.de> - Filter options, suggestions
15
+ #
16
+ # This product includes software developed by David Loren Parsons
17
+ # <http://www.pell.portland.or.us/~orc>.
18
+ #
19
+ # == Version
20
+ #
21
+ # $Id: bluecloth.rb 107 2009-03-13 22:56:29Z deveiant $
22
+ #
23
+ # == License
24
+ #
25
+ # :include: LICENSE
26
+ #--
27
+ # Please see the LICENSE file included in the distribution for copyright and licensing details.
28
+ #
29
+ class BlueCloth
30
+
31
+ # Release Version
32
+ VERSION = '2.0.0'
33
+
34
+ # SVN Revision
35
+ SVNREV = %q$Rev: 107 $
36
+
37
+ # SVN Id tag
38
+ SVNID = %q$Id: bluecloth.rb 107 2009-03-13 22:56:29Z deveiant $
39
+
40
+ # The defaults for all supported options.
41
+ DEFAULT_OPTIONS = {
42
+ :remove_links => false,
43
+ :remove_images => false,
44
+ :smartypants => true,
45
+ :pseudoprotocols => false,
46
+ :pandoc_headers => false,
47
+ :header_labels => false,
48
+ :escape_html => false,
49
+ :strict_mode => true,
50
+ }.freeze
51
+
52
+ # The number of characters of the original markdown source to include in the
53
+ # output of #inspect
54
+ INSPECT_TEXT_LENGTH = 50
55
+
56
+
57
+ #################################################################
58
+ ### C L A S S M E T H O D S
59
+ #################################################################
60
+
61
+ ### Convert the specified +opthash+ into a flags bitmask. If it's already a
62
+ ### Fixnum (e.g., if someone passed in an ORed flags argument instead of an
63
+ ### opthash), just return it as-is.
64
+ def self::flags_from_opthash( opthash={} )
65
+ return opthash if opthash.is_a?( Integer )
66
+
67
+ # Support BlueCloth1-style options
68
+ if opthash == :filter_html || opthash == [:filter_html]
69
+ opthash = { :escape_html => true }
70
+ elsif opthash == :filter_styles
71
+ opthash = {}
72
+ elsif !opthash.is_a?( Hash )
73
+ raise ArgumentError, "option %p not supported" % [ opthash ]
74
+ end
75
+
76
+ flags = 0
77
+
78
+ if opthash[:remove_links] then flags |= MKD_NOLINKS; end
79
+ if opthash[:remove_images] then flags |= MKD_NOIMAGE; end
80
+ if ! opthash[:smartypants] then flags |= MKD_NOPANTS; end
81
+ if ! opthash[:pseudoprotocols] then flags |= MKD_NO_EXT; end
82
+ if ! opthash[:pandoc_headers] then flags |= MKD_NOHEADER; end
83
+ if opthash[:header_labels] then flags |= MKD_TOC; end
84
+ if opthash[:mdtest_1_compat] then flags |= MKD_1_COMPAT; end
85
+ if opthash[:escape_html] then flags |= MKD_NOHTML; end
86
+ if opthash[:strict_mode] then flags |= MKD_STRICT; end
87
+
88
+ return flags
89
+ end
90
+
91
+
92
+ ### Returns a Hash that reflects the settings from the specified +flags+ Integer.
93
+ def self::opthash_from_flags( flags=0 )
94
+ flags = flags.to_i
95
+
96
+ opthash = {}
97
+ if ( flags & MKD_NOLINKS ).nonzero? then opthash[:remove_links] = true; end
98
+ if ( flags & MKD_NOIMAGE ).nonzero? then opthash[:remove_images] = true; end
99
+ if !( flags & MKD_NOPANTS ).nonzero? then opthash[:smartypants] = true; end
100
+ if !( flags & MKD_NO_EXT ).nonzero? then opthash[:pseudoprotocols] = true; end
101
+ if !( flags & MKD_NOHEADER ).nonzero? then opthash[:pandoc_headers] = true; end
102
+ if ( flags & MKD_TOC ).nonzero? then opthash[:header_labels] = true; end
103
+ if ( flags & MKD_1_COMPAT ).nonzero? then opthash[:mdtest_1_compat] = true; end
104
+ if ( flags & MKD_NOHTML ).nonzero? then opthash[:escape_html] = true; end
105
+ if ( flags & MKD_STRICT ).nonzero? then opthash[:strict_mode] = true; end
106
+
107
+ return opthash
108
+ end
109
+
110
+
111
+ #################################################################
112
+ ### I N S T A N C E M E T H O D S
113
+ #################################################################
114
+
115
+ ### Return a human-readable representation of the object suitable for debugging.
116
+ def inspect
117
+ return "#<%s:0x%x text: %p; options: %p>" % [
118
+ self.class.name,
119
+ self.object_id / 2,
120
+ self.text.length > INSPECT_TEXT_LENGTH ?
121
+ self.text[ 0, INSPECT_TEXT_LENGTH - 5] + '[...]' :
122
+ self.text,
123
+ self.options,
124
+ ]
125
+ end
126
+
127
+
128
+ ### Backward-compatible method: return +true+ if the object's :escape_html option was
129
+ ### set.
130
+ def filter_html
131
+ return self.options[:escape_html]
132
+ end
133
+
134
+
135
+ ### Backward-compatible method: raises an appropriate error notifying the user that
136
+ ### BlueCloth2 doesn't support this option.
137
+ def filter_html=( arg )
138
+ raise NotImplementedError,
139
+ "Sorry, this version of BlueCloth no longer supports toggling of HTML filtering" +
140
+ "via #filter_html=. You now must create the BlueCloth object with the :escape_html" +
141
+ "option set to true instead."
142
+ end
143
+
144
+
145
+
146
+ end # class BlueCloth
147
+
148
+ require 'bluecloth_ext'
@@ -0,0 +1,26 @@
1
+ # 1.9.1 fixes
2
+
3
+
4
+ # Make Pathname compatible with 1.8.7 Pathname.
5
+ unless Pathname.instance_methods.include?( :=~ )
6
+ class Pathname
7
+ def self::glob( *args ) # :yield: p
8
+ args = args.collect {|p| p.to_s }
9
+ if block_given?
10
+ Dir.glob(*args) {|f| yield self.new(f) }
11
+ else
12
+ Dir.glob(*args).map {|f| self.new(f) }
13
+ end
14
+ end
15
+
16
+ def =~( other )
17
+ self.to_s =~ other
18
+ end
19
+
20
+ def to_str
21
+ self.to_s
22
+ end
23
+ end
24
+ end
25
+
26
+
@@ -0,0 +1,76 @@
1
+ #
2
+ # Dependency-checking and Installation Rake Tasks
3
+ # $Id: dependencies.rb 43 2008-09-05 18:19:16Z deveiant $
4
+ #
5
+
6
+ require 'rubygems/dependency_installer'
7
+ require 'rubygems/source_index'
8
+ require 'rubygems/requirement'
9
+ require 'rubygems/doc_manager'
10
+
11
+ ### Install the specified +gems+ if they aren't already installed.
12
+ def install_gems( gems )
13
+
14
+ defaults = Gem::DependencyInstaller::DEFAULT_OPTIONS.merge({
15
+ :generate_rdoc => true,
16
+ :generate_ri => true,
17
+ :install_dir => Gem.dir,
18
+ :format_executable => false,
19
+ :test => false,
20
+ :version => Gem::Requirement.default,
21
+ })
22
+
23
+ # Check for root
24
+ if Process.euid != 0
25
+ $stderr.puts "This probably won't work, as you aren't root, but I'll try anyway"
26
+ end
27
+
28
+ gemindex = Gem::SourceIndex.from_installed_gems
29
+
30
+ gems.each do |gemname, reqstring|
31
+ requirement = Gem::Requirement.new( reqstring )
32
+ trace "requirement is: %p" % [ requirement ]
33
+
34
+ trace "Searching for an installed #{gemname}..."
35
+ specs = gemindex.find_name( gemname )
36
+ trace "...found %d specs: %s" %
37
+ [ specs.length, specs.collect {|s| "%s %s" % [s.name, s.version] }.join(', ') ]
38
+
39
+ if spec = specs.find {|spec| requirement.satisfied_by?(spec.version) }
40
+ log "Version %s of %s is already installed (needs %s); skipping..." %
41
+ [ spec.version, spec.name, requirement ]
42
+ next
43
+ end
44
+
45
+ rgv = Gem::Version.new( Gem::RubyGemsVersion )
46
+ installer = nil
47
+
48
+ log "Trying to install #{gemname.inspect} #{requirement}..."
49
+ if rgv >= Gem::Version.new( '1.1.1' )
50
+ installer = Gem::DependencyInstaller.new
51
+ installer.install( gemname, requirement )
52
+ else
53
+ installer = Gem::DependencyInstaller.new( gemname )
54
+ installer.install
55
+ end
56
+
57
+ installer.installed_gems.each do |spec|
58
+ log "Installed: %s" % [ spec.full_name ]
59
+ end
60
+
61
+ end
62
+ end
63
+
64
+
65
+ ### Task: install runtime dependencies
66
+ desc "Install runtime dependencies as gems"
67
+ task :install_dependencies do
68
+ install_gems( DEPENDENCIES )
69
+ end
70
+
71
+ ### Task: install gems for development tasks
72
+ desc "Install development dependencies as gems"
73
+ task :install_dev_dependencies do
74
+ install_gems( DEVELOPMENT_DEPENDENCIES )
75
+ end
76
+
@@ -0,0 +1,412 @@
1
+ # encoding: utf-8
2
+ #####################################################################
3
+ ### G L O B A L H E L P E R F U N C T I O N S
4
+ #####################################################################
5
+
6
+
7
+ require 'pathname'
8
+ require 'uri'
9
+ require 'open3'
10
+
11
+ begin
12
+ require 'readline'
13
+ include Readline
14
+ rescue LoadError
15
+ # Fall back to a plain prompt
16
+ def readline( text )
17
+ $stderr.print( text.chomp )
18
+ return $stdin.gets
19
+ end
20
+ end
21
+
22
+ # Set some ANSI escape code constants (Shamelessly stolen from Perl's
23
+ # Term::ANSIColor by Russ Allbery <rra@stanford.edu> and Zenin <zenin@best.com>
24
+ ANSI_ATTRIBUTES = {
25
+ 'clear' => 0,
26
+ 'reset' => 0,
27
+ 'bold' => 1,
28
+ 'dark' => 2,
29
+ 'underline' => 4,
30
+ 'underscore' => 4,
31
+ 'blink' => 5,
32
+ 'reverse' => 7,
33
+ 'concealed' => 8,
34
+
35
+ 'black' => 30, 'on_black' => 40,
36
+ 'red' => 31, 'on_red' => 41,
37
+ 'green' => 32, 'on_green' => 42,
38
+ 'yellow' => 33, 'on_yellow' => 43,
39
+ 'blue' => 34, 'on_blue' => 44,
40
+ 'magenta' => 35, 'on_magenta' => 45,
41
+ 'cyan' => 36, 'on_cyan' => 46,
42
+ 'white' => 37, 'on_white' => 47
43
+ }
44
+
45
+
46
+ MULTILINE_PROMPT = <<-'EOF'
47
+ Enter one or more values for '%s'.
48
+ A blank line finishes input.
49
+ EOF
50
+
51
+
52
+ CLEAR_TO_EOL = "\e[K"
53
+ CLEAR_CURRENT_LINE = "\e[2K"
54
+
55
+
56
+ ### Output a logging message
57
+ def log( *msg )
58
+ output = colorize( msg.flatten.join(' '), 'cyan' )
59
+ $stderr.puts( output )
60
+ end
61
+
62
+
63
+ ### Output a logging message if tracing is on
64
+ def trace( *msg )
65
+ return unless $trace
66
+ output = colorize( msg.flatten.join(' '), 'yellow' )
67
+ $stderr.puts( output )
68
+ end
69
+
70
+
71
+ ### Run the specified command +cmd+ with system(), failing if the execution
72
+ ### fails.
73
+ def run( *cmd )
74
+ cmd.flatten!
75
+
76
+ if cmd.length > 1
77
+ trace( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
78
+ else
79
+ trace( cmd )
80
+ end
81
+
82
+ if $dryrun
83
+ $stderr.puts "(dry run mode)"
84
+ else
85
+ system( *cmd )
86
+ unless $?.success?
87
+ fail "Command failed: [%s]" % [cmd.join(' ')]
88
+ end
89
+ end
90
+ end
91
+
92
+
93
+ ### Run a subordinate Rake process with the same options and the specified +targets+.
94
+ def rake( *targets )
95
+ opts = ARGV.select {|arg| arg[0,1] == '-' }
96
+ args = opts + targets.map {|t| t.to_s }
97
+ run 'rake', '-N', *args
98
+ end
99
+
100
+
101
+ ### Open a pipe to a process running the given +cmd+ and call the given block with it.
102
+ def pipeto( *cmd )
103
+ $DEBUG = true
104
+
105
+ cmd.flatten!
106
+ log( "Opening a pipe to: ", cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
107
+ if $dryrun
108
+ $stderr.puts "(dry run mode)"
109
+ else
110
+ open( '|-', 'w+' ) do |io|
111
+
112
+ # Parent
113
+ if io
114
+ yield( io )
115
+
116
+ # Child
117
+ else
118
+ exec( *cmd )
119
+ fail "Command failed: [%s]" % [cmd.join(' ')]
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+
126
+ ### Download the file at +sourceuri+ via HTTP and write it to +targetfile+.
127
+ def download( sourceuri, targetfile=nil )
128
+ oldsync = $defout.sync
129
+ $defout.sync = true
130
+ require 'open-uri'
131
+
132
+ targetpath = Pathname.new( targetfile )
133
+
134
+ log "Downloading %s to %s" % [sourceuri, targetfile]
135
+ trace " connecting..."
136
+ ifh = open( sourceuri ) do |ifh|
137
+ trace " connected..."
138
+ targetpath.open( File::WRONLY|File::TRUNC|File::CREAT, 0644 ) do |ofh|
139
+ log "Downloading..."
140
+ buf = ''
141
+
142
+ while ifh.read( 16384, buf )
143
+ until buf.empty?
144
+ bytes = ofh.write( buf )
145
+ buf.slice!( 0, bytes )
146
+ end
147
+ end
148
+
149
+ log "Done."
150
+ end
151
+
152
+ end
153
+
154
+ return targetpath
155
+ ensure
156
+ $defout.sync = oldsync
157
+ end
158
+
159
+
160
+ ### Return the fully-qualified path to the specified +program+ in the PATH.
161
+ def which( program )
162
+ ENV['PATH'].split(/:/).
163
+ collect {|dir| Pathname.new(dir) + program }.
164
+ find {|path| path.exist? && path.executable? }
165
+ end
166
+
167
+
168
+ ### Create a string that contains the ANSI codes specified and return it
169
+ def ansi_code( *attributes )
170
+ attributes.flatten!
171
+ attributes.collect! {|at| at.to_s }
172
+ # $stderr.puts "Returning ansicode for TERM = %p: %p" %
173
+ # [ ENV['TERM'], attributes ]
174
+ return '' unless /(?:vt10[03]|xterm(?:-color)?|linux|screen)/i =~ ENV['TERM']
175
+ attributes = ANSI_ATTRIBUTES.values_at( *attributes ).compact.join(';')
176
+
177
+ # $stderr.puts " attr is: %p" % [attributes]
178
+ if attributes.empty?
179
+ return ''
180
+ else
181
+ return "\e[%sm" % attributes
182
+ end
183
+ end
184
+
185
+
186
+ ### Colorize the given +string+ with the specified +attributes+ and return it, handling
187
+ ### line-endings, color reset, etc.
188
+ def colorize( *args )
189
+ string = ''
190
+
191
+ if block_given?
192
+ string = yield
193
+ else
194
+ string = args.shift
195
+ end
196
+
197
+ ending = string[/(\s)$/] || ''
198
+ string = string.rstrip
199
+
200
+ return ansi_code( args.flatten ) + string + ansi_code( 'reset' ) + ending
201
+ end
202
+
203
+
204
+ ### Output the specified <tt>msg</tt> as an ANSI-colored error message
205
+ ### (white on red).
206
+ def error_message( msg, details='' )
207
+ $stderr.puts colorize( 'bold', 'white', 'on_red' ) { msg } + details
208
+ end
209
+ alias :error :error_message
210
+
211
+
212
+ ### Highlight and embed a prompt control character in the given +string+ and return it.
213
+ def make_prompt_string( string )
214
+ return CLEAR_CURRENT_LINE + colorize( 'bold', 'green' ) { string + ' ' }
215
+ end
216
+
217
+
218
+ ### Output the specified <tt>prompt_string</tt> as a prompt (in green) and
219
+ ### return the user's input with leading and trailing spaces removed. If a
220
+ ### test is provided, the prompt will repeat until the test returns true.
221
+ ### An optional failure message can also be passed in.
222
+ def prompt( prompt_string, failure_msg="Try again." ) # :yields: response
223
+ prompt_string.chomp!
224
+ prompt_string << ":" unless /\W$/.match( prompt_string )
225
+ response = nil
226
+
227
+ begin
228
+ prompt = make_prompt_string( prompt_string )
229
+ response = readline( prompt ) || ''
230
+ response.strip!
231
+ if block_given? && ! yield( response )
232
+ error_message( failure_msg + "\n\n" )
233
+ response = nil
234
+ end
235
+ end while response.nil?
236
+
237
+ return response
238
+ end
239
+
240
+
241
+ ### Prompt the user with the given <tt>prompt_string</tt> via #prompt,
242
+ ### substituting the given <tt>default</tt> if the user doesn't input
243
+ ### anything. If a test is provided, the prompt will repeat until the test
244
+ ### returns true. An optional failure message can also be passed in.
245
+ def prompt_with_default( prompt_string, default, failure_msg="Try again." )
246
+ response = nil
247
+
248
+ begin
249
+ default ||= '~'
250
+ response = prompt( "%s [%s]" % [ prompt_string, default ] )
251
+ response = default.to_s if !response.nil? && response.empty?
252
+
253
+ trace "Validating response %p" % [ response ]
254
+
255
+ # the block is a validator. We need to make sure that the user didn't
256
+ # enter '~', because if they did, it's nil and we should move on. If
257
+ # they didn't, then call the block.
258
+ if block_given? && response != '~' && ! yield( response )
259
+ error_message( failure_msg + "\n\n" )
260
+ response = nil
261
+ end
262
+ end while response.nil?
263
+
264
+ return nil if response == '~'
265
+ return response
266
+ end
267
+
268
+
269
+ ### Prompt for an array of values
270
+ def prompt_for_multiple_values( label, default=nil )
271
+ $stderr.puts( MULTILINE_PROMPT % [label] )
272
+ if default
273
+ $stderr.puts "Enter a single blank line to keep the default:\n %p" % [ default ]
274
+ end
275
+
276
+ results = []
277
+ result = nil
278
+
279
+ begin
280
+ result = readline( make_prompt_string("> ") )
281
+ if result.nil? || result.empty?
282
+ results << default if default && results.empty?
283
+ else
284
+ results << result
285
+ end
286
+ end until result.nil? || result.empty?
287
+
288
+ return results.flatten
289
+ end
290
+
291
+
292
+ ### Turn echo and masking of input on/off.
293
+ def noecho( masked=false )
294
+ require 'termios'
295
+
296
+ rval = nil
297
+ term = Termios.getattr( $stdin )
298
+
299
+ begin
300
+ newt = term.dup
301
+ newt.c_lflag &= ~Termios::ECHO
302
+ newt.c_lflag &= ~Termios::ICANON if masked
303
+
304
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, newt )
305
+
306
+ rval = yield
307
+ ensure
308
+ Termios.tcsetattr( $stdin, Termios::TCSANOW, term )
309
+ end
310
+
311
+ return rval
312
+ end
313
+
314
+
315
+ ### Prompt the user for her password, turning off echo if the 'termios' module is
316
+ ### available.
317
+ def prompt_for_password( prompt="Password: " )
318
+ return noecho( true ) do
319
+ $stderr.print( prompt )
320
+ ($stdin.gets || '').chomp
321
+ end
322
+ end
323
+
324
+
325
+ ### Display a description of a potentially-dangerous task, and prompt
326
+ ### for confirmation. If the user answers with anything that begins
327
+ ### with 'y', yield to the block. If +abort_on_decline+ is +true+,
328
+ ### any non-'y' answer will fail with an error message.
329
+ def ask_for_confirmation( description, abort_on_decline=true )
330
+ puts description
331
+
332
+ answer = prompt_with_default( "Continue?", 'n' ) do |input|
333
+ input =~ /^[yn]/i
334
+ end
335
+
336
+ if answer =~ /^y/i
337
+ return yield
338
+ elsif abort_on_decline
339
+ error "Aborted."
340
+ fail
341
+ end
342
+
343
+ return false
344
+ end
345
+ alias :prompt_for_confirmation :ask_for_confirmation
346
+
347
+
348
+ ### Search line-by-line in the specified +file+ for the given +regexp+, returning the
349
+ ### first match, or nil if no match was found. If the +regexp+ has any capture groups,
350
+ ### those will be returned in an Array, else the whole matching line is returned.
351
+ def find_pattern_in_file( regexp, file )
352
+ rval = nil
353
+
354
+ File.open( file, 'r' ).each do |line|
355
+ if (( match = regexp.match(line) ))
356
+ rval = match.captures.empty? ? match[0] : match.captures
357
+ break
358
+ end
359
+ end
360
+
361
+ return rval
362
+ end
363
+
364
+
365
+ ### Search line-by-line in the output of the specified +cmd+ for the given +regexp+,
366
+ ### returning the first match, or nil if no match was found. If the +regexp+ has any
367
+ ### capture groups, those will be returned in an Array, else the whole matching line
368
+ ### is returned.
369
+ def find_pattern_in_pipe( regexp, *cmd )
370
+ output = []
371
+
372
+ log( cmd.collect {|part| part =~ /\s/ ? part.inspect : part} )
373
+ Open3.popen3( *cmd ) do |stdin, stdout, stderr|
374
+ stdin.close
375
+
376
+ output << stdout.gets until stdout.eof?
377
+ output << stderr.gets until stderr.eof?
378
+ end
379
+
380
+ result = output.find { |line| regexp.match(line) }
381
+ return $1 || result
382
+ end
383
+
384
+
385
+ ### Invoke the user's editor on the given +filename+ and return the exit code
386
+ ### from doing so.
387
+ def edit( filename )
388
+ editor = ENV['EDITOR'] || ENV['VISUAL'] || DEFAULT_EDITOR
389
+ system editor, filename
390
+ unless $?.success?
391
+ fail "Editor exited uncleanly."
392
+ end
393
+ end
394
+
395
+
396
+ ### Extract all the non Rake-target arguments from ARGV and return them.
397
+ def get_target_args
398
+ args = ARGV.reject {|arg| arg =~ /^-/ || Rake::Task.task_defined?(arg) }
399
+ return args
400
+ end
401
+
402
+
403
+ ### Log a subdirectory change, execute a block, and exit the subdirectory
404
+ def in_subdirectory( subdir )
405
+ block = Proc.new
406
+
407
+ log "Entering #{subdir}"
408
+ Dir.chdir( subdir, &block )
409
+ log "Leaving #{subdir}"
410
+ end
411
+
412
+