random-words 1.0.11 → 1.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/randw CHANGED
@@ -4,188 +4,6 @@
4
4
  require_relative "../lib/random-words"
5
5
  require "optparse"
6
6
 
7
- module RandomWords
8
- # Terminal methods for testing
9
- class Terminal
10
- def initialize(source = :english)
11
- # Create an instance of the generator
12
- @sentence_generator = RandomWords::Generator.new(source)
13
- @sentence_generator.use_extended_punctuation = true
14
- @colors = {
15
- text: :yellow,
16
- counter: :boldcyan,
17
- marker: :boldgreen,
18
- bracket: :cyan,
19
- language: :boldmagenta,
20
- }
21
- @sources = @sentence_generator.sources.keys.map(&:to_s)
22
- end
23
-
24
- def colors
25
- {
26
- black: 30,
27
- red: 31,
28
- green: 32,
29
- yellow: 33,
30
- blue: 34,
31
- magenta: 35,
32
- cyan: 36,
33
- white: 37,
34
- boldblack: "1;30",
35
- boldred: "1;31",
36
- boldgreen: "1;32",
37
- boldyellow: "1;33",
38
- boldblue: "1;34",
39
- boldmagenta: "1;35",
40
- boldcyan: "1;36",
41
- boldwhite: "1;37",
42
- reset: 0,
43
- }
44
- end
45
-
46
- def colorize_text(text, color)
47
- return text unless $stdout.isatty
48
-
49
- return text unless colors.key?(color)
50
-
51
- color_code = colors[color]
52
-
53
- "\e[#{color_code}m#{text}\e[0m"
54
- end
55
-
56
- def header_1(text)
57
- puts colorize_text("\n\n#{text}", :boldgreen)
58
- puts colorize_text("=" * text.length, :boldgreen)
59
- puts "\n"
60
- end
61
-
62
- def header_2(text)
63
- puts colorize_text("\n\n#{text}", :boldyellow)
64
- puts colorize_text("-" * text.length, :boldyellow)
65
- puts "\n"
66
- end
67
-
68
- def paragraphs(length, count = 3)
69
- @sentence_generator.sentence_length = length
70
- @sentence_generator.paragraph_length = count
71
- @sentence_generator.source = @sources.sample
72
-
73
- header_2("Random Paragraph (#{@sentence_generator.paragraph_length} #{@sentence_generator.sentence_length} sentences)")
74
- graf = @sentence_generator.paragraph
75
- puts text(graf)
76
- puts counter("#{graf.split(/ /).count} words, #{graf.length} characters")
77
- end
78
-
79
- def sentence(length)
80
- header_2("Random #{length} sentence")
81
- @sentence_generator.sentence_length = length
82
- s = @sentence_generator.sentence
83
- puts text(s)
84
- puts counter("#{s.split(/ /).count} words, #{s.length} characters")
85
- end
86
-
87
- # Generate and print random combined sentences
88
- def combined_sentences(length = nil)
89
- number_of_sentences = length || 2
90
-
91
- header_1("Random Combined Sentences:")
92
- @sources.count.times do |i|
93
- @sentence_generator.source = @sources[i - 1]
94
- @sentence_generator.sentence_length = :medium
95
- @sentence_generator.paragraph_length = number_of_sentences
96
- @sentence_generator.sentences(number_of_sentences)
97
- s = @sentence_generator.sentence
98
- puts "#{marker} #{text(s)}"
99
- puts counter("#{s.split(/ /).count} words, #{s.length} characters")
100
- puts colorize_text(" " * 2, :boldwhite)
101
- end
102
- end
103
-
104
- def random_sentences
105
- header_1("Random Sentences")
106
- @sentence_generator.lengths.keys.each_with_index do |length, index|
107
- @sentence_generator.source = @sources[index]
108
- sentence(length)
109
- end
110
- end
111
-
112
- def random_paragraphs
113
- header_1("Random Paragraphs")
114
- @sentence_generator.lengths.keys.each_with_index do |length, index|
115
- @sentence_generator.source = @sources[index]
116
- paragraphs(length, 3)
117
- end
118
- end
119
-
120
- # Generate and print specified number of words
121
- def random_words
122
- number_of_words = []
123
- @sources.count.times do |i|
124
- puts i
125
- number_of_words << (i * i) * 5 + 10
126
- end
127
-
128
- header_1("#{number_of_words} Random Words")
129
- @sources.each_with_index do |source, index|
130
- header_2("#{number_of_words[index]} Random Words")
131
- @sentence_generator.source = source
132
- s = @sentence_generator.words(number_of_words[index])
133
- puts "#{marker} #{text(s)} "
134
- puts counter("#{s.split(/ /).count} words, #{s.length} characters")
135
- end
136
- end
137
-
138
- # Generate and print specified number of characters
139
- def random_characters
140
- header_1("Random Characters (exact length)")
141
- [20, 50, 120, 200, 500].each_with_index do |i, index|
142
- @sentence_generator.source = @sources[index]
143
- chars = @sentence_generator.characters(i, whole_words: true)
144
- puts "#{marker} #{colorize_text("#{i}:", :boldwhite)} #{text(chars)} #{counter(chars.length)}"
145
- end
146
-
147
- # Generate and print specified number of characters
148
- max_characters = [15, 25, 53, 110, 600]
149
- min_characters = [10, 20, 50, 100, 500]
150
-
151
- header_1("Random Characters (length range)")
152
- max_characters.count.times do |i|
153
- @sentence_generator.source = @sources[i]
154
- chars = @sentence_generator.characters(min_characters[i], max_characters[i], whole_words: true)
155
- range = "#{marker} #{colorize_text("[#{min_characters[i]}-#{max_characters[i]}]: ", :boldwhite)}"
156
- puts "#{range}#{text(chars)} #{counter(chars.length)}"
157
- end
158
- end
159
-
160
- def marker
161
- colorize_text("•", @colors[:marker])
162
- end
163
-
164
- def text(text)
165
- colorize_text(text, @colors[:text])
166
- end
167
-
168
- def language
169
- "#{colorize_text("(",
170
- @colors[:bracket])}#{colorize_text("#{@sentence_generator.source}", @colors[:language])}#{colorize_text(")",
171
- @colors[:bracket])}"
172
- end
173
-
174
- def counter(length)
175
- "#{colorize_text("[",
176
- @colors[:bracket])}#{colorize_text("#{length}", @colors[:counter])}#{colorize_text("]", @colors[:bracket])} #{language}"
177
- end
178
-
179
- def print_test
180
- combined_sentences(3)
181
- random_sentences
182
- random_paragraphs
183
- random_words
184
- random_characters
185
- end
186
- end
187
- end
188
-
189
7
  # Default options for the script
