diff --git a/Gemfile b/Gemfile index 916b72e..ea8418b 100644 --- a/Gemfile +++ b/Gemfile @@ -2,9 +2,3 @@ source 'https://rubygems.org' # Specify your gem's dependencies in truc.gemspec gemspec - -group :test do - gem 'hashery', '~> 2.1.1' - gem "yajl-ruby", require: 'yajl', platforms: [:ruby_19, :ruby_20, :ruby_21] -end - diff --git a/Gemfile.lock b/Gemfile.lock index 7a61db4..276c6d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -2,39 +2,36 @@ PATH remote: . specs: prismic.io (1.8.2) - hashery (~> 2.1.1) GEM remote: https://rubygems.org/ specs: - diff-lcs (1.5.0) - hashery (2.1.2) - mini_portile2 (2.8.2) - nokogiri (1.15.2) + diff-lcs (1.6.2) + mini_portile2 (2.8.9) + nokogiri (1.19.3) mini_portile2 (~> 2.8.2) racc (~> 1.4) - nokogiri (1.15.2-java) + nokogiri (1.19.3-java) racc (~> 1.4) - nokogiri (1.15.2-x86_64-darwin) + nokogiri (1.19.3-x86_64-darwin) racc (~> 1.4) - nokogiri (1.15.2-x86_64-linux) + nokogiri (1.19.3-x86_64-linux-gnu) racc (~> 1.4) - racc (1.7.0) - racc (1.7.0-java) - rspec (3.12.0) - rspec-core (~> 3.12.0) - rspec-expectations (~> 3.12.0) - rspec-mocks (~> 3.12.0) - rspec-core (3.12.2) - rspec-support (~> 3.12.0) - rspec-expectations (3.12.3) + racc (1.8.1) + racc (1.8.1-java) + rspec (3.13.2) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.6) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-mocks (3.12.5) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.8) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.12.0) - rspec-support (3.12.0) - yajl-ruby (1.4.1) + rspec-support (~> 3.13.0) + rspec-support (3.13.7) PLATFORMS java @@ -43,12 +40,10 @@ PLATFORMS x86_64-linux DEPENDENCIES - hashery (~> 2.1.1) - nokogiri (~> 1.6) + nokogiri prismic.io! - rspec (~> 3.12) - rspec-core (~> 3.12) - yajl-ruby + rspec + rspec-core BUNDLED WITH - 2.2.20 + 4.0.12 diff --git a/lib/prismic/cache/lru.rb b/lib/prismic/cache/lru.rb index 2ad4a2a..7bb7292 100644 --- a/lib/prismic/cache/lru.rb +++ b/lib/prismic/cache/lru.rb @@ -1,7 +1,3 @@ -# encoding: utf-8 - -require 'hashery' - module Prismic # This is a simple cache class provided with the prismic.io Ruby kit. # @@ -14,13 +10,12 @@ module Prismic # you can create your API object like this: `Prismic.api(url, cache: # Prismic::DefaultCache)` class LruCache - - # @return [LRUHash] attr_reader :intern # @param max_size [Fixnum] (100) The default maximum of keys to store - def initialize(max_size=100) - @intern = Hashery::LRUHash.new(max_size) + def initialize(max_size = 100) + @max_size = max_size + @intern = {} end # Add a cache entry. @@ -30,7 +25,9 @@ def initialize(max_size=100) # # @return [Object] The stored value def set(key, value, expired_in = nil) - @intern.store(key, { :data => value, :expired_in => expired_in = expired_in && Time.now.getutc.to_i + expired_in }) + @intern.delete(key) + @intern[key] = { data: value, expired_in: expired_in && Time.now.getutc.to_i + expired_in } + @intern.delete(@intern.keys.first) while @intern.size > @max_size value end @@ -45,16 +42,18 @@ def []=(key, value) # @return [Object] The cache object as was stored def get(key) return delete(key) if expired?(key) - include?(key) ? @intern[key][:data] : nil + return nil unless include?(key) + + entry = @intern.delete(key) + @intern[key] = entry + entry[:data] end - alias :[] :get + alias [] get def get_or_set(key, value = nil, expired_in = nil) - if include?(key) && !expired?(key) - return get(key) - else - set(key, block_given? ? yield : value, expired_in) - end + return get(key) if include?(key) && !expired?(key) + + set(key, block_given? ? yield : value, expired_in) end def delete(key) @@ -70,10 +69,10 @@ def delete(key) def has_key?(key) @intern.has_key?(key) end - alias :include? :has_key? + alias include? has_key? def expired?(key) - if include?(key) && @intern[key][:expired_in] != nil + if include?(key) && !@intern[key][:expired_in].nil? expired_in = @intern[key][:expired_in] expired_in && expired_in < Time.now.getutc.to_i else @@ -85,7 +84,7 @@ def expired?(key) def invalidate_all! @intern.clear end - alias :clear! :invalidate_all! + alias clear! invalidate_all! # Expose the Hash keys # @@ -102,20 +101,17 @@ def keys def size @intern.size end - alias :length :size - + alias length size end # Available as an api cache for testing purposes (no caching) class BasicNullCache - def get(key) - end + def get(key); end - def set(key, value = nil, expired_in = nil) + def set(_key, value = nil, _expired_in = nil) block_given? ? yield : value end - alias_method :get_or_set, :set - + alias get_or_set set end # This default instance is used by the API to avoid creating a new instance diff --git a/lib/prismic/fragments/structured_text.rb b/lib/prismic/fragments/structured_text.rb index 1ff8964..cc8c021 100644 --- a/lib/prismic/fragments/structured_text.rb +++ b/lib/prismic/fragments/structured_text.rb @@ -1,104 +1,74 @@ -# encoding: utf-8 module Prismic module Fragments class StructuredText < Fragment - - # Used during the call of {StructuredText#as_html} : blocks are first gathered by groups, - # so that list items of the same list are placed within the same group, allowing to frame - # their serialization with or
    ...
