govspeak 5.6.0 → 5.7.0

Sign up to get free protection for your applications and to get access to all the features.
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