190
8
 
191
9
  @options = {
@@ -210,7 +28,11 @@ markdown_help = <<HELP
210
28
  mark: add ==highlights==
211
29
  headers: add headlines
212
30
  image: add images
31
+ hr: add horizontal rules
213
32
  table: add tables
33
+ fn: add footnotes
34
+ complete: complete HTML output
35
+ meta:[mmd|yaml]: add metadata header
214
36
  HELP
215
37
 
216
38
  OptionParser.new do |opts|
@@ -361,13 +183,18 @@ def markdown_settings(settings)
361
183
  mark: false,
362
184
  headers: false,
363
185
  table: false,
364
- extended: false
186
+ extended: false,
187
+ footnote: false,
188
+ hr: false,
189
+ meta_type: nil,
190
+ complete: false,
191
+ style: "style.css"
365
192
  }
366
193
  sources = []
367
194
  RandomWords::Generator.new(:latin).sources.each { |k, v| sources.concat(v.names.map(&:to_s)) }
368
195
 
369
196
  markdown_options = defaults.dup
370
- settings = settings.split(%r{[,/.\|]}).map(&:strip)
197
+ settings = settings.split(%r{[,/\|]}).map(&:strip)
371
198
 
372
199
  settings.each do |setting|
373
200
  case setting
@@ -384,7 +211,9 @@ def markdown_settings(settings)
384
211
  headers: true,
385
212
  image: true,
386
213
  table: true,