. - # Images, paragraphs, headings, embed, ... are then placed alone in their own BlockGroup. class BlockGroup attr_reader :kind, :blocks + def initialize(kind) @kind = kind @blocks = [] end + def <<(block) blocks << block end end + attr_accessor :blocks def initialize(blocks) @blocks = blocks end - # Serializes the current StructuredText fragment into a fully usable HTML code. - # You need to pass a proper link_resolver so that internal links are turned into the proper URL in - # your website. If you use a starter kit, one is provided, that you can still update later. - # - # This method simply executes the as_html methods on blocks; - # it is not advised to override this method if you want to change the HTML output, you should - # override the as_html method at the block level (like {Heading.as_html}, or {Preformatted.as_html}, - # for instance). - # @param link_resolver [LinkResolver] - # @param html_serializer [HtmlSerializer] - # @return [String] the resulting html snippet - - def as_html(link_resolver, html_serializer=nil) - # Defining blocks that deserve grouping, assigning them "group kind" names - block_group = ->(block){ - case block - when Block::ListItem - block.ordered? ? "ol" : "ul" - else - nil - end - } - # Initializing groups, which is an array of BlockGroup objects - groups, last = [], nil - blocks.each {|block| - group = block_group.(block) + def as_html(link_resolver, html_serializer = nil) + groups = [] + last = nil + blocks.each do |block| + group = if block.is_a?(Block::ListItem) + block.ordered? ? 'ol' : 'ul' + end groups << BlockGroup.new(group) if !last || group != last groups.last << block last = group - } - # HTML-serializing the groups object (delegating the serialization of Block objects), - # without forgetting to frame the BlockGroup objects right if needed - groups.map{|group| - html = group.blocks.map { |b| - b.as_html(link_resolver, html_serializer) - }.join + end + + result = String.new + groups.each_with_index do |group, i| + result << "\n\n" if i > 0 case group.kind - when "ol" - %(
    #{html}
) - when "ul" - %() + when 'ol' + result << '
    ' + group.blocks.each { |b| result << b.as_html(link_resolver, html_serializer) } + result << '
