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,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
+