387
- extended: true
214
+ extended: true,
215
+ footnote: true,
216
+ hr: true
388
217
  }
389
218
  markdown_options.merge!(new_options)
390
219
  when Regexp.union(sources)
@@ -415,6 +244,23 @@ def markdown_settings(settings)
415
244
  markdown_options[:length] = setting.to_length
416
245
  when 'x', 'extended'
417
246
  markdown_options[:extended] = true
247
+ when 'fn', 'footnotes'
248
+ markdown_options[:footnote] = true
249
+ when 'img', 'image'
250
+ markdown_options[:image] = true
251
+ when 'hr'
252
+ markdown_options[:hr] = true
253
+ when 'comp', 'complete'
254
+ markdown_options[:complete] = true
255
+ when /^(style|css):(.*)$/i
256
+ markdown_options[:style] = Regexp.last_match(2)
257
+ when /^meta:(.*?)$/i
258
+ case Regexp.last_match(1)
259
+ when 'mmd', 'multimarkdown'
260
+ markdown_options[:meta_type] = :multimarkdown
261
+ when 'yaml'
262
+ markdown_options[:meta_type] = :yaml
263
+ end
418
264
  else
419
265
  markdown_options[:source] = setting.to_source
420
266
  end
@@ -110,7 +110,10 @@ module RandomWords
110
110
  # Define number of sentences in paragraphs
111
111
  # @param length [Integer] The number of sentences in the paragraph
112
112
  def paragraph_length=(length)
113
- raise ArgumentError, 'Paragraph length must be a positive integer' unless length.is_a?(Integer) && length.positive?
113
+ unless length.is_a?(Integer) && length.positive?
114
+ raise ArgumentError,
115
+ 'Paragraph length must be a positive integer'
116
+ end
114
117
 
115
118
  @paragraph_length = length
116
119
  end
@@ -200,7 +203,8 @@ module RandomWords
200
203
  end
201
204
 
202
205
  def use_extended_punctuation=(use_extended_punctuation)
203
- raise ArgumentError, 'use_extended_punctuation must be a boolean' unless [true, false].include?(use_extended_punctuation)
206
+ raise ArgumentError, 'use_extended_punctuation must be a boolean' unless [true,
207
+ false].include?(use_extended_punctuation)
204
208
 
205
209
  @use_extended_punctuation = use_extended_punctuation
206
210
  @terminators.concat(@config.dictionary[:extended_punctuation]) if use_extended_punctuation
@@ -362,15 +366,41 @@ module RandomWords
362
366
  # @param settings [Hash] Settings for generating markdown
363
367
  # @return [String] A randomly generated markdown string
364
368
  def markdown(settings = {})
365
- input = RandomWords::LoremMarkdown.new(settings).output
366
- RandomWords::HTML2Markdown.new(input).markdown
369
+ input = RandomWords::LoremHTML.new(settings)
370
+ meta = {}
371
+ if settings[:meta_type]
372
+ meta[:type] = settings[:meta_type]
373
+ meta[:title] = input.title if input.title
374
+ meta[:style] = settings[:style] || 'style.css'
375
+ meta[:date] = Time.now.strftime('%Y-%m-%d %H:%M:%S')
376
+ end
377
+ RandomWords::HTML2Markdown.new(input, nil, meta).to_s
367
378
  end
368
379
 
369
380
  # Generate random HTML
370
381
  # @param settings [Hash] Settings for generating HTML
371
382
  # @return [String] A randomly generated HTML string
372
383
  def html(settings = {})
373
- RandomWords::LoremMarkdown.new(settings).output
384
+ html = RandomWords::LoremHTML.new(settings)
385
+ if settings[:complete]
386
+ style = settings[:style] || 'style.css'
387
+ <<~EOOUTPUT
388
+ <!DOCTYPE html>
389
+ <html lang="en">
390
+ <head>
391
+ \t<meta charset="UTF-8">
392
+ \t<meta name="viewport" content="width=device-width, initial-scale=1.0">
393
+ \t<title>#{html.title}</title>
394
+ \t<link rel="stylesheet" href="#{style}">
395
+ </head>
396
+ <body>
397
+ #{html.output.indent("\t")}
398
+ </body>
399
+ </html>
400
+ EOOUTPUT
401
+ else
402
+ html.output
403
+ end
374
404
  end
