govspeak 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6e0eb446c6565462c24ad3f78801c202917e9b20
4
- data.tar.gz: 16455957358cc36b9e743974e885c61b48259227
2
+ SHA256:
3
+ metadata.gz: c250f0022f95612e9050bc43e02f6af31655be6565d682c6eccea811c6a1988f
4
+ data.tar.gz: fcfe4cda98fa73030bcb4136c188b3e40e2dd66a0d2ecc51fb85fa9907aa68a0
5
5
  SHA512:
6
- metadata.gz: 4e89a5cc8e001699815ea701efef079fbf2ede8a25b99a0493f880135919c913f2186a3a2745fb4bd9bfa2331da156ba0b86da7e5b5a206eca4665dcf55a94a0
7
- data.tar.gz: e5295b34106249a7bc73751852688f0e00a80242380eaa6a09c94b2e185f6d5648132881148fdbc2d6658cbadd5b4352da340a7c89e5cdadf3b990b7654fa41b
6
+ metadata.gz: a4b73be188a3c99f33c02993c7dac9a6771e70d7c3d790b070da2c62208614dde600ce4c290a1906059d1bffd4a65c03796ff1996ff699a23ab706c9c950cd54
7
+ data.tar.gz: 7cc81ad911890c7baa0c1119b1733fad6e90b05d85ee748740e97fc82de093be9419bb909a51f80f94b9638f342889cc367c42a584ea191367812dbee496ce8c
@@ -1,3 +1,9 @@
1
+ ## 5.7.0
2
+
3
+ * Update ActionView to 5.x
4
+ * Fix translation files not loading when gem is used in an app
5
+ * Change the data inputted into Contacts to match contacts schema [#130](https://github.com/alphagov/govspeak/pull/130)
6
+
1
7
  ## 5.6.0
2
8
 
3
9
  * Update sanitize version to 4.6.x [#127](https://github.com/alphagov/govspeak/issues/127)
data/Rakefile CHANGED
@@ -12,10 +12,4 @@ Rake::TestTask.new("test") { |t|
12
12
  t.warning = true
13
13
  }
14
14
 
15
- require "gem_publisher"
16
- task :publish_gem do |t|
17
- gem = GemPublisher.publish_if_updated("govspeak.gemspec", :rubygems)
18
- puts "Published #{gem}" if gem
19
- end
20
-
21
- task :default => [:test]
15
+ task default: [:test]
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- lib = File.expand_path("../../lib", __FILE__)
3
+ lib = File.expand_path('../lib', __dir__)
4
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
 
6
6
  require "govspeak/cli"
@@ -4,6 +4,7 @@ require 'erb'
4
4
  require 'htmlentities'
5
5
  require 'kramdown'
6
6
  require 'kramdown/parser/kramdown_with_automatic_external_links'
7
+ require 'rinku'
7
8
  require 'govspeak/header_extractor'
8
9
  require 'govspeak/structured_header_extractor'
9
10
  require 'govspeak/html_validator'
@@ -16,14 +17,18 @@ require 'govspeak/presenters/attachment_presenter'
16
17
  require 'govspeak/presenters/contact_presenter'
17
18
  require 'govspeak/presenters/h_card_presenter'
18
19
 
20
+
19
21
  module Govspeak
22
+ def self.root
23
+ File.expand_path('..', File.dirname(__FILE__))
24
+ end
20
25
 
21
26
  class Document
22
-
23
27
  Parser = Kramdown::Parser::KramdownWithAutomaticExternalLinks
24
28
  PARSER_CLASS_NAME = Parser.name.split("::").last
29
+ UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.freeze
25
30
 
26
- @@extensions = []
31
+ @extensions = []
27
32
 
28
33
  attr_accessor :images
29
34
  attr_reader :attachments, :contacts, :links, :locale
@@ -32,6 +37,10 @@ module Govspeak
32
37
  new(source, options).to_html
33
38
  end
34
39
 
40
+ def self.extensions
41
+ @extensions
42
+ end
43
+
35
44
  def initialize(source, options = {})
36
45
  options = options.dup.deep_symbolize_keys
37
46
  @source = source ? source.dup : ""
@@ -40,25 +49,12 @@ module Govspeak
40
49
  @links = Array.wrap(options.delete(:links))
41
50
  @contacts = Array.wrap(options.delete(:contacts))
42
51
  @locale = options.fetch(:locale, "en")
43
- @options = {input: PARSER_CLASS_NAME}.merge(options)
52
+ @options = { input: PARSER_CLASS_NAME }.merge(options)
44
53
  @options[:entity_output] = :symbolic
45
- i18n_load_paths
46
- end
47
-
48
- def i18n_load_paths
49
- Dir.glob('locales/*.yml') do |f|
50
- I18n.load_path << f
51
- end
52
- end
53
- private :i18n_load_paths
54
-
55
- def kramdown_doc
56
- @kramdown_doc ||= Kramdown::Document.new(preprocess(@source), @options)
57
54
  end
58
- private :kramdown_doc
59
55
 
60
56
  def to_html
61
- @html ||= Govspeak::PostProcessor.process(kramdown_doc.to_html)
57
+ @to_html ||= Govspeak::PostProcessor.process(kramdown_doc.to_html)
62
58
  end
63
59
 
64
60
  def to_liquid
@@ -68,7 +64,11 @@ module Govspeak
68
64
  def t(*args)
69
65
  options = args.last.is_a?(Hash) ? args.last.dup : {}
70
66
  key = args.shift
71
- I18n.t(key, options.merge(locale: locale))
67
+ I18n.t!(key, options.merge(locale: locale))
68
+ end
69
+
70
+ def format_with_html_line_breaks(string)
71
+ ERB::Util.html_escape(string || "").strip.gsub(/(?:\r?\n)/, "<br/>").html_safe
72
72
  end
73
73
 
74
74
  def to_sanitized_html
@@ -99,9 +99,16 @@ module Govspeak
99
99
  Govspeak::LinkExtractor.new(self, website_root: website_root).call
100
100
  end
101
101
 
102
+ def extract_contact_content_ids
103
+ _, regex = self.class.extensions.find { |(title)| title == "Contact" }
104
+ return [] unless regex
105
+
106
+ @source.scan(regex).map(&:first).uniq.select { |id| id.match(UUID_REGEX) }
107
+ end
108
+
102
109
  def preprocess(source)
103
110
  source = Govspeak::BlockquoteExtraQuoteRemover.remove(source)
104
- @@extensions.each do |title,regexp,block|
111
+ self.class.extensions.each do |_, regexp, block|
105
112
  source.gsub!(regexp) {
106
113
  instance_exec(*Regexp.last_match.captures, &block)
107
114
  }
@@ -109,17 +116,12 @@ module Govspeak
109
116
  source
110
117
  end
111
118
 
112
- def encode(text)
113
- HTMLEntities.new.encode(text)
114
- end
115
- private :encode
116
-
117
119
  def self.extension(title, regexp = nil, &block)
118
120
  regexp ||= %r${::#{title}}(.*?){:/#{title}}$m
119
- @@extensions << [title, regexp, block]
121
+ @extensions << [title, regexp, block]
120
122
  end
121
123
 
122
- def self.surrounded_by(open, close=nil)
124
+ def self.surrounded_by(open, close = nil)
123
125
  open = Regexp::escape(open)
124
126
  if close
125
127
  close = Regexp::escape(close)
@@ -129,15 +131,15 @@ module Govspeak
129
131
  end
130
132
  end
131
133
 
132
- def self.wrap_with_div(class_name, character, parser=Kramdown::Document)
134
+ def self.wrap_with_div(class_name, character, parser = Kramdown::Document)
133
135
  extension(class_name, surrounded_by(character)) { |body|
134
136
  content = parser ? parser.new("#{body.strip}\n").to_html : body.strip
135
137
  %{\n<div class="#{class_name}">\n#{content}</div>\n}
136
138
  }
137
139
  end
138
140
 
139
- def insert_strong_inside_p(body, parser=Govspeak::Document)
140
- parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/,"<p><strong>\\1</strong></p>")
141
+ def insert_strong_inside_p(body, parser = Govspeak::Document)
142
+ parser.new(body.strip).to_html.sub(/^<p>(.*)<\/p>$/, "<p><strong>\\1</strong></p>")
141
143
  end
142
144
 
143
145
  extension('button', %r{
@@ -197,7 +199,7 @@ module Govspeak
197
199
  %{\n\n<div role="note" aria-label="Help" class="application-notice help-notice">\n#{Govspeak::Document.new(body.strip).to_html}</div>\n}
198
200
  }
199
201
 
200
- extension('barchart', /{barchart(.*?)}/) do |captures, body|
202
+ extension('barchart', /{barchart(.*?)}/) do |captures|
201
203
  stacked = '.mc-stacked' if captures.include? 'stacked'
202
204
  compact = '.compact' if captures.include? 'compact'
203
205
  negative = '.mc-negative' if captures.include? 'negative'
@@ -226,6 +228,7 @@ module Govspeak
226
228
  extension('attachment', /\[embed:attachments:(?!inline:|image:)\s*(.*?)\s*\]/) do |content_id, body|
227
229
  attachment = attachments.detect { |a| a[:content_id] == content_id }
228
230
  next "" unless attachment
231
+
229
232
  attachment = AttachmentPresenter.new(attachment)
230
233
  content = File.read(__dir__ + '/templates/attachment.html.erb')
231
234
  ERB.new(content).result(binding)
@@ -234,6 +237,7 @@ module Govspeak
234
237
  extension('attachment inline', /\[embed:attachments:inline:\s*(.*?)\s*\]/) do |content_id|
235
238
  attachment = attachments.detect { |a| a[:content_id] == content_id }
236
239
  next "" unless attachment
240
+
237
241
  attachment = AttachmentPresenter.new(attachment)
238
242
  span_id = attachment.id ? %{ id="attachment_#{attachment.id}"} : ""
239
243
  # new lines inside our title cause problems with govspeak rendering as this is expected to be on one line.
@@ -246,6 +250,7 @@ module Govspeak
246
250
  extension('attachment image', /\[embed:attachments:image:\s*(.*?)\s*\]/) do |content_id|
247
251
  attachment = attachments.detect { |a| a[:content_id] == content_id }
248
252
  next "" unless attachment
253
+
249
254
  attachment = AttachmentPresenter.new(attachment)
250
255
  title = (attachment.title || "").tr("\n", " ")
251
256
  render_image(attachment.url, title, nil, attachment.id)
@@ -265,8 +270,8 @@ module Govspeak
265
270
  id_attr = id ? %{ id="attachment_#{id}"} : ""
266
271
  lines = []
267
272
  lines << %{<figure#{id_attr} class="image embedded">}
268
- lines << %Q{<div class="img"><img src="#{encode(url)}" alt="#{encode(alt_text)}"></div>}
269
- lines << %Q{<figcaption>#{caption.strip}</figcaption>} if caption && !caption.strip.empty?
273
+ lines << %{<div class="img"><img src="#{encode(url)}" alt="#{encode(alt_text)}"></div>}
274
+ lines << %{<figcaption>#{caption.strip}</figcaption>} if caption && !caption.strip.empty?
270
275
  lines << '</figure>'
271
276
  lines.join
272
277
  end
@@ -281,7 +286,7 @@ module Govspeak
281
286
  wrap_with_div('call-to-action', '$CTA', Govspeak::Document)
282
287
 
283
288
  extension('address', surrounded_by("$A")) { |body|
284
- %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", "").gsub("\n", "<br />")}\n</p></div></div>\n}
289
+ %{\n<div class="address"><div class="adr org fn"><p>\n#{body.sub("\n", '').gsub("\n", '<br />')}\n</p></div></div>\n}
285
290
  }
286
291
 
287
292
  extension("legislative list", /(?<=\A|\n\n|\r\n\r\n)^\$LegislativeList\s*$(.*?)\$EndLegislativeList/m) do |body|
@@ -295,28 +300,29 @@ module Govspeak
295
300
  end
296
301
 
297
302
  extension("numbered list", /^[ \t]*((s\d+\.\s.*(?:\n|$))+)/) do |body|
298
- steps ||= 0
299
- body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do |b|
300
- "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
303
+ body.gsub!(/s(\d+)\.\s(.*)(?:\n|$)/) do
304
+ "<li>#{Govspeak::Document.new($2.strip).to_html}</li>\n"
301
305
  end
302
306
  %{<ol class="steps">\n#{body}</ol>}
303
307
  end
304
308
 
305
309
  def self.devolved_options
306
- { 'scotland' => 'Scotland',
307
- 'england' => 'England',
308
- 'england-wales' => 'England and Wales',
309
- 'northern-ireland' => 'Northern Ireland',
310
- 'wales' => 'Wales',
311
- 'london' => 'London' }
312
- end
313
-
314
- devolved_options.each do |k,v|
315
- extension("devolved-#{k}",/:#{k}:(.*?):#{k}:/m) do |body|
316
- %{<div class="devolved-content #{k}">
317
- <p class="devolved-header">This section applies to #{v}</p>
318
- <div class="devolved-body">#{Govspeak::Document.new(body.strip).to_html}</div>
319
- </div>\n}
310
+ { 'scotland' => 'Scotland',
311
+ 'england' => 'England',
312
+ 'england-wales' => 'England and Wales',
313
+ 'northern-ireland' => 'Northern Ireland',
314
+ 'wales' => 'Wales',
315
+ 'london' => 'London' }
316
+ end
317
+
318
+ devolved_options.each do |k, v|
319
+ extension("devolved-#{k}", /:#{k}:(.*?):#{k}:/m) do |body|
320
+ <<~HTML
321
+ <div class="devolved-content #{k}">
322
+ <p class="devolved-header">This section applies to #{v}</p>
323
+ <div class="devolved-body">#{Govspeak::Document.new(body.strip).to_html}</div>
324
+ </div>
325
+ HTML
320
326
  end
321
327
  end
322
328
 
@@ -336,6 +342,7 @@ module Govspeak
336
342
  extension('embed link', /\[embed:link:\s*(.*?)\s*\]/) do |content_id|
337
343
  link = links.detect { |l| l[:content_id] == content_id }
338
344
  next "" unless link
345
+
339
346
  if link[:url]
340
347
  "[#{link[:title]}](#{link[:url]})"
341
348
  else
@@ -343,17 +350,31 @@ module Govspeak
343
350
  end
344
351
  end
345
352
 
346
- def render_hcard_address(contact)
347
- HCardPresenter.from_contact(contact).render
348
- end
349
- private :render_hcard_address
350
-
351
353
  extension('Contact', /\[Contact:\s*(.*?)\s*\]/) do |content_id|
352
354
  contact = contacts.detect { |c| c[:content_id] == content_id }
353
355
  next "" unless contact
356
+
354
357
  contact = ContactPresenter.new(contact)
355
358
  @renderer ||= ERB.new(File.read(__dir__ + '/templates/contact.html.erb'))
356
359
  @renderer.result(binding)
357
360
  end
361
+
362
+ private
363
+
364
+ def kramdown_doc
365
+ @kramdown_doc ||= Kramdown::Document.new(preprocess(@source), @options)
366
+ end
367
+
368
+ def encode(text)
369
+ HTMLEntities.new.encode(text)
370
+ end
371
+
372
+ def render_hcard_address(contact_address)
373
+ HCardPresenter.new(contact_address).render
374
+ end
358
375
  end
359
376
  end
377
+
378
+ I18n.load_path.unshift(
379
+ *Dir.glob(File.expand_path('locales/*.yml', Govspeak.root))
380
+ )
@@ -1,7 +1,7 @@
1
1
  module Govspeak
2
2
  module BlockquoteExtraQuoteRemover
3
- QUOTE = '"\u201C\u201D\u201E\u201F\u2033\u2036'
4
- LINE_BREAK = '\r\n?|\n'
3
+ QUOTE = '"\u201C\u201D\u201E\u201F\u2033\u2036'.freeze
4
+ LINE_BREAK = '\r\n?|\n'.freeze
5
5
 
6
6
  # used to remove quotes from a markdown blockquote, as these will be inserted
7
7
  # as part of the rendering
@@ -13,6 +13,7 @@ module Govspeak
13
13
  # > test
14
14
  def self.remove(source)
15
15
  return if source.nil?
16
+
16
17
  source.gsub(/^>[ \t]*[#{QUOTE}]*([^ \t\n].+?)[#{QUOTE}]*[ \t]*(#{LINE_BREAK}?)$/, '> \1\2')
17
18
  end
18
19
  end
@@ -20,6 +20,7 @@ module Govspeak
20
20
  command.action do |args, options|
21
21
  input = get_input($stdin, args, options)
22
22
  raise "Nothing to render. Use --help for assistance" unless input
23
+
23
24
  puts Govspeak::Document.new(input, govspeak_options(options)).to_html
24
25
  end
25
26
  end
@@ -31,6 +32,7 @@ module Govspeak
31
32
  def get_input(stdin, args, options)
32
33
  return stdin.read unless stdin.tty?
33
34
  return read_file(options.file) if options.file
35
+
34
36
  args.empty? ? nil : args.join(" ")
35
37
  end
36
38
 
@@ -41,11 +43,11 @@ module Govspeak
41
43
 
42
44
  def govspeak_options(command_options)
43
45
  string = if command_options.options_file
44
- read_file(command_options.options_file)
46
+ read_file(command_options.options_file)
45
47
  else
46
- command_options.options
48
+ command_options.options
47
49
  end
48
- string ? JSON.load(string) : {}
50
+ string ? JSON.parse(string) : {}
49
51
  end
50
52
  end
51
53
  end
@@ -18,12 +18,13 @@ module Govspeak
18
18
  end
19
19
 
20
20
  private
21
- def id(el)
22
- el.attr.fetch('id', generate_id(el.options[:raw_text]))
21
+
22
+ def id(element)
23
+ element.attr.fetch('id', generate_id(element.options[:raw_text]))
23
24
  end
24
25
 
25
- def build_header(el)
26
- Header.new(el.options[:raw_text], el.options[:level], id(el))
26
+ def build_header(element)
27
+ Header.new(element.options[:raw_text], element.options[:level], id(element))
27
28
  end
28
29
 
29
30
  def find_headers(parent)
@@ -35,7 +36,7 @@ module Govspeak
35
36
  parent.children.each do |child|
36
37
  if child.type == :header
37
38
  headers << build_header(child)
38
- elsif child.children.size > 0
39
+ elsif !child.children.empty?
39
40
  headers << find_headers(child)
40
41
  end
41
42
  end
@@ -2,7 +2,6 @@ require 'addressable/uri'
2
2
  require 'sanitize'
3
3
 
4
4
  class Govspeak::HtmlSanitizer
5
-
6
5
  class ImageSourceWhitelister
7
6
  def initialize(allowed_image_hosts)
8
7
  @allowed_image_hosts = allowed_image_hosts
@@ -21,7 +20,8 @@ class Govspeak::HtmlSanitizer
21
20
 
22
21
  class TableCellTextAlignWhitelister
23
22
  def call(sanitize_context)
24
- return unless ["td", "th"].include?(sanitize_context[:node_name])
23
+ return unless %w[td th].include?(sanitize_context[:node_name])
24
+
25
25
  node = sanitize_context[:node]
26
26
 
27
27
  # Kramdown uses text-align to allow table cells to be aligned
@@ -51,7 +51,7 @@ class Govspeak::HtmlSanitizer
51
51
 
52
52
  def sanitize_without_images
53
53
  config = sanitize_config
54
- Sanitize.clean(@dirty_html, Sanitize::Config.merge(config, elements: config[:elements] - ["img"]))
54
+ Sanitize.clean(@dirty_html, Sanitize::Config.merge(config, elements: config[:elements] - %w[img]))
55
55
  end
56
56
 
57
57
  def button_sanitize_config
@@ -68,8 +68,8 @@ class Govspeak::HtmlSanitizer
68
68
  attributes: {
69
69
  :all => Sanitize::Config::RELAXED[:attributes][:all] + ["role", "aria-label"],
70
70
  "a" => Sanitize::Config::RELAXED[:attributes]["a"] + button_sanitize_config,
71
- "th" => Sanitize::Config::RELAXED[:attributes]["th"] + ["style"],
72
- "td" => Sanitize::Config::RELAXED[:attributes]["td"] + ["style"],
71
+ "th" => Sanitize::Config::RELAXED[:attributes]["th"] + %w[style],
72
+ "td" => Sanitize::Config::RELAXED[:attributes]["td"] + %w[style],
73
73
  }
74
74
  )
75
75
  end
@@ -7,7 +7,7 @@ module Govspeak
7
7
  # match Kramdown's internals.
8
8
 
9
9
  def self.with_kramdown_ordered_lists_disabled
10
- original_list_start = list_start
10
+ original_list_start = list_start
11
11
  redefine_kramdown_const(:LIST_START, list_start_ul)
12
12
  list_parser = kramdown_parsers.delete(:list)
13
13
  Kramdown::Parser::Kramdown.define_parser(:list, list_start_ul)
@@ -18,7 +18,6 @@ module Govspeak
18
18
  kramdown_parsers[:list] = list_parser
19
19
  end
20
20
 
21
- private
22
21
  def self.list_start
23
22
  Kramdown::Parser::Kramdown::LIST_START
24
23
  end