' + when 'ul' + result << '' else - html + group.blocks.each { |b| result << b.as_html(link_resolver, html_serializer) } end - }.join("\n\n") + end + result end - # Returns the StructuredText as plain text, with zero formatting. - # Non-textual blocks (like images and embeds) are simply ignored. - # - # @param separator [String] The string separator inserted between the blocks (a blank space by default) - # @return [String] The complete string representing the textual value of the StructuredText field. - def as_text(separator=' ') - blocks.map{|block| block.as_text }.compact.join(separator) + def as_text(separator = ' ') + blocks.map { |block| block.as_text }.compact.join(separator) end - # Finds the first highest title in a structured text def first_title - max_level = 6 # any title with a higher level kicks the current one out + max_level = 6 title = false @blocks.each do |block| - if block.is_a?(Prismic::Fragments::StructuredText::Block::Heading) - if block.level < max_level - title = block.text - max_level = block.level # new maximum - end + if block.is_a?(Prismic::Fragments::StructuredText::Block::Heading) && (block.level < max_level) + title = block.text + max_level = block.level end end title end class Span - # @return [Number] - attr_accessor :start - # @return [Number] - attr_accessor :end + attr_accessor :start, :end def initialize(start, finish) @start = start @@ -106,35 +76,38 @@ def initialize(start, finish) end class Label < Span - # @return [String] attr_accessor :label + def initialize(start, finish, label) super(start, finish) @label = label end - def serialize(text, link_resolver = nil) + + def serialize(text, _link_resolver = nil) "#{text}" end end class Em < Span - def serialize(text, link_resolver = nil) + def serialize(text, _link_resolver = nil) "#{text}" end end class Strong < Span - def serialize(text, link_resolver = nil) + def serialize(text, _link_resolver = nil) "#{text}" end end class Hyperlink < Span attr_accessor :link + def initialize(start, finish, link) super(start, finish) @link = link end + def serialize(text, link_resolver = nil) if link.is_a? Prismic::Fragments::DocumentLink and link.broken "#{text}" @@ -145,30 +118,28 @@ def serialize(text, link_resolver = nil) end end end - end class Block - - # Returns nil, as a block is not textual by default. - # This is meant to be overriden by textual blocks (see Prismic::Fragments::StructuredText::Block::Text.as_text, for instance) - # - # @return nil, always. def as_text nil end class Text - # @return [String] - attr_accessor :text - # @return [Array] - attr_accessor :spans - # @return [String] may be nil - attr_accessor :label + ESCAPE_MAP = { + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>' + }.freeze + ESCAPE_PATTERN = /['&"<>]/.freeze + + attr_accessor :text, :spans, :label def initialize(text, spans, label = nil) @text = text - @spans = spans.select{|span| span.start < span.end} + @spans = spans.select { |span| span.start < span.end } @label = label end @@ -176,91 +147,93 @@ def class_code (@label && %( class="#{label}")) || '' end - def as_html(link_resolver=nil, html_serializer=nil) - html = '' - # Getting Hashes of spanning tags to insert, sorted by starting position, and by ending position + def as_html(link_resolver = nil, html_serializer = nil) start_spans, end_spans = prepare_spans - # Open tags - stack = Array.new - (text.length + 1).times do |pos| # Looping to length + 1 to catch closing tags - end_spans[pos].each do |t| - # Close a tag - tag = stack.pop - inner_html = serialize(tag[:span], tag[:html], link_resolver, html_serializer) - if stack.empty? - # The tag was top-level - html += inner_html - else - # Add the content to the parent tag - stack[-1][:html] += inner_html + boundaries = boundary_positions + html = String.new + stack = [] + last_idx = boundaries.length - 1 + + boundaries.each_with_index do |pos, idx| + if (ending = end_spans[pos]) + ending.each do + tag = stack.pop + inner = serialize(tag[:span], tag[:html], link_resolver, html_serializer) + if stack.empty? + html << inner + else + stack[-1][:html] << inner + end end end - start_spans[pos].each do |tag| - # Open a tag - stack.push({ - :span => tag, - :html => '' - }) - end - if pos < text.length - if stack.empty? - # Top level text - html += cgi_escape_html(text[pos]) - else - # Inner text of a span - stack[-1][:html] += cgi_escape_html(text[pos]) + + if (starting = start_spans[pos]) + starting.each do |span| + stack.push(span: span, html: String.new) end end + + break if idx == last_idx + + next_pos = boundaries[idx + 1] + next if pos == next_pos + + escaped = cgi_escape_html(text[pos...next_pos]) + if stack.empty? + html << escaped + else + stack[-1][:html] << escaped + end end - html.gsub("\n", '
') + + html.gsub!("\n", '
') + html end def cgi_escape_html(string) - # We don't use CGI::escapeHTML because the implementation changed from 1.9 to 2.0 and that break tests - string.gsub(/['&\"<>]/, { - "'" => ''', - '&' => '&', - '"' => '"', - '<' => '<', - '>' => '>' - }) - end - - # Building two span Hashes: - # * start_spans, with the starting positions as keys, and spans as values - # * end_spans, with the ending positions as keys, and spans as values + string.gsub(ESCAPE_PATTERN, ESCAPE_MAP) + end + def prepare_spans - unless defined?(@prepared_spans) - start_spans = Hash.new{|h,k| h[k] = [] } - end_spans = Hash.new{|h,k| h[k] = [] } - spans.each {|span| - start_spans[span.start] << span - end_spans[span.end] << span - } - # Make sure the spans are sorted bigger first to respect the hierarchy - @start_spans = start_spans.each { |_, spans| spans.sort! { |a, b| b.end - b.start <=> a.end - a.start } } - @end_spans = end_spans + return [@start_spans, @end_spans] if @prepared_spans + + start_spans = Hash.new { |h, k| h[k] = [] } + end_spans = Hash.new { |h, k| h[k] = [] } + spans.each do |span| + start_spans[span.start] << span + end_spans[span.end] << span end + start_spans.each_value { |s| s.sort! { |a, b| (b.end - b.start) <=> (a.end - a.start) } } + + @start_spans = start_spans + @end_spans = end_spans + @prepared_spans = true [@start_spans, @end_spans] end - # Zero-formatted textual value of the block. - # - # @return The textual value. def as_text @text end def serialize(elt, text, link_resolver, html_serializer) custom_html = html_serializer && html_serializer.serialize(elt, text) - if custom_html.nil? - elt.serialize(text, link_resolver) - else - custom_html - end + custom_html.nil? ? elt.serialize(text, link_resolver) : custom_html end private :class_code, :cgi_escape_html + + private + + def boundary_positions + positions = [0, text.length] + spans.each do |span| + positions << span.start + positions << span.end + end + positions.uniq! + positions.sort! + positions + end end class Heading < Text @@ -271,7 +244,7 @@ def initialize(text, spans, level, label = nil) @level = level end - def as_html(link_resolver=nil, html_serializer=nil) + def as_html(link_resolver = nil, html_serializer = nil) custom_html = html_serializer && html_serializer.serialize(self, super) if custom_html.nil? %(#{super}) @@ -282,7 +255,7 @@ def as_html(link_resolver=nil, html_serializer=nil) end class Paragraph < Text - def as_html(link_resolver=nil, html_serializer=nil) + def as_html(link_resolver = nil, html_serializer = nil) custom_html = html_serializer && html_serializer.serialize(self, super) if custom_html.nil? %(#{super}

) @@ -293,7 +266,7 @@ def as_html(link_resolver=nil, html_serializer=nil) end class Preformatted < Text - def as_html(link_resolver=nil, html_serializer=nil) + def as_html(link_resolver = nil, html_serializer = nil) custom_html = html_serializer && html_serializer.serialize(self, super) if custom_html.nil? %(#{super}) @@ -305,14 +278,14 @@ def as_html(link_resolver=nil, html_serializer=nil) class ListItem < Text attr_accessor :ordered - alias :ordered? :ordered + alias ordered? ordered def initialize(text, spans, ordered, label = nil) super(text, spans, label) @ordered = ordered end - def as_html(link_resolver, html_serializer=nil) + def as_html(link_resolver, html_serializer = nil) custom_html = html_serializer && html_serializer.serialize(self, super) if custom_html.nil? %(#{super}) @@ -355,18 +328,13 @@ def link_to end def as_html(link_resolver, html_serializer = nil) - custom = nil - unless html_serializer.nil? - custom = html_serializer.serialize(self, '') - end - if custom.nil? - classes = ['block-img'] - unless @label.nil? - classes.push(@label) - end - %(

#{view.as_html(link_resolver)}

) + custom = html_serializer && html_serializer.serialize(self, '') + return custom unless custom.nil? + + if @label.nil? + %(

#{view.as_html(link_resolver)}

) else - custom + %(

#{view.as_html(link_resolver)}

) end end end @@ -396,19 +364,10 @@ def html end def as_html(link_resolver, html_serializer = nil) - custom = nil - unless html_serializer.nil? - custom = html_serializer.serialize(self, '') - end - if custom.nil? - embed.as_html(link_resolver) - else - custom - end + custom = html_serializer && html_serializer.serialize(self, '') + custom.nil? ? embed.as_html(link_resolver) : custom end - end - end end end diff --git a/prismic.gemspec b/prismic.gemspec index 4eef70c..85a7924 100644 --- a/prismic.gemspec +++ b/prismic.gemspec @@ -1,5 +1,4 @@ -# coding: utf-8 -lib = File.expand_path('../lib', __FILE__) +lib = File.expand_path('lib', __dir__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'prismic/version' @@ -8,8 +7,8 @@ Gem::Specification.new do |spec| spec.version = Prismic::VERSION spec.authors = ["Étienne Vallette d'Osia", 'Erwan Loisant', 'Samy Dindane', 'Rudy Rigot'] spec.email = ['evo@zenexity.com'] - spec.description = %q{The standard Prismic.io's API library.} - spec.summary = %q{Prismic.io development kit} + spec.description = "The standard Prismic.io's API library." + spec.summary = 'Prismic.io development kit' spec.homepage = 'http://prismic.io' spec.license = 'Apache-2.0' @@ -18,8 +17,7 @@ Gem::Specification.new do |spec| spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.require_paths = ['lib'] - spec.add_development_dependency 'rspec', '~> 3.12' - spec.add_development_dependency 'rspec-core', '~> 3.12' - spec.add_development_dependency 'nokogiri', '~> 1.6' - spec.add_runtime_dependency 'hashery', '~> 2.1.1' + spec.add_development_dependency 'nokogiri' + spec.add_development_dependency 'rspec' + spec.add_development_dependency 'rspec-core' end