375
405
 
376
406
  # Generate a random name
@@ -12,12 +12,18 @@ module RandomWords
12
12
  # Initialize the HTML2Markdown converter
13
13
  # @param str [String] The HTML string to convert
14
14
  # @param baseurl [String] The base URL for resolving relative links
15
- def initialize(str, baseurl = nil)
15
+ def initialize(str, baseurl = nil, meta = {})
16
16
  @links = []
17
+ @footnotes = []
17
18
  @baseuri = (baseurl ? URI.parse(baseurl) : nil)
18
19
  @section_level = 0
19
- @encoding = str.encoding
20
- @markdown = output_for(Nokogiri::HTML(str, baseurl).root).gsub(/\n\n\n+/, "\n\n\n")
20
+ input = str.is_a?(String) ? str : str.output
21
+ @encoding = input.encoding
22
+ @markdown = output_for(Nokogiri::HTML(input, baseurl).root).gsub(/\n\n\n+/, "\n\n\n").strip
23
+ @title = meta[:title] if meta[:title]
24
+ @date = meta[:date] if meta[:date]
25
+ @style = meta[:style] if meta[:style]
26
+ @meta_type = meta[:type] if meta[:type]
21
27
  end
22
28
 
23
29
  # Convert the HTML to Markdown
@@ -26,12 +32,45 @@ module RandomWords
26
32
  # converter = HTML2Markdown.new('<p>Hello world</p>')
27
33
  # puts converter.to_s
28
34
  def to_s
35
+ meta = ''
36
+
37
+ if @meta_type
38
+ meta = <<~EOMETA
39
+ title: #{@title}
40
+ date: #{@date}
41
+ css: #{@style}
42
+ EOMETA
43
+ case @meta_type
44
+ when :multimarkdown
45
+ meta = <<~EOF
46
+ #{meta}
47
+
48
+ EOF
49
+ when :yaml
50
+ meta = <<~EOF
51
+ ---
52
+ #{meta}
53
+ ---
54
+ EOF
55
+ end
56
+ end
29
57
  i = 0
30
58
  @markdown = TableCleanup.new(@markdown).clean
31
- "#{@markdown}\n\n" + @links.map { |link|
32
- i += 1
33
- "[#{i}]: #{link[:href]}" + (link[:title] ? %( "#{link[:title]}") : '')
34
- }.join("\n")
59
+ out = "#{meta}#{@markdown}"
60
+
61
+ if @links.any?
62
+ out += "\n\n" + @links.map do |link|
63
+ i += 1
64
+ "[#{i}]: #{link[:href]}" + (link[:title] ? %( "#{link[:title]}") : '')
65
+ end.join("\n")
66
+ end
67
+
68
+ if @footnotes.any?
69
+ out += "\n\n" + @footnotes.map { |link|
70
+ "[^fn#{link[:counter]}]: #{link[:title]}"
71
+ }.join("\n\n")
72
+ end
73
+ out
35
74
  end
36
75
 
37
76
  # Recursively convert child nodes
@@ -42,6 +81,14 @@ module RandomWords
42
81
  end.join
43
82
  end
44
83
 
84
+ # Add a footnote to the list of links
85
+ # @param counter [String] The footnote counter
86
+ # @param link [Hash] The link to add
87
+ def add_footnote(counter, link)
88
+ @footnotes << { counter: counter, title: link[:title] }
89
+ @footnotes.length
90
+ end
91
+
45
92
  # Add a link to the list of links
46
93
  # @param link [Hash] The link to add
47
94
  def add_link(link)
@@ -69,7 +116,7 @@ module RandomWords
69
116
  # @param str [String] The string to wrap
70
117
  # @return [String] The wrapped string
71
118
  def wrap(str)
72
- return str if str =~ /\n/
119
+ return str if str.include?("\n")
73
120
 
74
121
  out = []
75
122
  line = []
@@ -80,7 +127,7 @@ module RandomWords
80
127
  line = []
81
128
  end
82
129
  end
