actionmailer 0.3.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionmailer might be problematic. Click here for more details.

Files changed (34) hide show
  1. data/CHANGELOG +3 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +102 -0
  4. data/install.rb +61 -0
  5. data/lib/action_mailer.rb +44 -0
  6. data/lib/action_mailer/base.rb +111 -0
  7. data/lib/action_mailer/mail_helper.rb +17 -0
  8. data/lib/action_mailer/vendor/text/format.rb +1447 -0
  9. data/lib/action_mailer/vendor/tmail.rb +4 -0
  10. data/lib/action_mailer/vendor/tmail/address.rb +223 -0
  11. data/lib/action_mailer/vendor/tmail/base64.rb +52 -0
  12. data/lib/action_mailer/vendor/tmail/config.rb +50 -0
  13. data/lib/action_mailer/vendor/tmail/encode.rb +447 -0
  14. data/lib/action_mailer/vendor/tmail/facade.rb +531 -0
  15. data/lib/action_mailer/vendor/tmail/header.rb +893 -0
  16. data/lib/action_mailer/vendor/tmail/info.rb +16 -0
  17. data/lib/action_mailer/vendor/tmail/loader.rb +1 -0
  18. data/lib/action_mailer/vendor/tmail/mail.rb +420 -0
  19. data/lib/action_mailer/vendor/tmail/mailbox.rb +414 -0
  20. data/lib/action_mailer/vendor/tmail/mbox.rb +1 -0
  21. data/lib/action_mailer/vendor/tmail/net.rb +261 -0
  22. data/lib/action_mailer/vendor/tmail/obsolete.rb +116 -0
  23. data/lib/action_mailer/vendor/tmail/parser.rb +1503 -0
  24. data/lib/action_mailer/vendor/tmail/port.rb +358 -0
  25. data/lib/action_mailer/vendor/tmail/scanner.rb +22 -0
  26. data/lib/action_mailer/vendor/tmail/scanner_r.rb +244 -0
  27. data/lib/action_mailer/vendor/tmail/stringio.rb +260 -0
  28. data/lib/action_mailer/vendor/tmail/tmail.rb +1 -0
  29. data/lib/action_mailer/vendor/tmail/utils.rb +215 -0
  30. data/rakefile +99 -0
  31. data/test/fixtures/templates/signed_up.rhtml +3 -0
  32. data/test/fixtures/test_mailer/signed_up.rhtml +3 -0
  33. data/test/mail_service_test.rb +53 -0
  34. metadata +86 -0
