"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/lib/asciidoctor/helpers.rb" (1 Jun 2019, 10938 Bytes) of package /linux/www/asciidoctor-2.0.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "helpers.rb": 2.0.7_vs_2.0.8.

    1 # frozen_string_literal: true
    2 module Asciidoctor
    3 # Internal: Except where noted, a module that contains internal helper functions.
    4 module Helpers
    5   module_function
    6 
    7   # Public: Require the specified library using Kernel#require.
    8   #
    9   # Attempts to load the library specified in the first argument using the
   10   # Kernel#require. Rescues the LoadError if the library is not available and
   11   # passes a message to Kernel#raise if on_failure is :abort or Kernel#warn if
   12   # on_failure is :warn to communicate to the user that processing is being
   13   # aborted or functionality is disabled, respectively. If a gem_name is
   14   # specified, the message communicates that a required gem is not available.
   15   #
   16   # name       - the String name of the library to require.
   17   # gem_name   - a Boolean that indicates whether this library is provided by a RubyGem,
   18   #              or the String name of the RubyGem if it differs from the library name
   19   #              (default: true)
   20   # on_failure - a Symbol that indicates how to handle a load failure (:abort, :warn, :ignore) (default: :abort)
   21   #
   22   # Returns The [Boolean] return value of Kernel#require if the library can be loaded.
   23   # Otherwise, if on_failure is :abort, Kernel#raise is called with an appropriate message.
   24   # Otherwise, if on_failure is :warn, Kernel#warn is called with an appropriate message and nil returned.
   25   # Otherwise, nil is returned.
   26   def require_library name, gem_name = true, on_failure = :abort
   27     require name
   28   rescue ::LoadError
   29     include Logging unless include? Logging
   30     if gem_name
   31       gem_name = name if gem_name == true
   32       case on_failure
   33       when :abort
   34         details = $!.path == gem_name ? '' : %[ (reason: #{$!.path ? %(cannot load '#{$!.path}') : $!.message})]
   35         raise ::LoadError, %(asciidoctor: FAILED: required gem '#{gem_name}' is not available#{details}. Processing aborted.)
   36       when :warn
   37         details = $!.path == gem_name ? '' : %[ (reason: #{$!.path ? %(cannot load '#{$!.path}') : $!.message})]
   38         logger.warn %(optional gem '#{gem_name}' is not available#{details}. Functionality disabled.)
   39       end
   40     else
   41       case on_failure
   42       when :abort
   43         raise ::LoadError, %(asciidoctor: FAILED: #{$!.message.chomp '.'}. Processing aborted.)
   44       when :warn
   45         logger.warn %(#{$!.message.chomp '.'}. Functionality disabled.)
   46       end
   47     end
   48     nil
   49   end
   50 
   51   # Internal: Prepare the source data Array for parsing.
   52   #
   53   # Encodes the data to UTF-8, if necessary, and removes any trailing
   54   # whitespace from every line.
   55   #
   56   # If a BOM is found at the beginning of the data, a best attempt is made to
   57   # encode it to UTF-8 from the specified source encoding.
   58   #
   59   # data - the source data Array to prepare (no nil entries allowed)
   60   #
   61   # returns a String Array of prepared lines
   62   def prepare_source_array data
   63     return [] if data.empty?
   64     if (leading_2_bytes = (leading_bytes = (first = data[0]).unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
   65       data[0] = first.byteslice 2, first.bytesize
   66       # NOTE you can't split a UTF-16LE string using .lines when encoding is UTF-8; doing so will cause this line to fail
   67       return data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16LE).rstrip }
   68     elsif leading_2_bytes == BOM_BYTES_UTF_16BE
   69       data[0] = first.byteslice 2, first.bytesize
   70       return data.map {|line| (line.encode UTF_8, ::Encoding::UTF_16BE).rstrip }
   71     elsif leading_bytes == BOM_BYTES_UTF_8
   72       data[0] = first.byteslice 3, first.bytesize
   73     end
   74     if first.encoding == UTF_8
   75       data.map {|line| line.rstrip }
   76     else
   77       data.map {|line| (line.encode UTF_8).rstrip }
   78     end
   79   end
   80 
   81   # Internal: Prepare the source data String for parsing.
   82   #
   83   # Encodes the data to UTF-8, if necessary, splits it into an array, and
   84   # removes any trailing whitespace from every line.
   85   #
   86   # If a BOM is found at the beginning of the data, a best attempt is made to
   87   # encode it to UTF-8 from the specified source encoding.
   88   #
   89   # data - the source data String to prepare
   90   #
   91   # returns a String Array of prepared lines
   92   def prepare_source_string data
   93     return [] if data.nil_or_empty?
   94     if (leading_2_bytes = (leading_bytes = data.unpack 'C3').slice 0, 2) == BOM_BYTES_UTF_16LE
   95       data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16LE
   96     elsif leading_2_bytes == BOM_BYTES_UTF_16BE
   97       data = (data.byteslice 2, data.bytesize).encode UTF_8, ::Encoding::UTF_16BE
   98     elsif leading_bytes == BOM_BYTES_UTF_8
   99       data = data.byteslice 3, data.bytesize
  100       data = data.encode UTF_8 unless data.encoding == UTF_8
  101     elsif data.encoding != UTF_8
  102       data = data.encode UTF_8
  103     end
  104     [].tap {|lines| data.each_line {|line| lines << line.rstrip } }
  105   end
  106 
  107   # Internal: Efficiently checks whether the specified String resembles a URI
  108   #
  109   # Uses the Asciidoctor::UriSniffRx regex to check whether the String begins
  110   # with a URI prefix (e.g., http://). No validation of the URI is performed.
  111   #
  112   # str - the String to check
  113   #
  114   # returns true if the String is a URI, false if it is not
  115   def uriish? str
  116     (str.include? ':') && (UriSniffRx.match? str)
  117   end
  118 
  119   # Internal: Encode a URI component String for safe inclusion in a URI.
  120   #
  121   # str - the URI component String to encode
  122   #
  123   # Returns the String with all reserved URI characters encoded (e.g., /, &, =, space, etc).
  124   if RUBY_ENGINE == 'opal'
  125     def encode_uri_component str
  126       # patch necessary to adhere with RFC-3986 (and thus CGI.escape)
  127       # see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Description
  128       %x(
  129         return encodeURIComponent(str).replace(/%20|[!'()*]/g, function (m) {
  130           return m === '%20' ? '+' : '%' + m.charCodeAt(0).toString(16)
  131         })
  132       )
  133     end
  134   else
  135     CGI = ::CGI
  136     def encode_uri_component str
  137       CGI.escape str
  138     end
  139   end
  140 
  141   # Internal: Apply URI path encoding to spaces in the specified string (i.e., convert spaces to %20).
  142   #
  143   # str - the String to encode
  144   #
  145   # Returns the specified String with all spaces replaced with %20.
  146   def encode_spaces_in_uri str
  147     (str.include? ' ') ? (str.gsub ' ', '%20') : str
  148   end
  149 
  150   # Public: Removes the file extension from filename and returns the result
  151   #
  152   # filename - The String file name to process; expected to be a posix path
  153   #
  154   # Examples
  155   #
  156   #   Helpers.rootname 'part1/chapter1.adoc'
  157   #   # => "part1/chapter1"
  158   #
  159   # Returns the String filename with the file extension removed
  160   def rootname filename
  161     if (last_dot_idx = filename.rindex '.')
  162       (filename.index '/', last_dot_idx) ? filename : (filename.slice 0, last_dot_idx)
  163     else
  164       filename
  165     end
  166   end
  167 
  168   # Public: Retrieves the basename of the filename, optionally removing the extension, if present
  169   #
  170   # filename - The String file name to process.
  171   # drop_ext - A Boolean flag indicating whether to drop the extension
  172   #            or an explicit String extension to drop (default: nil).
  173   #
  174   # Examples
  175   #
  176   #   Helpers.basename 'images/tiger.png', true
  177   #   # => "tiger"
  178   #
  179   #   Helpers.basename 'images/tiger.png', '.png'
  180   #   # => "tiger"
  181   #
  182   # Returns the String filename with leading directories removed and, if specified, the extension removed
  183   def basename filename, drop_ext = nil
  184     if drop_ext
  185       ::File.basename filename, (drop_ext == true ? (extname filename) : drop_ext)
  186     else
  187       ::File.basename filename
  188     end
  189   end
  190 
  191   # Public: Returns whether this path has a file extension.
  192   #
  193   # path - The path String to check; expects a posix path
  194   #
  195   # Returns true if the path has a file extension, false otherwise
  196   def extname? path
  197     (last_dot_idx = path.rindex '.') && !(path.index '/', last_dot_idx)
  198   end
  199 
  200   # Public: Retrieves the file extension of the specified path. The file extension is the portion of the path in the
  201   # last path segment starting from the last period.
  202   #
  203   # This method differs from File.extname in that it gives us control over the fallback value and is more efficient.
  204   #
  205   # path     - The path String in which to look for a file extension
  206   # fallback - The fallback String to return if no file extension is present (optional, default: '')
  207   #
  208   # Returns the String file extension (with the leading dot included) or the fallback value if the path has no file extension.
  209   if ::File::ALT_SEPARATOR
  210     def extname path, fallback = ''
  211       if (last_dot_idx = path.rindex '.')
  212         (path.index '/', last_dot_idx) || (path.index ::File::ALT_SEPARATOR, last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
  213       else
  214         fallback
  215       end
  216     end
  217   else
  218     def extname path, fallback = ''
  219       if (last_dot_idx = path.rindex '.')
  220         (path.index '/', last_dot_idx) ? fallback : (path.slice last_dot_idx, path.length)
  221       else
  222         fallback
  223       end
  224     end
  225   end
  226 
  227   # Internal: Make a directory, ensuring all parent directories exist.
  228   def mkdir_p dir
  229     unless ::File.directory? dir
  230       unless (parent_dir = ::File.dirname dir) == '.'
  231         mkdir_p parent_dir
  232       end
  233       begin
  234         ::Dir.mkdir dir
  235       rescue ::SystemCallError
  236         raise unless ::File.directory? dir
  237       end
  238     end
  239   end
  240 
  241   ROMAN_NUMERALS = {
  242     'M' => 1000, 'CM' => 900, 'D' => 500, 'CD' => 400, 'C' => 100, 'XC' => 90,
  243     'L' => 50, 'XL' => 40, 'X' => 10, 'IX' => 9, 'V' => 5, 'IV' => 4, 'I' => 1
  244   }
  245   private_constant :ROMAN_NUMERALS
  246 
  247   # Internal: Converts an integer to a Roman numeral.
  248   #
  249   # val - the [Integer] value to convert
  250   #
  251   # Returns the [String] roman numeral for this integer
  252   def int_to_roman val
  253     ROMAN_NUMERALS.map do |l, i|
  254       repeat, val = val.divmod i
  255       l * repeat
  256     end.join
  257   end
  258 
  259   # Internal: Get the next value in the sequence.
  260   #
  261   # Handles both integer and character sequences.
  262   #
  263   # current - the value to increment as a String or Integer
  264   #
  265   # returns the next value in the sequence according to the current value's type
  266   def nextval current
  267     if ::Integer === current
  268       current + 1
  269     else
  270       intval = current.to_i
  271       if intval.to_s != current.to_s
  272         (current[0].ord + 1).chr
  273       else
  274         intval + 1
  275       end
  276     end
  277   end
  278 
  279   # Internal: Resolve the specified object as a Class
  280   #
  281   # object - The Object to resolve as a Class
  282   #
  283   # Returns a Class if the specified object is a Class (but not a Module) or
  284   # a String that resolves to a Class; otherwise, nil
  285   def resolve_class object
  286     ::Class === object ? object : (::String === object ? (class_for_name object) : nil)
  287   end
  288 
  289   # Internal: Resolves a Class object (not a Module) for the qualified name.
  290   #
  291   # Returns Class
  292   def class_for_name qualified_name
  293     raise unless ::Class === (resolved = ::Object.const_get qualified_name, false)
  294     resolved
  295   rescue
  296     raise ::NameError, %(Could not resolve class for name: #{qualified_name})
  297   end
  298 end
  299 end