83
- out << (line.join(' ') + (str[-1..-1] =~ /[ \t\n]/ ? str[-1..-1] : ''))
130
+ out << (line.join(' ') + (/[ \t\n]/.match?(str[-1..-1]) ? str[-1..-1] : ''))
84
131
  out.join("\n")
85
132
  end
86
133
 
@@ -89,7 +136,13 @@ module RandomWords
89
136
  # @return [String] The converted Markdown string
90
137
  def output_for(node)
91
138
  case node.name
92
- when 'head', 'style', 'script'
139
+ when 'title'
140
+ @title = node.content
141
+ ''
142
+ when 'head'
143
+ children = output_for_children(node)
144
+ ''
145
+ when 'style', 'script'
93
146
  ''
94
147
  when 'br'
95
148
  " \n"
@@ -106,7 +159,8 @@ module RandomWords
106
159
  "==#{output_for_children(node)}=="
107
160
  when 'blockquote'
108
161
  @section_level += 1
109
- o = "\n\n> #{output_for_children(node).lstrip.gsub("\n", "\n> ")}\n\n".gsub(/> \n(> \n)+/, "> \n")
162
+ o = "\n\n> #{output_for_children(node).lstrip.gsub("\n", "\n> ")}\n\n".gsub(/> \n(> \n)+/, "> \n").sub(/> \n$/,
163
+ "\n")
110
164
  @section_level -= 1
111
165
  o
112
166
  when 'cite'
@@ -130,7 +184,7 @@ module RandomWords
130
184
  @in_pre = false
131
185
  node.text.strip
132
186
  else
133
- "`#{output_for_children(node).gsub("\n", ' ')}`"
187
+ "`#{output_for_children(node).tr("\n", ' ')}`"
134
188
  end
135
189
  when 'pre'
136
190
  @in_pre = true
@@ -138,19 +192,25 @@ module RandomWords
138
192
  lang = '' if lang.nil? || lang.empty?
139
193
  "\n\n```#{lang}\n" + output_for_children(node).lstrip + "\n```\n\n"
140
194
  when 'hr'
141
- "\n\n----\n\n"
195
+ "\n\n* * * *\n\n"
142
196
  when 'a', 'link'
143
197
  link = { href: node['href'], title: node['title'] }
144
- "[#{output_for_children(node).gsub("\n", ' ')}][#{add_link(link)}]"
198
+ if link[:href] =~ /^#fn:(\d+)/
199
+ counter = Regexp.last_match[1]
200
+ add_footnote(counter, link)
201
+ "[^fn#{counter}]"
202
+ else
203
+ "[#{output_for_children(node).tr("\n", ' ')}][#{add_link(link)}]"
204
+ end
145
205
  when 'img'
146
206
  link = { href: node['src'], title: node['title'] }
147
207
  "![#{node['alt']}][#{add_link(link)}]"
148
208
  when 'video', 'audio', 'embed'
149
209
  link = { href: node['src'], title: node['title'] }
150
- "[#{output_for_children(node).gsub("\n", ' ')}][#{add_link(link)}]"
210
+ "[#{output_for_children(node).tr("\n", ' ')}][#{add_link(link)}]"
151
211
  when 'object'
152
212
  link = { href: node['data'], title: node['title'] }
153
- "[#{output_for_children(node).gsub("\n", ' ')}][#{add_link(link)}]"
213
+ "[#{output_for_children(node).tr("\n", ' ')}][#{add_link(link)}]"
154
214
  when 'i', 'em', 'u'
155
215
  "_#{output_for_children(node)}_"
156
216
  when 'b', 'strong'
@@ -176,10 +236,10 @@ module RandomWords
176
236
  header = "\n"
177
237
  if @table_header
178
238
  @table_header = false
179
- cells = node.children.select do |c|
239
+ cells = node.children.count do |c|
180
240
  %w[th td].include?(c.name)
181
- end.count
182
- header = "\n|#{cells.times.map { '-------' }.join('|')}|\n"
241
+ end
242
+ header = "\n|#{Array.new(cells) { '-------' }.join('|')}|\n"
183
243
  end
184
244
  node.children.select do |c|