@@ -0,0 +1,3 @@
1
+ *0.3*
2
+
3
+ * First release
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2004 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
data/README ADDED
@@ -0,0 +1,102 @@
1
+ = Action Mailer -- Easy email delivery and testing
2
+
3
+ Action Mailer is framework for designing email-service layers. These layers
4
+ are used to consolidate code for sending out forgotten passwords, welcoming
5
+ wishes on signup, invoices for billing, and any other use case that requires
6
+ a written notification to either a person or another system.
7
+
8
+ The framework works by setting up all the email details, except the body,
9
+ in methods on the service layer. Subject, recipients, sender, and timestamp
10
+ are all set up this way. An example of such a method:
11
+
12
+ def signed_up(recipient)
13
+ @recipients = recipient
14
+ @subject = "[Signed up] Welcome #{recipient}"
15
+ @from = "system@loudthinking.com"
16
+ @sent_on = Time.local(2004, 12, 12)
17
+
18
+ @body["recipient"] = recipient
19
+ end
20
+
21
+ The body of the email is created by using an Action View template (regular
22
+ ERb) that has the content of the @body hash available as instance variables.
23
+ So the corresponding body template for the method above could look like this:
24
+
25
+ Hello there,
26
+
27
+ Mr. <%= @recipient %>
28
+
29
+ And if the recipient was given as "david@loudthinking.com", the email
30
+ generated would look like this:
31
+
32
+ Date: Sun, 12 Dec 2004 00:00:00 +0100
33
+ From: system@loudthinking.com
34
+ To: david@loudthinking.com
35
+ Subject: [Signed up] Welcome david@loudthinking.com
36
+
37
+ Hello there,
38
+
39
+ Mr. david@loudthinking.com
40
+
41
+ You never actually call the instance methods like signed_up directly. Instead,
42
+ you call class methods like deliver_* and create_* that are automatically
43
+ created for each instance method. So if the signed_up method sat on
44
+ ApplicationMailer, it would look like this:
45
+
46
+ ApplicationMailer.create_signed_up("david@loudthinking.com") # => tmail object for testing
47
+ ApplicationMailer.deliver_signed_up("david@loudthinking.com") # sends the email
48
+ ApplicationMailer.new.signed_up("david@loudthinking.com") # won't work!
49
+
50
+
51
+ == Dependencies
52
+
53
+ Action Mailer requires that the Action Pack is either available to be required immediately
54
+ or is accessible as a GEM.
55
+
56
+
57
+ == Bundled software
58
+
59
+ * tmail 0.10.8 by Minero Aoki released under LGPL
60
+ Read more on http://i.loveruby.net/en/prog/tmail.html
61
+
62
+ * Text::Format 0.63 by Austin Ziegler released under OpenSource
63
+ Read more on http://www.halostatue.ca/ruby/Text__Format.html
64
+
65
+
66
+ == Download
67
+
68
+ The latest version of Action Mailer can be found at
69
+
70
+ * http://rubyforge.org/project/showfiles.php?group_id=361
71
+
72
+ Documentation can be found at
73
+
74
+ * http://actionmailer.rubyonrails.org
75
+
76
+
77
+ == Installation
78
+
79
+ You can install Action Mailer with the following command.
80
+
81
+ % [sudo] ruby install.rb
82
+
83
+ from its distribution directory.
84
+
85
+
86
+ == License
87
+
88
+ Action Mailer is released under the MIT license.
89
+
90
+
91
+ == Support
92
+
93
+ The Action Mailer homepage is http://actionmailer.rubyonrails.org. You can find
94
+ the Action Mailer RubyForge page at http://rubyforge.org/projects/actionmailer.
95
+ And as Jim from Rake says:
96
+
97
+ Feel free to submit commits or feature requests. If you send a patch,
98
+ remember to update the corresponding unit tests. If fact, I prefer
99
+ new feature to be submitted in the form of new unit tests.
100
+
101
+ For other information, feel free to ask on the ruby-talk mailing list (which
102
+ is mirrored to comp.lang.ruby) or contact mailto:david@loudthinking.com.
@@ -0,0 +1,61 @@
1
+ require 'rbconfig'
2
+ require 'find'
3
+ require 'ftools'
4
+
5
+ include Config
6
+
7
+ # this was adapted from rdoc's install.rb by ways of Log4r
8
+
9
+ $sitedir = CONFIG["sitelibdir"]
10
+ unless $sitedir
11
+ version = CONFIG["MAJOR"] + "." + CONFIG["MINOR"]
12
+ $libdir = File.join(CONFIG["libdir"], "ruby", version)
13
+ $sitedir = $:.find {|x| x =~ /site_ruby/ }
14
+ if !$sitedir
15
+ $sitedir = File.join($libdir, "site_ruby")
16
+ elsif $sitedir !~ Regexp.quote(version)
17
+ $sitedir = File.join($sitedir, version)
18
+ end
19
+ end
20
+
21
+ makedirs = %w{ action_mailer/vendor action_mailer/vendor/text action_mailer/vendor/tmail }
22
+ makedirs.each {|f| File::makedirs(File.join($sitedir, *f.split(/\//)))}
23
+
24
+ # deprecated files that should be removed
25
+ # deprecated = %w{ }
26
+
27
+ # files to install in library path
28
+ files = %w-
29
+ action_mailer.rb
30
+ action_mailer/base.rb
31
+ action_mailer/mail_helper.rb
32
+ action_mailer/vendor/text/format.rb
33
+ action_mailer/vendor/tmail.rb
34
+ action_mailer/vendor/tmail/address.rb
35
+ action_mailer/vendor/tmail/base64.rb
36
+ action_mailer/vendor/tmail/config.rb
37
+ action_mailer/vendor/tmail/encode.rb
38
+ action_mailer/vendor/tmail/facade.rb
39
+ action_mailer/vendor/tmail/header.rb
40
+ action_mailer/vendor/tmail/info.rb
41
+ action_mailer/vendor/tmail/loader.rb
42
+ action_mailer/vendor/tmail/mail.rb
43
+ action_mailer/vendor/tmail/mailbox.rb
44
+ action_mailer/vendor/tmail/mbox.rb
45
+ action_mailer/vendor/tmail/net.rb
46
+ action_mailer/vendor/tmail/obsolete.rb
47
+ action_mailer/vendor/tmail/parser.rb
48
+ action_mailer/vendor/tmail/port.rb
49
+ action_mailer/vendor/tmail/scanner.rb
50
+ action_mailer/vendor/tmail/scanner_r.rb
51
+ action_mailer/vendor/tmail/stringio.rb
52
+ action_mailer/vendor/tmail/tmail.rb
53
+ action_mailer/vendor/tmail/utils.rb
54
+ -
55
+
56
+ # the acual gruntwork
57
+ Dir.chdir("lib")
58
+ # File::safe_unlink *deprecated.collect{|f| File.join($sitedir, f.split(/\//))}
59
+ files.each {|f|
60
+ File::install(f, File.join($sitedir, *f.split(/\//)), 0644, true)
61
+ }
@@ -0,0 +1,44 @@
1
+ #--
2
+ # Copyright (c) 2004 David Heinemeier Hansson
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+ #++
23
+
24
+ begin
25
+ require 'action_controller'
26
+ rescue LoadError
27
+ # Action Pack is not already available, try RubyGems
28
+ require 'rubygems'
29
+ require_gem 'actionpack', '>= 0.9.0'
30
+ end
31
+
32
+ $:.unshift(File.dirname(__FILE__) + "/action_mailer/vendor/")
33
+
34
+ require 'action_mailer/base'
35
+ require 'action_mailer/mail_helper'
36
+ require 'action_mailer/vendor/tmail'
37
+ require 'net/smtp'
38
+
39
+ include TMail
40
+ ActionView::Base.class_eval { include MailHelper }
41
+
42
+ old_verbose, $VERBOSE = $VERBOSE, nil
43
+ TMail::Encoder.const_set("MAX_LINE_LEN", 200)
44
+ $VERBOSE = old_verbose
@@ -0,0 +1,111 @@
1
+ module ActionMailer #:nodoc:
2
+ # Usage:
3
+ #
4
+ # class ApplicationMailer < ActionMailer::Base
5
+ # def post_notification(recipients, post)
6
+ # @recipients = recipients
7
+ # @subject = "[#{post.account.name} #{post.title}]"
8
+ # @body["post"] = post
9
+ # @from = post.author.email_address_with_name
10
+ # end
11
+ #
12
+ # def comment_notification(recipient, comment)
13
+ # @recipients = recipient.email_address_with_name
14
+ # @subject = "[#{comment.post.project.client.firm.account.name}]" +
15
+ # " Re: #{comment.post.title}"
16
+ # @body["comment"] = comment
17
+ # @from = comment.author.email_address_with_name
18
+ # @sent_on = comment.posted_on
19
+ # end
20
+ # end
21
+ #
22
+ # # After this post_notification will look for "templates/application_mailer/post_notification.rhtml"
23
+ # ApplicationMailer.template_root = "templates"
24
+ #
25
+ # ApplicationMailer.create_comment_notification(david, hello_world) # => a tmail object
26
+ # ApplicationMailer.deliver_comment_notification(david, hello_world) # sends the email
27
+ class Base
28
+ private_class_method :new
29
+
30
+ # Template root determines the base from which template references will be made.
31
+ cattr_accessor :template_root
32
+
33
+ # The logger is used for generating information on the mailing run if available.
34
+ # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
35
+ cattr_accessor :logger
36
+
37
+ # Allows you to use a remote mail server. Just change it away from it's default "localhost" setting.
38
+ @@mail_server_address = "localhost"
39
+ cattr_accessor :mail_server_address
40
+
41
+ # On the off change that your mail server doesn't run on port 25, you can change it.
42
+ @@mail_server_port = 25
43
+ cattr_accessor :mail_server_port
44
+
45
+ # Whether or not errors should be raised if the email fails to be delivered
46
+ @@raise_delivery_errors = true
47
+ cattr_accessor :raise_delivery_errors
48
+
49
+ attr_accessor :recipients, :subject, :body, :from, :sent_on, :bcc, :cc
50
+
51
+ class << self
52
+ def method_missing(method_symbol, *parameters)#:nodoc:
53
+ case method_symbol.id2name
54
+ when /^create_([_a-z]*)/
55
+ create_from_action($1, *parameters)
56
+ when /^deliver_([_a-z]*)/
57
+ begin
58
+ deliver(send("create_" + $1, *parameters))
59
+ rescue Object => e
60
+ raise e if raise_delivery_errors
61
+ end
62
+ end
63
+ end
64
+
65
+ def mail(to, subject, body, from, timestamp = nil)#:nodoc:
66
+ deliver(create(to, subject, body, from, timestamp))
67
+ end
68
+
69
+ def create(to, subject, body, from, timestamp = nil)#:nodoc:
70
+ m = Mail.new
71
+ m.to, m.subject, m.body, m.from = to, subject, body, from
72
+ m.date = timestamp.respond_to?("to_time") ? timestamp.to_time : (timestamp || Time.now)
73
+ return m
74
+ end
75
+
76
+ def deliver(mail)#:nodoc:
77
+ logger.info "Sent mail:\n #{mail.encoded}" unless logger.nil?
78
+
79
+ Net::SMTP.start(mail_server_address, mail_server_port) do |smtp|
80
+ smtp.sendmail(mail.encoded, mail.from_address, mail.destinations)
81
+ end
82
+ end
83
+
84
+ private
85
+ def create_from_action(method_name, *parameters)
86
+ mailer = new
87
+ mailer.body = {}
88
+ mailer.send(method_name, *parameters)
89
+
90
+ if String === mailer.body
91
+ mail = create(mailer.recipients, mailer.subject, mailer.body, mailer.from, mailer.sent_on)
92
+ else
93
+ mail = create(mailer.recipients, mailer.subject, render_body(mailer, method_name), mailer.from, mailer.sent_on)
94
+ end
95
+
96
+ mail.bcc = @bcc if @bcc
97
+ mail.cc = @cc if @cc
98
+
99
+ return mail
100
+ end
101
+
102
+ def render_body(mailer, method_name)
103
+ ActionView::Base.new(template_path, mailer.body).render_file(method_name)
104
+ end
105
+
106
+ def template_path
107
+ template_root + "/" + Inflector.underscore(self.to_s)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,17 @@
1
+ require 'action_mailer/vendor/text/format'
2
+
3
+ module MailHelper#:nodoc:
4
+ def block_format(text)
5
+ formatted = text.split(/\n\r\n/).collect { |paragraph|
6
+ Text::Format.new(
7
+ :columns => 72, :first_indent => 2, :body_indent => 2, :text => paragraph
8
+ ).format
9
+ }.join("\n")
10
+
11
+ # Make list points stand on their own line
12
+ formatted.gsub!(/[ ]*([*]+) ([^*]*)/) { |s| " #{$1} #{$2.strip}\n" }
13
+ formatted.gsub!(/[ ]*([#]+) ([^#]*)/) { |s| " #{$1} #{$2.strip}\n" }
14
+
15
+ formatted
16
+ end
17
+ end
@@ -0,0 +1,1447 @@
1
+ #--
2
+ # Text::Format for Ruby
3
+ # Version 0.63
4
+ #
5
+ # Copyright (c) 2002 - 2003 Austin Ziegler
6
+ #
7
+ # $Id: format.rb,v 1.1.1.1 2004/10/14 11:59:57 webster132 Exp $
8
+ #
9
+ # ==========================================================================
10
+ # Revision History ::
11
+ # YYYY.MM.DD Change ID Developer
12
+ # Description
13
+ # --------------------------------------------------------------------------
14
+ # 2002.10.18 Austin Ziegler
15
+ # Fixed a minor problem with tabs not being counted. Changed
16
+ # abbreviations from Hash to Array to better suit Ruby's
17
+ # capabilities. Fixed problems with the way that Array arguments
18
+ # are handled in calls to the major object types, excepting in
19
+ # Text::Format#expand and Text::Format#unexpand (these will
20
+ # probably need to be fixed).
21
+ # 2002.10.30 Austin Ziegler
22
+ # Fixed the ordering of the <=> for binary tests. Fixed
23
+ # Text::Format#expand and Text::Format#unexpand to handle array
24
+ # arguments better.
25
+ # 2003.01.24 Austin Ziegler
26
+ # Fixed a problem with Text::Format::RIGHT_FILL handling where a
27
+ # single word is larger than #columns. Removed Comparable
28
+ # capabilities (<=> doesn't make sense; == does). Added Symbol
29
+ # equivalents for the Hash initialization. Hash initialization has
30
+ # been modified so that values are set as follows (Symbols are
31
+ # highest priority; strings are middle; defaults are lowest):
32
+ # @columns = arg[:columns] || arg['columns'] || @columns
33
+ # Added #hard_margins, #split_rules, #hyphenator, and #split_words.
34
+ # 2003.02.07 Austin Ziegler
35
+ # Fixed the installer for proper case-sensitive handling.
36
+ # 2003.03.28 Austin Ziegler
37
+ # Added the ability for a hyphenator to receive the formatter
38
+ # object. Fixed a bug for strings matching /\A\s*\Z/ failing
39
+ # entirely. Fixed a test case failing under 1.6.8.
40
+ # 2003.04.04 Austin Ziegler
41
+ # Handle the case of hyphenators returning nil for first/rest.
42
+ # 2003.09.17 Austin Ziegler
43
+ # Fixed a problem where #paragraphs(" ") was raising
44
+ # NoMethodError.
45
+ #
46
+ # ==========================================================================
47
+ #++
48
+
49
+ module Text #:nodoc:
50
+ # = Introduction
51
+ #
52
+ # Text::Format provides the ability to nicely format fixed-width text with
53
+ # knowledge of the writeable space (number of columns), margins, and
54
+ # indentation settings.
55
+ #
56
+ # Copyright:: Copyright (c) 2002 - 2003 by Austin Ziegler
57
+ # Version:: 0.63
58
+ # Based On:: Perl
59
+ # Text::Format[http://search.cpan.org/author/GABOR/Text-Format0.52/lib/Text/Format.pm],
60
+ # Copyright (c) 1998 G�bor Egressy
61
+ # Licence:: Ruby's, Perl Artistic, or GPL version 2 (or later)
62
+ #
63
+ class Format
64
+ VERSION = '0.63'
65
+
66
+ # Local abbreviations. More can be added with Text::Format.abbreviations
67
+ ABBREV = [ 'Mr', 'Mrs', 'Ms', 'Jr', 'Sr' ]
68
+
69
+ # Formatting values
70
+ LEFT_ALIGN = 0
71
+ RIGHT_ALIGN = 1
72
+ RIGHT_FILL = 2
73
+ JUSTIFY = 3
74
+
75
+ # Word split modes (only applies when #hard_margins is true).
76
+ SPLIT_FIXED = 1
77
+ SPLIT_CONTINUATION = 2
78
+ SPLIT_HYPHENATION = 4
79
+ SPLIT_CONTINUATION_FIXED = SPLIT_CONTINUATION | SPLIT_FIXED
80
+ SPLIT_HYPHENATION_FIXED = SPLIT_HYPHENATION | SPLIT_FIXED
81
+ SPLIT_HYPHENATION_CONTINUATION = SPLIT_HYPHENATION | SPLIT_CONTINUATION
82
+ SPLIT_ALL = SPLIT_HYPHENATION | SPLIT_CONTINUATION | SPLIT_FIXED
83
+
84
+ # Words forcibly split by Text::Format will be stored as split words.
85
+ # This class represents a word forcibly split.
86
+ class SplitWord
87
+ # The word that was split.
88
+ attr_reader :word
89
+ # The first part of the word that was split.
90
+ attr_reader :first
91
+ # The remainder of the word that was split.
92
+ attr_reader :rest
93
+
94
+ def initialize(word, first, rest) #:nodoc:
95
+ @word = word
96
+ @first = first
97
+ @rest = rest
98
+ end
99
+ end
100
+
101
+ private
102
+ LEQ_RE = /[.?!]['"]?$/
103
+
104
+ def brk_re(i) #:nodoc:
105
+ %r/((?:\S+\s+){#{i}})(.+)/
106
+ end
107
+
108
+ def posint(p) #:nodoc:
109
+ p.to_i.abs
110
+ end
111
+
112
+ public
113
+ # Compares two Text::Format objects. All settings of the objects are
114
+ # compared *except* #hyphenator. Generated results (e.g., #split_words)
115
+ # are not compared, either.
116
+ def ==(o)
117
+ (@text == o.text) &&
118
+ (@columns == o.columns) &&
119
+ (@left_margin == o.left_margin) &&
120
+ (@right_margin == o.right_margin) &&
121
+ (@hard_margins == o.hard_margins) &&
122
+ (@split_rules == o.split_rules) &&
123
+ (@first_indent == o.first_indent) &&
124
+ (@body_indent == o.body_indent) &&
125
+ (@tag_text == o.tag_text) &&
126
+ (@tabstop == o.tabstop) &&
127
+ (@format_style == o.format_style) &&
128
+ (@extra_space == o.extra_space) &&
129
+ (@tag_paragraph == o.tag_paragraph) &&
130
+ (@nobreak == o.nobreak) &&
131
+ (@abbreviations == o.abbreviations) &&
132
+ (@nobreak_regex == o.nobreak_regex)
133
+ end
134
+
135
+ # The text to be manipulated. Note that value is optional, but if the
136
+ # formatting functions are called without values, this text is what will
137
+ # be formatted.
138
+ #
139
+ # *Default*:: <tt>[]</tt>
140
+ # <b>Used in</b>:: All methods
141
+ attr_accessor :text
142
+
143
+ # The total width of the format area. The margins, indentation, and text
144
+ # are formatted into this space.
145
+ #
146
+ # COLUMNS
147
+ # <-------------------------------------------------------------->
148
+ # <-----------><------><---------------------------><------------>
149
+ # left margin indent text is formatted into here right margin
150
+ #
151
+ # *Default*:: <tt>72</tt>
152
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
153
+ # <tt>#center</tt>
154
+ attr_reader :columns
155
+
156
+ # The total width of the format area. The margins, indentation, and text
157
+ # are formatted into this space. The value provided is silently
158
+ # converted to a positive integer.
159
+ #
160
+ # COLUMNS
161
+ # <-------------------------------------------------------------->
162
+ # <-----------><------><---------------------------><------------>
163
+ # left margin indent text is formatted into here right margin
164
+ #
165
+ # *Default*:: <tt>72</tt>
166
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
167
+ # <tt>#center</tt>
168
+ def columns=(c)
169
+ @columns = posint(c)
170
+ end
171
+
172
+ # The number of spaces used for the left margin.
173
+ #
174
+ # columns
175
+ # <-------------------------------------------------------------->
176
+ # <-----------><------><---------------------------><------------>
177
+ # LEFT MARGIN indent text is formatted into here right margin
178
+ #
179
+ # *Default*:: <tt>0</tt>
180
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
181
+ # <tt>#center</tt>
182
+ attr_reader :left_margin
183
+
184
+ # The number of spaces used for the left margin. The value provided is
185
+ # silently converted to a positive integer value.
186
+ #
187
+ # columns
188
+ # <-------------------------------------------------------------->
189
+ # <-----------><------><---------------------------><------------>
190
+ # LEFT MARGIN indent text is formatted into here right margin
191
+ #
192
+ # *Default*:: <tt>0</tt>
193
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
194
+ # <tt>#center</tt>
195
+ def left_margin=(left)
196
+ @left_margin = posint(left)
197
+ end
198
+
199
+ # The number of spaces used for the right margin.
200
+ #
201
+ # columns
202
+ # <-------------------------------------------------------------->
203
+ # <-----------><------><---------------------------><------------>
204
+ # left margin indent text is formatted into here RIGHT MARGIN
205
+ #
206
+ # *Default*:: <tt>0</tt>
207
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
208
+ # <tt>#center</tt>
209
+ attr_reader :right_margin
210
+
211
+ # The number of spaces used for the right margin. The value provided is
212
+ # silently converted to a positive integer value.
213
+ #
214
+ # columns
215
+ # <-------------------------------------------------------------->
216
+ # <-----------><------><---------------------------><------------>
217
+ # left margin indent text is formatted into here RIGHT MARGIN
218
+ #
219
+ # *Default*:: <tt>0</tt>
220
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>,
221
+ # <tt>#center</tt>
222
+ def right_margin=(r)
223
+ @right_margin = posint(r)
224
+ end
225
+
226
+ # The number of spaces to indent the first line of a paragraph.
227
+ #
228
+ # columns
229
+ # <-------------------------------------------------------------->
230
+ # <-----------><------><---------------------------><------------>
231
+ # left margin INDENT text is formatted into here right margin
232
+ #
233
+ # *Default*:: <tt>4</tt>
234
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
235
+ attr_reader :first_indent
236
+
237
+ # The number of spaces to indent the first line of a paragraph. The
238
+ # value provided is silently converted to a positive integer value.
239
+ #
240
+ # columns
241
+ # <-------------------------------------------------------------->
242
+ # <-----------><------><---------------------------><------------>
243
+ # left margin INDENT text is formatted into here right margin
244
+ #
245
+ # *Default*:: <tt>4</tt>
246
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
247
+ def first_indent=(f)
248
+ @first_indent = posint(f)
249
+ end
250
+
251
+ # The number of spaces to indent all lines after the first line of a
252
+ # paragraph.
253
+ #
254
+ # columns
255
+ # <-------------------------------------------------------------->
256
+ # <-----------><------><---------------------------><------------>
257
+ # left margin INDENT text is formatted into here right margin
258
+ #
259
+ # *Default*:: <tt>0</tt>
260
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
261
+ attr_reader :body_indent
262
+
263
+ # The number of spaces to indent all lines after the first line of
264
+ # a paragraph. The value provided is silently converted to a
265
+ # positive integer value.
266
+ #
267
+ # columns
268
+ # <-------------------------------------------------------------->
269
+ # <-----------><------><---------------------------><------------>
270
+ # left margin INDENT text is formatted into here right margin
271
+ #
272
+ # *Default*:: <tt>0</tt>
273
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
274
+ def body_indent=(b)
275
+ @body_indent = posint(b)
276
+ end
277
+
278
+ # Normally, words larger than the format area will be placed on a line
279
+ # by themselves. Setting this to +true+ will force words larger than the
280
+ # format area to be split into one or more "words" each at most the size
281
+ # of the format area. The first line and the original word will be
282
+ # placed into <tt>#split_words</tt>. Note that this will cause the
283
+ # output to look *similar* to a #format_style of JUSTIFY. (Lines will be
284
+ # filled as much as possible.)
285
+ #
286
+ # *Default*:: +false+
287
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
288
+ attr_accessor :hard_margins
289
+
290
+ # An array of words split during formatting if #hard_margins is set to
291
+ # +true+.
292
+ # #split_words << Text::Format::SplitWord.new(word, first, rest)
293
+ attr_reader :split_words
294
+
295
+ # The object responsible for hyphenating. It must respond to
296
+ # #hyphenate_to(word, size) or #hyphenate_to(word, size, formatter) and
297
+ # return an array of the word split into two parts; if there is a
298
+ # hyphenation mark to be applied, responsibility belongs to the
299
+ # hyphenator object. The size is the MAXIMUM size permitted, including
300
+ # any hyphenation marks. If the #hyphenate_to method has an arity of 3,
301
+ # the formatter will be provided to the method. This allows the
302
+ # hyphenator to make decisions about the hyphenation based on the
303
+ # formatting rules.
304
+ #
305
+ # *Default*:: +nil+
306
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
307
+ attr_reader :hyphenator
308
+
309
+ # The object responsible for hyphenating. It must respond to
310
+ # #hyphenate_to(word, size) and return an array of the word hyphenated
311
+ # into two parts. The size is the MAXIMUM size permitted, including any
312
+ # hyphenation marks.
313
+ #
314
+ # *Default*:: +nil+
315
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
316
+ def hyphenator=(h)
317
+ raise ArgumentError, "#{h.inspect} is not a valid hyphenator." unless h.respond_to?(:hyphenate_to)
318
+ arity = h.method(:hyphenate_to).arity
319
+ raise ArgumentError, "#{h.inspect} must have exactly two or three arguments." unless [2, 3].include?(arity)
320
+ @hyphenator = h
321
+ @hyphenator_arity = arity
322
+ end
323
+
324
+ # Specifies the split mode; used only when #hard_margins is set to
325
+ # +true+. Allowable values are:
326
+ # [+SPLIT_FIXED+] The word will be split at the number of
327
+ # characters needed, with no marking at all.
328
+ # repre
329
+ # senta
330
+ # ion
331
+ # [+SPLIT_CONTINUATION+] The word will be split at the number of
332
+ # characters needed, with a C-style continuation
333
+ # character. If a word is the only item on a
334
+ # line and it cannot be split into an
335
+ # appropriate size, SPLIT_FIXED will be used.
336
+ # repr\
337
+ # esen\
338
+ # tati\
339
+ # on
340
+ # [+SPLIT_HYPHENATION+] The word will be split according to the
341
+ # hyphenator specified in #hyphenator. If there
342
+ # is no #hyphenator specified, works like
343
+ # SPLIT_CONTINUATION. The example is using
344
+ # TeX::Hyphen. If a word is the only item on a
345
+ # line and it cannot be split into an
346
+ # appropriate size, SPLIT_CONTINUATION mode will
347
+ # be used.
348
+ # rep-
349
+ # re-
350
+ # sen-
351
+ # ta-
352
+ # tion
353
+ #
354
+ # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
355
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
356
+ attr_reader :split_rules
357
+
358
+ # Specifies the split mode; used only when #hard_margins is set to
359
+ # +true+. Allowable values are:
360
+ # [+SPLIT_FIXED+] The word will be split at the number of
361
+ # characters needed, with no marking at all.
362
+ # repre
363
+ # senta
364
+ # ion
365
+ # [+SPLIT_CONTINUATION+] The word will be split at the number of
366
+ # characters needed, with a C-style continuation
367
+ # character.
368
+ # repr\
369
+ # esen\
370
+ # tati\
371
+ # on
372
+ # [+SPLIT_HYPHENATION+] The word will be split according to the
373
+ # hyphenator specified in #hyphenator. If there
374
+ # is no #hyphenator specified, works like
375
+ # SPLIT_CONTINUATION. The example is using
376
+ # TeX::Hyphen as the #hyphenator.
377
+ # rep-
378
+ # re-
379
+ # sen-
380
+ # ta-
381
+ # tion
382
+ #
383
+ # These values can be bitwise ORed together (e.g., <tt>SPLIT_FIXED |
384
+ # SPLIT_CONTINUATION</tt>) to provide fallback split methods. In the
385
+ # example given, an attempt will be made to split the word using the
386
+ # rules of SPLIT_CONTINUATION; if there is not enough room, the word
387
+ # will be split with the rules of SPLIT_FIXED. These combinations are
388
+ # also available as the following values:
389
+ # * +SPLIT_CONTINUATION_FIXED+
390
+ # * +SPLIT_HYPHENATION_FIXED+
391
+ # * +SPLIT_HYPHENATION_CONTINUATION+
392
+ # * +SPLIT_ALL+
393
+ #
394
+ # *Default*:: <tt>Text::Format::SPLIT_FIXED</tt>
395
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
396
+ def split_rules=(s)
397
+ raise ArgumentError, "Invalid value provided for split_rules." if ((s < SPLIT_FIXED) || (s > SPLIT_ALL))
398
+ @split_rules = s
399
+ end
400
+
401
+ # Indicates whether sentence terminators should be followed by a single
402
+ # space (+false+), or two spaces (+true+).
403
+ #
404
+ # *Default*:: +false+
405
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
406
+ attr_accessor :extra_space
407
+
408
+ # Defines the current abbreviations as an array. This is only used if
409
+ # extra_space is turned on.
410
+ #
411
+ # If one is abbreviating "President" as "Pres." (abbreviations =
412
+ # ["Pres"]), then the results of formatting will be as illustrated in
413
+ # the table below:
414
+ #
415
+ # extra_space | include? | !include?
416
+ # true | Pres. Lincoln | Pres. Lincoln
417
+ # false | Pres. Lincoln | Pres. Lincoln
418
+ #
419
+ # *Default*:: <tt>{}</tt>
420
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
421
+ attr_accessor :abbreviations
422
+
423
+ # Indicates whether the formatting of paragraphs should be done with
424
+ # tagged paragraphs. Useful only with <tt>#tag_text</tt>.
425
+ #
426
+ # *Default*:: +false+
427
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
428
+ attr_accessor :tag_paragraph
429
+
430
+ # The array of text to be placed before each paragraph when
431
+ # <tt>#tag_paragraph</tt> is +true+. When <tt>#format()</tt> is called,
432
+ # only the first element of the array is used. When <tt>#paragraphs</tt>
433
+ # is called, then each entry in the array will be used once, with
434
+ # corresponding paragraphs. If the tag elements are exhausted before the
435
+ # text is exhausted, then the remaining paragraphs will not be tagged.
436
+ # Regardless of indentation settings, a blank line will be inserted
437
+ # between all paragraphs when <tt>#tag_paragraph</tt> is +true+.
438
+ #
439
+ # *Default*:: <tt>[]</tt>
440
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
441
+ attr_accessor :tag_text
442
+
443
+ # Indicates whether or not the non-breaking space feature should be
444
+ # used.
445
+ #
446
+ # *Default*:: +false+
447
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
448
+ attr_accessor :nobreak
449
+
450
+ # A hash which holds the regular expressions on which spaces should not
451
+ # be broken. The hash is set up such that the key is the first word and
452
+ # the value is the second word.
453
+ #
454
+ # For example, if +nobreak_regex+ contains the following hash:
455
+ #
456
+ # { '^Mrs?\.$' => '\S+$', '^\S+$' => '^(?:S|J)r\.$'}
457
+ #
458
+ # Then "Mr. Jones", "Mrs. Jones", and "Jones Jr." would not be broken.
459
+ # If this simple matching algorithm indicates that there should not be a
460
+ # break at the current end of line, then a backtrack is done until there
461
+ # are two words on which line breaking is permitted. If two such words
462
+ # are not found, then the end of the line will be broken *regardless*.
463
+ # If there is a single word on the current line, then no backtrack is
464
+ # done and the word is stuck on the end.
465
+ #
466
+ # *Default*:: <tt>{}</tt>
467
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
468
+ attr_accessor :nobreak_regex
469
+
470
+ # Indicates the number of spaces that a single tab represents.
471
+ #
472
+ # *Default*:: <tt>8</tt>
473
+ # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
474
+ # <tt>#paragraphs</tt>
475
+ attr_reader :tabstop
476
+
477
+ # Indicates the number of spaces that a single tab represents.
478
+ #
479
+ # *Default*:: <tt>8</tt>
480
+ # <b>Used in</b>:: <tt>#expand</tt>, <tt>#unexpand</tt>,
481
+ # <tt>#paragraphs</tt>
482
+ def tabstop=(t)
483
+ @tabstop = posint(t)
484
+ end
485
+
486
+ # Specifies the format style. Allowable values are:
487
+ # [+LEFT_ALIGN+] Left justified, ragged right.
488
+ # |A paragraph that is|
489
+ # |left aligned.|
490
+ # [+RIGHT_ALIGN+] Right justified, ragged left.
491
+ # |A paragraph that is|
492
+ # | right aligned.|
493
+ # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
494
+ # spaces. (Essentially the same as +LEFT_ALIGN+ except
495
+ # that lines are padded on the right.)
496
+ # |A paragraph that is|
497
+ # |left aligned. |
498
+ # [+JUSTIFY+] Fully justified, words filled to width by spaces,
499
+ # except the last line.
500
+ # |A paragraph that|
501
+ # |is justified.|
502
+ #
503
+ # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
504
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
505
+ attr_reader :format_style
506
+
507
+ # Specifies the format style. Allowable values are:
508
+ # [+LEFT_ALIGN+] Left justified, ragged right.
509
+ # |A paragraph that is|
510
+ # |left aligned.|
511
+ # [+RIGHT_ALIGN+] Right justified, ragged left.
512
+ # |A paragraph that is|
513
+ # | right aligned.|
514
+ # [+RIGHT_FILL+] Left justified, right ragged, filled to width by
515
+ # spaces. (Essentially the same as +LEFT_ALIGN+ except
516
+ # that lines are padded on the right.)
517
+ # |A paragraph that is|
518
+ # |left aligned. |
519
+ # [+JUSTIFY+] Fully justified, words filled to width by spaces.
520
+ # |A paragraph that|
521
+ # |is justified.|
522
+ #
523
+ # *Default*:: <tt>Text::Format::LEFT_ALIGN</tt>
524
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
525
+ def format_style=(fs)
526
+ raise ArgumentError, "Invalid value provided for format_style." if ((fs < LEFT_ALIGN) || (fs > JUSTIFY))
527
+ @format_style = fs
528
+ end
529
+
530
+ # Indicates that the format style is left alignment.
531
+ #
532
+ # *Default*:: +true+
533
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
534
+ def left_align?
535
+ return @format_style == LEFT_ALIGN
536
+ end
537
+
538
+ # Indicates that the format style is right alignment.
539
+ #
540
+ # *Default*:: +false+
541
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
542
+ def right_align?
543
+ return @format_style == RIGHT_ALIGN
544
+ end
545
+
546
+ # Indicates that the format style is right fill.
547
+ #
548
+ # *Default*:: +false+
549
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
550
+ def right_fill?
551
+ return @format_style == RIGHT_FILL
552
+ end
553
+
554
+ # Indicates that the format style is full justification.
555
+ #
556
+ # *Default*:: +false+
557
+ # <b>Used in</b>:: <tt>#format</tt>, <tt>#paragraphs</tt>
558
+ def justify?
559
+ return @format_style == JUSTIFY
560
+ end
561
+
562
+ # The default implementation of #hyphenate_to implements
563
+ # SPLIT_CONTINUATION.
564
+ def hyphenate_to(word, size)
565
+ [word[0 .. (size - 2)] + "\\", word[(size - 1) .. -1]]
566
+ end
567
+
568
+ private
569
+ def __do_split_word(word, size) #:nodoc:
570
+ [word[0 .. (size - 1)], word[size .. -1]]
571
+ end
572
+
573
+ def __format(to_wrap) #:nodoc:
574
+ words = to_wrap.split(/\s+/).compact
575
+ words.shift if words[0].nil? or words[0].empty?
576
+ to_wrap = []
577
+
578
+ abbrev = false
579
+ width = @columns - @first_indent - @left_margin - @right_margin
580
+ indent_str = ' ' * @first_indent
581
+ first_line = true
582
+ line = words.shift
583
+ abbrev = __is_abbrev(line) unless line.nil? || line.empty?
584
+
585
+ while w = words.shift
586
+ if (w.size + line.size < (width - 1)) ||
587
+ ((line !~ LEQ_RE || abbrev) && (w.size + line.size < width))
588
+ line << " " if (line =~ LEQ_RE) && (not abbrev)
589
+ line << " #{w}"
590
+ else
591
+ line, w = __do_break(line, w) if @nobreak
592
+ line, w = __do_hyphenate(line, w, width) if @hard_margins
593
+ if w.index(/\s+/)
594
+ w, *w2 = w.split(/\s+/)
595
+ words.unshift(w2)
596
+ words.flatten!
597
+ end
598
+ to_wrap << __make_line(line, indent_str, width, w.nil?) unless line.nil?
599
+ if first_line
600
+ first_line = false
601
+ width = @columns - @body_indent - @left_margin - @right_margin
602
+ indent_str = ' ' * @body_indent
603
+ end
604
+ line = w
605
+ end
606
+
607
+ abbrev = __is_abbrev(w) unless w.nil?
608
+ end
609
+
610
+ loop do
611
+ break if line.nil? or line.empty?
612
+ line, w = __do_hyphenate(line, w, width) if @hard_margins
613
+ to_wrap << __make_line(line, indent_str, width, w.nil?)
614
+ line = w
615
+ end
616
+
617
+ if (@tag_paragraph && (to_wrap.size > 0)) then
618
+ clr = %r{`(\w+)'}.match([caller(1)].flatten[0])[1]
619
+ clr = "" if clr.nil?
620
+
621
+ if ((not @tag_text[0].nil?) && (@tag_cur.size < 1) &&
622
+ (clr != "__paragraphs")) then
623
+ @tag_cur = @tag_text[0]
624
+ end
625
+
626
+ fchar = /(\S)/.match(to_wrap[0])[1]
627
+ white = to_wrap[0].index(fchar)
628
+ if ((white - @left_margin - 1) > @tag_cur.size) then
629
+ white = @tag_cur.size + @left_margin
630
+ to_wrap[0].gsub!(/^ {#{white}}/, "#{' ' * @left_margin}#{@tag_cur}")
631
+ else
632
+ to_wrap.unshift("#{' ' * @left_margin}#{@tag_cur}\n")
633
+ end
634
+ end
635
+ to_wrap.join('')
636
+ end
637
+
638
+ # format lines in text into paragraphs with each element of @wrap a
639
+ # paragraph; uses Text::Format.format for the formatting
640
+ def __paragraphs(to_wrap) #:nodoc:
641
+ if ((@first_indent == @body_indent) || @tag_paragraph) then
642
+ p_end = "\n"
643
+ else
644
+ p_end = ''
645
+ end
646
+
647
+ cnt = 0
648
+ ret = []
649
+ to_wrap.each do |tw|
650
+ @tag_cur = @tag_text[cnt] if @tag_paragraph
651
+ @tag_cur = '' if @tag_cur.nil?
652
+ line = __format(tw)
653
+ ret << "#{line}#{p_end}" if (not line.nil?) && (line.size > 0)
654
+ cnt += 1
655
+ end
656
+
657
+ ret[-1].chomp! unless ret.empty?
658
+ ret.join('')
659
+ end
660
+
661
+ # center text using spaces on left side to pad it out empty lines
662
+ # are preserved
663
+ def __center(to_center) #:nodoc:
664
+ tabs = 0
665
+ width = @columns - @left_margin - @right_margin
666
+ centered = []
667
+ to_center.each do |tc|
668
+ s = tc.strip
669
+ tabs = s.count("\t")
670
+ tabs = 0 if tabs.nil?
671
+ ct = ((width - s.size - (tabs * @tabstop) + tabs) / 2)
672
+ ct = (width - @left_margin - @right_margin) - ct
673
+ centered << "#{s.rjust(ct)}\n"
674
+ end
675
+ centered.join('')
676
+ end
677
+
678
+ # expand tabs to spaces should be similar to Text::Tabs::expand
679
+ def __expand(to_expand) #:nodoc:
680
+ expanded = []
681
+ to_expand.split("\n").each { |te| expanded << te.gsub(/\t/, ' ' * @tabstop) }
682
+ expanded.join('')
683
+ end
684
+
685
+ def __unexpand(to_unexpand) #:nodoc:
686
+ unexpanded = []
687
+ to_unexpand.split("\n").each { |tu| unexpanded << tu.gsub(/ {#{@tabstop}}/, "\t") }
688
+ unexpanded.join('')
689
+ end
690
+
691
+ def __is_abbrev(word) #:nodoc:
692
+ # remove period if there is one.
693
+ w = word.gsub(/\.$/, '') unless word.nil?
694
+ return true if (!@extra_space || ABBREV.include?(w) || @abbreviations.include?(w))
695
+ false
696
+ end
697
+
698
+ def __make_line(line, indent, width, last = false) #:nodoc:
699
+ lmargin = " " * @left_margin
700
+ fill = " " * (width - line.size) if right_fill? && (line.size <= width)
701
+
702
+ if (justify? && ((not line.nil?) && (not line.empty?)) && line =~ /\S+\s+\S+/ && !last)
703
+ spaces = width - line.size
704
+ words = line.split(/(\s+)/)
705
+ ws = spaces / (words.size / 2)
706
+ spaces = spaces % (words.size / 2) if ws > 0
707
+ words.reverse.each do |rw|
708
+ next if (rw =~ /^\S/)
709
+ rw.sub!(/^/, " " * ws)
710
+ next unless (spaces > 0)
711
+ rw.sub!(/^/, " ")
712
+ spaces -= 1
713
+ end
714
+ line = words.join('')
715
+ end
716
+ line = "#{lmargin}#{indent}#{line}#{fill}\n" unless line.nil?
717
+ if right_align? && (not line.nil?)
718
+ line.sub(/^/, " " * (@columns - @right_margin - (line.size - 1)))
719
+ else
720
+ line
721
+ end
722
+ end
723
+
724
+ def __do_hyphenate(line, next_line, width) #:nodoc:
725
+ rline = line.dup rescue line
726
+ rnext = next_line.dup rescue next_line
727
+ loop do
728
+ if rline.size == width
729
+ break
730
+ elsif rline.size > width
731
+ words = rline.strip.split(/\s+/)
732
+ word = words[-1].dup
733
+ size = width - rline.size + word.size
734
+ if (size <= 0)
735
+ words[-1] = nil
736
+ rline = words.join(' ').strip
737
+ rnext = "#{word} #{rnext}".strip
738
+ next
739
+ end
740
+
741
+ first = rest = nil
742
+
743
+ if ((@split_rules & SPLIT_HYPHENATION) != 0)
744
+ if @hyphenator_arity == 2
745
+ first, rest = @hyphenator.hyphenate_to(word, size)
746
+ else
747
+ first, rest = @hyphenator.hyphenate_to(word, size, self)
748
+ end
749
+ end
750
+
751
+ if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
752
+ first, rest = self.hyphenate_to(word, size)
753
+ end
754
+
755
+ if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
756
+ first.nil? or @split_rules == SPLIT_FIXED
757
+ first, rest = __do_split_word(word, size)
758
+ end
759
+
760
+ if first.nil?
761
+ words[-1] = nil
762
+ rest = word
763
+ else
764
+ words[-1] = first
765
+ @split_words << SplitWord.new(word, first, rest)
766
+ end
767
+ rline = words.join(' ').strip
768
+ rnext = "#{rest} #{rnext}".strip
769
+ break
770
+ else
771
+ break if rnext.nil? or rnext.empty? or rline.nil? or rline.empty?
772
+ words = rnext.split(/\s+/)
773
+ word = words.shift
774
+ size = width - rline.size - 1
775
+
776
+ if (size <= 0)
777
+ rnext = "#{word} #{words.join(' ')}".strip
778
+ break
779
+ end
780
+
781
+ first = rest = nil
782
+
783
+ if ((@split_rules & SPLIT_HYPHENATION) != 0)
784
+ if @hyphenator_arity == 2
785
+ first, rest = @hyphenator.hyphenate_to(word, size)
786
+ else
787
+ first, rest = @hyphenator.hyphenate_to(word, size, self)
788
+ end
789
+ end
790
+
791
+ first, rest = self.hyphenate_to(word, size) if ((@split_rules & SPLIT_CONTINUATION) != 0) and first.nil?
792
+
793
+ first, rest = __do_split_word(word, size) if ((@split_rules & SPLIT_FIXED) != 0) and first.nil?
794
+
795
+ if (rline.size + (first ? first.size : 0)) < width
796
+ @split_words << SplitWord.new(word, first, rest)
797
+ rline = "#{rline} #{first}".strip
798
+ rnext = "#{rest} #{words.join(' ')}".strip
799
+ end
800
+ break
801
+ end
802
+ end
803
+ [rline, rnext]
804
+ end
805
+
806
+ def __do_break(line, next_line) #:nodoc:
807
+ no_brk = false
808
+ words = []
809
+ words = line.split(/\s+/) unless line.nil?
810
+ last_word = words[-1]
811
+
812
+ @nobreak_regex.each { |k, v| no_brk = ((last_word =~ /#{k}/) and (next_line =~ /#{v}/)) }
813
+
814
+ if no_brk && words.size > 1
815
+ i = words.size
816
+ while i > 0
817
+ no_brk = false
818
+ @nobreak_regex.each { |k, v| no_brk = ((words[i + 1] =~ /#{k}/) && (words[i] =~ /#{v}/)) }
819
+ i -= 1
820
+ break if not no_brk
821
+ end
822
+ if i > 0
823
+ l = brk_re(i).match(line)
824
+ line.sub!(brk_re(i), l[1])
825
+ next_line = "#{l[2]} #{next_line}"
826
+ line.sub!(/\s+$/, '')
827
+ end
828
+ end
829
+ [line, next_line]
830
+ end
831
+
832
+ def __create(arg = nil, &block) #:nodoc:
833
+ # Format::Text.new(text-to-wrap)
834
+ @text = arg unless arg.nil?
835
+ # Defaults
836
+ @columns = 72
837
+ @tabstop = 8
838
+ @first_indent = 4
839
+ @body_indent = 0
840
+ @format_style = LEFT_ALIGN
841
+ @left_margin = 0
842
+ @right_margin = 0
843
+ @extra_space = false
844
+ @text = Array.new if @text.nil?
845
+ @tag_paragraph = false
846
+ @tag_text = Array.new
847
+ @tag_cur = ""
848
+ @abbreviations = Array.new
849
+ @nobreak = false
850
+ @nobreak_regex = Hash.new
851
+ @split_words = Array.new
852
+ @hard_margins = false
853
+ @split_rules = SPLIT_FIXED
854
+ @hyphenator = self
855
+ @hyphenator_arity = self.method(:hyphenate_to).arity
856
+
857
+ instance_eval(&block) unless block.nil?
858
+ end
859
+
860
+ public
861
+ # Formats text into a nice paragraph format. The text is separated
862
+ # into words and then reassembled a word at a time using the settings
863
+ # of this Format object. If a word is larger than the number of
864
+ # columns available for formatting, then that word will appear on the
865
+ # line by itself.
866
+ #
867
+ # If +to_wrap+ is +nil+, then the value of <tt>#text</tt> will be
868
+ # worked on.
869
+ def format(to_wrap = nil)
870
+ to_wrap = @text if to_wrap.nil?
871
+ if to_wrap.class == Array
872
+ __format(to_wrap[0])
873
+ else
874
+ __format(to_wrap)
875
+ end
876
+ end
877
+
878
+ # Considers each element of text (provided or internal) as a paragraph.
879
+ # If <tt>#first_indent</tt> is the same as <tt>#body_indent</tt>, then
880
+ # paragraphs will be separated by a single empty line in the result;
881
+ # otherwise, the paragraphs will follow immediately after each other.
882
+ # Uses <tt>#format</tt> to do the heavy lifting.
883
+ def paragraphs(to_wrap = nil)
884
+ to_wrap = @text if to_wrap.nil?
885
+ __paragraphs([to_wrap].flatten)
886
+ end
887
+
888
+ # Centers the text, preserving empty lines and tabs.
889
+ def center(to_center = nil)
890
+ to_center = @text if to_center.nil?
891
+ __center([to_center].flatten)
892
+ end
893
+
894
+ # Replaces all tab characters in the text with <tt>#tabstop</tt> spaces.
895
+ def expand(to_expand = nil)
896
+ to_expand = @text if to_expand.nil?
897
+ if to_expand.class == Array
898
+ to_expand.collect { |te| __expand(te) }
899
+ else
900
+ __expand(to_expand)
901
+ end
902
+ end
903
+
904
+ # Replaces all occurrences of <tt>#tabstop</tt> consecutive spaces
905
+ # with a tab character.
906
+ def unexpand(to_unexpand = nil)
907
+ to_unexpand = @text if to_unexpand.nil?
908
+ if to_unexpand.class == Array
909
+ to_unexpand.collect { |te| v << __unexpand(te) }
910
+ else
911
+ __unexpand(to_unexpand)
912
+ end
913
+ end
914
+
915
+ # This constructor takes advantage of a technique for Ruby object
916
+ # construction introduced by Andy Hunt and Dave Thomas (see reference),
917
+ # where optional values are set using commands in a block.
918
+ #
919
+ # Text::Format.new {
920
+ # columns = 72
921
+ # left_margin = 0
922
+ # right_margin = 0
923
+ # first_indent = 4
924
+ # body_indent = 0
925
+ # format_style = Text::Format::LEFT_ALIGN
926
+ # extra_space = false
927
+ # abbreviations = {}
928
+ # tag_paragraph = false
929
+ # tag_text = []
930
+ # nobreak = false
931
+ # nobreak_regex = {}
932
+ # tabstop = 8
933
+ # text = nil
934
+ # }
935
+ #
936
+ # As shown above, +arg+ is optional. If +arg+ is specified and is a
937
+ # +String+, then arg is used as the default value of <tt>#text</tt>.
938
+ # Alternately, an existing Text::Format object can be used or a Hash can
939
+ # be used. With all forms, a block can be specified.
940
+ #
941
+ # *Reference*:: "Object Construction and Blocks"
942
+ # <http://www.pragmaticprogrammer.com/ruby/articles/insteval.html>
943
+ #
944
+ def initialize(arg = nil, &block)
945
+ case arg
946
+ when Text::Format
947
+ __create(arg.text) do
948
+ @columns = arg.columns
949
+ @tabstop = arg.tabstop
950
+ @first_indent = arg.first_indent
951
+ @body_indent = arg.body_indent
952
+ @format_style = arg.format_style
953
+ @left_margin = arg.left_margin
954
+ @right_margin = arg.right_margin
955
+ @extra_space = arg.extra_space
956
+ @tag_paragraph = arg.tag_paragraph
957
+ @tag_text = arg.tag_text
958
+ @abbreviations = arg.abbreviations
959
+ @nobreak = arg.nobreak
960
+ @nobreak_regex = arg.nobreak_regex
961
+ @text = arg.text
962
+ @hard_margins = arg.hard_margins
963
+ @split_words = arg.split_words
964
+ @split_rules = arg.split_rules
965
+ @hyphenator = arg.hyphenator
966
+ end
967
+ instance_eval(&block) unless block.nil?
968
+ when Hash
969
+ __create do
970
+ @columns = arg[:columns] || arg['columns'] || @columns
971
+ @tabstop = arg[:tabstop] || arg['tabstop'] || @tabstop
972
+ @first_indent = arg[:first_indent] || arg['first_indent'] || @first_indent
973
+ @body_indent = arg[:body_indent] || arg['body_indent'] || @body_indent
974
+ @format_style = arg[:format_style] || arg['format_style'] || @format_style
975
+ @left_margin = arg[:left_margin] || arg['left_margin'] || @left_margin
976
+ @right_margin = arg[:right_margin] || arg['right_margin'] || @right_margin
977
+ @extra_space = arg[:extra_space] || arg['extra_space'] || @extra_space
978
+ @text = arg[:text] || arg['text'] || @text
979
+ @tag_paragraph = arg[:tag_paragraph] || arg['tag_paragraph'] || @tag_paragraph
980
+ @tag_text = arg[:tag_text] || arg['tag_text'] || @tag_text
981
+ @abbreviations = arg[:abbreviations] || arg['abbreviations'] || @abbreviations
982
+ @nobreak = arg[:nobreak] || arg['nobreak'] || @nobreak
983
+ @nobreak_regex = arg[:nobreak_regex] || arg['nobreak_regex'] || @nobreak_regex
984
+ @hard_margins = arg[:hard_margins] || arg['hard_margins'] || @hard_margins
985
+ @split_rules = arg[:split_rules] || arg['split_rules'] || @split_rules
986
+ @hyphenator = arg[:hyphenator] || arg['hyphenator'] || @hyphenator
987
+ end
988
+ instance_eval(&block) unless block.nil?
989
+ when String
990
+ __create(arg, &block)
991
+ when NilClass
992
+ __create(&block)
993
+ else
994
+ raise TypeError
995
+ end
996
+ end
997
+ end
998
+ end
999
+
1000
+ if __FILE__ == $0
1001
+ require 'test/unit'
1002
+
1003
+ class TestText__Format < Test::Unit::TestCase #:nodoc:
1004
+ attr_accessor :format_o
1005
+
1006
+ GETTYSBURG = <<-'EOS'
1007
+ Four score and seven years ago our fathers brought forth on this
1008
+ continent a new nation, conceived in liberty and dedicated to the
1009
+ proposition that all men are created equal. Now we are engaged in
1010
+ a great civil war, testing whether that nation or any nation so
1011
+ conceived and so dedicated can long endure. We are met on a great
1012
+ battlefield of that war. We have come to dedicate a portion of
1013
+ that field as a final resting-place for those who here gave their
1014
+ lives that that nation might live. It is altogether fitting and
1015
+ proper that we should do this. But in a larger sense, we cannot
1016
+ dedicate, we cannot consecrate, we cannot hallow this ground.
1017
+ The brave men, living and dead who struggled here have consecrated
1018
+ it far above our poor power to add or detract. The world will
1019
+ little note nor long remember what we say here, but it can never
1020
+ forget what they did here. It is for us the living rather to be
1021
+ dedicated here to the unfinished work which they who fought here
1022
+ have thus far so nobly advanced. It is rather for us to be here
1023
+ dedicated to the great task remaining before us--that from these
1024
+ honored dead we take increased devotion to that cause for which
1025
+ they gave the last full measure of devotion--that we here highly
1026
+ resolve that these dead shall not have died in vain, that this
1027
+ nation under God shall have a new birth of freedom, and that
1028
+ government of the people, by the people, for the people shall
1029
+ not perish from the earth.
1030
+
1031
+ -- Pres. Abraham Lincoln, 19 November 1863
1032
+ EOS
1033
+
1034
+ FIVE_COL = "Four \nscore\nand s\neven \nyears\nago o\nur fa\nthers\nbroug\nht fo\nrth o\nn thi\ns con\ntinen\nt a n\new na\ntion,\nconce\nived \nin li\nberty\nand d\nedica\nted t\no the\npropo\nsitio\nn tha\nt all\nmen a\nre cr\neated\nequal\n. Now\nwe ar\ne eng\naged \nin a \ngreat\ncivil\nwar, \ntesti\nng wh\nether\nthat \nnatio\nn or \nany n\nation\nso co\nnceiv\ned an\nd so \ndedic\nated \ncan l\nong e\nndure\n. We \nare m\net on\na gre\nat ba\nttlef\nield \nof th\nat wa\nr. We\nhave \ncome \nto de\ndicat\ne a p\nortio\nn of \nthat \nfield\nas a \nfinal\nresti\nng-pl\nace f\nor th\nose w\nho he\nre ga\nve th\neir l\nives \nthat \nthat \nnatio\nn mig\nht li\nve. I\nt is \naltog\nether\nfitti\nng an\nd pro\nper t\nhat w\ne sho\nuld d\no thi\ns. Bu\nt in \na lar\nger s\nense,\nwe ca\nnnot \ndedic\nate, \nwe ca\nnnot \nconse\ncrate\n, we \ncanno\nt hal\nlow t\nhis g\nround\n. The\nbrave\nmen, \nlivin\ng and\ndead \nwho s\ntrugg\nled h\nere h\nave c\nonsec\nrated\nit fa\nr abo\nve ou\nr poo\nr pow\ner to\nadd o\nr det\nract.\nThe w\norld \nwill \nlittl\ne not\ne nor\nlong \nremem\nber w\nhat w\ne say\nhere,\nbut i\nt can\nnever\nforge\nt wha\nt the\ny did\nhere.\nIt is\nfor u\ns the\nlivin\ng rat\nher t\no be \ndedic\nated \nhere \nto th\ne unf\ninish\ned wo\nrk wh\nich t\nhey w\nho fo\nught \nhere \nhave \nthus \nfar s\no nob\nly ad\nvance\nd. It\nis ra\nther \nfor u\ns to \nbe he\nre de\ndicat\ned to\nthe g\nreat \ntask \nremai\nning \nbefor\ne us-\n-that\nfrom \nthese\nhonor\ned de\nad we\ntake \nincre\nased \ndevot\nion t\no tha\nt cau\nse fo\nr whi\nch th\ney ga\nve th\ne las\nt ful\nl mea\nsure \nof de\nvotio\nn--th\nat we\nhere \nhighl\ny res\nolve \nthat \nthese\ndead \nshall\nnot h\nave d\nied i\nn vai\nn, th\nat th\nis na\ntion \nunder\nGod s\nhall \nhave \na new\nbirth\nof fr\needom\n, and\nthat \ngover\nnment\nof th\ne peo\nple, \nby th\ne peo\nple, \nfor t\nhe pe\nople \nshall\nnot p\nerish\nfrom \nthe e\narth.\n-- Pr\nes. A\nbraha\nm Lin\ncoln,\n19 No\nvembe\nr 186\n3 \n"
1035
+
1036
+ FIVE_CNT = "Four \nscore\nand \nseven\nyears\nago \nour \nfath\\\ners \nbrou\\\nght \nforth\non t\\\nhis \ncont\\\ninent\na new\nnati\\\non, \nconc\\\neived\nin l\\\niber\\\nty a\\\nnd d\\\nedic\\\nated \nto t\\\nhe p\\\nropo\\\nsiti\\\non t\\\nhat \nall \nmen \nare \ncrea\\\nted \nequa\\\nl. N\\\now we\nare \nenga\\\nged \nin a \ngreat\ncivil\nwar, \ntest\\\ning \nwhet\\\nher \nthat \nnati\\\non or\nany \nnati\\\non so\nconc\\\neived\nand \nso d\\\nedic\\\nated \ncan \nlong \nendu\\\nre. \nWe a\\\nre m\\\net on\na gr\\\neat \nbatt\\\nlefi\\\neld \nof t\\\nhat \nwar. \nWe h\\\nave \ncome \nto d\\\nedic\\\nate a\nport\\\nion \nof t\\\nhat \nfield\nas a \nfinal\nrest\\\ning-\\\nplace\nfor \nthose\nwho \nhere \ngave \ntheir\nlives\nthat \nthat \nnati\\\non m\\\night \nlive.\nIt is\nalto\\\ngeth\\\ner f\\\nitti\\\nng a\\\nnd p\\\nroper\nthat \nwe s\\\nhould\ndo t\\\nhis. \nBut \nin a \nlarg\\\ner s\\\nense,\nwe c\\\nannot\ndedi\\\ncate,\nwe c\\\nannot\ncons\\\necra\\\nte, \nwe c\\\nannot\nhall\\\now t\\\nhis \ngrou\\\nnd. \nThe \nbrave\nmen, \nlivi\\\nng a\\\nnd d\\\nead \nwho \nstru\\\nggled\nhere \nhave \ncons\\\necra\\\nted \nit f\\\nar a\\\nbove \nour \npoor \npower\nto a\\\ndd or\ndetr\\\nact. \nThe \nworld\nwill \nlitt\\\nle n\\\note \nnor \nlong \nreme\\\nmber \nwhat \nwe s\\\nay h\\\nere, \nbut \nit c\\\nan n\\\never \nforg\\\net w\\\nhat \nthey \ndid \nhere.\nIt is\nfor \nus t\\\nhe l\\\niving\nrath\\\ner to\nbe d\\\nedic\\\nated \nhere \nto t\\\nhe u\\\nnfin\\\nished\nwork \nwhich\nthey \nwho \nfoug\\\nht h\\\nere \nhave \nthus \nfar \nso n\\\nobly \nadva\\\nnced.\nIt is\nrath\\\ner f\\\nor us\nto be\nhere \ndedi\\\ncated\nto t\\\nhe g\\\nreat \ntask \nrema\\\nining\nbefo\\\nre u\\\ns--t\\\nhat \nfrom \nthese\nhono\\\nred \ndead \nwe t\\\nake \nincr\\\neased\ndevo\\\ntion \nto t\\\nhat \ncause\nfor \nwhich\nthey \ngave \nthe \nlast \nfull \nmeas\\\nure \nof d\\\nevot\\\nion-\\\n-that\nwe h\\\nere \nhigh\\\nly r\\\nesol\\\nve t\\\nhat \nthese\ndead \nshall\nnot \nhave \ndied \nin v\\\nain, \nthat \nthis \nnati\\\non u\\\nnder \nGod \nshall\nhave \na new\nbirth\nof f\\\nreed\\\nom, \nand \nthat \ngove\\\nrnme\\\nnt of\nthe \npeop\\\nle, \nby t\\\nhe p\\\neopl\\\ne, f\\\nor t\\\nhe p\\\neople\nshall\nnot \nperi\\\nsh f\\\nrom \nthe \neart\\\nh. --\nPres.\nAbra\\\nham \nLinc\\\noln, \n19 N\\\novem\\\nber \n1863 \n"
1037
+
1038
+ # Tests both abbreviations and abbreviations=
1039
+ def test_abbreviations
1040
+ abbr = [" Pres. Abraham Lincoln\n", " Pres. Abraham Lincoln\n"]
1041
+ assert_nothing_raised { @format_o = Text::Format.new }
1042
+ assert_equal([], @format_o.abbreviations)
1043
+ assert_nothing_raised { @format_o.abbreviations = [ 'foo', 'bar' ] }
1044
+ assert_equal([ 'foo', 'bar' ], @format_o.abbreviations)
1045
+ assert_equal(abbr[0], @format_o.format(abbr[0]))
1046
+ assert_nothing_raised { @format_o.extra_space = true }
1047
+ assert_equal(abbr[1], @format_o.format(abbr[0]))
1048
+ assert_nothing_raised { @format_o.abbreviations = [ "Pres" ] }
1049
+ assert_equal([ "Pres" ], @format_o.abbreviations)
1050
+ assert_equal(abbr[0], @format_o.format(abbr[0]))
1051
+ assert_nothing_raised { @format_o.extra_space = false }
1052
+ assert_equal(abbr[0], @format_o.format(abbr[0]))
1053
+ end
1054
+
1055
+ # Tests both body_indent and body_indent=
1056
+ def test_body_indent
1057
+ assert_nothing_raised { @format_o = Text::Format.new }
1058
+ assert_equal(0, @format_o.body_indent)
1059
+ assert_nothing_raised { @format_o.body_indent = 7 }
1060
+ assert_equal(7, @format_o.body_indent)
1061
+ assert_nothing_raised { @format_o.body_indent = -3 }
1062
+ assert_equal(3, @format_o.body_indent)
1063
+ assert_nothing_raised { @format_o.body_indent = "9" }
1064
+ assert_equal(9, @format_o.body_indent)
1065
+ assert_nothing_raised { @format_o.body_indent = "-2" }
1066
+ assert_equal(2, @format_o.body_indent)
1067
+ assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[1])
1068
+ end
1069
+
1070
+ # Tests both columns and columns=
1071
+ def test_columns
1072
+ assert_nothing_raised { @format_o = Text::Format.new }
1073
+ assert_equal(72, @format_o.columns)
1074
+ assert_nothing_raised { @format_o.columns = 7 }
1075
+ assert_equal(7, @format_o.columns)
1076
+ assert_nothing_raised { @format_o.columns = -3 }
1077
+ assert_equal(3, @format_o.columns)
1078
+ assert_nothing_raised { @format_o.columns = "9" }
1079
+ assert_equal(9, @format_o.columns)
1080
+ assert_nothing_raised { @format_o.columns = "-2" }
1081
+ assert_equal(2, @format_o.columns)
1082
+ assert_nothing_raised { @format_o.columns = 40 }
1083
+ assert_equal(40, @format_o.columns)
1084
+ assert_match(/this continent$/,
1085
+ @format_o.format(GETTYSBURG).split("\n")[1])
1086
+ end
1087
+
1088
+ # Tests both extra_space and extra_space=
1089
+ def test_extra_space
1090
+ assert_nothing_raised { @format_o = Text::Format.new }
1091
+ assert(!@format_o.extra_space)
1092
+ assert_nothing_raised { @format_o.extra_space = true }
1093
+ assert(@format_o.extra_space)
1094
+ # The behaviour of extra_space is tested in test_abbreviations. There
1095
+ # is no need to reproduce it here.
1096
+ end
1097
+
1098
+ # Tests both first_indent and first_indent=
1099
+ def test_first_indent
1100
+ assert_nothing_raised { @format_o = Text::Format.new }
1101
+ assert_equal(4, @format_o.first_indent)
1102
+ assert_nothing_raised { @format_o.first_indent = 7 }
1103
+ assert_equal(7, @format_o.first_indent)
1104
+ assert_nothing_raised { @format_o.first_indent = -3 }
1105
+ assert_equal(3, @format_o.first_indent)
1106
+ assert_nothing_raised { @format_o.first_indent = "9" }
1107
+ assert_equal(9, @format_o.first_indent)
1108
+ assert_nothing_raised { @format_o.first_indent = "-2" }
1109
+ assert_equal(2, @format_o.first_indent)
1110
+ assert_match(/^ [^ ]/, @format_o.format(GETTYSBURG).split("\n")[0])
1111
+ end
1112
+
1113
+ def test_format_style
1114
+ assert_nothing_raised { @format_o = Text::Format.new }
1115
+ assert_equal(Text::Format::LEFT_ALIGN, @format_o.format_style)
1116
+ assert_match(/^November 1863$/,
1117
+ @format_o.format(GETTYSBURG).split("\n")[-1])
1118
+ assert_nothing_raised {
1119
+ @format_o.format_style = Text::Format::RIGHT_ALIGN
1120
+ }
1121
+ assert_equal(Text::Format::RIGHT_ALIGN, @format_o.format_style)
1122
+ assert_match(/^ +November 1863$/,
1123
+ @format_o.format(GETTYSBURG).split("\n")[-1])
1124
+ assert_nothing_raised {
1125
+ @format_o.format_style = Text::Format::RIGHT_FILL
1126
+ }
1127
+ assert_equal(Text::Format::RIGHT_FILL, @format_o.format_style)
1128
+ assert_match(/^November 1863 +$/,
1129
+ @format_o.format(GETTYSBURG).split("\n")[-1])
1130
+ assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
1131
+ assert_equal(Text::Format::JUSTIFY, @format_o.format_style)
1132
+ assert_match(/^of freedom, and that government of the people, by the people, for the$/,
1133
+ @format_o.format(GETTYSBURG).split("\n")[-3])
1134
+ assert_raises(ArgumentError) { @format_o.format_style = 33 }
1135
+ end
1136
+
1137
+ def test_tag_paragraph
1138
+ assert_nothing_raised { @format_o = Text::Format.new }
1139
+ assert(!@format_o.tag_paragraph)
1140
+ assert_nothing_raised { @format_o.tag_paragraph = true }
1141
+ assert(@format_o.tag_paragraph)
1142
+ assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
1143
+ Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
1144
+ end
1145
+
1146
+ def test_tag_text
1147
+ assert_nothing_raised { @format_o = Text::Format.new }
1148
+ assert_equal([], @format_o.tag_text)
1149
+ assert_equal(@format_o.format(GETTYSBURG),
1150
+ Text::Format.new.format(GETTYSBURG))
1151
+ assert_nothing_raised {
1152
+ @format_o.tag_paragraph = true
1153
+ @format_o.tag_text = ["Gettysburg Address", "---"]
1154
+ }
1155
+ assert_not_equal(@format_o.format(GETTYSBURG),
1156
+ Text::Format.new.format(GETTYSBURG))
1157
+ assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG]),
1158
+ Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG]))
1159
+ assert_not_equal(@format_o.paragraphs([GETTYSBURG, GETTYSBURG,
1160
+ GETTYSBURG]),
1161
+ Text::Format.new.paragraphs([GETTYSBURG, GETTYSBURG,
1162
+ GETTYSBURG]))
1163
+ end
1164
+
1165
+ def test_justify?
1166
+ assert_nothing_raised { @format_o = Text::Format.new }
1167
+ assert(!@format_o.justify?)
1168
+ assert_nothing_raised {
1169
+ @format_o.format_style = Text::Format::RIGHT_ALIGN
1170
+ }
1171
+ assert(!@format_o.justify?)
1172
+ assert_nothing_raised {
1173
+ @format_o.format_style = Text::Format::RIGHT_FILL
1174
+ }
1175
+ assert(!@format_o.justify?)
1176
+ assert_nothing_raised {
1177
+ @format_o.format_style = Text::Format::JUSTIFY
1178
+ }
1179
+ assert(@format_o.justify?)
1180
+ # The format testing is done in test_format_style
1181
+ end
1182
+
1183
+ def test_left_align?
1184
+ assert_nothing_raised { @format_o = Text::Format.new }
1185
+ assert(@format_o.left_align?)
1186
+ assert_nothing_raised {
1187
+ @format_o.format_style = Text::Format::RIGHT_ALIGN
1188
+ }
1189
+ assert(!@format_o.left_align?)
1190
+ assert_nothing_raised {
1191
+ @format_o.format_style = Text::Format::RIGHT_FILL
1192
+ }
1193
+ assert(!@format_o.left_align?)
1194
+ assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
1195
+ assert(!@format_o.left_align?)
1196
+ # The format testing is done in test_format_style
1197
+ end
1198
+
1199
+ def test_left_margin
1200
+ assert_nothing_raised { @format_o = Text::Format.new }
1201
+ assert_equal(0, @format_o.left_margin)
1202
+ assert_nothing_raised { @format_o.left_margin = -3 }
1203
+ assert_equal(3, @format_o.left_margin)
1204
+ assert_nothing_raised { @format_o.left_margin = "9" }
1205
+ assert_equal(9, @format_o.left_margin)
1206
+ assert_nothing_raised { @format_o.left_margin = "-2" }
1207
+ assert_equal(2, @format_o.left_margin)
1208
+ assert_nothing_raised { @format_o.left_margin = 7 }
1209
+ assert_equal(7, @format_o.left_margin)
1210
+ assert_nothing_raised {
1211
+ ft = @format_o.format(GETTYSBURG).split("\n")
1212
+ assert_match(/^ {11}Four score/, ft[0])
1213
+ assert_match(/^ {7}November/, ft[-1])
1214
+ }
1215
+ end
1216
+
1217
+ def test_hard_margins
1218
+ assert_nothing_raised { @format_o = Text::Format.new }
1219
+ assert(!@format_o.hard_margins)
1220
+ assert_nothing_raised {
1221
+ @format_o.hard_margins = true
1222
+ @format_o.columns = 5
1223
+ @format_o.first_indent = 0
1224
+ @format_o.format_style = Text::Format::RIGHT_FILL
1225
+ }
1226
+ assert(@format_o.hard_margins)
1227
+ assert_equal(FIVE_COL, @format_o.format(GETTYSBURG))
1228
+ assert_nothing_raised {
1229
+ @format_o.split_rules |= Text::Format::SPLIT_CONTINUATION
1230
+ assert_equal(Text::Format::SPLIT_CONTINUATION_FIXED,
1231
+ @format_o.split_rules)
1232
+ }
1233
+ assert_equal(FIVE_CNT, @format_o.format(GETTYSBURG))
1234
+ end
1235
+
1236
+ # Tests both nobreak and nobreak_regex, since one is only useful
1237
+ # with the other.
1238
+ def test_nobreak
1239
+ assert_nothing_raised { @format_o = Text::Format.new }
1240
+ assert(!@format_o.nobreak)
1241
+ assert(@format_o.nobreak_regex.empty?)
1242
+ assert_nothing_raised {
1243
+ @format_o.nobreak = true
1244
+ @format_o.nobreak_regex = { '^this$' => '^continent$' }
1245
+ @format_o.columns = 77
1246
+ }
1247
+ assert(@format_o.nobreak)
1248
+ assert_equal({ '^this$' => '^continent$' }, @format_o.nobreak_regex)
1249
+ assert_match(/^this continent/,
1250
+ @format_o.format(GETTYSBURG).split("\n")[1])
1251
+ end
1252
+
1253
+ def test_right_align?
1254
+ assert_nothing_raised { @format_o = Text::Format.new }
1255
+ assert(!@format_o.right_align?)
1256
+ assert_nothing_raised {
1257
+ @format_o.format_style = Text::Format::RIGHT_ALIGN
1258
+ }
1259
+ assert(@format_o.right_align?)
1260
+ assert_nothing_raised {
1261
+ @format_o.format_style = Text::Format::RIGHT_FILL
1262
+ }
1263
+ assert(!@format_o.right_align?)
1264
+ assert_nothing_raised { @format_o.format_style = Text::Format::JUSTIFY }
1265
+ assert(!@format_o.right_align?)
1266
+ # The format testing is done in test_format_style
1267
+ end
1268
+
1269
+ def test_right_fill?
1270
+ assert_nothing_raised { @format_o = Text::Format.new }
1271
+ assert(!@format_o.right_fill?)
1272
+ assert_nothing_raised {
1273
+ @format_o.format_style = Text::Format::RIGHT_ALIGN
1274
+ }
1275
+ assert(!@format_o.right_fill?)
1276
+ assert_nothing_raised {
1277
+ @format_o.format_style = Text::Format::RIGHT_FILL
1278
+ }
1279
+ assert(@format_o.right_fill?)
1280
+ assert_nothing_raised {
1281
+ @format_o.format_style = Text::Format::JUSTIFY
1282
+ }
1283
+ assert(!@format_o.right_fill?)
1284
+ # The format testing is done in test_format_style
1285
+ end
1286
+
1287
+ def test_right_margin
1288
+ assert_nothing_raised { @format_o = Text::Format.new }
1289
+ assert_equal(0, @format_o.right_margin)
1290
+ assert_nothing_raised { @format_o.right_margin = -3 }
1291
+ assert_equal(3, @format_o.right_margin)
1292
+ assert_nothing_raised { @format_o.right_margin = "9" }
1293
+ assert_equal(9, @format_o.right_margin)
1294
+ assert_nothing_raised { @format_o.right_margin = "-2" }
1295
+ assert_equal(2, @format_o.right_margin)
1296
+ assert_nothing_raised { @format_o.right_margin = 7 }
1297
+ assert_equal(7, @format_o.right_margin)
1298
+ assert_nothing_raised {
1299
+ ft = @format_o.format(GETTYSBURG).split("\n")
1300
+ assert_match(/^ {4}Four score.*forth on$/, ft[0])
1301
+ assert_match(/^November/, ft[-1])
1302
+ }
1303
+ end
1304
+
1305
+ def test_tabstop
1306
+ assert_nothing_raised { @format_o = Text::Format.new }
1307
+ assert_equal(8, @format_o.tabstop)
1308
+ assert_nothing_raised { @format_o.tabstop = 7 }
1309
+ assert_equal(7, @format_o.tabstop)
1310
+ assert_nothing_raised { @format_o.tabstop = -3 }
1311
+ assert_equal(3, @format_o.tabstop)
1312
+ assert_nothing_raised { @format_o.tabstop = "9" }
1313
+ assert_equal(9, @format_o.tabstop)
1314
+ assert_nothing_raised { @format_o.tabstop = "-2" }
1315
+ assert_equal(2, @format_o.tabstop)
1316
+ end
1317
+
1318
+ def test_text
1319
+ assert_nothing_raised { @format_o = Text::Format.new }
1320
+ assert_equal([], @format_o.text)
1321
+ assert_nothing_raised { @format_o.text = "Test Text" }
1322
+ assert_equal("Test Text", @format_o.text)
1323
+ assert_nothing_raised { @format_o.text = ["Line 1", "Line 2"] }
1324
+ assert_equal(["Line 1", "Line 2"], @format_o.text)
1325
+ end
1326
+
1327
+ def test_s_new
1328
+ # new(NilClass) { block }
1329
+ assert_nothing_raised do
1330
+ @format_o = Text::Format.new {
1331
+ self.text = "Test 1, 2, 3"
1332
+ }
1333
+ end
1334
+ assert_equal("Test 1, 2, 3", @format_o.text)
1335
+
1336
+ # new(Hash Symbols)
1337
+ assert_nothing_raised { @format_o = Text::Format.new(:columns => 72) }
1338
+ assert_equal(72, @format_o.columns)
1339
+
1340
+ # new(Hash String)
1341
+ assert_nothing_raised { @format_o = Text::Format.new('columns' => 72) }
1342
+ assert_equal(72, @format_o.columns)
1343
+
1344
+ # new(Hash) { block }
1345
+ assert_nothing_raised do
1346
+ @format_o = Text::Format.new('columns' => 80) {
1347
+ self.text = "Test 4, 5, 6"
1348
+ }
1349
+ end
1350
+ assert_equal("Test 4, 5, 6", @format_o.text)
1351
+ assert_equal(80, @format_o.columns)
1352
+
1353
+ # new(Text::Format)
1354
+ assert_nothing_raised do
1355
+ fo = Text::Format.new(@format_o)
1356
+ assert(fo == @format_o)
1357
+ end
1358
+
1359
+ # new(Text::Format) { block }
1360
+ assert_nothing_raised do
1361
+ fo = Text::Format.new(@format_o) { self.columns = 79 }
1362
+ assert(fo != @format_o)
1363
+ end
1364
+
1365
+ # new(String)
1366
+ assert_nothing_raised { @format_o = Text::Format.new("Test A, B, C") }
1367
+ assert_equal("Test A, B, C", @format_o.text)
1368
+
1369
+ # new(String) { block }
1370
+ assert_nothing_raised do
1371
+ @format_o = Text::Format.new("Test X, Y, Z") { self.columns = -5 }
1372
+ end
1373
+ assert_equal("Test X, Y, Z", @format_o.text)
1374
+ assert_equal(5, @format_o.columns)
1375
+ end
1376
+
1377
+ def test_center
1378
+ assert_nothing_raised { @format_o = Text::Format.new }
1379
+ assert_nothing_raised do
1380
+ ct = @format_o.center(GETTYSBURG.split("\n")).split("\n")
1381
+ assert_match(/^ Four score and seven years ago our fathers brought forth on this/, ct[0])
1382
+ assert_match(/^ not perish from the earth./, ct[-3])
1383
+ end
1384
+ end
1385
+
1386
+ def test_expand
1387
+ assert_nothing_raised { @format_o = Text::Format.new }
1388
+ assert_equal(" ", @format_o.expand("\t "))
1389
+ assert_nothing_raised { @format_o.tabstop = 4 }
1390
+ assert_equal(" ", @format_o.expand("\t "))
1391
+ end
1392
+
1393
+ def test_unexpand
1394
+ assert_nothing_raised { @format_o = Text::Format.new }
1395
+ assert_equal("\t ", @format_o.unexpand(" "))
1396
+ assert_nothing_raised { @format_o.tabstop = 4 }
1397
+ assert_equal("\t ", @format_o.unexpand(" "))
1398
+ end
1399
+
1400
+ def test_space_only
1401
+ assert_equal("", Text::Format.new.format(" "))
1402
+ assert_equal("", Text::Format.new.format("\n"))
1403
+ assert_equal("", Text::Format.new.format(" "))
1404
+ assert_equal("", Text::Format.new.format(" \n"))
1405
+ assert_equal("", Text::Format.new.paragraphs("\n"))
1406
+ assert_equal("", Text::Format.new.paragraphs(" "))
1407
+ assert_equal("", Text::Format.new.paragraphs(" "))
1408
+ assert_equal("", Text::Format.new.paragraphs(" \n"))
1409
+ assert_equal("", Text::Format.new.paragraphs(["\n"]))
1410
+ assert_equal("", Text::Format.new.paragraphs([" "]))
1411
+ assert_equal("", Text::Format.new.paragraphs([" "]))
1412
+ assert_equal("", Text::Format.new.paragraphs([" \n"]))
1413
+ end
1414
+
1415
+ def test_splendiferous
1416
+ h = nil
1417
+ test = "This is a splendiferous test"
1418
+ assert_nothing_raised { @format_o = Text::Format.new(:columns => 6, :left_margin => 0, :indent => 0, :first_indent => 0) }
1419
+ assert_match(/^splendiferous$/, @format_o.format(test))
1420
+ assert_nothing_raised { @format_o.hard_margins = true }
1421
+ assert_match(/^lendif$/, @format_o.format(test))
1422
+ assert_nothing_raised { h = Object.new }
1423
+ assert_nothing_raised do
1424
+ @format_o.split_rules = Text::Format::SPLIT_HYPHENATION
1425
+ class << h #:nodoc:
1426
+ def hyphenate_to(word, size)
1427
+ return ["", word] if size < 2
1428
+ [word[0 ... size], word[size .. -1]]
1429
+ end
1430
+ end
1431
+ @format_o.hyphenator = h
1432
+ end
1433
+ assert_match(/^iferou$/, @format_o.format(test))
1434
+ assert_nothing_raised { h = Object.new }
1435
+ assert_nothing_raised do
1436
+ class << h #:nodoc:
1437
+ def hyphenate_to(word, size, formatter)
1438
+ return ["", word] if word.size < formatter.columns
1439
+ [word[0 ... size], word[size .. -1]]
1440
+ end
1441
+ end
1442
+ @format_o.hyphenator = h
1443
+ end
1444
+ assert_match(/^ferous$/, @format_o.format(test))
1445
+ end
1446
+ end
1447
+ end