185
245
  %w[th td].include?(c.name)
@@ -190,7 +250,7 @@ module RandomWords
190
250
  "| #{output_for_children(node)} |"
191
251
  when 'text'
192
252
  # Sometimes Nokogiri lies. Force the encoding back to what we know it is
193
- if (c = node.content.force_encoding(@encoding)) =~ /\S/
253
+ if /\S/.match?((c = node.content.force_encoding(@encoding)))
194
254
  c.gsub!(/\n\n+/, '<$PreserveDouble$>')
195
255
  c.gsub!(/\s+/, ' ')
196
256
  c.gsub('<$PreserveDouble$>', "\n\n")
@@ -2,13 +2,16 @@
2
2
 
3
3
  module RandomWords
4
4
  # Generates random Lorem Ipsum text in Markdown format.
5
- class LoremMarkdown
5
+ class LoremHTML
6
6
  # Stores the output
7
7
  attr_accessor :output
8
8
 
9
9
  # Stores the RandomWords::Generator
10
10
  attr_accessor :generator
11
11
 
12
+ # Stores the title
13
+ attr_reader :title
14
+
12
15
  # Generates random Lorem Ipsum text.
13
16
  # @param options [Hash] Options for generating Lorem Ipsum text.
14
17
  # @return [String] A string of random Lorem Ipsum text.
@@ -18,7 +21,9 @@ module RandomWords
18
21
  strong: false,
19
22
  link: false,
20
23
  code: false,
21
- mark: false
24
+ mark: false,
25
+ footnote: false,
26
+ hr: false
22
27
  }
23
28
 
24
29
  defaults = {
@@ -36,7 +41,10 @@ module RandomWords
36
41
  mark: false,
37
42
  headers: false,
38
43
  table: false,
39
- extended: false
44
+ extended: false,
45
+ footnote: false,
46
+ hr: false,
47
+ image: false
40
48
  }
41
49
 
42
50
  @options = defaults.merge(options)
@@ -46,6 +54,7 @@ module RandomWords
46
54
  paragraph_length: @options[:sentences],
47
55
  use_extended_punctuation: @options[:extended]
48
56
  })
57
+ @title = fragment(2, 5).cap_first
49
58
 
50
59
  @output = ''
51
60
  strong = @options[:decorate]
@@ -53,16 +62,22 @@ module RandomWords
53
62
  links = @options[:link]
54
63
  code = @options[:code]
55
64
  mark = @options[:mark]
65
+ footnotes = @options[:footnote]
66
+ hr = @options[:hr]
67
+ image = @options[:image]
56
68
  force = []
57
69
  # Generate the specified number of paragraphs.
58
70
  @options[:grafs].times.with_index do |_, i|
59
- @output += paragraph(1, em: em, strong: strong, links: links, code: code, mark: mark, force: force)
71
+ @output += paragraph(1, em: em, strong: strong, links: links, code: code, mark: mark, footnotes: footnotes,
72
+ force: force)
60
73
  em = Random.rand(0..3).zero? && @options[:decorate]
61
74
  strong = Random.rand(0..3).zero? && @options[:decorate]
62
75
  links = Random.rand(0..3).zero? && @options[:link]
63
76
  code = Random.rand(0..3).zero? && @options[:code]
77
+ footnotes = Random.rand(0..3).zero? && @options[:footnote]
64
78
 
65
79
  if i == @options[:grafs] - 2
80
+ force << :footnote if !@added[:footnote] && @options[:footnote]
66
81
  force << :code if !@added[:code] && @options[:code]
67
82
  force << :link if !@added[:link] && @options[:link]
68
83
  force << :em if !@added[:em] && @options[:decorate]
@@ -92,6 +107,16 @@ module RandomWords
92
107
  inject_block(2, -> { list(items, :ol) })
93
108
  end
94
109
 
110
+ # Add horizontal rule if specified.
111
+ inject_block(1, -> { "<hr>\n\n" }) if @options[:hr]
112
+
113
+ # Add image if specified.
114
+ if @options[:image]
115
+ inject_block(1, lambda {
116
+ "<img src=\"https://picsum.photos/400/200\" alt=\"#{fragment(1, 4).cap_first}\" />\n\n"
117
+ })
118
+ end
119
+
95
120
  # Add definition list if specified.
96
121
  inject_block(1, -> { list(items, :dl) }) if @options[:dl]
97
122
 
@@ -147,6 +172,7 @@ module RandomWords
147
172
  strong = roll(2) && @options[:decorate]
148
173
  code = roll(4) && @options[:code]
149
174
  mark = roll(6) && @options[:mark]
175
+
150
176
  frag = fragment(1, 4).cap_first
151
177
  long_frag = fragment(4, 8).cap_first
152
178
  long_frag = inject_inline(long_frag, -> { link }) if links
@@ -154,6 +180,7 @@ module RandomWords
154
180
  long_frag = inject_inline(long_frag, -> { emphasis(:strong) }) if strong
155
181
  long_frag = inject_inline(long_frag, -> { code_span }, 1) if code
156
182
  long_frag = inject_inline(long_frag, -> { emphasis(:mark) }, 1) if mark
183
+
157
184
  long_frag = long_frag.restore_spaces if links
158
185
  if type == :dl
159
186
  ul += "\t<dt>#{frag}</dt>\n"
@@ -185,9 +212,7 @@ module RandomWords
185
212
  strong: @options[:decorate],
186
213
  links: @options[:link],
187
214
  code: @options[:code],
188
- mark: @options[:mark]).gsub(
189
- /\n+/, "\n"
190
- )
215
+ mark: @options[:mark]).squeeze("\n")
191
216
  quote += blockquote(level2, true).strip if level2.positive?
192
217
  "#{quote}#{cite}</blockquote>#{newline}"
193
218
  end
@@ -298,6 +323,16 @@ module RandomWords
298
323
  8).cap_first}</a>"
299
324
  end
300
325
 
326
+ # Generates a random footnote
327
+ # @return [String] The generated footnote.
328
+ def footnote
329
+ @added[:footnote] = true
330
+ @counter ||= 0
331
+ @counter += 1
332
+ %(<a%%href="#fn:#{@counter}"%%id="fnref:#{@counter}"%%title="#{fragment(10,
333
+ 20)}"%%class="footnote"><sup>#{@counter}</sup></a>)
334
+ end
335
+
301
336
  # Injects a block of text into the output.
302
337
  # @param [Integer] count The number of blocks to inject.
303
338
  # @param [Proc] block The block whose result to inject.
@@ -362,7 +397,7 @@ module RandomWords
362
397
  # @param [Boolean] mark Whether to include mark tags.
363
398
  # @param [Array] force An array of tags to force.
364
399
  # @return [String] The generated paragraph.
365
- def paragraph(count, em: false, strong: false, links: false, code: false, mark: false, force: [])
400
+ def paragraph(count, em: false, strong: false, links: false, code: false, mark: false, footnotes: false, force: [])
366
401
  output = ''
367
402
  s = { short: 2, medium: 4, long: 6, very_long: 8 }[@options[:length]]
368
403
  count.times do
@@ -372,13 +407,15 @@ module RandomWords
372
407
  should_code = force.include?(:code) || (code && roll(6))
373
408
  should_link = force.include?(:links) || (links && roll(4))
374
409
  should_mark = force.include?(:mark) || (mark && roll(8))
410
+ should_footnote = force.include?(:footnote) || (footnotes && roll(8))
411
+
375
412
  p = inject_inline(p, -> { link }) if should_link
376
413
  p = inject_inline(p, -> { emphasis('em') }) if should_em
377
414
  p = inject_inline(p, -> { emphasis('strong') }) if should_strong
378
415
  p = inject_inline(p, -> { emphasis('mark') }, 1) if should_mark
379
416
  p = inject_inline(p, -> { code_span }, 1) if should_code
380
-
381
- output += "<p>#{p.restore_spaces.cap_first.term('.')}</p>\n\n"
417
+ fn = should_footnote ? footnote.restore_spaces : ''
418
+ output += "<p>#{p.restore_spaces.cap_first.term('.')}#{fn}</p>\n\n"
382
419
  end
383
420
  output
384
421
  end