diff options
Diffstat (limited to 'lib')
790 files changed, 12938 insertions, 94833 deletions
diff --git a/lib/English.gemspec b/lib/English.gemspec index 5f4eb420c2..9c09555ca1 100644 --- a/lib/English.gemspec +++ b/lib/English.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "english" - spec.version = "0.8.0" + spec.version = "0.8.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] @@ -15,8 +15,13 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + excludes = %W[ + :^/test :^/spec :^/feature :^/bin + :^/Rakefile :^/Gemfile\* :^/.git* + :^/#{File.basename(__FILE__)} + ] + spec.files = IO.popen(%W[git ls-files -z --] + excludes, err: IO::NULL) do |f| + f.readlines("\x0", chomp: true) end spec.require_paths = ["lib"] end diff --git a/lib/English.rb b/lib/English.rb index 03fe721991..bf7896dcd6 100644 --- a/lib/English.rb +++ b/lib/English.rb @@ -9,7 +9,7 @@ # "waterbuffalo" =~ /buff/ # print $', $$, "\n" # -# With English: +# With 'English': # # require "English" # @@ -20,30 +20,30 @@ # Below is a full list of descriptive aliases and their associated global # variable: # -# $ERROR_INFO:: $! -# $ERROR_POSITION:: $@ -# $FS:: $; -# $FIELD_SEPARATOR:: $; -# $OFS:: $, -# $OUTPUT_FIELD_SEPARATOR:: $, -# $RS:: $/ -# $INPUT_RECORD_SEPARATOR:: $/ -# $ORS:: $\ -# $OUTPUT_RECORD_SEPARATOR:: $\ -# $INPUT_LINE_NUMBER:: $. -# $NR:: $. -# $LAST_READ_LINE:: $_ -# $DEFAULT_OUTPUT:: $> -# $DEFAULT_INPUT:: $< -# $PID:: $$ -# $PROCESS_ID:: $$ -# $CHILD_STATUS:: $? -# $LAST_MATCH_INFO:: $~ -# $ARGV:: $* -# $MATCH:: $& -# $PREMATCH:: $` -# $POSTMATCH:: $' -# $LAST_PAREN_MATCH:: $+ +# <tt>$ERROR_INFO</tt>:: <tt>$!</tt> +# <tt>$ERROR_POSITION</tt>:: <tt>$@</tt> +# <tt>$FS</tt>:: <tt>$;</tt> +# <tt>$FIELD_SEPARATOR</tt>:: <tt>$;</tt> +# <tt>$OFS</tt>:: <tt>$,</tt> +# <tt>$OUTPUT_FIELD_SEPARATOR</tt>:: <tt>$,</tt> +# <tt>$RS</tt>:: <tt>$/</tt> +# <tt>$INPUT_RECORD_SEPARATOR</tt>:: <tt>$/</tt> +# <tt>$ORS</tt>:: <tt>$\</tt> +# <tt>$OUTPUT_RECORD_SEPARATOR</tt>:: <tt>$\</tt> +# <tt>$NR</tt>:: <tt>$.</tt> +# <tt>$INPUT_LINE_NUMBER</tt>:: <tt>$.</tt> +# <tt>$LAST_READ_LINE</tt>:: <tt>$_</tt> +# <tt>$DEFAULT_OUTPUT</tt>:: <tt>$></tt> +# <tt>$DEFAULT_INPUT</tt>:: <tt>$<</tt> +# <tt>$PID</tt>:: <tt>$$</tt> +# <tt>$PROCESS_ID</tt>:: <tt>$$</tt> +# <tt>$CHILD_STATUS</tt>:: <tt>$?</tt> +# <tt>$LAST_MATCH_INFO</tt>:: <tt>$~</tt> +# <tt>$ARGV</tt>:: <tt>$*</tt> +# <tt>$MATCH</tt>:: <tt>$&</tt> +# <tt>$PREMATCH</tt>:: <tt>$`</tt> +# <tt>$POSTMATCH</tt>:: <tt>$'</tt> +# <tt>$LAST_PAREN_MATCH</tt>:: <tt>$+</tt> # module English end if false diff --git a/lib/benchmark.gemspec b/lib/benchmark.gemspec deleted file mode 100644 index 35deff8d18..0000000000 --- a/lib/benchmark.gemspec +++ /dev/null @@ -1,32 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Yukihiro Matsumoto"] - spec.email = ["matz@ruby-lang.org"] - - spec.summary = %q{a performance benchmarking library} - spec.description = spec.summary - spec.homepage = "https://github.com/ruby/benchmark" - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.required_ruby_version = ">= 2.1.0" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" - spec.executables = [] - spec.require_paths = ["lib"] -end diff --git a/lib/benchmark.rb b/lib/benchmark.rb deleted file mode 100644 index 5072bdc2f3..0000000000 --- a/lib/benchmark.rb +++ /dev/null @@ -1,588 +0,0 @@ -# frozen_string_literal: true -#-- -# benchmark.rb - a performance benchmarking library -# -# $Id$ -# -# Created by Gotoken (gotoken@notwork.org). -# -# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and -# Gavin Sinclair (editing). -#++ -# -# == Overview -# -# The Benchmark module provides methods for benchmarking Ruby code, giving -# detailed reports on the time taken for each task. -# - -# The Benchmark module provides methods to measure and report the time -# used to execute Ruby code. -# -# * Measure the time to construct the string given by the expression -# <code>"a"*1_000_000_000</code>: -# -# require 'benchmark' -# -# puts Benchmark.measure { "a"*1_000_000_000 } -# -# On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates: -# -# 0.350000 0.400000 0.750000 ( 0.835234) -# -# This report shows the user CPU time, system CPU time, the sum of -# the user and system CPU times, and the elapsed real time. The unit -# of time is seconds. -# -# * Do some experiments sequentially using the #bm method: -# -# require 'benchmark' -# -# n = 5000000 -# Benchmark.bm do |x| -# x.report { for i in 1..n; a = "1"; end } -# x.report { n.times do ; a = "1"; end } -# x.report { 1.upto(n) do ; a = "1"; end } -# end -# -# The result: -# -# user system total real -# 1.010000 0.000000 1.010000 ( 1.014479) -# 1.000000 0.000000 1.000000 ( 0.998261) -# 0.980000 0.000000 0.980000 ( 0.981335) -# -# * Continuing the previous example, put a label in each report: -# -# require 'benchmark' -# -# n = 5000000 -# Benchmark.bm(7) do |x| -# x.report("for:") { for i in 1..n; a = "1"; end } -# x.report("times:") { n.times do ; a = "1"; end } -# x.report("upto:") { 1.upto(n) do ; a = "1"; end } -# end -# -# The result: -# -# user system total real -# for: 1.010000 0.000000 1.010000 ( 1.015688) -# times: 1.000000 0.000000 1.000000 ( 1.003611) -# upto: 1.030000 0.000000 1.030000 ( 1.028098) -# -# * The times for some benchmarks depend on the order in which items -# are run. These differences are due to the cost of memory -# allocation and garbage collection. To avoid these discrepancies, -# the #bmbm method is provided. For example, to compare ways to -# sort an array of floats: -# -# require 'benchmark' -# -# array = (1..1000000).map { rand } -# -# Benchmark.bmbm do |x| -# x.report("sort!") { array.dup.sort! } -# x.report("sort") { array.dup.sort } -# end -# -# The result: -# -# Rehearsal ----------------------------------------- -# sort! 1.490000 0.010000 1.500000 ( 1.490520) -# sort 1.460000 0.000000 1.460000 ( 1.463025) -# -------------------------------- total: 2.960000sec -# -# user system total real -# sort! 1.460000 0.000000 1.460000 ( 1.460465) -# sort 1.450000 0.010000 1.460000 ( 1.448327) -# -# * Report statistics of sequential experiments with unique labels, -# using the #benchmark method: -# -# require 'benchmark' -# include Benchmark # we need the CAPTION and FORMAT constants -# -# n = 5000000 -# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x| -# tf = x.report("for:") { for i in 1..n; a = "1"; end } -# tt = x.report("times:") { n.times do ; a = "1"; end } -# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } -# [tf+tt+tu, (tf+tt+tu)/3] -# end -# -# The result: -# -# user system total real -# for: 0.950000 0.000000 0.950000 ( 0.952039) -# times: 0.980000 0.000000 0.980000 ( 0.984938) -# upto: 0.950000 0.000000 0.950000 ( 0.946787) -# >total: 2.880000 0.000000 2.880000 ( 2.883764) -# >avg: 0.960000 0.000000 0.960000 ( 0.961255) - -module Benchmark - - VERSION = "0.3.0" - - BENCHMARK_VERSION = "2002-04-25" # :nodoc: - - # Invokes the block with a Benchmark::Report object, which - # may be used to collect and report on the results of individual - # benchmark tests. Reserves +label_width+ leading spaces for - # labels on each line. Prints +caption+ at the top of the - # report, and uses +format+ to format each line. - # (Note: +caption+ must contain a terminating newline character, - # see the default Benchmark::Tms::CAPTION for an example.) - # - # Returns an array of Benchmark::Tms objects. - # - # If the block returns an array of - # Benchmark::Tms objects, these will be used to format - # additional lines of output. If +labels+ parameter are - # given, these are used to label these extra lines. - # - # _Note_: Other methods provide a simpler interface to this one, and are - # suitable for nearly all benchmarking requirements. See the examples in - # Benchmark, and the #bm and #bmbm methods. - # - # Example: - # - # require 'benchmark' - # include Benchmark # we need the CAPTION and FORMAT constants - # - # n = 5000000 - # Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x| - # tf = x.report("for:") { for i in 1..n; a = "1"; end } - # tt = x.report("times:") { n.times do ; a = "1"; end } - # tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end } - # [tf+tt+tu, (tf+tt+tu)/3] - # end - # - # Generates: - # - # user system total real - # for: 0.970000 0.000000 0.970000 ( 0.970493) - # times: 0.990000 0.000000 0.990000 ( 0.989542) - # upto: 0.970000 0.000000 0.970000 ( 0.972854) - # >total: 2.930000 0.000000 2.930000 ( 2.932889) - # >avg: 0.976667 0.000000 0.976667 ( 0.977630) - # - - def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report - sync = $stdout.sync - $stdout.sync = true - label_width ||= 0 - label_width += 1 - format ||= FORMAT - print ' '*label_width + caption unless caption.empty? - report = Report.new(label_width, format) - results = yield(report) - Array === results and results.grep(Tms).each {|t| - print((labels.shift || t.label || "").ljust(label_width), t.format(format)) - } - report.list - ensure - $stdout.sync = sync unless sync.nil? - end - - - # A simple interface to the #benchmark method, #bm generates sequential - # reports with labels. +label_width+ and +labels+ parameters have the same - # meaning as for #benchmark. - # - # require 'benchmark' - # - # n = 5000000 - # Benchmark.bm(7) do |x| - # x.report("for:") { for i in 1..n; a = "1"; end } - # x.report("times:") { n.times do ; a = "1"; end } - # x.report("upto:") { 1.upto(n) do ; a = "1"; end } - # end - # - # Generates: - # - # user system total real - # for: 0.960000 0.000000 0.960000 ( 0.957966) - # times: 0.960000 0.000000 0.960000 ( 0.960423) - # upto: 0.950000 0.000000 0.950000 ( 0.954864) - # - - def bm(label_width = 0, *labels, &blk) # :yield: report - benchmark(CAPTION, label_width, FORMAT, *labels, &blk) - end - - - # Sometimes benchmark results are skewed because code executed - # earlier encounters different garbage collection overheads than - # that run later. #bmbm attempts to minimize this effect by running - # the tests twice, the first time as a rehearsal in order to get the - # runtime environment stable, the second time for - # real. GC.start is executed before the start of each of - # the real timings; the cost of this is not included in the - # timings. In reality, though, there's only so much that #bmbm can - # do, and the results are not guaranteed to be isolated from garbage - # collection and other effects. - # - # Because #bmbm takes two passes through the tests, it can - # calculate the required label width. - # - # require 'benchmark' - # - # array = (1..1000000).map { rand } - # - # Benchmark.bmbm do |x| - # x.report("sort!") { array.dup.sort! } - # x.report("sort") { array.dup.sort } - # end - # - # Generates: - # - # Rehearsal ----------------------------------------- - # sort! 1.440000 0.010000 1.450000 ( 1.446833) - # sort 1.440000 0.000000 1.440000 ( 1.448257) - # -------------------------------- total: 2.890000sec - # - # user system total real - # sort! 1.460000 0.000000 1.460000 ( 1.458065) - # sort 1.450000 0.000000 1.450000 ( 1.455963) - # - # #bmbm yields a Benchmark::Job object and returns an array of - # Benchmark::Tms objects. - # - def bmbm(width = 0) # :yield: job - job = Job.new(width) - yield(job) - width = job.width + 1 - sync = $stdout.sync - $stdout.sync = true - - # rehearsal - puts 'Rehearsal '.ljust(width+CAPTION.length,'-') - ets = job.list.inject(Tms.new) { |sum,(label,item)| - print label.ljust(width) - res = Benchmark.measure(&item) - print res.format - sum + res - }.format("total: %tsec") - print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-') - - # take - print ' '*width + CAPTION - job.list.map { |label,item| - GC.start - print label.ljust(width) - Benchmark.measure(label, &item).tap { |res| print res } - } - ensure - $stdout.sync = sync unless sync.nil? - end - - # - # Returns the time used to execute the given block as a - # Benchmark::Tms object. Takes +label+ option. - # - # require 'benchmark' - # - # n = 1000000 - # - # time = Benchmark.measure do - # n.times { a = "1" } - # end - # puts time - # - # Generates: - # - # 0.220000 0.000000 0.220000 ( 0.227313) - # - def measure(label = "") # :yield: - t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield - t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC) - Benchmark::Tms.new(t1.utime - t0.utime, - t1.stime - t0.stime, - t1.cutime - t0.cutime, - t1.cstime - t0.cstime, - r1 - r0, - label) - end - - # - # Returns the elapsed real time used to execute the given block. - # The unit of time is seconds. - # - # Benchmark.realtime { "a" * 1_000_000_000 } - # #=> 0.5098029999935534 - # - def realtime # :yield: - r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield - Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0 - end - - module_function :benchmark, :measure, :realtime, :bm, :bmbm - - # - # A Job is a sequence of labelled blocks to be processed by the - # Benchmark.bmbm method. It is of little direct interest to the user. - # - class Job # :nodoc: - # - # Returns an initialized Job instance. - # Usually, one doesn't call this method directly, as new - # Job objects are created by the #bmbm method. - # +width+ is a initial value for the label offset used in formatting; - # the #bmbm method passes its +width+ argument to this constructor. - # - def initialize(width) - @width = width - @list = [] - end - - # - # Registers the given label and block pair in the job list. - # - def item(label = "", &blk) # :yield: - raise ArgumentError, "no block" unless block_given? - label = label.to_s - w = label.length - @width = w if @width < w - @list << [label, blk] - self - end - - alias report item - - # An array of 2-element arrays, consisting of label and block pairs. - attr_reader :list - - # Length of the widest label in the #list. - attr_reader :width - end - - # - # This class is used by the Benchmark.benchmark and Benchmark.bm methods. - # It is of little direct interest to the user. - # - class Report # :nodoc: - # - # Returns an initialized Report instance. - # Usually, one doesn't call this method directly, as new - # Report objects are created by the #benchmark and #bm methods. - # +width+ and +format+ are the label offset and - # format string used by Tms#format. - # - def initialize(width = 0, format = nil) - @width, @format, @list = width, format, [] - end - - # - # Prints the +label+ and measured time for the block, - # formatted by +format+. See Tms#format for the - # formatting rules. - # - def item(label = "", *format, &blk) # :yield: - print label.to_s.ljust(@width) - @list << res = Benchmark.measure(label, &blk) - print res.format(@format, *format) - res - end - - alias report item - - # An array of Benchmark::Tms objects representing each item. - attr_reader :list - end - - - - # - # A data object, representing the times associated with a benchmark - # measurement. - # - class Tms - - # Default caption, see also Benchmark::CAPTION - CAPTION = " user system total real\n" - - # Default format string, see also Benchmark::FORMAT - FORMAT = "%10.6u %10.6y %10.6t %10.6r\n" - - # User CPU time - attr_reader :utime - - # System CPU time - attr_reader :stime - - # User CPU time of children - attr_reader :cutime - - # System CPU time of children - attr_reader :cstime - - # Elapsed real time - attr_reader :real - - # Total time, that is +utime+ + +stime+ + +cutime+ + +cstime+ - attr_reader :total - - # Label - attr_reader :label - - # - # Returns an initialized Tms object which has - # +utime+ as the user CPU time, +stime+ as the system CPU time, - # +cutime+ as the children's user CPU time, +cstime+ as the children's - # system CPU time, +real+ as the elapsed real time and +label+ as the label. - # - def initialize(utime = 0.0, stime = 0.0, cutime = 0.0, cstime = 0.0, real = 0.0, label = nil) - @utime, @stime, @cutime, @cstime, @real, @label = utime, stime, cutime, cstime, real, label.to_s - @total = @utime + @stime + @cutime + @cstime - end - - # - # Returns a new Tms object whose times are the sum of the times for this - # Tms object, plus the time required to execute the code block (+blk+). - # - def add(&blk) # :yield: - self + Benchmark.measure(&blk) - end - - # - # An in-place version of #add. - # Changes the times of this Tms object by making it the sum of the times - # for this Tms object, plus the time required to execute - # the code block (+blk+). - # - def add!(&blk) - t = Benchmark.measure(&blk) - @utime = utime + t.utime - @stime = stime + t.stime - @cutime = cutime + t.cutime - @cstime = cstime + t.cstime - @real = real + t.real - self - end - - # - # Returns a new Tms object obtained by memberwise summation - # of the individual times for this Tms object with those of the +other+ - # Tms object. - # This method and #/() are useful for taking statistics. - # - def +(other); memberwise(:+, other) end - - # - # Returns a new Tms object obtained by memberwise subtraction - # of the individual times for the +other+ Tms object from those of this - # Tms object. - # - def -(other); memberwise(:-, other) end - - # - # Returns a new Tms object obtained by memberwise multiplication - # of the individual times for this Tms object by +x+. - # - def *(x); memberwise(:*, x) end - - # - # Returns a new Tms object obtained by memberwise division - # of the individual times for this Tms object by +x+. - # This method and #+() are useful for taking statistics. - # - def /(x); memberwise(:/, x) end - - # - # Returns the contents of this Tms object as - # a formatted string, according to a +format+ string - # like that passed to Kernel.format. In addition, #format - # accepts the following extensions: - # - # <tt>%u</tt>:: Replaced by the user CPU time, as reported by Tms#utime. - # <tt>%y</tt>:: Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem") - # <tt>%U</tt>:: Replaced by the children's user CPU time, as reported by Tms#cutime - # <tt>%Y</tt>:: Replaced by the children's system CPU time, as reported by Tms#cstime - # <tt>%t</tt>:: Replaced by the total CPU time, as reported by Tms#total - # <tt>%r</tt>:: Replaced by the elapsed real time, as reported by Tms#real - # <tt>%n</tt>:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame") - # - # If +format+ is not given, FORMAT is used as default value, detailing the - # user, system and real elapsed time. - # - def format(format = nil, *args) - str = (format || FORMAT).dup - str.gsub!(/(%[-+.\d]*)n/) { "#{$1}s" % label } - str.gsub!(/(%[-+.\d]*)u/) { "#{$1}f" % utime } - str.gsub!(/(%[-+.\d]*)y/) { "#{$1}f" % stime } - str.gsub!(/(%[-+.\d]*)U/) { "#{$1}f" % cutime } - str.gsub!(/(%[-+.\d]*)Y/) { "#{$1}f" % cstime } - str.gsub!(/(%[-+.\d]*)t/) { "#{$1}f" % total } - str.gsub!(/(%[-+.\d]*)r/) { "(#{$1}f)" % real } - format ? str % args : str - end - - # - # Same as #format. - # - def to_s - format - end - - # - # Returns a new 6-element array, consisting of the - # label, user CPU time, system CPU time, children's - # user CPU time, children's system CPU time and elapsed - # real time. - # - def to_a - [@label, @utime, @stime, @cutime, @cstime, @real] - end - - # - # Returns a hash containing the same data as `to_a`. - # - def to_h - { - label: @label, - utime: @utime, - stime: @stime, - cutime: @cutime, - cstime: @cstime, - real: @real - } - end - - protected - - # - # Returns a new Tms object obtained by memberwise operation +op+ - # of the individual times for this Tms object with those of the other - # Tms object (+x+). - # - # +op+ can be a mathematical operation such as <tt>+</tt>, <tt>-</tt>, - # <tt>*</tt>, <tt>/</tt> - # - def memberwise(op, x) - case x - when Benchmark::Tms - Benchmark::Tms.new(utime.__send__(op, x.utime), - stime.__send__(op, x.stime), - cutime.__send__(op, x.cutime), - cstime.__send__(op, x.cstime), - real.__send__(op, x.real) - ) - else - Benchmark::Tms.new(utime.__send__(op, x), - stime.__send__(op, x), - cutime.__send__(op, x), - cstime.__send__(op, x), - real.__send__(op, x) - ) - end - end - end - - # The default caption string (heading above the output times). - CAPTION = Benchmark::Tms::CAPTION - - # The default format string used to display times. See also Benchmark::Tms#format. - FORMAT = Benchmark::Tms::FORMAT -end diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index 38057cf6c5..85f23b2596 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -1,15 +1,12 @@ # -*- frozen-string-literal: true -*- # :stopdoc: +module Gem +end +# :startdoc: -module Gem::BUNDLED_GEMS +module Gem::BUNDLED_GEMS # :nodoc: SINCE = { - "matrix" => "3.1.0", - "net-ftp" => "3.1.0", - "net-imap" => "3.1.0", - "net-pop" => "3.1.0", - "net-smtp" => "3.1.0", - "prime" => "3.1.0", "racc" => "3.3.0", "abbrev" => "3.4.0", "base64" => "3.4.0", @@ -23,36 +20,23 @@ module Gem::BUNDLED_GEMS "resolv-replace" => "3.4.0", "rinda" => "3.4.0", "syslog" => "3.4.0", - "ostruct" => "3.5.0", - "pstore" => "3.5.0", - "rdoc" => "3.5.0", - "win32ole" => "3.5.0", - "fiddle" => "3.5.0", - "logger" => "3.5.0", - "benchmark" => "3.5.0", - "irb" => "3.5.0", - "reline" => "3.5.0", - # "readline" => "3.5.0", # This is wrapper for reline. We don't warn for this. + "ostruct" => "4.0.0", + "pstore" => "4.0.0", + "rdoc" => "4.0.0", + "win32ole" => "4.0.0", + "fiddle" => "4.0.0", + "logger" => "4.0.0", + "benchmark" => "4.0.0", + "irb" => "4.0.0", + "reline" => "4.0.0", + # "readline" => "4.0.0", # This is wrapper for reline. We don't warn for this. + "tsort" => "4.1.0", }.freeze - SINCE_FAST_PATH = SINCE.transform_keys { |g| g.sub(/\A.*\-/, "") }.freeze - EXACT = { "kconv" => "nkf", }.freeze - PREFIXED = { - "bigdecimal" => true, - "csv" => true, - "drb" => true, - "rinda" => true, - "syslog" => true, - }.freeze - - OPTIONAL = { - "fiddle" => true, - }.freeze - WARNED = {} # unfrozen conf = ::RbConfig::CONFIG @@ -70,30 +54,11 @@ module Gem::BUNDLED_GEMS [::Kernel.singleton_class, ::Kernel].each do |kernel_class| kernel_class.send(:alias_method, :no_warning_require, :require) kernel_class.send(:define_method, :require) do |name| - - message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names) - begin - result = kernel_class.send(:no_warning_require, name) - rescue LoadError => e - result = e - end - - # Don't warn if the gem is optional dependency and not found in the Bundler environment. - if !(result.is_a?(LoadError) && OPTIONAL[name]) && message - if ::Gem::BUNDLED_GEMS.uplevel > 0 - Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel - else - Kernel.warn message - end - end - - if result.is_a?(LoadError) - raise result - else - result + if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names) + Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel end + kernel_class.send(:no_warning_require, name) end - if kernel_class == ::Kernel kernel_class.send(:private, :require) else @@ -104,108 +69,97 @@ module Gem::BUNDLED_GEMS def self.uplevel frame_count = 0 - frames_to_skip = 3 + require_labels = ["replace_require", "require"] uplevel = 0 require_found = false Thread.each_caller_location do |cl| frame_count += 1 - if frames_to_skip >= 1 - frames_to_skip -= 1 - next - end - uplevel += 1 + if require_found - if cl.base_label != "require" + unless require_labels.include?(cl.base_label) return uplevel end else - if cl.base_label == "require" + if require_labels.include?(cl.base_label) require_found = true end end + uplevel += 1 # Don't show script name when bundle exec and call ruby script directly. if cl.path.end_with?("bundle") - frame_count = 0 - break + return end end - require_found ? 1 : frame_count - 1 - end - - def self.find_gem(path) - if !path - return - elsif path.start_with?(ARCHDIR) - n = path.delete_prefix(ARCHDIR).sub(DLEXT, "") - elsif path.start_with?(LIBDIR) - n = path.delete_prefix(LIBDIR).chomp(".rb") - else - return - end - (EXACT[n] || !!SINCE[n]) or PREFIXED[n = n[%r[\A[^/]+(?=/)]]] && n + require_found ? 1 : (frame_count - 1).nonzero? end def self.warning?(name, specs: nil) # name can be a feature name or a file path with String or Pathname - feature = File.path(name) - - # irb already has reline as a dependency on gemspec, so we don't want to warn about it. - # We should update this with a more general solution when we have another case. - # ex: Gem.loaded_specs[called_gem].dependencies.any? {|d| d.name == feature } - return false if feature.start_with?("reline") && caller_locations(2, 1)[0].to_s.include?("irb") + feature = File.path(name).sub(LIBEXT, "") # The actual checks needed to properly identify the gem being required # are costly (see [Bug #20641]), so we first do a much cheaper check # to exclude the vast majority of candidates. - if feature.include?("/") - # If requiring $LIBDIR/mutex_m.rb, we check SINCE_FAST_PATH["mutex_m"] - # We'll fail to warn requires for files that are not the entry point - # of the gem, e.g. require "logger/formatter.rb" won't warn. - # But that's acceptable because this warning is best effort, - # and in the overwhelming majority of cases logger.rb will end - # up required. - return unless SINCE_FAST_PATH[File.basename(feature, ".*")] + subfeature = if feature.include?("/") + # bootsnap expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`, + # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. + feature.delete_prefix!(ARCHDIR) + feature.delete_prefix!(LIBDIR) + # 1. A segment for the EXACT mapping and SINCE check + # 2. A segment for the SINCE check for dashed names + # 3. A segment to check if there's a subfeature + segments = feature.split("/", 3) + name = segments.shift + name = EXACT[name] || name + if !SINCE[name] + name = "#{name}-#{segments.shift}" + return unless SINCE[name] + end + segments.any? else - return unless SINCE_FAST_PATH[feature] + name = EXACT[feature] || feature + return unless SINCE[name] + false + end + + if suppress_list = Thread.current[:__bundled_gems_warning_suppression] + return if suppress_list.include?(name) || suppress_list.include?(feature) end - # bootsnap expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`, - # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. - name = feature.delete_prefix(ARCHDIR) - name.delete_prefix!(LIBDIR) - name.tr!("/", "-") - name.sub!(LIBEXT, "") return if specs.include?(name) - _t, path = $:.resolve_feature_path(feature) - if gem = find_gem(path) - return if specs.include?(gem) - caller = caller_locations(3, 3)&.find {|c| c&.absolute_path} - return if find_gem(caller&.absolute_path) - elsif SINCE[name] && !path - gem = true - else - return + + # Don't warn if a hyphenated gem provides this feature + # (e.g., benchmark-ips provides benchmark/ips, not the benchmark gem) + if subfeature + feature_parts = feature.split("/") + if feature_parts.size >= 2 + hyphenated_gem = "#{feature_parts[0]}-#{feature_parts[1]}" + return if specs.include?(hyphenated_gem) + end end return if WARNED[name] WARNED[name] = true - if gem == true - gem = name - "#{feature} was loaded from the standard library, but" - elsif gem - return if WARNED[gem] - WARNED[gem] = true - "#{feature} is found in #{gem}, which" + + level = RUBY_VERSION < SINCE[name] ? :warning : :error + + if subfeature + "#{feature} is found in #{name}, which" else - return - end + build_message(gem) + "#{feature} #{level == :warning ? "was loaded" : "used to be loaded"} from the standard library, but" + end + build_message(name, level) end - def self.build_message(gem) - msg = " #{RUBY_VERSION < SINCE[gem] ? "will no longer be" : "is not"} part of the default gems starting from Ruby #{SINCE[gem]}." + def self.build_message(name, level) + msg = if level == :warning + " will no longer be part of the default gems starting from Ruby #{SINCE[name]}" + else + " is not part of the default gems since Ruby #{SINCE[name]}." + end if defined?(Bundler) - msg += "\nYou can add #{gem} to your Gemfile or gemspec to silence this warning." + motivation = level == :warning ? "silence this warning" : "fix this error" + msg += "\nYou can add #{name} to your Gemfile or gemspec to #{motivation}." # We detect the gem name from caller_locations. First we walk until we find `require` # then take the first frame that's not from `require`. @@ -243,31 +197,75 @@ module Gem::BUNDLED_GEMS end end if caller_gem - msg += "\nAlso please contact the author of #{caller_gem} to request adding #{gem} into its gemspec." + msg += "\nAlso please contact the author of #{caller_gem} to request adding #{name} into its gemspec." end end else - msg += " Install #{gem} from RubyGems." + msg += " Install #{name} from RubyGems." end msg end - freeze + def self.force_activate(gem) + require "bundler" + Bundler.reset! + + # Build and activate a temporary definition containing the original gems + the requested gem + builder = Bundler::Dsl.new + + lockfile = nil + if Bundler::SharedHelpers.in_bundle? && Bundler.definition.gemfiles.size > 0 + Bundler.definition.gemfiles.each {|gemfile| builder.eval_gemfile(gemfile) } + lockfile = begin + Bundler.default_lockfile + rescue Bundler::GemfileNotFound + nil + end + else + # Fake BUNDLE_GEMFILE and BUNDLE_LOCKFILE to let checks pass + orig_gemfile = ENV["BUNDLE_GEMFILE"] + orig_lockfile = ENV["BUNDLE_LOCKFILE"] + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock" + end + + builder.gem gem + + definition = builder.to_definition(lockfile, nil) + definition.validate_runtime! + + begin + orig_ui = Bundler.ui + orig_no_lock = Bundler::Definition.no_lock + + ui = Bundler::UI::Shell.new + ui.level = "silent" + Bundler.ui = ui + Bundler::Definition.no_lock = true + + Bundler::Runtime.new(nil, definition).setup + rescue Bundler::GemNotFound + warn "Failed to activate #{gem}, please install it with 'gem install #{gem}'" + ensure + ENV['BUNDLE_GEMFILE'] = orig_gemfile if orig_gemfile + ENV['BUNDLE_LOCKFILE'] = orig_lockfile if orig_lockfile + Bundler.ui = orig_ui + Bundler::Definition.no_lock = orig_no_lock + end + end end # for RubyGems without Bundler environment. # If loading library is not part of the default gems and the bundled gems, warn it. class LoadError - def message + def message # :nodoc: return super unless path name = path.tr("/", "-") if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name] - warn name + Gem::BUNDLED_GEMS.build_message(name), uplevel: Gem::BUNDLED_GEMS.uplevel + warn name + Gem::BUNDLED_GEMS.build_message(name, :error), uplevel: Gem::BUNDLED_GEMS.uplevel end super end end - -# :startdoc: diff --git a/lib/bundler.rb b/lib/bundler.rb index 3cb1f076a0..51ea3beeb0 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -1,16 +1,15 @@ # frozen_string_literal: true +require_relative "bundler/rubygems_ext" require_relative "bundler/vendored_fileutils" -require "pathname" +autoload :Pathname, "pathname" unless defined?(Pathname) require "rbconfig" require_relative "bundler/errors" require_relative "bundler/environment_preserver" require_relative "bundler/plugin" -require_relative "bundler/rubygems_ext" require_relative "bundler/rubygems_integration" require_relative "bundler/version" -require_relative "bundler/constants" require_relative "bundler/current_ruby" require_relative "bundler/build_metadata" @@ -52,16 +51,17 @@ module Bundler autoload :Env, File.expand_path("bundler/env", __dir__) autoload :Fetcher, File.expand_path("bundler/fetcher", __dir__) autoload :FeatureFlag, File.expand_path("bundler/feature_flag", __dir__) + autoload :FREEBSD, File.expand_path("bundler/constants", __dir__) autoload :GemHelper, File.expand_path("bundler/gem_helper", __dir__) - autoload :GemHelpers, File.expand_path("bundler/gem_helpers", __dir__) autoload :GemVersionPromoter, File.expand_path("bundler/gem_version_promoter", __dir__) - autoload :Graph, File.expand_path("bundler/graph", __dir__) autoload :Index, File.expand_path("bundler/index", __dir__) autoload :Injector, File.expand_path("bundler/injector", __dir__) autoload :Installer, File.expand_path("bundler/installer", __dir__) autoload :LazySpecification, File.expand_path("bundler/lazy_specification", __dir__) autoload :LockfileParser, File.expand_path("bundler/lockfile_parser", __dir__) autoload :MatchRemoteMetadata, File.expand_path("bundler/match_remote_metadata", __dir__) + autoload :Materialization, File.expand_path("bundler/materialization", __dir__) + autoload :NULL, File.expand_path("bundler/constants", __dir__) autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__) autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__) autoload :Resolver, File.expand_path("bundler/resolver", __dir__) @@ -80,6 +80,7 @@ module Bundler autoload :UI, File.expand_path("bundler/ui", __dir__) autoload :URICredentialsFilter, File.expand_path("bundler/uri_credentials_filter", __dir__) autoload :URINormalizer, File.expand_path("bundler/uri_normalizer", __dir__) + autoload :WINDOWS, File.expand_path("bundler/constants", __dir__) autoload :SafeMarshal, File.expand_path("bundler/safe_marshal", __dir__) class << self @@ -111,13 +112,13 @@ module Bundler end def configured_bundle_path - @configured_bundle_path ||= settings.path.tap(&:validate!) + @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!) end # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin - path = settings[:bin] || "bin" + path = Bundler.settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path mkdir_p(path) path @@ -171,14 +172,14 @@ module Bundler self_manager.restart_with_locked_bundler_if_needed end - # Automatically install dependencies if Bundler.settings[:auto_install] exists. + # Automatically install dependencies if settings[:auto_install] exists. # This is set through config cmd `bundle config set --global auto_install 1`. # # Note that this method `nil`s out the global Definition object, so it # should be called first, before you instantiate anything like an # `Installer` that'll keep a reference to the old one instead. def auto_install - return unless settings[:auto_install] + return unless Bundler.settings[:auto_install] begin definition.specs @@ -209,7 +210,6 @@ module Bundler # Bundler.require(:test) # requires second_gem # def require(*groups) - load_plugins setup(*groups).require(*groups) end @@ -218,8 +218,7 @@ module Bundler end def environment - SharedHelpers.major_deprecation 2, "Bundler.environment has been removed in favor of Bundler.load", print_caller_location: true - load + SharedHelpers.feature_removed! "Bundler.environment has been removed in favor of Bundler.load" end # Returns an instance of Bundler::Definition for given Gemfile and lockfile @@ -237,10 +236,10 @@ module Bundler end def frozen_bundle? - frozen = settings[:frozen] + frozen = Bundler.settings[:frozen] return frozen unless frozen.nil? - settings[:deployment] + Bundler.settings[:deployment] end def locked_gems @@ -253,12 +252,6 @@ module Bundler end end - def most_specific_locked_platform?(platform) - return false unless defined?(@definition) && @definition - - definition.most_specific_locked_platform == platform - end - def ruby_scope "#{Bundler.rubygems.ruby_engine}/#{RbConfig::CONFIG["ruby_version"]}" end @@ -347,7 +340,7 @@ module Bundler def app_cache(custom_path = nil) path = custom_path || root - Pathname.new(path).join(settings.app_cache_path) + Pathname.new(path).join(Bundler.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -370,16 +363,11 @@ module Bundler ORIGINAL_ENV.clone end - # @deprecated Use `unbundled_env` instead def clean_env - message = - "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \ - "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" removed_message = "`Bundler.clean_env` has been removed in favor of `Bundler.unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - unbundled_env + Bundler::SharedHelpers.feature_removed!(removed_message) end # @return [Hash] Environment with all bundler-related variables removed @@ -397,16 +385,11 @@ module Bundler with_env(original_env) { yield } end - # @deprecated Use `with_unbundled_env` instead def with_clean_env - message = - "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \ - "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`" removed_message = "`Bundler.with_clean_env` has been removed in favor of `Bundler.with_unbundled_env`. " \ "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { yield } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run block with all bundler-related variables removed @@ -419,16 +402,11 @@ module Bundler with_original_env { Kernel.system(*args) } end - # @deprecated Use `unbundled_system` instead def clean_system(*args) - message = - "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \ - "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`" removed_message = "`Bundler.clean_system` has been removed in favor of `Bundler.unbundled_system`. " \ "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { Kernel.system(*args) } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run subcommand in an environment with all bundler related variables removed @@ -441,16 +419,11 @@ module Bundler with_original_env { Kernel.exec(*args) } end - # @deprecated Use `unbundled_exec` instead def clean_exec(*args) - message = - "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \ - "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`" removed_message = "`Bundler.clean_exec` has been removed in favor of `Bundler.unbundled_exec`. " \ "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec`" - Bundler::SharedHelpers.major_deprecation(2, message, removed_message: removed_message, print_caller_location: true) - with_env(unbundled_env) { Kernel.exec(*args) } + Bundler::SharedHelpers.feature_removed!(removed_message) end # Run a `Kernel.exec` to a subcommand in an environment with all bundler related variables removed @@ -459,10 +432,14 @@ module Bundler end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] + return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform] Gem::Platform.local end + def generic_local_platform + Gem::Platform.generic(local_platform) + end + def default_gemfile SharedHelpers.default_gemfile end @@ -499,18 +476,27 @@ module Bundler end def which(executable) - if File.file?(executable) && File.executable?(executable) - executable - elsif paths = ENV["PATH"] + executable_path = find_executable(executable) + return executable_path if executable_path + + if (paths = ENV["PATH"]) quote = '"' paths.split(File::PATH_SEPARATOR).find do |path| path = path[1..-2] if path.start_with?(quote) && path.end_with?(quote) - executable_path = File.expand_path(executable, path) - return executable_path if File.file?(executable_path) && File.executable?(executable_path) + executable_path = find_executable(File.expand_path(executable, path)) + return executable_path if executable_path end end end + def find_executable(path) + extensions = RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split + extensions = [RbConfig::CONFIG["EXEEXT"]] unless extensions&.any? + candidates = extensions.map {|ext| "#{path}#{ext}" } + + candidates.find {|candidate| File.file?(candidate) && File.executable?(candidate) } + end + def read_file(file) SharedHelpers.filesystem_access(file, :read) do File.open(file, "r:UTF-8", &:read) @@ -542,15 +528,7 @@ module Bundler def load_gemspec_uncached(file, validate = false) path = Pathname.new(file) contents = read_file(file) - spec = if contents.start_with?("---") # YAML header - eval_yaml_gemspec(path, contents) - else - # Eval the gemspec from its parent directory, because some gemspecs - # depend on "./" relative paths. - SharedHelpers.chdir(path.dirname.to_s) do - eval_gemspec(path, contents) - end - end + spec = eval_gemspec(path, contents) return unless spec spec.loaded_from = path.expand_path.to_s Bundler.rubygems.validate(spec) if validate @@ -563,28 +541,11 @@ module Bundler def git_present? return @git_present if defined?(@git_present) - @git_present = Bundler.which("git#{RbConfig::CONFIG["EXEEXT"]}") + @git_present = Bundler.which("git") end def feature_flag - @feature_flag ||= FeatureFlag.new(VERSION) - end - - def load_plugins(definition = Bundler.definition) - return if defined?(@load_plugins_ran) - - Bundler.rubygems.load_plugins - - requested_path_gems = definition.requested_specs.select {|s| s.source.is_a?(Source::Path) } - path_plugin_files = requested_path_gems.map do |spec| - spec.matches_for_glob("rubygems_plugin#{Bundler.rubygems.suffix_pattern}") - rescue TypeError - error_message = "#{spec.name} #{spec.version} has an invalid gemspec" - raise Gem::InvalidSpecificationException, error_message - end.flatten - Bundler.rubygems.load_plugin_files(path_plugin_files) - Bundler.rubygems.load_env_plugins - @load_plugins_ran = true + @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION) end def reset! @@ -600,7 +561,6 @@ module Bundler def reset_paths! @bin_path = nil - @bundler_major_version = nil @bundle_path = nil @configure = nil @configured_bundle_path = nil @@ -669,12 +629,18 @@ module Bundler Kernel.require "psych" Gem::Specification.from_yaml(contents) - rescue ::Psych::SyntaxError, ArgumentError, Gem::EndOfYAMLException, Gem::Exception - eval_gemspec(path, contents) end def eval_gemspec(path, contents) - eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s) + if contents.start_with?("---") # YAML header + eval_yaml_gemspec(path, contents) + else + # Eval the gemspec from its parent directory, because some gemspecs + # depend on "./" relative paths. + SharedHelpers.chdir(path.dirname.to_s) do + eval(contents, TOPLEVEL_BINDING.dup, path.expand_path.to_s) + end + end rescue ScriptError, StandardError => e msg = "There was an error while loading `#{path.basename}`: #{e.message}" diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb index 5d2a8b53bb..49d2518078 100644 --- a/lib/bundler/build_metadata.rb +++ b/lib/bundler/build_metadata.rb @@ -4,21 +4,26 @@ module Bundler # Represents metadata from when the Bundler gem was built. module BuildMetadata # begin ivars - @release = false + @built_at = nil # end ivars # A hash representation of the build metadata. def self.to_h { - "Built At" => built_at, + "Timestamp" => timestamp, "Git SHA" => git_commit_sha, - "Released Version" => release?, } end + # A timestamp representing the date the bundler gem was built, or the + # current time if never built + def self.timestamp + @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze + end + # A string representing the date the bundler gem was built. def self.built_at - @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + @built_at end # The SHA for the git commit the bundler gem was built from. @@ -34,10 +39,5 @@ module Bundler @git_commit_sha ||= "unknown" end - - # Whether this is an official release build of Bundler. - def self.release? - @release - end end end diff --git a/lib/bundler/bundler.gemspec b/lib/bundler/bundler.gemspec index 88411f295d..49319e81b4 100644 --- a/lib/bundler/bundler.gemspec +++ b/lib/bundler/bundler.gemspec @@ -23,16 +23,16 @@ Gem::Specification.new do |s| s.description = "Bundler manages an application's dependencies through its entire life, across many machines, systematically and repeatably" s.metadata = { - "bug_tracker_uri" => "https://github.com/rubygems/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", - "changelog_uri" => "https://github.com/rubygems/rubygems/blob/master/bundler/CHANGELOG.md", + "bug_tracker_uri" => "https://github.com/ruby/rubygems/issues?q=is%3Aopen+is%3Aissue+label%3ABundler", + "changelog_uri" => "https://github.com/ruby/rubygems/blob/master/bundler/CHANGELOG.md", "homepage_uri" => "https://bundler.io/", - "source_code_uri" => "https://github.com/rubygems/rubygems/tree/master/bundler", + "source_code_uri" => "https://github.com/ruby/rubygems/tree/master/bundler", } - s.required_ruby_version = ">= 3.1.0" + s.required_ruby_version = ">= 3.2.0" # It should match the RubyGems version shipped with `required_ruby_version` above - s.required_rubygems_version = ">= 3.3.3" + s.required_rubygems_version = ">= 3.4.1" s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) } diff --git a/lib/bundler/capistrano.rb b/lib/bundler/capistrano.rb index 705840143f..6d2437d895 100644 --- a/lib/bundler/capistrano.rb +++ b/lib/bundler/capistrano.rb @@ -1,22 +1,4 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, - "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler" - -# Capistrano task for Bundler. -# -# Add "require 'bundler/capistrano'" in your Capistrano deploy.rb, and -# Bundler will be activated after each new deployment. -require_relative "deployment" -require "capistrano/version" - -if defined?(Capistrano::Version) && Gem::Version.new(Capistrano::Version).release >= Gem::Version.new("3.0") - raise "For Capistrano 3.x integration, please use https://github.com/capistrano/bundler" -end - -Capistrano::Configuration.instance(:must_exist).load do - before "deploy:finalize_update", "bundle:install" - Bundler::Deployment.define_task(self, :task, except: { no_release: true }) - set :rake, lambda { "#{fetch(:bundle_cmd, "bundle")} exec rake" } -end +Bundler::SharedHelpers.feature_removed! "The Bundler task for Capistrano. Please use https://github.com/capistrano/bundler" diff --git a/lib/bundler/checksum.rb b/lib/bundler/checksum.rb index 60ba93417c..ce05818bb0 100644 --- a/lib/bundler/checksum.rb +++ b/lib/bundler/checksum.rb @@ -126,7 +126,7 @@ module Bundler end def removable? - type == :lock || type == :gem + [:lock, :gem].include?(type) end def ==(other) @@ -190,7 +190,7 @@ module Bundler def replace(spec, checksum) return unless checksum - lock_name = spec.name_tuple.lock_name + lock_name = spec.lock_name @store_mutex.synchronize do existing = fetch_checksum(lock_name, checksum.algo) if !existing || existing.same_source?(checksum) @@ -201,10 +201,18 @@ module Bundler end end - def register(spec, checksum) - return unless checksum + def missing?(spec) + @store[spec.lock_name].nil? + end + + def empty?(spec) + return false unless spec.source.is_a?(Bundler::Source::Rubygems) + + @store[spec.lock_name].empty? + end - register_checksum(spec.name_tuple.lock_name, checksum) + def register(spec, checksum) + register_checksum(spec.lock_name, checksum) end def merge!(other) @@ -216,9 +224,9 @@ module Bundler end def to_lock(spec) - lock_name = spec.name_tuple.lock_name + lock_name = spec.lock_name checksums = @store[lock_name] - if checksums + if checksums&.any? "#{lock_name} #{checksums.values.map(&:to_lock).sort.join(",")}" else lock_name @@ -229,11 +237,15 @@ module Bundler def register_checksum(lock_name, checksum) @store_mutex.synchronize do - existing = fetch_checksum(lock_name, checksum.algo) - if existing - merge_checksum(lock_name, checksum, existing) + if checksum + existing = fetch_checksum(lock_name, checksum.algo) + if existing + merge_checksum(lock_name, checksum, existing) + else + store_checksum(lock_name, checksum) + end else - store_checksum(lock_name, checksum) + init_checksum(lock_name) end end end @@ -243,7 +255,11 @@ module Bundler end def store_checksum(lock_name, checksum) - (@store[lock_name] ||= {})[checksum.algo] = checksum + init_checksum(lock_name)[checksum.algo] = checksum + end + + def init_checksum(lock_name) + @store[lock_name] ||= {} end def fetch_checksum(lock_name, algo) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 18c41f9be6..1f6a65ca57 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -11,7 +11,7 @@ module Bundler AUTO_INSTALL_CMDS = %w[show binstubs outdated exec open console licenses clean].freeze PARSEABLE_COMMANDS = %w[check config help exec platform show version].freeze - EXTENSIONS = ["c", "rust"].freeze + EXTENSIONS = ["c", "rust", "go"].freeze COMMAND_ALIASES = { "check" => "c", @@ -24,7 +24,7 @@ module Bundler }.freeze def self.start(*) - check_deprecated_ext_option(ARGV) if ARGV.include?("--ext") + check_invalid_ext_option(ARGV) if ARGV.include?("--ext") super ensure @@ -59,17 +59,29 @@ module Bundler def initialize(*args) super + current_cmd = args.last[:current_command].name + custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] if custom_gemfile && !custom_gemfile.empty? Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) - Bundler.reset_settings_and_root! + reset_settings = true + end + + # lock --lockfile works differently than install --lockfile + unless current_cmd == "lock" + custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] + if custom_lockfile && !custom_lockfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) + reset_settings = true + end end + Bundler.reset_settings_and_root! if reset_settings + Bundler.auto_switch Bundler.settings.set_command_option_if_given :retry, options[:retry] - current_cmd = args.last[:current_command].name Bundler.auto_install if AUTO_INSTALL_CMDS.include?(current_cmd) rescue UnknownArgumentError => e raise InvalidOption, e.message @@ -77,7 +89,7 @@ module Bundler self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options["verbose"] + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end @@ -92,7 +104,7 @@ module Bundler primary_commands = ["install", "update", "cache", "exec", "config", "help"] list = self.class.printable_commands(true) - by_name = list.group_by {|name, _message| name.match(/^bundle (\w+)/)[1] } + by_name = list.group_by {|name, _message| name.match(/^bundler? (\w+)/)[1] } utilities = by_name.keys.sort - primary_commands primary_commands.map! {|name| (by_name[name] || raise("no primary command #{name}")).first } utilities.map! {|name| by_name[name].first } @@ -107,7 +119,33 @@ module Bundler shell.say self.class.send(:class_options_help, shell) end - default_task(Bundler.feature_flag.default_cli_command) + + desc "install_or_cli_help", "Deprecated alias of install", hide: true + def install_or_cli_help + Bundler.ui.warn <<~MSG + `bundle install_or_cli_help` is a deprecated alias of `bundle install`. + It might be called due to the 'default_cli_command' being set to 'install_or_cli_help', + if so fix that by running `bundle config set default_cli_command install --global`. + MSG + invoke_other_command("install") + end + + def self.default_command(meth = nil) + return super if meth + + unless Bundler.settings[:default_cli_command] + Bundler.ui.info <<~MSG + In a future version of Bundler, running `bundle` without argument will no longer run `bundle install`. + Instead, the `cli_help` command will be displayed. Please use `bundle install` explicitly for scripts like CI/CD. + You can use the future behavior now with `bundle config set default_cli_command cli_help --global`, + or you can continue to use the current behavior with `bundle config set default_cli_command install --global`. + This message will be removed after a default_cli_command value is set. + + MSG + end + + Bundler.settings[:default_cli_command] || "install" + end class_option "no-color", type: :boolean, desc: "Disable colorization in output" class_option "retry", type: :numeric, aliases: "-r", banner: "NUM", @@ -130,7 +168,7 @@ module Bundler if man_pages.include?(command) man_page = man_pages[command] - if Bundler.which("man") && !man_path.match?(%r{^file:/.+!/META-INF/jruby.home/.+}) + if Bundler.which("man") && !man_path.match?(%r{^(?:file:/.+!|uri:classloader:)/META-INF/jruby.home/.+}) Kernel.exec("man", man_page) else puts File.read("#{man_path}/#{File.basename(man_page)}.ronn") @@ -143,7 +181,7 @@ module Bundler end def self.handle_no_command_error(command, has_namespace = $thor_runner) - if Bundler.feature_flag.plugins? && Bundler::Plugin.command?(command) + if Bundler.settings[:plugins] && Bundler::Plugin.command?(command) return Bundler::Plugin.exec_command(command, ARGV[1..-1]) end @@ -173,7 +211,7 @@ module Bundler D method_option "dry-run", type: :boolean, default: false, banner: "Lock the Gemfile" method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)" def check remembered_flag_deprecation("path") @@ -187,12 +225,11 @@ module Bundler long_desc <<-D Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If the gem is not found, Bundler prints a error message and if gem could not be removed due to any reason Bundler will display a warning. D - method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile" + method_option "install", type: :boolean, banner: "Runs 'bundle install' after removing the gems from the Gemfile (removed)" def remove(*gems) if ARGV.include?("--install") - message = "The `--install` flag has been deprecated. `bundle install` is triggered by default." removed_message = "The `--install` flag has been removed. `bundle install` is triggered by default." - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/remove" @@ -210,44 +247,52 @@ module Bundler If the bundle has already been installed, bundler will tell you so and then exit. D - method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin" - method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install" - method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments" - method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install" + method_option "binstubs", type: :string, lazy_default: "bin", banner: "Generate bin stubs for bundled gems to ./bin (removed)" + method_option "clean", type: :boolean, banner: "Run bundle clean automatically after install (removed)" + method_option "deployment", type: :boolean, banner: "Install using defaults tuned for deployment environments (removed)" + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this install (removed)" method_option "full-index", type: :boolean, banner: "Fall back to using the single-file index of all gems" method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "jobs", aliases: "-j", type: :numeric, banner: "Specify the number of jobs to run in parallel" - method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" + method_option "lockfile", type: :string, banner: "Use the specified lockfile instead of the default." method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." - method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "no-lock", type: :boolean, banner: "Don't create a lockfile." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." method_option "quiet", type: :boolean, banner: "Only output warnings and errors." - method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')" + method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default, usually 'ruby' (removed)" method_option "standalone", type: :array, lazy_default: [], banner: "Make a bundle that can work without the Bundler runtime" - method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application" - method_option "trust-policy", alias: "P", type: :string, banner: "Gem trust policy (like gem install -P). Must be one of " + - Bundler.rubygems.security_policy_keys.join("|") - method_option "target-rbconfig", type: :string, banner: "rbconfig.rb for the deployment target platform" - - method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group." - method_option "with", type: :array, banner: "Include gems that are part of the specified named group." + method_option "system", type: :boolean, banner: "Install to the system location ($BUNDLE_PATH or $GEM_HOME) even if the bundle was previously installed somewhere else for this application (removed)" + method_option "trust-policy", alias: "P", type: :string, banner: "Gem trust policy (like gem install -P). Must be one of #{Bundler.rubygems.security_policy_keys.join("|")}" + method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform" + method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)." + method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)." def install - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") - %w[clean deployment frozen no-prune path shebang without with].each do |option| remembered_flag_deprecation(option) end print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system") - remembered_negative_flag_deprecation("no-deployment") + remembered_flag_deprecation("deployment", negative: true) + + if ARGV.include?("--binstubs") + removed_message = "The --binstubs option has been removed in favor of `bundle binstubs --all`" + raise InvalidOption, removed_message + end require_relative "cli/install" + options = self.options.dup + options["lockfile"] ||= ENV["BUNDLE_LOCKFILE"] Bundler.settings.temporary(no_install: false) do - Install.new(options.dup).run + Install.new(options).run end + rescue GemfileNotFound => error + invoke_other_command("cli_help") + raise error # re-raise to show the error and get a failing exit status end map aliases_for("install") @@ -265,7 +310,7 @@ module Bundler method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" @@ -276,7 +321,6 @@ module Bundler method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." method_option "all", type: :boolean, banner: "Update everything." def update(*gems) - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") require_relative "cli/update" Bundler.settings.temporary(no_install: false) do Update.new(options, gems).run @@ -288,15 +332,12 @@ module Bundler Show lists the names and versions of all gems that are required by your Gemfile. Calling show with [GEM] will list the exact location of that gem on your machine. D - method_option "paths", type: :boolean, - banner: "List the paths of all gems that are required by your Gemfile." - method_option "outdated", type: :boolean, - banner: "Show verbose output including whether gems are outdated." + method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile." + method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)." def show(gem_name = nil) if ARGV.include?("--outdated") - message = "the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement" - removed_message = "the `--outdated` flag to `bundle show` was undocumented and has been removed without replacement" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`" + raise InvalidOption, removed_message end require_relative "cli/show" Show.new(options, gem_name).run @@ -306,6 +347,7 @@ module Bundler method_option "name-only", type: :boolean, banner: "print only the gem names" method_option "only-group", type: :array, default: [], banner: "print gems from a given set of groups" method_option "without-group", type: :array, default: [], banner: "print all gems except from a given set of groups" + method_option "format", type: :string, banner: "format output ('json' is the only supported format)" method_option "paths", type: :boolean, banner: "print the path to each gem in the bundle" def list require_relative "cli/list" @@ -329,12 +371,14 @@ module Bundler will create binstubs for all given gems. D method_option "force", type: :boolean, default: false, banner: "Overwrite existing binstubs if they exist" - method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory (default bin)" + method_option "path", type: :string, lazy_default: "bin", banner: "Binstub destination directory, `bin` by default (removed)" method_option "shebang", type: :string, banner: "Specify a different shebang executable name than the default (usually 'ruby')" method_option "standalone", type: :boolean, banner: "Make binstubs that can work without the Bundler runtime" method_option "all", type: :boolean, banner: "Install binstubs for all gems" method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms" def binstubs(*gems) + remembered_flag_deprecation("path", option_name: "bin") + require_relative "cli/binstubs" Binstubs.new(options, gems).run end @@ -400,17 +444,15 @@ module Bundler end desc "cache [OPTIONS]", "Locks and then caches all of the gems into vendor/cache" - method_option "all", type: :boolean, - default: Bundler.feature_flag.cache_all?, - banner: "Include all sources (including path and git)." + method_option "all", type: :boolean, default: Bundler.settings[:cache_all], banner: "Include all sources (including path and git) (removed)." method_option "all-platforms", type: :boolean, banner: "Include gems for all platforms present in the lockfile, not only the current one" method_option "cache-path", type: :string, banner: "Specify a different cache path than the default (vendor/cache)." method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "no-install", type: :boolean, banner: "Don't install the gems, only update the cache." - method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." - method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" + method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache (removed)." + method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." method_option "quiet", type: :boolean, banner: "Only output warnings and errors." - method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install" + method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install (removed)" long_desc <<-D The cache command will copy the .gem files for every gem in the bundle into the directory ./vendor/cache. If you then check that directory into your source @@ -419,18 +461,19 @@ module Bundler D def cache print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") + print_remembered_flag_deprecation("--no-all", "cache_all", "false") if ARGV.include?("--no-all") - if ARGV.include?("--path") - message = - "The `--path` flag is deprecated because its semantics are unclear. " \ - "Use `bundle config cache_path` to configure the path of your cache of gems, " \ - "and `bundle config path` to configure the path where your gems are installed, " \ - "and stop using this flag" + %w[frozen no-prune].each do |option| + remembered_flag_deprecation(option) + end + + if flag_passed?("--path") removed_message = "The `--path` flag has been removed because its semantics were unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ - "and `bundle config path` to configure the path where your gems are installed." - SharedHelpers.major_deprecation 2, message, removed_message: removed_message + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + raise InvalidOption, removed_message end require_relative "cli/cache" @@ -440,8 +483,8 @@ module Bundler map aliases_for("cache") desc "exec [OPTIONS]", "Run the command in context of the bundle" - method_option :keep_file_descriptors, type: :boolean, default: true - method_option :gemfile, type: :string, required: false + method_option :keep_file_descriptors, type: :boolean, default: true, banner: "Passes all file descriptors to the new processes. Default is true, and setting it to false is not permitted (removed)." + method_option :gemfile, type: :string, required: false, banner: "Use the specified gemfile instead of Gemfile" long_desc <<-D Exec runs a command, providing it access to the gems in the bundle. While using bundle exec you can require and call the bundled gems as if they were installed @@ -449,9 +492,8 @@ module Bundler D def exec(*args) if ARGV.include?("--no-keep-file-descriptors") - message = "The `--no-keep-file-descriptors` has been deprecated. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" removed_message = "The `--no-keep-file-descriptors` has been removed. `bundle exec` no longer mess with your file descriptors. Close them in the exec'd script if you need to" - SharedHelpers.major_deprecation(2, message, removed_message: removed_message) + raise InvalidOption, removed_message end require_relative "cli/exec" @@ -482,25 +524,23 @@ module Bundler Open.new(options, name).run end - unless Bundler.feature_flag.bundler_3_mode? - desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" - def console(group = nil) - require_relative "cli/console" - Console.new(options, group).run - end + desc "console [GROUP]", "Opens an IRB session with the bundle pre-loaded" + def console(group = nil) + require_relative "cli/console" + Console.new(options, group).run end desc "version", "Prints Bundler version information" def version cli_help = current_command.name == "cli_help" if cli_help || ARGV.include?("version") - build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.print_only_version_number? - Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + if !cli_help + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else - Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" end end @@ -520,79 +560,43 @@ module Bundler end end - unless Bundler.feature_flag.bundler_3_mode? - desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true - long_desc <<-D - Viz generates a PNG file of the current Gemfile as a dependency graph. - Viz requires the ruby-graphviz gem (and its dependencies). - The associated gems must also be installed via 'bundle install'. - D - method_option :file, type: :string, default: "gem_graph", aliases: "-f", desc: "The name to use for the generated file. see format option" - method_option :format, type: :string, default: "png", aliases: "-F", desc: "This is output format option. Supported format is png, jpg, svg, dot ..." - method_option :requirements, type: :boolean, default: false, aliases: "-R", desc: "Set to show the version of each required dependency." - method_option :version, type: :boolean, default: false, aliases: "-v", desc: "Set to show each gem version." - method_option :without, type: :array, default: [], aliases: "-W", banner: "GROUP[ GROUP...]", desc: "Exclude gems that are part of the specified named group." - def viz - SharedHelpers.major_deprecation 2, "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" - require_relative "cli/viz" - Viz.new(options.dup).run - end + desc "viz [OPTIONS]", "Generates a visual dependency graph", hide: true + def viz + SharedHelpers.feature_removed! "The `viz` command has been renamed to `graph` and moved to a plugin. See https://github.com/rubygems/bundler-graph" end - old_gem = instance_method(:gem) - desc "gem NAME [OPTIONS]", "Creates a skeleton for creating a rubygem" - method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], desc: "Generate a binary executable for your library." - method_option :coc, type: :boolean, desc: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." - method_option :edit, type: :string, aliases: "-e", required: false, banner: "EDITOR", - lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, - desc: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" - method_option :ext, type: :string, desc: "Generate the boilerplate for C extension code.", enum: EXTENSIONS - method_option :git, type: :boolean, default: true, desc: "Initialize a git repo inside your library." - method_option :mit, type: :boolean, desc: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." - method_option :rubocop, type: :boolean, desc: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true`." - method_option :changelog, type: :boolean, desc: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." - method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", - enum: %w[rspec minitest test-unit], - desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." - method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", - enum: %w[github gitlab circle], - desc: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" - method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", - enum: %w[rubocop standard], - desc: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" + method_option :exe, type: :boolean, default: false, aliases: ["--bin", "-b"], banner: "Generate a binary executable for your library." + method_option :coc, type: :boolean, banner: "Generate a code of conduct file. Set a default with `bundle config set --global gem.coc true`." + method_option :edit, type: :string, aliases: "-e", required: false, lazy_default: [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }, banner: "Open generated gemspec in the specified editor (defaults to $EDITOR or $BUNDLER_EDITOR)" + method_option :ext, type: :string, banner: "Generate the boilerplate for C extension code.", enum: EXTENSIONS + method_option :git, type: :boolean, default: true, banner: "Initialize a git repo inside your library." + method_option :mit, type: :boolean, banner: "Generate an MIT license file. Set a default with `bundle config set --global gem.mit true`." + method_option :rubocop, type: :boolean, banner: "Add rubocop to the generated Rakefile and gemspec. Set a default with `bundle config set --global gem.rubocop true` (removed)." + method_option :changelog, type: :boolean, banner: "Generate changelog file. Set a default with `bundle config set --global gem.changelog true`." + method_option :test, type: :string, lazy_default: Bundler.settings["gem.test"] || "", aliases: "-t", banner: "Use the specified test framework for your library", enum: %w[rspec minitest test-unit], desc: "Generate a test directory for your library, either rspec, minitest or test-unit. Set a default with `bundle config set --global gem.test (rspec|minitest|test-unit)`." + method_option :ci, type: :string, lazy_default: Bundler.settings["gem.ci"] || "", enum: %w[github gitlab circle], banner: "Generate CI configuration, either GitHub Actions, GitLab CI or CircleCI. Set a default with `bundle config set --global gem.ci (github|gitlab|circle)`" + method_option :linter, type: :string, lazy_default: Bundler.settings["gem.linter"] || "", enum: %w[rubocop standard], banner: "Add a linter and code formatter, either RuboCop or Standard. Set a default with `bundle config set --global gem.linter (rubocop|standard)`" method_option :github_username, type: :string, default: Bundler.settings["gem.github_username"], banner: "Set your username on GitHub", desc: "Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`." + method_option :bundle, type: :boolean, default: Bundler.settings["gem.bundle"], banner: "Automatically run `bundle install` after creation. Set a default with `bundle config set --global gem.bundle true`" def gem(name) - end - - commands["gem"].tap do |gem_command| - def gem_command.run(instance, args = []) - arity = 1 # name + require_relative "cli/gem" - require_relative "cli/gem" - cmd_args = args + [instance] - cmd_args.unshift(instance.options) + raise InvalidOption, "--rubocop has been removed, use --linter=rubocop" if ARGV.include?("--rubocop") + raise InvalidOption, "--no-rubocop has been removed, use --no-linter" if ARGV.include?("--no-rubocop") - cmd = begin - Gem.new(*cmd_args) - rescue ArgumentError => e - instance.class.handle_argument_error(self, e, args, arity) - end + cmd_args = args + [self] + cmd_args.unshift(options) - cmd.run - end + Gem.new(*cmd_args).run end - undef_method(:gem) - define_method(:gem, old_gem) - private :gem - def self.source_root File.expand_path("templates", __dir__) end - desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory", hide: true + desc "clean [OPTIONS]", "Cleans up unused gems in your bundler directory" method_option "dry-run", type: :boolean, default: false, banner: "Only print out changes, do not clean gems" method_option "force", type: :boolean, default: false, banner: "Forces cleaning up unused gems even if Bundler is configured to use globally installed gems. As a consequence, removes all system gems except for the ones in the current application." def clean @@ -608,12 +612,8 @@ module Bundler end desc "inject GEM VERSION", "Add the named gem, with version requirements, to the resolved Gemfile", hide: true - method_option "source", type: :string, banner: "Install gem from the given source" - method_option "group", type: :string, banner: "Install gem into a bundler group" - def inject(name, version) - SharedHelpers.major_deprecation 2, "The `inject` command has been replaced by the `add` command" - require_relative "cli/inject" - Inject.new(options.dup, name, version).run + def inject(*) + SharedHelpers.feature_removed! "The `inject` command has been replaced by the `add` command" end desc "lock", "Creates a lockfile without installing" @@ -623,6 +623,7 @@ module Bundler method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" method_option "lockfile", type: :string, default: nil, banner: "the path the lockfile should be written to" method_option "full-index", type: :boolean, default: false, banner: "Fall back to using the single-file index of all gems" + method_option "add-checksums", type: :boolean, default: false, banner: "Adds checksums to the lockfile" method_option "add-platform", type: :array, default: [], banner: "Add a new platform to the lockfile" method_option "remove-platform", type: :array, default: [], banner: "Remove a platform from the lockfile" method_option "normalize-platforms", type: :boolean, default: false, banner: "Normalize lockfile platforms" @@ -644,17 +645,8 @@ module Bundler end desc "doctor [OPTIONS]", "Checks the bundle for common problems" - long_desc <<-D - Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If - missing dependencies are detected, Bundler prints them and exits status 1. - Otherwise, Bundler prints a success message and exits with a status of 0. - D - method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" - method_option "quiet", type: :boolean, banner: "Only output warnings and errors." - def doctor - require_relative "cli/doctor" - Doctor.new(options).run - end + require_relative "cli/doctor" + subcommand("doctor", Doctor) desc "issue", "Learn how to report an issue in Bundler" def issue @@ -675,7 +667,7 @@ module Bundler end end - if Bundler.feature_flag.plugins? + if Bundler.settings[:plugins] require_relative "cli/plugin" desc "plugin", "Manage the bundler plugins" subcommand "plugin", Plugin @@ -709,18 +701,15 @@ module Bundler end end - def self.check_deprecated_ext_option(arguments) - # when deprecated version of `--ext` is called - # print out deprecation warning and pretend `--ext=c` was provided - if deprecated_ext_value?(arguments) - message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been deprecated. Please select a language, e.g. `--ext=rust` to generate a Rust extension. This gem will now be generated as if `--ext=c` was used." + def self.check_invalid_ext_option(arguments) + # when invalid version of `--ext` is called + if invalid_ext_value?(arguments) removed_message = "Extensions can now be generated using C or Rust, so `--ext` with no arguments has been removed. Please select a language, e.g. `--ext=rust` to generate a Rust extension." - SharedHelpers.major_deprecation 2, message, removed_message: removed_message - arguments[arguments.index("--ext")] = "--ext=c" + raise InvalidOption, removed_message end end - def self.deprecated_ext_value?(arguments) + def self.invalid_ext_value?(arguments) index = arguments.index("--ext") next_argument = arguments[index + 1] @@ -728,15 +717,15 @@ module Bundler # for example `bundle gem hello --ext c` return false if EXTENSIONS.include?(next_argument) - # deprecated call when --ext is called with no value in last position + # invalid call when --ext is called with no value in last position # for example `bundle gem hello_gem --ext` return true if next_argument.nil? - # deprecated call when --ext is followed by other parameter + # invalid call when --ext is followed by other parameter # for example `bundle gem --ext --no-ci hello_gem` return true if next_argument.start_with?("-") - # deprecated call when --ext is followed by gem name + # invalid call when --ext is followed by gem name # for example `bundle gem --ext hello_gem` return true if next_argument @@ -750,6 +739,19 @@ module Bundler config[:current_command] end + def invoke_other_command(name) + _, _, config = @_initializer + original_command = config[:current_command] + command = self.class.all_commands[name] + config[:current_command] = command + send(name) + ensure + config[:current_command] = original_command + end + + def current_command=(command) + end + def print_command return unless Bundler.ui.debug? cmd = current_command @@ -763,7 +765,7 @@ module Bundler end command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip command.reject!(&:empty?) - Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" end def warn_on_outdated_bundler @@ -790,44 +792,30 @@ module Bundler nil end - def remembered_negative_flag_deprecation(name) - positive_name = name.gsub(/\Ano-/, "") - option = current_command.options[positive_name] - flag_name = "--no-" + option.switch_name.gsub(/\A--/, "") - - flag_deprecation(positive_name, flag_name, option) - end - - def remembered_flag_deprecation(name) + def remembered_flag_deprecation(name, negative: false, option_name: nil) option = current_command.options[name] flag_name = option.switch_name - - flag_deprecation(name, flag_name, option) - end - - def flag_deprecation(name, flag_name, option) - name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } - return unless name_index + flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative + return unless flag_passed?(flag_name) value = options[name] value = value.join(" ").to_s if option.type == :array value = "'#{value}'" unless option.type == :boolean - print_remembered_flag_deprecation(flag_name, name.tr("-", "_"), value) + print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value) end def print_remembered_flag_deprecation(flag_name, option_name, option_value) - message = - "The `#{flag_name}` flag is deprecated because it relies on being " \ - "remembered across bundler invocations, which bundler will no longer " \ - "do in future versions. Instead please use `bundle config set #{option_name} " \ - "#{option_value}`, and stop using this flag" removed_message = "The `#{flag_name}` flag has been removed because it relied on being " \ - "remembered across bundler invocations, which bundler will no longer " \ - "do. Instead please use `bundle config set #{option_name} " \ - "#{option_value}`, and stop using this flag" - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message + "remembered across bundler invocations, which bundler no longer does. " \ + "Instead please use `bundle config set #{option_name} #{option_value}`, " \ + "and stop using this flag" + raise InvalidOption, removed_message + end + + def flag_passed?(name) + ARGV.any? {|arg| name == arg.split("=")[0] } end end end diff --git a/lib/bundler/cli/add.rb b/lib/bundler/cli/add.rb index 12a681a816..9f17604096 100644 --- a/lib/bundler/cli/add.rb +++ b/lib/bundler/cli/add.rb @@ -36,6 +36,16 @@ module Bundler end def validate_options! + raise InvalidOption, "You cannot specify `--git` and `--github` at the same time." if options["git"] && options["github"] + + unless options["git"] || options["github"] + raise InvalidOption, "You cannot specify `--branch` unless `--git` or `--github` is specified." if options["branch"] + + raise InvalidOption, "You cannot specify `--ref` unless `--git` or `--github` is specified." if options["ref"] + end + + raise InvalidOption, "You cannot specify `--branch` and `--ref` at the same time." if options["branch"] && options["ref"] + raise InvalidOption, "You cannot specify `--strict` and `--optimistic` at the same time." if options[:strict] && options[:optimistic] # raise error when no gems are specified diff --git a/lib/bundler/cli/cache.rb b/lib/bundler/cli/cache.rb index 2e63a16ec3..59605df847 100644 --- a/lib/bundler/cli/cache.rb +++ b/lib/bundler/cli/cache.rb @@ -10,17 +10,12 @@ module Bundler def run Bundler.ui.level = "warn" if options[:quiet] - Bundler.settings.set_command_option_if_given :path, options[:path] Bundler.settings.set_command_option_if_given :cache_path, options["cache-path"] - setup_cache_all install - # TODO: move cache contents here now that all bundles are locked - custom_path = Bundler.settings[:path] if options[:path] - Bundler.settings.temporary(cache_all_platforms: options["all-platforms"]) do - Bundler.load.cache(custom_path) + Bundler.load.cache end end @@ -33,11 +28,5 @@ module Bundler options["no-cache"] = true Bundler::CLI::Install.new(options).run end - - def setup_cache_all - all = options.fetch(:all, Bundler.feature_flag.cache_all? || nil) - - Bundler.settings.set_command_option_if_given :cache_all, all - end end end diff --git a/lib/bundler/cli/common.rb b/lib/bundler/cli/common.rb index 7ef6deb2cf..2f332ff364 100644 --- a/lib/bundler/cli/common.rb +++ b/lib/bundler/cli/common.rb @@ -94,11 +94,14 @@ module Bundler end def self.gem_not_found_message(missing_gem_name, alternatives) - require_relative "../similarity_detector" message = "Could not find gem '#{missing_gem_name}'." alternate_names = alternatives.map {|a| a.respond_to?(:name) ? a.name : a } - suggestions = SimilarityDetector.new(alternate_names).similar_word_list(missing_gem_name) - message += "\nDid you mean #{suggestions}?" if suggestions + if alternate_names.include?(missing_gem_name.downcase) + message += "\nDid you mean '#{missing_gem_name.downcase}'?" + elsif defined?(DidYouMean::SpellChecker) + suggestions = DidYouMean::SpellChecker.new(dictionary: alternate_names).correct(missing_gem_name) + message += "\nDid you mean #{word_list(suggestions)}?" unless suggestions.empty? + end message end @@ -130,9 +133,23 @@ module Bundler def self.clean_after_install? clean = Bundler.settings[:clean] return clean unless clean.nil? - clean ||= Bundler.feature_flag.auto_clean_without_path? && Bundler.settings[:path].nil? + clean ||= Bundler.feature_flag.bundler_5_mode? && Bundler.settings[:path].nil? clean &&= !Bundler.use_system_gems? clean end + + def self.word_list(words) + if words.empty? + return "" + end + + words = words.map {|word| "'#{word}'" } + + if words.length == 1 + return words[0] + end + + [words[0..-2].join(", "), words[-1]].join(" or ") + end end end diff --git a/lib/bundler/cli/config.rb b/lib/bundler/cli/config.rb index 77b502fe60..6a77e4a65e 100644 --- a/lib/bundler/cli/config.rb +++ b/lib/bundler/cli/config.rb @@ -26,8 +26,7 @@ module Bundler end message = "Using the `config` command without a subcommand [list, get, set, unset] is deprecated and will be removed in the future. Use `bundle #{new_args.join(" ")}` instead." - removed_message = "Using the `config` command without a subcommand [list, get, set, unset] is has been removed. Use `bundle #{new_args.join(" ")}` instead." - SharedHelpers.major_deprecation 3, message, removed_message: removed_message + SharedHelpers.feature_deprecated! message Base.new(options, name, value, self).run end diff --git a/lib/bundler/cli/console.rb b/lib/bundler/cli/console.rb index 840cf14fd7..2d1a2ce458 100644 --- a/lib/bundler/cli/console.rb +++ b/lib/bundler/cli/console.rb @@ -9,10 +9,6 @@ module Bundler end def run - message = "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`" - removed_message = "bundle console has been replaced by `bin/console` generated by `bundle gem <name>`" - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - group ? Bundler.require(:default, *group.split(" ").map!(&:to_sym)) : Bundler.require ARGV.clear @@ -24,9 +20,19 @@ module Bundler require name get_constant(name) rescue LoadError - Bundler.ui.error "Couldn't load console #{name}, falling back to irb" - require "irb" - get_constant("irb") + if name == "irb" + if defined?(Gem::BUNDLED_GEMS) && Gem::BUNDLED_GEMS.respond_to?(:force_activate) + Gem::BUNDLED_GEMS.force_activate "irb" + require name + return get_constant(name) + end + Bundler.ui.error "#{name} is not available" + exit 1 + else + Bundler.ui.error "Couldn't load console #{name}, falling back to irb" + name = "irb" + retry + end end def get_constant(name) @@ -36,9 +42,6 @@ module Bundler "irb" => :IRB, }[name] Object.const_get(const_name) - rescue NameError - Bundler.ui.error "Could not find constant #{const_name}" - exit 1 end end end diff --git a/lib/bundler/cli/doctor.rb b/lib/bundler/cli/doctor.rb index 1f6fc93c16..5fd6a73d91 100644 --- a/lib/bundler/cli/doctor.rb +++ b/lib/bundler/cli/doctor.rb @@ -1,157 +1,33 @@ # frozen_string_literal: true -require "rbconfig" -require "shellwords" -require "fiddle" - module Bundler - class CLI::Doctor - DARWIN_REGEX = /\s+(.+) \(compatibility / - LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/ - - attr_reader :options - - def initialize(options) - @options = options - end - - def otool_available? - Bundler.which("otool") - end - - def ldd_available? - Bundler.which("ldd") - end - - def dylibs_darwin(path) - output = `/usr/bin/otool -L #{path.shellescape}`.chomp - dylibs = output.split("\n")[1..-1].map {|l| l.match(DARWIN_REGEX).captures[0] }.uniq - # ignore @rpath and friends - dylibs.reject {|dylib| dylib.start_with? "@" } - end - - def dylibs_ldd(path) - output = `/usr/bin/ldd #{path.shellescape}`.chomp - output.split("\n").map do |l| - match = l.match(LDD_REGEX) - next if match.nil? - match.captures[0] - end.compact - end - - def dylibs(path) - case RbConfig::CONFIG["host_os"] - when /darwin/ - return [] unless otool_available? - dylibs_darwin(path) - when /(linux|solaris|bsd)/ - return [] unless ldd_available? - dylibs_ldd(path) - else # Windows, etc. - Bundler.ui.warn("Dynamic library check not supported on this platform.") - [] - end - end - - def bundles_for_gem(spec) - Dir.glob("#{spec.full_gem_path}/**/*.bundle") - end - - def check! - require_relative "check" - Bundler::CLI::Check.new({}).run - end - - def run - Bundler.ui.level = "warn" if options[:quiet] - Bundler.settings.validate! - check! - - definition = Bundler.definition - broken_links = {} - - definition.specs.each do |spec| - bundles_for_gem(spec).each do |bundle| - bad_paths = dylibs(bundle).select do |f| - Fiddle.dlopen(f) - false - rescue Fiddle::DLError - true - end - if bad_paths.any? - broken_links[spec] ||= [] - broken_links[spec].concat(bad_paths) - end - end - end - - permissions_valid = check_home_permissions - - if broken_links.any? - message = "The following gems are missing OS dependencies:" - broken_links.map do |spec, paths| - paths.uniq.map do |path| - "\n * #{spec.name}: #{path}" - end - end.flatten.sort.each {|m| message += m } - raise ProductionError, message - elsif !permissions_valid - Bundler.ui.info "No issues found with the installed bundle" - end - end - - private - - def check_home_permissions - require "find" - files_not_readable_or_writable = [] - files_not_rw_and_owned_by_different_user = [] - files_not_owned_by_current_user_but_still_rw = [] - broken_symlinks = [] - Find.find(Bundler.bundle_path.to_s).each do |f| - if !File.exist?(f) - broken_symlinks << f - elsif !File.writable?(f) || !File.readable?(f) - if File.stat(f).uid != Process.uid - files_not_rw_and_owned_by_different_user << f - else - files_not_readable_or_writable << f - end - elsif File.stat(f).uid != Process.uid - files_not_owned_by_current_user_but_still_rw << f - end - end - - ok = true - - if broken_symlinks.any? - Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}" - - ok = false - end - - if files_not_owned_by_current_user_but_still_rw.any? - Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ - "user, but are still readable/writable. These files are:\n - #{files_not_owned_by_current_user_but_still_rw.join("\n - ")}" - - ok = false - end - - if files_not_rw_and_owned_by_different_user.any? - Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ - "user, and are not readable/writable. These files are:\n - #{files_not_rw_and_owned_by_different_user.join("\n - ")}" - - ok = false - end - - if files_not_readable_or_writable.any? - Bundler.ui.warn "Files exist in the Bundler home that are not " \ - "readable/writable by the current user. These files are:\n - #{files_not_readable_or_writable.join("\n - ")}" - - ok = false - end - - ok + class CLI::Doctor < Thor + default_command(:diagnose) + + desc "diagnose [OPTIONS]", "Checks the bundle for common problems" + long_desc <<-D + Doctor scans the OS dependencies of each of the gems requested in the Gemfile. If + missing dependencies are detected, Bundler prints them and exits status 1. + Otherwise, Bundler prints a success message and exits with a status of 0. + D + method_option "gemfile", type: :string, banner: "Use the specified gemfile instead of Gemfile" + method_option "quiet", type: :boolean, banner: "Only output warnings and errors." + method_option "ssl", type: :boolean, default: false, banner: "Diagnose SSL problems." + def diagnose + require_relative "doctor/diagnose" + Diagnose.new(options).run + end + + desc "ssl [OPTIONS]", "Diagnose SSL problems" + long_desc <<-D + Diagnose SSL problems, especially related to certificates or TLS version while connecting to https://rubygems.org. + D + method_option "host", type: :string, banner: "The host to diagnose." + method_option "tls-version", type: :string, banner: "Specify the SSL/TLS version when running the diagnostic. Accepts either <1.1> or <1.2>" + method_option "verify-mode", type: :string, banner: "Specify the mode used for certification verification. Accepts either <peer> or <none>" + def ssl + require_relative "doctor/ssl" + SSL.new(options).run end end end diff --git a/lib/bundler/cli/doctor/diagnose.rb b/lib/bundler/cli/doctor/diagnose.rb new file mode 100644 index 0000000000..a878025dda --- /dev/null +++ b/lib/bundler/cli/doctor/diagnose.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require "rbconfig" +require "shellwords" + +module Bundler + class CLI::Doctor::Diagnose + DARWIN_REGEX = /\s+(.+) \(compatibility / + LDD_REGEX = /\t\S+ => (\S+) \(\S+\)/ + + attr_reader :options + + def initialize(options) + @options = options + end + + def otool_available? + Bundler.which("otool") + end + + def ldd_available? + Bundler.which("ldd") + end + + def dylibs_darwin(path) + output = `/usr/bin/otool -L #{path.shellescape}`.chomp + dylibs = output.split("\n")[1..-1].filter_map {|l| l.match(DARWIN_REGEX)&.match(1) }.uniq + # ignore @rpath and friends + dylibs.reject {|dylib| dylib.start_with? "@" } + end + + def dylibs_ldd(path) + output = `/usr/bin/ldd #{path.shellescape}`.chomp + output.split("\n").filter_map do |l| + match = l.match(LDD_REGEX) + next if match.nil? + match.captures[0] + end + end + + def dylibs(path) + case RbConfig::CONFIG["host_os"] + when /darwin/ + return [] unless otool_available? + dylibs_darwin(path) + when /(linux|solaris|bsd)/ + return [] unless ldd_available? + dylibs_ldd(path) + else # Windows, etc. + Bundler.ui.warn("Dynamic library check not supported on this platform.") + [] + end + end + + def bundles_for_gem(spec) + Dir.glob("#{spec.full_gem_path}/**/*.bundle") + end + + def lookup_with_fiddle(path) + require "fiddle" + Fiddle.dlopen(path) + false + rescue Fiddle::DLError + true + end + + def check! + require_relative "../check" + Bundler::CLI::Check.new({}).run + end + + def diagnose_ssl + require_relative "ssl" + Bundler::CLI::Doctor::SSL.new({}).run + end + + def run + Bundler.ui.level = "warn" if options[:quiet] + Bundler.settings.validate! + check! + diagnose_ssl if options[:ssl] + + definition = Bundler.definition + broken_links = {} + + definition.specs.each do |spec| + bundles_for_gem(spec).each do |bundle| + bad_paths = dylibs(bundle).select do |f| + lookup_with_fiddle(f) + end + if bad_paths.any? + broken_links[spec] ||= [] + broken_links[spec].concat(bad_paths) + end + end + end + + permissions_valid = check_home_permissions + + if broken_links.any? + message = "The following gems are missing OS dependencies:" + broken_links.flat_map do |spec, paths| + paths.uniq.map do |path| + "\n * #{spec.name}: #{path}" + end + end.sort.each {|m| message += m } + raise ProductionError, message + elsif permissions_valid + Bundler.ui.info "No issues found with the installed bundle" + end + end + + private + + def check_home_permissions + require "find" + files_not_readable = [] + files_not_readable_and_owned_by_different_user = [] + files_not_owned_by_current_user_but_still_readable = [] + broken_symlinks = [] + Find.find(Bundler.bundle_path.to_s).each do |f| + if !File.exist?(f) + broken_symlinks << f + elsif !File.readable?(f) + if File.stat(f).uid != Process.uid + files_not_readable_and_owned_by_different_user << f + else + files_not_readable << f + end + elsif File.stat(f).uid != Process.uid + files_not_owned_by_current_user_but_still_readable << f + end + end + + ok = true + + if broken_symlinks.any? + Bundler.ui.warn "Broken links exist in the Bundler home. Please report them to the offending gem's upstream repo. These files are:\n - #{broken_symlinks.join("\n - ")}" + + ok = false + end + + if files_not_owned_by_current_user_but_still_readable.any? + Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ + "user, but are still readable. These files are:\n - #{files_not_owned_by_current_user_but_still_readable.join("\n - ")}" + + ok = false + end + + if files_not_readable_and_owned_by_different_user.any? + Bundler.ui.warn "Files exist in the Bundler home that are owned by another " \ + "user, and are not readable. These files are:\n - #{files_not_readable_and_owned_by_different_user.join("\n - ")}" + + ok = false + end + + if files_not_readable.any? + Bundler.ui.warn "Files exist in the Bundler home that are not " \ + "readable by the current user. These files are:\n - #{files_not_readable.join("\n - ")}" + + ok = false + end + + ok + end + end +end diff --git a/lib/bundler/cli/doctor/ssl.rb b/lib/bundler/cli/doctor/ssl.rb new file mode 100644 index 0000000000..21fc4edf2d --- /dev/null +++ b/lib/bundler/cli/doctor/ssl.rb @@ -0,0 +1,249 @@ +# frozen_string_literal: true + +require "rubygems/remote_fetcher" +require "uri" + +module Bundler + class CLI::Doctor::SSL + attr_reader :options + + def initialize(options) + @options = options + end + + def run + return unless openssl_installed? + + output_ssl_environment + bundler_success = bundler_connection_successful? + rubygem_success = rubygem_connection_successful? + + return unless net_http_connection_successful? + + Explanation.summarize(bundler_success, rubygem_success, host) + end + + private + + def host + @options[:host] || "rubygems.org" + end + + def tls_version + @options[:"tls-version"].then do |version| + "TLS#{version.sub(".", "_")}".to_sym if version + end + end + + def verify_mode + mode = @options[:"verify-mode"] || :peer + + @verify_mode ||= mode.then {|mod| OpenSSL::SSL.const_get("verify_#{mod}".upcase) } + end + + def uri + @uri ||= URI("https://#{host}") + end + + def openssl_installed? + require "openssl" + + true + rescue LoadError + Bundler.ui.warn(<<~MSG) + Oh no! Your Ruby doesn't have OpenSSL, so it can't connect to #{host}. + You'll need to recompile or reinstall Ruby with OpenSSL support and try again. + MSG + + false + end + + def output_ssl_environment + Bundler.ui.info(<<~MESSAGE) + Here's your OpenSSL environment: + + OpenSSL: #{OpenSSL::VERSION} + Compiled with: #{OpenSSL::OPENSSL_VERSION} + Loaded with: #{OpenSSL::OPENSSL_LIBRARY_VERSION} + MESSAGE + end + + def bundler_connection_successful? + Bundler.ui.info("\nTrying connections to #{uri}:\n") + + bundler_uri = Gem::URI(uri.to_s) + Bundler::Fetcher.new( + Bundler::Source::Rubygems::Remote.new(bundler_uri) + ).send(:connection).request(bundler_uri) + + Bundler.ui.info("Bundler: success") + + true + rescue StandardError => error + Bundler.ui.warn("Bundler: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})") + + false + end + + def rubygem_connection_successful? + Gem::RemoteFetcher.fetcher.fetch_path(uri) + Bundler.ui.info("RubyGems: success") + + true + rescue StandardError => error + Bundler.ui.warn("RubyGems: failed (#{Explanation.explain_bundler_or_rubygems_error(error)})") + + false + end + + def net_http_connection_successful? + ::Gem::Net::HTTP.new(uri.host, uri.port).tap do |http| + http.use_ssl = true + http.min_version = tls_version + http.max_version = tls_version + http.verify_mode = verify_mode + end.start + + Bundler.ui.info("Ruby net/http: success") + warn_on_unsupported_tls12 + + true + rescue StandardError => error + Bundler.ui.warn(<<~MSG) + Ruby net/http: failed + + Unfortunately, this Ruby can't connect to #{host}. + + #{Explanation.explain_net_http_error(error, host, tls_version)} + MSG + + false + end + + def warn_on_unsupported_tls12 + ctx = OpenSSL::SSL::SSLContext.new + supported = true + + if ctx.respond_to?(:min_version=) + begin + ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION + rescue OpenSSL::SSL::SSLError, NameError + supported = false + end + else + supported = OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_2) # rubocop:disable Naming/VariableNumber + end + + Bundler.ui.warn(<<~EOM) unless supported + + WARNING: Although your Ruby can connect to #{host} today, your OpenSSL is very old! + WARNING: You will need to upgrade OpenSSL to use #{host}. + + EOM + end + + module Explanation + extend self + + def explain_bundler_or_rubygems_error(error) + case error.message + when /certificate verify failed/ + "certificate verification" + when /read server hello A/ + "SSL/TLS protocol version mismatch" + when /tlsv1 alert protocol version/ + "requested TLS version is too old" + else + error.message + end + end + + def explain_net_http_error(error, host, tls_version) + case error.message + # Check for certificate errors + when /certificate verify failed/ + <<~MSG + #{show_ssl_certs} + Your Ruby can't connect to #{host} because you are missing the certificate files OpenSSL needs to verify you are connecting to the genuine #{host} servers. + MSG + # Check for TLS version errors + when /read server hello A/, /tlsv1 alert protocol version/ + if tls_version.to_s == "TLS1_3" + "Your Ruby can't connect to #{host} because #{tls_version} isn't supported yet.\n" + else + <<~MSG + Your Ruby can't connect to #{host} because your version of OpenSSL is too old. + You'll need to upgrade your OpenSSL install and/or recompile Ruby to use a newer OpenSSL. + MSG + end + # OpenSSL doesn't support TLS version specified by argument + when /unknown SSL method/ + "Your Ruby can't connect because #{tls_version} isn't supported by your version of OpenSSL." + else + <<~MSG + Even worse, we're not sure why. + + Here's the full error information: + #{error.class}: #{error.message} + #{error.backtrace.join("\n ")} + + You might have more luck using Mislav's SSL doctor.rb script. You can get it here: + https://github.com/mislav/ssl-tools/blob/8b3dec4/doctor.rb + + Read more about the script and how to use it in this blog post: + https://mislav.net/2013/07/ruby-openssl/ + MSG + end + end + + def summarize(bundler_success, rubygems_success, host) + guide_url = "http://ruby.to/ssl-check-failed" + + message = if bundler_success && rubygems_success + <<~MSG + Hooray! This Ruby can connect to #{host}. + You are all set to use Bundler and RubyGems. + + MSG + elsif !bundler_success && !rubygems_success + <<~MSG + For some reason, your Ruby installation can connect to #{host}, but neither RubyGems nor Bundler can. + The most likely fix is to manually upgrade RubyGems by following the instructions at #{guide_url}. + After you've done that, run `gem install bundler` to upgrade Bundler, and then run this script again to make sure everything worked. ❣ + + MSG + elsif !bundler_success + <<~MSG + Although your Ruby installation and RubyGems can both connect to #{host}, Bundler is having trouble. + The most likely way to fix this is to upgrade Bundler by running `gem install bundler`. + Run this script again after doing that to make sure everything is all set. + If you're still having trouble, check out the troubleshooting guide at #{guide_url}. + + MSG + else + <<~MSG + It looks like Ruby and Bundler can connect to #{host}, but RubyGems itself cannot. + You can likely solve this by manually downloading and installing a RubyGems update. + Visit #{guide_url} for instructions on how to manually upgrade RubyGems. + + MSG + end + + Bundler.ui.info("\n#{message}") + end + + private + + def show_ssl_certs + ssl_cert_file = ENV["SSL_CERT_FILE"] || OpenSSL::X509::DEFAULT_CERT_FILE + ssl_cert_dir = ENV["SSL_CERT_DIR"] || OpenSSL::X509::DEFAULT_CERT_DIR + + <<~MSG + Below affect only Ruby net/http connections: + SSL_CERT_FILE: #{File.exist?(ssl_cert_file) ? "exists #{ssl_cert_file}" : "is missing #{ssl_cert_file}"} + SSL_CERT_DIR: #{Dir.exist?(ssl_cert_dir) ? "exists #{ssl_cert_dir}" : "is missing #{ssl_cert_dir}"} + MSG + end + end + end +end diff --git a/lib/bundler/cli/exec.rb b/lib/bundler/cli/exec.rb index f81cd5d2c4..2fdc416286 100644 --- a/lib/bundler/cli/exec.rb +++ b/lib/bundler/cli/exec.rb @@ -19,10 +19,13 @@ module Bundler validate_cmd! SharedHelpers.set_bundle_environment if bin_path = Bundler.which(cmd) - if !Bundler.settings[:disable_exec_load] && ruby_shebang?(bin_path) - return kernel_load(bin_path, *args) + if !Bundler.settings[:disable_exec_load] && directly_loadable?(bin_path) + bin_path.delete_suffix!(".bat") if Gem.win_platform? + kernel_load(bin_path, *args) + else + bin_path = "./" + bin_path unless File.absolute_path?(bin_path) + kernel_exec(bin_path, *args) end - kernel_exec(bin_path, *args) else # exec using the given command kernel_exec(cmd, *args) @@ -68,6 +71,29 @@ module Bundler "#{file} #{args.join(" ")}".strip end + def directly_loadable?(file) + if Gem.win_platform? + script_wrapper?(file) + else + ruby_shebang?(file) + end + end + + def script_wrapper?(file) + script_file = file.delete_suffix(".bat") + return false unless File.exist?(script_file) + + if File.zero?(script_file) + Bundler.ui.warn "#{script_file} is empty" + return false + end + + header = File.open(file, "r") {|f| f.read(32) } + ruby_exe = "#{RbConfig::CONFIG["RUBY_INSTALL_NAME"]}#{RbConfig::CONFIG["EXEEXT"]}" + ruby_exe = "ruby.exe" if ruby_exe.empty? + header.include?(ruby_exe) + end + def ruby_shebang?(file) possibilities = [ "#!/usr/bin/env ruby\n", diff --git a/lib/bundler/cli/gem.rb b/lib/bundler/cli/gem.rb index 22bcf0e47a..236ce530ec 100644 --- a/lib/bundler/cli/gem.rb +++ b/lib/bundler/cli/gem.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "pathname" - module Bundler class CLI Bundler.require_thor_actions @@ -15,6 +13,8 @@ module Bundler "test-unit" => "3.0", }.freeze + DEFAULT_GITHUB_USERNAME = "[USERNAME]" + attr_reader :options, :gem_name, :thor, :name, :target, :extension def initialize(options, gem_name, thor) @@ -26,12 +26,11 @@ module Bundler thor.destination_root = nil @name = @gem_name - @target = SharedHelpers.pwd.join(gem_name) + @target = Pathname.new(SharedHelpers.pwd).join(gem_name) @extension = options[:ext] validate_ext_name if @extension - validate_rust_builder_rubygems_version if @extension == "rust" end def run @@ -48,13 +47,16 @@ module Bundler git_author_name = use_git ? `git config user.name`.chomp : "" git_username = use_git ? `git config github.user`.chomp : "" git_user_email = use_git ? `git config user.email`.chomp : "" + github_username = github_username(git_username) - github_username = if options[:github_username].nil? - git_username - elsif options[:github_username] == false - "" + if github_username.empty? + homepage_uri = "TODO: Put your gem's website or public repo URL here." + source_code_uri = "TODO: Put your gem's public repo URL here." + changelog_uri = "TODO: Put your gem's CHANGELOG.md URL here." else - options[:github_username] + homepage_uri = "https://github.com/#{github_username}/#{name}" + source_code_uri = "https://github.com/#{github_username}/#{name}" + changelog_uri = "https://github.com/#{github_username}/#{name}/blob/main/CHANGELOG.md" end config = { @@ -69,12 +71,17 @@ module Bundler test: options[:test], ext: extension, exe: options[:exe], + bundle: options[:bundle], bundler_version: bundler_dependency_version, git: use_git, - github_username: github_username.empty? ? "[USERNAME]" : github_username, + github_username: github_username.empty? ? DEFAULT_GITHUB_USERNAME : github_username, required_ruby_version: required_ruby_version, rust_builder_required_rubygems_version: rust_builder_required_rubygems_version, minitest_constant_name: minitest_constant_name, + ignore_paths: %w[bin/], + homepage_uri: homepage_uri, + source_code_uri: source_code_uri, + changelog_uri: changelog_uri, } ensure_safe_gem_name(name, constant_array) @@ -95,7 +102,18 @@ module Bundler bin/setup ] - templates.merge!("gitignore.tt" => ".gitignore") if use_git + case Bundler.preferred_gemfile_name + when "Gemfile" + config[:ignore_paths] << "Gemfile" + when "gems.rb" + config[:ignore_paths] << "gems.rb" + config[:ignore_paths] << "gems.locked" + end + + if use_git + templates.merge!("gitignore.tt" => ".gitignore") + config[:ignore_paths] << ".gitignore" + end if test_framework = ask_and_set_test_framework config[:test] = test_framework @@ -109,6 +127,8 @@ module Bundler "spec/newgem_spec.rb.tt" => "spec/#{namespaced_path}_spec.rb" ) config[:test_task] = :spec + config[:ignore_paths] << ".rspec" + config[:ignore_paths] << "spec/" when "minitest" # Generate path for minitest target file (FileList["test/**/test_*.rb"]) # foo => test/test_foo.rb @@ -123,12 +143,14 @@ module Bundler "test/minitest/test_newgem.rb.tt" => "test/#{minitest_namespaced_path}.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" when "test-unit" templates.merge!( "test/test-unit/test_helper.rb.tt" => "test/test_helper.rb", "test/test-unit/newgem_test.rb.tt" => "test/#{namespaced_path}_test.rb" ) config[:test_task] = :test + config[:ignore_paths] << "test/" end end @@ -136,19 +158,19 @@ module Bundler case config[:ci] when "github" templates.merge!("github/workflows/main.yml.tt" => ".github/workflows/main.yml") - config[:ci_config_path] = ".github " + config[:ignore_paths] << ".github/" when "gitlab" templates.merge!("gitlab-ci.yml.tt" => ".gitlab-ci.yml") - config[:ci_config_path] = ".gitlab-ci.yml " + config[:ignore_paths] << ".gitlab-ci.yml" when "circle" templates.merge!("circleci/config.yml.tt" => ".circleci/config.yml") - config[:ci_config_path] = ".circleci " + config[:ignore_paths] << ".circleci/" end if ask_and_set(:mit, "Do you want to license your code permissively under the MIT license?", - "This means that any other developer or company will be legally allowed to use your code " \ - "for free as long as they admit you created it. You can read more about the MIT license " \ - "at https://choosealicense.com/licenses/mit.") + "Using a MIT license means that any other developer or company will be legally allowed " \ + "to use your code for free as long as they admit you created it. You can read more about " \ + "the MIT license at https://choosealicense.com/licenses/mit.") config[:mit] = true Bundler.ui.info "MIT License enabled in config" templates.merge!("LICENSE.txt.tt" => "LICENSE.txt") @@ -156,12 +178,8 @@ module Bundler if ask_and_set(:coc, "Do you want to include a code of conduct in gems you generate?", "Codes of conduct can increase contributions to your project by contributors who " \ - "prefer collaborative, safe spaces. You can read more about the code of conduct at " \ - "contributor-covenant.org. Having a code of conduct means agreeing to the responsibility " \ - "of enforcing it, so be sure that you are prepared to do that. Be sure that your email " \ - "address is specified as a contact in the generated code of conduct so that people know " \ - "who to contact in case of a violation. For suggestions about " \ - "how to enforce codes of conduct, see https://bit.ly/coc-enforcement.") + "prefer safe, respectful, productive, and collaborative spaces. \n" \ + "See https://github.com/ruby/rubygems/blob/master/CODE_OF_CONDUCT.md") config[:coc] = true Bundler.ui.info "Code of conduct enabled in config" templates.merge!("CODE_OF_CONDUCT.md.tt" => "CODE_OF_CONDUCT.md") @@ -185,10 +203,12 @@ module Bundler config[:linter_version] = rubocop_version Bundler.ui.info "RuboCop enabled in config" templates.merge!("rubocop.yml.tt" => ".rubocop.yml") + config[:ignore_paths] << ".rubocop.yml" when "standard" config[:linter_version] = standard_version Bundler.ui.info "Standard enabled in config" templates.merge!("standard.yml.tt" => ".standard.yml") + config[:ignore_paths] << ".standard.yml" end if config[:exe] @@ -213,13 +233,25 @@ module Bundler ) end + if extension == "go" + templates.merge!( + "ext/newgem/go.mod.tt" => "ext/#{name}/go.mod", + "ext/newgem/extconf-go.rb.tt" => "ext/#{name}/extconf.rb", + "ext/newgem/newgem.h.tt" => "ext/#{name}/#{underscored_name}.h", + "ext/newgem/newgem.go.tt" => "ext/#{name}/#{underscored_name}.go", + "ext/newgem/newgem-go.c.tt" => "ext/#{name}/#{underscored_name}.c", + ) + + config[:go_module_username] = config[:github_username] == DEFAULT_GITHUB_USERNAME ? "username" : config[:github_username] + end + if target.exist? && !target.directory? Bundler.ui.error "Couldn't create a new gem named `#{gem_name}` because there's an existing file named `#{gem_name}`." exit Bundler::BundlerError.all_errors[Bundler::GenericSystemCallError] end if use_git - Bundler.ui.info "Initializing git repo in #{target}" + Bundler.ui.info "\nInitializing git repo in #{target}" require "shellwords" `git init #{target.to_s.shellescape}` @@ -241,26 +273,33 @@ module Bundler IO.popen(%w[git add .], { chdir: target }, &:read) end + if config[:bundle] + Bundler.ui.info "Running bundle install in the new gem directory." + Dir.chdir(target) do + system("bundle install") + end + end + # Open gemspec in editor open_editor(options["edit"], target.join("#{name}.gemspec")) if options[:edit] - Bundler.ui.info "Gem '#{name}' was successfully created. " \ + Bundler.ui.info "\nGem '#{name}' was successfully created. " \ "For more information on making a RubyGem visit https://bundler.io/guides/creating_gem.html" end private def resolve_name(name) - SharedHelpers.pwd.join(name).basename.to_s + Pathname.new(SharedHelpers.pwd).join(name).basename.to_s end - def ask_and_set(key, header, message) + def ask_and_set(key, prompt, explanation) choice = options[key] choice = Bundler.settings["gem.#{key}"] if choice.nil? if choice.nil? - Bundler.ui.confirm header - choice = Bundler.ui.yes? "#{message} y/(n):" + Bundler.ui.info "\n#{explanation}" + choice = Bundler.ui.yes? "#{prompt} y/(n):" Bundler.settings.set_global("gem.#{key}", choice) end @@ -282,7 +321,7 @@ module Bundler test_framework = options[:test] || Bundler.settings["gem.test"] if test_framework.to_s.empty? - Bundler.ui.confirm "Do you want to generate tests with your gem?" + Bundler.ui.info "\nDo you want to generate tests with your gem?" Bundler.ui.info hint_text("test") result = Bundler.ui.ask "Enter a test framework. rspec/minitest/test-unit/(none):" @@ -322,12 +361,11 @@ module Bundler ci_template = options[:ci] || Bundler.settings["gem.ci"] if ci_template.to_s.empty? - Bundler.ui.confirm "Do you want to set up continuous integration for your gem? " \ + Bundler.ui.info "\nDo you want to set up continuous integration for your gem? " \ "Supported services:\n" \ "* CircleCI: https://circleci.com/\n" \ "* GitHub Actions: https://github.com/features/actions\n" \ - "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" \ - "\n" + "* GitLab CI: https://docs.gitlab.com/ee/ci/\n" Bundler.ui.info hint_text("ci") result = Bundler.ui.ask "Enter a CI service. github/gitlab/circle/(none):" @@ -352,14 +390,12 @@ module Bundler def ask_and_set_linter return if skip?(:linter) linter_template = options[:linter] || Bundler.settings["gem.linter"] - linter_template = deprecated_rubocop_option if linter_template.nil? if linter_template.to_s.empty? - Bundler.ui.confirm "Do you want to add a code linter and formatter to your gem? " \ + Bundler.ui.info "\nDo you want to add a code linter and formatter to your gem? " \ "Supported Linters:\n" \ "* RuboCop: https://rubocop.org\n" \ - "* Standard: https://github.com/standardrb/standard\n" \ - "\n" + "* Standard: https://github.com/standardrb/standard\n" Bundler.ui.info hint_text("linter") result = Bundler.ui.ask "Enter a linter. rubocop/standard/(none):" @@ -386,27 +422,6 @@ module Bundler linter_template end - def deprecated_rubocop_option - if !options[:rubocop].nil? - if options[:rubocop] - Bundler::SharedHelpers.major_deprecation 2, - "--rubocop is deprecated, use --linter=rubocop", - removed_message: "--rubocop has been removed, use --linter=rubocop" - "rubocop" - else - Bundler::SharedHelpers.major_deprecation 2, - "--no-rubocop is deprecated, use --linter", - removed_message: "--no-rubocop has been removed, use --linter" - false - end - elsif !Bundler.settings["gem.rubocop"].nil? - Bundler::SharedHelpers.major_deprecation 2, - "config gem.rubocop is deprecated; we've updated your config to use gem.linter instead", - removed_message: "config gem.rubocop has been removed; we've updated your config to use gem.linter instead" - Bundler.settings["gem.rubocop"] ? "rubocop" : false - end - end - def bundler_dependency_version v = Gem::Version.new(Bundler::VERSION) req = v.segments[0..1] @@ -446,7 +461,7 @@ module Bundler end def required_ruby_version - "3.1.0" + "3.2.0" end def rubocop_version @@ -457,10 +472,13 @@ module Bundler "1.3" end - def validate_rust_builder_rubygems_version - if Gem::Version.new(rust_builder_required_rubygems_version) > Gem.rubygems_version - Bundler.ui.error "Your RubyGems version (#{Gem.rubygems_version}) is too old to build Rust extension. Please update your RubyGems using `gem update --system` or any other way and try again." - exit 1 + def github_username(git_username) + if options[:github_username].nil? + git_username + elsif options[:github_username] == false + "" + else + options[:github_username] end end end diff --git a/lib/bundler/cli/info.rb b/lib/bundler/cli/info.rb index 8f34956aca..cd01d4949b 100644 --- a/lib/bundler/cli/info.rb +++ b/lib/bundler/cli/info.rb @@ -39,8 +39,8 @@ module Bundler path = File.expand_path("../../..", __dir__) else path = spec.full_gem_path - if spec.deleted_gem? - return Bundler.ui.warn "The gem #{name} has been deleted. It was installed at: #{path}" + if spec.installation_missing? + return Bundler.ui.warn "The gem #{name} is missing. It should be installed at #{path}, but was not found" end end @@ -65,19 +65,19 @@ module Bundler gem_info << "\tDefault Gem: yes\n" if spec.respond_to?(:default_gem?) && spec.default_gem? gem_info << "\tReverse Dependencies: \n\t\t#{gem_dependencies.join("\n\t\t")}" if gem_dependencies.any? - if name != "bundler" && spec.deleted_gem? - return Bundler.ui.warn "The gem #{name} has been deleted. Gemspec information is still available though:\n#{gem_info}" + if name != "bundler" && spec.installation_missing? + return Bundler.ui.warn "The gem #{name} is missing. Gemspec information is still available though:\n#{gem_info}" end Bundler.ui.info gem_info end def gem_dependencies - @gem_dependencies ||= Bundler.definition.specs.map do |spec| + @gem_dependencies ||= Bundler.definition.specs.filter_map do |spec| dependency = spec.dependencies.find {|dep| dep.name == gem_name } next unless dependency "#{spec.name} (#{spec.version}) depends on #{gem_name} (#{dependency.requirements_list.join(", ")})" - end.compact.sort + end.sort end end end diff --git a/lib/bundler/cli/inject.rb b/lib/bundler/cli/inject.rb deleted file mode 100644 index 8093a85283..0000000000 --- a/lib/bundler/cli/inject.rb +++ /dev/null @@ -1,60 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Inject - attr_reader :options, :name, :version, :group, :source, :gems - def initialize(options, name, version) - @options = options - @name = name - @version = version || last_version_number - @group = options[:group].split(",") unless options[:group].nil? - @source = options[:source] - @gems = [] - end - - def run - # The required arguments allow Thor to give useful feedback when the arguments - # are incorrect. This adds those first two arguments onto the list as a whole. - gems.unshift(source).unshift(group).unshift(version).unshift(name) - - # Build an array of Dependency objects out of the arguments - deps = [] - # when `inject` support addition of more than one gem, then this loop will - # help. Currently this loop is running once. - gems.each_slice(4) do |gem_name, gem_version, gem_group, gem_source| - ops = Gem::Requirement::OPS.map {|key, _val| key } - has_op = ops.any? {|op| gem_version.start_with? op } - gem_version = "~> #{gem_version}" unless has_op - deps << Bundler::Dependency.new(gem_name, gem_version, "group" => gem_group, "source" => gem_source) - end - - added = Injector.inject(deps, options) - - if added.any? - Bundler.ui.confirm "Added to Gemfile:" - Bundler.ui.confirm(added.map do |d| - name = "'#{d.name}'" - requirement = ", '#{d.requirement}'" - group = ", :group => #{d.groups.inspect}" if d.groups != Array(:default) - source = ", :source => '#{d.source}'" unless d.source.nil? - %(gem #{name}#{requirement}#{group}#{source}) - end.join("\n")) - else - Bundler.ui.confirm "All gems were already present in the Gemfile" - end - end - - private - - def last_version_number - definition = Bundler.definition(true) - definition.resolve_remotely! - specs = definition.index[name].sort_by(&:version) - unless options[:pre] - specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } - end - spec = specs.last - spec.version.to_s - end - end -end diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index b0b354cf10..67feba84bd 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -20,54 +20,32 @@ module Bundler Bundler::SharedHelpers.set_env "RB_USER_INSTALL", "1" if Gem.freebsd_platform? - # Disable color in deployment mode - Bundler.ui.shell = Thor::Shell::Basic.new if options[:deployment] - if target_rbconfig_path = options[:"target-rbconfig"] Bundler.rubygems.set_target_rbconfig(target_rbconfig_path) end - check_for_options_conflicts - check_trust_policy - if options[:deployment] || options[:frozen] || Bundler.frozen_bundle? - unless Bundler.default_lockfile.exist? - flag = "--deployment flag" if options[:deployment] - flag ||= "--frozen flag" if options[:frozen] - flag ||= "deployment setting" if Bundler.settings[:deployment] - flag ||= "frozen setting" if Bundler.settings[:frozen] - raise ProductionError, "The #{flag} requires a lockfile. Please make " \ - "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \ - "before deploying." - end - - options[:local] = true if Bundler.app_cache.exist? - - Bundler.settings.set_command_option :deployment, true if options[:deployment] - Bundler.settings.set_command_option :frozen, true if options[:frozen] - end - - # When install is called with --no-deployment, disable deployment mode - if options[:deployment] == false - Bundler.settings.set_command_option :frozen, nil - options[:system] = true + if Bundler.frozen_bundle? && !Bundler.default_lockfile.exist? + flag = "deployment setting" if Bundler.settings[:deployment] + flag = "frozen setting" if Bundler.settings[:frozen] + raise ProductionError, "The #{flag} requires a lockfile. Please make " \ + "sure you have checked your #{SharedHelpers.relative_lockfile_path} into version control " \ + "before deploying." end normalize_settings Bundler::Fetcher.disable_endpoint = options["full-index"] - if options["binstubs"] - Bundler::SharedHelpers.major_deprecation 2, - "The --binstubs option will be removed in favor of `bundle binstubs --all`", - removed_message: "The --binstubs option have been removed in favor of `bundle binstubs --all`" - end - - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] - definition = Bundler.definition + # For install we want to enable strict validation + # (rather than some optimizations we perform at app runtime). + definition = Bundler.definition(strict: true) definition.validate_runtime! + definition.lockfile = options["lockfile"] if options["lockfile"] + definition.lockfile = false if options["no-lock"] installer = Installer.install(Bundler.root, definition, options) @@ -87,8 +65,6 @@ module Bundler Bundler::CLI::Common.output_post_install_messages installer.post_install_messages - warn_ambiguous_gems - if CLI::Common.clean_after_install? require_relative "clean" Bundler::CLI::Clean.new(options).run @@ -114,26 +90,10 @@ module Bundler end def gems_installed_for(definition) - count = definition.specs.count + count = definition.specs.count {|spec| spec.name != "bundler" } "#{count} #{count == 1 ? "gem" : "gems"} now installed" end - def check_for_group_conflicts_in_cli_options - conflicting_groups = Array(options[:without]) & Array(options[:with]) - return if conflicting_groups.empty? - raise InvalidOption, "You can't list a group in both with and without." \ - " The offending groups are: #{conflicting_groups.join(", ")}." - end - - def check_for_options_conflicts - if (options[:path] || options[:deployment]) && options[:system] - error_message = String.new - error_message << "You have specified both --path as well as --system. Please choose only one option.\n" if options[:path] - error_message << "You have specified both --deployment as well as --system. Please choose only one option.\n" if options[:deployment] - raise InvalidOption.new(error_message) - end - end - def check_trust_policy trust_policy = options["trust-policy"] unless Bundler.rubygems.security_policies.keys.unshift(nil).include?(trust_policy) @@ -143,30 +103,11 @@ module Bundler Bundler.settings.set_command_option_if_given :"trust-policy", trust_policy end - def normalize_groups - check_for_group_conflicts_in_cli_options - - # need to nil them out first to get around validation for backwards compatibility - Bundler.settings.set_command_option :without, nil - Bundler.settings.set_command_option :with, nil - Bundler.settings.set_command_option :without, options[:without] - Bundler.settings.set_command_option :with, options[:with] - end - def normalize_settings - Bundler.settings.set_command_option :path, nil if options[:system] - Bundler.settings.set_command_option_if_given :path, options[:path] - if options["standalone"] && Bundler.settings[:path].nil? && !options["local"] - Bundler.settings.temporary(path_relative_to_cwd: false) do - Bundler.settings.set_command_option :path, "bundle" - end + Bundler.settings.set_command_option :path, "bundle" end - bin_option = options["binstubs"] - bin_option = nil if bin_option&.empty? - Bundler.settings.set_command_option :bin, bin_option if options["binstubs"] - Bundler.settings.set_command_option_if_given :shebang, options["shebang"] Bundler.settings.set_command_option_if_given :jobs, options["jobs"] @@ -177,23 +118,7 @@ module Bundler Bundler.settings.set_command_option_if_given :clean, options["clean"] - normalize_groups if options[:without] || options[:with] - - options[:force] = options[:redownload] - end - - def warn_ambiguous_gems - # TODO: remove this when we drop Bundler 1.x support - Installer.ambiguous_gems.to_a.each do |name, installed_from_uri, *also_found_in_uris| - Bundler.ui.warn "Warning: the gem '#{name}' was found in multiple sources." - Bundler.ui.warn "Installed from: #{installed_from_uri}" - Bundler.ui.warn "Also found in:" - also_found_in_uris.each {|uri| Bundler.ui.warn " * #{uri}" } - Bundler.ui.warn "You should add a source requirement to restrict this gem to your preferred source." - Bundler.ui.warn "For example:" - Bundler.ui.warn " gem '#{name}', :source => '#{installed_from_uri}'" - Bundler.ui.warn "Then uninstall the gem '#{name}' (or delete all bundled gems) and then install again." - end + options[:force] = options[:redownload] if options[:redownload] end end end diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index 5f2924c4bd..cbfb7da2d8 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -10,7 +10,7 @@ module Bundler be sure to check out these resources: 1. Check out our troubleshooting guide for quick fixes to common issues: - https://github.com/rubygems/rubygems/blob/master/bundler/doc/TROUBLESHOOTING.md + https://github.com/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md 2. Instructions for common Bundler uses can be found on the documentation site: https://bundler.io/ @@ -22,7 +22,7 @@ module Bundler still aren't working the way you expect them to, please let us know so that we can diagnose and help fix the problem you're having, by filling in the new issue form located at - https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, + https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md, and copy and pasting the information below. EOS @@ -34,8 +34,8 @@ module Bundler end def doctor - require_relative "doctor" - Bundler::CLI::Doctor.new({}).run + require_relative "doctor/diagnose" + Bundler::CLI::Doctor::Diagnose.new({}).run end end end diff --git a/lib/bundler/cli/list.rb b/lib/bundler/cli/list.rb index f56bf5b86a..6a467f45a9 100644 --- a/lib/bundler/cli/list.rb +++ b/lib/bundler/cli/list.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true +require "json" + module Bundler class CLI::List def initialize(options) @options = options @without_group = options["without-group"].map(&:to_sym) @only_group = options["only-group"].map(&:to_sym) + @format = options["format"] end def run @@ -25,6 +28,36 @@ module Bundler end end.reject {|s| s.name == "bundler" }.sort_by(&:name) + case @format + when "json" + print_json(specs: specs) + when nil + print_human(specs: specs) + else + raise InvalidOption, "Unknown option`--format=#{@format}`. Supported formats: `json`" + end + end + + private + + def print_json(specs:) + gems = if @options["name-only"] + specs.map {|s| { name: s.name } } + else + specs.map do |s| + { + name: s.name, + version: s.version.to_s, + git_version: s.git_version&.strip, + }.tap do |h| + h[:path] = s.full_gem_path if @options["paths"] + end + end + end + Bundler.ui.info({ gems: gems }.to_json) + end + + def print_human(specs:) return Bundler.ui.info "No gems in the Gemfile" if specs.empty? return specs.each {|s| Bundler.ui.info s.name } if @options["name-only"] @@ -37,8 +70,6 @@ module Bundler Bundler.ui.info "Use `bundle info` to print more detailed information about a gem" end - private - def verify_group_exists(groups) (@without_group + @only_group).each do |group| raise InvalidOption, "`#{group}` group could not be found." unless groups.include?(group) diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index d080480e19..2f78868936 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -35,15 +35,14 @@ module Bundler update = { bundler: bundler } end - file = options[:lockfile] - file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile - Bundler.settings.temporary(frozen: false) do - definition = Bundler.definition(update, file) + definition = Bundler.definition(update, Bundler.default_lockfile) + definition.add_checksums if options["add-checksums"] Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update] - options["remove-platform"].each do |platform| + options["remove-platform"].each do |platform_string| + platform = Gem::Platform.new(platform_string) definition.remove_platform(platform) end @@ -60,7 +59,7 @@ module Bundler raise InvalidOption, "Removing all platforms from the bundle is not allowed" end - definition.resolve_remotely! unless options[:local] + definition.remotely! unless options[:local] if options["normalize-platforms"] definition.normalize_platforms @@ -69,8 +68,11 @@ module Bundler if print puts definition.to_lock else + file = options[:lockfile] + file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile + puts "Writing lockfile to #{file}" - definition.lock + definition.write_lock(file, false) end end diff --git a/lib/bundler/cli/outdated.rb b/lib/bundler/cli/outdated.rb index 75fcdca641..0c8ba3ebf7 100644 --- a/lib/bundler/cli/outdated.rb +++ b/lib/bundler/cli/outdated.rb @@ -26,13 +26,15 @@ module Bundler def run check_for_deployment_mode! - gems.each do |gem_name| - Bundler::CLI::Common.select_spec(gem_name) - end - Bundler.definition.validate_runtime! current_specs = Bundler.ui.silence { Bundler.definition.resolve } + gems.each do |gem_name| + if current_specs[gem_name].empty? + raise GemNotFound, "Could not find gem '#{gem_name}'." + end + end + current_dependencies = Bundler.ui.silence do Bundler.load.dependencies.map {|dep| [dep.name, dep] }.to_h end @@ -153,7 +155,7 @@ module Bundler return active_spec if strict - active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version) + active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version) if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1 active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? } end diff --git a/lib/bundler/cli/plugin.rb b/lib/bundler/cli/plugin.rb index fd61ef0d95..32fa660fe0 100644 --- a/lib/bundler/cli/plugin.rb +++ b/lib/bundler/cli/plugin.rb @@ -10,11 +10,15 @@ module Bundler method_option "source", type: :string, default: nil, banner: "URL of the RubyGems source to fetch the plugin from" method_option "version", type: :string, default: nil, banner: "The version of the plugin to fetch" method_option "git", type: :string, default: nil, banner: "URL of the git repo to fetch from" - method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (deprecated)" + method_option "local_git", type: :string, default: nil, banner: "Path of the local git repo to fetch from (removed)" method_option "branch", type: :string, default: nil, banner: "The git branch to checkout" method_option "ref", type: :string, default: nil, banner: "The git revision to check out" method_option "path", type: :string, default: nil, banner: "Path of a local gem to directly use" def install(*plugins) + if options.key?(:local_git) + raise InvalidOption, "--local_git has been removed, use --git" + end + Bundler::Plugin.install(plugins, options) end diff --git a/lib/bundler/cli/pristine.rb b/lib/bundler/cli/pristine.rb index e0d7452c44..b8545fe4c9 100644 --- a/lib/bundler/cli/pristine.rb +++ b/lib/bundler/cli/pristine.rb @@ -11,6 +11,7 @@ module Bundler definition = Bundler.definition definition.validate_runtime! installer = Bundler::Installer.new(Bundler.root, definition) + git_sources = [] ProcessLock.lock do installed_specs = definition.specs.reject do |spec| @@ -41,6 +42,9 @@ module Bundler end FileUtils.rm_rf spec.extension_dir FileUtils.rm_rf spec.full_gem_path + + next if git_sources.include?(source) + git_sources << source else Bundler.ui.warn("Cannot pristine #{gem_name}. Gem is sourced from local path.") next @@ -49,7 +53,7 @@ module Bundler true end.map(&:name) - jobs = installer.send(:installation_parallelization, {}) + jobs = installer.send(:installation_parallelization) pristine_count = definition.specs.count - installed_specs.count # allow a pristining a single gem to skip the parallel worker jobs = [jobs, pristine_count].min diff --git a/lib/bundler/cli/show.rb b/lib/bundler/cli/show.rb index 59b0af42e1..67fdcc797e 100644 --- a/lib/bundler/cli/show.rb +++ b/lib/bundler/cli/show.rb @@ -6,7 +6,7 @@ module Bundler def initialize(options, gem_name) @options = options @gem_name = gem_name - @verbose = options[:verbose] || options[:outdated] + @verbose = options[:verbose] @latest_specs = fetch_latest_specs if @verbose end @@ -24,7 +24,7 @@ module Bundler return unless spec path = spec.full_gem_path unless File.directory?(path) - return Bundler.ui.warn "The gem #{gem_name} has been deleted. It was installed at: #{path}" + return Bundler.ui.warn "The gem #{gem_name} is missing. It should be installed at #{path}, but was not found" end end return Bundler.ui.info(path) @@ -57,12 +57,8 @@ module Bundler def fetch_latest_specs definition = Bundler.definition(true) - if options[:outdated] - Bundler.ui.info "Fetching remote specs for outdated check...\n\n" - Bundler.ui.silence { definition.resolve_remotely! } - else - definition.resolve_with_cache! - end + Bundler.ui.info "Fetching remote specs for outdated check...\n\n" + Bundler.ui.silence { definition.remotely! } Bundler.reset! definition.specs end diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index 985e8db051..9cc90acc58 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -15,7 +15,7 @@ module Bundler Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler - Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.feature_flag.plugins? + Plugin.gemfile_install(Bundler.default_gemfile) if Bundler.settings[:plugins] sources = Array(options[:source]) groups = Array(options[:group]).map(&:to_sym) @@ -23,10 +23,10 @@ module Bundler full_update = gems.empty? && sources.empty? && groups.empty? && !options[:ruby] && !update_bundler if full_update && !options[:all] - if Bundler.feature_flag.update_requires_all_flag? + if Bundler.settings[:update_requires_all_flag] raise InvalidOption, "To update everything, pass the `--all` flag." end - SharedHelpers.major_deprecation 3, "Pass --all to `bundle update` to update everything" + SharedHelpers.feature_deprecated! "Pass --all to `bundle update` to update everything" elsif !full_update && options[:all] raise InvalidOption, "Cannot specify --all along with specific options." end @@ -63,7 +63,7 @@ module Bundler opts = options.dup opts["update"] = true opts["local"] = options[:local] - opts["force"] = options[:redownload] + opts["force"] = options[:redownload] if options[:redownload] Bundler.settings.set_command_option_if_given :jobs, opts["jobs"] @@ -92,7 +92,7 @@ module Bundler locked_spec = locked_info[:spec] new_spec = Bundler.definition.specs[name].first unless new_spec - unless locked_spec.match_platform(Bundler.local_platform) + unless locked_spec.installable_on_platform?(Bundler.local_platform) Bundler.ui.warn "Bundler attempted to update #{name} but it was not considered because it is for a different platform from the current one" end diff --git a/lib/bundler/cli/viz.rb b/lib/bundler/cli/viz.rb deleted file mode 100644 index 5c09e00995..0000000000 --- a/lib/bundler/cli/viz.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CLI::Viz - attr_reader :options, :gem_name - def initialize(options) - @options = options - end - - def run - # make sure we get the right `graphviz`. There is also a `graphviz` - # gem we're not built to support - gem "ruby-graphviz" - require "graphviz" - - options[:without] = options[:without].join(":").tr(" ", ":").split(":") - output_file = File.expand_path(options[:file]) - - graph = Graph.new(Bundler.load, output_file, options[:version], options[:requirements], options[:format], options[:without]) - graph.viz - rescue LoadError => e - Bundler.ui.error e.inspect - Bundler.ui.warn "Make sure you have the graphviz ruby gem. You can install it with:" - Bundler.ui.warn "`gem install ruby-graphviz`" - rescue StandardError => e - raise unless e.message.to_s.include?("GraphViz not installed or dot not in PATH") - Bundler.ui.error e.message - Bundler.ui.warn "Please install GraphViz. On a Mac with Homebrew, you can run `brew install graphviz`." - end - end -end diff --git a/lib/bundler/compact_index_client.rb b/lib/bundler/compact_index_client.rb index a4f5bb1638..6865e30dbc 100644 --- a/lib/bundler/compact_index_client.rb +++ b/lib/bundler/compact_index_client.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "pathname" require "set" module Bundler @@ -28,11 +27,7 @@ module Bundler # It may be called concurrently without global interpreter lock in some Rubies. # As a result, some methods may look more complex than necessary to save memory or time. class CompactIndexClient - # NOTE: MD5 is here not because we expect a server to respond with it, but - # because we use it to generate the etag on first request during the upgrade - # to the compact index client that uses opaque etags saved to files. - # Remove once 2.5.0 has been out for a while. - SUPPORTED_DIGESTS = { "sha-256" => :SHA256, "md5" => :MD5 }.freeze + SUPPORTED_DIGESTS = { "sha-256" => :SHA256 }.freeze DEBUG_MUTEX = Thread::Mutex.new # info returns an Array of INFO Arrays. Each INFO Array has the following indices: diff --git a/lib/bundler/compact_index_client/cache.rb b/lib/bundler/compact_index_client/cache.rb index bedd7f8028..3bae6c9efd 100644 --- a/lib/bundler/compact_index_client/cache.rb +++ b/lib/bundler/compact_index_client/cache.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "gem_parser" +require "rubygems/resolver/api_set/gem_parser" module Bundler class CompactIndexClient diff --git a/lib/bundler/compact_index_client/gem_parser.rb b/lib/bundler/compact_index_client/gem_parser.rb deleted file mode 100644 index 60a1817607..0000000000 --- a/lib/bundler/compact_index_client/gem_parser.rb +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class CompactIndexClient - if defined?(Gem::Resolver::APISet::GemParser) - GemParser = Gem::Resolver::APISet::GemParser - else - class GemParser - EMPTY_ARRAY = [].freeze - private_constant :EMPTY_ARRAY - - def parse(line) - version_and_platform, rest = line.split(" ", 2) - version, platform = version_and_platform.split("-", 2) - dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest - dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY - requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY - [version, platform, dependencies, requirements] - end - - private - - def parse_dependency(string) - dependency = string.split(":") - dependency[-1] = dependency[-1].split("&") if dependency.size > 1 - dependency[0] = -dependency[0] - dependency - end - end - end - end -end diff --git a/lib/bundler/compact_index_client/parser.rb b/lib/bundler/compact_index_client/parser.rb index 3276abdd68..43581fd7ef 100644 --- a/lib/bundler/compact_index_client/parser.rb +++ b/lib/bundler/compact_index_client/parser.rb @@ -64,7 +64,7 @@ module Bundler end def gem_parser - @gem_parser ||= GemParser.new + @gem_parser ||= Gem::Resolver::APISet::GemParser.new end # This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects. diff --git a/lib/bundler/compact_index_client/updater.rb b/lib/bundler/compact_index_client/updater.rb index 88c7146900..6066fdc7c4 100644 --- a/lib/bundler/compact_index_client/updater.rb +++ b/lib/bundler/compact_index_client/updater.rb @@ -37,7 +37,8 @@ module Bundler file.digests = parse_digests(response) # server may ignore Range and return the full response if response.is_a?(Gem::Net::HTTPPartialContent) - break false unless file.append(response.body.byteslice(1..-1)) + tail = response.body.byteslice(1..-1) + break false unless tail && file.append(tail) else file.write(response.body) end diff --git a/lib/bundler/current_ruby.rb b/lib/bundler/current_ruby.rb index 93e0c401c0..17c7655adb 100644 --- a/lib/bundler/current_ruby.rb +++ b/lib/bundler/current_ruby.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "rubygems_ext" + module Bundler # Returns current version of Ruby # @@ -9,41 +11,28 @@ module Bundler end class CurrentRuby - KNOWN_MINOR_VERSIONS = %w[ - 1.8 - 1.9 - 2.0 - 2.1 - 2.2 - 2.3 - 2.4 - 2.5 - 2.6 - 2.7 - 3.0 - 3.1 - 3.2 - 3.3 - ].freeze - - KNOWN_MAJOR_VERSIONS = KNOWN_MINOR_VERSIONS.map {|v| v.split(".", 2).first }.uniq.freeze - - KNOWN_PLATFORMS = %w[ - jruby - maglev - mingw - mri - mswin - mswin64 - rbx - ruby - truffleruby - windows - x64_mingw - ].freeze + ALL_RUBY_VERSIONS = [*18..27, *30..34, *40..41].freeze + KNOWN_MINOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.reverse.join(".") }.freeze + KNOWN_MAJOR_VERSIONS = ALL_RUBY_VERSIONS.map {|v| v.digits.last.to_s }.uniq.freeze + PLATFORM_MAP = { + ruby: [Gem::Platform::RUBY, CurrentRuby::ALL_RUBY_VERSIONS], + mri: [Gem::Platform::RUBY, CurrentRuby::ALL_RUBY_VERSIONS], + rbx: [Gem::Platform::RUBY], + truffleruby: [Gem::Platform::RUBY], + jruby: [Gem::Platform::JAVA, [18, 19]], + windows: [Gem::Platform::WINDOWS, CurrentRuby::ALL_RUBY_VERSIONS], + # deprecated + mswin: [Gem::Platform::MSWIN, CurrentRuby::ALL_RUBY_VERSIONS], + mswin64: [Gem::Platform::MSWIN64, CurrentRuby::ALL_RUBY_VERSIONS - [18]], + mingw: [Gem::Platform::UNIVERSAL_MINGW, CurrentRuby::ALL_RUBY_VERSIONS], + x64_mingw: [Gem::Platform::UNIVERSAL_MINGW, CurrentRuby::ALL_RUBY_VERSIONS - [18, 19]], + }.each_with_object({}) do |(platform, spec), hash| + hash[platform] = spec[0] + spec[1]&.each {|version| hash[:"#{platform}_#{version}"] = spec[0] } + end.freeze def ruby? - return true if Bundler::GemHelpers.generic_local_platform_is_ruby? + return true if Bundler::MatchPlatform.generic_local_platform_is_ruby? !windows? && (RUBY_ENGINE == "ruby" || RUBY_ENGINE == "rbx" || RUBY_ENGINE == "maglev" || RUBY_ENGINE == "truffleruby") end @@ -61,7 +50,10 @@ module Bundler end def maglev? - RUBY_ENGINE == "maglev" + removed_message = + "`CurrentRuby#maglev?` was removed with no replacement. Please use the " \ + "built-in Ruby `RUBY_ENGINE` constant to check the Ruby implementation you are running on." + SharedHelpers.feature_removed!(removed_message) end def truffleruby? @@ -82,11 +74,21 @@ module Bundler RUBY_VERSION.start_with?("#{version}.") end - KNOWN_PLATFORMS.each do |platform| + PLATFORM_MAP.keys.each do |platform| define_method(:"#{platform}_#{trimmed_version}?") do send(:"#{platform}?") && send(:"on_#{trimmed_version}?") end end + + define_method(:"maglev_#{trimmed_version}?") do + removed_message = + "`CurrentRuby##{__method__}` was removed with no replacement. Please use the " \ + "built-in Ruby `RUBY_ENGINE` and `RUBY_VERSION` constants to perform a similar check." + + SharedHelpers.feature_removed!(removed_message) + + send(:"maglev?") && send(:"on_#{trimmed_version}?") + end end end end diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 5d5dc1461c..5ab577f504 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -1,25 +1,26 @@ # frozen_string_literal: true require_relative "lockfile_parser" +require_relative "worker" module Bundler class Definition - include GemHelpers - class << self # Do not create or modify a lockfile (Makes #lock a noop) attr_accessor :no_lock end + attr_writer :lockfile + attr_reader( :dependencies, + :locked_checksums, :locked_deps, :locked_gems, :platforms, :ruby_version, :lockfile, :gemfiles, - :locked_checksums, :sources ) @@ -58,17 +59,30 @@ module Bundler # @param ruby_version [Bundler::RubyVersion, nil] Requested Ruby Version # @param optional_groups [Array(String)] A list of optional groups def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, optional_groups = [], gemfiles = []) - if [true, false].include?(unlock) + unlock ||= {} + + if unlock == true + @unlocking_all = true + strict = false @unlocking_bundler = false @unlocking = unlock + @sources_to_unlock = [] + @unlocking_ruby = false + @explicit_unlocks = [] + conservative = false else + @unlocking_all = false + strict = unlock.delete(:strict) @unlocking_bundler = unlock.delete(:bundler) @unlocking = unlock.any? {|_k, v| !Array(v).empty? } + @sources_to_unlock = unlock.delete(:sources) || [] + @unlocking_ruby = unlock.delete(:ruby) + @explicit_unlocks = unlock.delete(:gems) || [] + conservative = unlock.delete(:conservative) end @dependencies = dependencies @sources = sources - @unlock = unlock @optional_groups = optional_groups @prefer_local = false @specs = nil @@ -83,74 +97,66 @@ module Bundler @locked_ruby_version = nil @new_platforms = [] - @removed_platform = nil + @removed_platforms = [] + @originally_invalid_platforms = [] if lockfile_exists? @lockfile_contents = Bundler.read_file(lockfile) - @locked_gems = LockfileParser.new(@lockfile_contents) + @locked_gems = LockfileParser.new(@lockfile_contents, strict: strict) @locked_platforms = @locked_gems.platforms + @most_specific_locked_platform = @locked_gems.most_specific_locked_platform @platforms = @locked_platforms.dup @locked_bundler_version = @locked_gems.bundler_version @locked_ruby_version = @locked_gems.ruby_version - @originally_locked_deps = @locked_gems.dependencies + @locked_deps = @locked_gems.dependencies @originally_locked_specs = SpecSet.new(@locked_gems.specs) + @originally_locked_sources = @locked_gems.sources @locked_checksums = @locked_gems.checksums - if unlock != true - @locked_deps = @originally_locked_deps - @locked_specs = @originally_locked_specs - @locked_sources = @locked_gems.sources - else - @unlock = {} - @locked_deps = {} + if @unlocking_all @locked_specs = SpecSet.new([]) @locked_sources = [] + else + @locked_specs = @originally_locked_specs + @locked_sources = @originally_locked_sources end - else - @unlock = {} - @platforms = [] - @locked_gems = nil - @locked_deps = {} - @locked_specs = SpecSet.new([]) - @originally_locked_deps = {} - @originally_locked_specs = @locked_specs - @locked_sources = [] - @locked_platforms = [] - @locked_checksums = Bundler.feature_flag.bundler_3_mode? - end - locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) } - @multisource_allowed = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? && Bundler.frozen_bundle? + locked_gem_sources = @originally_locked_sources.select {|s| s.is_a?(Source::Rubygems) } + multisource_lockfile = locked_gem_sources.size == 1 && locked_gem_sources.first.multiple_remotes? - if @multisource_allowed - unless sources.aggregate_global_source? + if multisource_lockfile msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. Make sure you run `bundle install` in non frozen mode and commit the result to make your lockfile secure." - Bundler::SharedHelpers.major_deprecation 2, msg + Bundler::SharedHelpers.feature_removed! msg end - - @sources.merged_gem_lockfile_sections!(locked_gem_sources.first) + else + @locked_gems = nil + @locked_platforms = [] + @most_specific_locked_platform = nil + @platforms = [] + @locked_deps = {} + @locked_specs = SpecSet.new([]) + @locked_sources = [] + @originally_locked_specs = @locked_specs + @originally_locked_sources = @locked_sources + @locked_checksums = Bundler.settings[:lockfile_checksums] end - @sources_to_unlock = @unlock.delete(:sources) || [] - @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object + @unlocking_ruby ||= if @ruby_version && locked_ruby_version_object @ruby_version.diff(locked_ruby_version_object) end - @unlocking ||= @unlock[:ruby] ||= (!@locked_ruby_version ^ !@ruby_version) + @unlocking ||= @unlocking_ruby ||= (!@locked_ruby_version ^ !@ruby_version) @current_platform_missing = add_current_platform unless Bundler.frozen_bundle? - converge_path_sources_to_gemspec_sources - @path_changes = converge_paths @source_changes = converge_sources + @path_changes = converge_paths - @explicit_unlocks = @unlock.delete(:gems) || [] - - if @unlock[:conservative] + if conservative @gems_to_unlock = @explicit_unlocks.any? ? @explicit_unlocks : @dependencies.map(&:name) else eager_unlock = @explicit_unlocks.map {|name| Dependency.new(name, ">= 0") } - @gems_to_unlock = @locked_specs.for(eager_unlock, false, platforms).map(&:name).uniq + @gems_to_unlock = @locked_specs.for(eager_unlock, platforms).map(&:name).uniq end @dependency_changes = converge_dependencies @@ -175,20 +181,53 @@ module Bundler resolve end + # + # Setup sources according to the given options and the state of the + # definition. + # + # @return [Boolean] Whether fetching remote information will be necessary or not + # + def setup_domain!(options = {}) + prefer_local! if options[:"prefer-local"] + + sources.cached! + + if options[:add_checksums] || (!options[:local] && install_needed?) + sources.remote! + true + else + Bundler.settings.set_command_option(:jobs, 1) unless install_needed? # to avoid the overhead of Bundler::Worker + sources.local! + false + end + end + def resolve_with_cache! + with_cache! + + resolve + end + + def with_cache! sources.local! sources.cached! - resolve end def resolve_remotely! + remotely! + + resolve + end + + def remotely! sources.cached! sources.remote! - resolve end def prefer_local! @prefer_local = true + + sources.prefer_local! end # For given dependency list returns a SpecSet with Gemspec of all the required @@ -210,7 +249,7 @@ module Bundler end def missing_specs - resolve.materialize(requested_dependencies).missing_specs + resolve.missing_specs_for(requested_dependencies) end def missing_specs? @@ -221,7 +260,7 @@ module Bundler rescue BundlerError => e @resolve = nil @resolver = nil - @resolution_packages = nil + @resolution_base = nil @source_requirements = nil @specs = nil @@ -246,12 +285,17 @@ module Bundler end def filter_relevant(dependencies) - platforms_array = [generic_local_platform].freeze dependencies.select do |d| - d.should_include? && !d.gem_platforms(platforms_array).empty? + relevant_deps?(d) end end + def relevant_deps?(dep) + platforms_array = [Bundler.generic_local_platform].freeze + + dep.should_include? && !dep.gem_platforms(platforms_array).empty? + end + def locked_dependencies @locked_deps.values end @@ -294,29 +338,25 @@ module Bundler SpecSet.new(filter_specs(@locked_specs, @dependencies - deleted_deps)) else Bundler.ui.debug "Found no changes, using resolution from the lockfile" - if @removed_platform || @locked_gems.may_include_redundant_platform_specific_gems? + if @removed_platforms.any? || @locked_gems.may_include_redundant_platform_specific_gems? SpecSet.new(filter_specs(@locked_specs, @dependencies)) else @locked_specs end end else - if lockfile_exists? - Bundler.ui.debug "Found changes from the lockfile, re-resolving dependencies because #{change_reason}" - else - Bundler.ui.debug "Resolving dependencies because there's no lockfile" - end + Bundler.ui.debug resolve_needed_reason start_resolution end end def spec_git_paths - sources.git_sources.map {|s| File.realpath(s.path) if File.exist?(s.path) }.compact + sources.git_sources.filter_map {|s| File.realpath(s.path) if File.exist?(s.path) } end def groups - dependencies.map(&:groups).flatten.uniq + dependencies.flat_map(&:groups).uniq end def lock(file_or_preserve_unknown_sections = false, preserve_unknown_sections_or_unused = false) @@ -335,15 +375,53 @@ module Bundler msg = "`Definition#lock` was passed a target file argument. #{suggestion}" - Bundler::SharedHelpers.major_deprecation 2, msg + Bundler::SharedHelpers.feature_removed! msg end write_lock(target_lockfile, preserve_unknown_sections) end + def write_lock(file, preserve_unknown_sections) + return if Definition.no_lock || !lockfile || file.nil? + + contents = to_lock + + # Convert to \r\n if the existing lock has them + # i.e., Windows with `git config core.autocrlf=true` + contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + + if @locked_bundler_version + locked_major = @locked_bundler_version.segments.first + current_major = bundler_version_to_lock.segments.first + + updating_major = locked_major < current_major + end + + preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + + if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) + return if Bundler.frozen_bundle? + SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } + return + end + + if Bundler.frozen_bundle? + Bundler.ui.error "Cannot write a changed lockfile while frozen." + return + end + + begin + SharedHelpers.filesystem_access(file) do |p| + File.open(p, "wb") {|f| f.puts(contents) } + end + rescue ReadOnlyFileSystemError + raise ProductionError, lockfile_changes_summary("file system is read-only") + end + end + def locked_ruby_version return unless ruby_version - if @unlock[:ruby] || !@locked_ruby_version + if @unlocking_ruby || !@locked_ruby_version Bundler::RubyVersion.system else @locked_ruby_version @@ -376,51 +454,18 @@ module Bundler raise ProductionError, "Frozen mode is set, but there's no lockfile" unless lockfile_exists? - added = [] - deleted = [] - changed = [] - - new_platforms = @platforms - @locked_platforms - deleted_platforms = @locked_platforms - @platforms - added.concat new_platforms.map {|p| "* platform: #{p}" } - deleted.concat deleted_platforms.map {|p| "* platform: #{p}" } - - added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? - deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? - - both_sources = Hash.new {|h, k| h[k] = [] } - current_dependencies.each {|d| both_sources[d.name][0] = d } - current_locked_dependencies.each {|d| both_sources[d.name][1] = d } - - both_sources.each do |name, (dep, lock_dep)| - next if dep.nil? || lock_dep.nil? - - gemfile_source = dep.source || default_source - lock_source = lock_dep.source || default_source - next if lock_source.include?(gemfile_source) - - gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source" - lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source" - changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" - end - - reason = nothing_changed? ? "some dependencies were deleted from your gemfile" : change_reason - msg = String.new - msg << "#{reason.capitalize.strip}, but the lockfile can't be updated because frozen mode is set" - msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? - msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? - msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? - msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_gemfile_path} to version control.\n" + msg = lockfile_changes_summary("frozen mode is set") + return unless msg unless explicit_flag suggested_command = unless Bundler.settings.locations("frozen").keys.include?(:env) "bundle config set frozen false" end - msg << "If this is a development machine, remove the #{SharedHelpers.relative_lockfile_path} " \ + msg << "\n\nIf this is a development machine, remove the #{SharedHelpers.relative_lockfile_path} " \ "freeze by running `#{suggested_command}`." if suggested_command end - raise ProductionError, msg if added.any? || deleted.any? || changed.any? || !nothing_changed? + raise ProductionError, msg end def validate_runtime! @@ -454,17 +499,17 @@ module Bundler end def validate_platforms! - return if current_platform_locked? + return if current_platform_locked? || @platforms.include?(Gem::Platform::RUBY) raise ProductionError, "Your bundle only supports platforms #{@platforms.map(&:to_s)} " \ - "but your local platform is #{local_platform}. " \ - "Add the current platform to the lockfile with\n`bundle lock --add-platform #{local_platform}` and try again." + "but your local platform is #{Bundler.local_platform}. " \ + "Add the current platform to the lockfile with\n`bundle lock --add-platform #{Bundler.local_platform}` and try again." end def normalize_platforms - @platforms = resolve.normalize_platforms!(current_dependencies, platforms) + resolve.normalize_platforms!(current_dependencies, platforms) - @resolve = SpecSet.new(resolve.for(current_dependencies, false, @platforms)) + @resolve = SpecSet.new(resolve.for(current_dependencies, @platforms)) end def add_platform(platform) @@ -475,89 +520,121 @@ module Bundler end def remove_platform(platform) - removed_platform = @platforms.delete(Gem::Platform.new(platform)) - @removed_platform ||= removed_platform - return if removed_platform - raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" - end + raise InvalidOption, "Unable to remove the platform `#{platform}` since the only platforms are #{@platforms.join ", "}" unless @platforms.include?(platform) - def most_specific_locked_platform - @platforms.min_by do |bundle_platform| - platform_specificity_match(bundle_platform, local_platform) - end + @removed_platforms << platform + @platforms.delete(platform) end def nothing_changed? - return false unless lockfile_exists? - - !@source_changes && - !@dependency_changes && - !@current_platform_missing && - @new_platforms.empty? && - !@path_changes && - !@local_changes && - !@missing_lockfile_dep && - !@unlocking_bundler && - !@locked_spec_with_missing_deps && - !@locked_spec_with_invalid_deps + !something_changed? end def no_resolve_needed? - !unlocking? && nothing_changed? + !resolve_needed? end def unlocking? @unlocking end - attr_writer :source_requirements + def add_checksums + require "rubygems/package" - private + @locked_checksums = true - def should_add_extra_platforms? - !lockfile_exists? && generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] - end + setup_domain!(add_checksums: true) - def lockfile_exists? - lockfile && File.exist?(lockfile) + # force materialization to real specifications, so that checksums are fetched + specs.each do |spec| + next unless spec.source.is_a?(Bundler::Source::Rubygems) + # Checksum was fetched from the compact index API. + next if !spec.source.checksum_store.missing?(spec) && !spec.source.checksum_store.empty?(spec) + # The gem isn't installed, can't compute the checksum. + next unless spec.loaded_from + + package = Gem::Package.new(spec.source.cached_built_in_gem(spec)) + checksum = Checksum.from_gem_package(package) + spec.source.checksum_store.register(spec, checksum) + end end - def write_lock(file, preserve_unknown_sections) - return if Definition.no_lock || file.nil? + private - contents = to_lock + def lockfile_changes_summary(update_refused_reason) + added = [] + deleted = [] + changed = [] - # Convert to \r\n if the existing lock has them - # i.e., Windows with `git config core.autocrlf=true` - contents.gsub!(/\n/, "\r\n") if @lockfile_contents.match?("\r\n") + added.concat @new_platforms.map {|p| "* platform: #{p}" } + deleted.concat @removed_platforms.map {|p| "* platform: #{p}" } - if @locked_bundler_version - locked_major = @locked_bundler_version.segments.first - current_major = bundler_version_to_lock.segments.first + added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any? + deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any? - updating_major = locked_major < current_major - end + both_sources = Hash.new {|h, k| h[k] = [] } + current_dependencies.each {|d| both_sources[d.name][0] = d } + current_locked_dependencies.each {|d| both_sources[d.name][1] = d } - preserve_unknown_sections ||= !updating_major && (Bundler.frozen_bundle? || !(unlocking? || @unlocking_bundler)) + both_sources.each do |name, (dep, lock_dep)| + next if dep.nil? || lock_dep.nil? - if File.exist?(file) && lockfiles_equal?(@lockfile_contents, contents, preserve_unknown_sections) - return if Bundler.frozen_bundle? - SharedHelpers.filesystem_access(file) { FileUtils.touch(file) } - return - end + gemfile_source = dep.source || default_source + lock_source = lock_dep.source || default_source + next if lock_source.include?(gemfile_source) - if Bundler.frozen_bundle? - Bundler.ui.error "Cannot write a changed lockfile while frozen." - return + gemfile_source_name = dep.source ? gemfile_source.to_gemfile : "no specified source" + lockfile_source_name = lock_dep.source ? lock_source.to_gemfile : "no specified source" + changed << "* #{name} from `#{lockfile_source_name}` to `#{gemfile_source_name}`" end - SharedHelpers.filesystem_access(file) do |p| - File.open(p, "wb") {|f| f.puts(contents) } - end + return unless added.any? || deleted.any? || changed.any? || resolve_needed? + + msg = String.new("#{change_reason[0].upcase}#{change_reason[1..-1].strip}, but ") + msg << "the lockfile " unless msg.start_with?("Your lockfile") + msg << "can't be updated because #{update_refused_reason}" + msg << "\n\nYou have added to the Gemfile:\n" << added.join("\n") if added.any? + msg << "\n\nYou have deleted from the Gemfile:\n" << deleted.join("\n") if deleted.any? + msg << "\n\nYou have changed in the Gemfile:\n" << changed.join("\n") if changed.any? + msg << "\n\nRun `bundle install` elsewhere and add the updated #{SharedHelpers.relative_lockfile_path} to version control.\n" unless unlocking? + msg + end + + def install_needed? + resolve_needed? || missing_specs? + end + + def something_changed? + return true unless lockfile_exists? + + @source_changes || + @dependency_changes || + @current_platform_missing || + @new_platforms.any? || + @path_changes || + @local_changes || + @missing_lockfile_dep || + @unlocking_bundler || + @locked_spec_with_missing_checksums || + @locked_spec_with_empty_checksums || + @locked_spec_with_missing_deps || + @locked_spec_with_invalid_deps + end + + def resolve_needed? + unlocking? || something_changed? + end + + def should_add_extra_platforms? + !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform] + end + + def lockfile_exists? + lockfile && File.exist?(lockfile) end def resolver - @resolver ||= Resolver.new(resolution_packages, gem_version_promoter) + @resolver ||= new_resolver(resolution_base) end def expanded_dependencies @@ -571,33 +648,51 @@ module Bundler [Dependency.new("bundler", @unlocking_bundler)] + dependencies end - def resolution_packages - @resolution_packages ||= begin + def resolution_base + @resolution_base ||= begin last_resolve = converge_locked_specs remove_invalid_platforms! - packages = Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: @gems_to_unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local) - packages = additional_base_requirements_to_prevent_downgrades(packages, last_resolve) - packages = additional_base_requirements_to_force_updates(packages) - packages + base = new_resolution_base(last_resolve: last_resolve, unlock: @unlocking_all || @gems_to_unlock) + base = additional_base_requirements_to_prevent_downgrades(base) + base = additional_base_requirements_to_force_updates(base) + base end end - def filter_specs(specs, deps) - SpecSet.new(specs).for(deps, false, platforms) + def filter_specs(specs, deps, skips: []) + SpecSet.new(specs).for(deps, platforms, skips: skips) end def materialize(dependencies) - specs = resolve.materialize(dependencies) - missing_specs = specs.missing_specs + specs = begin + resolve.materialize(dependencies) + rescue IncorrectLockfileDependencies => e + raise if Bundler.frozen_bundle? + + reresolve_without([e.spec]) + retry + end + + missing_specs = resolve.missing_specs if missing_specs.any? missing_specs.each do |s| locked_gem = @locked_specs[s.name].last next if locked_gem.nil? || locked_gem.version != s.version || sources.local_mode? - raise GemNotFound, "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \ - "no longer be found in that source. That means the author of #{locked_gem} has removed it. " \ - "You'll need to update your bundle to a version other than #{locked_gem} that hasn't been " \ - "removed in order to install." + + message = if sources.implicit_global_source? + "Because your Gemfile specifies no global remote source, your bundle is locked to " \ + "#{locked_gem} from #{locked_gem.source}. However, #{locked_gem} is not installed. You'll " \ + "need to either add a global remote source to your Gemfile or make sure #{locked_gem} is " \ + "available locally before rerunning Bundler." + else + "Your bundle is locked to #{locked_gem} from #{locked_gem.source}, but that version can " \ + "no longer be found in that source. That means the author of #{locked_gem} has removed it. " \ + "You'll need to update your bundle to a version other than #{locked_gem} that hasn't been " \ + "removed in order to install." + end + + raise GemNotFound, message end missing_specs_list = missing_specs.group_by(&:source).map do |source, missing_specs_for_source| @@ -607,106 +702,164 @@ module Bundler raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}" end - incomplete_specs = specs.incomplete_specs + partially_missing_specs = resolve.partially_missing_specs + + if partially_missing_specs.any? && !sources.local_mode? + Bundler.ui.warn "Some locked specs have possibly been yanked (#{partially_missing_specs.map(&:full_name).join(", ")}). Ignoring them..." + + resolve.delete(partially_missing_specs) + end + + incomplete_specs = resolve.incomplete_specs loop do break if incomplete_specs.empty? Bundler.ui.debug("The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies") sources.remote! - resolution_packages.delete(incomplete_specs) - @resolve = start_resolution + reresolve_without(incomplete_specs) specs = resolve.materialize(dependencies) - still_incomplete_specs = specs.incomplete_specs + still_incomplete_specs = resolve.incomplete_specs if still_incomplete_specs == incomplete_specs - package = resolution_packages.get_package(incomplete_specs.first.name) - resolver.raise_not_found! package + resolver.raise_incomplete! incomplete_specs end incomplete_specs = still_incomplete_specs end + insecurely_materialized_specs = resolve.insecurely_materialized_specs + + if insecurely_materialized_specs.any? + Bundler.ui.warn "The following platform specific gems are getting installed, yet the lockfile includes only their generic ruby version:\n" \ + " * #{insecurely_materialized_specs.map(&:full_name).join("\n * ")}\n" \ + "Please run `bundle lock --normalize-platforms` and commit the resulting lockfile.\n" \ + "Alternatively, you may run `bundle lock --add-platform <list-of-platforms-that-you-want-to-support>`" + end + bundler = sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last specs["bundler"] = bundler specs end + def reresolve_without(incomplete_specs) + resolution_base.delete(incomplete_specs) + @resolve = start_resolution + end + def start_resolution - local_platform_needed_for_resolvability = @most_specific_non_local_locked_ruby_platform && !@platforms.include?(local_platform) - @platforms << local_platform if local_platform_needed_for_resolvability - add_platform(Gem::Platform::RUBY) if RUBY_ENGINE == "truffleruby" + local_platform_needed_for_resolvability = @most_specific_non_local_locked_platform && !@platforms.include?(Bundler.local_platform) + @platforms << Bundler.local_platform if local_platform_needed_for_resolvability result = SpecSet.new(resolver.start) @resolved_bundler_version = result.find {|spec| spec.name == "bundler" }&.version - if @most_specific_non_local_locked_ruby_platform - if spec_set_incomplete_for_platform?(result, @most_specific_non_local_locked_ruby_platform) - @platforms.delete(@most_specific_non_local_locked_ruby_platform) + @new_platforms.each do |platform| + incomplete_specs = result.incomplete_specs_for_platform(current_dependencies, platform) + + if incomplete_specs.any? + resolver.raise_incomplete! incomplete_specs + end + end + + if @most_specific_non_local_locked_platform + if result.incomplete_for_platform?(current_dependencies, @most_specific_non_local_locked_platform) + @platforms.delete(@most_specific_non_local_locked_platform) elsif local_platform_needed_for_resolvability - @platforms.delete(local_platform) + @platforms.delete(Bundler.local_platform) end end - @platforms = result.add_extra_platforms!(platforms) if should_add_extra_platforms? + if should_add_extra_platforms? + result.add_extra_platforms!(platforms) + elsif @originally_invalid_platforms.any? + result.add_originally_invalid_platforms!(platforms, @originally_invalid_platforms) + end - SpecSet.new(result.for(dependencies, false, @platforms)) + SpecSet.new(result.for(dependencies, @platforms | [Gem::Platform::RUBY])) end def precompute_source_requirements_for_indirect_dependencies? - sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && !sources.aggregate_global_source? + sources.non_global_rubygems_sources.all?(&:dependency_api_available?) end def current_platform_locked? @platforms.any? do |bundle_platform| - MatchPlatform.platforms_match?(bundle_platform, local_platform) + Bundler.generic_local_platform == bundle_platform || Bundler.local_platform === bundle_platform end end def add_current_platform - return if @platforms.include?(local_platform) + return if @platforms.include?(Bundler.local_platform) - @most_specific_non_local_locked_ruby_platform = find_most_specific_locked_ruby_platform - return if @most_specific_non_local_locked_ruby_platform + @most_specific_non_local_locked_platform = find_most_specific_locked_platform + return if @most_specific_non_local_locked_platform - @platforms << local_platform + @platforms << Bundler.local_platform true end - def find_most_specific_locked_ruby_platform - return unless generic_local_platform_is_ruby? && current_platform_locked? + def find_most_specific_locked_platform + return unless current_platform_locked? - most_specific_locked_platform + @most_specific_locked_platform end - def change_reason - if unlocking? - unlock_targets = if @gems_to_unlock.any? - ["gems", @gems_to_unlock] - elsif @sources_to_unlock.any? - ["sources", @sources_to_unlock] + def resolve_needed_reason + if lockfile_exists? + if unlocking? + "Re-resolving dependencies because #{unlocking_reason}" + else + "Found changes from the lockfile, re-resolving dependencies because #{lockfile_changed_reason}" end + else + "Resolving dependencies because there's no lockfile" + end + end - unlock_reason = if unlock_targets - "#{unlock_targets.first}: (#{unlock_targets.last.join(", ")})" + def change_reason + if resolve_needed? + if unlocking? + unlocking_reason else - @unlock[:ruby] ? "ruby" : "" + lockfile_changed_reason end + else + "some dependencies were deleted from your gemfile" + end + end + + def unlocking_reason + unlock_targets = if @gems_to_unlock.any? + ["gems", @gems_to_unlock] + elsif @sources_to_unlock.any? + ["sources", @sources_to_unlock] + end - return "bundler is unlocking #{unlock_reason}" + unlock_reason = if unlock_targets + "#{unlock_targets.first}: (#{unlock_targets.last.join(", ")})" + else + @unlocking_ruby ? "ruby" : "" end + + "bundler is unlocking #{unlock_reason}" + end + + def lockfile_changed_reason [ [@source_changes, "the list of sources changed"], [@dependency_changes, "the dependencies in your gemfile changed"], - [@current_platform_missing, "your lockfile does not include the current platform"], - [@new_platforms.any?, "you added a new platform to your gemfile"], + [@current_platform_missing, "your lockfile is missing the current platform"], + [@new_platforms.any?, "you are adding a new platform to your lockfile"], [@path_changes, "the gemspecs for path gems changed"], [@local_changes, "the gemspecs for git local gems changed"], - [@missing_lockfile_dep, "your lock file is missing \"#{@missing_lockfile_dep}\""], + [@missing_lockfile_dep, "your lockfile is missing \"#{@missing_lockfile_dep}\""], [@unlocking_bundler, "an update to the version of Bundler itself was requested"], - [@locked_spec_with_missing_deps, "your lock file includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"], + [@locked_spec_with_missing_checksums, "your lockfile is missing a CHECKSUMS entry for \"#{@locked_spec_with_missing_checksums}\""], + [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""], + [@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"], [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""], ].select(&:first).map(&:last).join(", ") end @@ -723,8 +876,8 @@ module Bundler !locked || dependencies_for_source_changed?(source, locked) || specs_for_source_changed?(source) end - def dependencies_for_source_changed?(source, locked_source = source) - deps_for_source = @dependencies.select {|s| s.source == source } + def dependencies_for_source_changed?(source, locked_source) + deps_for_source = @dependencies.select {|dep| dep.source == source } locked_deps_for_source = locked_dependencies.select {|dep| dep.source == locked_source } deps_for_source.uniq.sort != locked_deps_for_source.sort @@ -732,7 +885,7 @@ module Bundler def specs_for_source_changed?(source) locked_index = Index.new - locked_index.use(@locked_specs.select {|s| source.can_lock?(s) }) + locked_index.use(@locked_specs.select {|s| s.replace_source_with!(source) }) !locked_index.subset?(source.specs) rescue PathError, GitError => e @@ -762,29 +915,40 @@ module Bundler end def check_lockfile - @missing_lockfile_dep = nil - @locked_spec_with_invalid_deps = nil @locked_spec_with_missing_deps = nil + @locked_spec_with_missing_checksums = nil + @locked_spec_with_empty_checksums = nil - missing = [] + missing_deps = [] + missing_checksums = [] + empty_checksums = [] invalid = [] @locked_specs.each do |s| + if @locked_checksums + checksum_store = s.source.checksum_store + + if checksum_store.missing?(s) + missing_checksums << s + elsif checksum_store.empty?(s) + empty_checksums << s + end + end + validation = @locked_specs.validate_deps(s) - missing << s if validation == :missing + missing_deps << s if validation == :missing invalid << s if validation == :invalid end - if missing.any? - @locked_specs.delete(missing) + @locked_spec_with_missing_checksums = missing_checksums.first.name if missing_checksums.any? + @locked_spec_with_empty_checksums = empty_checksums.first.name if empty_checksums.any? + + if missing_deps.any? + @locked_specs.delete(missing_deps) - @locked_spec_with_missing_deps = missing.first.name - elsif !@dependency_changes - @missing_lockfile_dep = current_dependencies.find do |d| - @locked_specs[d.name].empty? && d.name != "bundler" - end&.name + @locked_spec_with_missing_deps = missing_deps.first.name end if invalid.any? @@ -800,24 +964,6 @@ module Bundler end end - def converge_path_source_to_gemspec_source(source) - return source unless source.instance_of?(Source::Path) - gemspec_source = sources.path_sources.find {|s| s.is_a?(Source::Gemspec) && s.as_path_source == source } - gemspec_source || source - end - - def converge_path_sources_to_gemspec_sources - @locked_sources.map! do |source| - converge_path_source_to_gemspec_source(source) - end - @locked_specs.each do |spec| - spec.source &&= converge_path_source_to_gemspec_source(spec.source) - end - @locked_deps.each do |_, dep| - dep.source &&= converge_path_source_to_gemspec_source(dep.source) - end - end - def converge_sources # Replace the sources from the Gemfile with the sources from the Gemfile.lock, # if they exist in the Gemfile.lock and are `==`. If you can't find an equivalent @@ -827,7 +973,7 @@ module Bundler sources.all_sources.each do |source| # has to be done separately, because we want to keep the locked checksum # store for a source, even when doing a full update - if @locked_checksums && @locked_gems && locked_source = @locked_gems.sources.find {|s| s == source && !s.equal?(source) } + if @locked_checksums && @locked_gems && locked_source = @originally_locked_sources.find {|s| s == source && !s.equal?(source) } source.checksum_store.merge!(locked_source.checksum_store) end # If the source is unlockable and the current command allows an unlock of @@ -845,32 +991,40 @@ module Bundler end def converge_dependencies - changes = false + @missing_lockfile_dep = nil + @changed_dependencies = [] @dependencies.each do |dep| if dep.source dep.source = sources.get(dep.source) end + next unless relevant_deps?(dep) - unless locked_dep = @originally_locked_deps[dep.name] - changes = true - next + name = dep.name + + dep_changed = @locked_deps[name].nil? + + unless name == "bundler" + locked_specs = @originally_locked_specs[name] + + if locked_specs.empty? + @missing_lockfile_dep = name if dep_changed == false + else + if locked_specs.map(&:source).uniq.size > 1 + @locked_specs.delete(locked_specs.select {|s| s.source != dep.source }) + end + + unless dep.matches_spec?(locked_specs.first) + @gems_to_unlock << name + dep_changed = true + end + end end - # Gem::Dependency#== matches Gem::Dependency#type. As the lockfile - # doesn't carry a notion of the dependency type, if you use - # add_development_dependency in a gemspec that's loaded with the gemspec - # directive, the lockfile dependencies and resolved dependencies end up - # with a mismatch on #type. Work around that by setting the type on the - # dep from the lockfile. - locked_dep.instance_variable_set(:@type, dep.type) - - # We already know the name matches from the hash lookup - # so we only need to check the requirement now - changes ||= dep.requirement != locked_dep.requirement + @changed_dependencies << name if dep_changed end - changes + @changed_dependencies.any? end # Remove elements from the locked specs that are expired. This will most @@ -879,7 +1033,7 @@ module Bundler def converge_locked_specs converged = converge_specs(@locked_specs) - resolve = SpecSet.new(converged.reject {|s| @gems_to_unlock.include?(s.name) }) + resolve = SpecSet.new(converged) diff = nil @@ -902,41 +1056,46 @@ module Bundler specs.each do |s| name = s.name + next if @gems_to_unlock.include?(name) + dep = @dependencies.find {|d| s.satisfies?(d) } lockfile_source = s.source if dep - gemfile_source = dep.source || default_source - - deps << dep if !dep.source || lockfile_source.include?(dep.source) - @gems_to_unlock << name if lockfile_source.include?(dep.source) && lockfile_source != gemfile_source + replacement_source = dep.source - # Replace the locked dependency's source with the equivalent source from the Gemfile - s.source = gemfile_source + deps << dep if !replacement_source || lockfile_source.include?(replacement_source) || new_deps.include?(dep) else - # Replace the locked dependency's source with the default source, if the locked source is no longer in the Gemfile - s.source = default_source unless sources.get(lockfile_source) - end + parent_dep = @dependencies.find do |d| + next unless d.source && d.source != lockfile_source + next if d.source.is_a?(Source::Gemspec) - next if @sources_to_unlock.include?(s.source.name) + parent_locked_specs = @originally_locked_specs[d.name] - # Path sources have special logic - if s.source.instance_of?(Source::Path) || s.source.instance_of?(Source::Gemspec) - new_specs = begin - s.source.specs - rescue PathError - # if we won't need the source (according to the lockfile), - # don't error if the path source isn't available - next if specs. - for(requested_dependencies, false). - none? {|locked_spec| locked_spec.source == s.source } - - raise + parent_locked_specs.any? do |parent_spec| + parent_spec.runtime_dependencies.any? {|rd| rd.name == s.name } + end end - new_spec = new_specs[s].first + if parent_dep + replacement_source = parent_dep.source + else + replacement_source = sources.get(lockfile_source) + end + end + + # Replace the locked dependency's source with the equivalent source from the Gemfile + s.source = replacement_source || default_source + next if s.source_changed? + + source = s.source + next if @sources_to_unlock.include?(source.name) + + # Path sources have special logic + if source.is_a?(Source::Path) + new_spec = source.specs[s].first if new_spec - s.dependencies.replace(new_spec.dependencies) + s.runtime_dependencies.replace(new_spec.runtime_dependencies) else # If the spec is no longer in the path source, unlock it. This # commonly happens if the version changed in the gemspec @@ -944,14 +1103,10 @@ module Bundler end end - if dep.nil? && requested_dependencies.find {|d| name == d.name } - @gems_to_unlock << s.name - else - converged << s - end + converged << s end - filter_specs(converged, deps) + filter_specs(converged, deps, skips: @gems_to_unlock) end def metadata_dependencies @@ -965,7 +1120,23 @@ module Bundler @source_requirements ||= find_source_requirements end + def preload_git_source_worker + @preload_git_source_worker ||= Bundler::Worker.new(5, "Git source preloading", ->(source, _) { source.specs }) + end + + def preload_git_sources + sources.git_sources.each {|source| preload_git_source_worker.enq(source) } + ensure + preload_git_source_worker.stop + end + def find_source_requirements + if Gem.ruby_version >= Gem::Version.new("3.3") + # Ruby 3.2 has a bug that incorrectly triggers a circular dependency warning. This version will continue to + # fetch git repositories one by one. + preload_git_sources + end + # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) @@ -1016,57 +1187,63 @@ module Bundler current == proposed end - def additional_base_requirements_to_prevent_downgrades(resolution_packages, last_resolve) - return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources) - converge_specs(@originally_locked_specs - last_resolve).each do |locked_spec| - next if locked_spec.source.is_a?(Source::Path) - resolution_packages.base_requirements[locked_spec.name] = Gem::Requirement.new(">= #{locked_spec.version}") - end - resolution_packages - end + def additional_base_requirements_to_prevent_downgrades(resolution_base) + return resolution_base unless @locked_gems + @originally_locked_specs.each do |locked_spec| + next if locked_spec.source.is_a?(Source::Path) || locked_spec.source_changed? - def additional_base_requirements_to_force_updates(resolution_packages) - return resolution_packages if @explicit_unlocks.empty? - full_update = dup_for_full_unlock.resolve - @explicit_unlocks.each do |name| - version = full_update[name].first&.version - resolution_packages.base_requirements[name] = Gem::Requirement.new("= #{version}") if version + name = locked_spec.name + next if @changed_dependencies.include?(name) + + resolution_base.base_requirements[name] = Gem::Requirement.new(">= #{locked_spec.version}") end - resolution_packages + resolution_base end - def dup_for_full_unlock - unlocked_definition = self.class.new(@lockfile, @dependencies, @sources, true, @ruby_version, @optional_groups, @gemfiles) - unlocked_definition.source_requirements = source_requirements - unlocked_definition.gem_version_promoter.tap do |gvp| - gvp.level = gem_version_promoter.level - gvp.strict = gem_version_promoter.strict - gvp.pre = gem_version_promoter.pre + def additional_base_requirements_to_force_updates(resolution_base) + return resolution_base if @explicit_unlocks.empty? + full_update = SpecSet.new(new_resolver_for_full_update.start) + @explicit_unlocks.each do |name| + version = full_update.version_for(name) + resolution_base.base_requirements[name] = Gem::Requirement.new("= #{version}") if version end - unlocked_definition + resolution_base end def remove_invalid_platforms! return if Bundler.frozen_bundle? - platforms.reverse_each do |platform| - next if local_platform == platform || - @new_platforms.include?(platform) || - @path_changes || - @dependency_changes || - @locked_spec_with_invalid_deps || - !spec_set_incomplete_for_platform?(@originally_locked_specs, platform) + skips = (@new_platforms + [Bundler.local_platform]).uniq - remove_platform(platform) - end - end + # We should probably avoid removing non-ruby platforms, since that means + # lockfile will no longer install on those platforms, so a error to give + # heads up to the user may be better. However, we have tests expecting + # non ruby platform autoremoval to work, so leaving that in place for + # now. + skips |= platforms - [Gem::Platform::RUBY] if @dependency_changes - def spec_set_incomplete_for_platform?(spec_set, platform) - spec_set.incomplete_for_platform?(current_dependencies, platform) + @originally_invalid_platforms = @originally_locked_specs.remove_invalid_platforms!(current_dependencies, platforms, skips: skips) end def source_map @source_map ||= SourceMap.new(sources, dependencies, @locked_specs) end + + def new_resolver_for_full_update + new_resolver(unlocked_resolution_base) + end + + def unlocked_resolution_base + new_resolution_base(last_resolve: SpecSet.new([]), unlock: true) + end + + def new_resolution_base(last_resolve:, unlock:) + new_resolution_platforms = @current_platform_missing ? @new_platforms + [Bundler.local_platform] : @new_platforms + Resolver::Base.new(source_requirements, expanded_dependencies, last_resolve, @platforms, locked_specs: @originally_locked_specs, unlock: unlock, prerelease: gem_version_promoter.pre?, prefer_local: @prefer_local, new_platforms: new_resolution_platforms) + end + + def new_resolver(base) + Resolver.new(base, gem_version_promoter, @most_specific_locked_platform) + end end end diff --git a/lib/bundler/dependency.rb b/lib/bundler/dependency.rb index 2a4f72fe55..cb9c7a76ea 100644 --- a/lib/bundler/dependency.rb +++ b/lib/bundler/dependency.rb @@ -2,51 +2,92 @@ require "rubygems/dependency" require_relative "shared_helpers" -require_relative "rubygems_ext" module Bundler class Dependency < Gem::Dependency - attr_reader :autorequire - attr_reader :groups, :platforms, :gemfile, :path, :git, :github, :branch, :ref, :glob - - ALL_RUBY_VERSIONS = (18..27).to_a.concat((30..34).to_a).freeze - PLATFORM_MAP = { - ruby: [Gem::Platform::RUBY, ALL_RUBY_VERSIONS], - mri: [Gem::Platform::RUBY, ALL_RUBY_VERSIONS], - rbx: [Gem::Platform::RUBY], - truffleruby: [Gem::Platform::RUBY], - jruby: [Gem::Platform::JAVA, [18, 19]], - windows: [Gem::Platform::WINDOWS, ALL_RUBY_VERSIONS], - # deprecated - mswin: [Gem::Platform::MSWIN, ALL_RUBY_VERSIONS], - mswin64: [Gem::Platform::MSWIN64, ALL_RUBY_VERSIONS - [18]], - mingw: [Gem::Platform::MINGW, ALL_RUBY_VERSIONS], - x64_mingw: [Gem::Platform::X64_MINGW, ALL_RUBY_VERSIONS - [18, 19]], - }.each_with_object({}) do |(platform, spec), hash| - hash[platform] = spec[0] - spec[1]&.each {|version| hash[:"#{platform}_#{version}"] = spec[0] } - end.freeze - def initialize(name, version, options = {}, &blk) type = options["type"] || :runtime super(name, version, type) - @autorequire = nil - @groups = Array(options["group"] || :default).map(&:to_sym) - @source = options["source"] - @path = options["path"] - @git = options["git"] - @github = options["github"] - @branch = options["branch"] - @ref = options["ref"] - @glob = options["glob"] - @platforms = Array(options["platforms"]) - @env = options["env"] - @should_include = options.fetch("should_include", true) - @gemfile = options["gemfile"] - @force_ruby_platform = options["force_ruby_platform"] if options.key?("force_ruby_platform") + @options = options + end + + def groups + @groups ||= Array(@options["group"] || :default).map(&:to_sym) + end + + def source + return @source if defined?(@source) + + @source = @options["source"] + end + + def path + return @path if defined?(@path) + + @path = @options["path"] + end + + def git + return @git if defined?(@git) - @autorequire = Array(options["require"] || []) if options.key?("require") + @git = @options["git"] + end + + def github + return @github if defined?(@github) + + @github = @options["github"] + end + + def branch + return @branch if defined?(@branch) + + @branch = @options["branch"] + end + + def ref + return @ref if defined?(@ref) + + @ref = @options["ref"] + end + + def glob + return @glob if defined?(@glob) + + @glob = @options["glob"] + end + + def platforms + @platforms ||= Array(@options["platforms"]) + end + + def env + return @env if defined?(@env) + + @env = @options["env"] + end + + def should_include + @should_include ||= @options.fetch("should_include", true) + end + + def gemfile + return @gemfile if defined?(@gemfile) + + @gemfile = @options["gemfile"] + end + + def force_ruby_platform + return @force_ruby_platform if defined?(@force_ruby_platform) + + @force_ruby_platform = @options["force_ruby_platform"] + end + + def autorequire + return @autorequire if defined?(@autorequire) + + @autorequire = Array(@options["require"] || []) if @options.key?("require") end RUBY_PLATFORM_ARRAY = [Gem::Platform::RUBY].freeze @@ -56,37 +97,41 @@ module Bundler # passed in the `valid_platforms` parameter def gem_platforms(valid_platforms) return RUBY_PLATFORM_ARRAY if force_ruby_platform - return valid_platforms if @platforms.empty? + return valid_platforms if platforms.empty? - valid_platforms.select {|p| expanded_platforms.include?(GemHelpers.generic(p)) } + valid_platforms.select {|p| expanded_platforms.include?(Gem::Platform.generic(p)) } end def expanded_platforms - @expanded_platforms ||= @platforms.map {|pl| PLATFORM_MAP[pl] }.compact.flatten.uniq + @expanded_platforms ||= platforms.filter_map {|pl| CurrentRuby::PLATFORM_MAP[pl] }.flatten.uniq end def should_include? - @should_include && current_env? && current_platform? + should_include && current_env? && current_platform? end def gemspec_dev_dep? - type == :development + @gemspec_dev_dep ||= @options.fetch("gemspec_dev_dep", false) + end + + def gemfile_dep? + !gemspec_dev_dep? end def current_env? - return true unless @env - if @env.is_a?(Hash) - @env.all? do |key, val| + return true unless env + if env.is_a?(Hash) + env.all? do |key, val| ENV[key.to_s] && (val.is_a?(String) ? ENV[key.to_s] == val : ENV[key.to_s] =~ val) end else - ENV[@env.to_s] + ENV[env.to_s] end end def current_platform? - return true if @platforms.empty? - @platforms.any? do |p| + return true if platforms.empty? + platforms.any? do |p| Bundler.current_ruby.send("#{p}?") end end diff --git a/lib/bundler/deployment.rb b/lib/bundler/deployment.rb index b432ae6ae1..3344449e82 100644 --- a/lib/bundler/deployment.rb +++ b/lib/bundler/deployment.rb @@ -1,69 +1,6 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, "Bundler no longer integrates with " \ +Bundler::SharedHelpers.feature_removed! "Bundler no longer integrates with " \ "Capistrano, but Capistrano provides its own integration with " \ "Bundler via the capistrano-bundler gem. Use it instead." - -module Bundler - class Deployment - def self.define_task(context, task_method = :task, opts = {}) - if defined?(Capistrano) && context.is_a?(Capistrano::Configuration) - context_name = "capistrano" - role_default = "{:except => {:no_release => true}}" - error_type = ::Capistrano::CommandError - else - context_name = "vlad" - role_default = "[:app]" - error_type = ::Rake::CommandFailedError - end - - roles = context.fetch(:bundle_roles, false) - opts[:roles] = roles if roles - - context.send :namespace, :bundle do - send :desc, <<-DESC - Install the current Bundler environment. By default, gems will be \ - installed to the shared/bundle path. Gems in the development and \ - test group will not be installed. The install command is executed \ - with the --deployment and --quiet flags. If the bundle cmd cannot \ - be found then you can override the bundle_cmd variable to specify \ - which one it should use. The base path to the app is fetched from \ - the :latest_release variable. Set it for custom deploy layouts. - - You can override any of these defaults by setting the variables shown below. - - N.B. bundle_roles must be defined before you require 'bundler/#{context_name}' \ - in your deploy.rb file. - - set :bundle_gemfile, "Gemfile" - set :bundle_dir, File.join(fetch(:shared_path), 'bundle') - set :bundle_flags, "--deployment --quiet" - set :bundle_without, [:development, :test] - set :bundle_with, [:mysql] - set :bundle_cmd, "bundle" # e.g. "/opt/ruby/bin/bundle" - set :bundle_roles, #{role_default} # e.g. [:app, :batch] - DESC - send task_method, :install, opts do - bundle_cmd = context.fetch(:bundle_cmd, "bundle") - bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet") - bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), "bundle")) - bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile") - bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact - bundle_with = [*context.fetch(:bundle_with, [])].compact - app_path = context.fetch(:latest_release) - if app_path.to_s.empty? - raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.") - end - args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"] - args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty? - args << bundle_flags.to_s - args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty? - args << "--with #{bundle_with.join(" ")}" unless bundle_with.empty? - - run "cd #{app_path} && #{bundle_cmd} install #{args.join(" ")}" - end - end - end - end -end diff --git a/lib/bundler/digest.rb b/lib/bundler/digest.rb index 2c6d971f1b..158803033d 100644 --- a/lib/bundler/digest.rb +++ b/lib/bundler/digest.rb @@ -26,7 +26,7 @@ module Bundler end a, b, c, d, e = *words (16..79).each do |i| - w[i] = SHA1_MASK & rotate((w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]), 1) + w[i] = SHA1_MASK & rotate(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 1) end 0.upto(79) do |i| case i diff --git a/lib/bundler/dsl.rb b/lib/bundler/dsl.rb index df904f074a..6f06c4e918 100644 --- a/lib/bundler/dsl.rb +++ b/lib/bundler/dsl.rb @@ -9,14 +9,15 @@ module Bundler def self.evaluate(gemfile, lockfile, unlock) builder = new + builder.lockfile(lockfile) builder.eval_gemfile(gemfile) - builder.to_definition(lockfile, unlock) + builder.to_definition(builder.lockfile_path, unlock) end - VALID_PLATFORMS = Bundler::Dependency::PLATFORM_MAP.keys.freeze + VALID_PLATFORMS = Bundler::CurrentRuby::PLATFORM_MAP.keys.freeze VALID_KEYS = %w[group groups git path glob name branch ref tag require submodules - platform platforms type source install_if gemfile force_ruby_platform].freeze + platform platforms source install_if force_ruby_platform].freeze GITHUB_PULL_REQUEST_URL = %r{\Ahttps://github\.com/([A-Za-z0-9_\-\.]+/[A-Za-z0-9_\-\.]+)/pull/(\d+)\z} GITLAB_MERGE_REQUEST_URL = %r{\Ahttps://gitlab\.com/([A-Za-z0-9_\-\./]+)/-/merge_requests/(\d+)\z} @@ -38,6 +39,7 @@ module Bundler @gemspecs = [] @gemfile = nil @gemfiles = [] + @lockfile = nil add_git_sources end @@ -66,23 +68,23 @@ module Bundler development_group = opts[:development_group] || :development expanded_path = gemfile_root.join(path) - gemspecs = Gem::Util.glob_files_in_dir("{,*}.gemspec", expanded_path).map {|g| Bundler.load_gemspec(g) }.compact + gemspecs = Gem::Util.glob_files_in_dir("{,*}.gemspec", expanded_path).filter_map {|g| Bundler.load_gemspec(g) } gemspecs.reject! {|s| s.name != name } if name specs_by_name_and_version = gemspecs.group_by {|s| [s.name, s.version] } case specs_by_name_and_version.size when 1 specs = specs_by_name_and_version.values.first - spec = specs.find {|s| s.match_platform(Bundler.local_platform) } || specs.first + spec = specs.find {|s| s.installable_on_platform?(Bundler.local_platform) } || specs.first @gemspecs << spec - gem spec.name, name: spec.name, path: path, glob: glob + path path, "glob" => glob, "name" => spec.name, "gemspec" => spec do + add_dependency spec.name + end - group(development_group) do - spec.development_dependencies.each do |dep| - gem dep.name, *(dep.requirement.as_list + [type: :development]) - end + spec.development_dependencies.each do |dep| + add_dependency dep.name, dep.requirement.as_list, "gemspec_dev_dep" => true, "group" => development_group end when 0 raise InvalidOption, "There are no gemspecs at #{expanded_path}" @@ -94,79 +96,20 @@ module Bundler def gem(name, *args) options = args.last.is_a?(Hash) ? args.pop.dup : {} - options["gemfile"] = @gemfile version = args || [">= 0"] normalize_options(name, version, options) - dep = Dependency.new(name, version, options) - - # if there's already a dependency with this name we try to prefer one - if current = @dependencies.find {|d| d.name == dep.name } - if current.requirement != dep.requirement - current_requirement_open = current.requirements_list.include?(">= 0") - - gemspec_dep = [dep, current].find(&:gemspec_dev_dep?) - if gemspec_dep - gemfile_dep = [dep, current].find(&:runtime?) - - if gemfile_dep && !current_requirement_open - Bundler.ui.warn "A gemspec development dependency (#{gemspec_dep.name}, #{gemspec_dep.requirement}) is being overridden by a Gemfile dependency (#{gemfile_dep.name}, #{gemfile_dep.requirement}).\n" \ - "This behaviour may change in the future. Please remove either of them, or make sure they both have the same requirement\n" - elsif gemfile_dep.nil? - require_relative "vendor/pub_grub/lib/pub_grub/version_range" - require_relative "vendor/pub_grub/lib/pub_grub/version_constraint" - require_relative "vendor/pub_grub/lib/pub_grub/version_union" - require_relative "vendor/pub_grub/lib/pub_grub/rubygems" - - current_gemspec_range = PubGrub::RubyGems.requirement_to_range(current.requirement) - next_gemspec_range = PubGrub::RubyGems.requirement_to_range(dep.requirement) - - if current_gemspec_range.intersects?(next_gemspec_range) - dep = Dependency.new(name, current.requirement.as_list + dep.requirement.as_list, options) - else - raise GemfileError, "Two gemspecs have conflicting requirements on the same gem: #{dep} and #{current}" - end - end - else - update_prompt = "" - - if File.basename(@gemfile) == Injector::INJECTED_GEMS - if dep.requirements_list.include?(">= 0") && !current_requirement_open - update_prompt = ". Gem already added" - else - update_prompt = ". If you want to update the gem version, run `bundle update #{current.name}`" - - update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current_requirement_open - end - end - - raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \ - "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})" \ - "#{update_prompt}" - end - end + add_dependency(name, version, options) + end - unless current.gemspec_dev_dep? && dep.gemspec_dev_dep? - # Always prefer the dependency from the Gemfile - if current.gemspec_dev_dep? - @dependencies.delete(current) - elsif dep.gemspec_dev_dep? - return - elsif current.source != dep.source - raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ - "You specified that #{dep.name} (#{dep.requirement}) should come from " \ - "#{current.source || "an unspecified source"} and #{dep.source}\n" - else - Bundler.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \ - "You should probably keep only one of them.\n" \ - "Remove any duplicate entries and specify the gem only once.\n" \ - "While it's not a problem now, it could cause errors if you change the version of one of them later." - end - end - end + # For usage in Dsl.evaluate, since lockfile is used as part of the Gemfile. + def lockfile_path + @lockfile + end - @dependencies << dep + def lockfile(file) + @lockfile = file end def source(source, *args, &blk) @@ -209,8 +152,7 @@ module Bundler def path(path, options = {}, &blk) source_options = normalize_hash(options).merge( "path" => Pathname.new(path), - "root_path" => gemfile_root, - "gemspec" => gemspecs.find {|g| g.name == options["name"] } + "root_path" => gemfile_root ) source_options["global"] = true unless block_given? @@ -244,6 +186,7 @@ module Bundler def to_definition(lockfile, unlock) check_primary_source_safety + lockfile = @lockfile unless @lockfile.nil? Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version, @optional_groups, @gemfiles) end @@ -301,6 +244,80 @@ module Bundler private + def add_dependency(name, version = nil, options = {}) + options["gemfile"] = @gemfile + options["source"] ||= @source + options["env"] ||= @env + + dep = Dependency.new(name, version, options) + + # if there's already a dependency with this name we try to prefer one + if current = @dependencies.find {|d| d.name == name } + if current.requirement != dep.requirement + current_requirement_open = current.requirements_list.include?(">= 0") + + gemspec_dep = [dep, current].find(&:gemspec_dev_dep?) + if gemspec_dep + require_relative "vendor/pub_grub/lib/pub_grub/version_range" + require_relative "vendor/pub_grub/lib/pub_grub/version_constraint" + require_relative "vendor/pub_grub/lib/pub_grub/version_union" + require_relative "vendor/pub_grub/lib/pub_grub/rubygems" + + current_gemspec_range = PubGrub::RubyGems.requirement_to_range(current.requirement) + next_gemspec_range = PubGrub::RubyGems.requirement_to_range(dep.requirement) + + if current_gemspec_range.intersects?(next_gemspec_range) + dep = Dependency.new(name, current.requirement.as_list + dep.requirement.as_list, options) + else + gemfile_dep = [dep, current].find(&:gemfile_dep?) + + if gemfile_dep + raise GemfileError, "The #{name} dependency has conflicting requirements in Gemfile (#{gemfile_dep.requirement}) and gemspec (#{gemspec_dep.requirement})" + else + raise GemfileError, "Two gemspec development dependencies have conflicting requirements on the same gem: #{dep} and #{current}" + end + end + else + update_prompt = "" + + if File.basename(@gemfile) == Injector::INJECTED_GEMS + if dep.requirements_list.include?(">= 0") && !current_requirement_open + update_prompt = ". Gem already added" + else + update_prompt = ". If you want to update the gem version, run `bundle update #{name}`" + + update_prompt += ". You may also need to change the version requirement specified in the Gemfile if it's too restrictive." unless current_requirement_open + end + end + + raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \ + "You specified: #{name} (#{current.requirement}) and #{name} (#{dep.requirement})" \ + "#{update_prompt}" + end + end + + unless current.gemspec_dev_dep? && dep.gemspec_dev_dep? + # Always prefer the dependency from the Gemfile + if current.gemspec_dev_dep? + @dependencies.delete(current) + elsif dep.gemspec_dev_dep? + return + elsif current.source.to_s != dep.source.to_s + raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \ + "You specified that #{name} (#{dep.requirement}) should come from " \ + "#{current.source || "an unspecified source"} and #{dep.source}\n" + else + Bundler.ui.warn "Your Gemfile lists the gem #{name} (#{current.requirement}) more than once.\n" \ + "You should probably keep only one of them.\n" \ + "Remove any duplicate entries and specify the gem only once.\n" \ + "While it's not a problem now, it could cause errors if you change the version of one of them later." + end + end + end + + @dependencies << dep + end + def with_gemfile(gemfile) expanded_gemfile_path = Pathname.new(gemfile).expand_path(@gemfile&.parent) original_gemfile = @gemfile @@ -407,6 +424,13 @@ module Bundler raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}" end + windows_platforms = platforms.select {|pl| pl.to_s.match?(/mingw|mswin/) } + if windows_platforms.any? + windows_platforms = windows_platforms.map! {|pl| ":#{pl}" }.join(", ") + deprecated_message = "Platform #{windows_platforms} will be removed in the future. Please use platform :windows instead." + Bundler::SharedHelpers.feature_deprecated! deprecated_message + end + # Save sources passed in a key if opts.key?("source") source = normalize_source(opts["source"]) @@ -433,8 +457,6 @@ module Bundler opts["source"] = source end - opts["source"] ||= @source - opts["env"] ||= @env opts["platforms"] = platforms.dup opts["group"] = groups opts["should_include"] = install_if @@ -473,14 +495,10 @@ module Bundler def normalize_source(source) case source when :gemcutter, :rubygems, :rubyforge - message = - "The source :#{source} is deprecated because HTTP requests are insecure.\n" \ - "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." removed_message = "The source :#{source} is disallowed because HTTP requests are insecure.\n" \ "Please change your source to 'https://rubygems.org' if possible, or 'http://rubygems.org' if not." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - "http://rubygems.org" + Bundler::SharedHelpers.feature_removed! removed_message when String source else @@ -499,43 +517,18 @@ module Bundler " gem 'rails'\n" \ " end\n\n" - SharedHelpers.major_deprecation(2, msg.strip) + SharedHelpers.feature_removed! msg.strip end def check_rubygems_source_safety - if @sources.implicit_global_source? - implicit_global_source_warning - elsif @sources.aggregate_global_source? - multiple_global_source_warning - end - end - - def implicit_global_source_warning - Bundler::SharedHelpers.major_deprecation 2, "This Gemfile does not include an explicit global source. " \ - "Not using an explicit global source may result in a different lockfile being generated depending on " \ - "the gems you have installed locally before bundler is run. " \ - "Instead, define a global source in your Gemfile like this: source \"https://rubygems.org\"." + multiple_global_source_warning if @sources.aggregate_global_source? end def multiple_global_source_warning - if Bundler.feature_flag.bundler_3_mode? - msg = "This Gemfile contains multiple global sources. " \ - "Each source after the first must include a block to indicate which gems " \ - "should come from that source" - raise GemfileEvalError, msg - else - message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this warning, use " \ - "a block to indicate which gems should come from the secondary source." - removed_message = - "Your Gemfile contains multiple global sources. " \ - "Using `source` more than once without a block is a security risk, and " \ - "may result in installing unexpected gems. To resolve this error, use " \ - "a block to indicate which gems should come from the secondary source." - Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message - end + msg = "This Gemfile contains multiple global sources. " \ + "Each source after the first must include a block to indicate which gems " \ + "should come from that source" + raise GemfileEvalError, msg end class DSLError < GemfileError diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb index 201818cc33..c06684657d 100644 --- a/lib/bundler/endpoint_specification.rb +++ b/lib/bundler/endpoint_specification.rb @@ -6,7 +6,8 @@ module Bundler include MatchRemoteMetadata attr_reader :name, :version, :platform, :checksum - attr_accessor :source, :remote, :dependencies + attr_writer :dependencies + attr_accessor :remote, :locked_platform def initialize(name, version, platform, spec_fetcher, dependencies, metadata = nil) super() @@ -14,18 +15,29 @@ module Bundler @version = Gem::Version.create version @platform = Gem::Platform.new(platform) @spec_fetcher = spec_fetcher - @dependencies = dependencies.map {|dep, reqs| build_dependency(dep, reqs) } + @dependencies = nil + @unbuilt_dependencies = dependencies @loaded_from = nil @remote_specification = nil + @locked_platform = nil parse_metadata(metadata) end + def insecurely_materialized? + @locked_platform.to_s != @platform.to_s + end + def fetch_platform @platform end + def dependencies + @dependencies ||= @unbuilt_dependencies.map! {|dep, reqs| build_dependency(dep, reqs) } + end + alias_method :runtime_dependencies, :dependencies + # needed for standalone, load required_paths from local gemspec # after the gem is installed def require_paths @@ -115,6 +127,10 @@ module Bundler @remote_specification = spec end + def inspect + "#<#{self.class} @name=\"#{name}\" (#{full_name.delete_prefix("#{name}-")})>" + end + private def _remote_specification @@ -152,7 +168,7 @@ module Bundler end def build_dependency(name, requirements) - Gem::Dependency.new(name, requirements) + Dependency.new(name, requirements) end end end diff --git a/lib/bundler/environment_preserver.rb b/lib/bundler/environment_preserver.rb index 444ab6fd37..bf9478a299 100644 --- a/lib/bundler/environment_preserver.rb +++ b/lib/bundler/environment_preserver.rb @@ -6,6 +6,7 @@ module Bundler BUNDLER_KEYS = %w[ BUNDLE_BIN_PATH BUNDLE_GEMFILE + BUNDLE_LOCKFILE BUNDLER_VERSION BUNDLER_SETUP GEM_HOME diff --git a/lib/bundler/errors.rb b/lib/bundler/errors.rb index a0ce739ad7..d8df4d6ec5 100644 --- a/lib/bundler/errors.rb +++ b/lib/bundler/errors.rb @@ -25,6 +25,7 @@ module Bundler class GemNotFound < BundlerError; status_code(7); end class InstallHookError < BundlerError; status_code(8); end + class RemovedError < BundlerError; status_code(9); end class GemfileNotFound < BundlerError; status_code(10); end class GitError < BundlerError; status_code(11); end class DeprecatedError < BundlerError; status_code(12); end @@ -76,11 +77,6 @@ module Bundler def mismatch_resolution_instructions removable, remote = [@existing, @checksum].partition(&:removable?) case removable.size - when 0 - msg = +"Mismatched checksums each have an authoritative source:\n" - msg << " 1. #{@existing.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << " 2. #{@checksum.sources.reject(&:removable?).map(&:to_s).join(" and ")}\n" - msg << "You may need to alter your Gemfile sources to resolve this issue.\n" when 1 msg = +"If you trust #{remote.first.sources.first}, to resolve this issue you can:\n" msg << removable.first.removal_instructions @@ -135,7 +131,8 @@ module Bundler attr_reader :orig_exception def initialize(orig_exception, msg) - full_message = msg + "\nGem Load Error is: #{orig_exception.message}\n"\ + full_message = msg + "\nGem Load Error is: + #{orig_exception.full_message(highlight: false)}\n"\ "Backtrace for gem load error is:\n"\ "#{orig_exception.backtrace.join("\n")}\n"\ "Bundler Error Backtrace:\n" @@ -193,6 +190,24 @@ module Bundler status_code(31) end + class ReadOnlyFileSystemError < PermissionError + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "File system is read-only." + end + + status_code(42) + end + + class OperationNotPermittedError < PermissionError + def message + "There was an error while trying to #{action} `#{@path}`. " \ + "Underlying OS system call raised an EPERM error." + end + + status_code(43) + end + class GenericSystemCallError < BundlerError attr_reader :underlying_error @@ -207,7 +222,9 @@ module Bundler class DirectoryRemovalError < BundlerError def initialize(orig_exception, msg) full_message = "#{msg}.\n" \ - "The underlying error was #{orig_exception.class}: #{orig_exception.message}, with backtrace:\n" \ + "The underlying error was #{orig_exception.class}: + #{orig_exception.full_message(highlight: false)}, + with backtrace:\n" \ " #{orig_exception.backtrace.join("\n ")}\n\n" \ "Bundler Error Backtrace:" super(full_message) @@ -246,4 +263,18 @@ module Bundler end class InvalidArgumentError < BundlerError; status_code(40); end + + class IncorrectLockfileDependencies < BundlerError + attr_reader :spec + + def initialize(spec) + @spec = spec + end + + def message + "Bundler found incorrect dependencies in the lockfile for #{spec.full_name}" + end + + status_code(41) + end end diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index ab2189f7f0..dea8abedba 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -2,52 +2,19 @@ module Bundler class FeatureFlag - def self.settings_flag(flag, &default) - unless Bundler::Settings::BOOL_KEYS.include?(flag.to_s) - raise "Cannot use `#{flag}` as a settings feature flag since it isn't a bool key" - end + (1..10).each {|v| define_method("bundler_#{v}_mode?") { @major_version >= v } } - settings_method("#{flag}?", flag, &default) + def removed_major?(target_major_version) + @major_version > target_major_version end - private_class_method :settings_flag - def self.settings_option(key, &default) - settings_method(key, key, &default) + def deprecated_major?(target_major_version) + @major_version >= target_major_version end - private_class_method :settings_option - - def self.settings_method(name, key, &default) - define_method(name) do - value = Bundler.settings[key] - value = instance_eval(&default) if value.nil? - value - end - end - private_class_method :settings_method - - (1..10).each {|v| define_method("bundler_#{v}_mode?") { major_version >= v } } - - settings_flag(:allow_offline_install) { bundler_3_mode? } - settings_flag(:auto_clean_without_path) { bundler_3_mode? } - settings_flag(:cache_all) { bundler_3_mode? } - settings_flag(:default_install_uses_path) { bundler_3_mode? } - settings_flag(:forget_cli_options) { bundler_3_mode? } - settings_flag(:global_gem_cache) { bundler_3_mode? } - settings_flag(:path_relative_to_cwd) { bundler_3_mode? } - settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } - settings_flag(:print_only_version_number) { bundler_3_mode? } - settings_flag(:setup_makes_kernel_gem_public) { !bundler_3_mode? } - settings_flag(:update_requires_all_flag) { bundler_4_mode? } - - settings_option(:default_cli_command) { bundler_3_mode? ? :cli_help : :install } def initialize(bundler_version) @bundler_version = Gem::Version.create(bundler_version) + @major_version = @bundler_version.segments.first end - - def major_version - @bundler_version.segments.first - end - private :major_version end end diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 14721623f9..0b6ced6f39 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -2,7 +2,6 @@ require_relative "vendored_persistent" require_relative "vendored_timeout" -require "cgi" require_relative "vendored_securerandom" require "zlib" @@ -37,8 +36,9 @@ module Bundler # This is the error raised when a source is HTTPS and OpenSSL didn't load class SSLError < HTTPError def initialize(msg = nil) - super msg || "Could not load OpenSSL.\n" \ - "You must recompile Ruby with OpenSSL support." + super "Could not load OpenSSL.\n" \ + "You must recompile Ruby with OpenSSL support.\n" \ + "original error: #{msg}\n" end end @@ -72,19 +72,57 @@ module Bundler end end + HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze + deprecate_constant :HTTP_ERRORS + + NET_ERRORS = [ + :HTTPBadGateway, + :HTTPBadRequest, + :HTTPFailedDependency, + :HTTPForbidden, + :HTTPInsufficientStorage, + :HTTPMethodNotAllowed, + :HTTPMovedPermanently, + :HTTPNoContent, + :HTTPNotFound, + :HTTPNotImplemented, + :HTTPPreconditionFailed, + :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, + :HTTPUnauthorized, + :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, + :HTTPVersionNotSupported, + ].freeze + deprecate_constant :NET_ERRORS + # Exceptions classes that should bypass retry attempts. If your password didn't work the # first time, it's not going to the third time. - NET_ERRORS = [:HTTPBadGateway, :HTTPBadRequest, :HTTPFailedDependency, - :HTTPForbidden, :HTTPInsufficientStorage, :HTTPMethodNotAllowed, - :HTTPMovedPermanently, :HTTPNoContent, :HTTPNotFound, - :HTTPNotImplemented, :HTTPPreconditionFailed, :HTTPRequestEntityTooLarge, - :HTTPRequestURITooLong, :HTTPUnauthorized, :HTTPUnprocessableEntity, - :HTTPUnsupportedMediaType, :HTTPVersionNotSupported].freeze - FAIL_ERRORS = begin - fail_errors = [AuthenticationRequiredError, BadAuthenticationError, AuthenticationForbiddenError, FallbackError, SecurityError] - fail_errors << Gem::Requirement::BadRequirementError - fail_errors.concat(NET_ERRORS.map {|e| Gem::Net.const_get(e) }) - end.freeze + FAIL_ERRORS = [ + AuthenticationRequiredError, + BadAuthenticationError, + AuthenticationForbiddenError, + FallbackError, + SecurityError, + Gem::Requirement::BadRequirementError, + Gem::Net::HTTPBadGateway, + Gem::Net::HTTPBadRequest, + Gem::Net::HTTPFailedDependency, + Gem::Net::HTTPForbidden, + Gem::Net::HTTPInsufficientStorage, + Gem::Net::HTTPMethodNotAllowed, + Gem::Net::HTTPMovedPermanently, + Gem::Net::HTTPNoContent, + Gem::Net::HTTPNotFound, + Gem::Net::HTTPNotImplemented, + Gem::Net::HTTPPreconditionFailed, + Gem::Net::HTTPRequestEntityTooLarge, + Gem::Net::HTTPRequestURITooLong, + Gem::Net::HTTPUnauthorized, + Gem::Net::HTTPUnprocessableEntity, + Gem::Net::HTTPUnsupportedMediaType, + Gem::Net::HTTPVersionNotSupported, + ].freeze class << self attr_accessor :disable_endpoint, :api_timeout, :redirect_limit, :max_retries @@ -251,7 +289,13 @@ module Bundler needs_ssl = remote_uri.scheme == "https" || Bundler.settings[:ssl_verify_mode] || Bundler.settings[:ssl_client_cert] - raise SSLError if needs_ssl && !defined?(OpenSSL::SSL) + if needs_ssl + begin + require "openssl" + rescue StandardError, LoadError => e + raise SSLError.new(e.message) + end + end con = Gem::Net::HTTP::Persistent.new name: "bundler", proxy: :ENV if gem_proxy = Gem.configuration[:http_proxy] @@ -287,13 +331,6 @@ module Bundler paths.find {|path| File.file? path } end - HTTP_ERRORS = [ - Gem::Timeout::Error, EOFError, SocketError, Errno::ENETDOWN, Errno::ENETUNREACH, - Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, Errno::EAGAIN, - Gem::Net::HTTPBadResponse, Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH - ].freeze - def bundler_cert_store store = OpenSSL::X509::Store.new ssl_ca_cert = Bundler.settings[:ssl_ca_cert] || diff --git a/lib/bundler/fetcher/compact_index.rb b/lib/bundler/fetcher/compact_index.rb index 6c82d57011..52168111fe 100644 --- a/lib/bundler/fetcher/compact_index.rb +++ b/lib/bundler/fetcher/compact_index.rb @@ -110,7 +110,7 @@ module Bundler def call(path, headers) fetcher.downloader.fetch(fetcher.fetch_uri + path, headers) rescue NetworkDownError => e - raise unless Bundler.feature_flag.allow_offline_install? && headers["If-None-Match"] + raise unless headers["If-None-Match"] ui.warn "Using the cached data for the new index because of a network error: #{e}" Gem::Net::HTTPNotModified.new(nil, nil, nil) end diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index 0b807c9a4b..994b415e9c 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -1,7 +1,8 @@ # frozen_string_literal: true require_relative "base" -require "cgi" +require "cgi/escape" +require "cgi/util" unless defined?(CGI::EscapeExt) module Bundler class Fetcher diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 868b39b959..2eac6e7975 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,6 +3,28 @@ module Bundler class Fetcher class Downloader + HTTP_NON_RETRYABLE_ERRORS = [ + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, + Errno::ENETUNREACH, + Gem::Net::HTTP::Persistent::Error, + Errno::EHOSTUNREACH, + ].freeze + + HTTP_RETRYABLE_ERRORS = [ + Gem::Timeout::Error, + EOFError, + Errno::EINVAL, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Errno::EAGAIN, + Gem::Net::HTTPBadResponse, + Gem::Net::HTTPHeaderSyntaxError, + Gem::Net::ProtocolError, + Zlib::BufError, + ].freeze + attr_reader :connection attr_reader :redirect_limit @@ -67,15 +89,19 @@ module Bundler connection.request(uri, req) rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) - rescue *HTTP_ERRORS => e + rescue *HTTP_NON_RETRYABLE_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.message.to_s.include?("host down:") - raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ - "connection and try again." - else - raise HTTPError, "Network error while fetching #{filtered_uri}" \ + + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ + "connection and try again." + rescue *HTTP_RETRYABLE_ERRORS => e + Bundler.ui.trace e + + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" - end end private diff --git a/lib/bundler/fetcher/gem_remote_fetcher.rb b/lib/bundler/fetcher/gem_remote_fetcher.rb index 3fc7b68263..3c3c1826a1 100644 --- a/lib/bundler/fetcher/gem_remote_fetcher.rb +++ b/lib/bundler/fetcher/gem_remote_fetcher.rb @@ -5,6 +5,12 @@ require "rubygems/remote_fetcher" module Bundler class Fetcher class GemRemoteFetcher < Gem::RemoteFetcher + def initialize(*) + super + + @pool_size = 5 + end + def request(*args) super do |req| req.delete("User-Agent") if headers["User-Agent"] diff --git a/lib/bundler/friendly_errors.rb b/lib/bundler/friendly_errors.rb index e61ed64450..5e8eaee6bb 100644 --- a/lib/bundler/friendly_errors.rb +++ b/lib/bundler/friendly_errors.rb @@ -80,7 +80,7 @@ module Bundler First, try this link to see if there are any existing issue reports for this error: #{issues_url(e)} - If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}, and copy and paste the report template above in there. + If there aren't any reports for this error yet, please fill in the new issue form located at #{new_issue_url}. Make sure to copy and paste the full output of this command under the "What happened instead?" section. EOS end @@ -102,13 +102,14 @@ module Bundler def issues_url(exception) message = exception.message.lines.first.tr(":", " ").chomp message = message.split("-").first if exception.is_a?(Errno) - require "cgi" - "https://github.com/rubygems/rubygems/search?q=" \ + require "cgi/escape" + require "cgi/util" unless defined?(CGI::EscapeExt) + "https://github.com/ruby/rubygems/search?q=" \ "#{CGI.escape(message)}&type=Issues" end def new_issue_url - "https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" + "https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md" end end diff --git a/lib/bundler/gem_helpers.rb b/lib/bundler/gem_helpers.rb deleted file mode 100644 index 70ccceb491..0000000000 --- a/lib/bundler/gem_helpers.rb +++ /dev/null @@ -1,134 +0,0 @@ -# frozen_string_literal: true - -module Bundler - module GemHelpers - GENERIC_CACHE = { Gem::Platform::RUBY => Gem::Platform::RUBY } # rubocop:disable Style/MutableConstant - GENERICS = [ - [Gem::Platform.new("java"), Gem::Platform.new("java")], - [Gem::Platform.new("mswin32"), Gem::Platform.new("mswin32")], - [Gem::Platform.new("mswin64"), Gem::Platform.new("mswin64")], - [Gem::Platform.new("universal-mingw32"), Gem::Platform.new("universal-mingw32")], - [Gem::Platform.new("x64-mingw32"), Gem::Platform.new("x64-mingw32")], - [Gem::Platform.new("x86_64-mingw32"), Gem::Platform.new("x64-mingw32")], - [Gem::Platform.new("x64-mingw-ucrt"), Gem::Platform.new("x64-mingw-ucrt")], - [Gem::Platform.new("mingw32"), Gem::Platform.new("x86-mingw32")], - ].freeze - - def generic(p) - GENERIC_CACHE[p] ||= begin - _, found = GENERICS.find do |match, _generic| - p.os == match.os && (!match.cpu || p.cpu == match.cpu) - end - found || Gem::Platform::RUBY - end - end - module_function :generic - - def generic_local_platform - generic(local_platform) - end - module_function :generic_local_platform - - def local_platform - Bundler.local_platform - end - module_function :local_platform - - def generic_local_platform_is_ruby? - generic_local_platform == Gem::Platform::RUBY - end - module_function :generic_local_platform_is_ruby? - - def platform_specificity_match(spec_platform, user_platform) - spec_platform = Gem::Platform.new(spec_platform) - - PlatformMatch.specificity_score(spec_platform, user_platform) - end - module_function :platform_specificity_match - - def select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) - matching = if force_ruby - specs.select {|spec| spec.match_platform(Gem::Platform::RUBY) && spec.force_ruby_platform! } - else - specs.select {|spec| spec.match_platform(platform) } - end - - if prefer_locked - locked_originally = matching.select {|spec| spec.is_a?(LazySpecification) } - return locked_originally if locked_originally.any? - end - - sort_best_platform_match(matching, platform) - end - module_function :select_best_platform_match - - def select_best_local_platform_match(specs, force_ruby: false) - select_best_platform_match(specs, local_platform, force_ruby: force_ruby).map(&:materialize_for_installation).compact - end - module_function :select_best_local_platform_match - - def sort_best_platform_match(matching, platform) - exact = matching.select {|spec| spec.platform == platform } - return exact if exact.any? - - sorted_matching = matching.sort_by {|spec| platform_specificity_match(spec.platform, platform) } - exemplary_spec = sorted_matching.first - - sorted_matching.take_while {|spec| same_specificity(platform, spec, exemplary_spec) && same_deps(spec, exemplary_spec) } - end - module_function :sort_best_platform_match - - class PlatformMatch - def self.specificity_score(spec_platform, user_platform) - return -1 if spec_platform == user_platform - return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY - - os_match(spec_platform, user_platform) + - cpu_match(spec_platform, user_platform) * 10 + - platform_version_match(spec_platform, user_platform) * 100 - end - - def self.os_match(spec_platform, user_platform) - if spec_platform.os == user_platform.os - 0 - else - 1 - end - end - - def self.cpu_match(spec_platform, user_platform) - if spec_platform.cpu == user_platform.cpu - 0 - elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") - 0 - elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" - 1 - else - 2 - end - end - - def self.platform_version_match(spec_platform, user_platform) - if spec_platform.version == user_platform.version - 0 - elsif spec_platform.version.nil? - 1 - else - 2 - end - end - end - - def same_specificity(platform, spec, exemplary_spec) - platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) - end - module_function :same_specificity - - def same_deps(spec, exemplary_spec) - same_runtime_deps = spec.dependencies.sort == exemplary_spec.dependencies.sort - same_metadata_deps = spec.required_ruby_version == exemplary_spec.required_ruby_version && spec.required_rubygems_version == exemplary_spec.required_rubygems_version - same_runtime_deps && same_metadata_deps - end - module_function :same_deps - end -end diff --git a/lib/bundler/gem_version_promoter.rb b/lib/bundler/gem_version_promoter.rb index ecc65b4956..d64dbacfdb 100644 --- a/lib/bundler/gem_version_promoter.rb +++ b/lib/bundler/gem_version_promoter.rb @@ -132,8 +132,6 @@ module Bundler # Specific version moves can't always reliably be done during sorting # as not all elements are compared against each other. def post_sort(result, unlock, locked_version) - # default :major behavior in Bundler does not do this - return result if major? if unlock || locked_version.nil? result else diff --git a/lib/bundler/graph.rb b/lib/bundler/graph.rb deleted file mode 100644 index b22b17a453..0000000000 --- a/lib/bundler/graph.rb +++ /dev/null @@ -1,152 +0,0 @@ -# frozen_string_literal: true - -require "set" -module Bundler - class Graph - GRAPH_NAME = :Gemfile - - def initialize(env, output_file, show_version = false, show_requirements = false, output_format = "png", without = []) - @env = env - @output_file = output_file - @show_version = show_version - @show_requirements = show_requirements - @output_format = output_format - @without_groups = without.map(&:to_sym) - - @groups = [] - @relations = Hash.new {|h, k| h[k] = Set.new } - @node_options = {} - @edge_options = {} - - _populate_relations - end - - attr_reader :groups, :relations, :node_options, :edge_options, :output_file, :output_format - - def viz - GraphVizClient.new(self).run - end - - private - - def _populate_relations - parent_dependencies = _groups.values.to_set.flatten - loop do - break if parent_dependencies.empty? - - tmp = Set.new - parent_dependencies.each do |dependency| - child_dependencies = spec_for_dependency(dependency).runtime_dependencies.to_set - @relations[dependency.name] += child_dependencies.map(&:name).to_set - tmp += child_dependencies - - @node_options[dependency.name] = _make_label(dependency, :node) - child_dependencies.each do |c_dependency| - @edge_options["#{dependency.name}_#{c_dependency.name}"] = _make_label(c_dependency, :edge) - end - end - parent_dependencies = tmp - end - end - - def _groups - relations = Hash.new {|h, k| h[k] = Set.new } - @env.current_dependencies.each do |dependency| - dependency.groups.each do |group| - next if @without_groups.include?(group) - - relations[group.to_s].add(dependency) - @relations[group.to_s].add(dependency.name) - - @node_options[group.to_s] ||= _make_label(group, :node) - @edge_options["#{group}_#{dependency.name}"] = _make_label(dependency, :edge) - end - end - @groups = relations.keys - relations - end - - def _make_label(symbol_or_string_or_dependency, element_type) - case element_type.to_sym - when :node - if symbol_or_string_or_dependency.is_a?(Gem::Dependency) - label = symbol_or_string_or_dependency.name.dup - label << "\n#{spec_for_dependency(symbol_or_string_or_dependency).version}" if @show_version - else - label = symbol_or_string_or_dependency.to_s - end - when :edge - label = nil - if symbol_or_string_or_dependency.respond_to?(:requirements_list) && @show_requirements - tmp = symbol_or_string_or_dependency.requirements_list.join(", ") - label = tmp if tmp != ">= 0" - end - else - raise ArgumentError, "2nd argument is invalid" - end - label.nil? ? {} : { label: label } - end - - def spec_for_dependency(dependency) - @env.requested_specs.find {|s| s.name == dependency.name } - end - - class GraphVizClient - def initialize(graph_instance) - @graph_name = graph_instance.class::GRAPH_NAME - @groups = graph_instance.groups - @relations = graph_instance.relations - @node_options = graph_instance.node_options - @edge_options = graph_instance.edge_options - @output_file = graph_instance.output_file - @output_format = graph_instance.output_format - end - - def g - @g ||= ::GraphViz.digraph(@graph_name, concentrate: true, normalize: true, nodesep: 0.55) do |g| - g.edge[:weight] = 2 - g.edge[:fontname] = g.node[:fontname] = "Arial, Helvetica, SansSerif" - g.edge[:fontsize] = 12 - end - end - - def run - @groups.each do |group| - g.add_nodes( - group, { - style: "filled", - fillcolor: "#B9B9D5", - shape: "box3d", - fontsize: 16, - }.merge(@node_options[group]) - ) - end - - @relations.each do |parent, children| - children.each do |child| - if @groups.include?(parent) - g.add_nodes(child, { style: "filled", fillcolor: "#B9B9D5" }.merge(@node_options[child])) - g.add_edges(parent, child, { constraint: false }.merge(@edge_options["#{parent}_#{child}"])) - else - g.add_nodes(child, @node_options[child]) - g.add_edges(parent, child, @edge_options["#{parent}_#{child}"]) - end - end - end - - if @output_format.to_s == "debug" - $stdout.puts g.output none: String - Bundler.ui.info "debugging bundle viz..." - else - begin - g.output @output_format.to_sym => "#{@output_file}.#{@output_format}" - Bundler.ui.info "#{@output_file}.#{@output_format}" - rescue ArgumentError => e - warn "Unsupported output format. See Ruby-Graphviz/lib/graphviz/constants.rb" - raise e - end - end - end - end - end -end diff --git a/lib/bundler/index.rb b/lib/bundler/index.rb index df46facc88..9aef2dfa12 100644 --- a/lib/bundler/index.rb +++ b/lib/bundler/index.rb @@ -46,13 +46,6 @@ module Bundler true end - def search_all(name, &blk) - return enum_for(:search_all, name) unless blk - specs_by_name(name).each(&blk) - @duplicates[name]&.each(&blk) - @sources.each {|source| source.search_all(name, &blk) } - end - # Search this index's specs, and any source indexes that this index knows # about, returning all of the results. def search(query) @@ -131,6 +124,11 @@ module Bundler return unless other other.each do |spec| if existing = find_by_spec(spec) + unless dependencies_eql?(existing, spec) + Bundler.ui.warn "Local specification for #{spec.full_name} has different dependencies than the remote gem, ignoring it" + next + end + add_duplicate(existing) end add spec @@ -153,8 +151,8 @@ module Bundler end def dependencies_eql?(spec, other_spec) - deps = spec.dependencies.select {|d| d.type != :development } - other_deps = other_spec.dependencies.select {|d| d.type != :development } + deps = spec.runtime_dependencies + other_deps = other_spec.runtime_dependencies deps.sort == other_deps.sort end diff --git a/lib/bundler/injector.rb b/lib/bundler/injector.rb index c7e93c9ee0..21ff90ad13 100644 --- a/lib/bundler/injector.rb +++ b/lib/bundler/injector.rb @@ -41,7 +41,7 @@ module Bundler # resolve to see if the new deps broke anything @definition = builder.to_definition(lockfile_path, {}) - @definition.resolve_remotely! + @definition.remotely! # since nothing broke, we can add those gems to the gemfile append_to(gemfile_path, build_gem_lines(@options[:conservative_versioning])) if @deps.any? @@ -108,17 +108,17 @@ module Bundler end if d.groups != Array(:default) - group = d.groups.size == 1 ? ", :group => #{d.groups.first.inspect}" : ", :groups => #{d.groups.inspect}" + group = d.groups.size == 1 ? ", group: #{d.groups.first.inspect}" : ", groups: #{d.groups.inspect}" end - source = ", :source => \"#{d.source}\"" unless d.source.nil? - path = ", :path => \"#{d.path}\"" unless d.path.nil? - git = ", :git => \"#{d.git}\"" unless d.git.nil? - github = ", :github => \"#{d.github}\"" unless d.github.nil? - branch = ", :branch => \"#{d.branch}\"" unless d.branch.nil? - ref = ", :ref => \"#{d.ref}\"" unless d.ref.nil? - glob = ", :glob => \"#{d.glob}\"" unless d.glob.nil? - require_path = ", :require => #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil? + source = ", source: \"#{d.source}\"" unless d.source.nil? + path = ", path: \"#{d.path}\"" unless d.path.nil? + git = ", git: \"#{d.git}\"" unless d.git.nil? + github = ", github: \"#{d.github}\"" unless d.github.nil? + branch = ", branch: \"#{d.branch}\"" unless d.branch.nil? + ref = ", ref: \"#{d.ref}\"" unless d.ref.nil? + glob = ", glob: \"#{d.glob}\"" unless d.glob.nil? + require_path = ", require: #{convert_autorequire(d.autorequire)}" unless d.autorequire.nil? %(gem #{name}#{requirement}#{group}#{source}#{path}#{git}#{github}#{branch}#{ref}#{glob}#{require_path}) end.join("\n") @@ -184,7 +184,7 @@ module Bundler # @param [Array] gems Array of names of gems to be removed. # @param [Pathname] gemfile_path The Gemfile from which to remove dependencies. def remove_gems_from_gemfile(gems, gemfile_path) - patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2\)/ + patterns = /gem\s+(['"])#{Regexp.union(gems)}\1|gem\s*\((['"])#{Regexp.union(gems)}\2.*\)/ new_gemfile = [] multiline_removal = false File.readlines(gemfile_path).each do |line| diff --git a/lib/bundler/inline.rb b/lib/bundler/inline.rb index f2f5b22cd3..c861bee149 100644 --- a/lib/bundler/inline.rb +++ b/lib/bundler/inline.rb @@ -44,14 +44,16 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) raise ArgumentError, "Unknown options: #{opts.keys.join(", ")}" unless opts.empty? old_gemfile = ENV["BUNDLE_GEMFILE"] + old_lockfile = ENV["BUNDLE_LOCKFILE"] Bundler.unbundle_env! begin Bundler.instance_variable_set(:@bundle_path, Pathname.new(Gem.dir)) Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", "Gemfile" + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", "Gemfile.lock" - Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins? + Bundler::Plugin.gemfile_install(&gemfile) if Bundler.settings[:plugins] builder = Bundler::Dsl.new builder.instance_eval(&gemfile) @@ -94,5 +96,11 @@ def gemfile(force_latest_compatible = false, options = {}, &gemfile) else ENV["BUNDLE_GEMFILE"] = "" end + + if old_lockfile + ENV["BUNDLE_LOCKFILE"] = old_lockfile + else + ENV["BUNDLE_LOCKFILE"] = "" + end end end diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb index b65546a10a..c5fd75431f 100644 --- a/lib/bundler/installer.rb +++ b/lib/bundler/installer.rb @@ -7,12 +7,6 @@ require_relative "installer/gem_installer" module Bundler class Installer - class << self - attr_accessor :ambiguous_gems - - Installer.ambiguous_gems = [] - end - attr_reader :post_install_messages, :definition # Begins the installation process for Bundler. @@ -77,12 +71,9 @@ module Bundler return end - if resolve_if_needed(options) + if @definition.setup_domain!(options) ensure_specs_are_compatible! - Bundler.load_plugins(@definition) - options.delete(:jobs) - else - options[:jobs] = 1 # to avoid the overhead of Bundler::Worker + load_plugins end install(options) @@ -94,6 +85,11 @@ module Bundler end def generate_bundler_executable_stubs(spec, options = {}) + if spec.name == "bundler" + Bundler.ui.warn "Bundler itself does not use binstubs because its version is selected by RubyGems" + return + end + if options[:binstubs_cmd] && spec.executables.empty? options = {} spec.runtime_dependencies.each do |dep| @@ -118,10 +114,6 @@ module Bundler ruby_command = Thor::Util.ruby_command ruby_command = ruby_command template_path = File.expand_path("templates/Executable", __dir__) - if spec.name == "bundler" - template_path += ".bundler" - spec.executables = %(bundle) - end template = File.read(template_path) exists = [] @@ -196,19 +188,15 @@ module Bundler def install(options) standalone = options[:standalone] force = options[:force] - local = options[:local] - jobs = installation_parallelization(options) + local = options[:local] || options[:"prefer-local"] + jobs = installation_parallelization spec_installations = ParallelInstaller.call(self, @definition.specs, jobs, standalone, force, local: local) spec_installations.each do |installation| post_install_messages[installation.name] = installation.post_install_message if installation.has_post_install_message? end end - def installation_parallelization(options) - if jobs = options.delete(:jobs) - return jobs - end - + def installation_parallelization if jobs = Bundler.settings[:jobs] return jobs end @@ -216,6 +204,20 @@ module Bundler Bundler.settings.processor_count end + def load_plugins + Gem.load_plugins + + requested_path_gems = @definition.specs.select {|s| s.source.is_a?(Source::Path) } + path_plugin_files = requested_path_gems.flat_map do |spec| + spec.matches_for_glob("rubygems_plugin#{Bundler.rubygems.suffix_pattern}") + rescue TypeError + error_message = "#{spec.name} #{spec.version} has an invalid gemspec" + raise Gem::InvalidSpecificationException, error_message + end + Gem.load_plugin_files(path_plugin_files) + Gem.load_env_plugins + end + def ensure_specs_are_compatible! @definition.specs.each do |spec| unless spec.matches_current_ruby? @@ -229,19 +231,6 @@ module Bundler end end - # returns whether or not a re-resolve was needed - def resolve_if_needed(options) - @definition.prefer_local! if options[:"prefer-local"] - - if options[:local] || (@definition.no_resolve_needed? && !@definition.missing_specs?) - @definition.resolve_with_cache! - false - else - @definition.resolve_remotely! - true - end - end - def lock @definition.lock end diff --git a/lib/bundler/installer/gem_installer.rb b/lib/bundler/installer/gem_installer.rb index 1da91857bd..5c4fa78253 100644 --- a/lib/bundler/installer/gem_installer.rb +++ b/lib/bundler/installer/gem_installer.rb @@ -16,7 +16,6 @@ module Bundler def install_from_spec post_install_message = install Bundler.ui.debug "#{worker}: #{spec.name} (#{spec.version}) from #{spec.loaded_from}" - generate_executable_stubs [true, post_install_message] rescue Bundler::InstallHookError, Bundler::SecurityError, Bundler::APIResponseMismatchError, Bundler::InsecureInstallPathError raise @@ -71,15 +70,5 @@ module Bundler def out_of_space_message "#{install_error_message}\nYour disk is out of space. Free some space to be able to install your bundle." end - - def generate_executable_stubs - return if Bundler.feature_flag.forget_cli_options? - return if Bundler.settings[:inline] - if Bundler.settings[:bin] && standalone - installer.generate_standalone_bundler_executable_stubs(spec) - elsif Bundler.settings[:bin] - installer.generate_bundler_executable_stubs(spec, force: true) - end - end end end diff --git a/lib/bundler/installer/standalone.rb b/lib/bundler/installer/standalone.rb index cf5993448c..8b4de64df5 100644 --- a/lib/bundler/installer/standalone.rb +++ b/lib/bundler/installer/standalone.rb @@ -28,7 +28,7 @@ module Bundler private def paths - @specs.map do |spec| + @specs.flat_map do |spec| next if spec.name == "bundler" Array(spec.require_paths).map do |path| gem_path(path, spec). @@ -36,7 +36,7 @@ module Bundler sub(extensions_dir, 'extensions/\k<platform>/#{Gem.extension_api_version}') # This is a static string intentionally. It's interpolated at a later time. end - end.flatten.compact + end.compact end def version_dir diff --git a/lib/bundler/lazy_specification.rb b/lib/bundler/lazy_specification.rb index 74f7da188f..786dbcae65 100644 --- a/lib/bundler/lazy_specification.rb +++ b/lib/bundler/lazy_specification.rb @@ -8,20 +8,32 @@ module Bundler include MatchPlatform include ForcePlatform - attr_reader :name, :version, :platform + attr_reader :name, :version, :platform, :materialization attr_accessor :source, :remote, :force_ruby_platform, :dependencies, :required_ruby_version, :required_rubygems_version + # + # For backwards compatibility with existing lockfiles, if the most specific + # locked platform is not a specific platform like x86_64-linux or + # universal-java-11, then we keep the previous behaviour of resolving the + # best platform variant at materiliazation time. For previous bundler + # versions (before 2.2.0) this was always the case (except when the lockfile + # only included non-ruby platforms), but we're also keeping this behaviour + # on newer bundlers unless users generate the lockfile from scratch or + # explicitly add a more specific platform. + # + attr_accessor :most_specific_locked_platform + alias_method :runtime_dependencies, :dependencies def self.from_spec(s) lazy_spec = new(s.name, s.version, s.platform, s.source) - lazy_spec.dependencies = s.dependencies + lazy_spec.dependencies = s.runtime_dependencies lazy_spec.required_ruby_version = s.required_ruby_version lazy_spec.required_rubygems_version = s.required_rubygems_version lazy_spec end - def initialize(name, version, platform, source = nil) + def initialize(name, version, platform, source = nil, **materialization_options) @name = name @version = version @dependencies = [] @@ -31,8 +43,19 @@ module Bundler @original_source = source @source = source + @materialization_options = materialization_options @force_ruby_platform = default_force_ruby_platform + @most_specific_locked_platform = nil + @materialization = nil + end + + def missing? + @materialization == self + end + + def incomplete? + @materialization.nil? end def source_changed? @@ -99,47 +122,37 @@ module Bundler out end - def materialize_for_installation - source.local! + def materialize_for_cache + source.remote! - matching_specs = source.specs.search(use_exact_resolved_specifications? ? self : [name, version]) - return self if matching_specs.empty? + materialize(self, &:first) + end - candidates = if use_exact_resolved_specifications? - matching_specs - else - target_platform = ruby_platform_materializes_to_ruby_platform? ? platform : local_platform + def materialized_for_installation + @materialization = materialize_for_installation + + self unless incomplete? + end - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, target_platform) + def materialize_for_installation + source.local! - specification = __materialize__(installable_candidates, fallback_to_non_installable: false) - return specification unless specification.nil? + if use_exact_resolved_specifications? + spec = materialize(self) {|specs| choose_compatible(specs, fallback_to_non_installable: false) } + return spec if spec - if target_platform != platform - installable_candidates = GemHelpers.select_best_platform_match(matching_specs, platform) + # Exact spec is incompatible; in frozen mode, try to find a compatible platform variant + # In non-frozen mode, return nil to trigger re-resolution and lockfile update + if Bundler.frozen_bundle? + materialize([name, version]) {|specs| resolve_best_platform(specs) } end - - installable_candidates + else + materialize([name, version]) {|specs| resolve_best_platform(specs) } end - - __materialize__(candidates) end - # If in frozen mode, we fallback to a non-installable candidate because by - # doing this we avoid re-resolving and potentially end up changing the - # lock file, which is not allowed. In that case, we will give a proper error - # about the mismatch higher up the stack, right before trying to install the - # bad gem. - def __materialize__(candidates, fallback_to_non_installable: Bundler.frozen_bundle?) - search = candidates.reverse.find do |spec| - spec.is_a?(StubSpecification) || spec.matches_current_metadata? - end - if search.nil? && fallback_to_non_installable - search = candidates.last - else - search.dependencies = dependencies if search && search.full_name == full_name && (search.is_a?(RemoteSpecification) || search.is_a?(EndpointSpecification)) - end - search + def inspect + "#<#{self.class} @name=\"#{name}\" (#{full_name.delete_prefix("#{name}-")})>" end def to_s @@ -155,26 +168,103 @@ module Bundler @force_ruby_platform = true end + def replace_source_with!(gemfile_source) + return unless gemfile_source.can_lock?(self) + + @source = gemfile_source + + true + end + private def use_exact_resolved_specifications? - @use_exact_resolved_specifications ||= !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? + !source.is_a?(Source::Path) && ruby_platform_materializes_to_ruby_platform? + end + + # Try platforms in order of preference until finding a compatible spec. + # Used for legacy lockfiles and as a fallback when the exact locked spec + # is incompatible. Falls back to frozen bundle behavior if none match. + def resolve_best_platform(specs) + find_compatible_platform_spec(specs) || frozen_bundle_fallback(specs) + end + + def find_compatible_platform_spec(specs) + candidate_platforms.each do |plat| + candidates = MatchPlatform.select_best_platform_match(specs, plat) + spec = choose_compatible(candidates, fallback_to_non_installable: false) + return spec if spec + end + nil + end + + # Platforms to try in order of preference. Ruby platform is last since it + # requires compilation, but works when precompiled gems are incompatible. + def candidate_platforms + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + [target, platform, Gem::Platform::RUBY].uniq + end + + # In frozen mode, accept any candidate. Will error at install time. + # When target differs from locked platform, prefer locked platform's candidates + # to preserve lockfile integrity. + def frozen_bundle_fallback(specs) + target = source.is_a?(Source::Path) ? platform : Bundler.local_platform + fallback_platform = target == platform ? target : platform + candidates = MatchPlatform.select_best_platform_match(specs, fallback_platform) + choose_compatible(candidates) end - # - # For backwards compatibility with existing lockfiles, if the most specific - # locked platform is not a specific platform like x86_64-linux or - # universal-java-11, then we keep the previous behaviour of resolving the - # best platform variant at materiliazation time. For previous bundler - # versions (before 2.2.0) this was always the case (except when the lockfile - # only included non-ruby platforms), but we're also keeping this behaviour - # on newer bundlers unless users generate the lockfile from scratch or - # explicitly add a more specific platform. - # def ruby_platform_materializes_to_ruby_platform? - generic_platform = generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY + generic_platform = Bundler.generic_local_platform == Gem::Platform::JAVA ? Gem::Platform::JAVA : Gem::Platform::RUBY + + (most_specific_locked_platform != generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] + end + + def materialize(query) + matching_specs = source.specs.search(query) + return self if matching_specs.empty? - !Bundler.most_specific_locked_platform?(generic_platform) || force_ruby_platform || Bundler.settings[:force_ruby_platform] + yield matching_specs + end + + # If in frozen mode, we fallback to a non-installable candidate because by + # doing this we avoid re-resolving and potentially end up changing the + # lockfile, which is not allowed. In that case, we will give a proper error + # about the mismatch higher up the stack, right before trying to install the + # bad gem. + def choose_compatible(candidates, fallback_to_non_installable: Bundler.frozen_bundle?) + search = candidates.reverse.find do |spec| + spec.is_a?(StubSpecification) || spec.matches_current_metadata? + end + if search.nil? && fallback_to_non_installable + search = candidates.last + end + + if search + validate_dependencies(search) if search.platform == platform + + search.locked_platform = platform if search.instance_of?(RemoteSpecification) || search.instance_of?(EndpointSpecification) + end + search + end + + # Validate dependencies of this locked spec are consistent with dependencies + # of the actual spec that was materialized. + # + # Note that unless we are in strict mode (which we set during installation) + # we don't validate dependencies of locally installed gems but + # accept what's in the lockfile instead for performance, since loading + # dependencies of locally installed gems would mean evaluating all gemspecs, + # which would affect `bundler/setup` performance. + def validate_dependencies(spec) + if !@materialization_options[:strict] && spec.is_a?(StubSpecification) + spec.dependencies = dependencies + else + if !source.is_a?(Source::Path) && spec.runtime_dependencies.sort != dependencies.sort + raise IncorrectLockfileDependencies.new(self) + end + end end end end diff --git a/lib/bundler/lockfile_generator.rb b/lib/bundler/lockfile_generator.rb index 904552fa0c..6b6cf9d9ea 100644 --- a/lib/bundler/lockfile_generator.rb +++ b/lib/bundler/lockfile_generator.rb @@ -95,7 +95,7 @@ module Bundler out << " #{key}: #{val}\n" end when String - out << " #{value}\n" + out << " #{value}\n" else raise ArgumentError, "#{value.inspect} can't be serialized in a lockfile" end diff --git a/lib/bundler/lockfile_parser.rb b/lib/bundler/lockfile_parser.rb index 8a15e356c4..ac0ce1ef3d 100644 --- a/lib/bundler/lockfile_parser.rb +++ b/lib/bundler/lockfile_parser.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require_relative "shared_helpers" + module Bundler class LockfileParser class Position @@ -29,6 +31,7 @@ module Bundler :dependencies, :specs, :platforms, + :most_specific_locked_platform, :bundler_version, :ruby_version, :checksums, @@ -91,7 +94,7 @@ module Bundler lockfile_contents.split(BUNDLED).last.strip end - def initialize(lockfile) + def initialize(lockfile, strict: false) @platforms = [] @sources = [] @dependencies = {} @@ -103,6 +106,7 @@ module Bundler "Gemfile.lock" end @pos = Position.new(1, 1) + @strict = strict if lockfile.match?(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/) raise LockfileError, "Your #{@lockfile_path} contains merge conflicts.\n" \ @@ -136,7 +140,17 @@ module Bundler end @pos.advance!(line) end - @specs = @specs.values.sort_by!(&:full_name) + + if @platforms.include?(Gem::Platform::X64_MINGW_LEGACY) + SharedHelpers.feature_deprecated!("Found x64-mingw32 in lockfile, which is deprecated and will be removed in the future.") + end + + @most_specific_locked_platform = @platforms.min_by do |bundle_platform| + Gem::Platform.platform_specificity_match(bundle_platform, Bundler.local_platform) + end + @specs = @specs.values.sort_by!(&:full_name).each do |spec| + spec.most_specific_locked_platform = @most_specific_locked_platform + end rescue ArgumentError => e Bundler.ui.debug(e) raise LockfileError, "Your lockfile is unreadable. Run `rm #{@lockfile_path}` " \ @@ -231,7 +245,6 @@ module Bundler spaces = $1 return unless spaces.size == 2 checksums = $6 - return unless checksums name = $2 version = $3 platform = $4 @@ -241,10 +254,14 @@ module Bundler full_name = Gem::NameTuple.new(name, version, platform).full_name return unless spec = @specs[full_name] - checksums.split(",") do |lock_checksum| - column = line.index(lock_checksum) + 1 - checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}") - spec.source.checksum_store.register(spec, checksum) + if checksums + checksums.split(",") do |lock_checksum| + column = line.index(lock_checksum) + 1 + checksum = Checksum.from_lock(lock_checksum, "#{@lockfile_path}:#{@pos.line}:#{column}") + spec.source.checksum_store.register(spec, checksum) + end + else + spec.source.checksum_store.register(spec, nil) end end @@ -260,7 +277,7 @@ module Bundler version = Gem::Version.new(version) platform = platform ? Gem::Platform.new(platform) : Gem::Platform::RUBY - @current_spec = LazySpecification.new(name, version, platform, @current_source) + @current_spec = LazySpecification.new(name, version, platform, @current_source, strict: @strict) @current_source.add_dependency_names(name) @specs[@current_spec.full_name] = @current_spec diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index e477f77c5c..4474969db6 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-ADD" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-ADD" "1" "September 2025" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" @@ -9,33 +9,36 @@ Adds the named gem to the [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\. .SH "OPTIONS" .TP -\fB\-\-version\fR, \fB\-v\fR +\fB\-\-version=VERSION\fR, \fB\-v=VERSION\fR Specify version requirements(s) for the added gem\. .TP -\fB\-\-group\fR, \fB\-g\fR +\fB\-\-group=GROUP\fR, \fB\-g=GROUP\fR Specify the group(s) for the added gem\. Multiple groups should be separated by commas\. .TP -\fB\-\-source\fR, \fB\-s\fR +\fB\-\-source=SOURCE\fR, \fB\-s=SOURCE\fR Specify the source for the added gem\. .TP -\fB\-\-require\fR, \fB\-r\fR +\fB\-\-require=REQUIRE\fR, \fB\-r=REQUIRE\fR Adds require path to gem\. Provide false, or a path as a string\. .TP -\fB\-\-path\fR +\fB\-\-path=PATH\fR Specify the file system path for the added gem\. .TP -\fB\-\-git\fR +\fB\-\-git=GIT\fR Specify the git source for the added gem\. .TP -\fB\-\-github\fR +\fB\-\-github=GITHUB\fR Specify the github source for the added gem\. .TP -\fB\-\-branch\fR +\fB\-\-branch=BRANCH\fR Specify the git branch for the added gem\. .TP -\fB\-\-ref\fR +\fB\-\-ref=REF\fR Specify the git ref for the added gem\. .TP +\fB\-\-glob=GLOB\fR +Specify the location of a dependency's \.gemspec, expanded within Ruby (single quotes recommended)\. +.TP \fB\-\-quiet\fR Do not print progress information to the standard output\. .TP diff --git a/lib/bundler/man/bundle-add.1.ronn b/lib/bundler/man/bundle-add.1.ronn index ca09889200..48c0c66b09 100644 --- a/lib/bundler/man/bundle-add.1.ronn +++ b/lib/bundler/man/bundle-add.1.ronn @@ -14,33 +14,36 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`. ## OPTIONS -* `--version`, `-v`: +* `--version=VERSION`, `-v=VERSION`: Specify version requirements(s) for the added gem. -* `--group`, `-g`: +* `--group=GROUP`, `-g=GROUP`: Specify the group(s) for the added gem. Multiple groups should be separated by commas. -* `--source`, `-s`: +* `--source=SOURCE`, `-s=SOURCE`: Specify the source for the added gem. -* `--require`, `-r`: +* `--require=REQUIRE`, `-r=REQUIRE`: Adds require path to gem. Provide false, or a path as a string. -* `--path`: +* `--path=PATH`: Specify the file system path for the added gem. -* `--git`: +* `--git=GIT`: Specify the git source for the added gem. -* `--github`: +* `--github=GITHUB`: Specify the github source for the added gem. -* `--branch`: +* `--branch=BRANCH`: Specify the git branch for the added gem. -* `--ref`: +* `--ref=REF`: Specify the git ref for the added gem. +* `--glob=GLOB`: + Specify the location of a dependency's .gemspec, expanded within Ruby (single quotes recommended). + * `--quiet`: Do not print progress information to the standard output. diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 9b4b10d591..b8c153696b 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,30 +1,30 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-BINSTUBS" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-BINSTUBS" "1" "September 2025" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" -\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone] +\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-standalone] [\-\-all\-platforms] .SH "DESCRIPTION" Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\. .P For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\. .P -This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\. +This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the directory specified by \fBbin\fR setting if it has been configured\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\. .SH "OPTIONS" .TP \fB\-\-force\fR Overwrite existing binstubs if they exist\. .TP -\fB\-\-path\fR -The location to install the specified binstubs to\. This defaults to \fBbin\fR\. -.TP \fB\-\-standalone\fR Makes binstubs that can work without depending on Rubygems or Bundler at runtime\. .TP -\fB\-\-shebang\fR +\fB\-\-shebang=SHEBANG\fR Specify a different shebang executable name than the default (default 'ruby') .TP \fB\-\-all\fR Create binstubs for all gems in the bundle\. +.TP +\fB\-\-all\-platforms\fR +Install binstubs for all platforms\. diff --git a/lib/bundler/man/bundle-binstubs.1.ronn b/lib/bundler/man/bundle-binstubs.1.ronn index a96186929f..cbe5983f4d 100644 --- a/lib/bundler/man/bundle-binstubs.1.ronn +++ b/lib/bundler/man/bundle-binstubs.1.ronn @@ -3,7 +3,7 @@ bundle-binstubs(1) -- Install the binstubs of the listed gems ## SYNOPSIS -`bundle binstubs` <GEM_NAME> [--force] [--path PATH] [--standalone] +`bundle binstubs` <GEM_NAME> [--force] [--standalone] [--all-platforms] ## DESCRIPTION @@ -19,23 +19,24 @@ the file `bin/rspec`. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec. This command generates binstubs for executables in `GEM_NAME`. -Binstubs are put into `bin`, or the `--path` directory if one has been set. -Calling binstubs with [GEM [GEM]] will create binstubs for all given gems. +Binstubs are put into `bin`, or the directory specified by `bin` setting if it +has been configured. Calling binstubs with [GEM [GEM]] will create binstubs for +all given gems. ## OPTIONS * `--force`: Overwrite existing binstubs if they exist. -* `--path`: - The location to install the specified binstubs to. This defaults to `bin`. - * `--standalone`: Makes binstubs that can work without depending on Rubygems or Bundler at runtime. -* `--shebang`: +* `--shebang=SHEBANG`: Specify a different shebang executable name than the default (default 'ruby') * `--all`: Create binstubs for all gems in the bundle. + +* `--all-platforms`: + Install binstubs for all platforms. diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index ad26879bb9..c1dafbf070 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,16 +1,32 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CACHE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-CACHE" "1" "September 2025" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" -\fBbundle cache\fR +\fBbundle cache\fR [\fIOPTIONS\fR] .P alias: \fBpackage\fR, \fBpack\fR .SH "DESCRIPTION" Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\. +.SH "OPTIONS" +.TP +\fB\-\-all\-platforms\fR +Include gems for all platforms present in the lockfile, not only the current one\. +.TP +\fB\-\-cache\-path=CACHE\-PATH\fR +Specify a different cache path than the default (vendor/cache)\. +.TP +\fB\-\-gemfile=GEMFILE\fR +Use the specified gemfile instead of Gemfile\. +.TP +\fB\-\-no\-install\fR +Don't install the gems, only update the cache\. +.TP +\fB\-\-quiet\fR +Only output warnings and errors\. .SH "GIT AND PATH GEMS" -The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\. +The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This can be disabled setting \fBcache_all\fR to false\. .SH "SUPPORT FOR MULTIPLE PLATFORMS" When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\. .SH "REMOTE FETCHING" diff --git a/lib/bundler/man/bundle-cache.1.ronn b/lib/bundler/man/bundle-cache.1.ronn index 8112c2c551..51846c96b4 100644 --- a/lib/bundler/man/bundle-cache.1.ronn +++ b/lib/bundler/man/bundle-cache.1.ronn @@ -1,9 +1,9 @@ bundle-cache(1) -- Package your needed `.gem` files into your application -=========================================================================== +========================================================================= ## SYNOPSIS -`bundle cache` +`bundle cache` [*OPTIONS*] alias: `package`, `pack` @@ -13,11 +13,27 @@ Copy all of the `.gem` files needed to run the application into the `vendor/cache` directory. In the future, when running [`bundle install(1)`](bundle-install.1.html), use the gems in the cache in preference to the ones on `rubygems.org`. +## OPTIONS + +* `--all-platforms`: + Include gems for all platforms present in the lockfile, not only the current one. + +* `--cache-path=CACHE-PATH`: + Specify a different cache path than the default (vendor/cache). + +* `--gemfile=GEMFILE`: + Use the specified gemfile instead of Gemfile. + +* `--no-install`: + Don't install the gems, only update the cache. + +* `--quiet`: + Only output warnings and errors. + ## GIT AND PATH GEMS The `bundle cache` command can also package `:git` and `:path` dependencies -besides .gem files. This needs to be explicitly enabled via the `--all` option. -Once used, the `--all` option will be remembered. +besides .gem files. This can be disabled setting `cache_all` to false. ## SUPPORT FOR MULTIPLE PLATFORMS diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index be7b0abfcd..f83af1eb55 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,10 +1,10 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CHECK" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-CHECK" "1" "September 2025" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" -\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH] +\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] .SH "DESCRIPTION" \fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\. .P @@ -16,9 +16,6 @@ If the lockfile needs to be updated then it will be resolved using the gems inst \fB\-\-dry\-run\fR Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\. .TP -\fB\-\-gemfile\fR +\fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\. -.TP -\fB\-\-path\fR -Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\. diff --git a/lib/bundler/man/bundle-check.1.ronn b/lib/bundler/man/bundle-check.1.ronn index eb3ff1daf9..92589159c9 100644 --- a/lib/bundler/man/bundle-check.1.ronn +++ b/lib/bundler/man/bundle-check.1.ronn @@ -5,7 +5,6 @@ bundle-check(1) -- Verifies if dependencies are satisfied by installed gems `bundle check` [--dry-run] [--gemfile=FILE] - [--path=PATH] ## DESCRIPTION @@ -22,8 +21,6 @@ installed on the local machine, if they satisfy the requirements. * `--dry-run`: Locks the [`Gemfile(5)`][Gemfile(5)] before running the command. -* `--gemfile`: + +* `--gemfile=GEMFILE`: Use the specified gemfile instead of the [`Gemfile(5)`][Gemfile(5)]. -* `--path`: - Specify a different path than the system default (`$BUNDLE_PATH` or `$GEM_HOME`). - Bundler will remember this value for future installs on this machine. diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 43029c780e..c4d148c5df 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CLEAN" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-CLEAN" "1" "September 2025" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index b1cf8d7161..05c13e2d0f 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,16 +1,16 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONFIG" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-CONFIG" "1" "September 2025" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" -\fBbundle config\fR list +\fBbundle config\fR [list] .br -\fBbundle config\fR [get] NAME +\fBbundle config\fR [get [\-\-local|\-\-global]] NAME .br -\fBbundle config\fR [set] NAME VALUE +\fBbundle config\fR [set [\-\-local|\-\-global]] NAME VALUE .br -\fBbundle config\fR unset NAME +\fBbundle config\fR unset [\-\-local|\-\-global] NAME .SH "DESCRIPTION" This command allows you to interact with Bundler's configuration system\. .P @@ -25,47 +25,203 @@ Global config (\fB~/\.bundle/config\fR) Bundler default config .IP "" 0 .P +Executing \fBbundle\fR with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. +.SH "SUB\-COMMANDS" +.SS "list (default command)" Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\. +.SS "get" +Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and all locations where it was set\. .P -Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and where it was set\. -.P -Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. See \fB\-\-local\fR and \fB\-\-global\fR options below\. +\fBOPTIONS\fR +.TP +\fB\-\-local\fR +Get configuration from configuration file for the local application, namely, \fB<project_root>/\.bundle/config\fR, or \fB$BUNDLE_APP_CONFIG/config\fR if \fBBUNDLE_APP_CONFIG\fR is set\. +.TP +\fB\-\-global\fR +Get configuration from configuration file global to all bundles executed as the current user, namely, from \fB~/\.bundle/config\fR\. +.SS "set" +Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. .P +\fBOPTIONS\fR +.TP +\fB\-\-local\fR Executing \fBbundle config set \-\-local <name> <value>\fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB<project_root>/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\. -.P +.TP +\fB\-\-global\fR Executing \fBbundle config set \-\-global <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\. -.P +.SS "unset" Executing \fBbundle config unset <name>\fR will delete the configuration in both local and global sources\. .P -Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\. -.P +\fBOPTIONS\fR +.TP +\fB\-\-local\fR Executing \fBbundle config unset \-\-local <name>\fR will delete the configuration only from the local application\. +.TP +\fB\-\-global\fR +Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\. +.SH "CONFIGURATION KEYS" +Configuration keys in bundler have two forms: the canonical form and the environment variable form\. .P -Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\. -.SH "REMEMBERING OPTIONS" -Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application's configuration (normally, \fB\./\.bundle/config\fR)\. +For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. .P -However, this will be changed in bundler 3, so it's better not to rely on this behavior\. If these options must be remembered, it's better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\. +The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. .P -The options that can be configured are: +Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. +.SH "LIST OF AVAILABLE KEYS" +The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. +.TP +\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR) +Automatically run \fBbundle install\fR when gems are missing\. +.TP +\fBbin\fR (\fBBUNDLE_BIN\fR) +If configured, \fBbundle binstubs\fR will install executables from gems in the bundle to the specified directory\. Otherwise it will create them in a \fBbin\fR directory relative to the Gemfile directory\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, \fBbundle binstubs\fR will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +.TP +\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR) +Cache all gems, including path and git gems\. This needs to be explicitly before bundler 4, but will be the default on bundler 4\. +.TP +\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR) +Cache gems for all platforms\. +.TP +\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR) +The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. +.TP +\fBclean\fR (\fBBUNDLE_CLEAN\fR) +Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. Defaults to \fBtrue\fR in Bundler 4, as long as \fBpath\fR is not explicitly configured\. +.TP +\fBconsole\fR (\fBBUNDLE_CONSOLE\fR) +The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. +.TP +\fBdefault_cli_command\fR (\fBBUNDLE_DEFAULT_CLI_COMMAND\fR) +The command that running \fBbundle\fR without arguments should run\. Defaults to \fBcli_help\fR since Bundler 4, but can also be \fBinstall\fR which was the previous default\. +.TP +\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR) +Equivalent to setting \fBfrozen\fR to \fBtrue\fR and \fBpath\fR to \fBvendor/bundle\fR\. +.TP +\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR) +Allow installing gems even if they do not match the checksum provided by RubyGems\. +.TP +\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR) +Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. +.TP +\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR) +Allow Bundler to use a local git override without a branch specified in the Gemfile\. +.TP +\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR) +Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. +.TP +\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR) +Stop Bundler from accessing gems installed to RubyGems' normal location\. +.TP +\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR) +Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. +.TP +\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR) +Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. +.TP +\fBfrozen\fR (\fBBUNDLE_FROZEN\fR) +Disallow any automatic changes to \fBGemfile\.lock\fR\. Bundler commands will be blocked unless the lockfile can be installed exactly as written\. Usually this will happen when changing the \fBGemfile\fR manually and forgetting to update the lockfile through \fBbundle lock\fR or \fBbundle install\fR\. +.TP +\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR) +Sets a GitHub username or organization to be used in the \fBREADME\fR and \fB\.gemspec\fR files when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. +.TP +\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR) +Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. +.TP +\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR) +The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. +.TP +\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR) +Whether Bundler should cache all gems and compiled extensions globally, rather than locally to the configured installation path\. +.TP +\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR) +When set, no funding requests will be printed\. +.TP +\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR) +When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. +.TP +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) +Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. +.TP +\fBjobs\fR (\fBBUNDLE_JOBS\fR) +The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. +.TP +\fBlockfile\fR (\fBBUNDLE_LOCKFILE\fR) +The path to the lockfile that bundler should use\. By default, Bundler adds \fB\.lock\fR to the end of the \fBgemfile\fR entry\. Can be set to \fBfalse\fR in the Gemfile to disable lockfile creation entirely (see gemfile(5))\. +.TP +\fBlockfile_checksums\fR (\fBBUNDLE_LOCKFILE_CHECKSUMS\fR) +Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources\. Defaults to true\. +.TP +\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR) +Whether \fBbundle package\fR should skip installing gems\. +.TP +\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR) +Whether Bundler should leave outdated gems unpruned when caching\. +.TP +\fBonly\fR (\fBBUNDLE_ONLY\fR) +A space\-separated list of groups to install only gems of the specified groups\. Please check carefully if you want to install also gems without a group, because they get put inside \fBdefault\fR group\. For example \fBonly test:default\fR will install all gems specified in test group and without one\. +.TP +\fBpath\fR (\fBBUNDLE_PATH\fR) +The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. When not set, Bundler install by default to a \fB\.bundle\fR directory relative to repository root in Bundler 4, and to the default system path (\fBGem\.dir\fR) before Bundler 4\. That means that before Bundler 4, Bundler shares this location with Rubygems, and \fBgem install \|\.\|\.\|\.\fR will have gems installed in the same location and therefore, gems installed without \fBpath\fR set will show up by calling \fBgem list\fR\. This will not be the case in Bundler 4\. +.TP +\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR) +Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. +.TP +\fBplugins\fR (\fBBUNDLE_PLUGINS\fR) +Enable Bundler's experimental plugin system\. +.TP +\fBprefer_patch\fR (BUNDLE_PREFER_PATCH) +Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. +.TP +\fBredirect\fR (\fBBUNDLE_REDIRECT\fR) +The number of redirects allowed for network requests\. Defaults to \fB5\fR\. +.TP +\fBretry\fR (\fBBUNDLE_RETRY\fR) +The number of times to retry failed network requests\. Defaults to \fB3\fR\. +.TP +\fBshebang\fR (\fBBUNDLE_SHEBANG\fR) +The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. +.TP +\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR) +Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. +.TP +\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR) +Silence the warning Bundler prints when installing gems as root\. +.TP +\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR) +The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\. +.TP +\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR) +Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. +.TP +\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR) +Path to a designated file containing a X\.509 client certificate and key in PEM format\. +.TP +\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR) +The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. +.TP +\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR) +The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. +.TP +\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR) +The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. .TP -\fBbin\fR -Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) +Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. .TP -\fBdeployment\fR -In deployment mode, Bundler will 'roll\-out' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\. +\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR) +The custom user agent fragment Bundler includes in API requests\. .TP -\fBonly\fR -A space\-separated list of groups to install only gems of the specified groups\. +\fBverbose\fR (\fBBUNDLE_VERBOSE\fR) +Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\. .TP -\fBpath\fR -The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. +\fBversion\fR (\fBBUNDLE_VERSION\fR) +The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. .TP -\fBwithout\fR -A space\-separated list of groups referencing gems to skip during installation\. +\fBwith\fR (\fBBUNDLE_WITH\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should install\. .TP -\fBwith\fR -A space\-separated list of \fBoptional\fR groups referencing gems to include during installation\. +\fBwithout\fR (\fBBUNDLE_WITHOUT\fR) +A space\-separated or \fB:\fR\-separated list of groups whose gems bundler should not install\. .SH "BUILD OPTIONS" You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\. .P @@ -84,121 +240,6 @@ bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mys .IP "" 0 .P After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\. -.SH "CONFIGURATION KEYS" -Configuration keys in bundler have two forms: the canonical form and the environment variable form\. -.P -For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn't install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\. -.P -The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\. -.P -Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\. -.SH "LIST OF AVAILABLE KEYS" -The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. -.IP "\(bu" 4 -\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\. -.IP "\(bu" 4 -\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\. -.IP "\(bu" 4 -\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\. -.IP "\(bu" 4 -\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\. -.IP "\(bu" 4 -\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\. -.IP "\(bu" 4 -\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\. -.IP "\(bu" 4 -\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\. -.IP "\(bu" 4 -\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\. -.IP "\(bu" 4 -\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\. -.IP "\(bu" 4 -\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\. -.IP "\(bu" 4 -\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. -.IP "\(bu" 4 -\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\. -.IP "\(bu" 4 -\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\. -.IP "\(bu" 4 -\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\. -.IP "\(bu" 4 -\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\. -.IP "\(bu" 4 -\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\. -.IP "\(bu" 4 -\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\. -.IP "\(bu" 4 -\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\. -.IP "\(bu" 4 -\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\. -.IP "\(bu" 4 -\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\. -.IP "\(bu" 4 -\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\. -.IP "\(bu" 4 -\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\. -.IP "\(bu" 4 -\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\. -.IP "\(bu" 4 -\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\. -.IP "\(bu" 4 -\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\. -.IP "\(bu" 4 -\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\. -.IP "\(bu" 4 -\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\. -.IP "\(bu" 4 -\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\. -.IP "\(bu" 4 -\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\. -.IP "\(bu" 4 -\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\. -.IP "\(bu" 4 -\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\. -.IP "\(bu" 4 -\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\. -.IP "\(bu" 4 -\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\. -.IP "\(bu" 4 -\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\. -.IP "\(bu" 4 -\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. -.IP "\(bu" 4 -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\. -.IP "\(bu" 4 -\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\. -.IP "\(bu" 4 -\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\. -.IP "\(bu" 4 -\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\. -.IP "\(bu" 4 -\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\. -.IP "\(bu" 4 -\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\. -.IP "\(bu" 4 -\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\. -.IP "\(bu" 4 -\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. -.IP "\(bu" 4 -\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\. -.IP "\(bu" 4 -\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\. -.IP "\(bu" 4 -\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\. -.IP "\(bu" 4 -\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\. -.IP "\(bu" 4 -\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\. -.IP "\(bu" 4 -\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\. -.IP "\(bu" 4 -\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. -.IP "\(bu" 4 -\fBwith\fR (\fBBUNDLE_WITH\fR): A \fB:\fR\-separated list of groups whose gems bundler should install\. -.IP "\(bu" 4 -\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A \fB:\fR\-separated list of groups whose gems bundler should not install\. -.IP "" 0 .SH "LOCAL GIT REPOS" Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override: .IP "" 4 @@ -274,7 +315,7 @@ export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit" For gems with a git source with HTTP(S) URL you can specify credentials like so: .IP "" 4 .nf -bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password +bundle config set \-\-global https://github\.com/ruby/rubygems\.git username:password .fi .IP "" 0 .P diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 56e1dfd3bc..7c34f1d1af 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -3,10 +3,10 @@ bundle-config(1) -- Set bundler configuration options ## SYNOPSIS -`bundle config` list<br> -`bundle config` [get] NAME<br> -`bundle config` [set] NAME VALUE<br> -`bundle config` unset NAME +`bundle config` [list]<br> +`bundle config` [get [--local|--global]] NAME<br> +`bundle config` [set [--local|--global]] NAME VALUE<br> +`bundle config` unset [--local|--global] NAME ## DESCRIPTION @@ -19,98 +19,67 @@ Bundler loads configuration settings in this order: 3. Global config (`~/.bundle/config`) 4. Bundler default config -Executing `bundle config list` will print a list of all bundler -configuration for the current bundle, and where that configuration -was set. - -Executing `bundle config get <name>` will print the value of that configuration -setting, and where it was set. - -Executing `bundle config set <name> <value>` defaults to setting `local` -configuration if executing from within a local application, otherwise it will -set `global` configuration. See `--local` and `--global` options below. - -Executing `bundle config set --local <name> <value>` will set that configuration -in the directory for the local application. The configuration will be stored in -`<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration -will be stored in `$BUNDLE_APP_CONFIG/config`. - -Executing `bundle config set --global <name> <value>` will set that -configuration to the value specified for all bundles executed as the current -user. The configuration will be stored in `~/.bundle/config`. If <name> already -is set, <name> will be overridden and user will be warned. - -Executing `bundle config unset <name>` will delete the configuration in both -local and global sources. - -Executing `bundle config unset --global <name>` will delete the configuration -only from the user configuration. - -Executing `bundle config unset --local <name>` will delete the configuration -only from the local application. - -Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will +Executing `bundle` with the `BUNDLE_IGNORE_CONFIG` environment variable set will cause it to ignore all configuration. -## REMEMBERING OPTIONS +## SUB-COMMANDS -Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or -`--without production`, are remembered between commands and saved to your local -application's configuration (normally, `./.bundle/config`). +### list (default command) -However, this will be changed in bundler 3, so it's better not to rely on this -behavior. If these options must be remembered, it's better to set them using -`bundle config` (e.g., `bundle config set --local path foo`). +Executing `bundle config list` will print a list of all bundler +configuration for the current bundle, and where that configuration +was set. -The options that can be configured are: +### get -* `bin`: - Creates a directory (defaults to `~/bin`) and place any executables from the - gem there. These executables run in Bundler's context. If used, you might add - this directory to your environment's `PATH` variable. For instance, if the - `rails` gem comes with a `rails` executable, this flag will create a - `bin/rails` executable that ensures that all referred dependencies will be - resolved using the bundled gems. +Executing `bundle config get <name>` will print the value of that configuration +setting, and all locations where it was set. -* `deployment`: - In deployment mode, Bundler will 'roll-out' the bundle for - `production` use. Please check carefully if you want to have this option - enabled in `development` or `test` environments. +**OPTIONS** -* `only`: - A space-separated list of groups to install only gems of the specified groups. +* `--local`: + Get configuration from configuration file for the local application, namely, + `<project_root>/.bundle/config`, or `$BUNDLE_APP_CONFIG/config` if + `BUNDLE_APP_CONFIG` is set. -* `path`: - The location to install the specified gems to. This defaults to Rubygems' - setting. Bundler shares this location with Rubygems, `gem install ...` will - have gem installed there, too. Therefore, gems installed without a - `--path ...` setting will show up by calling `gem list`. Accordingly, gems - installed to other locations will not get listed. +* `--global`: + Get configuration from configuration file global to all bundles executed as + the current user, namely, from `~/.bundle/config`. -* `without`: - A space-separated list of groups referencing gems to skip during installation. +### set -* `with`: - A space-separated list of **optional** groups referencing gems to include during installation. +Executing `bundle config set <name> <value>` defaults to setting `local` +configuration if executing from within a local application, otherwise it will +set `global` configuration. -## BUILD OPTIONS +**OPTIONS** -You can use `bundle config` to give Bundler the flags to pass to the gem -installer every time bundler tries to install a particular gem. +* `--local`: + Executing `bundle config set --local <name> <value>` will set that configuration + in the directory for the local application. The configuration will be stored in + `<project_root>/.bundle/config`. If `BUNDLE_APP_CONFIG` is set, the configuration + will be stored in `$BUNDLE_APP_CONFIG/config`. -A very common example, the `mysql` gem, requires Snow Leopard users to -pass configuration flags to `gem install` to specify where to find the -`mysql_config` executable. +* `--global`: + Executing `bundle config set --global <name> <value>` will set that + configuration to the value specified for all bundles executed as the current + user. The configuration will be stored in `~/.bundle/config`. If <name> already + is set, <name> will be overridden and user will be warned. - gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config +### unset -Since the specific location of that executable can change from machine -to machine, you can specify these flags on a per-machine basis. +Executing `bundle config unset <name>` will delete the configuration in both +local and global sources. - bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config +**OPTIONS** -After running this command, every time bundler needs to install the -`mysql` gem, it will pass along the flags you specified. +* `--local`: + Executing `bundle config unset --local <name>` will delete the configuration + only from the local application. + +* `--global`: + Executing `bundle config unset --global <name>` will delete the configuration + only from the user configuration. ## CONFIGURATION KEYS @@ -137,19 +106,20 @@ the environment variable `BUNDLE_LOCAL__RACK`. The following is a list of all configuration keys and their purpose. You can learn more about their operation in [bundle install(1)](bundle-install.1.html). -* `allow_offline_install` (`BUNDLE_ALLOW_OFFLINE_INSTALL`): - Allow Bundler to use cached data when installing without network access. -* `auto_clean_without_path` (`BUNDLE_AUTO_CLEAN_WITHOUT_PATH`): - Automatically run `bundle clean` after installing when an explicit `path` - has not been set and Bundler is not installing into the system gems. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): - Install executables from gems in the bundle to the specified directory. - Defaults to `false`. + If configured, `bundle binstubs` will install executables from gems in the + bundle to the specified directory. Otherwise it will create them in a `bin` + directory relative to the Gemfile directory. These executables run in + Bundler's context. If used, you might add this directory to your + environment's `PATH` variable. For instance, if the `rails` gem comes with a + `rails` executable, `bundle binstubs` will create a `bin/rails` executable + that ensures that all referred dependencies will be resolved using the + bundled gems. * `cache_all` (`BUNDLE_CACHE_ALL`): Cache all gems, including path and git gems. This needs to be explicitly - configured on bundler 1 and bundler 2, but will be the default on bundler 3. + before bundler 4, but will be the default on bundler 4. * `cache_all_platforms` (`BUNDLE_CACHE_ALL_PLATFORMS`): Cache gems for all platforms. * `cache_path` (`BUNDLE_CACHE_PATH`): @@ -158,15 +128,16 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Defaults to `vendor/cache`. * `clean` (`BUNDLE_CLEAN`): Whether Bundler should run `bundle clean` automatically after - `bundle install`. + `bundle install`. Defaults to `true` in Bundler 4, as long as `path` is not + explicitly configured. * `console` (`BUNDLE_CONSOLE`): The console that `bundle console` starts. Defaults to `irb`. -* `default_install_uses_path` (`BUNDLE_DEFAULT_INSTALL_USES_PATH`): - Whether a `bundle install` without an explicit `--path` argument defaults - to installing gems in `.bundle`. +* `default_cli_command` (`BUNDLE_DEFAULT_CLI_COMMAND`): + The command that running `bundle` without arguments should run. Defaults to + `cli_help` since Bundler 4, but can also be `install` which was the previous + default. * `deployment` (`BUNDLE_DEPLOYMENT`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. + Equivalent to setting `frozen` to `true` and `path` to `vendor/bundle`. * `disable_checksum_validation` (`BUNDLE_DISABLE_CHECKSUM_VALIDATION`): Allow installing gems even if they do not match the checksum provided by RubyGems. @@ -188,12 +159,13 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Ignore the current machine's platform and install only `ruby` platform gems. As a result, gems with native extensions will be compiled from source. * `frozen` (`BUNDLE_FROZEN`): - Disallow changes to the `Gemfile`. When the `Gemfile` is changed and the - lockfile has not been updated, running Bundler commands will be blocked. - Defaults to `true` when `--deployment` is used. + Disallow any automatic changes to `Gemfile.lock`. Bundler commands will + be blocked unless the lockfile can be installed exactly as written. + Usually this will happen when changing the `Gemfile` manually and forgetting + to update the lockfile through `bundle lock` or `bundle install`. * `gem.github_username` (`BUNDLE_GEM__GITHUB_USERNAME`): - Sets a GitHub username or organization to be used in `README` file when you - create a new gem via `bundle gem` command. It can be overridden by passing an + Sets a GitHub username or organization to be used in the `README` and `.gemspec` files + when you create a new gem via `bundle gem` command. It can be overridden by passing an explicit `--github-username` flag to `bundle gem`. * `gem.push_key` (`BUNDLE_GEM__PUSH_KEY`): Sets the `--key` parameter for `gem push` when using the `rake release` @@ -205,8 +177,8 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). will search up from the current working directory until it finds a `Gemfile`. * `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`): - Whether Bundler should cache all gems globally, rather than locally to the - installing Ruby installation. + Whether Bundler should cache all gems and compiled extensions globally, + rather than locally to the configured installation path. * `ignore_funding_requests` (`BUNDLE_IGNORE_FUNDING_REQUESTS`): When set, no funding requests will be printed. * `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): @@ -217,34 +189,41 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). * `jobs` (`BUNDLE_JOBS`): The number of gems Bundler can install in parallel. Defaults to the number of available processors. +* `lockfile` (`BUNDLE_LOCKFILE`): + The path to the lockfile that bundler should use. By default, Bundler adds + `.lock` to the end of the `gemfile` entry. Can be set to `false` in the + Gemfile to disable lockfile creation entirely (see gemfile(5)). +* `lockfile_checksums` (`BUNDLE_LOCKFILE_CHECKSUMS`): + Whether Bundler should include a checksums section in new lockfiles, to protect from compromised gem sources. Defaults to true. * `no_install` (`BUNDLE_NO_INSTALL`): Whether `bundle package` should skip installing gems. * `no_prune` (`BUNDLE_NO_PRUNE`): Whether Bundler should leave outdated gems unpruned when caching. * `only` (`BUNDLE_ONLY`): A space-separated list of groups to install only gems of the specified groups. + Please check carefully if you want to install also gems without a group, because + they get put inside `default` group. For example `only test:default` will install + all gems specified in test group and without one. * `path` (`BUNDLE_PATH`): The location on disk where all gems in your bundle will be located regardless of `$GEM_HOME` or `$GEM_PATH` values. Bundle gems not found in this location - will be installed by `bundle install`. Defaults to `Gem.dir`. When --deployment - is used, defaults to vendor/bundle. + will be installed by `bundle install`. When not set, Bundler install by + default to a `.bundle` directory relative to repository root in Bundler 4, + and to the default system path (`Gem.dir`) before Bundler 4. That means that + before Bundler 4, Bundler shares this location with Rubygems, and `gem + install ...` will have gems installed in the same location and therefore, + gems installed without `path` set will show up by calling `gem list`. This + will not be the case in Bundler 4. * `path.system` (`BUNDLE_PATH__SYSTEM`): Whether Bundler will install gems into the default system path (`Gem.dir`). -* `path_relative_to_cwd` (`BUNDLE_PATH_RELATIVE_TO_CWD`) - Makes `--path` relative to the CWD instead of the `Gemfile`. * `plugins` (`BUNDLE_PLUGINS`): Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): - Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. * `retry` (`BUNDLE_RETRY`): The number of times to retry failed network requests. Defaults to `3`. -* `setup_makes_kernel_gem_public` (`BUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC`): - Have `Bundler.setup` make the `Kernel#gem` method public, even though - RubyGems declares it as private. * `shebang` (`BUNDLE_SHEBANG`): The program name that should be invoked for generated binstubs. Defaults to the ruby install name used to generate the binstub. @@ -253,6 +232,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). be changed in the next major version. * `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): Silence the warning Bundler prints when installing gems as root. +* `simulate_version` (`BUNDLE_SIMULATE_VERSION`): + The virtual version Bundler should use for activating feature flags. Can be + used to simulate all the new functionality that will be enabled in a future + major version. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. @@ -271,6 +254,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): The custom user agent fragment Bundler includes in API requests. +* `verbose` (`BUNDLE_VERBOSE`): + Whether Bundler should print verbose output. Defaults to `false`, unless the + `--verbose` CLI flag is used. * `version` (`BUNDLE_VERSION`): The version of Bundler to use when running under Bundler environment. Defaults to `lockfile`. You can also specify `system` or `x.y.z`. @@ -278,9 +264,28 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). `system` will use the system version of Bundler, and `x.y.z` will use the specified version of Bundler. * `with` (`BUNDLE_WITH`): - A `:`-separated list of groups whose gems bundler should install. + A space-separated or `:`-separated list of groups whose gems bundler should install. * `without` (`BUNDLE_WITHOUT`): - A `:`-separated list of groups whose gems bundler should not install. + A space-separated or `:`-separated list of groups whose gems bundler should not install. + +## BUILD OPTIONS + +You can use `bundle config` to give Bundler the flags to pass to the gem +installer every time bundler tries to install a particular gem. + +A very common example, the `mysql` gem, requires Snow Leopard users to +pass configuration flags to `gem install` to specify where to find the +`mysql_config` executable. + + gem install mysql -- --with-mysql-config=/usr/local/mysql/bin/mysql_config + +Since the specific location of that executable can change from machine +to machine, you can specify these flags on a per-machine basis. + + bundle config set --global build.mysql --with-mysql-config=/usr/local/mysql/bin/mysql_config + +After running this command, every time bundler needs to install the +`mysql` gem, it will pass along the flags you specified. ## LOCAL GIT REPOS @@ -359,7 +364,7 @@ Or you can set the credentials as an environment variable like this: For gems with a git source with HTTP(S) URL you can specify credentials like so: - bundle config set --global https://github.com/rubygems/rubygems.git username:password + bundle config set --global https://github.com/ruby/rubygems.git username:password Or you can set the credentials as an environment variable like so: diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 720318ec84..5ab15668be 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,8 +1,8 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-CONSOLE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-CONSOLE" "1" "September 2025" "" .SH "NAME" -\fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded +\fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" \fBbundle console\fR [GROUP] .SH "DESCRIPTION" @@ -29,7 +29,5 @@ $ bundle console Resolving dependencies\|\.\|\.\|\. [1] pry(main)> .fi -.SH "NOTES" -This command was deprecated in Bundler 2\.1 and will be removed in 3\.0\. Use \fBbin/console\fR script, which can be generated by \fBbundle gem <NAME>\fR\. .SH "SEE ALSO" Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR diff --git a/lib/bundler/man/bundle-console.1.ronn b/lib/bundler/man/bundle-console.1.ronn index f9096d386a..ed842ae1c3 100644 --- a/lib/bundler/man/bundle-console.1.ronn +++ b/lib/bundler/man/bundle-console.1.ronn @@ -1,5 +1,5 @@ -bundle-console(1) -- Deprecated way to open an IRB session with the bundle pre-loaded -===================================================================================== +bundle-console(1) -- Open an IRB session with the bundle pre-loaded +=================================================================== ## SYNOPSIS @@ -34,11 +34,6 @@ the shell from the following: Resolving dependencies... [1] pry(main)> -## NOTES - -This command was deprecated in Bundler 2.1 and will be removed in 3.0. -Use `bin/console` script, which can be generated by `bundle gem <NAME>`. - ## SEE ALSO [Gemfile(5)](https://bundler.io/man/gemfile.5.html) diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index 21c1777241..a0329dfc48 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,14 +1,21 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-DOCTOR" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-DOCTOR" "1" "September 2025" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" -\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl] +.br +\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=TLS\-VERSION] [\-\-verify\-mode=VERIFY\-MODE] +.br +\fBbundle doctor\fR help [COMMAND] .SH "DESCRIPTION" +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\. +.SH "SUB\-COMMANDS" +.SS "diagnose (default command)" Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. .P -Examples of common problems caught by bundle\-doctor include: +Examples of common problems caught include: .IP "\(bu" 4 Invalid Bundler settings .IP "\(bu" 4 @@ -20,11 +27,43 @@ Uninstalled gems .IP "\(bu" 4 Missing dependencies .IP "" 0 -.SH "OPTIONS" +.P +\fBOPTIONS\fR .TP \fB\-\-quiet\fR Only output warnings and errors\. .TP -\fB\-\-gemfile=<gemfile>\fR +\fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +.TP +\fB\-\-ssl\fR +Diagnose common SSL problems when connecting to https://rubygems\.org\. +.IP +This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\. +.SS "ssl" +If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as: +.IP "\(bu" 4 +Verify the Ruby OpenSSL version installed on your system\. +.IP "\(bu" 4 +Check the OpenSSL library version used for compilation\. +.IP "\(bu" 4 +Ensure CA certificates are correctly setup on your machine\. +.IP "\(bu" 4 +Open a TLS connection and verify the outcome\. +.IP "" 0 +.P +\fBOPTIONS\fR +.TP +\fB\-\-host=HOST\fR +Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\. +.TP +\fB\-\-tls\-version=TLS\-VERSION\fR +Specify the TLS version when opening the connection to HOST\. +.IP +Accepted values are: \fB1\.1\fR or \fB1\.2\fR\. +.TP +\fB\-\-verify\-mode=VERIFY\-MODE\fR +Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\. +.IP +Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\. diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn index 271ee800ad..7495099ff5 100644 --- a/lib/bundler/man/bundle-doctor.1.ronn +++ b/lib/bundler/man/bundle-doctor.1.ronn @@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems ## SYNOPSIS -`bundle doctor` [--quiet] - [--gemfile=GEMFILE] +`bundle doctor [diagnose]` [--quiet] + [--gemfile=GEMFILE] + [--ssl]<br> +`bundle doctor ssl` [--host=HOST] + [--tls-version=TLS-VERSION] + [--verify-mode=VERIFY-MODE]<br> +`bundle doctor` help [COMMAND] ## DESCRIPTION +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue. + +## SUB-COMMANDS + +### diagnose (default command) + Checks your Gemfile and gem environment for common problems. If issues are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits status 0. -Examples of common problems caught by bundle-doctor include: +Examples of common problems caught include: * Invalid Bundler settings * Mismatched Ruby versions @@ -20,14 +31,47 @@ Examples of common problems caught by bundle-doctor include: * Uninstalled gems * Missing dependencies -## OPTIONS +**OPTIONS** * `--quiet`: Only output warnings and errors. -* `--gemfile=<gemfile>`: +* `--gemfile=GEMFILE`: The location of the Gemfile(5) which Bundler should use. This defaults to a Gemfile(5) in the current working directory. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. + +* `--ssl`: + Diagnose common SSL problems when connecting to https://rubygems.org. + + This flag runs the `bundle doctor ssl` subcommand with default values + underneath. + +### ssl + +If you've experienced issues related to SSL certificates and/or TLS versions while connecting +to https://rubygems.org, this command can help troubleshoot common problems. +The diagnostic will perform a few checks such as: + +* Verify the Ruby OpenSSL version installed on your system. +* Check the OpenSSL library version used for compilation. +* Ensure CA certificates are correctly setup on your machine. +* Open a TLS connection and verify the outcome. + +**OPTIONS** + +* `--host=HOST`: + Perform the diagnostic on HOST. Defaults to `rubygems.org`. + +* `--tls-version=TLS-VERSION`: + Specify the TLS version when opening the connection to HOST. + + Accepted values are: `1.1` or `1.2`. + +* `--verify-mode=VERIFY-MODE`: + Specify the TLS verify mode when opening the connection to HOST. + Defaults to `SSL_VERIFY_PEER`. + + Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`. diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1 new file mode 100644 index 0000000000..eee3ca05d0 --- /dev/null +++ b/lib/bundler/man/bundle-env.1 @@ -0,0 +1,9 @@ +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-ENV" "1" "September 2025" "" +.SH "NAME" +\fBbundle\-env\fR \- Print information about the environment Bundler is running under +.SH "SYNOPSIS" +\fBbundle env\fR +.SH "DESCRIPTION" +Prints information about the environment Bundler is running under\. diff --git a/lib/bundler/man/bundle-env.1.ronn b/lib/bundler/man/bundle-env.1.ronn new file mode 100644 index 0000000000..c2df9c29c2 --- /dev/null +++ b/lib/bundler/man/bundle-env.1.ronn @@ -0,0 +1,10 @@ +bundle-env(1) -- Print information about the environment Bundler is running under +================================================================================= + +## SYNOPSIS + +`bundle env` + +## DESCRIPTION + +Prints information about the environment Bundler is running under. diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index a4e74e6b60..24c84889b5 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,10 +1,10 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-EXEC" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-EXEC" "1" "September 2025" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" -\fBbundle exec\fR [\-\-keep\-file\-descriptors] \fIcommand\fR +\fBbundle exec\fR [\-\-gemfile=GEMFILE] \fIcommand\fR .SH "DESCRIPTION" This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\. .P @@ -13,8 +13,8 @@ Essentially, if you would normally have run something like \fBrspec spec/my_spec Note that \fBbundle exec\fR does not require that an executable is available on your shell's \fB$PATH\fR\. .SH "OPTIONS" .TP -\fB\-\-keep\-file\-descriptors\fR -Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\. +\fB\-\-gemfile=GEMFILE\fR +Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. .SH "BUNDLE INSTALL \-\-BINSTUBS" If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\. .P @@ -71,8 +71,8 @@ end Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this: .IP "" 4 .nf -Bundler\.clean_system('brew install wget') -Bundler\.clean_exec('brew install wget') +Bundler\.unbundled_system('brew install wget') +Bundler\.unbundled_exec('brew install wget') .fi .IP "" 0 .SH "RUBYGEMS PLUGINS" diff --git a/lib/bundler/man/bundle-exec.1.ronn b/lib/bundler/man/bundle-exec.1.ronn index 9d5b559f26..e51a66a084 100644 --- a/lib/bundler/man/bundle-exec.1.ronn +++ b/lib/bundler/man/bundle-exec.1.ronn @@ -3,7 +3,7 @@ bundle-exec(1) -- Execute a command in the context of the bundle ## SYNOPSIS -`bundle exec` [--keep-file-descriptors] <command> +`bundle exec` [--gemfile=GEMFILE] <command> ## DESCRIPTION @@ -20,9 +20,8 @@ available on your shell's `$PATH`. ## OPTIONS -* `--keep-file-descriptors`: - Passes all file descriptors to the new processes. Default is true from - bundler version 2.2.26. Setting it to false is now deprecated. +* `--gemfile=GEMFILE`: + Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. ## BUNDLE INSTALL --BINSTUBS @@ -105,8 +104,8 @@ need to use `with_unbundled_env`. Bundler provides convenience helpers that wrap `system` and `exec`, and they can be used like this: - Bundler.clean_system('brew install wget') - Bundler.clean_exec('brew install wget') + Bundler.unbundled_system('brew install wget') + Bundler.unbundled_exec('brew install wget') ## RUBYGEMS PLUGINS diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1 new file mode 100644 index 0000000000..fe24a25ca1 --- /dev/null +++ b/lib/bundler/man/bundle-fund.1 @@ -0,0 +1,22 @@ +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-FUND" "1" "September 2025" "" +.SH "NAME" +\fBbundle\-fund\fR \- Lists information about gems seeking funding assistance +.SH "SYNOPSIS" +\fBbundle fund\fR [\fIOPTIONS\fR] +.SH "DESCRIPTION" +\fBbundle fund\fR lists information about gems seeking funding assistance\. +.SH "OPTIONS" +.TP +\fB\-\-group=<list>\fR, \fB\-g=<list>\fR +Fetch funding information for a specific group\. +.SH "EXAMPLES" +.nf +# Lists funding information for all gems +bundle fund + +# Lists funding information for a specific group +bundle fund \-\-group=security +.fi + diff --git a/lib/bundler/man/bundle-fund.1.ronn b/lib/bundler/man/bundle-fund.1.ronn new file mode 100644 index 0000000000..faf8b9c4a7 --- /dev/null +++ b/lib/bundler/man/bundle-fund.1.ronn @@ -0,0 +1,25 @@ +bundle-fund(1) -- Lists information about gems seeking funding assistance +========================================================================= + +## SYNOPSIS + +`bundle fund` [*OPTIONS*] + +## DESCRIPTION + +**bundle fund** lists information about gems seeking funding assistance. + +## OPTIONS + +* `--group=<list>`, `-g=<list>`: + Fetch funding information for a specific group. + +## EXAMPLES + +``` +# Lists funding information for all gems +bundle fund + +# Lists funding information for a specific group +bundle fund --group=security +``` diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 54be9f4891..85c0f57674 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-GEM" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-GEM" "1" "September 2025" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" @@ -19,55 +19,87 @@ The generated project skeleton can be customized with OPTIONS, as explained belo \fBgem\.test\fR .IP "" 0 .SH "OPTIONS" -.IP "\(bu" 4 -\fB\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. -.IP "\(bu" 4 -\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: +.TP +\fB\-\-exe\fR, \fB\-\-bin\fR, \fB\-b\fR +Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\. +.TP +\fB\-\-no\-exe\fR +Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\. +.TP +\fB\-\-coc\fR +Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-coc\fR +Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\. +.TP +\fB\-\-changelog\fR +Add a \fBCHANGELOG\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. Update the default with \fBbundle config set \-\-global gem\.changelog <true|false>\fR\. +.TP +\fB\-\-no\-changelog\fR +Do not create a \fBCHANGELOG\.md\fR (overrides \fB\-\-changelog\fR specified in the global config)\. +.TP +\fB\-\-ext=c\fR, \fB\-\-ext=go\fR, \fB\-\-ext=rust\fR +Add boilerplate for C, Go (currently go\-gem\-wrapper \fIhttps://github\.com/ruby\-go\-gem/go\-gem\-wrapper\fR based) or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\. +.TP +\fB\-\-no\-ext\fR +Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\. +.TP +\fB\-\-git\fR +Initialize a git repo inside your library\. +.TP +\fB\-\-github\-username=GITHUB_USERNAME\fR +Fill in GitHub username on README so that you don't have to do it manually\. Set a default with \fBbundle config set \-\-global gem\.github_username <your_username>\fR\. +.TP +\fB\-\-mit\fR +Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. +.TP +\fB\-\-no\-mit\fR +Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\. +.TP +\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR +Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified: .IP When Bundler is configured to generate tests, this defaults to Bundler's global config setting \fBgem\.test\fR\. .IP When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-test\fR +Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\. +.TP +\fB\-\-ci\fR, \fB\-\-ci=circle\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR +Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\. .IP When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: +.TP +\fB\-\-no\-ci\fR +Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\. +.TP +\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR +Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified: .IP When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\. .IP When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\. .IP When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\. -.IP "\(bu" 4 -\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. -.IP "\(bu" 4 -\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR: Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. -.IP "" 0 +.TP +\fB\-\-no\-linter\fR +Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\. +.TP +\fB\-\-edit=EDIT\fR, \fB\-e=EDIT\fR +Open the resulting GEM_NAME\.gemspec in EDIT, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\. +.TP +\fB\-\-bundle\fR +Run \fBbundle install\fR after creating the gem\. +.TP +\fB\-\-no\-bundle\fR +Do not run \fBbundle install\fR after creating the gem\. .SH "SEE ALSO" .IP "\(bu" 4 bundle config(1) \fIbundle\-config\.1\.html\fR diff --git a/lib/bundler/man/bundle-gem.1.ronn b/lib/bundler/man/bundle-gem.1.ronn index 2d71d8dabe..488c8113e4 100644 --- a/lib/bundler/man/bundle-gem.1.ronn +++ b/lib/bundler/man/bundle-gem.1.ronn @@ -1,5 +1,5 @@ bundle-gem(1) -- Generate a project skeleton for creating a rubygem -==================================================================== +=================================================================== ## SYNOPSIS @@ -24,7 +24,7 @@ configuration file using the following names: ## OPTIONS -* `--exe` or `-b` or `--bin`: +* `--exe`, `--bin`, `-b`: Specify that Bundler should create a binary executable (as `exe/GEM_NAME`) in the generated rubygem project. This binary will also be added to the `GEM_NAME.gemspec` manifest. This behavior is disabled by default. @@ -41,14 +41,30 @@ configuration file using the following names: Do not create a `CODE_OF_CONDUCT.md` (overrides `--coc` specified in the global config). -* `--ext=c`, `--ext=rust` - Add boilerplate for C or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior +* `--changelog`: + Add a `CHANGELOG.md` file to the root of the generated project. If + this option is unspecified, an interactive prompt will be displayed and the + answer will be saved in Bundler's global config for future `bundle gem` use. + Update the default with `bundle config set --global gem.changelog <true|false>`. + +* `--no-changelog`: + Do not create a `CHANGELOG.md` (overrides `--changelog` specified in the + global config). + +* `--ext=c`, `--ext=go`, `--ext=rust`: + Add boilerplate for C, Go (currently [go-gem-wrapper](https://github.com/ruby-go-gem/go-gem-wrapper) based) or Rust (currently [magnus](https://docs.rs/magnus) based) extension code to the generated project. This behavior is disabled by default. * `--no-ext`: Do not add extension code (overrides `--ext` specified in the global config). +* `--git`: + Initialize a git repo inside your library. + +* `--github-username=GITHUB_USERNAME`: + Fill in GitHub username on README so that you don't have to do it manually. Set a default with `bundle config set --global gem.github_username <your_username>`. + * `--mit`: Add an MIT license to a `LICENSE.txt` file in the root of the generated project. Your name from the global git config is used for the copyright @@ -80,7 +96,7 @@ configuration file using the following names: Do not use a test framework (overrides `--test` specified in the global config). -* `--ci`, `--ci=github`, `--ci=gitlab`, `--ci=circle`: +* `--ci`, `--ci=circle`, `--ci=github`, `--ci=gitlab`: Specify the continuous integration service that Bundler should use when generating the project. Acceptable values are `github`, `gitlab` and `circle`. A configuration file will be generated in the project directory. @@ -119,10 +135,16 @@ configuration file using the following names: * `--no-linter`: Do not add a linter (overrides `--linter` specified in the global config). -* `-e`, `--edit[=EDITOR]`: - Open the resulting GEM_NAME.gemspec in EDITOR, or the default editor if not +* `--edit=EDIT`, `-e=EDIT`: + Open the resulting GEM_NAME.gemspec in EDIT, or the default editor if not specified. The default is `$BUNDLER_EDITOR`, `$VISUAL`, or `$EDITOR`. +* `--bundle`: + Run `bundle install` after creating the gem. + +* `--no-bundle`: + Do not run `bundle install` after creating the gem. + ## SEE ALSO * [bundle config(1)](bundle-config.1.html) diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 7058ed8172..05fd5a7c48 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-HELP" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-HELP" "1" "September 2025" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 7a47949c02..96c7d876f6 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,14 +1,17 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INFO" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-INFO" "1" "September 2025" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" -\fBbundle info\fR [GEM_NAME] [\-\-path] +\fBbundle info\fR [GEM_NAME] [\-\-path] [\-\-version] .SH "DESCRIPTION" Given a gem name present in your bundle, print the basic information about it such as homepage, version, path and summary\. .SH "OPTIONS" .TP \fB\-\-path\fR Print the path of the given gem +.TP +\fB\-\-version\fR +Print gem version diff --git a/lib/bundler/man/bundle-info.1.ronn b/lib/bundler/man/bundle-info.1.ronn index cecdeb564f..e99db8c614 100644 --- a/lib/bundler/man/bundle-info.1.ronn +++ b/lib/bundler/man/bundle-info.1.ronn @@ -1,10 +1,11 @@ bundle-info(1) -- Show information for the given gem in your bundle -========================================================================= +=================================================================== ## SYNOPSIS `bundle info` [GEM_NAME] [--path] + [--version] ## DESCRIPTION @@ -14,4 +15,7 @@ Given a gem name present in your bundle, print the basic information about it ## OPTIONS * `--path`: -Print the path of the given gem + Print the path of the given gem + +* `--version`: + Print gem version diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 96b2a7d78f..83dad5c050 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INIT" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-INIT" "1" "September 2025" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" @@ -9,10 +9,10 @@ Init generates a default [\fBGemfile(5)\fR][Gemfile(5)] in the current working directory\. When adding a [\fBGemfile(5)\fR][Gemfile(5)] to a gem with a gemspec, the \fB\-\-gemspec\fR option will automatically add each dependency listed in the gemspec file to the newly created [\fBGemfile(5)\fR][Gemfile(5)]\. .SH "OPTIONS" .TP -\fB\-\-gemspec\fR +\fB\-\-gemspec=GEMSPEC\fR Use the specified \.gemspec to create the [\fBGemfile(5)\fR][Gemfile(5)] .TP -\fB\-\-gemfile\fR +\fB\-\-gemfile=GEMFILE\fR Use the specified name for the gemfile instead of \fBGemfile\fR .SH "FILES" Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\. diff --git a/lib/bundler/man/bundle-init.1.ronn b/lib/bundler/man/bundle-init.1.ronn index 7d3cede1f6..ab3c427b52 100644 --- a/lib/bundler/man/bundle-init.1.ronn +++ b/lib/bundler/man/bundle-init.1.ronn @@ -14,9 +14,10 @@ created [`Gemfile(5)`][Gemfile(5)]. ## OPTIONS -* `--gemspec`: +* `--gemspec=GEMSPEC`: Use the specified .gemspec to create the [`Gemfile(5)`][Gemfile(5)] -* `--gemfile`: + +* `--gemfile=GEMFILE`: Use the specified name for the gemfile instead of `Gemfile` ## FILES diff --git a/lib/bundler/man/bundle-inject.1 b/lib/bundler/man/bundle-inject.1 deleted file mode 100644 index 20e4a824c4..0000000000 --- a/lib/bundler/man/bundle-inject.1 +++ /dev/null @@ -1,23 +0,0 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INJECT" "1" "October 2024" "" -.SH "NAME" -\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile -.SH "SYNOPSIS" -\fBbundle inject\fR [GEM] [VERSION] -.SH "DESCRIPTION" -Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\. -.P -This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn't listed yet\. -.P -Example: -.IP "" 4 -.nf -bundle install -bundle inject 'rack' '> 0' -.fi -.IP "" 0 -.P -This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\. -.P -The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\. diff --git a/lib/bundler/man/bundle-inject.1.ronn b/lib/bundler/man/bundle-inject.1.ronn deleted file mode 100644 index 95704eddad..0000000000 --- a/lib/bundler/man/bundle-inject.1.ronn +++ /dev/null @@ -1,24 +0,0 @@ -bundle-inject(1) -- Add named gem(s) with version requirements to Gemfile -========================================================================= - -## SYNOPSIS - -`bundle inject` [GEM] [VERSION] - -## DESCRIPTION - -Adds the named gem(s) with their version requirements to the resolved -[`Gemfile(5)`][Gemfile(5)]. - -This command will add the gem to both your [`Gemfile(5)`][Gemfile(5)] and Gemfile.lock if it -isn't listed yet. - -Example: - - bundle install - bundle inject 'rack' '> 0' - -This will inject the 'rack' gem with a version greater than 0 in your -[`Gemfile(5)`][Gemfile(5)] and Gemfile.lock. - -The `bundle inject` command was deprecated in Bundler 2.1 and will be removed in Bundler 3.0. diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index ffdbec98c2..68530f3ebb 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,10 +1,10 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-INSTALL" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-INSTALL" "1" "September 2025" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=POLICY] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] +\fBbundle install\fR [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-lockfile=LOCKFILE] [\-\-no\-cache] [\-\-no\-lock] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -12,58 +12,35 @@ If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), B .P If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\. .SH "OPTIONS" -The \fB\-\-clean\fR, \fB\-\-deployment\fR, \fB\-\-frozen\fR, \fB\-\-no\-prune\fR, \fB\-\-path\fR, \fB\-\-shebang\fR, \fB\-\-system\fR, \fB\-\-without\fR and \fB\-\-with\fR options are deprecated because they only make sense if they are applied to every subsequent \fBbundle install\fR run automatically and that requires \fBbundler\fR to silently remember them\. Since \fBbundler\fR will no longer remember CLI flags in future versions, \fBbundle config\fR (see bundle\-config(1)) should be used to apply them permanently\. .TP -\fB\-\-binstubs[=<directory>]\fR -Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\. -.IP -Creates a directory (defaults to \fB~/bin\fR) and places any executables from the gem there\. These executables run in Bundler's context\. If used, you might add this directory to your environment's \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\. -.TP -\fB\-\-clean\fR -On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don't worry, gems currently in use will not be removed\. -.IP -This option is deprecated in favor of the \fBclean\fR setting\. -.TP -\fB\-\-deployment\fR -In \fIdeployment mode\fR, Bundler will 'roll\-out' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\. -.IP -This option is deprecated in favor of the \fBdeployment\fR setting\. -.TP -\fB\-\-redownload\fR -Force download every gem, even if the required versions are already available locally\. -.TP -\fB\-\-frozen\fR -Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\. -.IP -This option is deprecated in favor of the \fBfrozen\fR setting\. +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. .TP \fB\-\-full\-index\fR Bundler will not call Rubygems' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\. .TP -\fB\-\-gemfile=<gemfile>\fR +\fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. .TP -\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR +\fB\-\-jobs=<number>\fR, \fB\-j=<number>\fR The maximum number of parallel download and install jobs\. The default is the number of available processors\. .TP \fB\-\-local\fR Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. .TP +\fB\-\-lockfile=LOCKFILE\fR +The location of the lockfile which Bundler should use\. This defaults to the Gemfile location with \fB\.lock\fR appended\. +.TP \fB\-\-prefer\-local\fR Force using locally installed gems, or gems already present in Rubygems' cache or in \fBvendor/cache\fR, when resolving, even if newer versions are available remotely\. Only attempt to connect to \fBrubygems\.org\fR for gems that are not present locally\. .TP \fB\-\-no\-cache\fR Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\. .TP -\fB\-\-no\-prune\fR -Don't remove stale gems from the cache when the installation finishes\. +\fB\-\-no\-lock\fR +Do not create a lockfile\. Useful if you want to install dependencies but not lock versions of gems\. Recommended for library development, and other situations where the code is expected to work with a range of dependency versions\. .IP -This option is deprecated in favor of the \fBno_prune\fR setting\. -.TP -\fB\-\-path=<path>\fR -The location to install the specified gems to\. This defaults to Rubygems' setting\. Bundler shares this location with Rubygems, \fBgem install \|\.\|\.\|\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \|\.\|\.\|\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\. -.IP -This option is deprecated in favor of the \fBpath\fR setting\. +This has the same effect as using \fBlockfile false\fR in the Gemfile\. See gemfile(5) for more information\. .TP \fB\-\-quiet\fR Do not print progress information to the standard output\. @@ -71,33 +48,16 @@ Do not print progress information to the standard output\. \fB\-\-retry=[<number>]\fR Retry failed network or git requests for \fInumber\fR times\. .TP -\fB\-\-shebang=<ruby\-executable>\fR -Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\. -.IP -This option is deprecated in favor of the \fBshebang\fR setting\. -.TP \fB\-\-standalone[=<list>]\fR -Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install has to be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\. +Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install can be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler's own setup in the manner required\. .TP -\fB\-\-system\fR -Installs the gems specified in the bundle to the system's Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\. -.IP -This option is deprecated in favor of the \fBsystem\fR setting\. -.TP -\fB\-\-trust\-policy=[<policy>]\fR +\fB\-\-trust\-policy=TRUST\-POLICY\fR Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\. .TP -\fB\-\-with=<list>\fR -A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\. -.IP -This option is deprecated in favor of the \fBwith\fR setting\. -.TP -\fB\-\-without=<list>\fR -A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\. -.IP -This option is deprecated in favor of the \fBwithout\fR setting\. +\fB\-\-target\-rbconfig=TARGET\-RBCONFIG\fR +Path to rbconfig\.rb for the deployment target platform\. .SH "DEPLOYMENT MODE" -Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\. +Bundler's defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fBdeployment\fR setting\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\. .IP "1." 4 A \fBGemfile\.lock\fR is required\. .IP @@ -117,14 +77,14 @@ In development, it's convenient to share the gems used in your application with .IP In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\. .IP -As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\. +As a result, when \fBdeployment\fR is configured, \fBbundle install\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fBpath\fR setting\. .IP "" 0 .SH "INSTALLING GROUPS" By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\. .P -However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\. +However, you can explicitly tell Bundler to skip installing certain groups with the \fBwithout\fR setting\. This setting takes a space\-separated list of groups\. .P -While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\. +While the \fBwithout\fR setting will skip \fIinstalling\fR the gems in the specified groups, \fBbundle install\fR will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\. .P This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\. .P @@ -145,7 +105,7 @@ end .P In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\. .P -When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\. +When you configure \fBbundle config without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\. .P This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\. .P diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index 1187455c92..c7d88bfb73 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -3,27 +3,20 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile ## SYNOPSIS -`bundle install` [--binstubs[=DIRECTORY]] - [--clean] - [--deployment] - [--frozen] +`bundle install` [--force] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--local] + [--lockfile=LOCKFILE] [--no-cache] - [--no-prune] - [--path PATH] + [--no-lock] [--prefer-local] [--quiet] - [--redownload] [--retry=NUMBER] - [--shebang] [--standalone[=GROUP[ GROUP...]]] - [--system] - [--trust-policy=POLICY] - [--with=GROUP[ GROUP...]] - [--without=GROUP[ GROUP...]] + [--trust-policy=TRUST-POLICY] + [--target-rbconfig=TARGET-RBCONFIG] ## DESCRIPTION @@ -44,63 +37,22 @@ update process below under [CONSERVATIVE UPDATING][]. ## OPTIONS -The `--clean`, `--deployment`, `--frozen`, `--no-prune`, `--path`, `--shebang`, -`--system`, `--without` and `--with` options are deprecated because they only -make sense if they are applied to every subsequent `bundle install` run -automatically and that requires `bundler` to silently remember them. Since -`bundler` will no longer remember CLI flags in future versions, `bundle config` -(see bundle-config(1)) should be used to apply them permanently. - -* `--binstubs[=<directory>]`: - Binstubs are scripts that wrap around executables. Bundler creates a small Ruby - file (a binstub) that loads Bundler, runs the command, and puts it in `bin/`. - This lets you link the binstub inside of an application to the exact gem - version the application needs. - - Creates a directory (defaults to `~/bin`) and places any executables from the - gem there. These executables run in Bundler's context. If used, you might add - this directory to your environment's `PATH` variable. For instance, if the - `rails` gem comes with a `rails` executable, this flag will create a - `bin/rails` executable that ensures that all referred dependencies will be - resolved using the bundled gems. - -* `--clean`: - On finishing the installation Bundler is going to remove any gems not present - in the current Gemfile(5). Don't worry, gems currently in use will not be - removed. - - This option is deprecated in favor of the `clean` setting. - -* `--deployment`: - In [deployment mode][DEPLOYMENT MODE], Bundler will 'roll-out' the bundle for - production or CI use. Please check carefully if you want to have this option - enabled in your development environment. - - This option is deprecated in favor of the `deployment` setting. - -* `--redownload`: - Force download every gem, even if the required versions are already available - locally. - -* `--frozen`: - Do not allow the Gemfile.lock to be updated after this install. Exits - non-zero if there are going to be changes to the Gemfile.lock. - - This option is deprecated in favor of the `frozen` setting. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. * `--full-index`: Bundler will not call Rubygems' API endpoint (default) but download and cache a (currently big) index file of all gems. Performance can be improved for large bundles that seldom change by enabling this option. -* `--gemfile=<gemfile>`: +* `--gemfile=GEMFILE`: The location of the Gemfile(5) which Bundler should use. This defaults to a Gemfile(5) in the current working directory. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. -* `--jobs=[<number>]`, `-j[<number>]`: +* `--jobs=<number>`, `-j=<number>`: The maximum number of parallel download and install jobs. The default is the number of available processors. @@ -110,6 +62,10 @@ automatically and that requires `bundler` to silently remember them. Since appropriate platform-specific gem exists on `rubygems.org` it will not be found. +* `--lockfile=LOCKFILE`: + The location of the lockfile which Bundler should use. This defaults + to the Gemfile location with `.lock` appended. + * `--prefer-local`: Force using locally installed gems, or gems already present in Rubygems' cache or in `vendor/cache`, when resolving, even if newer versions are available @@ -121,19 +77,14 @@ automatically and that requires `bundler` to silently remember them. Since does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install. -* `--no-prune`: - Don't remove stale gems from the cache when the installation finishes. +* `--no-lock`: + Do not create a lockfile. Useful if you want to install dependencies but not + lock versions of gems. Recommended for library development, and other + situations where the code is expected to work with a range of dependency + versions. - This option is deprecated in favor of the `no_prune` setting. - -* `--path=<path>`: - The location to install the specified gems to. This defaults to Rubygems' - setting. Bundler shares this location with Rubygems, `gem install ...` will - have gem installed there, too. Therefore, gems installed without a - `--path ...` setting will show up by calling `gem list`. Accordingly, gems - installed to other locations will not get listed. - - This option is deprecated in favor of the `path` setting. + This has the same effect as using `lockfile false` in the Gemfile. + See gemfile(5) for more information. * `--quiet`: Do not print progress information to the standard output. @@ -141,54 +92,27 @@ automatically and that requires `bundler` to silently remember them. Since * `--retry=[<number>]`: Retry failed network or git requests for <number> times. -* `--shebang=<ruby-executable>`: - Uses the specified ruby executable (usually `ruby`) to execute the scripts - created with `--binstubs`. In addition, if you use `--binstubs` together with - `--shebang jruby` these executables will be changed to execute `jruby` - instead. - - This option is deprecated in favor of the `shebang` setting. - * `--standalone[=<list>]`: Makes a bundle that can work without depending on Rubygems or Bundler at - runtime. A space separated list of groups to install has to be specified. + runtime. A space separated list of groups to install can be specified. Bundler creates a directory named `bundle` and installs the bundle there. It also generates a `bundle/bundler/setup.rb` file to replace Bundler's own setup - in the manner required. Using this option implicitly sets `path`, which is a - [remembered option][REMEMBERED OPTIONS]. - -* `--system`: - Installs the gems specified in the bundle to the system's Rubygems location. - This overrides any previous configuration of `--path`. + in the manner required. - This option is deprecated in favor of the `system` setting. - -* `--trust-policy=[<policy>]`: +* `--trust-policy=TRUST-POLICY`: Apply the Rubygems security policy <policy>, where policy is one of `HighSecurity`, `MediumSecurity`, `LowSecurity`, `AlmostNoSecurity`, or `NoSecurity`. For more details, please see the Rubygems signing documentation linked below in [SEE ALSO][]. -* `--with=<list>`: - A space-separated list of groups referencing gems to install. If an - optional group is given it is installed. If a group is given that is - in the remembered list of groups given to --without, it is removed - from that list. - - This option is deprecated in favor of the `with` setting. - -* `--without=<list>`: - A space-separated list of groups referencing gems to skip during installation. - If a group is given that is in the remembered list of groups given - to --with, it is removed from that list. - - This option is deprecated in favor of the `without` setting. +* `--target-rbconfig=TARGET-RBCONFIG`: + Path to rbconfig.rb for the deployment target platform. ## DEPLOYMENT MODE Bundler's defaults are optimized for development. To switch to -defaults optimized for deployment and for CI, use the `--deployment` -flag. Do not activate deployment mode on development machines, as it +defaults optimized for deployment and for CI, use the `deployment` +setting. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified. 1. A `Gemfile.lock` is required. @@ -220,9 +144,9 @@ will cause an error when the Gemfile(5) is modified. gems to the system, or the web server may not have permission to read them. - As a result, `bundle install --deployment` installs gems to - the `vendor/bundle` directory in the application. This may be - overridden using the `--path` option. + As a result, when `deployment` is configured, `bundle install` installs gems + to the `vendor/bundle` directory in the application. This may be + overridden using the `path` setting. ## INSTALLING GROUPS @@ -230,12 +154,12 @@ By default, `bundle install` will install all gems in all groups in your Gemfile(5), except those declared for a different platform. However, you can explicitly tell Bundler to skip installing -certain groups with the `--without` option. This option takes +certain groups with the `without` setting. This setting takes a space-separated list of groups. -While the `--without` option will skip _installing_ the gems in the -specified groups, it will still _download_ those gems and use them to -resolve the dependencies of every gem in your Gemfile(5). +While the `without` setting will skip _installing_ the gems in the +specified groups, `bundle install` will still _download_ those gems and use them +to resolve the dependencies of every gem in your Gemfile(5). This is so that installing a different set of groups on another machine (such as a production server) will not change the @@ -261,7 +185,7 @@ For a simple illustration, consider the following Gemfile(5): In this case, `sinatra` depends on any version of Rack (`>= 1.0`), while `rack-perftools-profiler` depends on 1.x (`~> 1.0`). -When you run `bundle install --without production` in development, we +When you configure `bundle config without production` in development, we look at the dependencies of `rack-perftools-profiler` as well. That way, you do not spend all your time developing against Rack 2.0, using new APIs unavailable in Rack 1.x, only to have Bundler switch to Rack 1.2 diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1 new file mode 100644 index 0000000000..394d6c5469 --- /dev/null +++ b/lib/bundler/man/bundle-issue.1 @@ -0,0 +1,45 @@ +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-ISSUE" "1" "September 2025" "" +.SH "NAME" +\fBbundle\-issue\fR \- Get help reporting Bundler issues +.SH "SYNOPSIS" +\fBbundle issue\fR +.SH "DESCRIPTION" +Provides guidance on reporting Bundler issues and outputs detailed system information that should be included when filing a bug report\. This command: +.IP "1." 4 +Displays links to troubleshooting resources +.IP "2." 4 +Shows instructions for reporting issues +.IP "3." 4 +Outputs comprehensive environment information needed for debugging +.IP "" 0 +.P +The command helps ensure that bug reports include all necessary system details for effective troubleshooting\. +.SH "OUTPUT" +The command outputs several sections: +.IP "\(bu" 4 +Troubleshooting links and resources +.IP "\(bu" 4 +Link to the GitHub issue template +.IP "\(bu" 4 +Environment information including: Bundler version and platforms, Ruby version and configuration, RubyGems version and paths, Development tool versions (Git, RVM, rbenv, chruby) +.IP "\(bu" 4 +Bundler build metadata +.IP "\(bu" 4 +Current Bundler settings +.IP "\(bu" 4 +Bundle Doctor output +.IP "" 0 +.SH "EXAMPLES" +Get issue reporting information: +.IP "" 4 +.nf +$ bundle issue +.fi +.IP "" 0 +.SH "SEE ALSO" +.IP "\(bu" 4 +bundle\-doctor(1) +.IP "" 0 + diff --git a/lib/bundler/man/bundle-issue.1.ronn b/lib/bundler/man/bundle-issue.1.ronn new file mode 100644 index 0000000000..37f676a354 --- /dev/null +++ b/lib/bundler/man/bundle-issue.1.ronn @@ -0,0 +1,37 @@ +bundle-issue(1) -- Get help reporting Bundler issues +==================================================== + +## SYNOPSIS + +`bundle issue` + +## DESCRIPTION + +Provides guidance on reporting Bundler issues and outputs detailed system information that should be included when filing a bug report. This command: + +1. Displays links to troubleshooting resources +2. Shows instructions for reporting issues +3. Outputs comprehensive environment information needed for debugging + +The command helps ensure that bug reports include all necessary system details for effective troubleshooting. + +## OUTPUT + +The command outputs several sections: + +* Troubleshooting links and resources +* Link to the GitHub issue template +* Environment information including: Bundler version and platforms, Ruby version and configuration, RubyGems version and paths, Development tool versions (Git, RVM, rbenv, chruby) +* Bundler build metadata +* Current Bundler settings +* Bundle Doctor output + +## EXAMPLES + +Get issue reporting information: + + $ bundle issue + +## SEE ALSO + +* bundle-doctor(1) diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1 new file mode 100644 index 0000000000..2931e42dd7 --- /dev/null +++ b/lib/bundler/man/bundle-licenses.1 @@ -0,0 +1,9 @@ +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-LICENSES" "1" "September 2025" "" +.SH "NAME" +\fBbundle\-licenses\fR \- Print the license of all gems in the bundle +.SH "SYNOPSIS" +\fBbundle licenses\fR +.SH "DESCRIPTION" +Prints the license of all gems in the bundle\. diff --git a/lib/bundler/man/bundle-licenses.1.ronn b/lib/bundler/man/bundle-licenses.1.ronn new file mode 100644 index 0000000000..91caba6c2a --- /dev/null +++ b/lib/bundler/man/bundle-licenses.1.ronn @@ -0,0 +1,10 @@ +bundle-licenses(1) -- Print the license of all gems in the bundle +================================================================= + +## SYNOPSIS + +`bundle licenses` + +## DESCRIPTION + +Prints the license of all gems in the bundle. diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 41d9de5e8c..d8bcf20585 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LIST" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-LIST" "1" "September 2025" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" @@ -19,6 +19,8 @@ bundle list \-\-without\-group test bundle list \-\-only\-group dev .P bundle list \-\-only\-group dev test \-\-paths +.P +bundle list \-\-format json .SH "OPTIONS" .TP \fB\-\-name\-only\fR @@ -32,4 +34,7 @@ A space\-separated list of groups of gems to skip during printing\. .TP \fB\-\-only\-group=<list>\fR A space\-separated list of groups of gems to print\. +.TP +\fB\-\-format=FORMAT\fR +Format output ('json' is the only supported format) diff --git a/lib/bundler/man/bundle-list.1.ronn b/lib/bundler/man/bundle-list.1.ronn index dc058ecd5f..9ec2b13282 100644 --- a/lib/bundler/man/bundle-list.1.ronn +++ b/lib/bundler/man/bundle-list.1.ronn @@ -1,5 +1,5 @@ bundle-list(1) -- List all the gems in the bundle -========================================================================= +================================================= ## SYNOPSIS @@ -21,13 +21,21 @@ bundle list --only-group dev bundle list --only-group dev test --paths +bundle list --format json + ## OPTIONS * `--name-only`: Print only the name of each gem. + * `--paths`: Print the path to each gem in the bundle. + * `--without-group=<list>`: A space-separated list of groups of gems to skip during printing. + * `--only-group=<list>`: A space-separated list of groups of gems to print. + +* `--format=FORMAT`: + Format output ('json' is the only supported format) diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 2fa3dbaebb..478d173535 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,35 +1,47 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-LOCK" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-LOCK" "1" "September 2025" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" -\fBbundle lock\fR [\-\-update] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-add\-platform] [\-\-remove\-platform] [\-\-patch] [\-\-minor] [\-\-major] [\-\-strict] [\-\-conservative] +\fBbundle lock\fR [\-\-update] [\-\-bundler[=BUNDLER]] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-add\-checksums] [\-\-add\-platform] [\-\-remove\-platform] [\-\-normalize\-platforms] [\-\-patch] [\-\-minor] [\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] .SH "DESCRIPTION" Lock the gems specified in Gemfile\. .SH "OPTIONS" .TP -\fB\-\-update=<*gems>\fR +\fB\-\-update[=<list>]\fR Ignores the existing lockfile\. Resolve then updates lockfile\. Taking a list of gems or updating all gems if no list is given\. .TP +\fB\-\-bundler[=BUNDLER]\fR +Update the locked version of bundler to the given version or the latest version if no version is given\. +.TP \fB\-\-local\fR Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\. .TP \fB\-\-print\fR Prints the lockfile to STDOUT instead of writing to the file system\. .TP -\fB\-\-lockfile=<path>\fR +\fB\-\-lockfile=LOCKFILE\fR The path where the lockfile should be written to\. .TP \fB\-\-full\-index\fR Fall back to using the single\-file index of all gems\. .TP -\fB\-\-add\-platform\fR +\fB\-\-gemfile=GEMFILE\fR +Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. +.TP +\fB\-\-add\-checksums\fR +Add checksums to the lockfile\. +.TP +\fB\-\-add\-platform=<list>\fR Add a new platform to the lockfile, re\-resolving for the addition of that platform\. .TP -\fB\-\-remove\-platform\fR +\fB\-\-remove\-platform=<list>\fR Remove a platform from the lockfile\. .TP +\fB\-\-normalize\-platforms\fR +Normalize lockfile platforms\. +.TP \fB\-\-patch\fR If updating, prefer updating only to next patch version\. .TP @@ -39,6 +51,9 @@ If updating, prefer updating only to next minor version\. \fB\-\-major\fR If updating, prefer updating to next major version (default)\. .TP +\fB\-\-pre\fR +If updating, always choose the highest allowed version, regardless of prerelease status\. +.TP \fB\-\-strict\fR If updating, do not allow any gem to be updated past latest \-\-patch | \-\-minor | \-\-major\. .TP diff --git a/lib/bundler/man/bundle-lock.1.ronn b/lib/bundler/man/bundle-lock.1.ronn index 3aa5920f5a..6d3e63c982 100644 --- a/lib/bundler/man/bundle-lock.1.ronn +++ b/lib/bundler/man/bundle-lock.1.ronn @@ -4,15 +4,20 @@ bundle-lock(1) -- Creates / Updates a lockfile without installing ## SYNOPSIS `bundle lock` [--update] + [--bundler[=BUNDLER]] [--local] [--print] [--lockfile=PATH] [--full-index] + [--gemfile=GEMFILE] + [--add-checksums] [--add-platform] [--remove-platform] + [--normalize-platforms] [--patch] [--minor] [--major] + [--pre] [--strict] [--conservative] @@ -22,10 +27,14 @@ Lock the gems specified in Gemfile. ## OPTIONS -* `--update=<*gems>`: +* `--update[=<list>]`: Ignores the existing lockfile. Resolve then updates lockfile. Taking a list of gems or updating all gems if no list is given. +* `--bundler[=BUNDLER]`: + Update the locked version of bundler to the given version or the latest + version if no version is given. + * `--local`: Do not attempt to connect to `rubygems.org`. Instead, Bundler will use the gems already present in Rubygems' cache or in `vendor/cache`. Note that if a @@ -35,19 +44,28 @@ Lock the gems specified in Gemfile. * `--print`: Prints the lockfile to STDOUT instead of writing to the file system. -* `--lockfile=<path>`: +* `--lockfile=LOCKFILE`: The path where the lockfile should be written to. * `--full-index`: Fall back to using the single-file index of all gems. -* `--add-platform`: +* `--gemfile=GEMFILE`: + Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. + +* `--add-checksums`: + Add checksums to the lockfile. + +* `--add-platform=<list>`: Add a new platform to the lockfile, re-resolving for the addition of that platform. -* `--remove-platform`: +* `--remove-platform=<list>`: Remove a platform from the lockfile. +* `--normalize-platforms`: + Normalize lockfile platforms. + * `--patch`: If updating, prefer updating only to next patch version. @@ -57,6 +75,9 @@ Lock the gems specified in Gemfile. * `--major`: If updating, prefer updating to next major version (default). +* `--pre`: + If updating, always choose the highest allowed version, regardless of prerelease status. + * `--strict`: If updating, do not allow any gem to be updated past latest --patch | --minor | --major. diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index befe20caa5..2f13b1329f 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OPEN" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-OPEN" "1" "September 2025" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" @@ -27,6 +27,6 @@ bundle open 'rack' \-\-path 'README\.md' Will open the README\.md file of the 'rack' gem source in your bundle\. .SH "OPTIONS" .TP -\fB\-\-path\fR +\fB\-\-path[=PATH]\fR Specify GEM source relative path to open\. diff --git a/lib/bundler/man/bundle-open.1.ronn b/lib/bundler/man/bundle-open.1.ronn index a857f3a965..24dbe97e44 100644 --- a/lib/bundler/man/bundle-open.1.ronn +++ b/lib/bundler/man/bundle-open.1.ronn @@ -23,5 +23,6 @@ Will open the source directory for the 'rack' gem in your bundle. Will open the README.md file of the 'rack' gem source in your bundle. ## OPTIONS -* `--path`: + +* `--path[=PATH]`: Specify GEM source relative path to open. diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index 4028aa943a..7e10d202be 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,10 +1,10 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-OUTDATED" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-OUTDATED" "1" "September 2025" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" -\fBbundle outdated\fR [GEM] [\-\-local] [\-\-pre] [\-\-source] [\-\-strict] [\-\-parseable | \-\-porcelain] [\-\-group=GROUP] [\-\-groups] [\-\-patch|\-\-minor|\-\-major] [\-\-filter\-major] [\-\-filter\-minor] [\-\-filter\-patch] [\-\-only\-explicit] +\fBbundle outdated\fR [GEM] [\-\-local] [\-\-pre] [\-\-source] [\-\-filter\-strict | \-\-strict] [\-\-update\-strict] [\-\-parseable | \-\-porcelain] [\-\-group=GROUP] [\-\-groups] [\-\-patch|\-\-minor|\-\-major] [\-\-filter\-major] [\-\-filter\-minor] [\-\-filter\-patch] [\-\-only\-explicit] .SH "DESCRIPTION" Outdated lists the names and versions of gems that have a newer version available in the given source\. Calling outdated with [GEM [GEM]] will only check for newer versions of the given gems\. Prerelease gems are ignored by default\. If your gems are up to date, Bundler will exit with a status of 0\. Otherwise, it will exit 1\. .SH "OPTIONS" @@ -15,16 +15,19 @@ Do not attempt to fetch gems remotely and use the gem cache instead\. \fB\-\-pre\fR Check for newer pre\-release gems\. .TP -\fB\-\-source\fR +\fB\-\-source=<list>\fR Check against a specific source\. .TP -\fB\-\-strict\fR +\fB\-\-filter\-strict\fR, \fB\-\-strict\fR Only list newer versions allowed by your Gemfile requirements, also respecting conservative update flags (\-\-patch, \-\-minor, \-\-major)\. .TP +\fB\-\-update\-strict\fR +Strict conservative resolution, do not allow any gem to be updated past latest \-\-patch | \-\-minor | \-\-major\. +.TP \fB\-\-parseable\fR, \fB\-\-porcelain\fR Use minimal formatting for more parseable output\. .TP -\fB\-\-group\fR +\fB\-\-group=GROUP\fR List gems from a specific group\. .TP \fB\-\-groups\fR diff --git a/lib/bundler/man/bundle-outdated.1.ronn b/lib/bundler/man/bundle-outdated.1.ronn index 4ac65d0532..6f67a31977 100644 --- a/lib/bundler/man/bundle-outdated.1.ronn +++ b/lib/bundler/man/bundle-outdated.1.ronn @@ -6,7 +6,8 @@ bundle-outdated(1) -- List installed gems with newer versions available `bundle outdated` [GEM] [--local] [--pre] [--source] - [--strict] + [--filter-strict | --strict] + [--update-strict] [--parseable | --porcelain] [--group=GROUP] [--groups] @@ -31,16 +32,19 @@ are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1. * `--pre`: Check for newer pre-release gems. -* `--source`: +* `--source=<list>`: Check against a specific source. -* `--strict`: +* `--filter-strict`, `--strict`: Only list newer versions allowed by your Gemfile requirements, also respecting conservative update flags (--patch, --minor, --major). +* `--update-strict`: + Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major. + * `--parseable`, `--porcelain`: Use minimal formatting for more parseable output. -* `--group`: +* `--group=GROUP`: List gems from a specific group. * `--groups`: diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index 9436800771..6a3a08c3a9 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLATFORM" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-PLATFORM" "1" "September 2025" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index ffaac44558..25da562475 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,12 +1,12 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PLUGIN" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-PLUGIN" "1" "September 2025" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" -\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR] +\fBbundle plugin\fR install PLUGINS [\-\-source=SOURCE] [\-\-version=VERSION] [\-\-git=GIT] [\-\-branch=BRANCH|\-\-ref=REF] [\-\-path=PATH] .br -\fBbundle plugin\fR uninstall PLUGINS +\fBbundle plugin\fR uninstall PLUGINS [\-\-all] .br \fBbundle plugin\fR list .br @@ -16,18 +16,23 @@ You can install, uninstall, and list plugin(s) with this command to extend funct .SH "SUB\-COMMANDS" .SS "install" Install the given plugin(s)\. +.P +For example, \fBbundle plugin install bundler\-graph\fR will install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. Note that the global source specified in Gemfile is ignored\. +.P +\fBOPTIONS\fR .TP -\fBbundle plugin install bundler\-graph\fR -Install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. The global source, specified in source in Gemfile is ignored\. -.TP -\fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR -Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\. +\fB\-\-source=SOURCE\fR +Install the plugin gem from a specific source, rather than from globally configured sources\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR .TP -\fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR -You can specify the version of the gem via \fB\-\-version\fR\. +\fB\-\-version=VERSION\fR +Specify a version of the plugin gem to install via \fB\-\-version\fR\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR .TP -\fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR -Install bundler\-graph gem from Git repository\. You can use standard Git URLs like: +\fB\-\-git=GIT\fR +Install the plugin gem from a Git repository\. You can use standard Git URLs like: .IP \fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR .br @@ -37,12 +42,25 @@ Install bundler\-graph gem from Git repository\. You can use standard Git URLs l .br \fBfile:///path/to/repo\fR .IP -When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. +Example: \fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR .TP -\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR -Install bundler\-graph gem from a local path\. +\fB\-\-branch=BRANCH\fR +When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR to use\. +.TP +\fB\-\-ref=REF\fR +When you specify \fB\-\-git\fR, you can use \fB\-\-ref\fR to specify any tag, or commit hash (revision) to use\. +.TP +\fB\-\-path=PATH\fR +Install the plugin gem from a local path\. +.IP +Example: \fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR .SS "uninstall" Uninstall the plugin(s) specified in PLUGINS\. +.P +\fBOPTIONS\fR +.TP +\fB\-\-all\fR +Uninstall all the installed plugins\. If no plugin is installed, then it does nothing\. .SS "list" List the installed plugins and available commands\. .P diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn index b0a34660ea..b54e0c08b4 100644 --- a/lib/bundler/man/bundle-plugin.1.ronn +++ b/lib/bundler/man/bundle-plugin.1.ronn @@ -3,10 +3,10 @@ bundle-plugin(1) -- Manage Bundler plugins ## SYNOPSIS -`bundle plugin` install PLUGINS [--source=<SOURCE>] [--version=<version>] - [--git=<git-url>] [--branch=<branch>|--ref=<rev>] - [--path=<path>]<br> -`bundle plugin` uninstall PLUGINS<br> +`bundle plugin` install PLUGINS [--source=SOURCE] [--version=VERSION] + [--git=GIT] [--branch=BRANCH|--ref=REF] + [--path=PATH]<br> +`bundle plugin` uninstall PLUGINS [--all]<br> `bundle plugin` list<br> `bundle plugin` help [COMMAND] @@ -20,32 +20,53 @@ You can install, uninstall, and list plugin(s) with this command to extend funct Install the given plugin(s). -* `bundle plugin install bundler-graph`: - Install bundler-graph gem from globally configured sources (defaults to RubyGems.org). The global source, specified in source in Gemfile is ignored. +For example, `bundle plugin install bundler-graph` will install bundler-graph +gem from globally configured sources (defaults to RubyGems.org). Note that the +global source specified in Gemfile is ignored. -* `bundle plugin install bundler-graph --source https://example.com`: - Install bundler-graph gem from example.com. The global source, specified in source in Gemfile is not considered. +**OPTIONS** -* `bundle plugin install bundler-graph --version 0.2.1`: - You can specify the version of the gem via `--version`. +* `--source=SOURCE`: + Install the plugin gem from a specific source, rather than from globally configured sources. -* `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph`: - Install bundler-graph gem from Git repository. You can use standard Git URLs like: + Example: `bundle plugin install bundler-graph --source https://example.com` + +* `--version=VERSION`: + Specify a version of the plugin gem to install via `--version`. + + Example: `bundle plugin install bundler-graph --version 0.2.1` + +* `--git=GIT`: + Install the plugin gem from a Git repository. You can use standard Git URLs like: `ssh://[user@]host.xz[:port]/path/to/repo.git`<br> `http[s]://host.xz[:port]/path/to/repo.git`<br> `/path/to/repo`<br> `file:///path/to/repo` - When you specify `--git`, you can use `--branch` or `--ref` to specify any branch, tag, or commit hash (revision) to use. + Example: `bundle plugin install bundler-graph --git https://github.com/rubygems/bundler-graph` + +* `--branch=BRANCH`: + When you specify `--git`, you can use `--branch` to use. -* `bundle plugin install bundler-graph --path ../bundler-graph`: - Install bundler-graph gem from a local path. +* `--ref=REF`: + When you specify `--git`, you can use `--ref` to specify any tag, or commit + hash (revision) to use. + +* `--path=PATH`: + Install the plugin gem from a local path. + + Example: `bundle plugin install bundler-graph --path ../bundler-graph` ### uninstall Uninstall the plugin(s) specified in PLUGINS. +**OPTIONS** + +* `--all`: + Uninstall all the installed plugins. If no plugin is installed, then it does nothing. + ### list List the installed plugins and available commands. diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 91deb5c72b..a8316b5cca 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-PRISTINE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-PRISTINE" "1" "September 2025" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-pristine.1.ronn b/lib/bundler/man/bundle-pristine.1.ronn index e2d6b6a348..984debeb3d 100644 --- a/lib/bundler/man/bundle-pristine.1.ronn +++ b/lib/bundler/man/bundle-pristine.1.ronn @@ -1,5 +1,5 @@ bundle-pristine(1) -- Restores installed gems to their pristine condition -=========================================================================== +========================================================================= ## SYNOPSIS diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index eaf3e624c9..4dc7a03b9f 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,21 +1,15 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-REMOVE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-REMOVE" "1" "September 2025" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" -\fBbundle remove [GEM [GEM \|\.\|\.\|\.]] [\-\-install]\fR +`bundle remove [GEM [GEM \|\.\|\.\|\.]] .SH "DESCRIPTION" Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\. -.SH "OPTIONS" -.TP -\fB\-\-install\fR -Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\. .P Example: .P bundle remove rails .P bundle remove rails rack -.P -bundle remove rails rack \-\-install diff --git a/lib/bundler/man/bundle-remove.1.ronn b/lib/bundler/man/bundle-remove.1.ronn index 40a239b4a2..49cb4dc1fd 100644 --- a/lib/bundler/man/bundle-remove.1.ronn +++ b/lib/bundler/man/bundle-remove.1.ronn @@ -1,23 +1,16 @@ bundle-remove(1) -- Removes gems from the Gemfile -=========================================================================== +================================================= ## SYNOPSIS -`bundle remove [GEM [GEM ...]] [--install]` +`bundle remove [GEM [GEM ...]] ## DESCRIPTION Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid. If a gem cannot be removed, a warning is printed. If a gem is already absent from the Gemfile, and error is raised. -## OPTIONS - -* `--install`: - Runs `bundle install` after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s). - Example: bundle remove rails bundle remove rails rack - -bundle remove rails rack --install diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 3e48ca67f1..901460962c 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-SHOW" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-SHOW" "1" "September 2025" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 55cd40bfe4..2f9932dc1e 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,10 +1,10 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-UPDATE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-UPDATE" "1" "September 2025" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" -\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-jobs=JOBS] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-redownload] [\-\-strict] [\-\-conservative] +\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] .SH "DESCRIPTION" Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\. .P @@ -14,10 +14,10 @@ You would use \fBbundle update\fR to explicitly update the version of a gem\. \fB\-\-all\fR Update all gems specified in Gemfile\. .TP -\fB\-\-group=<name>\fR, \fB\-g=[<name>]\fR +\fB\-\-group=<list>\fR, \fB\-g=<list>\fR Only update the gems in the specified group\. For instance, you can update all gems in the development group with \fBbundle update \-\-group development\fR\. You can also call \fBbundle update rails \-\-group test\fR to update the rails gem and all gems in the test group, for example\. .TP -\fB\-\-source=<name>\fR +\fB\-\-source=<list>\fR The name of a \fB:git\fR or \fB:path\fR source used in the Gemfile(5)\. For instance, with a \fB:git\fR source of \fBhttp://github\.com/rails/rails\.git\fR, you would call \fBbundle update \-\-source rails\fR .TP \fB\-\-local\fR @@ -26,13 +26,19 @@ Do not attempt to fetch gems remotely and use the gem cache instead\. \fB\-\-ruby\fR Update the locked version of Ruby to the current version of Ruby\. .TP -\fB\-\-bundler\fR +\fB\-\-bundler[=BUNDLER]\fR Update the locked version of bundler to the invoked bundler version\. .TP +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. +.TP \fB\-\-full\-index\fR Fall back to using the single\-file index of all gems\. .TP -\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR +\fB\-\-gemfile=GEMFILE\fR +Use the specified gemfile instead of [\fBGemfile(5)\fR][Gemfile(5)]\. +.TP +\fB\-\-jobs=<number>\fR, \fB\-j=<number>\fR Specify the number of jobs to run in parallel\. The default is the number of available processors\. .TP \fB\-\-retry=[<number>]\fR @@ -41,9 +47,6 @@ Retry failed network or git requests for \fInumber\fR times\. \fB\-\-quiet\fR Only output warnings and errors\. .TP -\fB\-\-redownload\fR -Force downloading every gem\. -.TP \fB\-\-patch\fR Prefer updating only to next patch version\. .TP @@ -53,6 +56,9 @@ Prefer updating only to next minor version\. \fB\-\-major\fR Prefer updating to next major version (default)\. .TP +\fB\-\-pre\fR +Always choose the highest allowed version, regardless of prerelease status\. +.TP \fB\-\-strict\fR Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\. .TP diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn index fe500cdc96..bfe381677c 100644 --- a/lib/bundler/man/bundle-update.1.ronn +++ b/lib/bundler/man/bundle-update.1.ronn @@ -9,11 +9,13 @@ bundle-update(1) -- Update your gems to the latest available versions [--local] [--ruby] [--bundler[=VERSION]] + [--force] [--full-index] - [--jobs=JOBS] + [--gemfile=GEMFILE] + [--jobs=NUMBER] [--quiet] [--patch|--minor|--major] - [--redownload] + [--pre] [--strict] [--conservative] @@ -32,13 +34,13 @@ gem. * `--all`: Update all gems specified in Gemfile. -* `--group=<name>`, `-g=[<name>]`: +* `--group=<list>`, `-g=<list>`: Only update the gems in the specified group. For instance, you can update all gems in the development group with `bundle update --group development`. You can also call `bundle update rails --group test` to update the rails gem and all gems in the test group, for example. -* `--source=<name>`: +* `--source=<list>`: The name of a `:git` or `:path` source used in the Gemfile(5). For instance, with a `:git` source of `http://github.com/rails/rails.git`, you would call `bundle update --source rails` @@ -49,13 +51,19 @@ gem. * `--ruby`: Update the locked version of Ruby to the current version of Ruby. -* `--bundler`: +* `--bundler[=BUNDLER]`: Update the locked version of bundler to the invoked bundler version. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. + * `--full-index`: Fall back to using the single-file index of all gems. -* `--jobs=[<number>]`, `-j[<number>]`: +* `--gemfile=GEMFILE`: + Use the specified gemfile instead of [`Gemfile(5)`][Gemfile(5)]. + +* `--jobs=<number>`, `-j=<number>`: Specify the number of jobs to run in parallel. The default is the number of available processors. @@ -65,9 +73,6 @@ gem. * `--quiet`: Only output warnings and errors. -* `--redownload`: - Force downloading every gem. - * `--patch`: Prefer updating only to next patch version. @@ -77,6 +82,9 @@ gem. * `--major`: Prefer updating to next major version (default). +* `--pre`: + Always choose the highest allowed version, regardless of prerelease status. + * `--strict`: Do not allow any gem to be updated past latest `--patch` | `--minor` | `--major`. diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 003931cb47..6462ec7958 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VERSION" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE\-VERSION" "1" "September 2025" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-viz.1 b/lib/bundler/man/bundle-viz.1 deleted file mode 100644 index 2848ba1877..0000000000 --- a/lib/bundler/man/bundle-viz.1 +++ /dev/null @@ -1,30 +0,0 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE\-VIZ" "1" "October 2024" "" -.SH "NAME" -\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile -.SH "SYNOPSIS" -\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP] -.SH "DESCRIPTION" -\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\. -.P -The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\. -.P -\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\. -.SH "OPTIONS" -.TP -\fB\-\-file\fR, \fB\-f\fR -The name to use for the generated file\. See \fB\-\-format\fR option -.TP -\fB\-\-format\fR, \fB\-F\fR -This is output format option\. Supported format is png, jpg, svg, dot \|\.\|\.\|\. -.TP -\fB\-\-requirements\fR, \fB\-R\fR -Set to show the version of each required dependency\. -.TP -\fB\-\-version\fR, \fB\-v\fR -Set to show each gem version\. -.TP -\fB\-\-without\fR, \fB\-W\fR -Exclude gems that are part of the specified named group\. - diff --git a/lib/bundler/man/bundle-viz.1.ronn b/lib/bundler/man/bundle-viz.1.ronn deleted file mode 100644 index f220256943..0000000000 --- a/lib/bundler/man/bundle-viz.1.ronn +++ /dev/null @@ -1,32 +0,0 @@ -bundle-viz(1) -- Generates a visual dependency graph for your Gemfile -===================================================================== - -## SYNOPSIS - -`bundle viz` [--file=FILE] - [--format=FORMAT] - [--requirements] - [--version] - [--without=GROUP GROUP] - -## DESCRIPTION - -`viz` generates a PNG file of the current `Gemfile(5)` as a dependency graph. -`viz` requires the ruby-graphviz gem (and its dependencies). - -The associated gems must also be installed via [`bundle install(1)`](bundle-install.1.html). - -`viz` command was deprecated in Bundler 2.2. Use [bundler-graph plugin](https://github.com/rubygems/bundler-graph) instead. - -## OPTIONS - -* `--file`, `-f`: - The name to use for the generated file. See `--format` option -* `--format`, `-F`: - This is output format option. Supported format is png, jpg, svg, dot ... -* `--requirements`, `-R`: - Set to show the version of each required dependency. -* `--version`, `-v`: - Set to show each gem version. -* `--without`, `-W`: - Exclude gems that are part of the specified named group. diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 688c29961a..9f7feb4133 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "BUNDLE" "1" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "BUNDLE" "1" "September 2025" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" @@ -66,9 +66,6 @@ Open an installed gem in the editor \fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR Generate a lockfile for your dependencies .TP -\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR (deprecated) -Generate a visual representation of your dependencies -.TP \fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR Generate a simple \fBGemfile\fR, placed in the current directory .TP @@ -94,9 +91,3 @@ Manage Bundler plugins Prints Bundler version information .SH "PLUGINS" When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-<command>\fR and execute it, passing down any extra arguments to it\. -.SH "OBSOLETE" -These commands are obsolete and should no longer be used: -.IP "\(bu" 4 -\fBbundle inject(1)\fR -.IP "" 0 - diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn index 8245effabd..1c2b3df7af 100644 --- a/lib/bundler/man/bundle.1.ronn +++ b/lib/bundler/man/bundle.1.ronn @@ -76,9 +76,6 @@ We divide `bundle` subcommands into primary commands and utilities: * [`bundle lock(1)`](bundle-lock.1.html): Generate a lockfile for your dependencies -* [`bundle viz(1)`](bundle-viz.1.html) (deprecated): - Generate a visual representation of your dependencies - * [`bundle init(1)`](bundle-init.1.html): Generate a simple `Gemfile`, placed in the current directory @@ -108,9 +105,3 @@ We divide `bundle` subcommands into primary commands and utilities: When running a command that isn't listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named `bundler-<command>` and execute it, passing down any extra arguments to it. - -## OBSOLETE - -These commands are obsolete and should no longer be used: - -* `bundle inject(1)` diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index dcf4b34c5f..a8c055a0c1 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ -.\" generated with nRonn/v0.11.1 -.\" https://github.com/n-ronn/nronn/tree/0.11.1 -.TH "GEMFILE" "5" "October 2024" "" +.\" generated with Ronn-NG/v0.10.1 +.\" http://github.com/apjanke/ronn-ng/tree/0.10.1 +.TH "GEMFILE" "5" "September 2025" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" @@ -469,4 +469,35 @@ For implicit gems (dependencies of explicit gems), any source, git, or path repo .IP "3." 4 If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\. .IP "" 0 +.SH "LOCKFILE" +By default, Bundler will create a lockfile by adding \fB\.lock\fR to the end of the Gemfile name\. To change this, use the \fBlockfile\fR method: +.IP "" 4 +.nf +lockfile "/path/to/lockfile\.lock" +.fi +.IP "" 0 +.P +This is useful when you want to use different lockfiles per ruby version or platform\. +.P +To avoid writing a lock file, use \fBfalse\fR as the argument: +.IP "" 4 +.nf +lockfile false +.fi +.IP "" 0 +.P +This is useful for library development and other situations where the code is expected to work with a range of dependency versions\. +.SS "LOCKFILE PRECEDENCE" +When determining path to the lockfile or whether to create a lockfile, the following precedence is used: +.IP "1." 4 +The \fBbundle install\fR \fB\-\-no\-lock\fR option (which disables lockfile creation)\. +.IP "2." 4 +The \fBbundle install\fR \fB\-\-lockfile\fR option\. +.IP "3." 4 +The \fBBUNDLE_LOCKFILE\fR environment variable\. +.IP "4." 4 +The \fBlockfile\fR method in the Gemfile\. +.IP "5." 4 +The default behavior of adding \fB\.lock\fR to the end of the Gemfile name\. +.IP "" 0 diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index 802549737e..18d7bb826e 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -556,3 +556,31 @@ bundler uses the following priority order: If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1.13, so Bundler prints a warning and will abort with an error in the future. + +## LOCKFILE + +By default, Bundler will create a lockfile by adding `.lock` to the end of the +Gemfile name. To change this, use the `lockfile` method: + + lockfile "/path/to/lockfile.lock" + +This is useful when you want to use different lockfiles per ruby version or +platform. + +To avoid writing a lock file, use `false` as the argument: + + lockfile false + +This is useful for library development and other situations where the code is +expected to work with a range of dependency versions. + +### LOCKFILE PRECEDENCE + +When determining path to the lockfile or whether to create a lockfile, the +following precedence is used: + +1. The `bundle install` `--no-lock` option (which disables lockfile creation). +1. The `bundle install` `--lockfile` option. +1. The `BUNDLE_LOCKFILE` environment variable. +1. The `lockfile` method in the Gemfile. +1. The default behavior of adding `.lock` to the end of the Gemfile name. diff --git a/lib/bundler/man/index.txt b/lib/bundler/man/index.txt index 24f7633e66..f610ba852a 100644 --- a/lib/bundler/man/index.txt +++ b/lib/bundler/man/index.txt @@ -8,13 +8,16 @@ bundle-clean(1) bundle-clean.1 bundle-config(1) bundle-config.1 bundle-console(1) bundle-console.1 bundle-doctor(1) bundle-doctor.1 +bundle-env(1) bundle-env.1 bundle-exec(1) bundle-exec.1 +bundle-fund(1) bundle-fund.1 bundle-gem(1) bundle-gem.1 bundle-help(1) bundle-help.1 bundle-info(1) bundle-info.1 bundle-init(1) bundle-init.1 -bundle-inject(1) bundle-inject.1 bundle-install(1) bundle-install.1 +bundle-issue(1) bundle-issue.1 +bundle-licenses(1) bundle-licenses.1 bundle-list(1) bundle-list.1 bundle-lock(1) bundle-lock.1 bundle-open(1) bundle-open.1 @@ -26,4 +29,3 @@ bundle-remove(1) bundle-remove.1 bundle-show(1) bundle-show.1 bundle-update(1) bundle-update.1 bundle-version(1) bundle-version.1 -bundle-viz(1) bundle-viz.1 diff --git a/lib/bundler/match_metadata.rb b/lib/bundler/match_metadata.rb index f6cc27df32..6fd2994a85 100644 --- a/lib/bundler/match_metadata.rb +++ b/lib/bundler/match_metadata.rb @@ -13,5 +13,18 @@ module Bundler def matches_current_rubygems? @required_rubygems_version.satisfied_by?(Gem.rubygems_version) end + + def expanded_dependencies + runtime_dependencies + [ + metadata_dependency("Ruby", @required_ruby_version), + metadata_dependency("RubyGems", @required_rubygems_version), + ].compact + end + + def metadata_dependency(name, requirement) + return if requirement.nil? || requirement.none? + + Gem::Dependency.new("#{name}\0", requirement) + end end end diff --git a/lib/bundler/match_platform.rb b/lib/bundler/match_platform.rb index ece9fb8679..479818e5ec 100644 --- a/lib/bundler/match_platform.rb +++ b/lib/bundler/match_platform.rb @@ -1,23 +1,42 @@ # frozen_string_literal: true -require_relative "gem_helpers" - module Bundler module MatchPlatform - include GemHelpers + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform - def match_platform(p) - MatchPlatform.platforms_match?(platform, p) + false end - def self.platforms_match?(gemspec_platform, local_platform) - return true if gemspec_platform.nil? - return true if gemspec_platform == Gem::Platform::RUBY - return true if local_platform == gemspec_platform - gemspec_platform = Gem::Platform.new(gemspec_platform) - return true if gemspec_platform === local_platform + def self.select_best_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = select_all_platform_match(specs, platform, force_ruby: force_ruby, prefer_locked: prefer_locked) - false + Gem::Platform.sort_and_filter_best_platform_match(matching, platform) + end + + def self.select_best_local_platform_match(specs, force_ruby: false) + local = Bundler.local_platform + matching = select_all_platform_match(specs, local, force_ruby: force_ruby).filter_map(&:materialized_for_installation) + + Gem::Platform.sort_best_platform_match(matching, local) + end + + def self.select_all_platform_match(specs, platform, force_ruby: false, prefer_locked: false) + matching = specs.select {|spec| spec.installable_on_platform?(force_ruby ? Gem::Platform::RUBY : platform) } + + specs.each(&:force_ruby_platform!) if force_ruby + + if prefer_locked + locked_originally = matching.select {|spec| spec.is_a?(::Bundler::LazySpecification) } + return locked_originally if locked_originally.any? + end + + matching + end + + def self.generic_local_platform_is_ruby? + Bundler.generic_local_platform == Gem::Platform::RUBY end end end diff --git a/lib/bundler/materialization.rb b/lib/bundler/materialization.rb new file mode 100644 index 0000000000..82e48464a7 --- /dev/null +++ b/lib/bundler/materialization.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +module Bundler + # + # This class materializes a set of resolved specifications (`LazySpecification`) + # for a given gem into the most appropriate real specifications + # (`StubSepecification`, `EndpointSpecification`, etc), given a dependency and a + # target platform. + # + class Materialization + def initialize(dep, platform, candidates:) + @dep = dep + @platform = platform + @candidates = candidates + end + + def complete? + specs.any? + end + + def specs + @specs ||= if @candidates.nil? + [] + elsif platform + MatchPlatform.select_best_platform_match(@candidates, platform, force_ruby: dep.force_ruby_platform) + else + MatchPlatform.select_best_local_platform_match(@candidates, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) + end + end + + def dependencies + (materialized_spec || specs.first).runtime_dependencies.map {|d| [d, platform] } + end + + def materialized_spec + specs.reject(&:missing?).first&.materialization + end + + def completely_missing_specs + return [] unless specs.all?(&:missing?) + + specs + end + + def partially_missing_specs + specs.select(&:missing?) + end + + def incomplete_specs + return [] if complete? + + @candidates || LazySpecification.new(dep.name, nil, nil) + end + + private + + attr_reader :dep, :platform + end +end diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 7790a3f6f8..fd6da6cf6d 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -195,7 +195,7 @@ module Bundler @sources[name] end - # @param [Hash] The options that are present in the lock file + # @param [Hash] The options that are present in the lockfile # @return [API::Source] the instance of the class that handles the source # type passed in locked_opts def from_lock(locked_opts) @@ -220,7 +220,7 @@ module Bundler # # @param [String] event def hook(event, *args, &arg_blk) - return unless Bundler.feature_flag.plugins? + return unless Bundler.settings[:plugins] unless Events.defined_event?(event) raise ArgumentError, "Event '#{event}' not defined in Bundler::Plugin::Events" end diff --git a/lib/bundler/plugin/api/source.rb b/lib/bundler/plugin/api/source.rb index 690f379389..6c888d0373 100644 --- a/lib/bundler/plugin/api/source.rb +++ b/lib/bundler/plugin/api/source.rb @@ -67,7 +67,7 @@ module Bundler # to check out same version of gem later. # # There options are passed when the source plugin is created from the - # lock file. + # lockfile. # # @return [Hash] def options_to_lock diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index c2ab8f90da..0682d37772 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -31,9 +31,13 @@ module Bundler begin load_index(global_index_file, true) - rescue GenericSystemCallError + rescue PermissionError # no need to fail when on a read-only FS, for example nil + rescue ArgumentError => e + # ruby 3.4 checks writability in Dir.tmpdir + raise unless e.message&.include?("could not find a temporary directory") + nil end load_index(local_index_file) if SharedHelpers.in_bundle? end diff --git a/lib/bundler/plugin/installer.rb b/lib/bundler/plugin/installer.rb index 4f60862bb4..853ad9edca 100644 --- a/lib/bundler/plugin/installer.rb +++ b/lib/bundler/plugin/installer.rb @@ -34,7 +34,7 @@ module Bundler # @return [Hash] map of names to their specs they are installed with def install_definition(definition) def definition.lock(*); end - definition.resolve_remotely! + definition.remotely! specs = definition.specs install_from_specs specs @@ -43,16 +43,6 @@ module Bundler private def check_sources_consistency!(options) - if options.key?(:git) && options.key?(:local_git) - raise InvalidOption, "Remote and local plugin git sources can't be both specified" - end - - # back-compat; local_git is an alias for git - if options.key?(:local_git) - Bundler::SharedHelpers.major_deprecation(2, "--local_git is deprecated, use --git") - options[:git] = options.delete(:local_git) - end - if (options.keys & [:source, :git, :path]).length > 1 raise InvalidOption, "Only one of --source, --git, or --path may be specified" end diff --git a/lib/bundler/plugin/installer/path.rb b/lib/bundler/plugin/installer/path.rb index 58a8fa7426..58c4924eb0 100644 --- a/lib/bundler/plugin/installer/path.rb +++ b/lib/bundler/plugin/installer/path.rb @@ -8,6 +8,14 @@ module Bundler SharedHelpers.in_bundle? ? Bundler.root : Plugin.root end + def eql?(other) + return unless other.class == self.class + expanded_original_path == other.expanded_original_path && + version == other.version + end + + alias_method :==, :eql? + def generate_bin(spec, disable_extensions = false) # Need to find a way without code duplication # For now, we can ignore this diff --git a/lib/bundler/plugin/source_list.rb b/lib/bundler/plugin/source_list.rb index 746996de55..d929ade29e 100644 --- a/lib/bundler/plugin/source_list.rb +++ b/lib/bundler/plugin/source_list.rb @@ -23,7 +23,7 @@ module Bundler private - def rubygems_aggregate_class + def source_class Plugin::Installer::Rubygems end end diff --git a/lib/bundler/process_lock.rb b/lib/bundler/process_lock.rb index 36bb5f768d..784b17e363 100644 --- a/lib/bundler/process_lock.rb +++ b/lib/bundler/process_lock.rb @@ -6,7 +6,7 @@ module Bundler lock_file_path = File.join(bundle_path, "bundler.lock") base_lock_file_path = lock_file_path.delete_suffix(".lock") - require "fileutils" if Bundler.rubygems.provides?("< 3.5.23") + require "fileutils" if Bundler.rubygems.provides?("< 3.6.0") begin SharedHelpers.filesystem_access(lock_file_path, :write) do diff --git a/lib/bundler/remote_specification.rb b/lib/bundler/remote_specification.rb index 9d237f3fa0..ab163e2b04 100644 --- a/lib/bundler/remote_specification.rb +++ b/lib/bundler/remote_specification.rb @@ -12,7 +12,7 @@ module Bundler attr_reader :name, :version, :platform attr_writer :dependencies - attr_accessor :source, :remote + attr_accessor :source, :remote, :locked_platform def initialize(name, version, platform, spec_fetcher) @name = name @@ -21,6 +21,11 @@ module Bundler @platform = Gem::Platform.new(platform) @spec_fetcher = spec_fetcher @dependencies = nil + @locked_platform = nil + end + + def insecurely_materialized? + @locked_platform.to_s != @platform.to_s end # Needed before installs, since the arch matters then and quick diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb index a38b6974f8..1dbf565d46 100644 --- a/lib/bundler/resolver.rb +++ b/lib/bundler/resolver.rb @@ -12,13 +12,13 @@ module Bundler require_relative "resolver/candidate" require_relative "resolver/incompatibility" require_relative "resolver/root" + require_relative "resolver/strategy" - include GemHelpers - - def initialize(base, gem_version_promoter) + def initialize(base, gem_version_promoter, most_specific_locked_platform = nil) @source_requirements = base.source_requirements @base = base @gem_version_promoter = gem_version_promoter + @most_specific_locked_platform = most_specific_locked_platform end def start @@ -77,10 +77,10 @@ module Bundler end def solve_versions(root:, logger:) - solver = PubGrub::VersionSolver.new(source: self, root: root, logger: logger) + solver = PubGrub::VersionSolver.new(source: self, root: root, strategy: Strategy.new(self), logger: logger) result = solver.solve - resolved_specs = result.map {|package, version| version.to_specs(package) }.flatten - resolved_specs |= @base.specs_compatible_with(SpecSet.new(resolved_specs)) + resolved_specs = result.flat_map {|package, version| version.to_specs(package, @most_specific_locked_platform) } + SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs) rescue PubGrub::SolveFailure => e incompatibility = e.incompatibility @@ -165,16 +165,8 @@ module Bundler PubGrub::VersionConstraint.new(package, range: range) end - def versions_for(package, range=VersionRange.any) - versions = select_sorted_versions(package, range) - - # Conditional avoids (among other things) calling - # sort_versions_by_preferred with the root package - if versions.size > 1 - sort_versions_by_preferred(package, versions) - else - versions - end + def versions_for(package, range = VersionRange.any) + range.select_versions(@sorted_versions[package]) end def no_versions_incompatibility_for(package, unsatisfied_term) @@ -236,7 +228,7 @@ module Bundler sorted_versions[high] end - range = PubGrub::VersionRange.new(min: low, max: high, include_min: true) + range = PubGrub::VersionRange.new(min: low, max: high, include_min: !low.nil?) self_constraint = PubGrub::VersionConstraint.new(package, range: range) @@ -279,12 +271,12 @@ module Bundler next groups if platform_specs.all?(&:empty?) end - ruby_specs = select_best_platform_match(specs, Gem::Platform::RUBY) + ruby_specs = MatchPlatform.select_best_platform_match(specs, Gem::Platform::RUBY) ruby_group = Resolver::SpecGroup.new(ruby_specs) unless ruby_group.empty? - platform_specs.each do |specs| - ruby_group.merge(Resolver::SpecGroup.new(specs)) + platform_specs.each do |s| + ruby_group.merge(Resolver::SpecGroup.new(s)) end groups << Resolver::Candidate.new(version, group: ruby_group, priority: -1) @@ -318,6 +310,16 @@ module Bundler "Gemfile" end + def raise_incomplete!(incomplete_specs) + raise_not_found!(@base.get_package(incomplete_specs.first.name)) + end + + def sort_versions_by_preferred(package, versions) + @gem_version_promoter.sort_versions(package, versions) + end + + private + def raise_not_found!(package) name = package.name source = source_for(name) @@ -354,8 +356,6 @@ module Bundler raise GemNotFound, message end - private - def filtered_versions_for(package) @gem_version_promoter.filter_versions(package, @all_versions[package]) end @@ -388,9 +388,18 @@ module Bundler end def filter_remote_specs(specs, package) - return specs unless package.prefer_local? + if package.prefer_local? + local_specs = specs.select {|s| s.is_a?(StubSpecification) } - specs.select {|s| s.is_a?(StubSpecification) } + if local_specs.empty? + package.consider_remote_versions! + specs + else + local_specs + end + else + specs + end end # Ignore versions that depend on themselves incorrectly @@ -404,10 +413,6 @@ module Bundler requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec) end - def sort_versions_by_preferred(package, versions) - @gem_version_promoter.sort_versions(package, versions) - end - def repository_for(package) source_for(package.name) end @@ -417,13 +422,13 @@ module Bundler end def prepare_dependencies(requirements, packages) - to_dependency_hash(requirements, packages).map do |dep_package, dep_constraint| + to_dependency_hash(requirements, packages).filter_map do |dep_package, dep_constraint| name = dep_package.name next [dep_package, dep_constraint] if name == "bundler" dep_range = dep_constraint.range - versions = select_sorted_versions(dep_package, dep_range) + versions = versions_for(dep_package, dep_range) if versions.empty? if dep_package.ignores_prereleases? || dep_package.prefer_local? @all_versions.delete(dep_package) @@ -431,7 +436,7 @@ module Bundler end dep_package.consider_prereleases! if dep_package.ignores_prereleases? dep_package.consider_remote_versions! if dep_package.prefer_local? - versions = select_sorted_versions(dep_package, dep_range) + versions = versions_for(dep_package, dep_range) end if versions.empty? && select_all_versions(dep_package, dep_range).any? @@ -443,11 +448,7 @@ module Bundler next unless dep_package.current_platform? raise_not_found!(dep_package) - end.compact.to_h - end - - def select_sorted_versions(package, range) - range.select_versions(@sorted_versions[package]) + end.to_h end def select_all_versions(package, range) diff --git a/lib/bundler/resolver/base.rb b/lib/bundler/resolver/base.rb index 4c2aed32de..932a92ff41 100644 --- a/lib/bundler/resolver/base.rb +++ b/lib/bundler/resolver/base.rb @@ -5,10 +5,11 @@ require_relative "package" module Bundler class Resolver class Base - attr_reader :packages, :requirements, :source_requirements + attr_reader :packages, :requirements, :source_requirements, :locked_specs def initialize(source_requirements, dependencies, base, platforms, options) @source_requirements = source_requirements + @locked_specs = options[:locked_specs] @base = base @@ -16,7 +17,7 @@ module Bundler hash[name] = Package.new(name, platforms, **options) end - @requirements = dependencies.map do |dep| + @requirements = dependencies.filter_map do |dep| dep_platforms = dep.gem_platforms(platforms) # Dependencies scoped to external platforms are ignored @@ -27,11 +28,7 @@ module Bundler @packages[name] = Package.new(name, dep_platforms, **options.merge(dependency: dep)) dep - end.compact - end - - def specs_compatible_with(result) - @base.specs_compatible_with(result) + end end def [](name) diff --git a/lib/bundler/resolver/candidate.rb b/lib/bundler/resolver/candidate.rb index f593fc5d61..5298b2530f 100644 --- a/lib/bundler/resolver/candidate.rb +++ b/lib/bundler/resolver/candidate.rb @@ -17,7 +17,7 @@ module Bundler # Some candidates may also keep some information explicitly about the # package they refer to. These candidates are referred to as "canonical" and # are used when materializing resolution results back into RubyGems - # specifications that can be installed, written to lock files, and so on. + # specifications that can be installed, written to lockfiles, and so on. # class Candidate include Comparable @@ -34,10 +34,10 @@ module Bundler @spec_group.dependencies end - def to_specs(package) + def to_specs(package, most_specific_locked_platform) return [] if package.meta? - @spec_group.to_specs(package.force_ruby_platform?) + @spec_group.to_specs(package.force_ruby_platform?, most_specific_locked_platform) end def prerelease? @@ -48,35 +48,38 @@ module Bundler @version.segments end - def sort_obj - [@version, @priority] - end - def <=>(other) return unless other.is_a?(self.class) - sort_obj <=> other.sort_obj + version_comparison = version <=> other.version + return version_comparison unless version_comparison.zero? + + priority <=> other.priority end def ==(other) return unless other.is_a?(self.class) - sort_obj == other.sort_obj + version == other.version && priority == other.priority end def eql?(other) return unless other.is_a?(self.class) - sort_obj.eql?(other.sort_obj) + version.eql?(other.version) && priority.eql?(other.priority) end def hash - sort_obj.hash + [@version, @priority].hash end def to_s @version.to_s end + + protected + + attr_reader :priority end end end diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb index 5aecc12d05..3906be3f57 100644 --- a/lib/bundler/resolver/package.rb +++ b/lib/bundler/resolver/package.rb @@ -15,19 +15,24 @@ module Bundler class Package attr_reader :name, :platforms, :dependency, :locked_version - def initialize(name, platforms, locked_specs:, unlock:, prerelease: false, prefer_local: false, dependency: nil) + def initialize(name, platforms, locked_specs:, unlock:, prerelease: false, prefer_local: false, dependency: nil, new_platforms: []) @name = name @platforms = platforms - @locked_version = locked_specs[name].first&.version + @locked_version = locked_specs.version_for(name) @unlock = unlock @dependency = dependency || Dependency.new(name, @locked_version) + @platforms |= [Gem::Platform::RUBY] if @dependency.default_force_ruby_platform @top_level = !dependency.nil? @prerelease = @dependency.prerelease? || @locked_version&.prerelease? || prerelease ? :consider_first : :ignore @prefer_local = prefer_local + @new_platforms = new_platforms end def platform_specs(specs) - platforms.map {|platform| GemHelpers.select_best_platform_match(specs, platform, prefer_locked: !unlock?) } + platforms.map do |platform| + prefer_locked = @new_platforms.include?(platform) ? false : !unlock? + MatchPlatform.select_best_platform_match(specs, platform, prefer_locked: prefer_locked) + end end def to_s @@ -55,7 +60,7 @@ module Bundler end def unlock? - @unlock.empty? || @unlock.include?(name) + @unlock == true || @unlock.include?(name) end def ignores_prereleases? diff --git a/lib/bundler/resolver/spec_group.rb b/lib/bundler/resolver/spec_group.rb index f950df6eda..ac6ba86c4c 100644 --- a/lib/bundler/resolver/spec_group.rb +++ b/lib/bundler/resolver/spec_group.rb @@ -25,10 +25,11 @@ module Bundler @source ||= exemplary_spec.source end - def to_specs(force_ruby_platform) + def to_specs(force_ruby_platform, most_specific_locked_platform) @specs.map do |s| lazy_spec = LazySpecification.from_spec(s) lazy_spec.force_ruby_platform = force_ruby_platform + lazy_spec.most_specific_locked_platform = most_specific_locked_platform lazy_spec end end @@ -38,9 +39,7 @@ module Bundler end def dependencies - @dependencies ||= @specs.map do |spec| - __dependencies(spec) + metadata_dependencies(spec) - end.flatten.uniq.sort + @dependencies ||= @specs.flat_map(&:expanded_dependencies).uniq.sort end def ==(other) @@ -70,28 +69,6 @@ module Bundler def exemplary_spec @specs.first end - - def __dependencies(spec) - dependencies = [] - spec.dependencies.each do |dep| - next if dep.type == :development - dependencies << Dependency.new(dep.name, dep.requirement) - end - dependencies - end - - def metadata_dependencies(spec) - [ - metadata_dependency("Ruby", spec.required_ruby_version), - metadata_dependency("RubyGems", spec.required_rubygems_version), - ].compact - end - - def metadata_dependency(name, requirement) - return if requirement.nil? || requirement.none? - - Dependency.new("#{name}\0", requirement) - end end end end diff --git a/lib/bundler/resolver/strategy.rb b/lib/bundler/resolver/strategy.rb new file mode 100644 index 0000000000..4f343bf0ce --- /dev/null +++ b/lib/bundler/resolver/strategy.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +module Bundler + class Resolver + class Strategy + def initialize(source) + @source = source + end + + def next_package_and_version(unsatisfied) + package, range = next_term_to_try_from(unsatisfied) + + [package, most_preferred_version_of(package, range).first] + end + + private + + def next_term_to_try_from(unsatisfied) + unsatisfied.min_by do |package, range| + matching_versions = @source.versions_for(package, range) + higher_versions = @source.versions_for(package, range.upper_invert) + + [matching_versions.count <= 1 ? 0 : 1, higher_versions.count] + end + end + + def most_preferred_version_of(package, range) + versions = @source.versions_for(package, range) + + # Conditional avoids (among other things) calling + # sort_versions_by_preferred with the root package + if versions.size > 1 + @source.sort_versions_by_preferred(package, versions) + else + versions + end + end + end + end +end diff --git a/lib/bundler/ruby_dsl.rb b/lib/bundler/ruby_dsl.rb index fb4b79c4df..5e52f38c8f 100644 --- a/lib/bundler/ruby_dsl.rb +++ b/lib/bundler/ruby_dsl.rb @@ -42,12 +42,26 @@ module Bundler # Loads the file relative to the dirname of the Gemfile itself. def normalize_ruby_file(filename) file_content = Bundler.read_file(gemfile.dirname.join(filename)) - # match "ruby-3.2.2" or "ruby 3.2.2" capturing version string up to the first space or comment - if /^ruby(-|\s+)([^\s#]+)/.match(file_content) - $2 + # match "ruby-3.2.2", ruby = "3.2.2", ruby = '3.2.2' or "ruby 3.2.2" capturing version string up to the first space or comment + version_match = /^ # Start of line + ruby # Literal "ruby" + [\s-]* # Optional whitespace or hyphens (for "ruby-3.2.2" format) + (?:=\s*)? # Optional equals sign with whitespace (for ruby = "3.2.2" format) + (?: + "([^"]+)" # Double quoted version + | + '([^']+)' # Single quoted version + | + ([^\s#"']+) # Unquoted version + ) + /x.match(file_content) + if version_match + version_match[1] || version_match[2] || version_match[3] else file_content.strip end + rescue Errno::ENOENT + raise GemfileError, "Could not find version file #{filename}" end end end diff --git a/lib/bundler/ruby_version.rb b/lib/bundler/ruby_version.rb index 0ed5cbc6ca..7f60dde476 100644 --- a/lib/bundler/ruby_version.rb +++ b/lib/bundler/ruby_version.rb @@ -43,7 +43,6 @@ module Bundler def to_s(versions = self.versions) output = String.new("ruby #{versions_string(versions)}") - output << "p#{patchlevel}" if patchlevel && patchlevel != "-1" output << " (#{engine} #{versions_string(engine_versions)})" unless engine == "ruby" output @@ -72,8 +71,7 @@ module Bundler def ==(other) versions == other.versions && engine == other.engine && - engine_versions == other.engine_versions && - patchlevel == other.patchlevel + engine_versions == other.engine_versions end def host diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 54c016adcc..fedf44b0e6 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -13,15 +13,6 @@ require "rubygems" unless defined?(Gem) # `Gem::Source` from the redefined `Gem::Specification#source`. require "rubygems/source" -# Cherry-pick fixes to `Gem.ruby_version` to be useful for modern Bundler -# versions and ignore patchlevels -# (https://github.com/rubygems/rubygems/pull/5472, -# https://github.com/rubygems/rubygems/pull/5486). May be removed once RubyGems -# 3.3.12 support is dropped. -unless Gem.ruby_version.to_s == RUBY_VERSION || RUBY_PATCHLEVEL == -1 - Gem.instance_variable_set(:@ruby_version, Gem::Version.new(RUBY_VERSION)) -end - module Gem # Can be removed once RubyGems 3.5.11 support is dropped unless Gem.respond_to?(:freebsd_platform?) @@ -58,14 +49,133 @@ module Gem end end + require "rubygems/platform" + + class Platform + # Can be removed once RubyGems 3.6.9 support is dropped + unless respond_to?(:generic) + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: + + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS + + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE + + class << self + ## + # Returns the generic platform for the given platform. + + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY + + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end + + ## + # Returns the platform specificity match for the given spec platform and user platform. + + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end + + ## + # Sorts and filters the best platform match for the given matching specs and platform. + + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? + + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? + + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first + + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end + + ## + # Sorts the best platform match for the given matching specs and platform. + + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end + + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end + + end + end + require "rubygems/specification" # Can be removed once RubyGems 3.5.14 support is dropped VALIDATES_FOR_RESOLUTION = Specification.new.respond_to?(:validate_for_resolution).freeze - # Can be removed once RubyGems 3.3.15 support is dropped - FLATTENS_REQUIRED_PATHS = Specification.new.respond_to?(:flatten_require_paths).freeze - class Specification # Can be removed once RubyGems 3.5.15 support is dropped correct_array_attributes = @@default_value.select {|_k,v| v.is_a?(Array) }.keys @@ -77,16 +187,19 @@ module Gem require_relative "match_platform" include ::Bundler::MatchMetadata - include ::Bundler::MatchPlatform - attr_accessor :remote, :location, :relative_loaded_from + attr_accessor :remote, :relative_loaded_from + + module AllowSettingSource + attr_writer :source - remove_method :source - attr_writer :source - def source - (defined?(@source) && @source) || Gem::Source::Installed.new + def source + (defined?(@source) && @source) || super + end end + prepend AllowSettingSource + alias_method :rg_full_gem_path, :full_gem_path alias_method :rg_loaded_from, :loaded_from @@ -129,21 +242,8 @@ module Gem full_gem_path end - unless const_defined?(:LATEST_RUBY_WITHOUT_PATCH_VERSIONS) - LATEST_RUBY_WITHOUT_PATCH_VERSIONS = Gem::Version.new("2.1") - - alias_method :rg_required_ruby_version=, :required_ruby_version= - def required_ruby_version=(req) - self.rg_required_ruby_version = req - - @required_ruby_version.requirements.map! do |op, v| - if v >= LATEST_RUBY_WITHOUT_PATCH_VERSIONS && v.release.segments.size == 4 - [op == "~>" ? "=" : op, Gem::Version.new(v.segments.tap {|s| s.delete_at(3) }.join("."))] - else - [op, v] - end - end - end + def insecurely_materialized? + false end def groups @@ -169,35 +269,30 @@ module Gem dependencies - development_dependencies end - def deleted_gem? + def installation_missing? !default_gem? && !File.directory?(full_gem_path) end + def lock_name + @lock_name ||= name_tuple.lock_name + end + unless VALIDATES_FOR_RESOLUTION def validate_for_resolution SpecificationPolicy.new(self).validate_for_resolution end end - unless FLATTENS_REQUIRED_PATHS - def flatten_require_paths - return unless raw_require_paths.first.is_a?(Array) - - warn "#{name} #{version} includes a gemspec with `require_paths` set to an array of arrays. Newer versions of this gem might've already fixed this" - raw_require_paths.flatten! - end + if Gem.rubygems_version < Gem::Version.new("3.5.22") + module FixPathSourceMissingExtensions + def missing_extensions? + return false if %w[Bundler::Source::Path Bundler::Source::Gemspec].include?(source.class.name) - class << self - module RequirePathFlattener - def from_yaml(input) - spec = super(input) - spec.flatten_require_paths - spec - end + super end - - prepend RequirePathFlattener end + + prepend FixPathSourceMissingExtensions end private @@ -280,86 +375,6 @@ module Gem end end - require "rubygems/platform" - - class Platform - JAVA = Gem::Platform.new("java") - MSWIN = Gem::Platform.new("mswin32") - MSWIN64 = Gem::Platform.new("mswin64") - MINGW = Gem::Platform.new("x86-mingw32") - X64_MINGW = [Gem::Platform.new("x64-mingw32"), - Gem::Platform.new("x64-mingw-ucrt")].freeze - WINDOWS = [MSWIN, MSWIN64, MINGW, X64_MINGW].flatten.freeze - X64_LINUX = Gem::Platform.new("x86_64-linux") - X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") - - if X64_LINUX === X64_LINUX_MUSL - remove_method :=== - - def ===(other) - return nil unless Gem::Platform === other - - # universal-mingw32 matches x64-mingw-ucrt - return true if (@cpu == "universal" || other.cpu == "universal") && - @os.start_with?("mingw") && other.os.start_with?("mingw") - - # cpu - ([nil,"universal"].include?(@cpu) || [nil, "universal"].include?(other.cpu) || @cpu == other.cpu || - (@cpu == "arm" && other.cpu.start_with?("armv"))) && - - # os - @os == other.os && - - # version - ( - (@os != "linux" && (@version.nil? || other.version.nil?)) || - (@os == "linux" && (normalized_linux_version_ext == other.normalized_linux_version_ext || ["musl#{@version}", "musleabi#{@version}", "musleabihf#{@version}"].include?(other.version))) || - @version == other.version - ) - end - - # This is a copy of RubyGems 3.3.23 or higher `normalized_linux_method`. - # Once only 3.3.23 is supported, we can use the method in RubyGems. - def normalized_linux_version_ext - return nil unless @version - - without_gnu_nor_abi_modifiers = @version.sub(/\Agnu/, "").sub(/eabi(hf)?\Z/, "") - return nil if without_gnu_nor_abi_modifiers.empty? - - without_gnu_nor_abi_modifiers - end - end - end - - Platform.singleton_class.module_eval do - unless Platform.singleton_methods.include?(:match_spec?) - def match_spec?(spec) - match_gem?(spec.platform, spec.name) - end - - def match_gem?(platform, gem_name) - match_platforms?(platform, Gem.platforms) - end - end - - match_platforms_defined = Gem::Platform.respond_to?(:match_platforms?, true) - - if !match_platforms_defined || Gem::Platform.send(:match_platforms?, Gem::Platform::X64_LINUX_MUSL, [Gem::Platform::X64_LINUX]) - - private - - remove_method :match_platforms? if match_platforms_defined - - def match_platforms?(platform, platforms) - platforms.any? do |local_platform| - platform.nil? || - local_platform == platform || - (local_platform != Gem::Platform::RUBY && platform =~ local_platform) - end - end - end - end - # On universal Rubies, resolve the "universal" arch to the real CPU arch, without changing the extension directory. class BasicSpecification if /^universal\.(?<arch>.*?)-/ =~ (CROSS_COMPILING || RUBY_PLATFORM) @@ -388,6 +403,11 @@ module Gem @ignored = missing_extensions? end end + + # Can be removed once RubyGems 3.6.9 support is dropped + unless new.respond_to?(:installable_on_platform?) + include(::Bundler::MatchPlatform) + end end require "rubygems/name_tuple" @@ -397,7 +417,7 @@ module Gem unless Gem::NameTuple.new("a", Gem::Version.new("1"), Gem::Platform.new("x86_64-linux")).platform.is_a?(String) alias_method :initialize_with_platform, :initialize - def initialize(name, version, platform=Gem::Platform::RUBY) + def initialize(name, version, platform = Gem::Platform::RUBY) if Gem::Platform === platform initialize_with_platform(name, version, platform.to_s) else @@ -434,7 +454,18 @@ module Gem end end - unless Gem.rubygems_version >= Gem::Version.new("3.5.23") + unless Gem.rubygems_version >= Gem::Version.new("3.6.7") + module UnfreezeCompactIndexParsedResponse + def parse(line) + version, platform, dependencies, requirements = super + [version, platform, dependencies.frozen? ? dependencies.dup : dependencies, requirements.frozen? ? requirements.dup : requirements] + end + end + + Resolver::APISet::GemParser.prepend(UnfreezeCompactIndexParsedResponse) + end + + if Gem.rubygems_version < Gem::Version.new("3.6.0") class Package; end require "rubygems/package/tar_reader" require "rubygems/package/tar_reader/entry" diff --git a/lib/bundler/rubygems_gem_installer.rb b/lib/bundler/rubygems_gem_installer.rb index 1af1b85ff0..64ce6193d3 100644 --- a/lib/bundler/rubygems_gem_installer.rb +++ b/lib/bundler/rubygems_gem_installer.rb @@ -69,7 +69,7 @@ module Bundler end def generate_plugins - return unless Gem::Installer.instance_methods(false).include?(:generate_plugins) + return unless Gem::Installer.method_defined?(:generate_plugins, false) latest = Gem::Specification.stubs_for(spec.name).first return if latest && latest.version > spec.version @@ -103,6 +103,10 @@ module Bundler end end + def build_jobs + Bundler.settings[:jobs] || super + end + def build_extensions extension_cache_path = options[:bundler_extension_cache_path] extension_dir = spec.extension_dir diff --git a/lib/bundler/rubygems_integration.rb b/lib/bundler/rubygems_integration.rb index 86396b01e4..e04ef23259 100644 --- a/lib/bundler/rubygems_integration.rb +++ b/lib/bundler/rubygems_integration.rb @@ -134,18 +134,6 @@ module Bundler loaded_gem_paths.flatten end - def load_plugins - Gem.load_plugins - end - - def load_plugin_files(plugin_files) - Gem.load_plugin_files(plugin_files) - end - - def load_env_plugins - Gem.load_env_plugins - end - def ui=(obj) Gem::DefaultUserInteraction.ui = obj end @@ -189,7 +177,7 @@ module Bundler end end - def replace_gem(specs, specs_by_name) + def replace_gem(specs_by_name) executables = nil [::Kernel.singleton_class, ::Kernel].each do |kernel_class| @@ -226,16 +214,11 @@ module Bundler e.requirement = dep.requirement raise e end - - # backwards compatibility shim, see https://github.com/rubygems/bundler/issues/5102 - kernel_class.send(:public, :gem) if Bundler.feature_flag.setup_makes_kernel_gem_public? end end # Used to give better error messages when activating specs outside of the current bundle def replace_bin_path(specs_by_name) - gem_class = (class << Gem; self; end) - redefine_method(gem_class, :find_spec_for_exe) do |gem_name, *args| exec_name = args.first raise ArgumentError, "you must supply exec_name" unless exec_name @@ -286,7 +269,7 @@ module Bundler else Gem::BUNDLED_GEMS.replace_require(specs) if Gem::BUNDLED_GEMS.respond_to?(:replace_require) end - replace_gem(specs, specs_by_name) + replace_gem(specs_by_name) stub_rubygems(specs_by_name.values) replace_bin_path(specs_by_name) @@ -305,7 +288,6 @@ module Bundler default_spec_name = default_spec.name next if specs_by_name.key?(default_spec_name) - specs << default_spec specs_by_name[default_spec_name] = default_spec end @@ -358,9 +340,13 @@ module Bundler Gem::Specification.all = specs end - redefine_method((class << Gem; self; end), :finish_resolve) do |*| + redefine_method(gem_class, :finish_resolve) do |*| [] end + + redefine_method(gem_class, :load_plugins) do |*| + load_plugin_files specs.flat_map(&:plugins) + end end def plain_specs @@ -393,7 +379,9 @@ module Bundler def download_gem(spec, uri, cache_dir, fetcher) require "rubygems/remote_fetcher" uri = Bundler.settings.mirror_for(uri) - Bundler::Retry.new("download gem from #{uri}").attempts do + redacted_uri = Gem::Uri.redact(uri) + + Bundler::Retry.new("download gem from #{redacted_uri}").attempts do gem_file_name = spec.file_name local_gem_path = File.join cache_dir, gem_file_name return if File.exist? local_gem_path @@ -415,7 +403,7 @@ module Bundler end end rescue Gem::RemoteFetcher::FetchError => e - raise Bundler::HTTPError, "Could not download gem from #{uri} due to underlying error <#{e.message}>" + raise Bundler::HTTPError, "Could not download gem from #{redacted_uri} due to underlying error <#{e.message}>" end def build(spec, skip_validation = false) @@ -428,11 +416,7 @@ module Bundler end def all_specs - SharedHelpers.major_deprecation 2, "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" - - Gem::Specification.stubs.map do |stub| - StubSpecification.from_stub(stub) - end + SharedHelpers.feature_removed! "Bundler.rubygems.all_specs has been removed in favor of Bundler.rubygems.installed_specs" end def installed_specs @@ -448,7 +432,7 @@ module Bundler end def find_bundler(version) - find_name("bundler").find {|s| s.version.to_s == version } + find_name("bundler").find {|s| s.version.to_s == version.to_s } end def find_name(name) @@ -458,6 +442,12 @@ module Bundler def default_stubs Gem::Specification.default_stubs("*.gemspec") end + + private + + def gem_class + class << Gem; self; end + end end def self.rubygems diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 9792a81962..5280e72aa2 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -50,35 +50,30 @@ module Bundler Plugin.hook(Plugin::Events::GEM_BEFORE_REQUIRE_ALL, dependencies) dependencies.each do |dep| - required_file = nil Plugin.hook(Plugin::Events::GEM_BEFORE_REQUIRE, dep) - begin - # Loop through all the specified autorequires for the - # dependency. If there are none, use the dependency's name - # as the autorequire. - Array(dep.autorequire || dep.name).each do |file| - # Allow `require: true` as an alias for `require: <name>` - file = dep.name if file == true - required_file = file - begin - Kernel.require file - rescue RuntimeError => e - raise e if e.is_a?(LoadError) # we handle this a little later + # Loop through all the specified autorequires for the + # dependency. If there are none, use the dependency's name + # as the autorequire. + Array(dep.autorequire || dep.name).each do |file| + # Allow `require: true` as an alias for `require: <name>` + file = dep.name if file == true + required_file = file + begin + Kernel.require required_file + rescue LoadError => e + if dep.autorequire.nil? && e.path == required_file + if required_file.include?("-") + required_file = required_file.tr("-", "/") + retry + end + else raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end - end - rescue LoadError => e - raise if dep.autorequire || e.path != required_file - - if dep.autorequire.nil? && dep.name.include?("-") - begin - namespaced_file = dep.name.tr("-", "/") - Kernel.require namespaced_file - rescue LoadError => e - raise if e.path != namespaced_file - end + rescue StandardError => e + raise Bundler::GemRequireError.new e, + "There was an error while trying to load the gem '#{file}'." end end @@ -135,8 +130,20 @@ module Bundler specs_to_cache.each do |spec| next if spec.name == "bundler" - next if spec.source.is_a?(Source::Gemspec) - spec.source.cache(spec, custom_path) if spec.source.respond_to?(:cache) + + source = spec.source + next if source.is_a?(Source::Gemspec) + + if source.respond_to?(:migrate_cache) + source.migrate_cache(custom_path, local: local) + elsif source.respond_to?(:cache) + source.cache(spec, custom_path) + end + end + + Dir[cache_path.join("*/.git")].each do |git_dir| + FileUtils.rm_rf(git_dir) + FileUtils.touch(File.expand_path("../.bundlecache", git_dir)) end prune_cache(cache_path) unless Bundler.settings[:no_prune] @@ -167,7 +174,14 @@ module Bundler spec_cache_paths = [] spec_gemspec_paths = [] spec_extension_paths = [] - Bundler.rubygems.add_default_gems_to(specs).values.each do |spec| + specs_to_keep = Bundler.rubygems.add_default_gems_to(specs).values + + current_bundler = Bundler.rubygems.find_bundler(Bundler.gem_version) + if current_bundler + specs_to_keep << current_bundler + end + + specs_to_keep.each do |spec| spec_gem_paths << spec.full_gem_path # need to check here in case gems are nested like for the rails git repo md = %r{(.+bundler/gems/.+-[a-f0-9]{7,12})}.match(spec.full_gem_path) @@ -233,7 +247,11 @@ module Bundler cached.each do |path| Bundler.ui.info " * #{File.basename(path)}" - File.delete(path) + + begin + File.delete(path) + rescue Errno::ENOENT + end end end end @@ -263,10 +281,10 @@ module Bundler def setup_manpath # Add man/ subdirectories from activated bundles to MANPATH for man(1) - manuals = $LOAD_PATH.map do |path| + manuals = $LOAD_PATH.filter_map do |path| man_subdir = path.sub(/lib$/, "man") man_subdir unless Dir[man_subdir + "/man?/"].empty? - end.compact + end return if manuals.empty? Bundler::SharedHelpers.set_env "MANPATH", manuals.concat( diff --git a/lib/bundler/self_manager.rb b/lib/bundler/self_manager.rb index 6ab41b99f7..1db77fd46b 100644 --- a/lib/bundler/self_manager.rb +++ b/lib/bundler/self_manager.rb @@ -7,13 +7,15 @@ module Bundler # class SelfManager def restart_with_locked_bundler_if_needed - return unless needs_switching? && installed? + restart_version = find_restart_version + return unless restart_version && installed?(restart_version) restart_with(restart_version) end def install_locked_bundler_and_restart_with_it_if_needed - return unless needs_switching? + restart_version = find_restart_version + return unless restart_version if restart_version == lockfile_version Bundler.ui.info \ @@ -29,8 +31,6 @@ module Bundler end def update_bundler_and_restart_with_it_if_needed(target) - return unless autoswitching_applies? - spec = resolve_update_version_from(target) return unless spec @@ -38,7 +38,7 @@ module Bundler Bundler.ui.info "Updating bundler to #{version}." - install(spec) + install(spec) unless installed?(version) restart_with(version) end @@ -68,46 +68,37 @@ module Bundler def restart_with(version) configured_gem_home = ENV["GEM_HOME"] + configured_orig_gem_home = ENV["BUNDLER_ORIG_GEM_HOME"] configured_gem_path = ENV["GEM_PATH"] + configured_orig_gem_path = ENV["BUNDLER_ORIG_GEM_PATH"] - # Bundler specs need some stuff to be required before Bundler starts - # running, for example, for faking the compact index API. However, these - # flags are lost when we reexec to a different version of Bundler. In the - # future, we may be able to properly reconstruct the original Ruby - # invocation (see https://bugs.ruby-lang.org/issues/6648), but for now - # there's no way to do it, so we need to be explicit about how to re-exec. - # This may be a feature end users request at some point, but maybe by that - # time, we have builtin tools to do. So for now, we use an undocumented - # ENV variable only for our specs. - bundler_spec_original_cmd = ENV["BUNDLER_SPEC_ORIGINAL_CMD"] - if bundler_spec_original_cmd - require "shellwords" - cmd = [*Shellwords.shellsplit(bundler_spec_original_cmd), *ARGV] - else - cmd = [$PROGRAM_NAME, *ARGV] - cmd.unshift(Gem.ruby) unless File.executable?($PROGRAM_NAME) - end + argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0 + cmd = [argv0, *ARGV] + cmd.unshift(Gem.ruby) unless File.executable?(argv0) Bundler.with_original_env do Kernel.exec( - { "GEM_HOME" => configured_gem_home, "GEM_PATH" => configured_gem_path, "BUNDLER_VERSION" => version.to_s }, + { + "GEM_HOME" => configured_gem_home, + "BUNDLER_ORIG_GEM_HOME" => configured_orig_gem_home, + "GEM_PATH" => configured_gem_path, + "BUNDLER_ORIG_GEM_PATH" => configured_orig_gem_path, + "BUNDLER_VERSION" => version.to_s, + }, *cmd ) end end - def needs_switching? + def needs_switching?(restart_version) autoswitching_applies? && - Bundler.settings[:version] != "system" && released?(restart_version) && - !running?(restart_version) && - !updating? + !running?(restart_version) end def autoswitching_applies? - ENV["BUNDLER_VERSION"].nil? && + (ENV["BUNDLER_VERSION"].nil? || ENV["BUNDLER_VERSION"].empty?) && ruby_can_restart_with_same_arguments? && - SharedHelpers.in_bundle? && lockfile_version end @@ -141,6 +132,7 @@ module Bundler end def find_latest_matching_spec(requirement) + Bundler.configure local_result = find_latest_matching_spec_from_collection(local_specs, requirement) return local_result if local_result && requirement.specific? @@ -170,18 +162,14 @@ module Bundler $PROGRAM_NAME != "-e" end - def updating? - "update".start_with?(ARGV.first || " ") && ARGV[1..-1].any? {|a| a.start_with?("--bundler") } - end - - def installed? + def installed?(restart_version) Bundler.configure Bundler.rubygems.find_bundler(restart_version.to_s) end def current_version - @current_version ||= Gem::Version.new(Bundler::VERSION) + @current_version ||= Bundler.gem_version end def lockfile_version @@ -193,13 +181,16 @@ module Bundler @lockfile_version = nil end - def restart_version - return @restart_version if defined?(@restart_version) - # BUNDLE_VERSION=x.y.z - @restart_version = Gem::Version.new(Bundler.settings[:version]) - rescue ArgumentError - # BUNDLE_VERSION=lockfile - @restart_version = lockfile_version + def find_restart_version + return unless SharedHelpers.in_bundle? + + configured_version = Bundler.settings[:version] + return if configured_version == "system" + + restart_version = configured_version == "lockfile" ? lockfile_version : Gem::Version.new(configured_version) + return unless needs_switching?(restart_version) + + restart_version end end end diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 4dda36242d..d00a4bb916 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -7,13 +7,10 @@ module Bundler autoload :Validator, File.expand_path("settings/validator", __dir__) BOOL_KEYS = %w[ - allow_offline_install - auto_clean_without_path auto_install cache_all cache_all_platforms clean - default_install_uses_path deployment disable_checksum_validation disable_exec_load @@ -22,41 +19,26 @@ module Bundler disable_shared_gems disable_version_check force_ruby_platform - forget_cli_options frozen gem.changelog gem.coc gem.mit + gem.bundle git.allow_insecure global_gem_cache ignore_messages init_gems_rb inline + lockfile_checksums no_install no_prune - path_relative_to_cwd path.system plugins prefer_patch - print_only_version_number - setup_makes_kernel_gem_public silence_deprecations silence_root_warning update_requires_all_flag - ].freeze - - REMEMBERED_KEYS = %w[ - bin - cache_all - clean - deployment - frozen - no_prune - path - shebang - path.system - without - with + verbose ].freeze NUMBER_KEYS = %w[ @@ -83,8 +65,10 @@ module Bundler gem.rubocop gem.test gemfile + lockfile path shebang + simulate_version system_bindir trust-policy version @@ -98,6 +82,11 @@ module Bundler "BUNDLE_RETRY" => 3, "BUNDLE_TIMEOUT" => 10, "BUNDLE_VERSION" => "lockfile", + "BUNDLE_LOCKFILE_CHECKSUMS" => true, + "BUNDLE_CACHE_ALL" => true, + "BUNDLE_PLUGINS" => true, + "BUNDLE_GLOBAL_GEM_CACHE" => false, + "BUNDLE_UPDATE_REQUIRES_ALL_FLAG" => false, }.freeze def initialize(root = nil) @@ -129,12 +118,8 @@ module Bundler end def set_command_option(key, value) - if !is_remembered(key) || Bundler.feature_flag.forget_cli_options? - temporary(key => value) - value - else - set_local(key, value) - end + temporary(key => value) + value end def set_command_option_if_given(key, value) @@ -273,7 +258,7 @@ module Bundler def use_system_gems? return true if system_path return false if explicit_path - !Bundler.feature_flag.default_install_uses_path? + !Bundler.feature_flag.bundler_5_mode? end def base_path @@ -388,10 +373,6 @@ module Bundler ARRAY_KEYS.include?(self.class.key_to_s(key)) end - def is_remembered(key) - REMEMBERED_KEYS.include?(self.class.key_to_s(key)) - end - def is_credential(key) key == "gem.push_key" end diff --git a/lib/bundler/settings/validator.rb b/lib/bundler/settings/validator.rb index 0a57ea7f03..9aa1627fb2 100644 --- a/lib/bundler/settings/validator.rb +++ b/lib/bundler/settings/validator.rb @@ -74,29 +74,6 @@ module Bundler fail!(key, value, "`#{other_key}` is current set to #{other_setting.inspect}", "the `#{conflicting.join("`, `")}` groups conflict") end end - - rule %w[path], "relative paths are expanded relative to the current working directory" do |key, value, settings| - next if value.nil? - - path = Pathname.new(value) - next if !path.relative? || !Bundler.feature_flag.path_relative_to_cwd? - - path = path.expand_path - - root = begin - Bundler.root - rescue GemfileNotFound - Pathname.pwd.expand_path - end - - path = begin - path.relative_path_from(root) - rescue ArgumentError - path - end - - set(settings, key, path.to_s) - end end end end diff --git a/lib/bundler/shared_helpers.rb b/lib/bundler/shared_helpers.rb index 47dd801f6e..2aa8abe0a0 100644 --- a/lib/bundler/shared_helpers.rb +++ b/lib/bundler/shared_helpers.rb @@ -4,8 +4,6 @@ require_relative "version" require_relative "rubygems_integration" require_relative "current_ruby" -autoload :Pathname, "pathname" - module Bundler autoload :WINDOWS, File.expand_path("constants", __dir__) autoload :FREEBSD, File.expand_path("constants", __dir__) @@ -25,6 +23,9 @@ module Bundler end def default_lockfile + given = ENV["BUNDLE_LOCKFILE"] + return Pathname.new(given) if given && !given.empty? + gemfile = default_gemfile case gemfile.basename.to_s @@ -57,7 +58,7 @@ module Bundler def pwd Bundler.rubygems.ext_lock.synchronize do - Pathname.pwd + Dir.pwd end end @@ -104,7 +105,8 @@ module Bundler def filesystem_access(path, action = :write, &block) yield(path.dup) rescue Errno::EACCES => e - raise unless e.message.include?(path.to_s) || action == :create + path_basename = File.basename(path.to_s) + raise unless e.message.include?(path_basename) || action == :create raise PermissionError.new(path, action) rescue Errno::EAGAIN @@ -115,30 +117,27 @@ module Bundler raise NoSpaceOnDeviceError.new(path, action) rescue Errno::ENOTSUP raise OperationNotSupportedError.new(path, action) + rescue Errno::EPERM + raise OperationNotPermittedError.new(path, action) + rescue Errno::EROFS + raise ReadOnlyFileSystemError.new(path, action) rescue Errno::EEXIST, Errno::ENOENT raise rescue SystemCallError => e raise GenericSystemCallError.new(e, "There was an error #{[:create, :write].include?(action) ? "creating" : "accessing"} `#{path}`.") end - def major_deprecation(major_version, message, removed_message: nil, print_caller_location: false) - if print_caller_location - caller_location = caller_locations(2, 2).first - suffix = " (called at #{caller_location.path}:#{caller_location.lineno})" - message += suffix - removed_message += suffix if removed_message - end + def feature_deprecated!(message) + return unless prints_major_deprecations? - bundler_major_version = Bundler.bundler_major_version - if bundler_major_version > major_version - require_relative "errors" - raise DeprecatedError, "[REMOVED] #{removed_message || message}" - end - - return unless bundler_major_version >= major_version && prints_major_deprecations? Bundler.ui.warn("[DEPRECATED] #{message}") end + def feature_removed!(message) + require_relative "errors" + raise RemovedError, "[REMOVED] #{message}" + end + def print_major_deprecations! multiple_gemfiles = search_up(".") do |dir| gemfiles = gemfile_names.select {|gf| File.file? File.expand_path(gf, dir) } @@ -162,10 +161,10 @@ module Bundler extra_deps = new_deps - old_deps return if extra_deps.empty? - Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has either corrupted API or lockfile dependencies" \ + Bundler.ui.debug "#{spec.full_name} from #{spec.remote} has corrupted API dependencies" \ " (was expecting #{old_deps.map(&:to_s)}, but the real spec has #{new_deps.map(&:to_s)})" raise APIResponseMismatchError, - "Downloading #{spec.full_name} revealed dependencies not in the API or the lockfile (#{extra_deps.join(", ")})." \ + "Downloading #{spec.full_name} revealed dependencies not in the API (#{extra_deps.join(", ")})." \ "\nRunning `bundle update #{spec.name}` should fix the problem." end @@ -302,6 +301,7 @@ module Bundler def set_bundle_variables Bundler::SharedHelpers.set_env "BUNDLE_BIN_PATH", bundle_bin_path Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", find_gemfile.to_s + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", default_lockfile.to_s Bundler::SharedHelpers.set_env "BUNDLER_VERSION", Bundler::VERSION Bundler::SharedHelpers.set_env "BUNDLER_SETUP", File.expand_path("setup", __dir__) end @@ -382,7 +382,6 @@ module Bundler end def prints_major_deprecations? - require_relative "../bundler" return false if Bundler.settings[:silence_deprecations] require_relative "deprecate" return false if Bundler::Deprecate.skip diff --git a/lib/bundler/similarity_detector.rb b/lib/bundler/similarity_detector.rb deleted file mode 100644 index 50e66b9cab..0000000000 --- a/lib/bundler/similarity_detector.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Bundler - class SimilarityDetector - SimilarityScore = Struct.new(:string, :distance) - - # initialize with an array of words to be matched against - def initialize(corpus) - @corpus = corpus - end - - # return an array of words similar to 'word' from the corpus - def similar_words(word, limit = 3) - words_by_similarity = @corpus.map {|w| SimilarityScore.new(w, levenshtein_distance(word, w)) } - words_by_similarity.select {|s| s.distance <= limit }.sort_by(&:distance).map(&:string) - end - - # return the result of 'similar_words', concatenated into a list - # (eg "a, b, or c") - def similar_word_list(word, limit = 3) - words = similar_words(word, limit) - if words.length == 1 - words[0] - elsif words.length > 1 - [words[0..-2].join(", "), words[-1]].join(" or ") - end - end - - protected - - # https://www.informit.com/articles/article.aspx?p=683059&seqNum=36 - def levenshtein_distance(this, that, ins = 2, del = 2, sub = 1) - # ins, del, sub are weighted costs - return nil if this.nil? - return nil if that.nil? - dm = [] # distance matrix - - # Initialize first row values - dm[0] = (0..this.length).collect {|i| i * ins } - fill = [0] * (this.length - 1) - - # Initialize first column values - (1..that.length).each do |i| - dm[i] = [i * del, fill.flatten] - end - - # populate matrix - (1..that.length).each do |i| - (1..this.length).each do |j| - # critical comparison - dm[i][j] = [ - dm[i - 1][j - 1] + (this[j - 1] == that[i - 1] ? 0 : sub), - dm[i][j - 1] + ins, - dm[i - 1][j] + del, - ].min - end - end - - # The last value in matrix is the Levenshtein distance between the strings - dm[that.length][this.length] - end - end -end diff --git a/lib/bundler/source.rb b/lib/bundler/source.rb index 115dbd1378..2b90a0eff1 100644 --- a/lib/bundler/source.rb +++ b/lib/bundler/source.rb @@ -35,6 +35,8 @@ module Bundler spec.source == self end + def prefer_local!; end + def local!; end def local_only!; end @@ -77,7 +79,7 @@ module Bundler end def extension_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless source_slug = extension_cache_slug(spec) Bundler.user_cache.join( "extensions", Gem::Platform.local.to_s, Bundler.ruby_scope, diff --git a/lib/bundler/source/gemspec.rb b/lib/bundler/source/gemspec.rb index 7e3447e776..ed766dbe74 100644 --- a/lib/bundler/source/gemspec.rb +++ b/lib/bundler/source/gemspec.rb @@ -4,14 +4,15 @@ module Bundler class Source class Gemspec < Path attr_reader :gemspec + attr_writer :checksum_store def initialize(options) super @gemspec = options["gemspec"] end - def as_path_source - Path.new(options) + def to_s + "gemspec at `#{@path}`" end end end diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb index ef36efb3f4..bb669ebba3 100644 --- a/lib/bundler/source/git.rb +++ b/lib/bundler/source/git.rb @@ -102,7 +102,7 @@ module Bundler end def identifier - uri_with_specifiers([humanized_ref, cached_revision, glob_for_display]) + uri_with_specifiers([humanized_ref, locked_revision, glob_for_display]) end def uri_with_specifiers(specifiers) @@ -176,10 +176,10 @@ module Bundler "#{current_branch} but Gemfile specifies #{branch}" end - changed = cached_revision && cached_revision != revision + changed = locked_revision && locked_revision != revision - if !Bundler.settings[:disable_local_revision_check] && changed && !@unlocked && !git_proxy.contains?(cached_revision) - raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(cached_revision)} " \ + if !Bundler.settings[:disable_local_revision_check] && changed && !@unlocked && !git_proxy.contains?(locked_revision) + raise GitError, "The Gemfile lock is pointing to revision #{shortref_for_display(locked_revision)} " \ "but the current branch in your local override for #{name} does not contain such commit. " \ "Please make sure your branch is up to date." end @@ -188,12 +188,10 @@ module Bundler end def specs(*) - set_up_app_cache!(app_cache_path) if use_app_cache? + set_cache_path!(app_cache_path) if use_app_cache? if requires_checkout? && !@copied - FileUtils.rm_rf(app_cache_path) if use_app_cache? && git_proxy.not_a_repository? - - fetch + fetch unless use_app_cache? checkout end @@ -216,17 +214,16 @@ module Bundler requires_checkout? ? spec.post_install_message : nil end + def migrate_cache(custom_path = nil, local: false) + if local + cache_to(custom_path, try_migrate: false) + else + cache_to(custom_path, try_migrate: true) + end + end + def cache(spec, custom_path = nil) - app_cache_path = app_cache_path(custom_path) - return unless Bundler.feature_flag.cache_all? - return if install_path == app_cache_path - return if cache_path == app_cache_path - cached! - FileUtils.rm_rf(app_cache_path) - git_proxy.checkout if requires_checkout? - FileUtils.cp_r("#{cache_path}/.", app_cache_path) - FileUtils.touch(app_cache_path.join(".bundlecache")) - FileUtils.rm_rf(Dir.glob(app_cache_path.join("hooks/*.sample"))) + cache_to(custom_path, try_migrate: false) end def load_spec_files @@ -241,7 +238,7 @@ module Bundler # across different projects, this cache will be shared. # When using local git repos, this is set to the local repo. def cache_path - @cache_path ||= if Bundler.feature_flag.global_gem_cache? + @cache_path ||= if Bundler.settings[:global_gem_cache] Bundler.user_cache else Bundler.bundle_path.join("cache", "bundler") @@ -249,7 +246,7 @@ module Bundler end def app_cache_dirname - "#{base_name}-#{shortref_for_path(cached_revision || revision)}" + "#{base_name}-#{shortref_for_path(locked_revision || revision)}" end def revision @@ -270,9 +267,39 @@ module Bundler private + def cache_to(custom_path, try_migrate: false) + return unless Bundler.settings[:cache_all] + + app_cache_path = app_cache_path(custom_path) + + migrate = try_migrate ? bare_repo?(app_cache_path) : false + + set_cache_path!(nil) if migrate + + return if cache_path == app_cache_path + + cached! + FileUtils.rm_rf(app_cache_path) + git_proxy.checkout if migrate || requires_checkout? + git_proxy.copy_to(app_cache_path, @submodules) + serialize_gemspecs_in(app_cache_path) + end + def checkout Bundler.ui.debug " * Checking out revision: #{ref}" - git_proxy.copy_to(install_path, submodules) + if use_app_cache? && !bare_repo?(app_cache_path) + SharedHelpers.filesystem_access(install_path.dirname) do |p| + FileUtils.mkdir_p(p) + end + FileUtils.cp_r("#{app_cache_path}/.", install_path) + else + if use_app_cache? && bare_repo?(app_cache_path) + Bundler.ui.warn "Installing from cache in old \"bare repository\" format for compatibility. " \ + "Please run `bundle cache` and commit the updated cache to migrate to the new format and get rid of this warning." + end + + git_proxy.copy_to(install_path, submodules) + end serialize_gemspecs_in(install_path) @copied = true end @@ -320,13 +347,8 @@ module Bundler @install_path = path end - def set_up_app_cache!(path) - FileUtils.mkdir_p(path.join("refs")) - set_cache_path!(path) - end - def has_app_cache? - cached_revision && super + locked_revision && super end def use_app_cache? @@ -334,11 +356,15 @@ module Bundler end def requires_checkout? - allow_git_ops? && !local? && !cached_revision_checked_out? + allow_git_ops? && !local? && !locked_revision_checked_out? end - def cached_revision_checked_out? - cached_revision && cached_revision == revision && install_path.exist? + def locked_revision_checked_out? + locked_revision && locked_revision == revision && installed? + end + + def installed? + git_proxy.installed_to?(install_path) end def base_name @@ -375,7 +401,7 @@ module Bundler Bundler::Digest.sha1(input) end - def cached_revision + def locked_revision options["revision"] end @@ -384,13 +410,12 @@ module Bundler end def git_proxy - @git_proxy ||= GitProxy.new(cache_path, uri, options, cached_revision, self) + @git_proxy ||= GitProxy.new(cache_path, uri, options, locked_revision, self) end def fetch git_proxy.checkout rescue GitError => e - raise unless Bundler.feature_flag.allow_offline_install? Bundler.ui.warn "Using cached git data because of network errors:\n#{e}" end @@ -417,6 +442,10 @@ module Bundler def override_for(path) Bundler.settings.local_overrides.key(path) end + + def bare_repo?(path) + File.exist?(path.join("objects")) && File.exist?(path.join("HEAD")) + end end end end diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb index 744235bc04..cd352c22a7 100644 --- a/lib/bundler/source/git/git_proxy.rb +++ b/lib/bundler/source/git/git_proxy.rb @@ -16,7 +16,7 @@ module Bundler def initialize(command) msg = String.new msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, " - msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " + msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md " msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}" super msg end @@ -84,12 +84,6 @@ module Bundler end end - def not_a_repository? - _, status = git_null("rev-parse", "--resolve-git-dir", path.to_s, dir: path) - - !status.success? - end - def contains?(commit) allowed_with_path do result, status = git_null("branch", "--contains", commit, dir: path) @@ -127,7 +121,7 @@ module Bundler FileUtils.rm_rf(p) end git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s - File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination) + File.chmod((File.stat(destination).mode | 0o777) & ~File.umask, destination) rescue Errno::EEXIST => e file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1] raise GitError, "Bundler could not install a gem because it needs to " \ @@ -153,6 +147,12 @@ module Bundler end end + def installed_to?(destination) + # if copy_to is interrupted, it may leave a partially installed directory that + # contains .git but no other files -- consider this not to be installed + Dir.exist?(destination) && (Dir.children(destination) - [".git"]).any? + end + private def git_remote_fetch(args) @@ -185,7 +185,8 @@ module Bundler _, err, status = capture(command, nil) return extra_ref if status.success? - if err.include?("Could not find remote branch") + if err.include?("Could not find remote branch") || # git up to 2.49 + err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri) else idx = command.index("--depth") @@ -262,7 +263,7 @@ module Bundler end def not_pinned? - branch || tag || ref.nil? + branch_option || ref.nil? end def pinned_to_full_sha? @@ -304,8 +305,8 @@ module Bundler end def has_revision_cached? - return unless @revision && path.exist? - git("cat-file", "-e", @revision, dir: path) + return unless commit && path.exist? + git("cat-file", "-e", commit, dir: path) true rescue GitError false @@ -407,11 +408,7 @@ module Bundler def capture3_args_for(cmd, dir) return ["git", *cmd] unless dir - if Bundler.feature_flag.bundler_3_mode? || supports_minus_c? - ["git", "-C", dir.to_s, *cmd] - else - ["git", *cmd, { chdir: dir.to_s }] - end + ["git", "-C", dir.to_s, *cmd] end def extra_clone_args @@ -426,7 +423,7 @@ module Bundler # anyways. return args if @revision - args += ["--branch", branch || tag] if branch || tag + args += ["--branch", branch_option] if branch_option args end @@ -442,12 +439,12 @@ module Bundler extra_args end - def full_clone? - depth.nil? + def branch_option + branch || tag end - def supports_minus_c? - @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5") + def full_clone? + depth.nil? end def needs_allow_any_sha1_in_want? diff --git a/lib/bundler/source/path.rb b/lib/bundler/source/path.rb index d4c530e922..82e782ba25 100644 --- a/lib/bundler/source/path.rb +++ b/lib/bundler/source/path.rb @@ -24,7 +24,7 @@ module Bundler @path = Pathname.new(options["path"]) expanded_path = expand(@path) @path = if @path.relative? - expanded_path.relative_path_from(root_path.expand_path) + expanded_path.relative_path_from(File.expand_path(root_path)) else expanded_path end @@ -53,6 +53,8 @@ module Bundler "source at `#{@path}`" end + alias_method :identifier, :to_s + alias_method :to_gemfile, :path def hash @@ -60,8 +62,8 @@ module Bundler end def eql?(other) - return unless other.class == self.class - expanded_original_path == other.expanded_original_path && + [Gemspec, Path].include?(other.class) && + expanded_original_path == other.expanded_original_path && version == other.version end @@ -81,7 +83,7 @@ module Bundler def cache(spec, custom_path = nil) app_cache_path = app_cache_path(custom_path) - return unless Bundler.feature_flag.cache_all? + return unless Bundler.settings[:cache_all] return if expand(@original_path).to_s.index(root_path.to_s + "/") == 0 unless @original_path.exist? @@ -124,11 +126,7 @@ module Bundler end def expand(somepath) - if Bundler.current_ruby.jruby? # TODO: Unify when https://github.com/rubygems/bundler/issues/7598 fixed upstream and all supported jrubies include the fix - somepath.expand_path(root_path).expand_path - else - somepath.expand_path(root_path) - end + somepath.expand_path(root_path) rescue ArgumentError => e Bundler.ui.debug(e) raise PathError, "There was an error while trying to use the path " \ @@ -167,6 +165,13 @@ module Bundler next unless spec = load_gemspec(file) spec.source = self + # The ignore attribute is for ignoring installed gems that don't + # have extensions correctly compiled for activation. In the case of + # path sources, there's a single version of each gem in the path + # source available to Bundler, so we always certainly want to + # consider that for activation and never makes sense to ignore it. + spec.ignored = false + # Validation causes extension_dir to be calculated, which depends # on #source, so we validate here instead of load_gemspec validate_spec(spec) @@ -214,7 +219,7 @@ module Bundler # Some gem authors put absolute paths in their gemspec # and we have to save them from themselves - spec.files = spec.files.map do |path| + spec.files = spec.files.filter_map do |path| next path unless /\A#{Pathname::SEPARATOR_PAT}/o.match?(path) next if File.directory?(path) begin @@ -222,7 +227,7 @@ module Bundler rescue ArgumentError path end - end.compact + end installer = Path::Installer.new( spec, diff --git a/lib/bundler/source/rubygems.rb b/lib/bundler/source/rubygems.rb index 36185561fa..e1e030ffc8 100644 --- a/lib/bundler/source/rubygems.rb +++ b/lib/bundler/source/rubygems.rb @@ -8,7 +8,7 @@ module Bundler autoload :Remote, File.expand_path("rubygems/remote", __dir__) # Ask for X gems per API request - API_REQUEST_SIZE = 50 + API_REQUEST_SIZE = 100 attr_accessor :remotes @@ -19,6 +19,7 @@ module Bundler @allow_remote = false @allow_cached = false @allow_local = options["allow_local"] || false + @prefer_local = false @checksum_store = Checksum::Store.new Array(options["remotes"]).reverse_each {|r| add_remote(r) } @@ -30,6 +31,10 @@ module Bundler @caches ||= [cache_path, *Bundler.rubygems.gem_cache] end + def prefer_local! + @prefer_local = true + end + def local_only! @specs = nil @allow_local = true @@ -37,6 +42,10 @@ module Bundler @allow_remote = false end + def local_only? + @allow_local && !@allow_remote + end + def local! return if @allow_local @@ -139,9 +148,15 @@ module Bundler index.merge!(cached_specs) if @allow_cached index.merge!(installed_specs) if @allow_local - # complete with default specs, only if not already available in the - # index through remote, cached, or installed specs - index.use(default_specs) if @allow_local + if @allow_local + if @prefer_local + index.merge!(default_specs) + else + # complete with default specs, only if not already available in the + # index through remote, cached, or installed specs + index.use(default_specs) + end + end index end @@ -153,12 +168,6 @@ module Bundler return nil # no post-install message end - if spec.remote - # Check for this spec from other sources - uris = [spec.remote, *remotes_for_spec(spec)].map(&:anonymized_uri).uniq - Installer.ambiguous_gems << [spec.name, *uris] if uris.length > 1 - end - path = fetch_gem_if_possible(spec, options[:previous_spec]) raise GemNotFound, "Could not find #{spec.file_name} for installation" unless path @@ -202,7 +211,11 @@ module Bundler message += " with native extensions" if spec.extensions.any? Bundler.ui.confirm message - installed_spec = installer.install + installed_spec = nil + + Gem.time("Installed #{spec.name} in", 0, true) do + installed_spec = installer.install + end spec.full_gem_path = installed_spec.full_gem_path spec.loaded_from = installed_spec.loaded_from @@ -317,13 +330,6 @@ module Bundler remotes.map(&method(:remove_auth)) end - def remotes_for_spec(spec) - specs.search_all(spec.name).inject([]) do |uris, s| - uris << s.remote if s.remote - uris - end - end - def cached_gem(spec) global_cache_path = download_cache_path(spec) caches << global_cache_path if global_cache_path @@ -439,7 +445,7 @@ module Bundler end def installed?(spec) - installed_specs[spec].any? && !spec.deleted_gem? + installed_specs[spec].any? && !spec.installation_missing? end def rubygems_dir @@ -476,7 +482,10 @@ module Bundler uri = spec.remote.uri Bundler.ui.confirm("Fetching #{version_message(spec, previous_spec)}") gem_remote_fetcher = remote_fetchers.fetch(spec.remote).gem_remote_fetcher - Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + + Gem.time("Downloaded #{spec.name} in", 0, true) do + Bundler.rubygems.download_gem(spec, uri, download_cache_path, gem_remote_fetcher) + end end # Returns the global cache path of the calling Rubygems::Source object. @@ -491,7 +500,7 @@ module Bundler # @return [Pathname] The global cache path. # def download_cache_path(spec) - return unless Bundler.feature_flag.global_gem_cache? + return unless Bundler.settings[:global_gem_cache] return unless remote = spec.remote return unless cache_slug = remote.cache_slug diff --git a/lib/bundler/source/rubygems/remote.rb b/lib/bundler/source/rubygems/remote.rb index 9c5c06de24..ed55912a99 100644 --- a/lib/bundler/source/rubygems/remote.rb +++ b/lib/bundler/source/rubygems/remote.rb @@ -16,6 +16,9 @@ module Bundler @anonymized_uri = remove_auth(@uri).freeze end + MAX_CACHE_SLUG_HOST_SIZE = 255 - 1 - 32 # 255 minus dot minus MD5 length + private_constant :MAX_CACHE_SLUG_HOST_SIZE + # @return [String] A slug suitable for use as a cache key for this # remote. # @@ -28,10 +31,15 @@ module Bundler host = cache_uri.to_s.start_with?("file://") ? nil : cache_uri.host uri_parts = [host, cache_uri.user, cache_uri.port, cache_uri.path] - uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.compact.join(".")) + uri_parts.compact! + uri_digest = SharedHelpers.digest(:MD5).hexdigest(uri_parts.join(".")) + + uri_parts.pop + host_parts = uri_parts.join(".") + return uri_digest if host_parts.empty? - uri_parts[-1] = uri_digest - uri_parts.compact.join(".") + shortened_host_parts = host_parts[0...MAX_CACHE_SLUG_HOST_SIZE] + [shortened_host_parts, uri_digest].join(".") end end diff --git a/lib/bundler/source_list.rb b/lib/bundler/source_list.rb index 5f9dd68f17..38fa0972e6 100644 --- a/lib/bundler/source_list.rb +++ b/lib/bundler/source_list.rb @@ -9,7 +9,7 @@ module Bundler :metadata_source def global_rubygems_source - @global_rubygems_source ||= rubygems_aggregate_class.new("allow_local" => true) + @global_rubygems_source ||= source_class.new("allow_local" => true) end def initialize @@ -21,19 +21,9 @@ module Bundler @rubygems_sources = [] @metadata_source = Source::Metadata.new - @merged_gem_lockfile_sections = false @local_mode = true end - def merged_gem_lockfile_sections? - @merged_gem_lockfile_sections - end - - def merged_gem_lockfile_sections!(replacement_source) - @merged_gem_lockfile_sections = true - @global_rubygems_source = replacement_source - end - def aggregate_global_source? global_rubygems_source.multiple_remotes? end @@ -90,10 +80,6 @@ module Bundler @rubygems_sources end - def rubygems_remotes - rubygems_sources.map(&:remotes).flatten.uniq - end - def all_sources path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source] end @@ -103,7 +89,7 @@ module Bundler end def get(source) - source_list_for(source).find {|s| equivalent_source?(source, s) } + source_list_for(source).find {|s| s.include?(source) } end def lock_sources @@ -115,11 +101,7 @@ module Bundler end def lock_rubygems_sources - if merged_gem_lockfile_sections? - [combine_rubygems_sources] - else - rubygems_sources.sort_by(&:identifier) - end + rubygems_sources.sort_by(&:identifier) end # Returns true if there are changes @@ -129,16 +111,11 @@ module Bundler @rubygems_sources, @path_sources, @git_sources, @plugin_sources = map_sources(replacement_sources) @global_rubygems_source = global_replacement_source(replacement_sources) - different_sources?(lock_sources, replacement_sources) + !equivalent_sources?(lock_sources, replacement_sources) end - # Returns true if there are changes - def expired_sources?(replacement_sources) - return false if replacement_sources.empty? - - lock_sources = dup_with_replaced_sources(replacement_sources).lock_sources - - different_sources?(lock_sources, replacement_sources) + def prefer_local! + all_sources.each(&:prefer_local!) end def local_only! @@ -161,52 +138,60 @@ module Bundler private - def dup_with_replaced_sources(replacement_sources) - new_source_list = dup - new_source_list.replace_sources!(replacement_sources) - new_source_list - end - def map_sources(replacement_sources) rubygems = @rubygems_sources.map do |source| - replace_rubygems_source(replacement_sources, source) || source + replace_rubygems_source(replacement_sources, source) end git, plugin = [@git_sources, @plugin_sources].map do |sources| sources.map do |source| - replacement_sources.find {|s| s == source } || source + replace_source(replacement_sources, source) end end path = @path_sources.map do |source| - replacement_sources.find {|s| s == (source.is_a?(Source::Gemspec) ? source.as_path_source : source) } || source + replace_path_source(replacement_sources, source) end [rubygems, path, git, plugin] end def global_replacement_source(replacement_sources) - replacement_source = replace_rubygems_source(replacement_sources, global_rubygems_source) - return global_rubygems_source unless replacement_source - - replacement_source.local! - replacement_source + replace_rubygems_source(replacement_sources, global_rubygems_source, &:local!) end def replace_rubygems_source(replacement_sources, gemfile_source) + replace_source(replacement_sources, gemfile_source) do |replacement_source| + # locked sources never include credentials so always prefer remotes from the gemfile + replacement_source.remotes = gemfile_source.remotes + + yield replacement_source if block_given? + + replacement_source + end + end + + def replace_source(replacement_sources, gemfile_source) replacement_source = replacement_sources.find {|s| s == gemfile_source } - return unless replacement_source + return gemfile_source unless replacement_source + + replacement_source = yield(replacement_source) if block_given? - # locked sources never include credentials so always prefer remotes from the gemfile - replacement_source.remotes = gemfile_source.remotes replacement_source end - def different_sources?(lock_sources, replacement_sources) - !equivalent_sources?(lock_sources, replacement_sources) + def replace_path_source(replacement_sources, gemfile_source) + replace_source(replacement_sources, gemfile_source) do |replacement_source| + if gemfile_source.is_a?(Source::Gemspec) + gemfile_source.checksum_store = replacement_source.checksum_store + gemfile_source + else + replacement_source + end + end end - def rubygems_aggregate_class + def source_class Source::Rubygems end @@ -225,10 +210,6 @@ module Bundler end end - def combine_rubygems_sources - Source::Rubygems.new("remotes" => rubygems_remotes) - end - def warn_on_git_protocol(source) return if Bundler.settings["git.allow_insecure"] @@ -243,9 +224,5 @@ module Bundler def equivalent_sources?(lock_sources, replacement_sources) lock_sources.sort_by(&:identifier) == replacement_sources.sort_by(&:identifier) end - - def equivalent_source?(source, other_source) - source == other_source - end end end diff --git a/lib/bundler/source_map.rb b/lib/bundler/source_map.rb index ca73e01f9d..cb88caf1bd 100644 --- a/lib/bundler/source_map.rb +++ b/lib/bundler/source_map.rb @@ -23,15 +23,12 @@ module Bundler if previous_source.nil? requirements[indirect_dependency_name] = source else - no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode? - msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."] msg.concat [previous_source, source].map {|s| " * #{s}" }.sort - msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from." + msg << "You must add this gem to the source block for the source you wish it to be installed from." msg = msg.join("\n") - raise SecurityError, msg if no_ambiguous_sources - Bundler.ui.warn "Warning: #{msg}" + raise SecurityError, msg end end diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index fac1bea1ea..f9179e7a06 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -7,55 +7,27 @@ module Bundler include Enumerable include TSort - attr_reader :incomplete_specs - - def initialize(specs, incomplete_specs = []) + def initialize(specs) @specs = specs - @incomplete_specs = incomplete_specs end - def for(dependencies, check = false, platforms = [nil]) - handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h - deps = dependencies.product(platforms) - specs = [] - - loop do - break unless dep = deps.shift - - name = dep[0].name - platform = dep[1] - incomplete = false - - key = [name, platform] - next if handled.key?(key) - - handled[key] = true - - specs_for_dep = specs_for_dependency(*dep) - if specs_for_dep.any? - specs.concat(specs_for_dep) - - specs_for_dep.first.dependencies.each do |d| - next if d.type == :development - incomplete = true if d.name != "bundler" && lookup[d.name].nil? - deps << [d, dep[1]] - end - else - incomplete = true - end - - if incomplete && check - @incomplete_specs += lookup[name] || [LazySpecification.new(name, nil, nil)] - end + def for(dependencies, platforms = [nil], legacy_platforms = [nil], skips: []) + if [true, false].include?(platforms) + Bundler::SharedHelpers.feature_removed! \ + "SpecSet#for received a `check` parameter, but that's no longer used and deprecated. " \ + "SpecSet#for always implicitly performs validation. Please remove this parameter" end - specs.uniq + materialize_dependencies(dependencies, platforms, skips: skips) + + @materializations.flat_map(&:specs).uniq end def normalize_platforms!(deps, platforms) - complete_platforms = add_extra_platforms!(platforms) + remove_invalid_platforms!(deps, platforms) + add_extra_platforms!(platforms) - complete_platforms.map do |platform| + platforms.map! do |platform| next platform if platform == Gem::Platform::RUBY begin @@ -68,26 +40,48 @@ module Bundler next platform if incomplete_for_platform?(deps, less_specific_platform) less_specific_platform - end.uniq + end.uniq! + end + + def add_originally_invalid_platforms!(platforms, originally_invalid_platforms) + originally_invalid_platforms.each do |originally_invalid_platform| + platforms << originally_invalid_platform if complete_platform(originally_invalid_platform) + end + end + + def remove_invalid_platforms!(deps, platforms, skips: []) + invalid_platforms = [] + + platforms.reject! do |platform| + next false if skips.include?(platform) + + invalid = incomplete_for_platform?(deps, platform) + invalid_platforms << platform if invalid + invalid + end + + invalid_platforms end def add_extra_platforms!(platforms) - return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty? + if @specs.empty? + platforms.concat([Gem::Platform::RUBY]).uniq + return + end new_platforms = all_platforms.select do |platform| next if platforms.include?(platform) - next unless GemHelpers.generic(platform) == Gem::Platform::RUBY + next unless Gem::Platform.generic(platform) == Gem::Platform::RUBY complete_platform(platform) end - return platforms if new_platforms.empty? + return if new_platforms.empty? platforms.concat(new_platforms) + return if new_platforms.include?(Bundler.local_platform) less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform } platforms.delete(Bundler.local_platform) if less_specific_platform - - platforms end def validate_deps(s) @@ -107,15 +101,13 @@ module Bundler end def []=(key, value) - @specs << value + delete_by_name(key) - reset! + add_spec(value) end def delete(specs) - Array(specs).each {|spec| @specs.delete(spec) } - - reset! + Array(specs).each {|spec| remove_spec(spec) } end def sort! @@ -131,55 +123,78 @@ module Bundler end def materialize(deps) - materialized = self.for(deps, true) + materialize_dependencies(deps) - SpecSet.new(materialized, incomplete_specs) + SpecSet.new(materialized_specs) end # Materialize for all the specs in the spec set, regardless of what platform they're for - # This is in contrast to how for does platform filtering (and specifically different from how `materialize` calls `for` only for the current platform) # @return [Array<Gem::Specification>] def materialized_for_all_platforms @specs.map do |s| next s unless s.is_a?(LazySpecification) - s.source.remote! - spec = s.materialize_for_installation + spec = s.materialize_for_cache raise GemNotFound, "Could not find #{s.full_name} in any of the sources" unless spec spec end end def incomplete_for_platform?(deps, platform) - return false if @specs.empty? + incomplete_specs_for_platform(deps, platform).any? + end + + def incomplete_specs_for_platform(deps, platform) + return [] if @specs.empty? validation_set = self.class.new(@specs) - validation_set.for(deps, true, [platform]) + validation_set.for(deps, [platform]) + validation_set.incomplete_specs + end + + def missing_specs_for(deps) + materialize_dependencies(deps) - validation_set.incomplete_specs.any? + missing_specs end def missing_specs - @specs.select {|s| s.is_a?(LazySpecification) } + @materializations.flat_map(&:completely_missing_specs) + end + + def partially_missing_specs + @materializations.flat_map(&:partially_missing_specs) + end + + def incomplete_specs + @materializations.flat_map(&:incomplete_specs) + end + + def insecurely_materialized_specs + materialized_specs.select(&:insecurely_materialized?) end def -(other) - SpecSet.new(to_a - other.to_a) + SharedHelpers.feature_removed! "SpecSet#- has been removed with no replacement" end def find_by_name_and_platform(name, platform) - @specs.detect {|spec| spec.name == name && spec.match_platform(platform) } + @specs.detect {|spec| spec.name == name && spec.installable_on_platform?(platform) } end - def specs_compatible_with(other) - select do |spec| - other.valid?(spec) - end + def specs_with_additional_variants_from(other) + sorted | additional_variants_from(other) end def delete_by_name(name) @specs.reject! {|spec| spec.name == name } + @sorted&.reject! {|spec| spec.name == name } + return if @lookup.nil? + + @lookup[name] = nil + end - reset! + def version_for(name) + exemplary_spec(name)&.version end def what_required(spec) @@ -190,7 +205,7 @@ module Bundler end def <<(spec) - @specs << spec + SharedHelpers.feature_removed! "SpecSet#<< has been removed with no replacement" end def length @@ -217,11 +232,41 @@ module Bundler s.matches_current_metadata? && valid_dependencies?(s) end + def to_s + map(&:full_name).to_s + end + private - def reset! - @sorted = nil - @lookup = nil + def materialize_dependencies(dependencies, platforms = [nil], skips: []) + handled = ["bundler"].product(platforms).map {|k| [k, true] }.to_h + deps = dependencies.product(platforms) + @materializations = [] + + loop do + break unless dep = deps.shift + + dependency = dep[0] + platform = dep[1] + name = dependency.name + + key = [name, platform] + next if handled.key?(key) + + handled[key] = true + + materialization = Materialization.new(dependency, platform, candidates: lookup[name]) + + deps.concat(materialization.dependencies) if materialization.complete? + + @materializations << materialization unless skips.include?(name) + end + + @materializations + end + + def materialized_specs + @materializations.filter_map(&:materialized_spec) end def complete_platform(platform) @@ -230,7 +275,7 @@ module Bundler valid_platform = lookup.all? do |_, specs| spec = specs.first matching_specs = spec.source.specs.search([spec.name, spec.version]) - platform_spec = GemHelpers.select_best_platform_match(matching_specs, platform).find do |s| + platform_spec = MatchPlatform.select_best_platform_match(matching_specs, platform).find do |s| valid?(s) end @@ -243,9 +288,7 @@ module Bundler end if valid_platform && new_specs.any? - @specs.concat(new_specs) - - reset! + new_specs.each {|spec| add_spec(spec) } end valid_platform @@ -255,20 +298,28 @@ module Bundler @specs.flat_map {|spec| spec.source.specs.search([spec.name, spec.version]).map(&:platform) }.uniq end + def additional_variants_from(other) + other.select do |other_spec| + spec = exemplary_spec(other_spec.name) + next unless spec + + selected = spec.version == other_spec.version && valid_dependencies?(other_spec) + other_spec.source = spec.source if selected + selected + end + end + def valid_dependencies?(s) validate_deps(s) == :valid end def sorted - rake = @specs.find {|s| s.name == "rake" } - begin - @sorted ||= ([rake] + tsort).compact.uniq - rescue TSort::Cyclic => error - cgems = extract_circular_gems(error) - raise CyclicDependencyError, "Your bundle requires gems that depend" \ - " on each other, creating an infinite loop. Please remove either" \ - " gem '#{cgems[0]}' or gem '#{cgems[1]}' and try again." - end + @sorted ||= ([@specs.find {|s| s.name == "rake" }] + tsort).compact.uniq + rescue TSort::Cyclic => error + cgems = extract_circular_gems(error) + raise CyclicDependencyError, "Your bundle requires gems that depend" \ + " on each other, creating an infinite loop. Please remove either" \ + " gem '#{cgems[0]}' or gem '#{cgems[1]}' and try again." end def extract_circular_gems(error) @@ -279,8 +330,7 @@ module Bundler @lookup ||= begin lookup = {} @specs.each do |s| - lookup[s.name] ||= [] - lookup[s.name] << s + index_spec(lookup, s.name, s) end lookup end @@ -291,17 +341,6 @@ module Bundler @specs.sort_by(&:name).each {|s| yield s } end - def specs_for_dependency(dep, platform) - specs_for_name = lookup[dep.name] - return [] unless specs_for_name - - if platform - GemHelpers.select_best_platform_match(specs_for_name, platform, force_ruby: dep.force_ruby_platform) - else - GemHelpers.select_best_local_platform_match(specs_for_name, force_ruby: dep.force_ruby_platform || dep.default_force_ruby_platform) - end - end - def tsort_each_child(s) s.dependencies.sort_by(&:name).each do |d| next if d.type == :development @@ -312,5 +351,40 @@ module Bundler specs_for_name.each {|s2| yield s2 } end end + + def add_spec(spec) + @specs << spec + + name = spec.name + + @sorted&.insert(@sorted.bsearch_index {|s| s.name >= name } || @sorted.size, spec) + return if @lookup.nil? + + index_spec(@lookup, name, spec) + end + + def remove_spec(spec) + @specs.delete(spec) + @sorted&.delete(spec) + return if @lookup.nil? + + indexed_specs = @lookup[spec.name] + return unless indexed_specs + + if indexed_specs.size > 1 + @lookup[spec.name].delete(spec) + else + @lookup[spec.name] = nil + end + end + + def index_spec(hash, key, value) + hash[key] ||= [] + hash[key] << value + end + + def exemplary_spec(name) + self[name].first + end end end diff --git a/lib/bundler/stub_specification.rb b/lib/bundler/stub_specification.rb index 718920f091..026f753d41 100644 --- a/lib/bundler/stub_specification.rb +++ b/lib/bundler/stub_specification.rb @@ -9,6 +9,10 @@ module Bundler spec end + def insecurely_materialized? + false + end + attr_reader :checksum attr_accessor :stub, :ignored @@ -112,6 +116,10 @@ module Bundler stub.raw_require_paths end + def inspect + "#<#{self.class} @name=\"#{name}\" (#{full_name.delete_prefix("#{name}-")})>" + end + private def _remote_specification diff --git a/lib/bundler/templates/Executable b/lib/bundler/templates/Executable index 9ff6f00898..b085c24da6 100644 --- a/lib/bundler/templates/Executable +++ b/lib/bundler/templates/Executable @@ -10,17 +10,6 @@ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("<%= relative_gemfile_path %>", __dir__) -bundle_binstub = File.expand_path("bundle", __dir__) - -if File.file?(bundle_binstub) - if File.read(bundle_binstub, 300).include?("This file was generated by Bundler") - load(bundle_binstub) - else - abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. -Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") - end -end - require "rubygems" require "bundler/setup" diff --git a/lib/bundler/templates/Executable.bundler b/lib/bundler/templates/Executable.bundler deleted file mode 100644 index caa2021701..0000000000 --- a/lib/bundler/templates/Executable.bundler +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env <%= Bundler.settings[:shebang] || RbConfig::CONFIG["ruby_install_name"] %> -# frozen_string_literal: true - -# -# This file was generated by Bundler. -# -# The application '<%= executable %>' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require "rubygems" - -m = Module.new do - module_function - - def invoked_as_script? - File.expand_path($0) == File.expand_path(__FILE__) - end - - def env_var_version - ENV["BUNDLER_VERSION"] - end - - def cli_arg_version - return unless invoked_as_script? # don't want to hijack other binstubs - return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` - bundler_version = nil - update_index = nil - ARGV.each_with_index do |a, i| - if update_index && update_index.succ == i && a.match?(Gem::Version::ANCHORED_VERSION_PATTERN) - bundler_version = a - end - next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ - bundler_version = $1 - update_index = i - end - bundler_version - end - - def gemfile - gemfile = ENV["BUNDLE_GEMFILE"] - return gemfile if gemfile && !gemfile.empty? - - File.expand_path("<%= relative_gemfile_path %>", __dir__) - end - - def lockfile - lockfile = - case File.basename(gemfile) - when "gems.rb" then gemfile.sub(/\.rb$/, ".locked") - else "#{gemfile}.lock" - end - File.expand_path(lockfile) - end - - def lockfile_version - return unless File.file?(lockfile) - lockfile_contents = File.read(lockfile) - return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ - Regexp.last_match(1) - end - - def bundler_requirement - @bundler_requirement ||= - env_var_version || - cli_arg_version || - bundler_requirement_for(lockfile_version) - end - - def bundler_requirement_for(version) - return "#{Gem::Requirement.default}.a" unless version - - bundler_gem_version = Gem::Version.new(version) - - bundler_gem_version.approximate_recommendation - end - - def load_bundler! - ENV["BUNDLE_GEMFILE"] ||= gemfile - - activate_bundler - end - - def activate_bundler - gem_error = activation_error_handling do - gem "bundler", bundler_requirement - end - return if gem_error.nil? - require_error = activation_error_handling do - require "bundler/version" - end - return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) - warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" - exit 42 - end - - def activation_error_handling - yield - nil - rescue StandardError, LoadError => e - e - end -end - -m.load_bundler! - -if m.invoked_as_script? - load Gem.bin_path("<%= spec.name %>", "<%= executable %>") -end diff --git a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt index 67fe8cee79..633baebdd5 100644 --- a/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt +++ b/lib/bundler/templates/newgem/CODE_OF_CONDUCT.md.tt @@ -1,132 +1,10 @@ -# Contributor Covenant Code of Conduct +# Code of Conduct -## Our Pledge +<%= config[:name].inspect %> follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.): -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. +* Participants will be tolerant of opposing views. +* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks. +* When interpreting the words and actions of others, participants should always assume good intentions. +* Behaviour which can be reasonably considered harassment will not be tolerated. -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official email address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -[INSERT CONTACT METHOD]. -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations +If you have any concerns about behaviour within this project, please contact us at [<%= config[:email].inspect %>](mailto:<%= config[:email].inspect %>). diff --git a/lib/bundler/templates/newgem/Gemfile.tt b/lib/bundler/templates/newgem/Gemfile.tt index de82a63c5f..ea7a33ee28 100644 --- a/lib/bundler/templates/newgem/Gemfile.tt +++ b/lib/bundler/templates/newgem/Gemfile.tt @@ -5,6 +5,7 @@ source "https://rubygems.org" # Specify your gem's dependencies in <%= config[:name] %>.gemspec gemspec +gem "irb" gem "rake", "~> 13.0" <%- if config[:ext] -%> diff --git a/lib/bundler/templates/newgem/README.md.tt b/lib/bundler/templates/newgem/README.md.tt index f9c97d5c7e..0ec6a12fa7 100644 --- a/lib/bundler/templates/newgem/README.md.tt +++ b/lib/bundler/templates/newgem/README.md.tt @@ -26,7 +26,7 @@ TODO: Write usage instructions here ## Development -After checking out the repo, run `bin/setup` to install dependencies.<% if config[:test] %> Then, run `rake <%= config[:test].sub('mini', '').sub('rspec', 'spec') %>` to run the tests.<% end %> You can also run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the gem in this directory, ignoring other installed copies of this gem.<% end %> +After checking out the repo, run `bin/setup` to install dependencies.<% if config[:test] %> Then, run `rake <%= config[:test_task] %>` to run the tests.<% end %> You can also run `bin/console` for an interactive prompt that will allow you to experiment.<% if config[:bin] %> Run `bundle exec <%= config[:name] %>` to use the gem in this directory, ignoring other installed copies of this gem.<% end %> To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org). <% if config[:git] -%> diff --git a/lib/bundler/templates/newgem/Rakefile.tt b/lib/bundler/templates/newgem/Rakefile.tt index 172183d4b4..83f10009c7 100644 --- a/lib/bundler/templates/newgem/Rakefile.tt +++ b/lib/bundler/templates/newgem/Rakefile.tt @@ -59,6 +59,11 @@ Rake::ExtensionTask.new("<%= config[:underscored_name] %>", GEMSPEC) do |ext| end <% end -%> +<% if config[:ext] == "go" -%> +require "go_gem/rake_task" + +GoGem::RakeTask.new("<%= config[:underscored_name] %>") +<% end -%> <% end -%> <% if default_task_names.size == 1 -%> task default: <%= default_task_names.first.inspect %> diff --git a/lib/bundler/templates/newgem/circleci/config.yml.tt b/lib/bundler/templates/newgem/circleci/config.yml.tt index f40f029bf1..c4dd9d0647 100644 --- a/lib/bundler/templates/newgem/circleci/config.yml.tt +++ b/lib/bundler/templates/newgem/circleci/config.yml.tt @@ -7,6 +7,10 @@ jobs: environment: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' <%- end -%> +<%- if config[:ext] == 'go' -%> + environment: + GO_VERSION: '1.23.0' +<%- end -%> steps: - checkout <%- if config[:ext] == 'rust' -%> @@ -17,6 +21,14 @@ jobs: name: Install a RubyGems version that can compile rust extensions command: gem update --system '<%= ::Gem.rubygems_version %>' <%- end -%> +<%- if config[:ext] == 'go' -%> + - run: + name: Install Go + command: | + wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + tar -C /usr/local -xzf /tmp/go.tar.gz + echo 'export PATH=/usr/local/go/bin:"$PATH"' >> "$BASH_ENV" +<%- end -%> - run: name: Run the default task command: | diff --git a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt index 0ebce0e4a0..c0dc63fbfa 100644 --- a/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt +++ b/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt @@ -12,4 +12,4 @@ publish = false crate-type = ["cdylib"] [dependencies] -magnus = { version = "0.6.2" } +magnus = { version = "0.8.2" } diff --git a/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt new file mode 100644 index 0000000000..a689e21ebe --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/extconf-go.rb.tt @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require "mkmf" +require "go_gem/mkmf" + +# Makes all symbols private by default to avoid unintended conflict +# with other gems. To explicitly export symbols you can use RUBY_FUNC_EXPORTED +# selectively, or entirely remove this flag. +append_cflags("-fvisibility=hidden") + +create_go_makefile(<%= config[:makefile_path].inspect %>) diff --git a/lib/bundler/templates/newgem/ext/newgem/go.mod.tt b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt new file mode 100644 index 0000000000..3f4819d004 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/go.mod.tt @@ -0,0 +1,5 @@ +module github.com/<%= config[:go_module_username] %>/<%= config[:underscored_name] %> + +go 1.23 + +require github.com/ruby-go-gem/go-gem-wrapper latest diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt new file mode 100644 index 0000000000..119c0c96ea --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem-go.c.tt @@ -0,0 +1,2 @@ +#include "<%= config[:underscored_name] %>.h" +#include "_cgo_export.h" diff --git a/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt new file mode 100644 index 0000000000..f19b750e58 --- /dev/null +++ b/lib/bundler/templates/newgem/ext/newgem/newgem.go.tt @@ -0,0 +1,31 @@ +package main + +/* +#include "<%= config[:underscored_name] %>.h" + +VALUE rb_<%= config[:underscored_name] %>_sum(VALUE self, VALUE a, VALUE b); +*/ +import "C" + +import ( + "github.com/ruby-go-gem/go-gem-wrapper/ruby" +) + +//export rb_<%= config[:underscored_name] %>_sum +func rb_<%= config[:underscored_name] %>_sum(_ C.VALUE, a C.VALUE, b C.VALUE) C.VALUE { + longA := ruby.NUM2LONG(ruby.VALUE(a)) + longB := ruby.NUM2LONG(ruby.VALUE(b)) + + sum := longA + longB + + return C.VALUE(ruby.LONG2NUM(sum)) +} + +//export Init_<%= config[:underscored_name] %> +func Init_<%= config[:underscored_name] %>() { + rb_m<%= config[:constant_array].join %> := ruby.RbDefineModule(<%= config[:constant_name].inspect %>) + ruby.RbDefineSingletonMethod(rb_m<%= config[:constant_array].join %>, "sum", C.rb_<%= config[:underscored_name] %>_sum, 2) +} + +func main() { +} diff --git a/lib/bundler/templates/newgem/github/workflows/main.yml.tt b/lib/bundler/templates/newgem/github/workflows/main.yml.tt index d1b5ae0534..7f3e3a5b66 100644 --- a/lib/bundler/templates/newgem/github/workflows/main.yml.tt +++ b/lib/bundler/templates/newgem/github/workflows/main.yml.tt @@ -18,6 +18,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false <%- if config[:ext] == 'rust' -%> - name: Set up Ruby & Rust uses: oxidize-rb/actions/setup-ruby-and-rust@v1 @@ -33,5 +35,11 @@ jobs: ruby-version: ${{ matrix.ruby }} bundler-cache: true <%- end -%> +<%- if config[:ext] == 'go' -%> + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: ext/<%= config[:underscored_name] %>/go.mod +<%- end -%> - name: Run the default task run: bundle exec rake diff --git a/lib/bundler/templates/newgem/gitlab-ci.yml.tt b/lib/bundler/templates/newgem/gitlab-ci.yml.tt index d2e1f33736..adbd70cbc0 100644 --- a/lib/bundler/templates/newgem/gitlab-ci.yml.tt +++ b/lib/bundler/templates/newgem/gitlab-ci.yml.tt @@ -6,6 +6,11 @@ default: - apt-get update && apt-get install -y clang - gem update --system '<%= ::Gem.rubygems_version %>' <%- end -%> +<%- if config[:ext] == 'go' -%> + - wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz -O /tmp/go.tar.gz + - tar -C /usr/local -xzf /tmp/go.tar.gz + - export PATH=/usr/local/go/bin:$PATH +<%- end -%> - gem install bundler -v <%= Bundler::VERSION %> - bundle install @@ -14,5 +19,9 @@ example_job: variables: RB_SYS_FORCE_INSTALL_RUST_TOOLCHAIN: 'true' <%- end -%> +<%- if config[:ext] == 'go' -%> + variables: + GO_VERSION: '1.23.0' +<%- end -%> script: - bundle exec rake diff --git a/lib/bundler/templates/newgem/lib/newgem.rb.tt b/lib/bundler/templates/newgem/lib/newgem.rb.tt index caf6e32f4a..3aedee0d25 100644 --- a/lib/bundler/templates/newgem/lib/newgem.rb.tt +++ b/lib/bundler/templates/newgem/lib/newgem.rb.tt @@ -2,7 +2,7 @@ require_relative "<%= File.basename(config[:namespaced_path]) %>/version" <%- if config[:ext] -%> -require_relative "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" +require "<%= File.basename(config[:namespaced_path]) %>/<%= config[:underscored_name] %>" <%- end -%> <%- config[:constant_array].each_with_index do |c, i| -%> diff --git a/lib/bundler/templates/newgem/newgem.gemspec.tt b/lib/bundler/templates/newgem/newgem.gemspec.tt index ced300f379..513875fd63 100644 --- a/lib/bundler/templates/newgem/newgem.gemspec.tt +++ b/lib/bundler/templates/newgem/newgem.gemspec.tt @@ -10,7 +10,7 @@ Gem::Specification.new do |spec| spec.summary = "TODO: Write a short summary, because RubyGems requires one." spec.description = "TODO: Write a longer description or delete this line." - spec.homepage = "TODO: Put your gem's website or public repo URL here." + spec.homepage = "<%= config[:homepage_uri] %>" <%- if config[:mit] -%> spec.license = "MIT" <%- end -%> @@ -20,10 +20,11 @@ Gem::Specification.new do |spec| <%- end -%> spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'" - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here." - spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here." + spec.metadata["source_code_uri"] = "<%= config[:source_code_uri] %>" +<%- if config[:changelog] -%> + spec.metadata["changelog_uri"] = "<%= config[:changelog_uri] %>" +<%- end -%> # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. @@ -31,13 +32,13 @@ Gem::Specification.new do |spec| spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls| ls.readlines("\x0", chomp: true).reject do |f| (f == gemspec) || - f.start_with?(*%w[bin/ test/ spec/ features/ .git <%= config[:ci_config_path] %>appveyor Gemfile]) + f.start_with?(*%w[<%= config[:ignore_paths].join(" ") %>]) end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] -<%- if config[:ext] == 'c' || config[:ext] == 'rust' -%> +<%- if %w(c rust go).include?(config[:ext]) -%> spec.extensions = ["ext/<%= config[:underscored_name] %>/extconf.rb"] <%- end -%> @@ -46,6 +47,9 @@ Gem::Specification.new do |spec| <%- if config[:ext] == 'rust' -%> spec.add_dependency "rb_sys", "~> 0.9.91" <%- end -%> +<%- if config[:ext] == 'go' -%> + spec.add_dependency "go_gem", "~> 0.2" +<%- end -%> # For more information and examples about making a new gem, check out our # guide at: https://bundler.io/guides/creating_gem.html diff --git a/lib/bundler/ui/shell.rb b/lib/bundler/ui/shell.rb index 6df1512a5b..b836208da8 100644 --- a/lib/bundler/ui/shell.rb +++ b/lib/bundler/ui/shell.rb @@ -17,6 +17,7 @@ module Bundler @level = ENV["DEBUG"] ? "debug" : "info" @warning_history = [] @output_stream = :stdout + @thread_safe_logger_key = "logger_level_#{object_id}" end def add_color(string, *color) @@ -80,11 +81,11 @@ module Bundler end def ask(msg) - @shell.ask(msg) + @shell.ask(msg, :green) end def yes?(msg) - @shell.yes?(msg) + @shell.yes?(msg, :green) end def no?(msg) @@ -97,11 +98,13 @@ module Bundler end def level(name = nil) - return @level unless name + current_level = Thread.current.thread_variable_get(@thread_safe_logger_key) || @level + return current_level unless name + unless index = LEVELS.index(name) raise "#{name.inspect} is not a valid level" end - index <= LEVELS.index(@level) + index <= LEVELS.index(current_level) end def output_stream=(symbol) @@ -167,12 +170,13 @@ module Bundler end * "\n" end - def with_level(level) - original = @level - @level = level + def with_level(desired_level) + old_level = level + Thread.current.thread_variable_set(@thread_safe_logger_key, desired_level) + yield ensure - @level = original + Thread.current.thread_variable_set(@thread_safe_logger_key, old_level) end def with_output_stream(symbol) diff --git a/lib/bundler/uri_credentials_filter.rb b/lib/bundler/uri_credentials_filter.rb index a83f5304e2..6804187433 100644 --- a/lib/bundler/uri_credentials_filter.rb +++ b/lib/bundler/uri_credentials_filter.rb @@ -16,7 +16,7 @@ module Bundler if uri.userinfo # oauth authentication - if uri.password == "x-oauth-basic" || uri.password == "x" + if uri.password == "x-oauth-basic" || uri.password == "x" || uri.password.nil? # URI as string does not display with password if no user is set oauth_designation = uri.password uri.user = oauth_designation diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb index 317088a866..e8aaf70016 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool.rb @@ -39,7 +39,7 @@ end # - :auto_reload_after_fork - automatically drop all connections after fork, defaults to true # class Bundler::ConnectionPool - DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true} + DEFAULTS = {size: 5, timeout: 5, auto_reload_after_fork: true}.freeze def self.wrap(options, &block) Wrapper.new(options, &block) @@ -99,10 +99,14 @@ class Bundler::ConnectionPool @available = TimedStack.new(@size, &block) @key = :"pool-#{@available.object_id}" @key_count = :"pool-#{@available.object_id}-count" - INSTANCES[self] = self if INSTANCES + @discard_key = :"pool-#{@available.object_id}-discard" + INSTANCES[self] = self if @auto_reload_after_fork && INSTANCES end def with(options = {}) + # We need to manage exception handling manually here in order + # to work correctly with `Gem::Timeout.timeout` and `Thread#raise`. + # Otherwise an interrupted Thread can leak connections. Thread.handle_interrupt(Exception => :never) do conn = checkout(options) begin @@ -116,20 +120,65 @@ class Bundler::ConnectionPool end alias_method :then, :with + ## + # Marks the current thread's checked-out connection for discard. + # + # When a connection is marked for discard, it will not be returned to the pool + # when checked in. Instead, the connection will be discarded. + # This is useful when a connection has become invalid or corrupted + # and should not be reused. + # + # Takes an optional block that will be called with the connection to be discarded. + # The block should perform any necessary clean-up on the connection. + # + # @yield [conn] + # @yieldparam conn [Object] The connection to be discarded. + # @yieldreturn [void] + # + # + # Note: This only affects the connection currently checked out by the calling thread. + # The connection will be discarded when +checkin+ is called. + # + # @return [void] + # + # @example + # pool.with do |conn| + # begin + # conn.execute("SELECT 1") + # rescue SomeConnectionError + # pool.discard_current_connection # Mark connection as bad + # raise + # end + # end + def discard_current_connection(&block) + ::Thread.current[@discard_key] = block || proc { |conn| conn } + end + def checkout(options = {}) if ::Thread.current[@key] ::Thread.current[@key_count] += 1 ::Thread.current[@key] else ::Thread.current[@key_count] = 1 - ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout) + ::Thread.current[@key] = @available.pop(options[:timeout] || @timeout, options) end end def checkin(force: false) if ::Thread.current[@key] if ::Thread.current[@key_count] == 1 || force - @available.push(::Thread.current[@key]) + if ::Thread.current[@discard_key] + begin + @available.decrement_created + ::Thread.current[@discard_key].call(::Thread.current[@key]) + rescue + nil + ensure + ::Thread.current[@discard_key] = nil + end + else + @available.push(::Thread.current[@key]) + end ::Thread.current[@key] = nil ::Thread.current[@key_count] = nil else @@ -146,7 +195,6 @@ class Bundler::ConnectionPool # Shuts down the Bundler::ConnectionPool by passing each connection to +block+ and # then removing it from the pool. Attempting to checkout a connection after # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+. - def shutdown(&block) @available.shutdown(&block) end @@ -155,11 +203,16 @@ class Bundler::ConnectionPool # Reloads the Bundler::ConnectionPool by passing each connection to +block+ and then # removing it the pool. Subsequent checkouts will create new connections as # needed. - def reload(&block) @available.shutdown(reload: true, &block) end + ## Reaps idle connections that have been idle for over +idle_seconds+. + # +idle_seconds+ defaults to 60. + def reap(idle_seconds = 60, &block) + @available.reap(idle_seconds, &block) + end + # Size of this connection pool attr_reader :size # Automatically drop all connections after fork @@ -169,6 +222,11 @@ class Bundler::ConnectionPool def available @available.length end + + # Number of pool entries created and idle in the pool. + def idle + @available.idle + end end require_relative "connection_pool/timed_stack" diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb index 35d1d7cc35..026d2c5be2 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb @@ -1,8 +1,8 @@ ## # The TimedStack manages a pool of homogeneous connections (or any resource -# you wish to manage). Connections are created lazily up to a given maximum +# you wish to manage). Connections are created lazily up to a given maximum # number. - +# # Examples: # # ts = TimedStack.new(1) { MyConnection.new } @@ -16,14 +16,12 @@ # conn = ts.pop # ts.pop timeout: 5 # #=> raises Bundler::ConnectionPool::TimeoutError after 5 seconds - class Bundler::ConnectionPool::TimedStack attr_reader :max ## # Creates a new pool with +size+ connections that are created from the given # +block+. - def initialize(size = 0, &block) @create_block = block @created = 0 @@ -35,12 +33,12 @@ class Bundler::ConnectionPool::TimedStack end ## - # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be + # Returns +obj+ to the stack. +options+ is ignored in TimedStack but may be # used by subclasses that extend TimedStack. - def push(obj, options = {}) @mutex.synchronize do if @shutdown_block + @created -= 1 unless @created == 0 @shutdown_block.call(obj) else store_connection obj, options @@ -52,14 +50,16 @@ class Bundler::ConnectionPool::TimedStack alias_method :<<, :push ## - # Retrieves a connection from the stack. If a connection is available it is - # immediately returned. If no connection is available within the given + # Retrieves a connection from the stack. If a connection is available it is + # immediately returned. If no connection is available within the given # timeout a Bundler::ConnectionPool::TimeoutError is raised. # - # +:timeout+ is the only checked entry in +options+ and is preferred over - # the +timeout+ argument (which will be removed in a future release). Other - # options may be used by subclasses that extend TimedStack. - + # @option options [Float] :timeout (0.5) Wait this many seconds for an available entry + # @option options [Class] :exception (Bundler::ConnectionPool::TimeoutError) Exception class to raise + # if an entry was not available within the timeout period. Use `exception: false` to return nil. + # + # The +timeout+ argument will be removed in 3.0. + # Other options may be used by subclasses that extend TimedStack. def pop(timeout = 0.5, options = {}) options, timeout = timeout, 0.5 if Hash === timeout timeout = options.fetch :timeout, timeout @@ -68,13 +68,22 @@ class Bundler::ConnectionPool::TimedStack @mutex.synchronize do loop do raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block - return fetch_connection(options) if connection_stored?(options) + if (conn = try_fetch_connection(options)) + return conn + end connection = try_create(options) return connection if connection to_wait = deadline - current_time - raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" if to_wait <= 0 + if to_wait <= 0 + exc = options.fetch(:exception, Bundler::ConnectionPool::TimeoutError) + if exc + raise Bundler::ConnectionPool::TimeoutError, "Waited #{timeout} sec, #{length}/#{@max} available" + else + return nil + end + end @resource.wait(@mutex, to_wait) end end @@ -85,7 +94,6 @@ class Bundler::ConnectionPool::TimedStack # removing it from the pool. Attempting to checkout a connection after # shutdown will raise +Bundler::ConnectionPool::PoolShuttingDownError+ unless # +:reload+ is +true+. - def shutdown(reload: false, &block) raise ArgumentError, "shutdown must receive a block" unless block @@ -99,19 +107,49 @@ class Bundler::ConnectionPool::TimedStack end ## - # Returns +true+ if there are no available connections. + # Reaps connections that were checked in more than +idle_seconds+ ago. + def reap(idle_seconds, &block) + raise ArgumentError, "reap must receive a block" unless block + raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric) + raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block + + idle.times do + conn = + @mutex.synchronize do + raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block + + reserve_idle_connection(idle_seconds) + end + break unless conn + + block.call(conn) + end + end + ## + # Returns +true+ if there are no available connections. def empty? (@created - @que.length) >= @max end ## # The number of connections available on the stack. - def length @max - @created + @que.length end + ## + # The number of connections created and available on the stack. + def idle + @que.length + end + + ## + # Reduce the created count + def decrement_created + @created -= 1 unless @created == 0 + end + private def current_time @@ -121,8 +159,17 @@ class Bundler::ConnectionPool::TimedStack ## # This is an extension point for TimedStack and is called with a mutex. # - # This method must returns true if a connection is available on the stack. + # This method must returns a connection from the stack if one exists. Allows + # subclasses with expensive match/search algorithms to avoid double-handling + # their stack. + def try_fetch_connection(options = nil) + connection_stored?(options) && fetch_connection(options) + end + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must returns true if a connection is available on the stack. def connection_stored?(options = nil) !@que.empty? end @@ -131,31 +178,48 @@ class Bundler::ConnectionPool::TimedStack # This is an extension point for TimedStack and is called with a mutex. # # This method must return a connection from the stack. - def fetch_connection(options = nil) - @que.pop + @que.pop&.first end ## # This is an extension point for TimedStack and is called with a mutex. # # This method must shut down all connections on the stack. - def shutdown_connections(options = nil) - while connection_stored?(options) - conn = fetch_connection(options) + while (conn = try_fetch_connection(options)) + @created -= 1 unless @created == 0 @shutdown_block.call(conn) end - @created = 0 end ## # This is an extension point for TimedStack and is called with a mutex. # - # This method must return +obj+ to the stack. + # This method returns the oldest idle connection if it has been idle for more than idle_seconds. + # This requires that the stack is kept in order of checked in time (oldest first). + def reserve_idle_connection(idle_seconds) + return unless idle_connections?(idle_seconds) + + @created -= 1 unless @created == 0 + + @que.shift.first + end + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # Returns true if the first connection in the stack has been idle for more than idle_seconds + def idle_connections?(idle_seconds) + connection_stored? && (current_time - @que.first.last > idle_seconds) + end + + ## + # This is an extension point for TimedStack and is called with a mutex. + # + # This method must return +obj+ to the stack. def store_connection(obj, options = nil) - @que.push obj + @que.push [obj, current_time] end ## @@ -163,7 +227,6 @@ class Bundler::ConnectionPool::TimedStack # # This method must create a connection if and only if the total number of # connections allowed has not been met. - def try_create(options = nil) unless @created == @max object = @create_block.call diff --git a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb index 384d6fc977..2e9eebdbb6 100644 --- a/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +++ b/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb @@ -1,3 +1,3 @@ class Bundler::ConnectionPool - VERSION = "2.4.1" + VERSION = "2.5.5" end diff --git a/lib/bundler/vendor/fileutils/.document b/lib/bundler/vendor/fileutils/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/fileutils/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/fileutils/lib/fileutils.rb b/lib/bundler/vendor/fileutils/lib/fileutils.rb index 6db19caf6f..a11fdc7176 100644 --- a/lib/bundler/vendor/fileutils/lib/fileutils.rb +++ b/lib/bundler/vendor/fileutils/lib/fileutils.rb @@ -180,7 +180,8 @@ end # - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452]. # module Bundler::FileUtils - VERSION = "1.7.2" + # The version number. + VERSION = "1.8.0" def self.private_module_function(name) #:nodoc: module_function name @@ -705,11 +706,12 @@ module Bundler::FileUtils # def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil) if relative - return ln_sr(src, dest, force: force, noop: noop, verbose: verbose) + return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose) end - fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose + fu_output_message "ln -s#{force ? 'f' : ''}#{ + target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose return if noop - fu_each_src_dest0(src, dest) do |s,d| + fu_each_src_dest0(src, dest, target_directory) do |s,d| remove_file d, true if force File.symlink s, d end @@ -729,42 +731,37 @@ module Bundler::FileUtils # Like Bundler::FileUtils.ln_s, but create links relative to +dest+. # def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil) - options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" - dest = File.path(dest) - srcs = Array(src) - link = proc do |s, target_dir_p = true| - s = File.path(s) - if target_dir_p - d = File.join(destdirs = dest, File.basename(s)) + cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose + fu_each_src_dest0(src, dest, target_directory) do |s,d| + if target_directory + parent = File.dirname(d) + destdirs = fu_split_path(parent) + real_ddirs = fu_split_path(File.realpath(parent)) else - destdirs = File.dirname(d = dest) + destdirs ||= fu_split_path(dest) + real_ddirs ||= fu_split_path(File.realdirpath(dest)) end - destdirs = fu_split_path(File.realpath(destdirs)) - if fu_starting_path?(s) - srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s))) - base = fu_relative_components_from(srcdirs, destdirs) - s = File.join(*base) + srcdirs = fu_split_path(s) + i = fu_common_components(srcdirs, destdirs) + n = destdirs.size - i + n -= 1 unless target_directory + link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1]) + begin + real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil + rescue else - srcdirs = fu_clean_components(*fu_split_path(s)) - base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs) - while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last) - srcdirs.shift - base.pop - end - s = File.join(*base, *srcdirs) + i = fu_common_components(real_sdirs, real_ddirs) + n = real_ddirs.size - i + n -= 1 unless target_directory + link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1]) + link1 = link2 if link1.size > link2.size end - fu_output_message "ln -s#{options} #{s} #{d}" if verbose + s = File.join(link1) + fu_output_message [cmd, s, d].flatten.join(' ') if verbose next if noop remove_file d, true if force File.symlink s, d end - case srcs.size - when 0 - when 1 - link[srcs[0], target_directory && File.directory?(dest)] - else - srcs.each(&link) - end end module_function :ln_sr @@ -799,13 +796,13 @@ module Bundler::FileUtils # File.file?('dest1/dir1/t2.txt') # => true # File.file?('dest1/dir1/t3.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. - # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default). + # - +remove_destination+ - removes +dest+ before creating links (+false+ by default). # # Raises an exception if +dest+ is the path to an existing file or directory - # and keyword argument <tt>remove_destination: true</tt> is not given. + # and optional argument +remove_destination+ is not given. # # Related: Bundler::FileUtils.ln (has different options). # @@ -1028,12 +1025,12 @@ module Bundler::FileUtils # directories, and symbolic links; # other file types (FIFO streams, device files, etc.) are not supported. # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link, - # follows the link. - # - <tt>preserve: true</tt> - preserves file times. - # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - +dereference_root+ - if +src+ is a symbolic link, + # follows the link (+false+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1064,12 +1061,12 @@ module Bundler::FileUtils # Bundler::FileUtils.copy_file('src0.txt', 'dest0.txt') # File.file?('dest0.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference: false</tt> - if +src+ is a symbolic link, - # does not follow the link. - # - <tt>preserve: true</tt> - preserves file times. - # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - +dereference+ - if +src+ is a symbolic link, + # follows the link (+true+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1490,7 +1487,8 @@ module Bundler::FileUtils # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_dir(path, force = false) - remove_entry path, force # FIXME?? check if it is a directory + raise Errno::ENOTDIR, path unless force or File.directory?(path) + remove_entry path, force end module_function :remove_dir @@ -1651,7 +1649,7 @@ module Bundler::FileUtils when "a" mask | 07777 else - raise ArgumentError, "invalid `who' symbol in file mode: #{chr}" + raise ArgumentError, "invalid 'who' symbol in file mode: #{chr}" end end end @@ -1705,7 +1703,7 @@ module Bundler::FileUtils copy_mask = user_mask(chr) (current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111) else - raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}" + raise ArgumentError, "invalid 'perm' symbol in file mode: #{chr}" end end @@ -2028,21 +2026,22 @@ module Bundler::FileUtils private - module StreamUtils_ + module StreamUtils_ # :nodoc: + private case (defined?(::RbConfig) ? ::RbConfig::CONFIG['host_os'] : ::RUBY_PLATFORM) when /mswin|mingw/ - def fu_windows?; true end + def fu_windows?; true end #:nodoc: else - def fu_windows?; false end + def fu_windows?; false end #:nodoc: end def fu_copy_stream0(src, dest, blksize = nil) #:nodoc: IO.copy_stream(src, dest) end - def fu_stream_blksize(*streams) + def fu_stream_blksize(*streams) #:nodoc: streams.each do |s| next unless s.respond_to?(:stat) size = fu_blksize(s.stat) @@ -2051,14 +2050,14 @@ module Bundler::FileUtils fu_default_blksize() end - def fu_blksize(st) + def fu_blksize(st) #:nodoc: s = st.blksize return nil unless s return nil if s == 0 s end - def fu_default_blksize + def fu_default_blksize #:nodoc: 1024 end end @@ -2473,6 +2472,10 @@ module Bundler::FileUtils def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) + unless target_directory or tmp.size <= 1 + tmp = tmp.map {|f| File.path(f)} # A workaround for RBS + raise ArgumentError, "extra target #{tmp}" + end tmp.each do |s| s = File.path(s) yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) @@ -2503,11 +2506,15 @@ module Bundler::FileUtils end private_module_function :fu_output_message - def fu_split_path(path) + def fu_split_path(path) #:nodoc: path = File.path(path) list = [] until (parent, base = File.split(path); parent == path or parent == ".") - list << base + if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path)) + list.pop + else + list << base + end path = parent end list << path @@ -2515,16 +2522,16 @@ module Bundler::FileUtils end private_module_function :fu_split_path - def fu_relative_components_from(target, base) #:nodoc: + def fu_common_components(target, base) #:nodoc: i = 0 while target[i]&.== base[i] i += 1 end - Array.new(base.size-i, '..').concat(target[i..-1]) + i end - private_module_function :fu_relative_components_from + private_module_function :fu_common_components - def fu_clean_components(*comp) + def fu_clean_components(*comp) #:nodoc: comp.shift while comp.first == "." return comp if comp.empty? clean = [comp.shift] @@ -2532,7 +2539,7 @@ module Bundler::FileUtils while c = comp.shift if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) clean.pop - path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + path.sub!(%r((?<=\A|/)[^/]+/\z), "") else clean << c path << c << "/" @@ -2543,11 +2550,11 @@ module Bundler::FileUtils private_module_function :fu_clean_components if fu_windows? - def fu_starting_path?(path) + def fu_starting_path?(path) #:nodoc: path&.start_with?(%r(\w:|/)) end else - def fu_starting_path?(path) + def fu_starting_path?(path) #:nodoc: path&.start_with?("/") end end diff --git a/lib/bundler/vendor/net-http-persistent/.document b/lib/bundler/vendor/net-http-persistent/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/net-http-persistent/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb index cfc0f48197..93e403a5bb 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb @@ -1,6 +1,10 @@ require_relative '../../../../../vendored_net_http' require_relative '../../../../../vendored_uri' -require 'cgi' # for escaping +begin + require 'cgi/escape' +rescue LoadError + require 'cgi/util' # for escaping +end require_relative '../../../../connection_pool/lib/connection_pool' autoload :OpenSSL, 'openssl' @@ -42,9 +46,8 @@ autoload :OpenSSL, 'openssl' # # perform the POST, the Gem::URI is always required # response http.request post_uri, post # -# Note that for GET, HEAD and other requests that do not have a body you want -# to use Gem::URI#request_uri not Gem::URI#path. The request_uri contains the query -# params which are sent in the body for other requests. +# ⚠ Note that for GET, HEAD and other requests that do not have a body, +# it uses Gem::URI#request_uri as default to send query params # # == TLS/SSL # @@ -60,6 +63,7 @@ autoload :OpenSSL, 'openssl' # #ca_path :: Directory with certificate-authorities # #cert_store :: An SSL certificate store # #ciphers :: List of SSl ciphers allowed +# #extra_chain_cert :: Extra certificates to be added to the certificate chain # #private_key :: The client's SSL private key # #reuse_ssl_sessions :: Reuse a previously opened SSL session for a new # connection @@ -176,7 +180,7 @@ class Gem::Net::HTTP::Persistent ## # The version of Gem::Net::HTTP::Persistent you are using - VERSION = '4.0.4' + VERSION = '4.0.6' ## # Error class for errors raised by Gem::Net::HTTP::Persistent. Various @@ -268,6 +272,11 @@ class Gem::Net::HTTP::Persistent attr_reader :ciphers ## + # Extra certificates to be added to the certificate chain + + attr_reader :extra_chain_cert + + ## # Sends debug_output to this IO via Gem::Net::HTTP#set_debug_output. # # Never use this method in production code, it causes a serious security @@ -587,6 +596,21 @@ class Gem::Net::HTTP::Persistent reconnect_ssl end + if Gem::Net::HTTP.method_defined?(:extra_chain_cert=) + ## + # Extra certificates to be added to the certificate chain. + # It is only supported starting from Gem::Net::HTTP version 0.1.1 + def extra_chain_cert= extra_chain_cert + @extra_chain_cert = extra_chain_cert + + reconnect_ssl + end + else + def extra_chain_cert= _extra_chain_cert + raise "extra_chain_cert= is not supported by this version of Gem::Net::HTTP" + end + end + ## # Creates a new connection for +uri+ @@ -605,47 +629,49 @@ class Gem::Net::HTTP::Persistent connection = @pool.checkout net_http_args - http = connection.http + begin + http = connection.http - connection.ressl @ssl_generation if - connection.ssl_generation != @ssl_generation + connection.ressl @ssl_generation if + connection.ssl_generation != @ssl_generation - if not http.started? then - ssl http if use_ssl - start http - elsif expired? connection then - reset connection - end + if not http.started? then + ssl http if use_ssl + start http + elsif expired? connection then + reset connection + end - http.keep_alive_timeout = @idle_timeout if @idle_timeout - http.max_retries = @max_retries if http.respond_to?(:max_retries=) - http.read_timeout = @read_timeout if @read_timeout - http.write_timeout = @write_timeout if - @write_timeout && http.respond_to?(:write_timeout=) + http.keep_alive_timeout = @idle_timeout if @idle_timeout + http.max_retries = @max_retries if http.respond_to?(:max_retries=) + http.read_timeout = @read_timeout if @read_timeout + http.write_timeout = @write_timeout if + @write_timeout && http.respond_to?(:write_timeout=) + + return yield connection + rescue Errno::ECONNREFUSED + if http.proxy? + address = http.proxy_address + port = http.proxy_port + else + address = http.address + port = http.port + end - return yield connection - rescue Errno::ECONNREFUSED - if http.proxy? - address = http.proxy_address - port = http.proxy_port - else - address = http.address - port = http.port - end + raise Error, "connection refused: #{address}:#{port}" + rescue Errno::EHOSTDOWN + if http.proxy? + address = http.proxy_address + port = http.proxy_port + else + address = http.address + port = http.port + end - raise Error, "connection refused: #{address}:#{port}" - rescue Errno::EHOSTDOWN - if http.proxy? - address = http.proxy_address - port = http.proxy_port - else - address = http.address - port = http.port + raise Error, "host down: #{address}:#{port}" + ensure + @pool.checkin net_http_args end - - raise Error, "host down: #{address}:#{port}" - ensure - @pool.checkin net_http_args end ## @@ -782,7 +808,7 @@ class Gem::Net::HTTP::Persistent @proxy_connection_id = [nil, *@proxy_args].join ':' if @proxy_uri.query then - @no_proxy = CGI.parse(@proxy_uri.query)['no_proxy'].join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? } + @no_proxy = Gem::URI.decode_www_form(@proxy_uri.query).filter_map { |k, v| v if k == 'no_proxy' }.join(',').downcase.split(',').map { |x| x.strip }.reject { |x| x.empty? } end end @@ -953,7 +979,8 @@ class Gem::Net::HTTP::Persistent end ## - # Shuts down all connections + # Shuts down all connections. Attempting to checkout a connection after + # shutdown will raise an error. # # *NOTE*: Calling shutdown for can be dangerous! # @@ -965,6 +992,17 @@ class Gem::Net::HTTP::Persistent end ## + # Discard all existing connections. Subsequent checkouts will create + # new connections as needed. + # + # If any thread is still using a connection it may cause an error! Call + # #reload when you are completely done making requests! + + def reload + @pool.reload { |http| http.finish } + end + + ## # Enables SSL on +connection+ def ssl connection @@ -1021,6 +1059,10 @@ application: connection.key = @private_key end + if defined?(@extra_chain_cert) and @extra_chain_cert + connection.extra_chain_cert = @extra_chain_cert + end + connection.cert_store = if @cert_store then @cert_store else diff --git a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb index 214804fcd9..034fbe39b8 100644 --- a/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +++ b/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb @@ -63,7 +63,8 @@ class Gem::Net::HTTP::Persistent::TimedStackMulti < Bundler::ConnectionPool::Tim if @created >= @max && @enqueued >= 1 oldest, = @lru.first @lru.delete oldest - @ques[oldest].pop + connection = @ques[oldest].pop + connection.close if connection.respond_to?(:close) @created -= 1 end diff --git a/lib/bundler/vendor/pub_grub/.document b/lib/bundler/vendor/pub_grub/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/pub_grub/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb index dce20d37ad..491151ec0b 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb @@ -79,29 +79,17 @@ module Bundler::PubGrub dependencies_for(@root_package, @root_version) end - # Override me (maybe) - # - # If not overridden, the order returned by all_versions_for will be used - # - # Returns: Array of versions in preferred order - def sort_versions_by_preferred(package, sorted_versions) - indexes = @version_indexes[package] - sorted_versions.sort_by { |version| indexes[version] } - end - def initialize @root_package = Package.root @root_version = Package.root_version - @cached_versions = Hash.new do |h,k| + @sorted_versions = Hash.new do |h,k| if k == @root_package h[k] = [@root_version] else - h[k] = all_versions_for(k) + h[k] = all_versions_for(k).sort end end - @sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort } - @version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h } @cached_dependencies = Hash.new do |packages, package| if package == @root_package @@ -117,15 +105,7 @@ module Bundler::PubGrub end def versions_for(package, range=VersionRange.any) - versions = range.select_versions(@sorted_versions[package]) - - # Conditional avoids (among other things) calling - # sort_versions_by_preferred with the root package - if versions.size > 1 - sort_versions_by_preferred(package, versions) - else - versions - end + range.select_versions(@sorted_versions[package]) end def no_versions_incompatibility_for(_package, unsatisfied_term) @@ -164,7 +144,7 @@ module Bundler::PubGrub sorted_versions[high] end - range = VersionRange.new(min: low, max: high, include_min: true) + range = VersionRange.new(min: low, max: high, include_min: !low.nil?) self_constraint = VersionConstraint.new(package, range: range) diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/strategy.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/strategy.rb new file mode 100644 index 0000000000..6955655ba4 --- /dev/null +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/strategy.rb @@ -0,0 +1,42 @@ +module Bundler::PubGrub + class Strategy + def initialize(source) + @source = source + + @root_package = Package.root + @root_version = Package.root_version + + @version_indexes = Hash.new do |h,k| + if k == @root_package + h[k] = { @root_version => 0 } + else + h[k] = @source.all_versions_for(k).each.with_index.to_h + end + end + end + + def next_package_and_version(unsatisfied) + package, range = next_term_to_try_from(unsatisfied) + + [package, most_preferred_version_of(package, range)] + end + + private + + def most_preferred_version_of(package, range) + versions = @source.versions_for(package, range) + + indexes = @version_indexes[package] + versions.min_by { |version| indexes[version] } + end + + def next_term_to_try_from(unsatisfied) + unsatisfied.min_by do |package, range| + matching_versions = @source.versions_for(package, range) + higher_versions = @source.versions_for(package, range.upper_invert) + + [matching_versions.count <= 1 ? 0 : 1, higher_versions.count] + end + end + end +end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb index 8d73c3f7b5..49dcf716a3 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb @@ -76,6 +76,9 @@ module Bundler::PubGrub end def initialize(min: nil, max: nil, include_min: false, include_max: false, name: nil) + raise ArgumentError, "Ranges without a lower bound cannot have include_min == true" if !min && include_min == true + raise ArgumentError, "Ranges without an upper bound cannot have include_max == true" if !max && include_max == true + @min = min @max = max @include_min = include_min @@ -311,10 +314,19 @@ module Bundler::PubGrub def contiguous_to?(other) return false if other.empty? + return true if any? + + intersects?(other) || contiguous_below?(other) || contiguous_above?(other) + end + + def contiguous_below?(other) + return false if !max || !other.min + + max == other.min && (include_max || other.include_min) + end - intersects?(other) || - (min == other.max && (include_min || other.include_max)) || - (max == other.min && (include_max || other.include_min)) + def contiguous_above?(other) + other.contiguous_below?(self) end def allows_all?(other) @@ -375,15 +387,15 @@ module Bundler::PubGrub def invert return self.class.empty if any? - low = VersionRange.new(max: min, include_max: !include_min) - high = VersionRange.new(min: max, include_min: !include_max) + low = -> { VersionRange.new(max: min, include_max: !include_min) } + high = -> { VersionRange.new(min: max, include_min: !include_max) } if !min - high + high.call elsif !max - low + low.call else - low.union(high) + low.call.union(high.call) end end diff --git a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb index 4caf6b355b..000923e99a 100644 --- a/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb +++ b/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb @@ -2,17 +2,20 @@ require_relative 'partial_solution' require_relative 'term' require_relative 'incompatibility' require_relative 'solve_failure' +require_relative 'strategy' module Bundler::PubGrub class VersionSolver attr_reader :logger attr_reader :source attr_reader :solution + attr_reader :strategy - def initialize(source:, root: Package.root, logger: Bundler::PubGrub.logger) + def initialize(source:, root: Package.root, strategy: Strategy.new(source), logger: Bundler::PubGrub.logger) @logger = logger @source = source + @strategy = strategy # { package => [incompatibility, ...]} @incompatibilities = Hash.new do |h, k| @@ -36,26 +39,25 @@ module Bundler::PubGrub # Returns true if there is more work to be done, false otherwise def work - return false if solved? - - next_package = choose_package_version - propagate(next_package) - - if solved? + unsatisfied_terms = solution.unsatisfied + if unsatisfied_terms.empty? logger.info { "Solution found after #{solution.attempted_solutions} attempts:" } solution.decisions.each do |package, version| next if Package.root?(package) logger.info { "* #{package} #{version}" } end - false - else - true + return false end + + next_package = choose_package_version_from(unsatisfied_terms) + propagate(next_package) + + true end def solve - work until solved? + while work; end solution.decisions end @@ -105,29 +107,15 @@ module Bundler::PubGrub unsatisfied.package end - def next_package_to_try - solution.unsatisfied.min_by do |term| - package = term.package - range = term.constraint.range - matching_versions = source.versions_for(package, range) - higher_versions = source.versions_for(package, range.upper_invert) + def choose_package_version_from(unsatisfied_terms) + remaining = unsatisfied_terms.map { |t| [t.package, t.constraint.range] }.to_h - [matching_versions.count <= 1 ? 0 : 1, higher_versions.count] - end.package - end - - def choose_package_version - if solution.unsatisfied.empty? - logger.info "No packages unsatisfied. Solving complete!" - return nil - end + package, version = strategy.next_package_and_version(remaining) - package = next_package_to_try - unsatisfied_term = solution.unsatisfied.find { |t| t.package == package } - version = source.versions_for(package, unsatisfied_term.constraint.range).first logger.debug { "attempting #{package} #{version}" } if version.nil? + unsatisfied_term = unsatisfied_terms.find { |t| t.package == package } add_incompatibility source.no_versions_incompatibility_for(package, unsatisfied_term) return package end diff --git a/lib/bundler/vendor/securerandom/.document b/lib/bundler/vendor/securerandom/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/securerandom/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/securerandom/lib/random/formatter.rb b/lib/bundler/vendor/securerandom/lib/random/formatter.rb deleted file mode 100644 index e429709789..0000000000 --- a/lib/bundler/vendor/securerandom/lib/random/formatter.rb +++ /dev/null @@ -1,373 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true - -# == \Random number formatter. -# -# Formats generated random numbers in many manners. When <tt>'random/formatter'</tt> -# is required, several methods are added to empty core module <tt>Bundler::Random::Formatter</tt>, -# making them available as Random's instance and module methods. -# -# Standard library Bundler::SecureRandom is also extended with the module, and the methods -# described below are available as a module methods in it. -# -# === Examples -# -# Generate random hexadecimal strings: -# -# require 'bundler/vendor/securerandom/lib/random/formatter' -# -# prng = Random.new -# prng.hex(10) #=> "52750b30ffbc7de3b362" -# prng.hex(10) #=> "92b15d6c8dc4beb5f559" -# prng.hex(13) #=> "39b290146bea6ce975c37cfc23" -# # or just -# Random.hex #=> "1aed0c631e41be7f77365415541052ee" -# -# Generate random base64 strings: -# -# prng.base64(10) #=> "EcmTPZwWRAozdA==" -# prng.base64(10) #=> "KO1nIU+p9DKxGg==" -# prng.base64(12) #=> "7kJSM/MzBJI+75j8" -# Random.base64(4) #=> "bsQ3fQ==" -# -# Generate random binary strings: -# -# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" -# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" -# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" -# -# Generate alphanumeric strings: -# -# prng.alphanumeric(10) #=> "S8baxMJnPl" -# prng.alphanumeric(10) #=> "aOxAg8BAJe" -# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" -# -# Generate UUIDs: -# -# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" -# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" -# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" -# -# All methods are available in the standard library Bundler::SecureRandom, too: -# -# Bundler::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" - -module Bundler::Random::Formatter - - # Generate a random binary string. - # - # The argument _n_ specifies the length of the result string. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in future. - # - # The result may contain any byte: "\x00" - "\xff". - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" - # # or - # prng = Random.new - # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" - def random_bytes(n=nil) - n = n ? n.to_int : 16 - gen_random(n) - end - - # Generate a random hexadecimal string. - # - # The argument _n_ specifies the length, in bytes, of the random number to be generated. - # The length of the resulting hexadecimal string is twice of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain 0-9 and a-f. - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" - # # or - # prng = Random.new - # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" - def hex(n=nil) - random_bytes(n).unpack1("H*") - end - - # Generate a random base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" - # # or - # prng = Random.new - # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" - # - # See RFC 3548 for the definition of base64. - def base64(n=nil) - [random_bytes(n)].pack("m0") - end - - # Generate a random URL-safe base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The boolean argument _padding_ specifies the padding. - # If it is false or nil, padding is not generated. - # Otherwise padding is generated. - # By default, padding is not generated because "=" may be used as a URL delimiter. - # - # The result may contain A-Z, a-z, 0-9, "-" and "_". - # "=" is also used if _padding_ is true. - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" - # # or - # prng = Random.new - # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" - # - # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" - # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" - # - # See RFC 3548 for the definition of URL-safe base64. - def urlsafe_base64(n=nil, padding=false) - s = [random_bytes(n)].pack("m0") - s.tr!("+/", "-_") - s.delete!("=") unless padding - s - end - - # Generate a random v4 UUID (Universally Unique IDentifier). - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" - # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" - # # or - # prng = Random.new - # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" - # - # The version 4 UUID is purely random (except the version). - # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. - # - # The result contains 122 random bits (15.25 random bytes). - # - # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. - # - def uuid - ary = random_bytes(16).unpack("NnnnnN") - ary[2] = (ary[2] & 0x0fff) | 0x4000 - ary[3] = (ary[3] & 0x3fff) | 0x8000 - "%08x-%04x-%04x-%04x-%04x%08x" % ary - end - - alias uuid_v4 uuid - - # Generate a random v7 UUID (Universally Unique IDentifier). - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" - # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" - # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" - # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" - # # |<--sorted-->| |<----- random ---->| - # - # # or - # prng = Random.new - # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" - # - # The version 7 UUID starts with the least significant 48 bits of a 64 bit - # Unix timestamp (milliseconds since the epoch) and fills the remaining bits - # with random data, excluding the version and variant bits. - # - # This allows version 7 UUIDs to be sorted by creation time. Time ordered - # UUIDs can be used for better database index locality of newly inserted - # records, which may have a significant performance benefit compared to random - # data inserts. - # - # The result contains 74 random bits (9.25 random bytes). - # - # Note that this method cannot be made reproducable because its output - # includes not only random bits but also timestamp. - # - # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] - # for details of UUIDv7. - # - # ==== Monotonicity - # - # UUIDv7 has millisecond precision by default, so multiple UUIDs created - # within the same millisecond are not issued in monotonically increasing - # order. To create UUIDs that are time-ordered with sub-millisecond - # precision, up to 12 bits of additional timestamp may added with - # +extra_timestamp_bits+. The extra timestamp precision comes at the expense - # of random bits. Setting <tt>extra_timestamp_bits: 12</tt> provides ~244ns - # of precision, but only 62 random bits (7.75 random bytes). - # - # prng = Random.new - # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } - # # => - # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", - # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", - # "0188d4c7-13da-754a-88ea-ac0baeedd8db", - # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] - # # |<--- sorted --->| |<-- random --->| - # - # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } - # # => - # ["0188d4c7-3333-7a95-850a-de6edb858f7e", - # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order - # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order - # "0188d4c7-3333-7af9-87c3-8f612edac82e"] - # # |<--- sorted -->||<---- random --->| - # - # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based - # on UTC, which excludes leap seconds and can rollback the clock. To avoid - # this, the system clock can synchronize with an NTP server configured to use - # a "leap smear" approach. NTP or PTP will also be needed to synchronize - # across distributed nodes. - # - # Counters and other mechanisms for stronger guarantees of monotonicity are - # not implemented. Applications with stricter requirements should follow - # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] - # of the specification. - # - def uuid_v7(extra_timestamp_bits: 0) - case (extra_timestamp_bits = Integer(extra_timestamp_bits)) - when 0 # min timestamp precision - ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) - rand = random_bytes(10) - rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version - rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant - "%08x-%04x-%s" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - rand.unpack("H4H4H12").join("-") - ] - - when 12 # max timestamp precision - ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) - .divmod(1_000_000) - extra_bits = ns * 4096 / 1_000_000 - rand = random_bytes(8) - rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant - "%08x-%04x-7%03x-%s" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - extra_bits, - rand.unpack("H4H12").join("-") - ] - - when (0..12) # the generic version is slower than the special cases above - rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") - rand_mask_bits = 12 - extra_timestamp_bits - ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) - .divmod(1_000_000) - "%08x-%04x-%04x-%04x-%04x%08x" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - 0x7000 | - ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | - rand_a & ((1 << rand_mask_bits) - 1), - 0x8000 | (rand_b1 & 0x3fff), - rand_b2, - rand_b3 - ] - - else - raise ArgumentError, "extra_timestamp_bits must be in 0..12" - end - end - - # Internal interface to Random; Generate random data _n_ bytes. - private def gen_random(n) - self.bytes(n) - end - - # Generate a string that randomly draws from a - # source array of characters. - # - # The argument _source_ specifies the array of characters from which - # to generate the string. - # The argument _n_ specifies the length, in characters, of the string to be - # generated. - # - # The result may contain whatever characters are in the source array. - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" - # prng.choose([*'0'..'9'], 5) #=> "27309" - private def choose(source, n) - size = source.size - m = 1 - limit = size - while limit * size <= 0x100000000 - limit *= size - m += 1 - end - result = ''.dup - while m <= n - rs = random_number(limit) - is = rs.digits(size) - (m-is.length).times { is << 0 } - result << source.values_at(*is).join('') - n -= m - end - if 0 < n - rs = random_number(limit) - is = rs.digits(size) - if is.length < n - (n-is.length).times { is << 0 } - else - is.pop while n < is.length - end - result.concat source.values_at(*is).join('') - end - result - end - - # The default character list for #alphanumeric. - ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] - - # Generate a random alphanumeric string. - # - # The argument _n_ specifies the length, in characters, of the alphanumeric - # string to be generated. - # The argument _chars_ specifies the character list which the result is - # consist of. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. - # - # require 'bundler/vendor/securerandom/lib/random/formatter' - # - # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" - # # or - # prng = Random.new - # prng.alphanumeric(10) #=> "i6K93NdqiH" - # - # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" - # # or - # prng = Random.new - # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." - def alphanumeric(n = nil, chars: ALPHANUMERIC) - n = 16 if n.nil? - choose(chars, n) - end -end diff --git a/lib/bundler/vendor/securerandom/lib/securerandom.rb b/lib/bundler/vendor/securerandom/lib/securerandom.rb index e797054468..01b7fa15a6 100644 --- a/lib/bundler/vendor/securerandom/lib/securerandom.rb +++ b/lib/bundler/vendor/securerandom/lib/securerandom.rb @@ -1,7 +1,7 @@ # -*- coding: us-ascii -*- # frozen_string_literal: true -require_relative 'random/formatter' +require 'random/formatter' # == Secure random number generator interface. # @@ -18,7 +18,7 @@ require_relative 'random/formatter' # * /dev/urandom # * Win32 # -# Bundler::SecureRandom is extended by the Bundler::Random::Formatter module which +# Bundler::SecureRandom is extended by the Random::Formatter module which # defines the following methods: # # * alphanumeric @@ -41,7 +41,7 @@ require_relative 'random/formatter' module Bundler::SecureRandom # The version - VERSION = "0.3.1" + VERSION = "0.4.1" class << self # Returns a random binary string containing +size+ bytes. @@ -51,6 +51,12 @@ module Bundler::SecureRandom return gen_random(n) end + # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end if RUBY_VERSION < '3.3' + private # :stopdoc: @@ -88,9 +94,9 @@ module Bundler::SecureRandom # :startdoc: - # Generate random data bytes for Bundler::Random::Formatter + # Generate random data bytes for Random::Formatter public :gen_random end end -Bundler::SecureRandom.extend(Bundler::Random::Formatter) +Bundler::SecureRandom.extend(Random::Formatter) diff --git a/lib/bundler/vendor/thor/.document b/lib/bundler/vendor/thor/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/thor/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/thor/lib/thor.rb b/lib/bundler/vendor/thor/lib/thor.rb index 627722164f..945bdbd551 100644 --- a/lib/bundler/vendor/thor/lib/thor.rb +++ b/lib/bundler/vendor/thor/lib/thor.rb @@ -439,6 +439,17 @@ class Bundler::Thor command && disable_required_check.include?(command.name.to_sym) end + # Checks if a specified command exists. + # + # ==== Parameters + # command_name<String>:: The name of the command to check for existence. + # + # ==== Returns + # Boolean:: +true+ if the command exists, +false+ otherwise. + def command_exists?(command_name) #:nodoc: + commands.keys.include?(normalize_command_name(command_name)) + end + protected # Returns this class exclusive options array set. @@ -614,7 +625,7 @@ class Bundler::Thor # alias name. def find_command_possibilities(meth) len = meth.to_s.length - possibilities = all_commands.merge(map).keys.select { |n| meth == n[0, len] }.sort + possibilities = all_commands.reject { |_k, c| c.hidden? }.merge(map).keys.select { |n| meth == n[0, len] }.sort unique_possibilities = possibilities.map { |k| map[k] || k }.uniq if possibilities.include?(meth) diff --git a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb index 80a0255996..d8c9863054 100644 --- a/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb +++ b/lib/bundler/vendor/thor/lib/thor/actions/file_manipulation.rb @@ -10,7 +10,6 @@ class Bundler::Thor # destination<String>:: the relative path to the destination root. # config<Hash>:: give :verbose => false to not log the status, and # :mode => :preserve, to preserve the file mode from the source. - # # ==== Examples # @@ -243,6 +242,35 @@ class Bundler::Thor insert_into_file(path, *(args << config), &block) end + # Run a regular expression replacement on a file, raising an error if the + # contents of the file are not changed. + # + # ==== Parameters + # path<String>:: path of the file to be changed + # flag<Regexp|String>:: the regexp or string to be replaced + # replacement<String>:: the replacement, can be also given as a block + # config<Hash>:: give :verbose => false to not log the status, and + # :force => true, to force the replacement regardless of runner behavior. + # + # ==== Example + # + # gsub_file! 'app/controllers/application_controller.rb', /#\s*(filter_parameter_logging :password)/, '\1' + # + # gsub_file! 'README', /rake/, :green do |match| + # match << " no more. Use thor!" + # end + # + def gsub_file!(path, flag, *args, &block) + config = args.last.is_a?(Hash) ? args.pop : {} + + return unless behavior == :invoke || config.fetch(:force, false) + + path = File.expand_path(path, destination_root) + say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) + + actually_gsub_file(path, flag, args, true, &block) unless options[:pretend] + end + # Run a regular expression replacement on a file. # # ==== Parameters @@ -268,16 +296,11 @@ class Bundler::Thor path = File.expand_path(path, destination_root) say_status :gsub, relative_to_original_destination_root(path), config.fetch(:verbose, true) - unless options[:pretend] - content = File.binread(path) - content.gsub!(flag, *args, &block) - File.open(path, "wb") { |file| file.write(content) } - end + actually_gsub_file(path, flag, args, false, &block) unless options[:pretend] end - # Uncomment all lines matching a given regex. It will leave the space - # which existed before the comment hash in tact but will remove any spacing - # between the comment hash and the beginning of the line. + # Uncomment all lines matching a given regex. Preserves indentation before + # the comment hash and removes the hash and any immediate following space. # # ==== Parameters # path<String>:: path of the file to be changed @@ -291,7 +314,7 @@ class Bundler::Thor def uncomment_lines(path, flag, *args) flag = flag.respond_to?(:source) ? flag.source : flag - gsub_file(path, /^(\s*)#[[:blank:]]*(.*#{flag})/, '\1\2', *args) + gsub_file(path, /^(\s*)#[[:blank:]]?(.*#{flag})/, '\1\2', *args) end # Comment all lines matching a given regex. It will leave the space @@ -350,7 +373,7 @@ class Bundler::Thor end def with_output_buffer(buf = "".dup) #:nodoc: - raise ArgumentError, "Buffer can not be a frozen object" if buf.frozen? + raise ArgumentError, "Buffer cannot be a frozen object" if buf.frozen? old_buffer = output_buffer self.output_buffer = buf yield @@ -359,6 +382,17 @@ class Bundler::Thor self.output_buffer = old_buffer end + def actually_gsub_file(path, flag, args, error_on_no_change, &block) + content = File.binread(path) + success = content.gsub!(flag, *args, &block) + + if success.nil? && error_on_no_change + raise Bundler::Thor::Error, "The content of #{path} did not change" + end + + File.open(path, "wb") { |file| file.write(content) } + end + # Bundler::Thor::Actions#capture depends on what kind of buffer is used in ERB. # Thus CapturableERB fixes ERB to use String buffer. class CapturableERB < ERB diff --git a/lib/bundler/vendor/thor/lib/thor/group.rb b/lib/bundler/vendor/thor/lib/thor/group.rb index 7ea11e8f93..30bc311294 100644 --- a/lib/bundler/vendor/thor/lib/thor/group.rb +++ b/lib/bundler/vendor/thor/lib/thor/group.rb @@ -211,6 +211,17 @@ class Bundler::Thor::Group raise error, msg end + # Checks if a specified command exists. + # + # ==== Parameters + # command_name<String>:: The name of the command to check for existence. + # + # ==== Returns + # Boolean:: +true+ if the command exists, +false+ otherwise. + def command_exists?(command_name) #:nodoc: + commands.keys.include?(command_name) + end + protected # The method responsible for dispatching given the args. diff --git a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb index b9e94e4669..ee9db4ad8a 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/argument.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/argument.rb @@ -26,10 +26,7 @@ class Bundler::Thor def print_default if @type == :array and @default.is_a?(Array) - @default.map { |x| - p = x.gsub('"','\\"') - "\"#{p}\"" - }.join(" ") + @default.map(&:dump).join(" ") else @default end diff --git a/lib/bundler/vendor/thor/lib/thor/parser/option.rb b/lib/bundler/vendor/thor/lib/thor/parser/option.rb index c6af4e1e87..72617c7e34 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/option.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/option.rb @@ -89,8 +89,8 @@ class Bundler::Thor sample = "[#{sample}]".dup unless required? - if boolean? - sample << ", [#{dasherize('no-' + human_name)}]" unless (name == "force") || name.match(/\Ano[\-_]/) + if boolean? && name != "force" && !name.match(/\A(no|skip)[\-_]/) + sample << ", [#{dasherize('no-' + human_name)}], [#{dasherize('skip-' + human_name)}]" end aliases_for_usage.ljust(padding) + sample diff --git a/lib/bundler/vendor/thor/lib/thor/parser/options.rb b/lib/bundler/vendor/thor/lib/thor/parser/options.rb index 978e76b132..fe22d989e5 100644 --- a/lib/bundler/vendor/thor/lib/thor/parser/options.rb +++ b/lib/bundler/vendor/thor/lib/thor/parser/options.rb @@ -144,7 +144,7 @@ class Bundler::Thor def check_exclusive! opts = @assigns.keys # When option A and B are exclusive, if A and B are given at the same time, - # the diffrence of argument array size will decrease. + # the difference of argument array size will decrease. found = @exclusives.find{ |ex| (ex - opts).size < ex.size - 1 } if found names = names_to_switch_names(found & opts).map{|n| "'#{n}'"} @@ -250,7 +250,8 @@ class Bundler::Thor @parsing_options end - # Parse boolean values which can be given as --foo=true, --foo or --no-foo. + # Parse boolean values which can be given as --foo=true or --foo for true values, or + # --foo=false, --no-foo or --skip-foo for false values. # def parse_boolean(switch) if current_is_value? diff --git a/lib/bundler/vendor/thor/lib/thor/runner.rb b/lib/bundler/vendor/thor/lib/thor/runner.rb index c7cc873131..f0ce6df96c 100644 --- a/lib/bundler/vendor/thor/lib/thor/runner.rb +++ b/lib/bundler/vendor/thor/lib/thor/runner.rb @@ -1,9 +1,8 @@ require_relative "../thor" require_relative "group" -require "yaml" require "digest/sha2" -require "pathname" +require "pathname" unless defined?(Pathname) class Bundler::Thor::Runner < Bundler::Thor #:nodoc: map "-T" => :list, "-i" => :install, "-u" => :update, "-v" => :version @@ -195,6 +194,7 @@ private def thor_yaml @thor_yaml ||= begin yaml_file = File.join(thor_root, "thor.yml") + require "yaml" yaml = YAML.load_file(yaml_file) if File.exist?(yaml_file) yaml || {} end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb index dc3179e5f3..da02b94227 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/basic.rb @@ -67,15 +67,15 @@ class Bundler::Thor # Readline. # # ==== Example - # ask("What is your name?") + # ask("What is your name?") # - # ask("What is the planet furthest from the sun?", :default => "Pluto") + # ask("What is the planet furthest from the sun?", :default => "Neptune") # - # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) + # ask("What is your favorite Neopolitan flavor?", :limited_to => ["strawberry", "chocolate", "vanilla"]) # - # ask("What is your password?", :echo => false) + # ask("What is your password?", :echo => false) # - # ask("Where should the file be saved?", :path => true) + # ask("Where should the file be saved?", :path => true) # def ask(statement, *args) options = args.last.is_a?(Hash) ? args.pop : {} @@ -93,7 +93,7 @@ class Bundler::Thor # are passed straight to puts (behavior got from Highline). # # ==== Example - # say("I know you knew that.") + # say("I know you knew that.") # def say(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) return if quiet? @@ -110,7 +110,7 @@ class Bundler::Thor # are passed straight to puts (behavior got from Highline). # # ==== Example - # say_error("error: something went wrong") + # say_error("error: something went wrong") # def say_error(message = "", color = nil, force_new_line = (message.to_s !~ /( |\t)\Z/)) return if quiet? @@ -143,14 +143,14 @@ class Bundler::Thor stdout.flush end - # Make a question the to user and returns true if the user replies "y" or + # Asks the user a question and returns true if the user replies "y" or # "yes". # def yes?(statement, color = nil) !!(ask(statement, color, add_to_history: false) =~ is?(:yes)) end - # Make a question the to user and returns true if the user replies "n" or + # Asks the user a question and returns true if the user replies "n" or # "no". # def no?(statement, color = nil) @@ -314,7 +314,7 @@ class Bundler::Thor diff_cmd = ENV["THOR_DIFF"] || ENV["RAILS_DIFF"] || "diff -u" require "tempfile" - Tempfile.open(File.basename(destination), File.dirname(destination)) do |temp| + Tempfile.open(File.basename(destination), File.dirname(destination), binmode: true) do |temp| temp.write content temp.rewind system %(#{diff_cmd} "#{destination}" "#{temp.path}") @@ -372,16 +372,12 @@ class Bundler::Thor Tempfile.open([File.basename(destination), File.extname(destination)], File.dirname(destination)) do |temp| temp.write content temp.rewind - system %(#{merge_tool} "#{temp.path}" "#{destination}") + system(merge_tool, temp.path, destination) end end def merge_tool #:nodoc: - @merge_tool ||= ENV["THOR_MERGE"] || git_merge_tool - end - - def git_merge_tool #:nodoc: - `git config merge.tool`.rstrip rescue "" + @merge_tool ||= ENV["THOR_MERGE"] || "git difftool --no-index" end end end diff --git a/lib/bundler/vendor/thor/lib/thor/shell/html.rb b/lib/bundler/vendor/thor/lib/thor/shell/html.rb index 0277b882b7..a0a8520e5c 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/html.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/html.rb @@ -64,7 +64,7 @@ class Bundler::Thor # Ask something to the user and receives a response. # # ==== Example - # ask("What is your name?") + # ask("What is your name?") # # TODO: Implement #ask for Bundler::Thor::Shell::HTML def ask(statement, color = nil) diff --git a/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb index 525f9ce5bb..dee3614753 100644 --- a/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb +++ b/lib/bundler/vendor/thor/lib/thor/shell/table_printer.rb @@ -102,33 +102,17 @@ class Bundler::Thor def truncate(string) return string unless @truncate - as_unicode do - chars = string.chars.to_a - if chars.length <= @truncate - chars.join - else - chars[0, @truncate - 3 - @indent].join + "..." - end + chars = string.chars.to_a + if chars.length <= @truncate + chars.join + else + chars[0, @truncate - 3 - @indent].join + "..." end end def indentation " " * @indent end - - if "".respond_to?(:encode) - def as_unicode - yield - end - else - def as_unicode - old = $KCODE # rubocop:disable Style/GlobalVars - $KCODE = "U" # rubocop:disable Style/GlobalVars - yield - ensure - $KCODE = old # rubocop:disable Style/GlobalVars - end - end end end end diff --git a/lib/bundler/vendor/thor/lib/thor/util.rb b/lib/bundler/vendor/thor/lib/thor/util.rb index 68916daf2e..cd8f9ece87 100644 --- a/lib/bundler/vendor/thor/lib/thor/util.rb +++ b/lib/bundler/vendor/thor/lib/thor/util.rb @@ -133,7 +133,7 @@ class Bundler::Thor *pieces, command = namespace.split(":") namespace = pieces.join(":") namespace = "default" if namespace.empty? - klass = Bundler::Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.commands.keys.include?(command) } + klass = Bundler::Thor::Base.subclasses.detect { |thor| thor.namespace == namespace && thor.command_exists?(command) } end unless klass # look for a Bundler::Thor::Group with the right name klass = Bundler::Thor::Util.find_by_namespace(namespace) diff --git a/lib/bundler/vendor/thor/lib/thor/version.rb b/lib/bundler/vendor/thor/lib/thor/version.rb index 1fb00017ed..5474a2f71b 100644 --- a/lib/bundler/vendor/thor/lib/thor/version.rb +++ b/lib/bundler/vendor/thor/lib/thor/version.rb @@ -1,3 +1,3 @@ class Bundler::Thor - VERSION = "1.3.0" + VERSION = "1.4.0" end diff --git a/lib/bundler/vendor/tsort/.document b/lib/bundler/vendor/tsort/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/tsort/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/uri/.document b/lib/bundler/vendor/uri/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/bundler/vendor/uri/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/bundler/vendor/uri/lib/uri.rb b/lib/bundler/vendor/uri/lib/uri.rb index 976320f6bd..57b380c480 100644 --- a/lib/bundler/vendor/uri/lib/uri.rb +++ b/lib/bundler/vendor/uri/lib/uri.rb @@ -1,6 +1,6 @@ # frozen_string_literal: false # Bundler::URI is a module providing classes to handle Uniform Resource Identifiers -# (RFC2396[http://tools.ietf.org/html/rfc2396]). +# (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). # # == Features # @@ -47,14 +47,14 @@ # A good place to view an RFC spec is http://www.ietf.org/rfc.html. # # Here is a list of all related RFC's: -# - RFC822[http://tools.ietf.org/html/rfc822] -# - RFC1738[http://tools.ietf.org/html/rfc1738] -# - RFC2255[http://tools.ietf.org/html/rfc2255] -# - RFC2368[http://tools.ietf.org/html/rfc2368] -# - RFC2373[http://tools.ietf.org/html/rfc2373] -# - RFC2396[http://tools.ietf.org/html/rfc2396] -# - RFC2732[http://tools.ietf.org/html/rfc2732] -# - RFC3986[http://tools.ietf.org/html/rfc3986] +# - RFC822[https://www.rfc-editor.org/rfc/rfc822] +# - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] +# - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] +# - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] +# - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] +# - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] +# - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] +# - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] # # == Class tree # diff --git a/lib/bundler/vendor/uri/lib/uri/common.rb b/lib/bundler/vendor/uri/lib/uri/common.rb index 89044da036..38339119c5 100644 --- a/lib/bundler/vendor/uri/lib/uri/common.rb +++ b/lib/bundler/vendor/uri/lib/uri/common.rb @@ -13,26 +13,54 @@ require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Bundler::URI - include RFC2396_REGEXP + # The default parser instance for RFC 2396. + RFC2396_PARSER = RFC2396_Parser.new + Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) - REGEXP = RFC2396_REGEXP - Parser = RFC2396_Parser + # The default parser instance for RFC 3986. RFC3986_PARSER = RFC3986_Parser.new Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) - RFC2396_PARSER = RFC2396_Parser.new - Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) - # Bundler::URI::Parser.new - DEFAULT_PARSER = Parser.new - DEFAULT_PARSER.pattern.each_pair do |sym, str| - unless REGEXP::PATTERN.const_defined?(sym) - REGEXP::PATTERN.const_set(sym, str) + # The default parser instance. + DEFAULT_PARSER = RFC3986_PARSER + Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) + + # Set the default parser instance. + def self.parser=(parser = RFC3986_PARSER) + remove_const(:Parser) if defined?(::Bundler::URI::Parser) + const_set("Parser", parser.class) + + remove_const(:PARSER) if defined?(::Bundler::URI::PARSER) + const_set("PARSER", parser) + + remove_const(:REGEXP) if defined?(::Bundler::URI::REGEXP) + remove_const(:PATTERN) if defined?(::Bundler::URI::PATTERN) + if Parser == RFC2396_Parser + const_set("REGEXP", Bundler::URI::RFC2396_REGEXP) + const_set("PATTERN", Bundler::URI::RFC2396_REGEXP::PATTERN) + end + + Parser.new.regexp.each_pair do |sym, str| + remove_const(sym) if const_defined?(sym, false) + const_set(sym, str) end end - DEFAULT_PARSER.regexp.each_pair do |sym, str| - const_set(sym, str) + self.parser = RFC3986_PARSER + + def self.const_missing(const) # :nodoc: + if const == :REGEXP + warn "Bundler::URI::REGEXP is obsolete. Use Bundler::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE + Bundler::URI::RFC2396_REGEXP + elsif value = RFC2396_PARSER.regexp[const] + warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + value + elsif value = RFC2396_Parser.const_get(const) + warn "Bundler::URI::#{const} is obsolete. Use Bundler::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE + value + else + super + end end - Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) @@ -66,7 +94,41 @@ module Bundler::URI module_function :make_components_hash end - module Schemes + module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -79,7 +141,7 @@ module Bundler::URI # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) - Schemes.const_set(scheme.to_s.upcase, klass) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -97,14 +159,14 @@ module Bundler::URI # # Related: Bundler::URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -123,12 +185,10 @@ module Bundler::URI # # => #<Bundler::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) uri_class = INITIAL_SCHEMES[const_name] - uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) - Schemes.const_get(const_name, false) - end + uri_class ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -170,7 +230,7 @@ module Bundler::URI # ["fragment", "top"]] # def self.split(uri) - RFC3986_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \Bundler::URI object constructed from the given string +uri+: @@ -180,11 +240,11 @@ module Bundler::URI # Bundler::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => #<Bundler::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first Bundler::URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid Bundler::URI characters. # def self.parse(uri) - RFC3986_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given Bundler::URI strings +str+ @@ -211,7 +271,7 @@ module Bundler::URI # # => #<Bundler::URI::HTTP http://example.com/foo/bar> # def self.join(*str) - RFC3986_PARSER.join(*str) + DEFAULT_PARSER.join(*str) end # @@ -240,7 +300,7 @@ module Bundler::URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Bundler::URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -277,14 +337,14 @@ module Bundler::URI # def self.regexp(schemes = nil)# :nodoc: warn "Bundler::URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end - TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze + TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: @@ -382,6 +442,8 @@ module Bundler::URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # Bundler::URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -396,6 +458,8 @@ module Bundler::URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) @@ -834,6 +898,7 @@ module Bundler # Returns a \Bundler::URI object derived from the given +uri+, # which may be a \Bundler::URI string or an existing \Bundler::URI object: # + # require 'bundler/vendor/uri/lib/uri' # # Returns a new Bundler::URI. # uri = Bundler::URI('http://github.com/ruby/ruby') # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> @@ -841,6 +906,8 @@ module Bundler # Bundler::URI(uri) # # => #<Bundler::URI::HTTP http://github.com/ruby/ruby> # + # You must require 'bundler/vendor/uri/lib/uri' to use this method. + # def URI(uri) if uri.is_a?(Bundler::URI::Generic) uri diff --git a/lib/bundler/vendor/uri/lib/uri/file.rb b/lib/bundler/vendor/uri/lib/uri/file.rb index 8d75a9de7a..21dd9ee535 100644 --- a/lib/bundler/vendor/uri/lib/uri/file.rb +++ b/lib/bundler/vendor/uri/lib/uri/file.rb @@ -47,7 +47,7 @@ module Bundler::URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # - # uri3 = Bundler::URI::File.build({:path => Bundler::URI::escape('/path/my file.txt')}) + # uri3 = Bundler::URI::File.build({:path => Bundler::URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) @@ -70,17 +70,17 @@ module Bundler::URI # raise InvalidURIError def check_userinfo(user) - raise Bundler::URI::InvalidURIError, "can not set userinfo for file Bundler::URI" + raise Bundler::URI::InvalidURIError, "cannot set userinfo for file Bundler::URI" end # raise InvalidURIError def check_user(user) - raise Bundler::URI::InvalidURIError, "can not set user for file Bundler::URI" + raise Bundler::URI::InvalidURIError, "cannot set user for file Bundler::URI" end # raise InvalidURIError def check_password(user) - raise Bundler::URI::InvalidURIError, "can not set password for file Bundler::URI" + raise Bundler::URI::InvalidURIError, "cannot set password for file Bundler::URI" end # do nothing diff --git a/lib/bundler/vendor/uri/lib/uri/ftp.rb b/lib/bundler/vendor/uri/lib/uri/ftp.rb index 48b4c6718d..f83985fd3d 100644 --- a/lib/bundler/vendor/uri/lib/uri/ftp.rb +++ b/lib/bundler/vendor/uri/lib/uri/ftp.rb @@ -17,7 +17,7 @@ module Bundler::URI # This class will be redesigned because of difference of implementations; # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it # is a good summary about the de facto spec. - # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04 + # https://datatracker.ietf.org/doc/html/draft-hoffman-ftp-uri-04 # class FTP < Generic # A Default port of 21 for Bundler::URI::FTP. diff --git a/lib/bundler/vendor/uri/lib/uri/generic.rb b/lib/bundler/vendor/uri/lib/uri/generic.rb index 762c425ac1..30dab60903 100644 --- a/lib/bundler/vendor/uri/lib/uri/generic.rb +++ b/lib/bundler/vendor/uri/lib/uri/generic.rb @@ -73,7 +73,7 @@ module Bundler::URI # # At first, tries to create a new Bundler::URI::Generic instance using # Bundler::URI::Generic::build. But, if exception Bundler::URI::InvalidComponentError is raised, - # then it does Bundler::URI::Escape.escape all Bundler::URI components and tries again. + # then it does Bundler::URI::RFC2396_PARSER.escape all Bundler::URI components and tries again. # def self.build2(args) begin @@ -82,7 +82,7 @@ module Bundler::URI if args.kind_of?(Array) return self.build(args.collect{|x| if x.is_a?(String) - DEFAULT_PARSER.escape(x) + Bundler::URI::RFC2396_PARSER.escape(x) else x end @@ -91,7 +91,7 @@ module Bundler::URI tmp = {} args.each do |key, value| tmp[key] = if value - DEFAULT_PARSER.escape(value) + Bundler::URI::RFC2396_PARSER.escape(value) else value end @@ -126,9 +126,9 @@ module Bundler::URI end end else - component = self.class.component rescue ::Bundler::URI::Generic::COMPONENT + component = self.component rescue ::Bundler::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module Bundler::URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module Bundler::URI # Returns the parser to be used. # - # Unless a Bundler::URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module Bundler::URI end # - # Checks the scheme +v+ component against the Bundler::URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module Bundler::URI # # Checks the user +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -393,7 +393,7 @@ module Bundler::URI def check_user(v) if @opaque raise InvalidURIError, - "can not set user with opaque" + "cannot set user with opaque" end return v unless v @@ -409,7 +409,7 @@ module Bundler::URI # # Checks the password +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -417,7 +417,7 @@ module Bundler::URI def check_password(v, user = @user) if @opaque raise InvalidURIError, - "can not set password with opaque" + "cannot set password with opaque" end return v unless v @@ -511,7 +511,7 @@ module Bundler::URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module Bundler::URI # See also Bundler::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module Bundler::URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Bundler::URI decoding. def decoded_user Bundler::URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module Bundler::URI # # Checks the host +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -596,7 +602,7 @@ module Bundler::URI if @opaque raise InvalidURIError, - "can not set host with registry or opaque" + "cannot set host with registry or opaque" elsif parser.regexp[:HOST] !~ v raise InvalidComponentError, "bad component(expected host component): #{v}" @@ -615,6 +621,13 @@ module Bundler::URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module Bundler::URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module Bundler::URI # # Checks the port +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -685,7 +699,7 @@ module Bundler::URI if @opaque raise InvalidURIError, - "can not set port with registry or opaque" + "cannot set port with registry or opaque" elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v raise InvalidComponentError, "bad component(expected port component): #{v.inspect}" @@ -729,26 +743,27 @@ module Bundler::URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end def check_registry(v) # :nodoc: - raise InvalidURIError, "can not set registry" + raise InvalidURIError, "cannot set registry" end private :check_registry - def set_registry(v) #:nodoc: - raise InvalidURIError, "can not set registry" + def set_registry(v) # :nodoc: + raise InvalidURIError, "cannot set registry" end protected :set_registry - def registry=(v) - raise InvalidURIError, "can not set registry" + def registry=(v) # :nodoc: + raise InvalidURIError, "cannot set registry" end # # Checks the path +v+ component for RFC2396 compliance - # and against the Bundler::URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module Bundler::URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the Bundler::URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -866,7 +881,7 @@ module Bundler::URI # hier_part = ( net_path | abs_path ) [ "?" query ] if @host || @port || @user || @path # userinfo = @user + ':' + @password raise InvalidURIError, - "can not set opaque with host, port, userinfo or path" + "cannot set opaque with host, port, userinfo or path" elsif v && parser.regexp[:OPAQUE] !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" @@ -905,7 +920,7 @@ module Bundler::URI end # - # Checks the fragment +v+ component against the Bundler::URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -945,7 +960,7 @@ module Bundler::URI # == Description # # Bundler::URI has components listed in order of decreasing significance from left to right, - # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3. + # see RFC3986 https://www.rfc-editor.org/rfc/rfc3986 1.2.3. # # == Usage # @@ -1121,7 +1136,7 @@ module Bundler::URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1133,17 +1148,14 @@ module Bundler::URI base.fragment=(nil) # RFC2396, Section 5.2, 4) - if !authority - base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path - else - # RFC2396, Section 5.2, 4) - base.set_path(rel.path) if rel.path + if authority + base.set_authority(*authority) + base.set_path(rel.path) + elsif base.path && rel.path + base.set_path(merge_path(base.path, rel.path)) end # RFC2396, Section 5.2, 7) - base.set_userinfo(rel.userinfo) if rel.userinfo - base.set_host(rel.host) if rel.host - base.set_port(rel.port) if rel.port base.query = rel.query if rel.query base.fragment=(rel.fragment) if rel.fragment @@ -1235,7 +1247,7 @@ module Bundler::URI return rel, rel end - # you can modify `rel', but can not `oth'. + # you can modify `rel', but cannot `oth'. return oth, rel end private :route_from0 @@ -1260,7 +1272,7 @@ module Bundler::URI # #=> #<Bundler::URI::Generic /main.rbx?page=1> # def route_from(oth) - # you can modify `rel', but can not `oth'. + # you can modify `rel', but cannot `oth'. begin oth, rel = route_from0(oth) rescue @@ -1364,6 +1376,9 @@ module Bundler::URI str << ':' str << @port.to_s end + if (@host || @port) && !@path.empty? && !@path.start_with?('/') + str << '/' + end str << @path if @query str << '?' @@ -1389,29 +1404,18 @@ module Bundler::URI end end + # Returns the hash value. def hash self.component_ary.hash end + # Compares with _oth_ for Hash. def eql?(oth) self.class == oth.class && parser == oth.parser && self.component_ary.eql?(oth.component_ary) end -=begin - ---- Bundler::URI::Generic#===(oth) - -=end -# def ===(oth) -# raise NotImplementedError -# end - -=begin -=end - - # Returns an Array of the components defined from the COMPONENT Array. def component_ary component.collect do |x| @@ -1448,7 +1452,7 @@ module Bundler::URI end end - def inspect + def inspect # :nodoc: "#<#{self.class} #{self}>" end @@ -1536,7 +1540,7 @@ module Bundler::URI else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/bundler/vendor/uri/lib/uri/http.rb b/lib/bundler/vendor/uri/lib/uri/http.rb index 2c44810644..9b217ee266 100644 --- a/lib/bundler/vendor/uri/lib/uri/http.rb +++ b/lib/bundler/vendor/uri/lib/uri/http.rb @@ -61,6 +61,18 @@ module Bundler::URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # @@ -85,7 +97,7 @@ module Bundler::URI # == Description # # Returns the authority for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. + # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. # # # Example: @@ -106,7 +118,7 @@ module Bundler::URI # == Description # # Returns the origin for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc6454. + # https://www.rfc-editor.org/rfc/rfc6454. # # # Example: diff --git a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb index 09c22c9906..522113fe67 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module Bundler::URI # # == Synopsis # - # Bundler::URI::Parser.new([opts]) + # Bundler::URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module Bundler::URI # # == Examples # - # p = Bundler::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = Bundler::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<Bundler::URI::HTTP http://example.jp/%uABCD> # Bundler::URI.parse(u.to_s) #=> raises Bundler::URI::InvalidURIError # @@ -108,12 +108,12 @@ module Bundler::URI # The Hash of patterns. # - # See also Bundler::URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also Bundler::URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split Bundler::URI against +regexp[:ABS_URI]+. @@ -140,11 +140,11 @@ module Bundler::URI if !scheme raise InvalidURIError, - "bad Bundler::URI(absolute but no scheme): #{uri}" + "bad Bundler::URI (absolute but no scheme): #{uri}" end if !opaque && (!path && (!host && !registry)) raise InvalidURIError, - "bad Bundler::URI(absolute but no path): #{uri}" + "bad Bundler::URI (absolute but no path): #{uri}" end when @regexp[:REL_URI] @@ -173,7 +173,7 @@ module Bundler::URI # server = [ [ userinfo "@" ] hostport ] else - raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri}" + raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri}" end path = '' if !path && !opaque # (see RFC2396 Section 5.2) @@ -202,8 +202,7 @@ module Bundler::URI # # == Usage # - # p = Bundler::URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # Bundler::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<Bundler::URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module Bundler::URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also Bundler::URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module Bundler::URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -321,14 +320,14 @@ module Bundler::URI str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } end - @@to_s = Kernel.instance_method(:to_s) - if @@to_s.respond_to?(:bind_call) - def inspect - @@to_s.bind_call(self) + TO_S = Kernel.instance_method(:to_s) # :nodoc: + if TO_S.respond_to?(:bind_call) + def inspect # :nodoc: + TO_S.bind_call(self) end else - def inspect - @@to_s.bind(self).call + def inspect # :nodoc: + TO_S.bind(self).call end end @@ -524,6 +523,8 @@ module Bundler::URI ret end + # Returns +uri+ as-is if it is Bundler::URI, or convert it to Bundler::URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(Bundler::URI::Generic) uri @@ -536,4 +537,11 @@ module Bundler::URI end end # class Parser + + # Backward compatibility for Bundler::URI::REGEXP::PATTERN::* + RFC2396_Parser.new.pattern.each_pair do |sym, str| + unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) + RFC2396_REGEXP::PATTERN.const_set(sym, str) + end + end end # module Bundler::URI diff --git a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb index 4c9882f595..d1ff28df23 100644 --- a/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb +++ b/lib/bundler/vendor/uri/lib/uri/rfc3986_parser.rb @@ -78,7 +78,7 @@ module Bundler::URI begin uri = uri.to_str rescue NoMethodError - raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri.inspect}" + raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri.inspect}" end uri.ascii_only? or raise InvalidURIError, "Bundler::URI must be ascii only #{uri.dump}" @@ -127,7 +127,7 @@ module Bundler::URI m["fragment"] ] else - raise InvalidURIError, "bad Bundler::URI(is not Bundler::URI?): #{uri.inspect}" + raise InvalidURIError, "bad Bundler::URI (is not Bundler::URI?): #{uri.inspect}" end end @@ -135,12 +135,35 @@ module Bundler::URI Bundler::URI.for(*self.split(uri), self) end - def join(*uris) # :nodoc: uris[0] = convert_to_uri(uris[0]) uris.inject :merge end + # Compatibility for RFC2396 parser + def extract(str, schemes = nil, &block) # :nodoc: + warn "Bundler::URI::RFC3986_PARSER.extract is obsolete. Use Bundler::URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE + RFC2396_PARSER.extract(str, schemes, &block) + end + + # Compatibility for RFC2396 parser + def make_regexp(schemes = nil) # :nodoc: + warn "Bundler::URI::RFC3986_PARSER.make_regexp is obsolete. Use Bundler::URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE + RFC2396_PARSER.make_regexp(schemes) + end + + # Compatibility for RFC2396 parser + def escape(str, unsafe = nil) # :nodoc: + warn "Bundler::URI::RFC3986_PARSER.escape is obsolete. Use Bundler::URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE + unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) + end + + # Compatibility for RFC2396 parser + def unescape(str, escaped = nil) # :nodoc: + warn "Bundler::URI::RFC3986_PARSER.unescape is obsolete. Use Bundler::URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE + escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) + end + @@to_s = Kernel.instance_method(:to_s) if @@to_s.respond_to?(:bind_call) def inspect diff --git a/lib/bundler/vendor/uri/lib/uri/version.rb b/lib/bundler/vendor/uri/lib/uri/version.rb index ac94e15221..ad76308e81 100644 --- a/lib/bundler/vendor/uri/lib/uri/version.rb +++ b/lib/bundler/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Bundler::URI # :stopdoc: - VERSION_CODE = '001301'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/bundler/vendored_securerandom.rb b/lib/bundler/vendored_securerandom.rb index 6c15f4a2b2..6a704dbd40 100644 --- a/lib/bundler/vendored_securerandom.rb +++ b/lib/bundler/vendored_securerandom.rb @@ -7,8 +7,6 @@ begin require "rubygems/vendored_securerandom" rescue LoadError - module Bundler::Random; end require_relative "vendor/securerandom/lib/securerandom" Gem::SecureRandom = Bundler::SecureRandom - Gem::Random = Bundler::Random end diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index f2f6236cda..ca7bb0719a 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,13 +1,21 @@ # frozen_string_literal: false module Bundler - VERSION = "2.6.0.dev".freeze + VERSION = "4.1.0.dev".freeze def self.bundler_major_version - @bundler_major_version ||= VERSION.split(".").first.to_i + @bundler_major_version ||= gem_version.segments.first end def self.gem_version @gem_version ||= Gem::Version.create(VERSION) end + + def self.verbose_version + @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}" + end + + def self.simulated_version + @simulated_version ||= Bundler.settings[:simulate_version] + end end diff --git a/lib/bundler/vlad.rb b/lib/bundler/vlad.rb index 6179d0e4eb..c3a3d949a6 100644 --- a/lib/bundler/vlad.rb +++ b/lib/bundler/vlad.rb @@ -1,17 +1,4 @@ # frozen_string_literal: true require_relative "shared_helpers" -Bundler::SharedHelpers.major_deprecation 2, - "The Bundler task for Vlad" - -# Vlad task for Bundler. -# -# Add "require 'bundler/vlad'" in your Vlad deploy.rb, and -# include the vlad:bundle:install task in your vlad:deploy task. -require_relative "deployment" - -include Rake::DSL if defined? Rake::DSL - -namespace :vlad do - Bundler::Deployment.define_task(Rake::RemoteTask, :remote_task, roles: :app) -end +Bundler::SharedHelpers.feature_removed! "The Bundler task for Vlad" diff --git a/lib/bundler/worker.rb b/lib/bundler/worker.rb index 3ebd6f01db..7137484cc6 100644 --- a/lib/bundler/worker.rb +++ b/lib/bundler/worker.rb @@ -88,7 +88,7 @@ module Bundler @threads = Array.new(@size) do |i| Thread.start { process_queue(i) }.tap do |thread| - thread.name = "#{name} Worker ##{i}" if thread.respond_to?(:name=) + thread.name = "#{name} Worker ##{i}" end rescue ThreadError => e creation_errors << e diff --git a/lib/cgi.rb b/lib/cgi.rb index 7af85e7fc8..bb306d2e06 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -1,297 +1,7 @@ # frozen_string_literal: true -# -# cgi.rb - cgi support library -# -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Wakou Aoyama <wakou@ruby-lang.org> -# -# Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber) -# -# == Overview -# -# The Common Gateway Interface (CGI) is a simple protocol for passing an HTTP -# request from a web server to a standalone program, and returning the output -# to the web browser. Basically, a CGI program is called with the parameters -# of the request passed in either in the environment (GET) or via $stdin -# (POST), and everything it prints to $stdout is returned to the client. -# -# This file holds the CGI class. This class provides functionality for -# retrieving HTTP request parameters, managing cookies, and generating HTML -# output. -# -# The file CGI::Session provides session management functionality; see that -# class for more details. -# -# See http://www.w3.org/CGI/ for more information on the CGI protocol. -# -# == Introduction -# -# CGI is a large class, providing several categories of methods, many of which -# are mixed in from other modules. Some of the documentation is in this class, -# some in the modules CGI::QueryExtension and CGI::HtmlExtension. See -# CGI::Cookie for specific information on handling cookies, and cgi/session.rb -# (CGI::Session) for information on sessions. -# -# For queries, CGI provides methods to get at environmental variables, -# parameters, cookies, and multipart request data. For responses, CGI provides -# methods for writing output and generating HTML. -# -# Read on for more details. Examples are provided at the bottom. -# -# == Queries -# -# The CGI class dynamically mixes in parameter and cookie-parsing -# functionality, environmental variable access, and support for -# parsing multipart requests (including uploaded files) from the -# CGI::QueryExtension module. -# -# === Environmental Variables -# -# The standard CGI environmental variables are available as read-only -# attributes of a CGI object. The following is a list of these variables: -# -# -# AUTH_TYPE HTTP_HOST REMOTE_IDENT -# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER -# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD -# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME -# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME -# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT -# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL -# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE -# HTTP_CACHE_CONTROL REMOTE_ADDR -# HTTP_FROM REMOTE_HOST -# -# -# For each of these variables, there is a corresponding attribute with the -# same name, except all lower case and without a preceding HTTP_. -# +content_length+ and +server_port+ are integers; the rest are strings. -# -# === Parameters -# -# The method #params() returns a hash of all parameters in the request as -# name/value-list pairs, where the value-list is an Array of one or more -# values. The CGI object itself also behaves as a hash of parameter names -# to values, but only returns a single value (as a String) for each -# parameter name. -# -# For instance, suppose the request contains the parameter -# "favourite_colours" with the multiple values "blue" and "green". The -# following behavior would occur: -# -# cgi.params["favourite_colours"] # => ["blue", "green"] -# cgi["favourite_colours"] # => "blue" -# -# If a parameter does not exist, the former method will return an empty -# array, the latter an empty string. The simplest way to test for existence -# of a parameter is by the #has_key? method. -# -# === Cookies -# -# HTTP Cookies are automatically parsed from the request. They are available -# from the #cookies() accessor, which returns a hash from cookie name to -# CGI::Cookie object. -# -# === Multipart requests -# -# If a request's method is POST and its content type is multipart/form-data, -# then it may contain uploaded files. These are stored by the QueryExtension -# module in the parameters of the request. The parameter name is the name -# attribute of the file input field, as usual. However, the value is not -# a string, but an IO object, either an IOString for small files, or a -# Tempfile for larger ones. This object also has the additional singleton -# methods: -# -# #local_path():: the path of the uploaded file on the local filesystem -# #original_filename():: the name of the file on the client computer -# #content_type():: the content type of the file -# -# == Responses -# -# The CGI class provides methods for sending header and content output to -# the HTTP client, and mixes in methods for programmatic HTML generation -# from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML -# to use for HTML generation is specified at object creation time. -# -# === Writing output -# -# The simplest way to send output to the HTTP client is using the #out() method. -# This takes the HTTP headers as a hash parameter, and the body content -# via a block. The headers can be generated as a string using the #http_header() -# method. The output stream can be written directly to using the #print() -# method. -# -# === Generating HTML -# -# Each HTML element has a corresponding method for generating that -# element as a String. The name of this method is the same as that -# of the element, all lowercase. The attributes of the element are -# passed in as a hash, and the body as a no-argument block that evaluates -# to a String. The HTML generation module knows which elements are -# always empty, and silently drops any passed-in body. It also knows -# which elements require matching closing tags and which don't. However, -# it does not know what attributes are legal for which elements. -# -# There are also some additional HTML generation methods mixed in from -# the CGI::HtmlExtension module. These include individual methods for the -# different types of form inputs, and methods for elements that commonly -# take particular attributes where the attributes can be directly specified -# as arguments, rather than via a hash. -# -# === Utility HTML escape and other methods like a function. -# -# There are some utility tool defined in cgi/util.rb . -# And when include, you can use utility methods like a function. -# -# == Examples of use -# -# === Get form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# # if not 'field_name' included, then return "". -# fields = cgi.keys # <== array of field names -# -# # returns true if form has 'field_name' -# cgi.has_key?('field_name') -# cgi.has_key?('field_name') -# cgi.include?('field_name') -# -# CAUTION! <code>cgi['field_name']</code> returned an Array with the old -# cgi.rb(included in Ruby 1.6) -# -# === Get form values as hash -# -# require "cgi" -# cgi = CGI.new -# params = cgi.params -# -# cgi.params is a hash. -# -# cgi.params['new_field_name'] = ["value"] # add new param -# cgi.params['field_name'] = ["new_value"] # change value -# cgi.params.delete('field_name') # delete param -# cgi.params.clear # delete all params -# -# -# === Save form values to file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# db["params"] = cgi.params -# end -# -# -# === Restore form values from file -# -# require "pstore" -# db = PStore.new("query.db") -# db.transaction do -# cgi.params = db["params"] -# end -# -# -# === Get multipart form values -# -# require "cgi" -# cgi = CGI.new -# value = cgi['field_name'] # <== value string for 'field_name' -# value.read # <== body of value -# value.local_path # <== path to local file of value -# value.original_filename # <== original filename of value -# value.content_type # <== content_type of value -# -# and value has StringIO or Tempfile class methods. -# -# === Get cookie values -# -# require "cgi" -# cgi = CGI.new -# values = cgi.cookies['name'] # <== array of 'name' -# # if not 'name' included, then return []. -# names = cgi.cookies.keys # <== array of cookie names -# -# and cgi.cookies is a hash. -# -# === Get cookie objects -# -# require "cgi" -# cgi = CGI.new -# for name, cookie in cgi.cookies -# cookie.expires = Time.now + 30 -# end -# cgi.out("cookie" => cgi.cookies) {"string"} -# -# cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... } -# -# require "cgi" -# cgi = CGI.new -# cgi.cookies['name'].expires = Time.now + 30 -# cgi.out("cookie" => cgi.cookies['name']) {"string"} -# -# === Print http header and html string to $DEFAULT_OUTPUT ($>) -# -# require "cgi" -# cgi = CGI.new("html4") # add HTML generation methods -# cgi.out do -# cgi.html do -# cgi.head do -# cgi.title { "TITLE" } -# end + -# cgi.body do -# cgi.form("ACTION" => "uri") do -# cgi.p do -# cgi.textarea("get_text") + -# cgi.br + -# cgi.submit -# end -# end + -# cgi.pre do -# CGI.escapeHTML( -# "params: #{cgi.params.inspect}\n" + -# "cookies: #{cgi.cookies.inspect}\n" + -# ENV.collect do |key, value| -# "#{key} --> #{value}\n" -# end.join("") -# ) -# end -# end -# end -# end -# -# # add HTML generation methods -# CGI.new("html3") # html3.2 -# CGI.new("html4") # html4.01 (Strict) -# CGI.new("html4Tr") # html4.01 Transitional -# CGI.new("html4Fr") # html4.01 Frameset -# CGI.new("html5") # html5 -# -# === Some utility methods -# -# require 'cgi/util' -# CGI.escapeHTML('Usage: foo "bar" <baz>') -# -# -# === Some utility methods like a function -# -# require 'cgi/util' -# include CGI::Util -# escapeHTML('Usage: foo "bar" <baz>') -# h('Usage: foo "bar" <baz>') # alias -# -# - -class CGI - VERSION = "0.4.1" -end - -require 'cgi/core' -require 'cgi/cookie' -require 'cgi/util' -CGI.autoload(:HtmlExtension, 'cgi/html') +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI library is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you need to use the full features of CGI library, Please install cgi gem. +WARNING diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec deleted file mode 100644 index 381c55a5ca..0000000000 --- a/lib/cgi/cgi.gemspec +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Yukihiro Matsumoto"] - spec.email = ["matz@ruby-lang.org"] - - spec.summary = %q{Support for the Common Gateway Interface protocol.} - spec.description = %q{Support for the Common Gateway Interface protocol.} - spec.homepage = "https://github.com/ruby/cgi" - spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = ">= 2.5.0" - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - spec.executables = [] - - spec.files = [ - "LICENSE.txt", - "README.md", - *Dir["lib{.rb,/**/*.rb}", "bin/*"] ] - - spec.require_paths = ["lib"] - - if Gem::Platform === spec.platform and spec.platform =~ 'java' or RUBY_ENGINE == 'jruby' - spec.platform = 'java' - spec.require_paths << "ext/java/org/jruby/ext/cgi/escape/lib" - spec.files += Dir["ext/java/**/*.{rb}", "lib/cgi/escape.jar"] - else - spec.files += Dir["ext/cgi/**/*.{rb,c,h,sh}", "ext/cgi/escape/depend", "lib/cgi/escape.so"] - spec.extensions = ["ext/cgi/escape/extconf.rb"] - end -end diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb deleted file mode 100644 index 9498e2f9fa..0000000000 --- a/lib/cgi/cookie.rb +++ /dev/null @@ -1,209 +0,0 @@ -# frozen_string_literal: true -require_relative 'util' -class CGI - # Class representing an HTTP cookie. - # - # In addition to its specific fields and methods, a Cookie instance - # is a delegator to the array of its values. - # - # See RFC 2965. - # - # == Examples of use - # cookie1 = CGI::Cookie.new("name", "value1", "value2", ...) - # cookie1 = CGI::Cookie.new("name" => "name", "value" => "value") - # cookie1 = CGI::Cookie.new('name' => 'name', - # 'value' => ['value1', 'value2', ...], - # 'path' => 'path', # optional - # 'domain' => 'domain', # optional - # 'expires' => Time.now, # optional - # 'secure' => true, # optional - # 'httponly' => true # optional - # ) - # - # cgi.out("cookie" => [cookie1, cookie2]) { "string" } - # - # name = cookie1.name - # values = cookie1.value - # path = cookie1.path - # domain = cookie1.domain - # expires = cookie1.expires - # secure = cookie1.secure - # httponly = cookie1.httponly - # - # cookie1.name = 'name' - # cookie1.value = ['value1', 'value2', ...] - # cookie1.path = 'path' - # cookie1.domain = 'domain' - # cookie1.expires = Time.now + 30 - # cookie1.secure = true - # cookie1.httponly = true - class Cookie < Array - @@accept_charset="UTF-8" unless defined?(@@accept_charset) - - TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z" - PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z" - DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z" - - # Create a new CGI::Cookie object. - # - # :call-seq: - # Cookie.new(name_string,*value) - # Cookie.new(options_hash) - # - # +name_string+:: - # The name of the cookie; in this form, there is no #domain or - # #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment - # variable, and #secure is false. - # <tt>*value</tt>:: - # value or list of values of the cookie - # +options_hash+:: - # A Hash of options to initialize this Cookie. Possible options are: - # - # name:: the name of the cookie. Required. - # value:: the cookie's value or list of values. - # path:: the path for which this cookie applies. Defaults to - # the value of the +SCRIPT_NAME+ environment variable. - # domain:: the domain for which this cookie applies. - # expires:: the time at which this cookie expires, as a +Time+ object. - # secure:: whether this cookie is a secure cookie or not (default to - # false). Secure cookies are only transmitted to HTTPS - # servers. - # httponly:: whether this cookie is a HttpOnly cookie or not (default to - # false). HttpOnly cookies are not available to javascript. - # - # These keywords correspond to attributes of the cookie object. - def initialize(name = "", *value) - @domain = nil - @expires = nil - if name.kind_of?(String) - self.name = name - self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - @secure = false - @httponly = false - return super(value) - end - - options = name - unless options.has_key?("name") - raise ArgumentError, "`name' required" - end - - self.name = options["name"] - value = Array(options["value"]) - # simple support for IE - self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "") - self.domain = options["domain"] - @expires = options["expires"] - @secure = options["secure"] == true - @httponly = options["httponly"] == true - - super(value) - end - - # Name of this cookie, as a +String+ - attr_reader :name - # Set name of this cookie - def name=(str) - if str and !TOKEN_RE.match?(str) - raise ArgumentError, "invalid name: #{str.dump}" - end - @name = str - end - - # Path for which this cookie applies, as a +String+ - attr_reader :path - # Set path for which this cookie applies - def path=(str) - if str and !PATH_VALUE_RE.match?(str) - raise ArgumentError, "invalid path: #{str.dump}" - end - @path = str - end - - # Domain for which this cookie applies, as a +String+ - attr_reader :domain - # Set domain for which this cookie applies - def domain=(str) - if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str)) - raise ArgumentError, "invalid domain: #{str.dump}" - end - @domain = str - end - - # Time at which this cookie expires, as a +Time+ - attr_accessor :expires - # True if this cookie is secure; false otherwise - attr_reader :secure - # True if this cookie is httponly; false otherwise - attr_reader :httponly - - # Returns the value or list of values for this cookie. - def value - self - end - - # Replaces the value of this cookie with a new value or list of values. - def value=(val) - replace(Array(val)) - end - - # Set whether the Cookie is a secure cookie or not. - # - # +val+ must be a boolean. - def secure=(val) - @secure = val if val == true or val == false - @secure - end - - # Set whether the Cookie is a httponly cookie or not. - # - # +val+ must be a boolean. - def httponly=(val) - @httponly = !!val - end - - # Convert the Cookie to its string representation. - def to_s - val = collect{|v| CGI.escape(v) }.join("&") - buf = "#{@name}=#{val}".dup - buf << "; domain=#{@domain}" if @domain - buf << "; path=#{@path}" if @path - buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires - buf << "; secure" if @secure - buf << "; HttpOnly" if @httponly - buf - end - - # Parse a raw cookie string into a hash of cookie-name=>Cookie - # pairs. - # - # cookies = CGI::Cookie.parse("raw_cookie_string") - # # { "name1" => cookie1, "name2" => cookie2, ... } - # - def self.parse(raw_cookie) - cookies = Hash.new([]) - return cookies unless raw_cookie - - raw_cookie.split(/;\s?/).each do |pairs| - name, values = pairs.split('=',2) - next unless name and values - values ||= "" - values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) } - if cookies.has_key?(name) - values = cookies[name].value + values - end - cookies[name] = Cookie.new(name, *values) - end - - cookies - end - - # A summary of cookie string. - def inspect - "#<CGI::Cookie: #{self.to_s.inspect}>" - end - - end # class Cookie -end - - diff --git a/lib/cgi/core.rb b/lib/cgi/core.rb deleted file mode 100644 index 62e606837a..0000000000 --- a/lib/cgi/core.rb +++ /dev/null @@ -1,900 +0,0 @@ -# frozen_string_literal: true -#-- -# Methods for generating HTML, parsing CGI-related parameters, and -# generating HTTP responses. -#++ -class CGI - unless const_defined?(:Util) - module Util - @@accept_charset = "UTF-8" # :nodoc: - end - include Util - extend Util - end - - $CGI_ENV = ENV # for FCGI support - - # String for carriage return - CR = "\015" - - # String for linefeed - LF = "\012" - - # Standard internet newline sequence - EOL = CR + LF - - REVISION = '$Id$' #:nodoc: - - # Whether processing will be required in binary vs text - NEEDS_BINMODE = File::BINARY != 0 - - # Path separators in different environments. - PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'} - - # HTTP status codes. - HTTP_STATUS = { - "OK" => "200 OK", - "PARTIAL_CONTENT" => "206 Partial Content", - "MULTIPLE_CHOICES" => "300 Multiple Choices", - "MOVED" => "301 Moved Permanently", - "REDIRECT" => "302 Found", - "NOT_MODIFIED" => "304 Not Modified", - "BAD_REQUEST" => "400 Bad Request", - "AUTH_REQUIRED" => "401 Authorization Required", - "FORBIDDEN" => "403 Forbidden", - "NOT_FOUND" => "404 Not Found", - "METHOD_NOT_ALLOWED" => "405 Method Not Allowed", - "NOT_ACCEPTABLE" => "406 Not Acceptable", - "LENGTH_REQUIRED" => "411 Length Required", - "PRECONDITION_FAILED" => "412 Precondition Failed", - "SERVER_ERROR" => "500 Internal Server Error", - "NOT_IMPLEMENTED" => "501 Method Not Implemented", - "BAD_GATEWAY" => "502 Bad Gateway", - "VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates" - } - - # :startdoc: - - # Synonym for ENV. - def env_table - ENV - end - - # Synonym for $stdin. - def stdinput - $stdin - end - - # Synonym for $stdout. - def stdoutput - $stdout - end - - private :env_table, :stdinput, :stdoutput - - # Create an HTTP header block as a string. - # - # :call-seq: - # http_header(content_type_string="text/html") - # http_header(headers_hash) - # - # Includes the empty line that ends the header block. - # - # +content_type_string+:: - # If this form is used, this string is the <tt>Content-Type</tt> - # +headers_hash+:: - # A Hash of header values. The following header keys are recognized: - # - # type:: The Content-Type header. Defaults to "text/html" - # charset:: The charset of the body, appended to the Content-Type header. - # nph:: A boolean value. If true, prepend protocol string and status - # code, and date; and sets default values for "server" and - # "connection" if not explicitly set. - # status:: - # The HTTP status code as a String, returned as the Status header. The - # values are: - # - # OK:: 200 OK - # PARTIAL_CONTENT:: 206 Partial Content - # MULTIPLE_CHOICES:: 300 Multiple Choices - # MOVED:: 301 Moved Permanently - # REDIRECT:: 302 Found - # NOT_MODIFIED:: 304 Not Modified - # BAD_REQUEST:: 400 Bad Request - # AUTH_REQUIRED:: 401 Authorization Required - # FORBIDDEN:: 403 Forbidden - # NOT_FOUND:: 404 Not Found - # METHOD_NOT_ALLOWED:: 405 Method Not Allowed - # NOT_ACCEPTABLE:: 406 Not Acceptable - # LENGTH_REQUIRED:: 411 Length Required - # PRECONDITION_FAILED:: 412 Precondition Failed - # SERVER_ERROR:: 500 Internal Server Error - # NOT_IMPLEMENTED:: 501 Method Not Implemented - # BAD_GATEWAY:: 502 Bad Gateway - # VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates - # - # server:: The server software, returned as the Server header. - # connection:: The connection type, returned as the Connection header (for - # instance, "close". - # length:: The length of the content that will be sent, returned as the - # Content-Length header. - # language:: The language of the content, returned as the Content-Language - # header. - # expires:: The time on which the current content expires, as a +Time+ - # object, returned as the Expires header. - # cookie:: - # A cookie or cookies, returned as one or more Set-Cookie headers. The - # value can be the literal string of the cookie; a CGI::Cookie object; - # an Array of literal cookie strings or Cookie objects; or a hash all of - # whose values are literal cookie strings or Cookie objects. - # - # These cookies are in addition to the cookies held in the - # @output_cookies field. - # - # Other headers can also be set; they are appended as key: value. - # - # Examples: - # - # http_header - # # Content-Type: text/html - # - # http_header("text/plain") - # # Content-Type: text/plain - # - # http_header("nph" => true, - # "status" => "OK", # == "200 OK" - # # "status" => "200 GOOD", - # "server" => ENV['SERVER_SOFTWARE'], - # "connection" => "close", - # "type" => "text/html", - # "charset" => "iso-2022-jp", - # # Content-Type: text/html; charset=iso-2022-jp - # "length" => 103, - # "language" => "ja", - # "expires" => Time.now + 30, - # "cookie" => [cookie1, cookie2], - # "my_header1" => "my_value", - # "my_header2" => "my_value") - # - # This method does not perform charset conversion. - def http_header(options='text/html') - if options.is_a?(String) - content_type = options - buf = _header_for_string(content_type) - elsif options.is_a?(Hash) - if options.size == 1 && options.has_key?('type') - content_type = options['type'] - buf = _header_for_string(content_type) - else - buf = _header_for_hash(options.dup) - end - else - raise ArgumentError.new("expected String or Hash but got #{options.class}") - end - if defined?(MOD_RUBY) - _header_for_modruby(buf) - return '' - else - buf << EOL # empty line of separator - return buf - end - end # http_header() - - # This method is an alias for #http_header, when HTML5 tag maker is inactive. - # - # NOTE: use #http_header to create HTTP header blocks, this alias is only - # provided for backwards compatibility. - # - # Using #header with the HTML5 tag maker will create a <header> element. - alias :header :http_header - - def _no_crlf_check(str) - if str - str = str.to_s - raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/ - str - else - nil - end - end - private :_no_crlf_check - - def _header_for_string(content_type) #:nodoc: - buf = ''.dup - if nph?() - buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}" - buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}" - buf << "Connection: close#{EOL}" - end - buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}" - if @output_cookies - @output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" } - end - return buf - end # _header_for_string - private :_header_for_string - - def _header_for_hash(options) #:nodoc: - buf = ''.dup - ## add charset to option['type'] - options['type'] ||= 'text/html' - charset = options.delete('charset') - options['type'] += "; charset=#{charset}" if charset - ## NPH - options.delete('nph') if defined?(MOD_RUBY) - if options.delete('nph') || nph?() - protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0' - status = options.delete('status') - status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK' - buf << "#{protocol} #{status}#{EOL}" - buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}" - options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || '' - options['connection'] ||= 'close' - end - ## common headers - status = options.delete('status') - buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status - server = options.delete('server') - buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server - connection = options.delete('connection') - buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection - type = options.delete('type') - buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type - length = options.delete('length') - buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length - language = options.delete('language') - buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language - expires = options.delete('expires') - buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires - ## cookie - if cookie = options.delete('cookie') - case cookie - when String, Cookie - buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" - when Array - arr = cookie - arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - when Hash - hash = cookie - hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - end - if @output_cookies - @output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" } - end - ## other headers - options.each do |key, value| - buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}" - end - return buf - end # _header_for_hash - private :_header_for_hash - - def nph? #:nodoc: - return /IIS\/(\d+)/ =~ $CGI_ENV['SERVER_SOFTWARE'] && $1.to_i < 5 - end - - def _header_for_modruby(buf) #:nodoc: - request = Apache::request - buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value| - $stderr.printf("name:%s value:%s\n", name, value) if $DEBUG - case name - when 'Set-Cookie' - request.headers_out.add(name, value) - when /^status$/i - request.status_line = value - request.status = value.to_i - when /^content-type$/i - request.content_type = value - when /^content-encoding$/i - request.content_encoding = value - when /^location$/i - request.status = 302 if request.status == 200 - request.headers_out[name] = value - else - request.headers_out[name] = value - end - end - request.send_http_header - return '' - end - private :_header_for_modruby - - # Print an HTTP header and body to $DEFAULT_OUTPUT ($>) - # - # :call-seq: - # cgi.out(content_type_string='text/html') - # cgi.out(headers_hash) - # - # +content_type_string+:: - # If a string is passed, it is assumed to be the content type. - # +headers_hash+:: - # This is a Hash of headers, similar to that used by #http_header. - # +block+:: - # A block is required and should evaluate to the body of the response. - # - # <tt>Content-Length</tt> is automatically calculated from the size of - # the String returned by the content block. - # - # If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header - # is output (the content block is still required, but it is ignored). - # - # If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then the - # content is converted to this charset, and the language is set to "ja". - # - # Example: - # - # cgi = CGI.new - # cgi.out{ "string" } - # # Content-Type: text/html - # # Content-Length: 6 - # # - # # string - # - # cgi.out("text/plain") { "string" } - # # Content-Type: text/plain - # # Content-Length: 6 - # # - # # string - # - # cgi.out("nph" => true, - # "status" => "OK", # == "200 OK" - # "server" => ENV['SERVER_SOFTWARE'], - # "connection" => "close", - # "type" => "text/html", - # "charset" => "iso-2022-jp", - # # Content-Type: text/html; charset=iso-2022-jp - # "language" => "ja", - # "expires" => Time.now + (3600 * 24 * 30), - # "cookie" => [cookie1, cookie2], - # "my_header1" => "my_value", - # "my_header2" => "my_value") { "string" } - # # HTTP/1.1 200 OK - # # Date: Sun, 15 May 2011 17:35:54 GMT - # # Server: Apache 2.2.0 - # # Connection: close - # # Content-Type: text/html; charset=iso-2022-jp - # # Content-Length: 6 - # # Content-Language: ja - # # Expires: Tue, 14 Jun 2011 17:35:54 GMT - # # Set-Cookie: foo - # # Set-Cookie: bar - # # my_header1: my_value - # # my_header2: my_value - # # - # # string - def out(options = "text/html") # :yield: - - options = { "type" => options } if options.kind_of?(String) - content = yield - options["length"] = content.bytesize.to_s - output = stdoutput - output.binmode if defined? output.binmode - output.print http_header(options) - output.print content unless "HEAD" == env_table['REQUEST_METHOD'] - end - - - # Print an argument or list of arguments to the default output stream - # - # cgi = CGI.new - # cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print - def print(*options) - stdoutput.print(*options) - end - - # Parse an HTTP query string into a hash of key=>value pairs. - # - # params = CGI.parse("query_string") - # # {"name1" => ["value1", "value2", ...], - # # "name2" => ["value1", "value2", ...], ... } - # - def self.parse(query) - params = {} - query.split(/[&;]/).each do |pairs| - key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) } - - next unless key - - params[key] ||= [] - params[key].push(value) if value - end - - params.default=[].freeze - params - end - - # Maximum content length of post data - ##MAX_CONTENT_LENGTH = 2 * 1024 * 1024 - - # Maximum number of request parameters when multipart - MAX_MULTIPART_COUNT = 128 - - # Mixin module that provides the following: - # - # 1. Access to the CGI environment variables as methods. See - # documentation to the CGI class for a list of these variables. The - # methods are exposed by removing the leading +HTTP_+ (if it exists) and - # downcasing the name. For example, +auth_type+ will return the - # environment variable +AUTH_TYPE+, and +accept+ will return the value - # for +HTTP_ACCEPT+. - # - # 2. Access to cookies, including the cookies attribute. - # - # 3. Access to parameters, including the params attribute, and overloading - # #[] to perform parameter value lookup by key. - # - # 4. The initialize_query method, for initializing the above - # mechanisms, handling multipart forms, and allowing the - # class to be used in "offline" mode. - # - module QueryExtension - - %w[ CONTENT_LENGTH SERVER_PORT ].each do |env| - define_method(env.delete_prefix('HTTP_').downcase) do - (val = env_table[env]) && Integer(val) - end - end - - %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST - REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME - SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE - - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST - HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env| - define_method(env.delete_prefix('HTTP_').downcase) do - env_table[env] - end - end - - # Get the raw cookies as a string. - def raw_cookie - env_table["HTTP_COOKIE"] - end - - # Get the raw RFC2965 cookies as a string. - def raw_cookie2 - env_table["HTTP_COOKIE2"] - end - - # Get the cookies as a hash of cookie-name=>Cookie pairs. - attr_accessor :cookies - - # Get the parameters as a hash of name=>values pairs, where - # values is an Array. - attr_reader :params - - # Get the uploaded files as a hash of name=>values pairs - attr_reader :files - - # Set all the parameters. - def params=(hash) - @params.clear - @params.update(hash) - end - - ## - # Parses multipart form elements according to - # http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2 - # - # Returns a hash of multipart form parameters with bodies of type StringIO or - # Tempfile depending on whether the multipart form element exceeds 10 KB - # - # params[name => body] - # - def read_multipart(boundary, content_length) - ## read first boundary - stdin = stdinput - first_line = "--#{boundary}#{EOL}" - content_length -= first_line.bytesize - status = stdin.read(first_line.bytesize) - raise EOFError.new("no content body") unless status - raise EOFError.new("bad content body") unless first_line == status - ## parse and set params - params = {} - @files = {} - boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/ - boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize - buf = ''.dup - bufsize = 10 * 1024 - max_count = MAX_MULTIPART_COUNT - n = 0 - tempfiles = [] - while true - (n += 1) < max_count or raise StandardError.new("too many parameters.") - ## create body (StringIO or Tempfile) - body = create_body(bufsize < content_length) - tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile) - class << body - if method_defined?(:path) - alias local_path path - else - def local_path - nil - end - end - attr_reader :original_filename, :content_type - end - ## find head and boundary - head = nil - separator = EOL * 2 - until head && matched = boundary_rexp.match(buf) - if !head && pos = buf.index(separator) - len = pos + EOL.bytesize - head = buf[0, len] - buf = buf[(pos+separator.bytesize)..-1] - else - if head && buf.size > boundary_size - len = buf.size - boundary_size - body.print(buf[0, len]) - buf[0, len] = '' - end - c = stdin.read(bufsize < content_length ? bufsize : content_length) - raise EOFError.new("bad content body") if c.nil? || c.empty? - buf << c - content_length -= c.bytesize - end - end - ## read to end of boundary - m = matched - len = m.begin(0) - s = buf[0, len] - if s =~ /(\r?\n)\z/ - s = buf[0, len - $1.bytesize] - end - body.print(s) - buf = buf[m.end(0)..-1] - boundary_end = m[1] - content_length = -1 if boundary_end == '--' - ## reset file cursor position - body.rewind - ## original filename - /Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head) - filename = $1 || $2 || ''.dup - filename = CGI.unescape(filename) if unescape_filename?() - body.instance_variable_set(:@original_filename, filename) - ## content type - /Content-Type: (.*)/i.match(head) - (content_type = $1 || ''.dup).chomp! - body.instance_variable_set(:@content_type, content_type) - ## query parameter name - /Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head) - name = $1 || $2 || '' - if body.original_filename.empty? - value=body.read.dup.force_encoding(@accept_charset) - body.close! if defined?(Tempfile) && body.kind_of?(Tempfile) - (params[name] ||= []) << value - unless value.valid_encoding? - if @accept_charset_error_block - @accept_charset_error_block.call(name,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - class << params[name].last;self;end.class_eval do - define_method(:read){self} - define_method(:original_filename){""} - define_method(:content_type){""} - end - else - (params[name] ||= []) << body - @files[name]=body - end - ## break loop - break if content_length == -1 - end - raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/ - params.default = [] - params - rescue Exception - if tempfiles - tempfiles.each {|t| - if t.path - t.close! - end - } - end - raise - end # read_multipart - private :read_multipart - def create_body(is_large) #:nodoc: - if is_large - require 'tempfile' - body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) - else - begin - require 'stringio' - body = StringIO.new("".b) - rescue LoadError - require 'tempfile' - body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT) - end - end - body.binmode if defined? body.binmode - return body - end - def unescape_filename? #:nodoc: - user_agent = $CGI_ENV['HTTP_USER_AGENT'] - return false unless user_agent - return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent) - end - - # offline mode. read name=value pairs on standard input. - def read_from_cmdline - require "shellwords" - - string = unless ARGV.empty? - ARGV.join(' ') - else - if STDIN.tty? - STDERR.print( - %|(offline mode: enter name=value pairs on standard input)\n| - ) - end - array = readlines rescue nil - if not array.nil? - array.join(' ').gsub(/\n/n, '') - else - "" - end - end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26') - - words = Shellwords.shellwords(string) - - if words.find{|x| /=/n.match(x) } - words.join('&') - else - words.join('+') - end - end - private :read_from_cmdline - - # A wrapper class to use a StringIO object as the body and switch - # to a TempFile when the passed threshold is passed. - # Initialize the data from the query. - # - # Handles multipart forms (in particular, forms that involve file uploads). - # Reads query parameters in the @params field, and cookies into @cookies. - def initialize_query() - if ("POST" == env_table['REQUEST_METHOD']) and - %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?| =~ env_table['CONTENT_TYPE'] - current_max_multipart_length = @max_multipart_length.respond_to?(:call) ? @max_multipart_length.call : @max_multipart_length - raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > current_max_multipart_length - boundary = $1.dup - @multipart = true - @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH'])) - else - @multipart = false - @params = CGI.parse( - case env_table['REQUEST_METHOD'] - when "GET", "HEAD" - if defined?(MOD_RUBY) - Apache::request.args or "" - else - env_table['QUERY_STRING'] or "" - end - when "POST" - stdinput.binmode if defined? stdinput.binmode - stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or '' - else - read_from_cmdline - end.dup.force_encoding(@accept_charset) - ) - unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT - @params.each do |key,values| - values.each do |value| - unless value.valid_encoding? - if @accept_charset_error_block - @accept_charset_error_block.call(key,value) - else - raise InvalidEncoding,"Accept-Charset encoding error" - end - end - end - end - end - end - - @cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE'])) - end - private :initialize_query - - # Returns whether the form contained multipart/form-data - def multipart? - @multipart - end - - # Get the value for the parameter with a given key. - # - # If the parameter has multiple values, only the first will be - # retrieved; use #params to get the array of values. - def [](key) - params = @params[key] - return '' unless params - value = params[0] - if @multipart - if value - return value - elsif defined? StringIO - StringIO.new("".b) - else - Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT) - end - else - str = if value then value.dup else "" end - str - end - end - - # Return all query parameter names as an array of String. - def keys(*args) - @params.keys(*args) - end - - # Returns true if a given query string parameter exists. - def has_key?(*args) - @params.has_key?(*args) - end - alias key? has_key? - alias include? has_key? - - end # QueryExtension - - # Exception raised when there is an invalid encoding detected - class InvalidEncoding < Exception; end - - # @@accept_charset is default accept character set. - # This default value default is "UTF-8" - # If you want to change the default accept character set - # when create a new CGI instance, set this: - # - # CGI.accept_charset = "EUC-JP" - # - @@accept_charset="UTF-8" if false # needed for rdoc? - - # Return the accept character set for all new CGI instances. - def self.accept_charset - @@accept_charset - end - - # Set the accept character set for all new CGI instances. - def self.accept_charset=(accept_charset) - @@accept_charset=accept_charset - end - - # Return the accept character set for this CGI instance. - attr_reader :accept_charset - - # @@max_multipart_length is the maximum length of multipart data. - # The default value is 128 * 1024 * 1024 bytes - # - # The default can be set to something else in the CGI constructor, - # via the :max_multipart_length key in the option hash. - # - # See CGI.new documentation. - # - @@max_multipart_length= 128 * 1024 * 1024 - - # Create a new CGI instance. - # - # :call-seq: - # CGI.new(tag_maker) { block } - # CGI.new(options_hash = {}) { block } - # - # - # <tt>tag_maker</tt>:: - # This is the same as using the +options_hash+ form with the value <tt>{ - # :tag_maker => tag_maker }</tt> Note that it is recommended to use the - # +options_hash+ form, since it also allows you specify the charset you - # will accept. - # <tt>options_hash</tt>:: - # A Hash that recognizes three options: - # - # <tt>:accept_charset</tt>:: - # specifies encoding of received query string. If omitted, - # <tt>@@accept_charset</tt> is used. If the encoding is not valid, a - # CGI::InvalidEncoding will be raised. - # - # Example. Suppose <tt>@@accept_charset</tt> is "UTF-8" - # - # when not specified: - # - # cgi=CGI.new # @accept_charset # => "UTF-8" - # - # when specified as "EUC-JP": - # - # cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP" - # - # <tt>:tag_maker</tt>:: - # String that specifies which version of the HTML generation methods to - # use. If not specified, no HTML generation methods will be loaded. - # - # The following values are supported: - # - # "html3":: HTML 3.x - # "html4":: HTML 4.0 - # "html4Tr":: HTML 4.0 Transitional - # "html4Fr":: HTML 4.0 with Framesets - # "html5":: HTML 5 - # - # <tt>:max_multipart_length</tt>:: - # Specifies maximum length of multipart data. Can be an Integer scalar or - # a lambda, that will be evaluated when the request is parsed. This - # allows more complex logic to be set when determining whether to accept - # multipart data (e.g. consult a registered users upload allowance) - # - # Default is 128 * 1024 * 1024 bytes - # - # cgi=CGI.new(:max_multipart_length => 268435456) # simple scalar - # - # cgi=CGI.new(:max_multipart_length => -> {check_filesystem}) # lambda - # - # <tt>block</tt>:: - # If provided, the block is called when an invalid encoding is - # encountered. For example: - # - # encoding_errors={} - # cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value| - # encoding_errors[name] = value - # end - # - # Finally, if the CGI object is not created in a standard CGI call - # environment (that is, it can't locate REQUEST_METHOD in its environment), - # then it will run in "offline" mode. In this mode, it reads its parameters - # from the command line or (failing that) from standard input. Otherwise, - # cookies and other parameters are parsed automatically from the standard - # CGI locations, which varies according to the REQUEST_METHOD. - def initialize(options = {}, &block) # :yields: name, value - @accept_charset_error_block = block_given? ? block : nil - @options={ - :accept_charset=>@@accept_charset, - :max_multipart_length=>@@max_multipart_length - } - case options - when Hash - @options.merge!(options) - when String - @options[:tag_maker]=options - end - @accept_charset=@options[:accept_charset] - @max_multipart_length=@options[:max_multipart_length] - if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE") - Apache.request.setup_cgi_env - end - - extend QueryExtension - @multipart = false - - initialize_query() # set @params, @cookies - @output_cookies = nil - @output_hidden = nil - - case @options[:tag_maker] - when "html3" - require_relative 'html' - extend Html3 - extend HtmlExtension - when "html4" - require_relative 'html' - extend Html4 - extend HtmlExtension - when "html4Tr" - require_relative 'html' - extend Html4Tr - extend HtmlExtension - when "html4Fr" - require_relative 'html' - extend Html4Tr - extend Html4Fr - extend HtmlExtension - when "html5" - require_relative 'html' - extend Html5 - extend HtmlExtension - end - end - -end # class CGI diff --git a/lib/cgi/escape.rb b/lib/cgi/escape.rb new file mode 100644 index 0000000000..555d24a5da --- /dev/null +++ b/lib/cgi/escape.rb @@ -0,0 +1,232 @@ +# frozen_string_literal: true + +# Since Ruby 4.0, \CGI is a small holder for various escaping methods, included from CGI::Escape +# +# require 'cgi/escape' +# +# CGI.escape("Ruby programming language") +# #=> "Ruby+programming+language" +# CGI.escapeURIComponent("Ruby programming language") +# #=> "Ruby%20programming%20language" +# +# See CGI::Escape module for methods list and their description. +class CGI + module Escape; end + include Escape + extend Escape + module EscapeExt; end # :nodoc: +end + +# Web-related escape/unescape functionality. +module CGI::Escape + @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) + + # URL-encode a string into application/x-www-form-urlencoded. + # Space characters (<tt>" "</tt>) are encoded with plus signs (<tt>"+"</tt>) + # url_encoded_string = CGI.escape("'Stop!' said Fred") + # # => "%27Stop%21%27+said+Fred" + def escape(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.tr!(' ', '+') + buffer.force_encoding(encoding) + end + + # URL-decode an application/x-www-form-urlencoded string with encoding(optional). + # string = CGI.unescape("%27Stop%21%27+said+Fred") + # # => "'Stop!' said Fred" + def unescape(string, encoding = @@accept_charset) + str = string.tr('+', ' ') + str = str.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + # URL-encode a string following RFC 3986 + # Space characters (<tt>" "</tt>) are encoded with (<tt>"%20"</tt>) + # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") + # # => "%27Stop%21%27%20said%20Fred" + def escapeURIComponent(string) + encoding = string.encoding + buffer = string.b + buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m| + '%' + m.unpack('H2' * m.bytesize).join('%').upcase + end + buffer.force_encoding(encoding) + end + alias escape_uri_component escapeURIComponent + + # URL-decode a string following RFC 3986 with encoding(optional). + # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred") + # # => "'Stop!'+said Fred" + def unescapeURIComponent(string, encoding = @@accept_charset) + str = string.b + str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| + [m.delete('%')].pack('H*') + end + str.force_encoding(encoding) + str.valid_encoding? ? str : str.force_encoding(string.encoding) + end + + alias unescape_uri_component unescapeURIComponent + + # The set of special characters and their escaped values + TABLE_FOR_ESCAPE_HTML__ = { # :nodoc: + "'" => ''', + '&' => '&', + '"' => '"', + '<' => '<', + '>' => '>', + } + + # \Escape special characters in HTML, namely <tt>'&\"<></tt> + # CGI.escapeHTML('Usage: foo "bar" <baz>') + # # => "Usage: foo "bar" <baz>" + def escapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] + string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) + string.encode!(origenc) if origenc + string + else + string = string.b + string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) + end + end + + # Unescape a string that has been HTML-escaped + # CGI.unescapeHTML("Usage: foo "bar" <baz>") + # # => "Usage: foo \"bar\" <baz>" + def unescapeHTML(string) + enc = string.encoding + unless enc.ascii_compatible? + if enc.dummy? + origenc = enc + enc = Encoding::Converter.asciicompat_encoding(enc) + string = enc ? string.encode(enc) : string.b + end + string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do + case $1.encode(Encoding::US_ASCII) + when 'apos' then "'".encode(enc) + when 'amp' then '&'.encode(enc) + when 'quot' then '"'.encode(enc) + when 'gt' then '>'.encode(enc) + when 'lt' then '<'.encode(enc) + when /\A#0*(\d+)\z/ then $1.to_i.chr(enc) + when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc) + end + end + string.encode!(origenc) if origenc + return string + end + return string unless string.include? '&' + charlimit = case enc + when Encoding::UTF_8; 0x10ffff + when Encoding::ISO_8859_1; 256 + else 128 + end + string = string.b + string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + match = $1.dup + case match + when 'apos' then "'" + when 'amp' then '&' + when 'quot' then '"' + when 'gt' then '>' + when 'lt' then '<' + when /\A#0*(\d+)\z/ + n = $1.to_i + if n < charlimit + n.chr(enc) + else + "&##{$1};" + end + when /\A#x([0-9a-f]+)\z/i + n = $1.hex + if n < charlimit + n.chr(enc) + else + "&#x#{$1};" + end + else + "&#{match};" + end + end + string.force_encoding enc + end + + alias escape_html escapeHTML + alias h escapeHTML + + alias unescape_html unescapeHTML + + # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there + unless RUBY_ENGINE == 'truffleruby' + begin + require 'cgi/escape.so' + rescue LoadError + end + end + + # \Escape only the tags of certain HTML elements in +string+. + # + # Takes an element or elements or array of elements. Each element + # is specified by the name of the element, without angle brackets. + # This matches both the start and the end tag of that element. + # The attribute list of the open tag will also be escaped (for + # instance, the double-quotes surrounding attribute values). + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def escapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do + CGI.escapeHTML($&) + end + else + string + end + end + + # Undo escaping such as that done by CGI.escapeElement + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG") + # # "<BR><A HREF="url"></A>" + # + # print CGI.unescapeElement( + # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) + # # "<BR><A HREF="url"></A>" + def unescapeElement(string, *elements) + elements = elements[0] if elements[0].kind_of?(Array) + unless elements.empty? + string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do + unescapeHTML($&) + end + else + string + end + end + + alias escape_element escapeElement + + alias unescape_element unescapeElement + +end diff --git a/lib/cgi/html.rb b/lib/cgi/html.rb deleted file mode 100644 index 1543943320..0000000000 --- a/lib/cgi/html.rb +++ /dev/null @@ -1,1035 +0,0 @@ -# frozen_string_literal: true -class CGI - # Base module for HTML-generation mixins. - # - # Provides methods for code generation for tags following - # the various DTD element types. - module TagMaker # :nodoc: - - # Generate code for an element with required start and end tags. - # - # - - - def nn_element(element, attributes = {}) - s = nOE_element(element, attributes) - if block_given? - s << yield.to_s - end - s << "</#{element.upcase}>" - end - - def nn_element_def(attributes = {}, &block) - nn_element(__callee__, attributes, &block) - end - - # Generate code for an empty element. - # - # - O EMPTY - def nOE_element(element, attributes = {}) - attributes={attributes=>nil} if attributes.kind_of?(String) - s = "<#{element.upcase}".dup - attributes.each do|name, value| - next unless value - s << " " - s << CGI.escapeHTML(name.to_s) - if value != true - s << '="' - s << CGI.escapeHTML(value.to_s) - s << '"' - end - end - s << ">" - end - - def nOE_element_def(attributes = {}, &block) - nOE_element(__callee__, attributes, &block) - end - - - # Generate code for an element for which the end (and possibly the - # start) tag is optional. - # - # O O or - O - def nO_element(element, attributes = {}) - s = nOE_element(element, attributes) - if block_given? - s << yield.to_s - s << "</#{element.upcase}>" - end - s - end - - def nO_element_def(attributes = {}, &block) - nO_element(__callee__, attributes, &block) - end - - end # TagMaker - - - # Mixin module providing HTML generation methods. - # - # For example, - # cgi.a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # Modules Html3, Html4, etc., contain more basic HTML-generation methods - # (+#title+, +#h1+, etc.). - # - # See class CGI for a detailed example. - # - module HtmlExtension - - - # Generate an Anchor element as a string. - # - # +href+ can either be a string, giving the URL - # for the HREF attribute, or it can be a hash of - # the element's attributes. - # - # The body of the element is the string returned by the no-argument - # block passed in. - # - # a("http://www.example.com") { "Example" } - # # => "<A HREF=\"http://www.example.com\">Example</A>" - # - # a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" } - # # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>" - # - def a(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a Document Base URI element as a String. - # - # +href+ can either by a string, giving the base URL for the HREF - # attribute, or it can be a has of the element's attributes. - # - # The passed-in no-argument block is ignored. - # - # base("http://www.example.com/cgi") - # # => "<BASE HREF=\"http://www.example.com/cgi\">" - def base(href = "") # :yield: - attributes = if href.kind_of?(String) - { "HREF" => href } - else - href - end - super(attributes) - end - - # Generate a BlockQuote element as a string. - # - # +cite+ can either be a string, give the URI for the source of - # the quoted text, or a hash, giving all attributes of the element, - # or it can be omitted, in which case the element has no attributes. - # - # The body is provided by the passed-in no-argument block - # - # blockquote("http://www.example.com/quotes/foo.html") { "Foo!" } - # #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE> - def blockquote(cite = {}) # :yield: - attributes = if cite.kind_of?(String) - { "CITE" => cite } - else - cite - end - super(attributes) - end - - - # Generate a Table Caption element as a string. - # - # +align+ can be a string, giving the alignment of the caption - # (one of top, bottom, left, or right). It can be a hash of - # all the attributes of the element. Or it can be omitted. - # - # The body of the element is provided by the passed-in no-argument block. - # - # caption("left") { "Capital Cities" } - # # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION> - def caption(align = {}) # :yield: - attributes = if align.kind_of?(String) - { "ALIGN" => align } - else - align - end - super(attributes) - end - - - # Generate a Checkbox Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +value+, and +checked+. +checked+ is a boolean value; - # if true, the CHECKED attribute will be included in the element. - # - # Alternatively, the attributes can be specified as a hash. - # - # checkbox("name") - # # = checkbox("NAME" => "name") - # - # checkbox("name", "value") - # # = checkbox("NAME" => "name", "VALUE" => "value") - # - # checkbox("name", "value", true) - # # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true) - def checkbox(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "checkbox", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "checkbox" - name - end - input(attributes) - end - - # Generate a sequence of checkbox elements, as a String. - # - # The checkboxes will all have the same +name+ attribute. - # Each checkbox is followed by a label. - # There will be one checkbox for each value. Each value - # can be specified as a String, which will be used both - # as the value of the VALUE attribute and as the label - # for that checkbox. A single-element array has the - # same effect. - # - # Each value can also be specified as a three-element array. - # The first element is the VALUE attribute; the second is the - # label; and the third is a boolean specifying whether this - # checkbox is CHECKED. - # - # Each value can also be specified as a two-element - # array, by omitting either the value element (defaults - # to the same as the label), or the boolean checked element - # (defaults to false). - # - # checkbox_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz - # - # checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz - # - # checkbox_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # checkbox_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def checkbox_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - checkbox(name, value) + value - else - if value[-1] == true || value[-1] == false - checkbox(name, value[0], value[-1]) + - value[-2] - else - checkbox(name, value[0]) + - value[-1] - end - end - }.join - end - - - # Generate an File Upload Input element as a string. - # - # The attributes of the element can be specified as three arguments, - # +name+, +size+, and +maxlength+. +maxlength+ is the maximum length - # of the file's _name_, not of the file's _contents_. - # - # Alternatively, the attributes can be specified as a hash. - # - # See #multipart_form() for forms that include file uploads. - # - # file_field("name") - # # <INPUT TYPE="file" NAME="name" SIZE="20"> - # - # file_field("name", 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - # - # file_field("name", 40, 100) - # # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100"> - # - # file_field("NAME" => "name", "SIZE" => 40) - # # <INPUT TYPE="file" NAME="name" SIZE="40"> - def file_field(name = "", size = 20, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "file", "NAME" => name, - "SIZE" => size.to_s } - else - name["TYPE"] = "file" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - - # Generate a Form element as a string. - # - # +method+ should be either "get" or "post", and defaults to the latter. - # +action+ defaults to the current CGI script name. +enctype+ - # defaults to "application/x-www-form-urlencoded". - # - # Alternatively, the attributes can be specified as a hash. - # - # See also #multipart_form() for forms that include file uploads. - # - # form{ "string" } - # # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get") { "string" } - # # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("get", "url") { "string" } - # # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM> - # - # form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" } - # # <FORM METHOD="post" ENCTYPE="enctype">string</FORM> - def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded") - attributes = if method.kind_of?(String) - { "METHOD" => method, "ACTION" => action, - "ENCTYPE" => enctype } - else - unless method.has_key?("METHOD") - method["METHOD"] = "post" - end - unless method.has_key?("ENCTYPE") - method["ENCTYPE"] = enctype - end - method - end - if block_given? - body = yield - else - body = "" - end - if @output_hidden - body << @output_hidden.collect{|k,v| - "<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">" - }.join - end - super(attributes){body} - end - - # Generate a Hidden Input element as a string. - # - # The attributes of the element can be specified as two arguments, - # +name+ and +value+. - # - # Alternatively, the attributes can be specified as a hash. - # - # hidden("name") - # # <INPUT TYPE="hidden" NAME="name"> - # - # hidden("name", "value") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value"> - # - # hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo"> - def hidden(name = "", value = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "hidden", "NAME" => name, "VALUE" => value } - else - name["TYPE"] = "hidden" - name - end - input(attributes) - end - - # Generate a top-level HTML element as a string. - # - # The attributes of the element are specified as a hash. The - # pseudo-attribute "PRETTY" can be used to specify that the generated - # HTML string should be indented. "PRETTY" can also be specified as - # a string as the sole argument to this method. The pseudo-attribute - # "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it - # should include the entire text of this tag, including angle brackets. - # - # The body of the html element is supplied as a block. - # - # html{ "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML> - # - # html("LANG" => "ja") { "string" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML> - # - # html("DOCTYPE" => false) { "string" } - # # <HTML>string</HTML> - # - # html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" } - # # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML> - # - # html("PRETTY" => " ") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY" => "\t") { "<BODY></BODY>" } - # # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # html("PRETTY") { "<BODY></BODY>" } - # # = html("PRETTY" => " ") { "<BODY></BODY>" } - # - # html(if $VERBOSE then "PRETTY" end) { "HTML string" } - # - def html(attributes = {}) # :yield: - if nil == attributes - attributes = {} - elsif "PRETTY" == attributes - attributes = { "PRETTY" => true } - end - pretty = attributes.delete("PRETTY") - pretty = " " if true == pretty - buf = "".dup - - if attributes.has_key?("DOCTYPE") - if attributes["DOCTYPE"] - buf << attributes.delete("DOCTYPE") - else - attributes.delete("DOCTYPE") - end - else - buf << doctype - end - - buf << super(attributes) - - if pretty - CGI.pretty(buf, pretty) - else - buf - end - - end - - # Generate an Image Button Input element as a string. - # - # +src+ is the URL of the image to use for the button. +name+ - # is the input name. +alt+ is the alternative text for the image. - # - # Alternatively, the attributes can be specified as a hash. - # - # image_button("url") - # # <INPUT TYPE="image" SRC="url"> - # - # image_button("url", "name", "string") - # # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string"> - # - # image_button("SRC" => "url", "ALT" => "string") - # # <INPUT TYPE="image" SRC="url" ALT="string"> - def image_button(src = "", name = nil, alt = nil) - attributes = if src.kind_of?(String) - { "TYPE" => "image", "SRC" => src, "NAME" => name, - "ALT" => alt } - else - src["TYPE"] = "image" - src["SRC"] ||= "" - src - end - input(attributes) - end - - - # Generate an Image element as a string. - # - # +src+ is the URL of the image. +alt+ is the alternative text for - # the image. +width+ is the width of the image, and +height+ is - # its height. - # - # Alternatively, the attributes can be specified as a hash. - # - # img("src", "alt", 100, 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - # - # img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50) - # # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50"> - def img(src = "", alt = "", width = nil, height = nil) - attributes = if src.kind_of?(String) - { "SRC" => src, "ALT" => alt } - else - src - end - attributes["WIDTH"] = width.to_s if width - attributes["HEIGHT"] = height.to_s if height - super(attributes) - end - - - # Generate a Form element with multipart encoding as a String. - # - # Multipart encoding is used for forms that include file uploads. - # - # +action+ is the action to perform. +enctype+ is the encoding - # type, which defaults to "multipart/form-data". - # - # Alternatively, the attributes can be specified as a hash. - # - # multipart_form{ "string" } - # # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM> - # - # multipart_form("url") { "string" } - # # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM> - def multipart_form(action = nil, enctype = "multipart/form-data") - attributes = if action == nil - { "METHOD" => "post", "ENCTYPE" => enctype } - elsif action.kind_of?(String) - { "METHOD" => "post", "ACTION" => action, - "ENCTYPE" => enctype } - else - unless action.has_key?("METHOD") - action["METHOD"] = "post" - end - unless action.has_key?("ENCTYPE") - action["ENCTYPE"] = enctype - end - action - end - if block_given? - form(attributes){ yield } - else - form(attributes) - end - end - - - # Generate a Password Input element as a string. - # - # +name+ is the name of the input field. +value+ is its default - # value. +size+ is the size of the input field display. +maxlength+ - # is the maximum length of the inputted password. - # - # Alternatively, attributes can be specified as a hash. - # - # password_field("name") - # # <INPUT TYPE="password" NAME="name" SIZE="40"> - # - # password_field("name", "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40"> - # - # password_field("password", "value", 80, 200) - # # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # password_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="password" NAME="name" VALUE="value"> - def password_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "password", "NAME" => name, - "VALUE" => value, "SIZE" => size.to_s } - else - name["TYPE"] = "password" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a Select element as a string. - # - # +name+ is the name of the element. The +values+ are the options that - # can be selected from the Select menu. Each value can be a String or - # a one, two, or three-element Array. If a String or a one-element - # Array, this is both the value of that option and the text displayed for - # it. If a three-element Array, the elements are the option value, displayed - # text, and a boolean value specifying whether this option starts as selected. - # The two-element version omits either the option value (defaults to the same - # as the display text) or the boolean selected specifier (defaults to false). - # - # The attributes and options can also be specified as a hash. In this - # case, options are specified as an array of values as described above, - # with the hash key of "VALUES". - # - # popup_menu("name", "foo", "bar", "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar">bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["foo"], ["bar", true], "baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="foo">foo</OPTION> - # # <OPTION VALUE="bar" SELECTED>bar</OPTION> - # # <OPTION VALUE="baz">baz</OPTION> - # # </SELECT> - # - # popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <SELECT NAME="name"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - # - # popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true, - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - # # <SELECT NAME="name" MULTIPLE SIZE="2"> - # # <OPTION VALUE="1">Foo</OPTION> - # # <OPTION SELECTED VALUE="2">Bar</OPTION> - # # <OPTION VALUE="Baz">Baz</OPTION> - # # </SELECT> - def popup_menu(name = "", *values) - - if name.kind_of?(Hash) - values = name["VALUES"] - size = name["SIZE"].to_s if name["SIZE"] - multiple = name["MULTIPLE"] - name = name["NAME"] - else - size = nil - multiple = nil - end - - select({ "NAME" => name, "SIZE" => size, - "MULTIPLE" => multiple }){ - values.collect{|value| - if value.kind_of?(String) - option({ "VALUE" => value }){ value } - else - if value[value.size - 1] == true - option({ "VALUE" => value[0], "SELECTED" => true }){ - value[value.size - 2] - } - else - option({ "VALUE" => value[0] }){ - value[value.size - 1] - } - end - end - }.join - } - - end - - # Generates a radio-button Input element. - # - # +name+ is the name of the input field. +value+ is the value of - # the field if checked. +checked+ specifies whether the field - # starts off checked. - # - # Alternatively, the attributes can be specified as a hash. - # - # radio_button("name", "value") - # # <INPUT TYPE="radio" NAME="name" VALUE="value"> - # - # radio_button("name", "value", true) - # # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED> - # - # radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo") - # # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo"> - def radio_button(name = "", value = nil, checked = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "radio", "NAME" => name, - "VALUE" => value, "CHECKED" => checked } - else - name["TYPE"] = "radio" - name - end - input(attributes) - end - - # Generate a sequence of radio button Input elements, as a String. - # - # This works the same as #checkbox_group(). However, it is not valid - # to have more than one radiobutton in a group checked. - # - # radio_group("name", "foo", "bar", "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["foo"], ["bar", true], "baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar - # # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz - # - # radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz") - # # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo - # # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar - # # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz - # - # radio_group("NAME" => "name", - # "VALUES" => ["foo", "bar", "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["foo"], ["bar", true], "baz"]) - # - # radio_group("NAME" => "name", - # "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"]) - def radio_group(name = "", *values) - if name.kind_of?(Hash) - values = name["VALUES"] - name = name["NAME"] - end - values.collect{|value| - if value.kind_of?(String) - radio_button(name, value) + value - else - if value[-1] == true || value[-1] == false - radio_button(name, value[0], value[-1]) + - value[-2] - else - radio_button(name, value[0]) + - value[-1] - end - end - }.join - end - - # Generate a reset button Input element, as a String. - # - # This resets the values on a form to their initial values. +value+ - # is the text displayed on the button. +name+ is the name of this button. - # - # Alternatively, the attributes can be specified as a hash. - # - # reset - # # <INPUT TYPE="reset"> - # - # reset("reset") - # # <INPUT TYPE="reset" VALUE="reset"> - # - # reset("VALUE" => "reset", "ID" => "foo") - # # <INPUT TYPE="reset" VALUE="reset" ID="foo"> - def reset(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "reset", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "reset" - value - end - input(attributes) - end - - alias scrolling_list popup_menu - - # Generate a submit button Input element, as a String. - # - # +value+ is the text to display on the button. +name+ is the name - # of the input. - # - # Alternatively, the attributes can be specified as a hash. - # - # submit - # # <INPUT TYPE="submit"> - # - # submit("ok") - # # <INPUT TYPE="submit" VALUE="ok"> - # - # submit("ok", "button1") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1"> - # - # submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo") - # # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo"> - def submit(value = nil, name = nil) - attributes = if (not value) or value.kind_of?(String) - { "TYPE" => "submit", "VALUE" => value, "NAME" => name } - else - value["TYPE"] = "submit" - value - end - input(attributes) - end - - # Generate a text field Input element, as a String. - # - # +name+ is the name of the input field. +value+ is its initial - # value. +size+ is the size of the input area. +maxlength+ - # is the maximum length of input accepted. - # - # Alternatively, the attributes can be specified as a hash. - # - # text_field("name") - # # <INPUT TYPE="text" NAME="name" SIZE="40"> - # - # text_field("name", "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40"> - # - # text_field("name", "value", 80) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80"> - # - # text_field("name", "value", 80, 200) - # # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200"> - # - # text_field("NAME" => "name", "VALUE" => "value") - # # <INPUT TYPE="text" NAME="name" VALUE="value"> - def text_field(name = "", value = nil, size = 40, maxlength = nil) - attributes = if name.kind_of?(String) - { "TYPE" => "text", "NAME" => name, "VALUE" => value, - "SIZE" => size.to_s } - else - name["TYPE"] = "text" - name - end - attributes["MAXLENGTH"] = maxlength.to_s if maxlength - input(attributes) - end - - # Generate a TextArea element, as a String. - # - # +name+ is the name of the textarea. +cols+ is the number of - # columns and +rows+ is the number of rows in the display. - # - # Alternatively, the attributes can be specified as a hash. - # - # The body is provided by the passed-in no-argument block - # - # textarea("name") - # # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10) - # - # textarea("name", 40, 5) - # # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5) - def textarea(name = "", cols = 70, rows = 10) # :yield: - attributes = if name.kind_of?(String) - { "NAME" => name, "COLS" => cols.to_s, - "ROWS" => rows.to_s } - else - name - end - super(attributes) - end - - end # HtmlExtension - - - # Mixin module for HTML version 3 generation methods. - module Html3 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">| - end - - instance_method(:nn_element_def).tap do |m| - # - - - for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG - DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV CENTER MAP - APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT TABLE TITLE - STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE - CAPTION ] - define_method(element.downcase, m) - end - end - - instance_method(:nOE_element_def).tap do |m| - # - O EMPTY - for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT - ISINDEX META ] - define_method(element.downcase, m) - end - end - - instance_method(:nO_element_def).tap do |m| - # O O or - O - for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION TR - TH TD ] - define_method(element.downcase, m) - end - end - - end # Html3 - - - # Mixin module for HTML version 4 generation methods. - module Html4 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">| - end - - # Initialize the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD - VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT - H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP - FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT - TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY - COLGROUP TR TH TD HEAD ] - define_method(element.downcase, m) - end - end - - end # Html4 - - - # Mixin module for HTML version 4 transitional generation methods. - module Html4Tr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN - CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO - ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q - INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET - LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT - NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT - COL ISINDEX META ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY - COLGROUP TR TH TD HEAD ] - define_method(element.downcase, m) - end - end - - end # Html4Tr - - - # Mixin module for generating HTML version 4 with framesets. - module Html4Fr # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ FRAMESET ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ FRAME ] - define_method(element.downcase, m) - end - end - - end # Html4Fr - - - # Mixin module for HTML version 5 generation methods. - module Html5 # :nodoc: - include TagMaker - - # The DOCTYPE declaration for this version of HTML - def doctype - %|<!DOCTYPE HTML>| - end - - # Initialise the HTML generation methods for this version. - # - - - instance_method(:nn_element_def).tap do |m| - for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP HEADER - FOOTER FIGURE FIGCAPTION S TIME U MARK RUBY BDI IFRAME - VIDEO AUDIO CANVAS DATALIST OUTPUT PROGRESS METER DETAILS - SUMMARY MENU DIALOG I B SMALL EM STRONG DFN CODE SAMP KBD - VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT - H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT - FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT - TEXTAREA FORM A BLOCKQUOTE CAPTION ] - define_method(element.downcase, m) - end - end - - # - O EMPTY - instance_method(:nOE_element_def).tap do |m| - for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META - COMMAND EMBED KEYGEN SOURCE TRACK WBR ] - define_method(element.downcase, m) - end - end - - # O O or - O - instance_method(:nO_element_def).tap do |m| - for element in %w[ HTML HEAD BODY P DT DD LI OPTION THEAD TFOOT TBODY - OPTGROUP COLGROUP RT RP TR TH TD ] - define_method(element.downcase, m) - end - end - - end # Html5 - - class HTML3 - include Html3 - include HtmlExtension - end - - class HTML4 - include Html4 - include HtmlExtension - end - - class HTML4Tr - include Html4Tr - include HtmlExtension - end - - class HTML4Fr - include Html4Tr - include Html4Fr - include HtmlExtension - end - - class HTML5 - include Html5 - include HtmlExtension - end - -end diff --git a/lib/cgi/session.rb b/lib/cgi/session.rb deleted file mode 100644 index aab60869bb..0000000000 --- a/lib/cgi/session.rb +++ /dev/null @@ -1,562 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session.rb - session support for cgi scripts -# -# Copyright (C) 2001 Yukihiro "Matz" Matsumoto -# Copyright (C) 2000 Network Applied Communication Laboratory, Inc. -# Copyright (C) 2000 Information-technology Promotion Agency, Japan -# -# Author: Yukihiro "Matz" Matsumoto -# -# Documentation: William Webber (william@williamwebber.com) - -require 'cgi' -require 'tmpdir' - -class CGI - - # == Overview - # - # This file provides the CGI::Session class, which provides session - # support for CGI scripts. A session is a sequence of HTTP requests - # and responses linked together and associated with a single client. - # Information associated with the session is stored - # on the server between requests. A session id is passed between client - # and server with every request and response, transparently - # to the user. This adds state information to the otherwise stateless - # HTTP request/response protocol. - # - # == Lifecycle - # - # A CGI::Session instance is created from a CGI object. By default, - # this CGI::Session instance will start a new session if none currently - # exists, or continue the current session for this client if one does - # exist. The +new_session+ option can be used to either always or - # never create a new session. See #new() for more details. - # - # #delete() deletes a session from session storage. It - # does not however remove the session id from the client. If the client - # makes another request with the same id, the effect will be to start - # a new session with the old session's id. - # - # == Setting and retrieving session data. - # - # The Session class associates data with a session as key-value pairs. - # This data can be set and retrieved by indexing the Session instance - # using '[]', much the same as hashes (although other hash methods - # are not supported). - # - # When session processing has been completed for a request, the - # session should be closed using the close() method. This will - # store the session's state to persistent storage. If you want - # to store the session's state to persistent storage without - # finishing session processing for this request, call the update() - # method. - # - # == Storing session state - # - # The caller can specify what form of storage to use for the session's - # data with the +database_manager+ option to CGI::Session::new. The - # following storage classes are provided as part of the standard library: - # - # CGI::Session::FileStore:: stores data as plain text in a flat file. Only - # works with String data. This is the default - # storage type. - # CGI::Session::MemoryStore:: stores data in an in-memory hash. The data - # only persists for as long as the current Ruby - # interpreter instance does. - # CGI::Session::PStore:: stores data in Marshalled format. Provided by - # cgi/session/pstore.rb. Supports data of any type, - # and provides file-locking and transaction support. - # - # Custom storage types can also be created by defining a class with - # the following methods: - # - # new(session, options) - # restore # returns hash of session data. - # update - # close - # delete - # - # Changing storage type mid-session does not work. Note in particular - # that by default the FileStore and PStore session data files have the - # same name. If your application switches from one to the other without - # making sure that filenames will be different - # and clients still have old sessions lying around in cookies, then - # things will break nastily! - # - # == Maintaining the session id. - # - # Most session state is maintained on the server. However, a session - # id must be passed backwards and forwards between client and server - # to maintain a reference to this session state. - # - # The simplest way to do this is via cookies. The CGI::Session class - # provides transparent support for session id communication via cookies - # if the client has cookies enabled. - # - # If the client has cookies disabled, the session id must be included - # as a parameter of all requests sent by the client to the server. The - # CGI::Session class in conjunction with the CGI class will transparently - # add the session id as a hidden input field to all forms generated - # using the CGI#form() HTML generation method. No built-in support is - # provided for other mechanisms, such as URL re-writing. The caller is - # responsible for extracting the session id from the session_id - # attribute and manually encoding it in URLs and adding it as a hidden - # input to HTML forms created by other mechanisms. Also, session expiry - # is not automatically handled. - # - # == Examples of use - # - # === Setting the user's name - # - # require 'cgi' - # require 'cgi/session' - # require 'cgi/session/pstore' # provides CGI::Session::PStore - # - # cgi = CGI.new("html4") - # - # session = CGI::Session.new(cgi, - # 'database_manager' => CGI::Session::PStore, # use PStore - # 'session_key' => '_rb_sess_id', # custom session key - # 'session_expires' => Time.now + 30 * 60, # 30 minute timeout - # 'prefix' => 'pstore_sid_') # PStore option - # if cgi.has_key?('user_name') and cgi['user_name'] != '' - # # coerce to String: cgi[] returns the - # # string-like CGI::QueryExtension::Value - # session['user_name'] = cgi['user_name'].to_s - # elsif !session['user_name'] - # session['user_name'] = "guest" - # end - # session.close - # - # === Creating a new session safely - # - # require 'cgi' - # require 'cgi/session' - # - # cgi = CGI.new("html4") - # - # # We make sure to delete an old session if one exists, - # # not just to free resources, but to prevent the session - # # from being maliciously hijacked later on. - # begin - # session = CGI::Session.new(cgi, 'new_session' => false) - # session.delete - # rescue ArgumentError # if no old session - # end - # session = CGI::Session.new(cgi, 'new_session' => true) - # session.close - # - class Session - - class NoSession < RuntimeError #:nodoc: - end - - # The id of this session. - attr_reader :session_id, :new_session - - def Session::callback(dbman) #:nodoc: - Proc.new{ - dbman[0].close unless dbman.empty? - } - end - - # Create a new session id. - # - # The session id is a secure random number by SecureRandom - # if possible, otherwise an SHA512 hash based upon the time, - # a random number, and a constant string. This routine is - # used internally for automatically generated session ids. - def create_new_id - require 'securerandom' - begin - # by OpenSSL, or system provided entropy pool - session_id = SecureRandom.hex(16) - rescue NotImplementedError - # never happens on modern systems - require 'digest' - d = Digest('SHA512').new - now = Time::now - d.update(now.to_s) - d.update(String(now.usec)) - d.update(String(rand(0))) - d.update(String($$)) - d.update('foobar') - session_id = d.hexdigest[0, 32] - end - session_id - end - private :create_new_id - - - # Create a new file to store the session data. - # - # This file will be created if it does not exist, or opened if it - # does. - # - # This path is generated under _tmpdir_ from _prefix_, the - # digested session id, and _suffix_. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the FileStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to "cgi_sid_". - # suffix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to the empty string. - def new_store_file(option={}) # :nodoc: - dir = option['tmpdir'] || Dir::tmpdir - prefix = option['prefix'] - suffix = option['suffix'] - require 'digest/md5' - md5 = Digest::MD5.hexdigest(session_id)[0,16] - path = dir+"/" - path << prefix if prefix - path << md5 - path << suffix if suffix - if File::exist? path - hash = nil - elsif new_session - hash = {} - else - raise NoSession, "uninitialized session" - end - return path, hash - end - - # Create a new CGI::Session object for +request+. - # - # +request+ is an instance of the +CGI+ class (see cgi.rb). - # +option+ is a hash of options for initialising this - # CGI::Session instance. The following options are - # recognised: - # - # session_key:: the parameter name used for the session id. - # Defaults to '_session_id'. - # session_id:: the session id to use. If not provided, then - # it is retrieved from the +session_key+ parameter - # of the request, or automatically generated for - # a new session. - # new_session:: if true, force creation of a new session. If not set, - # a new session is only created if none currently - # exists. If false, a new session is never created, - # and if none currently exists and the +session_id+ - # option is not set, an ArgumentError is raised. - # database_manager:: the name of the class providing storage facilities - # for session state persistence. Built-in support - # is provided for +FileStore+ (the default), - # +MemoryStore+, and +PStore+ (from - # cgi/session/pstore.rb). See the documentation for - # these classes for more details. - # - # The following options are also recognised, but only apply if the - # session id is stored in a cookie. - # - # session_expires:: the time the current session expires, as a - # +Time+ object. If not set, the session will terminate - # when the user's browser is closed. - # session_domain:: the hostname domain for which this session is valid. - # If not set, defaults to the hostname of the server. - # session_secure:: if +true+, this session will only work over HTTPS. - # session_path:: the path for which this session applies. Defaults - # to the directory of the CGI script. - # - # +option+ is also passed on to the session storage class initializer; see - # the documentation for each session storage class for the options - # they support. - # - # The retrieved or created session is automatically added to +request+ - # as a cookie, and also to its +output_hidden+ table, which is used - # to add hidden input elements to forms. - # - # *WARNING* the +output_hidden+ - # fields are surrounded by a <fieldset> tag in HTML 4 generation, which - # is _not_ invisible on many browsers; you may wish to disable the - # use of fieldsets with code similar to the following - # (see https://blade.ruby-lang.org/ruby-list/37805) - # - # cgi = CGI.new("html4") - # class << cgi - # undef_method :fieldset - # end - # - def initialize(request, option={}) - @new_session = false - session_key = option['session_key'] || '_session_id' - session_id = option['session_id'] - unless session_id - if option['new_session'] - session_id = create_new_id - @new_session = true - end - end - unless session_id - if request.key?(session_key) - session_id = request[session_key] - session_id = session_id.read if session_id.respond_to?(:read) - end - unless session_id - session_id, = request.cookies[session_key] - end - unless session_id - unless option.fetch('new_session', true) - raise ArgumentError, "session_key `%s' should be supplied"%session_key - end - session_id = create_new_id - @new_session = true - end - end - @session_id = session_id - dbman = option['database_manager'] || FileStore - begin - @dbman = dbman::new(self, option) - rescue NoSession - unless option.fetch('new_session', true) - raise ArgumentError, "invalid session_id `%s'"%session_id - end - session_id = @session_id = create_new_id unless session_id - @new_session=true - retry - end - request.instance_eval do - @output_hidden = {session_key => session_id} unless option['no_hidden'] - @output_cookies = [ - Cookie::new("name" => session_key, - "value" => session_id, - "expires" => option['session_expires'], - "domain" => option['session_domain'], - "secure" => option['session_secure'], - "path" => - if option['session_path'] - option['session_path'] - elsif ENV["SCRIPT_NAME"] - File::dirname(ENV["SCRIPT_NAME"]) - else - "" - end) - ] unless option['no_cookies'] - end - @dbprot = [@dbman] - ObjectSpace::define_finalizer(self, Session::callback(@dbprot)) - end - - # Retrieve the session data for key +key+. - def [](key) - @data ||= @dbman.restore - @data[key] - end - - # Set the session data for key +key+. - def []=(key, val) - @write_lock ||= true - @data ||= @dbman.restore - @data[key] = val - end - - # Store session data on the server. For some session storage types, - # this is a no-op. - def update - @dbman.update - end - - # Store session data on the server and close the session storage. - # For some session storage types, this is a no-op. - def close - @dbman.close - @dbprot.clear - end - - # Delete the session from storage. Also closes the storage. - # - # Note that the session's data is _not_ automatically deleted - # upon the session expiring. - def delete - @dbman.delete - @dbprot.clear - end - - # File-based session storage class. - # - # Implements session storage as a flat file of 'key=value' values. - # This storage type only works directly with String values; the - # user is responsible for converting other types to Strings when - # storing and from Strings when retrieving. - class FileStore - # Create a new FileStore instance. - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the FileStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to "cgi_sid_". - # suffix:: the prefix to add to the session id when generating - # the filename for this session's FileStore file. - # Defaults to the empty string. - # - # This session's FileStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'prefix' => 'cgi_sid_'}.update(option) - @path, @hash = session.new_store_file(option) - end - - # Restore session state from the session's FileStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @hash = {} - begin - lockf = File.open(@path+".lock", "r") - lockf.flock File::LOCK_SH - f = File.open(@path, 'r') - for line in f - line.chomp! - k, v = line.split('=',2) - @hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v)) - end - ensure - f&.close - lockf&.close - end - end - @hash - end - - # Save session state to the session's FileStore file. - def update - return unless @hash - begin - lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600) - lockf.flock File::LOCK_EX - f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600) - for k,v in @hash - f.printf "%s=%s\n", CGI.escape(k), CGI.escape(String(Marshal.dump(v))) - end - f.close - File.rename @path+".new", @path - ensure - f&.close - lockf&.close - end - end - - # Update and close the session's FileStore file. - def close - update - end - - # Close and delete the session's FileStore file. - def delete - File::unlink @path+".lock" rescue nil - File::unlink @path+".new" rescue nil - File::unlink @path rescue nil - end - end - - # In-memory session storage class. - # - # Implements session storage as a global in-memory hash. Session - # data will only persist for as long as the Ruby interpreter - # instance does. - class MemoryStore - GLOBAL_HASH_TABLE = {} #:nodoc: - - # Create a new MemoryStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognized. - def initialize(session, option=nil) - @session_id = session.session_id - unless GLOBAL_HASH_TABLE.key?(@session_id) - unless session.new_session - raise CGI::Session::NoSession, "uninitialized session" - end - GLOBAL_HASH_TABLE[@session_id] = {} - end - end - - # Restore session state. - # - # Returns session data as a hash. - def restore - GLOBAL_HASH_TABLE[@session_id] - end - - # Update session state. - # - # A no-op. - def update - # don't need to update; hash is shared - end - - # Close session storage. - # - # A no-op. - def close - # don't need to close - end - - # Delete the session state. - def delete - GLOBAL_HASH_TABLE.delete(@session_id) - end - end - - # Dummy session storage class. - # - # Implements session storage place holder. No actual storage - # will be done. - class NullStore - # Create a new NullStore instance. - # - # +session+ is the session this instance is associated with. - # +option+ is a list of initialisation options. None are - # currently recognised. - def initialize(session, option=nil) - end - - # Restore (empty) session state. - def restore - {} - end - - # Update session state. - # - # A no-op. - def update - end - - # Close session storage. - # - # A no-op. - def close - end - - # Delete the session state. - # - # A no-op. - def delete - end - end - end -end diff --git a/lib/cgi/session/pstore.rb b/lib/cgi/session/pstore.rb deleted file mode 100644 index 45d0d8ae2c..0000000000 --- a/lib/cgi/session/pstore.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true -# -# cgi/session/pstore.rb - persistent storage of marshalled session data -# -# Documentation: William Webber (william@williamwebber.com) -# -# == Overview -# -# This file provides the CGI::Session::PStore class, which builds -# persistent of session data on top of the pstore library. See -# cgi/session.rb for more details on session storage managers. - -require_relative '../session' -require 'pstore' - -class CGI - class Session - # PStore-based session storage class. - # - # This builds upon the top-level PStore class provided by the - # library file pstore.rb. Session data is marshalled and stored - # in a file. File locking and transaction services are provided. - class PStore - # Create a new CGI::Session::PStore instance - # - # This constructor is used internally by CGI::Session. The - # user does not generally need to call it directly. - # - # +session+ is the session for which this instance is being - # created. The session id must only contain alphanumeric - # characters; automatically generated session ids observe - # this requirement. - # - # +option+ is a hash of options for the initializer. The - # following options are recognised: - # - # tmpdir:: the directory to use for storing the PStore - # file. Defaults to Dir::tmpdir (generally "/tmp" - # on Unix systems). - # prefix:: the prefix to add to the session id when generating - # the filename for this session's PStore file. - # Defaults to the empty string. - # - # This session's PStore file will be created if it does - # not exist, or opened if it does. - def initialize(session, option={}) - option = {'suffix'=>''}.update(option) - path, @hash = session.new_store_file(option) - @p = ::PStore.new(path) - @p.transaction do |p| - File.chmod(0600, p.path) - end - end - - # Restore session state from the session's PStore file. - # - # Returns the session state as a hash. - def restore - unless @hash - @p.transaction do - @hash = @p['hash'] || {} - end - end - @hash - end - - # Save session state to the session's PStore file. - def update - @p.transaction do - @p['hash'] = @hash - end - end - - # Update and close the session's PStore file. - def close - update - end - - # Close and delete the session's PStore file. - def delete - path = @p.path - File::unlink path - end - - end - end -end -# :enddoc: diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index 4986e544e0..50a2e91665 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -1,258 +1,7 @@ # frozen_string_literal: true -class CGI - module Util; end - include Util - extend Util -end -module CGI::Util - @@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset) - # URL-encode a string into application/x-www-form-urlencoded. - # Space characters (+" "+) are encoded with plus signs (+"+"+) - # url_encoded_string = CGI.escape("'Stop!' said Fred") - # # => "%27Stop%21%27+said+Fred" - def escape(string) - encoding = string.encoding - buffer = string.b - buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m| - '%' + m.unpack('H2' * m.bytesize).join('%').upcase - end - buffer.tr!(' ', '+') - buffer.force_encoding(encoding) - end - - # URL-decode an application/x-www-form-urlencoded string with encoding(optional). - # string = CGI.unescape("%27Stop%21%27+said+Fred") - # # => "'Stop!' said Fred" - def unescape(string, encoding = @@accept_charset) - str = string.tr('+', ' ') - str = str.b - str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| - [m.delete('%')].pack('H*') - end - str.force_encoding(encoding) - str.valid_encoding? ? str : str.force_encoding(string.encoding) - end - - # URL-encode a string following RFC 3986 - # Space characters (+" "+) are encoded with (+"%20"+) - # url_encoded_string = CGI.escapeURIComponent("'Stop!' said Fred") - # # => "%27Stop%21%27%20said%20Fred" - def escapeURIComponent(string) - encoding = string.encoding - buffer = string.b - buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m| - '%' + m.unpack('H2' * m.bytesize).join('%').upcase - end - buffer.force_encoding(encoding) - end - alias escape_uri_component escapeURIComponent - - # URL-decode a string following RFC 3986 with encoding(optional). - # string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred") - # # => "'Stop!'+said Fred" - def unescapeURIComponent(string, encoding = @@accept_charset) - str = string.b - str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m| - [m.delete('%')].pack('H*') - end - str.force_encoding(encoding) - str.valid_encoding? ? str : str.force_encoding(string.encoding) - end - - alias unescape_uri_component unescapeURIComponent - - # The set of special characters and their escaped values - TABLE_FOR_ESCAPE_HTML__ = { - "'" => ''', - '&' => '&', - '"' => '"', - '<' => '<', - '>' => '>', - } - - # Escape special characters in HTML, namely '&\"<> - # CGI.escapeHTML('Usage: foo "bar" <baz>') - # # => "Usage: foo "bar" <baz>" - def escapeHTML(string) - enc = string.encoding - unless enc.ascii_compatible? - if enc.dummy? - origenc = enc - enc = Encoding::Converter.asciicompat_encoding(enc) - string = enc ? string.encode(enc) : string.b - end - table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] - string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) - string.encode!(origenc) if origenc - string - else - string = string.b - string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) - string.force_encoding(enc) - end - end - - # TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there - unless RUBY_ENGINE == 'truffleruby' - begin - require 'cgi/escape' - rescue LoadError - end - end - - # Unescape a string that has been HTML-escaped - # CGI.unescapeHTML("Usage: foo "bar" <baz>") - # # => "Usage: foo \"bar\" <baz>" - def unescapeHTML(string) - enc = string.encoding - unless enc.ascii_compatible? - if enc.dummy? - origenc = enc - enc = Encoding::Converter.asciicompat_encoding(enc) - string = enc ? string.encode(enc) : string.b - end - string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do - case $1.encode(Encoding::US_ASCII) - when 'apos' then "'".encode(enc) - when 'amp' then '&'.encode(enc) - when 'quot' then '"'.encode(enc) - when 'gt' then '>'.encode(enc) - when 'lt' then '<'.encode(enc) - when /\A#0*(\d+)\z/ then $1.to_i.chr(enc) - when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc) - end - end - string.encode!(origenc) if origenc - return string - end - return string unless string.include? '&' - charlimit = case enc - when Encoding::UTF_8; 0x10ffff - when Encoding::ISO_8859_1; 256 - else 128 - end - string = string.b - string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do - match = $1.dup - case match - when 'apos' then "'" - when 'amp' then '&' - when 'quot' then '"' - when 'gt' then '>' - when 'lt' then '<' - when /\A#0*(\d+)\z/ - n = $1.to_i - if n < charlimit - n.chr(enc) - else - "&##{$1};" - end - when /\A#x([0-9a-f]+)\z/i - n = $1.hex - if n < charlimit - n.chr(enc) - else - "&#x#{$1};" - end - else - "&#{match};" - end - end - string.force_encoding enc - end - - # Synonym for CGI.escapeHTML(str) - alias escape_html escapeHTML - - # Synonym for CGI.unescapeHTML(str) - alias unescape_html unescapeHTML - - # Escape only the tags of certain HTML elements in +string+. - # - # Takes an element or elements or array of elements. Each element - # is specified by the name of the element, without angle brackets. - # This matches both the start and the end tag of that element. - # The attribute list of the open tag will also be escaped (for - # instance, the double-quotes surrounding attribute values). - # - # print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG") - # # "<BR><A HREF="url"></A>" - # - # print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"]) - # # "<BR><A HREF="url"></A>" - def escapeElement(string, *elements) - elements = elements[0] if elements[0].kind_of?(Array) - unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do - CGI.escapeHTML($&) - end - else - string - end - end - - # Undo escaping such as that done by CGI.escapeElement() - # - # print CGI.unescapeElement( - # CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG") - # # "<BR><A HREF="url"></A>" - # - # print CGI.unescapeElement( - # CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"]) - # # "<BR><A HREF="url"></A>" - def unescapeElement(string, *elements) - elements = elements[0] if elements[0].kind_of?(Array) - unless elements.empty? - string.gsub(/<\/?(?:#{elements.join("|")})(?!\w)(?:.|\n)*?>/i) do - unescapeHTML($&) - end - else - string - end - end - - # Synonym for CGI.escapeElement(str) - alias escape_element escapeElement - - # Synonym for CGI.unescapeElement(str) - alias unescape_element unescapeElement - - # Format a +Time+ object as a String using the format specified by RFC 1123. - # - # CGI.rfc1123_date(Time.now) - # # Sat, 01 Jan 2000 00:00:00 GMT - def rfc1123_date(time) - time.getgm.strftime("%a, %d %b %Y %T GMT") - end - - # Prettify (indent) an HTML string. - # - # +string+ is the HTML string to indent. +shift+ is the indentation - # unit to use; it defaults to two spaces. - # - # print CGI.pretty("<HTML><BODY></BODY></HTML>") - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - # print CGI.pretty("<HTML><BODY></BODY></HTML>", "\t") - # # <HTML> - # # <BODY> - # # </BODY> - # # </HTML> - # - def pretty(string, shift = " ") - lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\0\n") - end_pos = 0 - while end_pos = lines.index(/^<\/(\w+)/, end_pos) - element = $1.dup - start_pos = lines.rindex(/^\s*<#{element}/i, end_pos) - lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__" - end - lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1') - end - - alias h escapeHTML -end +require "cgi/escape" +warn <<-WARNING, uplevel: Gem::BUNDLED_GEMS.uplevel if $VERBOSE +CGI::Util is removed from Ruby 4.0. Please use cgi/escape instead for CGI.escape and CGI.unescape features. +If you are using CGI.parse, please install and use the cgi gem instead. +WARNING diff --git a/lib/delegate.gemspec b/lib/delegate.gemspec index 6c3feac74b..f7fcc1ceb9 100644 --- a/lib/delegate.gemspec +++ b/lib/delegate.gemspec @@ -25,5 +25,5 @@ Gem::Specification.new do |spec| `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.require_paths = ["lib"] - spec.required_ruby_version = '>= 2.7' + spec.required_ruby_version = '>= 3.0' end diff --git a/lib/delegate.rb b/lib/delegate.rb index 1ea4fb985b..0cc3ddb1b0 100644 --- a/lib/delegate.rb +++ b/lib/delegate.rb @@ -39,7 +39,8 @@ # Be advised, RDoc will not detect delegated methods. # class Delegator < BasicObject - VERSION = "0.3.1" + # The version string + VERSION = "0.6.1" kernel = ::Kernel.dup kernel.class_eval do @@ -77,7 +78,7 @@ class Delegator < BasicObject end # - # Handles the magic of delegation through \_\_getobj\_\_. + # Handles the magic of delegation through +__getobj__+. # ruby2_keywords def method_missing(m, *args, &block) r = true @@ -94,7 +95,7 @@ class Delegator < BasicObject # # Checks for a method provided by this the delegate object by forwarding the - # call through \_\_getobj\_\_. + # call through +__getobj__+. # def respond_to_missing?(m, include_private) r = true @@ -107,7 +108,7 @@ class Delegator < BasicObject r end - KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) + KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?) # :nodoc: private_constant :KERNEL_RESPOND_TO # Handle BasicObject instances @@ -126,7 +127,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ methods. + # of this object's and +__getobj__+ methods. # def methods(all=true) __getobj__.methods(all) | super @@ -134,7 +135,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ public methods. + # of this object's and +__getobj__+ public methods. # def public_methods(all=true) __getobj__.public_methods(all) | super @@ -142,7 +143,7 @@ class Delegator < BasicObject # # Returns the methods available to this delegate object as the union - # of this object's and \_\_getobj\_\_ protected methods. + # of this object's and +__getobj__+ protected methods. # def protected_methods(all=true) __getobj__.protected_methods(all) | super @@ -175,7 +176,7 @@ class Delegator < BasicObject end # - # Delegates ! to the \_\_getobj\_\_ + # Delegates ! to the +__getobj__+ # def ! !__getobj__ @@ -198,7 +199,7 @@ class Delegator < BasicObject end # - # Serialization support for the object returned by \_\_getobj\_\_. + # Serialization support for the object returned by +__getobj__+. # def marshal_dump ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var} @@ -232,7 +233,7 @@ class Delegator < BasicObject ## # :method: freeze - # Freeze both the object returned by \_\_getobj\_\_ and self. + # Freeze both the object returned by +__getobj__+ and self. # def freeze __getobj__.freeze @@ -398,6 +399,17 @@ def DelegateClass(superclass, &block) protected_instance_methods -= ignores public_instance_methods = superclass.public_instance_methods public_instance_methods -= ignores + + normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } + + source = normal.map do |method| + "def #{method}(...); __getobj__.#{method}(...); end" + end + + protected_instance_methods.each do |method| + source << "def #{method}(...); __getobj__.__send__(#{method.inspect}, ...); end" + end + klass.module_eval do def __getobj__ # :nodoc: unless defined?(@delegate_dc_obj) @@ -406,18 +418,21 @@ def DelegateClass(superclass, &block) end @delegate_dc_obj end + def __setobj__(obj) # :nodoc: __raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj) @delegate_dc_obj = obj end - protected_instance_methods.each do |method| - define_method(method, Delegator.delegating_block(method)) - protected method - end - public_instance_methods.each do |method| + + class_eval(source.join(";"), __FILE__, __LINE__) + + special.each do |method| define_method(method, Delegator.delegating_block(method)) end + + protected(*protected_instance_methods) end + klass.define_singleton_method :public_instance_methods do |all=true| super(all) | superclass.public_instance_methods end diff --git a/lib/did_you_mean.rb b/lib/did_you_mean.rb index e177665099..74cd176042 100644 --- a/lib/did_you_mean.rb +++ b/lib/did_you_mean.rb @@ -113,30 +113,6 @@ module DidYouMean correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0' correct_error NoMatchingPatternKeyError, PatternKeyNameChecker if defined?(::NoMatchingPatternKeyError) - # TODO: Remove on the 3.4 development start: - class DeprecatedMapping # :nodoc: - def []=(key, value) - warn "Calling `DidYouMean::SPELL_CHECKERS[#{key.to_s}] = #{value.to_s}' has been deprecated. " \ - "Please call `DidYouMean.correct_error(#{key.to_s}, #{value.to_s})' instead." - - DidYouMean.correct_error(key, value) - end - - def merge!(hash) - warn "Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. " \ - "Please call `DidYouMean.correct_error(error_name, spell_checker)' instead." - - hash.each do |error_class, spell_checker| - DidYouMean.correct_error(error_class, spell_checker) - end - end - end - - # TODO: Remove on the 3.4 development start: - SPELL_CHECKERS = DeprecatedMapping.new - deprecate_constant :SPELL_CHECKERS - private_constant :DeprecatedMapping - # Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+. def self.formatter if defined?(Ractor) diff --git a/lib/did_you_mean/version.rb b/lib/did_you_mean/version.rb index 5745ca1efd..85d80e4230 100644 --- a/lib/did_you_mean/version.rb +++ b/lib/did_you_mean/version.rb @@ -1,3 +1,3 @@ module DidYouMean - VERSION = "1.6.3".freeze + VERSION = "2.0.0".freeze end diff --git a/lib/erb.rb b/lib/erb.rb index bc1615d7da..445d4795b0 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -12,340 +12,824 @@ # # You can redistribute it and/or modify it under the same terms as Ruby. -require 'cgi/util' +# A NOTE ABOUT TERMS: +# +# Formerly: The documentation in this file used the term _template_ to refer to an ERB object. +# +# Now: The documentation in this file uses the term _template_ +# to refer to the string input to ERB.new. +# +# The reason for the change: When documenting the ERB executable erb, +# we need a term that refers to its string input; +# _source_ is not a good idea, because ERB#src means something entirely different; +# the two different sorts of sources would bring confusion. +# +# Therefore we use the term _template_ to refer to: +# +# - The string input to ERB.new +# - The string input to executable erb. +# + require 'erb/version' require 'erb/compiler' require 'erb/def_method' require 'erb/util' +# :markup: markdown # -# = ERB -- Ruby Templating +# Class **ERB** (the name stands for **Embedded Ruby**) +# is an easy-to-use, but also very powerful, [template processor][template processor]. # -# == Introduction +# ## Usage # -# ERB provides an easy to use but powerful templating system for Ruby. Using -# ERB, actual Ruby code can be added to any plain text document for the -# purposes of generating document information details and/or flow control. +# Before you can use \ERB, you must first require it +# (examples on this page assume that this has been done): # -# A very simple example is this: +# ``` +# require 'erb' +# ``` # -# require 'erb' +# ## In Brief # -# x = 42 -# template = ERB.new <<-EOF -# The value of x is: <%= x %> -# EOF -# puts template.result(binding) +# Here's how \ERB works: # -# <em>Prints:</em> The value of x is: 42 +# - You can create a *template*: a plain-text string that includes specially formatted *tags*.. +# - You can create an \ERB object to store the template. +# - You can call instance method ERB#result to get the *result*. # -# More complex examples are given below. +# \ERB supports tags of three kinds: # +# - [Expression tags][expression tags]: +# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression; +# in the result, the value of the expression replaces the entire tag: # -# == Recognized Tags +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'xyzzy' +# erb.result(binding) # => "The magic word is xyzzy." # -# ERB recognizes certain tags in the provided template and converts them based -# on the rules below: +# The above call to #result passes argument `binding`, +# which contains the binding of variable `magic_word` to its string value `'xyzzy'`. # -# <% Ruby code -- inline with output %> -# <%= Ruby expression -- replace with result %> -# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.) -# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) -# %% replaced with % if first thing on a line and % processing is used -# <%% or %%> -- replace with <% or %> respectively +# The below call to #result need not pass a binding, +# because its expression `Date::DAYNAMES` is globally defined. # -# All other text is passed through ERB filtering unchanged. +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." # +# - [Execution tags][execution tags]: +# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed: # -# == Options +# template = '<% File.write("t.txt", "Some stuff.") %>' +# ERB.new(template).result +# File.read('t.txt') # => "Some stuff." # -# There are several settings you can change when you use ERB: -# * the nature of the tags that are recognized; -# * the binding used to resolve local variables in the template. +# - [Comment tags][comment tags]: +# each begins with `'<%#'`, ends with `'%>'`; contains comment text; +# in the result, the entire tag is omitted. # -# See the ERB.new and ERB#result methods for more detail. +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." # -# == Character encodings +# ## Some Simple Examples # -# ERB (or Ruby code generated by ERB) returns a string in the same -# character encoding as the input string. When the input string has -# a magic comment, however, it returns a string in the encoding specified -# by the magic comment. +# Here's a simple example of \ERB in action: # -# # -*- coding: utf-8 -*- -# require 'erb' +# ``` +# template = 'The time is <%= Time.now %>.' +# erb = ERB.new(template) +# erb.result +# # => "The time is 2025-09-09 10:49:26 -0500." +# ``` # -# template = ERB.new <<EOF -# <%#-*- coding: Big5 -*-%> -# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>. -# EOF -# puts template.result +# Details: # -# <em>Prints:</em> \_\_ENCODING\_\_ is Big5. +# 1. A plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= Time.now %>'` includes a Ruby expression, `Time.now`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`. +# 4. Method call `erb.result` generates a string that contains the run-time value of `Time.now`, +# as computed at the time of the call. # +# The +# \ERB object may be re-used: # -# == Examples +# ``` +# erb.result +# # => "The time is 2025-09-09 10:49:33 -0500." +# ``` # -# === Plain Text +# Another example: # -# ERB is useful for any generic templating situation. Note that in this example, we use the -# convenient "% at start of line" tag, and we quote the template literally with -# <tt>%q{...}</tt> to avoid trouble with the backslash. +# ``` +# template = 'The magic word is <%= magic_word %>.' +# erb = ERB.new(template) +# magic_word = 'abracadabra' +# erb.result(binding) +# # => "The magic word is abracadabra." +# ``` # -# require "erb" +# Details: # -# # Create template. -# template = %q{ -# From: James Edward Gray II <james@grayproductions.net> -# To: <%= to %> -# Subject: Addressing Needs +# 1. As before, a plain-text string is assigned to variable `template`. +# Its embedded [expression tag][expression tags] `'<%= magic_word %>'` has a variable *name*, `magic_word`. +# 2. The string is put into a new \ERB object, and stored in variable `erb`; +# note that `magic_word` need not be defined before the \ERB object is created. +# 3. `magic_word = 'abracadabra'` assigns a value to variable `magic_word`. +# 4. Method call `erb.result(binding)` generates a string +# that contains the *value* of `magic_word`. # -# <%= to[/\w+/] %>: +# As before, the \ERB object may be re-used: # -# Just wanted to send a quick note assuring that your needs are being -# addressed. +# ``` +# magic_word = 'xyzzy' +# erb.result(binding) +# # => "The magic word is xyzzy." +# ``` # -# I want you to know that my team will keep working on the issues, -# especially: +# ## Bindings # -# <%# ignore numerous minor requests -- focus on priorities %> -# % priorities.each do |priority| -# * <%= priority %> -# % end +# A call to method #result, which produces the formatted result string, +# requires a [Binding object][binding object] as its argument. +# +# The binding object provides the bindings for expressions in [expression tags][expression tags]. +# +# There are three ways to provide the required binding: +# +# - [Default binding][default binding]. +# - [Local binding][local binding]. +# - [Augmented binding][augmented binding] +# +# ### Default Binding +# +# When you pass no `binding` argument to method #result, +# the method uses its default binding: the one returned by method #new_toplevel. +# This binding has the bindings defined by Ruby itself, +# which are those for Ruby's constants and variables. +# +# That binding is sufficient for an expression tag that refers only to Ruby's constants and variables; +# these expression tags refer only to Ruby's global constant `RUBY_COPYRIGHT` and global variable `$0`: # -# Thanks for your patience. +# ``` +# template = <<TEMPLATE +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# puts ERB.new(template).result +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# (The current process is `irb` because that's where we're doing these examples!) +# +# ### Local Binding +# +# The default binding is *not* sufficient for an expression +# that refers to a a constant or variable that is not defined there: +# +# ``` +# Foo = 1 # Defines local constant Foo. +# foo = 2 # Defines local variable foo. +# template = <<TEMPLATE +# The current value of constant Foo is <%= Foo %>. +# The current value of variable foo is <%= foo %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# This call below raises `NameError` because although `Foo` and `foo` are defined locally, +# they are not defined in the default binding: +# +# ``` +# erb.result # Raises NameError. +# ``` +# +# To make the locally-defined constants and variables available, +# you can call #result with the local binding: +# +# ``` +# puts erb.result(binding) +# The current value of constant Foo is 1. +# The current value of variable foo is 2. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ### Augmented Binding +# +# Another way to make variable bindings (but not constant bindings) available +# is to use method #result_with_hash(hash); +# the passed hash has name/value pairs that are to be used to define and assign variables +# in a copy of the default binding: +# +# ``` +# template = <<TEMPLATE +# The current value of variable bar is <%= bar %>. +# The current value of variable baz is <%= baz %>. +# The Ruby copyright is <%= RUBY_COPYRIGHT.inspect %>. +# The current process is <%= $0 %>. +# TEMPLATE +# erb = ERB.new(template) +# ``` +# +# Both of these calls raise `NameError`, because `bar` and `baz` +# are not defined in either the default binding or the local binding. +# +# ``` +# puts erb.result # Raises NameError. +# puts erb.result(binding) # Raises NameError. +# ``` +# +# This call passes a hash that causes `bar` and `baz` to be defined +# in a new binding (derived from #new_toplevel): +# +# ``` +# hash = {bar: 3, baz: 4} +# puts erb.result_with_hash(hash) +# The current value of variable bar is 3. +# The current value of variable baz is 4. +# The Ruby copyright is "ruby - Copyright (C) 1993-2025 Yukihiro Matsumoto". +# The current process is irb. +# ``` +# +# ## Tags +# +# The examples above use expression tags. +# These are the tags available in \ERB: +# +# - [Expression tag][expression tags]: the tag contains a Ruby expression; +# in the result, the entire tag is to be replaced with the run-time value of the expression. +# - [Execution tag][execution tags]: the tag contains Ruby code; +# in the result, the entire tag is to be replaced with the run-time value of the code. +# - [Comment tag][comment tags]: the tag contains comment code; +# in the result, the entire tag is to be omitted. +# +# ### Expression Tags +# +# You can embed a Ruby expression in a template using an *expression tag*. +# +# Its syntax is `<%= _expression_ %>`, +# where *expression* is any valid Ruby expression. +# +# When you call method #result, +# the method evaluates the expression and replaces the entire expression tag with the expression's value: +# +# ``` +# ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result +# # => "Today is Monday." +# ERB.new('Tomorrow will be <%= Date::DAYNAMES[Date.today.wday + 1] %>.').result +# # => "Tomorrow will be Tuesday." +# ERB.new('Yesterday was <%= Date::DAYNAMES[Date.today.wday - 1] %>.').result +# # => "Yesterday was Sunday." +# ``` +# +# Note that whitespace before and after the expression +# is allowed but not required, +# and that such whitespace is stripped from the result. +# +# ``` +# ERB.new('My appointment is on <%=Date::DAYNAMES[Date.today.wday + 2]%>.').result +# # => "My appointment is on Wednesday." +# ERB.new('My appointment is on <%= Date::DAYNAMES[Date.today.wday + 2] %>.').result +# # => "My appointment is on Wednesday." +# ``` +# +# ### Execution Tags +# +# You can embed Ruby executable code in template using an *execution tag*. +# +# Its syntax is `<% _code_ %>`, +# where *code* is any valid Ruby code. +# +# When you call method #result, +# the method executes the code and removes the entire execution tag +# (generating no text in the result): +# +# ``` +# ERB.new('foo <% Dir.chdir("C:/") %> bar').result # => "foo bar" +# ``` +# +# Whitespace before and after the embedded code is optional: +# +# ``` +# ERB.new('foo <%Dir.chdir("C:/")%> bar').result # => "foo bar" +# ``` +# +# You can interleave text with execution tags to form a control structure +# such as a conditional, a loop, or a `case` statements. +# +# Conditional: +# +# ``` +# template = <<TEMPLATE +# <% if verbosity %> +# An error has occurred. +# <% else %> +# Oops! +# <% end %> +# TEMPLATE +# erb = ERB.new(template) +# verbosity = true +# erb.result(binding) +# # => "\nAn error has occurred.\n\n" +# verbosity = false +# erb.result(binding) +# # => "\nOops!\n\n" +# ``` +# +# Note that the interleaved text may itself contain expression tags: +# +# Loop: +# +# ``` +# template = <<TEMPLATE +# <% Date::ABBR_DAYNAMES.each do |dayname| %> +# <%= dayname %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\nSun\n\nMon\n\nTue\n\nWed\n\nThu\n\nFri\n\nSat\n\n" +# ``` +# +# Other, non-control, lines of Ruby code may be interleaved with the text, +# and the Ruby code may itself contain regular Ruby comments: +# +# ``` +# template = <<TEMPLATE +# <% 3.times do %> +# <%= Time.now %> +# <% sleep(1) # Let's make the times different. %> +# <% end %> +# TEMPLATE +# ERB.new(template).result +# # => "\n2025-09-09 11:36:02 -0500\n\n\n2025-09-09 11:36:03 -0500\n\n\n2025-09-09 11:36:04 -0500\n\n\n" +# ``` +# +# The execution tag may also contain multiple lines of code: +# +# ``` +# template = <<TEMPLATE +# <% +# (0..2).each do |i| +# (0..2).each do |j| +# %> +# * <%=i%>,<%=j%> +# <% +# end +# end +# %> +# TEMPLATE +# ERB.new(template).result +# # => "\n* 0,0\n\n* 0,1\n\n* 0,2\n\n* 1,0\n\n* 1,1\n\n* 1,2\n\n* 2,0\n\n* 2,1\n\n* 2,2\n\n" +# ``` +# +# #### Shorthand Format for Execution Tags +# +# You can use keyword argument `trim_mode: '%'` to enable a shorthand format for execution tags; +# this example uses the shorthand format `% _code_` instead of `<% _code_ %>`: +# +# ``` +# template = <<TEMPLATE +# % priorities.each do |priority| +# * <%= priority %> +# % end +# TEMPLATE +# erb = ERB.new(template, trim_mode: '%') +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# puts erb.result(binding) +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# ``` +# +# Note that in the shorthand format, the character `'%'` must be the first character in the code line +# (no leading whitespace). +# +# #### Suppressing Unwanted Blank Lines +# +# With keyword argument `trim_mode` not given, +# all blank lines go into the result: +# +# ``` +# template = <<TEMPLATE +# <% if true %> +# <%= RUBY_VERSION %> +# <% end %> +# TEMPLATE +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "\n" +# ``` +# +# You can give `trim_mode: '-'`, you can suppress each blank line +# whose source line ends with `-%>` (instead of `%>`): +# +# ``` +# template = <<TEMPLATE +# <% if true -%> +# <%= RUBY_VERSION %> +# <% end -%> +# TEMPLATE +# ERB.new(template, trim_mode: '-').result.lines.each {|line| puts line.inspect } +# "3.4.5\n" +# ``` +# +# It is an error to use the trailing `'-%>'` notation without `trim_mode: '-'`: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } # Raises SyntaxError. +# ``` +# +# #### Suppressing Unwanted Newlines +# +# Consider this template: +# +# ``` +# template = <<TEMPLATE +# <% RUBY_VERSION %> +# <%= RUBY_VERSION %> +# foo <% RUBY_VERSION %> +# foo <%= RUBY_VERSION %> +# TEMPLATE +# ``` +# +# With keyword argument `trim_mode` not given, all newlines go into the result: +# +# ``` +# ERB.new(template).result.lines.each {|line| puts line.inspect } +# "\n" +# "3.4.5\n" +# "foo \n" +# "foo 3.4.5\n" +# ``` +# +# You can give `trim_mode: '>'` to suppress the trailing newline +# for each line that ends with `'%>'` (regardless of its beginning): +# +# ``` +# ERB.new(template, trim_mode: '>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo foo 3.4.5" +# ``` +# +# You can give `trim_mode: '<>'` to suppress the trailing newline +# for each line that both begins with `'<%'` and ends with `'%>'`: +# +# ``` +# ERB.new(template, trim_mode: '<>').result.lines.each {|line| puts line.inspect } +# "3.4.5foo \n" +# "foo 3.4.5\n" +# ``` +# +# #### Combining Trim Modes +# +# You can combine certain trim modes: +# +# - `'%-'`: Enable shorthand and omit each blank line ending with `'-%>'`. +# - `'%>'`: Enable shorthand and omit newline for each line ending with `'%>'`. +# - `'%<>'`: Enable shorthand and omit newline for each line starting with `'<%'` and ending with `'%>'`. +# +# ### Comment Tags +# +# You can embed a comment in a template using a *comment tag*; +# its syntax is `<%# _text_ %>`, +# where *text* is the text of the comment. +# +# When you call method #result, +# it removes the entire comment tag +# (generating no text in the result). +# +# Example: +# +# ``` +# template = 'Some stuff;<%# Note to self: figure out what the stuff is. %> more stuff.' +# ERB.new(template).result # => "Some stuff; more stuff." +# ``` +# +# A comment tag may appear anywhere in the template. +# +# Note that the beginning of the tag must be `'<%#'`, not `'<% #'`. +# +# In this example, the tag begins with `'<% #'`, and so is an execution tag, not a comment tag; +# the cited code consists entirely of a Ruby-style comment (which is of course ignored): +# +# ``` +# ERB.new('Some stuff;<% # Note to self: figure out what the stuff is. %> more stuff.').result +# # => "Some stuff;" +# ``` +# +# ## Encodings +# +# An \ERB object has an [encoding][encoding], +# which is by default the encoding of the template string; +# the result string will also have that encoding. # -# James Edward Gray II -# }.gsub(/^ /, '') +# ``` +# template = <<TEMPLATE +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:UTF-8> +# erb.result.encoding # => #<Encoding:UTF-8> +# ``` # -# message = ERB.new(template, trim_mode: "%<>") +# You can specify a different encoding by adding a [magic comment][magic comments] +# at the top of the given template: # -# # Set up template data. -# to = "Community Spokesman <spokesman@ruby_community.org>" -# priorities = [ "Run Ruby Quiz", -# "Document Modules", -# "Answer Questions on Ruby Talk" ] +# ``` +# template = <<TEMPLATE +# <%#-*- coding: Big5 -*-%> +# <%# Comment. %> +# TEMPLATE +# erb = ERB.new(template) +# template.encoding # => #<Encoding:UTF-8> +# erb.encoding # => #<Encoding:Big5> +# erb.result.encoding # => #<Encoding:Big5> +# ``` # -# # Produce result. -# email = message.result -# puts email +# ## Error Reporting # -# <i>Generates:</i> +# Consider this template (containing an error): # -# From: James Edward Gray II <james@grayproductions.net> -# To: Community Spokesman <spokesman@ruby_community.org> -# Subject: Addressing Needs +# ``` +# template = '<%= nosuch %>' +# erb = ERB.new(template) +# ``` # -# Community: +# When \ERB reports an error, +# it includes a file name (if available) and a line number; +# the file name comes from method #filename, the line number from method #lineno. # -# Just wanted to send a quick note assuring that your needs are being addressed. +# Initially, those values are `nil` and `0`, respectively; +# these initial values are reported as `'(erb)'` and `1`, respectively: # -# I want you to know that my team will keep working on the issues, especially: +# ``` +# erb.filename # => nil +# erb.lineno # => 0 +# erb.result +# (erb):1:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# * Run Ruby Quiz -# * Document Modules -# * Answer Questions on Ruby Talk +# You can use methods #filename= and #lineno= to assign values +# that are more meaningful in your context: # -# Thanks for your patience. +# ``` +# erb.filename = 't.txt' +# erb.lineno = 555 +# erb.result +# t.txt:556:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# James Edward Gray II +# You can use method #location= to set both values: # -# === Ruby in HTML +# ``` +# erb.location = ['u.txt', 999] +# erb.result +# u.txt:1000:in '<main>': undefined local variable or method 'nosuch' for main (NameError) +# ``` # -# ERB is often used in <tt>.rhtml</tt> files (HTML with embedded Ruby). Notice the need in -# this example to provide a special binding when the template is run, so that the instance -# variables in the Product object can be resolved. +# ## Plain Text with Embedded Ruby # -# require "erb" +# Here's a plain-text template; +# it uses the literal notation `'%q{ ... }'` to define the template +# (see [%q literals][%q literals]); +# this avoids problems with backslashes. # -# # Build template data class. -# class Product -# def initialize( code, name, desc, cost ) -# @code = code -# @name = name -# @desc = desc -# @cost = cost +# ``` +# template = %q{ +# From: James Edward Gray II <james@grayproductions.net> +# To: <%= to %> +# Subject: Addressing Needs # -# @features = [ ] -# end +# <%= to[/\w+/] %>: # -# def add_feature( feature ) -# @features << feature -# end +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# # Support templating of member data. -# def get_binding -# binding -# end +# I want you to know that my team will keep working on the issues, +# especially: # -# # ... -# end +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end # -# # Create template. -# template = %{ -# <html> -# <head><title>Ruby Toys -- <%= @name %></title></head> -# <body> +# Thanks for your patience. # -# <h1><%= @name %> (<%= @code %>)</h1> -# <p><%= @desc %></p> +# James Edward Gray II +# } +# ``` # -# <ul> -# <% @features.each do |f| %> -# <li><b><%= f %></b></li> -# <% end %> -# </ul> +# The template will need these: # -# <p> -# <% if @cost < 10 %> -# <b>Only <%= @cost %>!!!</b> -# <% else %> -# Call for a price, today! -# <% end %> -# </p> +# ``` +# to = 'Community Spokesman <spokesman@ruby_community.org>' +# priorities = [ 'Run Ruby Quiz', +# 'Document Modules', +# 'Answer Questions on Ruby Talk' ] +# ``` # -# </body> -# </html> -# }.gsub(/^ /, '') +# Finally, create the \ERB object and get the result # -# rhtml = ERB.new(template) +# ``` +# erb = ERB.new(template, trim_mode: '%<>') +# puts erb.result(binding) # -# # Set up template data. -# toy = Product.new( "TZ-1002", -# "Rubysapien", -# "Geek's Best Friend! Responds to Ruby commands...", -# 999.95 ) -# toy.add_feature("Listens for verbal commands in the Ruby language!") -# toy.add_feature("Ignores Perl, Java, and all C variants.") -# toy.add_feature("Karate-Chop Action!!!") -# toy.add_feature("Matz signature on left leg.") -# toy.add_feature("Gem studded eyes... Rubies, of course!") +# From: James Edward Gray II <james@grayproductions.net> +# To: Community Spokesman <spokesman@ruby_community.org> +# Subject: Addressing Needs # -# # Produce result. -# rhtml.run(toy.get_binding) +# Community: # -# <i>Generates (some blank lines removed):</i> +# Just wanted to send a quick note assuring that your needs are being +# addressed. # -# <html> -# <head><title>Ruby Toys -- Rubysapien</title></head> -# <body> +# I want you to know that my team will keep working on the issues, +# especially: # -# <h1>Rubysapien (TZ-1002)</h1> -# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk # -# <ul> -# <li><b>Listens for verbal commands in the Ruby language!</b></li> -# <li><b>Ignores Perl, Java, and all C variants.</b></li> -# <li><b>Karate-Chop Action!!!</b></li> -# <li><b>Matz signature on left leg.</b></li> -# <li><b>Gem studded eyes... Rubies, of course!</b></li> -# </ul> +# Thanks for your patience. # -# <p> -# Call for a price, today! -# </p> +# James Edward Gray II +# ``` # -# </body> -# </html> +# ## HTML with Embedded Ruby # +# This example shows an HTML template. # -# == Notes +# First, here's a custom class, `Product`: # -# There are a variety of templating solutions available in various Ruby projects. -# For example, RDoc, distributed with Ruby, uses its own template engine, which -# can be reused elsewhere. +# ``` +# class Product +# def initialize(code, name, desc, cost) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# @features = [] +# end # -# Other popular engines could be found in the corresponding -# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of -# The Ruby Toolbox. +# def add_feature(feature) +# @features << feature +# end +# +# # Support templating of member data. +# def get_binding +# binding +# end +# +# end +# ``` +# +# The template below will need these values: +# +# ``` +# toy = Product.new('TZ-1002', +# 'Rubysapien', +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 +# ) +# toy.add_feature('Listens for verbal commands in the Ruby language!') +# toy.add_feature('Ignores Perl, Java, and all C variants.') +# toy.add_feature('Karate-Chop Action!!!') +# toy.add_feature('Matz signature on left leg.') +# toy.add_feature('Gem studded eyes... Rubies, of course!') +# ``` +# +# Here's the HTML: +# +# ``` +# template = <<TEMPLATE +# <html> +# <head><title>Ruby Toys -- <%= @name %></title></head> +# <body> +# <h1><%= @name %> (<%= @code %>)</h1> +# <p><%= @desc %></p> +# <ul> +# <% @features.each do |f| %> +# <li><b><%= f %></b></li> +# <% end %> +# </ul> +# <p> +# <% if @cost < 10 %> +# <b>Only <%= @cost %>!!!</b> +# <% else %> +# Call for a price, today! +# <% end %> +# </p> +# </body> +# </html> +# TEMPLATE +# ``` +# +# Finally, create the \ERB object and get the result (omitting some blank lines): +# +# ``` +# erb = ERB.new(template) +# puts erb.result(toy.get_binding) +# <html> +# <head><title>Ruby Toys -- Rubysapien</title></head> +# <body> +# <h1>Rubysapien (TZ-1002)</h1> +# <p>Geek's Best Friend! Responds to Ruby commands...</p> +# <ul> +# <li><b>Listens for verbal commands in the Ruby language!</b></li> +# <li><b>Ignores Perl, Java, and all C variants.</b></li> +# <li><b>Karate-Chop Action!!!</b></li> +# <li><b>Matz signature on left leg.</b></li> +# <li><b>Gem studded eyes... Rubies, of course!</b></li> +# </ul> +# <p> +# Call for a price, today! +# </p> +# </body> +# </html> +# ``` +# +# +# ## Other Template Processors +# +# Various Ruby projects have their own template processors. +# The Ruby Processing System [RDoc][rdoc], for example, has one that can be used elsewhere. +# +# Other popular template processors may found in the [Template Engines][template engines] page +# of the Ruby Toolbox. +# +# [%q literals]: https://docs.ruby-lang.org/en/master/syntax/literals_rdoc.html#label-25q-3A+Non-Interpolable+String+Literals +# [augmented binding]: rdoc-ref:ERB@Augmented+Binding +# [binding object]: https://docs.ruby-lang.org/en/master/Binding.html +# [comment tags]: rdoc-ref:ERB@Comment+Tags +# [default binding]: rdoc-ref:ERB@Default+Binding +# [encoding]: https://docs.ruby-lang.org/en/master/Encoding.html +# [execution tags]: rdoc-ref:ERB@Execution+Tags +# [expression tags]: rdoc-ref:ERB@Expression+Tags +# [kernel#binding]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-binding +# [local binding]: rdoc-ref:ERB@Local+Binding +# [magic comments]: https://docs.ruby-lang.org/en/master/syntax/comments_rdoc.html#label-Magic+Comments +# [rdoc]: https://ruby.github.io/rdoc +# [sprintf]: https://docs.ruby-lang.org/en/master/Kernel.html#method-i-sprintf +# [template engines]: https://www.ruby-toolbox.com/categories/template_engines +# [template processor]: https://en.wikipedia.org/wiki/Template_processor # class ERB - Revision = '$Date:: $' # :nodoc: #' - deprecate_constant :Revision - - # Returns revision information for the erb.rb module. + # :markup: markdown + # + # :call-seq: + # self.version -> string + # + # Returns the string \ERB version. def self.version VERSION end + # :markup: markdown # - # Constructs a new ERB object with the template specified in _str_. + # :call-seq: + # ERB.new(template, trim_mode: nil, eoutvar: '_erbout') # - # An ERB object works by building a chunk of Ruby code that will output - # the completed template when run. + # Returns a new \ERB object containing the given string +template+. # - # If _trim_mode_ is passed a String containing one or more of the following - # modifiers, ERB will adjust its code generation as listed: + # For details about `template`, its embedded tags, and generated results, see ERB. # - # % enables Ruby code processing for lines beginning with % - # <> omit newline for lines starting with <% and ending in %> - # > omit newline for lines ending in %> - # - omit blank lines ending in -%> + # **Keyword Argument `trim_mode`** # - # _eoutvar_ can be used to set the name of the variable ERB will build up - # its output in. This is useful when you need to run multiple ERB - # templates through the same binding and/or when you want to control where - # output ends up. Pass the name of the variable to be used inside a String. + # You can use keyword argument `trim_mode: '%'` + # to enable the [shorthand format][shorthand format] for execution tags. # - # === Example + # This value allows [blank line control][blank line control]: # - # require "erb" + # - `'-'`: Omit each blank line ending with `'%>'`. # - # # build data class - # class Listings - # PRODUCT = { :name => "Chicken Fried Steak", - # :desc => "A well messages pattie, breaded and fried.", - # :cost => 9.95 } + # Other values allow [newline control][newline control]: # - # attr_reader :product, :price + # - `'>'`: Omit newline for each line ending with `'%>'`. + # - `'<>'`: Omit newline for each line starting with `'<%'` and ending with `'%>'`. # - # def initialize( product = "", price = "" ) - # @product = product - # @price = price - # end + # You can also [combine trim modes][combine trim modes]. # - # def build - # b = binding - # # create and run templates, filling member data variables - # ERB.new(<<~'END_PRODUCT', trim_mode: "", eoutvar: "@product").result b - # <%= PRODUCT[:name] %> - # <%= PRODUCT[:desc] %> - # END_PRODUCT - # ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b - # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> - # <%= PRODUCT[:desc] %> - # END_PRICE - # end - # end + # **Keyword Argument `eoutvar`** # - # # setup template data - # listings = Listings.new - # listings.build + # The string value of keyword argument `eoutvar` specifies the name of the variable + # that method #result uses to construct its result string; + # see #src. # - # puts listings.product + "\n" + listings.price + # This is useful when you need to run multiple \ERB templates through the same binding + # and/or when you want to control where output ends up. # - # _Generates_ + # It's good practice to choose a variable name that begins with an underscore: `'_'`. # - # Chicken Fried Steak - # A well messages pattie, breaded and fried. + # [blank line control]: rdoc-ref:ERB@Suppressing+Unwanted+Blank+Lines + # [combine trim modes]: rdoc-ref:ERB@Combining+Trim+Modes + # [newline control]: rdoc-ref:ERB@Suppressing+Unwanted+Newlines + # [shorthand format]: rdoc-ref:ERB@Shorthand+Format+for+Execution+Tags # - # Chicken Fried Steak -- 9.95 - # A well messages pattie, breaded and fried. - # - def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout') - # Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar. - if safe_level != NOT_GIVEN - warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1 - end - if legacy_trim_mode != NOT_GIVEN - warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1 - trim_mode = legacy_trim_mode - end - if legacy_eoutvar != NOT_GIVEN - warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1 - eoutvar = legacy_eoutvar - end - + def initialize(str, trim_mode: nil, eoutvar: '_erbout') compiler = make_compiler(trim_mode) set_eoutvar(compiler, eoutvar) @src, @encoding, @frozen_string = *compiler.compile(str) @@ -353,54 +837,137 @@ class ERB @lineno = 0 @_init = self.class.singleton_class end - NOT_GIVEN = Object.new - private_constant :NOT_GIVEN - - ## - # Creates a new compiler for ERB. See ERB::Compiler.new for details + # :markup: markdown + # + # :call-seq: + # make_compiler -> erb_compiler + # + # Returns a new ERB::Compiler with the given `trim_mode`; + # for `trim_mode` values, see ERB.new: + # + # ``` + # ERB.new('').make_compiler(nil) + # # => #<ERB::Compiler:0x000001cff9467678 @insert_cmd="print", @percent=false, @post_cmd=[], @pre_cmd=[], @put_cmd="print", @trim_mode=nil> + # ``` + # def make_compiler(trim_mode) ERB::Compiler.new(trim_mode) end - # The Ruby code generated by ERB + # :markup: markdown + # + # Returns the Ruby code that, when executed, generates the result; + # the code is executed by method #result, + # and by its wrapper methods #result_with_hash and #run: + # + # ``` + # template = 'The time is <%= Time.now %>.' + # erb = ERB.new(template) + # erb.src + # # => "#coding:UTF-8\n_erbout = +''; _erbout.<< \"The time is \".freeze; _erbout.<<(( Time.now ).to_s); _erbout.<< \".\".freeze; _erbout" + # erb.result + # # => "The time is 2025-09-18 15:58:08 -0500." + # ``` + # + # In a more readable format: + # + # ``` + # # puts erb.src.split('; ') + # # #coding:UTF-8 + # # _erbout = +'' + # # _erbout.<< "The time is ".freeze + # # _erbout.<<(( Time.now ).to_s) + # # _erbout.<< ".".freeze + # # _erbout + # ``` + # + # Variable `_erbout` is used to store the intermediate results in the code; + # the name `_erbout` is the default in ERB.new, + # and can be changed via keyword argument `eoutvar`: + # + # ``` + # erb = ERB.new(template, eoutvar: '_foo') + # puts template.src.split('; ') + # #coding:UTF-8 + # _foo = +'' + # _foo.<< "The time is ".freeze + # _foo.<<(( Time.now ).to_s) + # _foo.<< ".".freeze + # _foo + # ``` + # attr_reader :src - # The encoding to eval + # :markup: markdown + # + # Returns the encoding of `self`; + # see [Encodings][encodings]: + # + # [encodings]: rdoc-ref:ERB@Encodings + # attr_reader :encoding - # The optional _filename_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the file name to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :filename - # The optional _lineno_ argument passed to Kernel#eval when the ERB code - # is run + # :markup: markdown + # + # Sets or returns the line number to be used in reporting errors; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting attr_accessor :lineno + # :markup: markdown # - # Sets optional filename and line number that will be used in ERB code - # evaluation and error reporting. See also #filename= and #lineno= - # - # erb = ERB.new('<%= some_x %>') - # erb.render - # # undefined local variable or method `some_x' - # # from (erb):1 + # :call-seq: + # location = [filename, lineno] => [filename, lineno] + # location = filename -> filename # - # erb.location = ['file.erb', 3] - # # All subsequent error reporting would use new location - # erb.render - # # undefined local variable or method `some_x' - # # from file.erb:4 + # Sets the values of #filename and, if given, #lineno; + # see [Error Reporting][error reporting]. # + # [error reporting]: rdoc-ref:ERB@Error+Reporting def location=((filename, lineno)) @filename = filename @lineno = lineno if lineno end + # :markup: markdown # - # Can be used to set _eoutvar_ as described in ERB::new. It's probably - # easier to just use the constructor though, since calling this method - # requires the setup of an ERB _compiler_ object. + # :call-seq: + # set_eoutvar(compiler, eoutvar = '_erbout') -> [eoutvar] + # + # Sets the `eoutvar` value in the ERB::Compiler object `compiler`; + # returns a 1-element array containing the value of `eoutvar`: + # + # ``` + # template = ERB.new('') + # compiler = template.make_compiler(nil) + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="print", + # @percent=false, + # @post_cmd=[], + # @pre_cmd=[], + # @put_cmd="print", + # @trim_mode=nil> + # template.set_eoutvar(compiler, '_foo') # => ["_foo"] + # pp compiler + # #<ERB::Compiler:0x000001cff8a9aa00 + # @insert_cmd="_foo.<<", + # @percent=false, + # @post_cmd=["_foo"], + # @pre_cmd=["_foo = +''"], + # @put_cmd="_foo.<<", + # @trim_mode=nil> + # ``` # def set_eoutvar(compiler, eoutvar = '_erbout') compiler.put_cmd = "#{eoutvar}.<<" @@ -409,18 +976,34 @@ class ERB compiler.post_cmd = [eoutvar] end - # Generate results and print them. (see ERB#result) + # :markup: markdown + # + # :call-seq: + # run(binding = new_toplevel) -> nil + # + # Like #result, but prints the result string (instead of returning it); + # returns `nil`. def run(b=new_toplevel) print self.result(b) end + # :markup: markdown + # + # :call-seq: + # result(binding = new_toplevel) -> new_string + # + # Returns the string result formed by processing \ERB tags found in the stored template in `self`. + # + # With no argument given, uses the default binding; + # see [Default Binding][default binding]. + # + # With argument `binding` given, uses the local binding; + # see [Local Binding][local binding]. # - # Executes the generated ERB code to produce a completed template, returning - # the results of that code. (See ERB::new for details on how this process - # can be affected by _safe_level_.) + # See also #result_with_hash. # - # _b_ accepts a Binding object which is used to set the context of - # code evaluation. + # [default binding]: rdoc-ref:ERB@Default+Binding + # [local binding]: rdoc-ref:ERB@Local+Binding # def result(b=new_toplevel) unless @_init.equal?(self.class.singleton_class) @@ -429,8 +1012,18 @@ class ERB eval(@src, b, (@filename || '(erb)'), @lineno) end - # Render a template on a new toplevel binding with local variables specified - # by a Hash object. + # :markup: markdown + # + # :call-seq: + # result_with_hash(hash) -> new_string + # + # Returns the string result formed by processing \ERB tags found in the stored string in `self`; + # see [Augmented Binding][augmented binding]. + # + # See also #result. + # + # [augmented binding]: rdoc-ref:ERB@Augmented+Binding + # def result_with_hash(hash) b = new_toplevel(hash.keys) hash.each_pair do |key, value| @@ -439,10 +1032,22 @@ class ERB result(b) end - ## - # Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do - # not specify a binding. - + # :markup: markdown + # + # :call-seq: + # new_toplevel(symbols) -> new_binding + # + # Returns a new binding based on `TOPLEVEL_BINDING`; + # used to create a default binding for a call to #result. + # + # See [Default Binding][default binding]. + # + # Argument `symbols` is an array of symbols; + # each symbol `symbol` is defined as a new variable to hide and + # prevent it from overwriting a variable of the same name already + # defined within the binding. + # + # [default binding]: rdoc-ref:ERB@Default+Binding def new_toplevel(vars = nil) b = TOPLEVEL_BINDING if vars @@ -455,13 +1060,31 @@ class ERB end private :new_toplevel - # Define _methodname_ as instance method of _mod_ from compiled Ruby source. + # :markup: markdown + # + # :call-seq: + # def_method(module, method_signature, filename = '(ERB)') -> method_name + # + # Creates and returns a new instance method in the given module `module`; + # returns the method name as a symbol. + # + # The method is created from the given `method_signature`, + # which consists of the method name and its argument names (if any). + # + # The `filename` sets the value of #filename; + # see [Error Reporting][error reporting]. + # + # [error reporting]: rdoc-ref:ERB@Error+Reporting + # + # ``` + # template = '<%= arg1 %> <%= arg2 %>' + # erb = ERB.new(template) + # MyModule = Module.new + # erb.def_method(MyModule, 'render(arg1, arg2)') # => :render + # class MyClass; include MyModule; end + # MyClass.new.render('foo', 123) # => "foo 123" + # ``` # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.def_method(MyClass, 'render(arg1, arg2)', filename) - # print MyClass.new.render('foo', 123) def def_method(mod, methodname, fname='(ERB)') src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n" mod.module_eval do @@ -469,35 +1092,81 @@ class ERB end end - # Create unnamed module, define _methodname_ as instance method of it, and return it. - # - # example: - # filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyModule = erb.def_module('render(arg1, arg2)') - # class MyClass - # include MyModule - # end + # :markup: markdown + # + # :call-seq: + # def_module(method_name = 'erb') -> new_module + # + # Returns a new nameless module that has instance method `method_name`. + # + # ``` + # template = '<%= arg1 %> <%= arg2 %>' + # erb = ERB.new(template) + # MyModule = template.def_module('render(arg1, arg2)') + # class MyClass + # include MyModule + # end + # MyClass.new.render('foo', 123) + # # => "foo 123" + # ``` + # def def_module(methodname='erb') mod = Module.new def_method(mod, methodname, @filename || '(ERB)') mod end - # Define unnamed class which has _methodname_ as instance method, and return it. + # :markup: markdown # - # example: - # class MyClass_ - # def initialize(arg1, arg2) - # @arg1 = arg1; @arg2 = arg2 - # end + # :call-seq: + # def_class(super_class = Object, method_name = 'result') -> new_class + # + # Returns a new nameless class whose superclass is `super_class`, + # and which has instance method `method_name`. + # + # Create a template from HTML that has embedded expression tags that use `@arg1` and `@arg2`: + # + # ``` + # html = <<TEMPLATE + # <html> + # <body> + # <p><%= @arg1 %></p> + # <p><%= @arg2 %></p> + # </body> + # </html> + # TEMPLATE + # template = ERB.new(html) + # ``` + # + # Create a base class that has `@arg1` and `@arg2`: + # + # ``` + # class MyBaseClass + # def initialize(arg1, arg2) + # @arg1 = arg1 + # @arg2 = arg2 # end - # filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml - # erb = ERB.new(File.read(filename)) - # erb.filename = filename - # MyClass = erb.def_class(MyClass_, 'render()') - # print MyClass.new('foo', 123).render() + # end + # ``` + # + # Use method #def_class to create a subclass that has method `:render`: + # + # ``` + # MySubClass = template.def_class(MyBaseClass, :render) + # ``` + # + # Generate the result: + # + # ``` + # puts MySubClass.new('foo', 123).render + # <html> + # <body> + # <p>foo</p> + # <p>123</p> + # </body> + # </html> + # ``` + # def def_class(superklass=Object, methodname='result') cls = Class.new(superklass) def_method(cls, methodname, @filename || '(ERB)') diff --git a/lib/erb/compiler.rb b/lib/erb/compiler.rb index 7096c8dcea..6d70288b4f 100644 --- a/lib/erb/compiler.rb +++ b/lib/erb/compiler.rb @@ -80,10 +80,16 @@ class ERB::Compiler # :nodoc: end class Scanner # :nodoc: - @scanner_map = {} + @scanner_map = defined?(Ractor) ? Ractor.make_shareable({}) : {} class << self - def register_scanner(klass, trim_mode, percent) - @scanner_map[[trim_mode, percent]] = klass + if defined?(Ractor) + def register_scanner(klass, trim_mode, percent) + @scanner_map = Ractor.make_shareable({ **@scanner_map, [trim_mode, percent] => klass }) + end + else + def register_scanner(klass, trim_mode, percent) + @scanner_map[[trim_mode, percent]] = klass + end end alias :regist_scanner :register_scanner end @@ -219,7 +225,7 @@ class ERB::Compiler # :nodoc: end end - ERB_STAG = %w(<%= <%# <%) + ERB_STAG = %w(<%= <%# <%).freeze def is_erb_stag?(s) ERB_STAG.member?(s) end @@ -466,7 +472,16 @@ class ERB::Compiler # :nodoc: return enc, frozen end + # :stopdoc: + WARNING_UPLEVEL = Class.new { + attr_reader :c + def initialize from + @c = caller.length - from.length + end + }.new(caller(0)).c + private_constant :WARNING_UPLEVEL + def warn_invalid_trim_mode(mode, uplevel:) - warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + 1 + warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + WARNING_UPLEVEL end end diff --git a/lib/erb/def_method.rb b/lib/erb/def_method.rb index aee989a926..e503b37140 100644 --- a/lib/erb/def_method.rb +++ b/lib/erb/def_method.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -#-- + # ERB::DefMethod # # Utility module to define eRuby script as instance method. diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec index 94a8fd5c3e..3793e5d70f 100644 --- a/lib/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -2,23 +2,23 @@ begin require_relative 'lib/erb/version' rescue LoadError # for Ruby core repository - require_relative 'erb/version' + require_relative 'version' end Gem::Specification.new do |spec| spec.name = 'erb' - spec.version = ERB.const_get(:VERSION, false) + spec.version = ERB::VERSION spec.authors = ['Masatoshi SEKI', 'Takashi Kokubun'] spec.email = ['seki@ruby-lang.org', 'k0kubun@ruby-lang.org'] spec.summary = %q{An easy to use but powerful templating system for Ruby.} spec.description = %q{An easy to use but powerful templating system for Ruby.} spec.homepage = 'https://github.com/ruby/erb' - spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0') spec.licenses = ['Ruby', 'BSD-2-Clause'] spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md" spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } @@ -27,12 +27,11 @@ Gem::Specification.new do |spec| spec.executables = ['erb'] spec.require_paths = ['lib'] + spec.required_ruby_version = '>= 3.2.0' + if RUBY_ENGINE == 'jruby' spec.platform = 'java' else - spec.required_ruby_version = '>= 2.7.0' spec.extensions = ['ext/erb/escape/extconf.rb'] end - - spec.add_dependency 'cgi', '>= 0.3.3' end diff --git a/lib/erb/util.rb b/lib/erb/util.rb index 1d2a36275d..d7d69eb4f1 100644 --- a/lib/erb/util.rb +++ b/lib/erb/util.rb @@ -1,18 +1,25 @@ # frozen_string_literal: true -#-- -# ERB::Escape -# -# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope -# Rails will not monkey-patch ERB::Escape#html_escape. + +# Load CGI.escapeHTML and CGI.escapeURIComponent. +# CRuby: +# cgi.gem v0.1.0+ (Ruby 2.7-3.4) and Ruby 4.0+ stdlib have 'cgi/escape' and CGI.escapeHTML. +# cgi.gem v0.3.3+ (Ruby 3.2-3.4) and Ruby 4.0+ stdlib have CGI.escapeURIComponent. +# JRuby: cgi.gem has a Java extension 'cgi/escape'. +# TruffleRuby: lib/truffle/cgi/escape.rb requires 'cgi/util'. +require 'cgi/escape' + +# Load or define ERB::Escape#html_escape. +# We don't build the C extension 'cgi/escape' for JRuby, TruffleRuby, and WASM. +# miniruby (used by CRuby build scripts) also fails to load erb/escape.so. begin - # We don't build the C extension for JRuby, TruffleRuby, and WASM - if $LOAD_PATH.resolve_feature_path('erb/escape') - require 'erb/escape' - end -rescue LoadError # resolve_feature_path raises LoadError on TruffleRuby 22.3.0 -end -unless defined?(ERB::Escape) + require 'erb/escape' +rescue LoadError + # ERB::Escape + # + # A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope + # Rails will not monkey-patch ERB::Escape#html_escape. module ERB::Escape + # :stopdoc: def html_escape(s) CGI.escapeHTML(s.to_s) end @@ -20,7 +27,6 @@ unless defined?(ERB::Escape) end end -#-- # ERB::Util # # A utility module for conversion routines, often handy in HTML generation. @@ -42,20 +48,28 @@ module ERB::Util alias h html_escape module_function :h - # - # A utility method for encoding the String _s_ as a URL. - # - # require "erb" - # include ERB::Util - # - # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") - # - # _Generates_ - # - # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide - # - def url_encode(s) - CGI.escapeURIComponent(s.to_s) + if CGI.respond_to?(:escapeURIComponent) + # + # A utility method for encoding the String _s_ as a URL. + # + # require "erb" + # include ERB::Util + # + # puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide") + # + # _Generates_ + # + # Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide + # + def url_encode(s) + CGI.escapeURIComponent(s.to_s) + end + else # cgi.gem <= v0.3.2 + def url_encode(s) + s.to_s.b.gsub(/[^a-zA-Z0-9_\-.~]/n) do |m| + sprintf("%%%02X", m.unpack1("C")) + end + end end alias u url_encode module_function :u diff --git a/lib/erb/version.rb b/lib/erb/version.rb index b5fe39b330..e367a1b5c7 100644 --- a/lib/erb/version.rb +++ b/lib/erb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class ERB - VERSION = '4.0.4' - private_constant :VERSION + # The string \ERB version. + VERSION = '6.0.1' end diff --git a/lib/error_highlight/base.rb b/lib/error_highlight/base.rb index e2077fa5a6..bc4a62c9d6 100644 --- a/lib/error_highlight/base.rb +++ b/lib/error_highlight/base.rb @@ -1,13 +1,13 @@ require_relative "version" module ErrorHighlight - # Identify the code fragment at that a given exception occurred. + # Identify the code fragment where a given exception occurred. # # Options: # # point_type: :name | :args - # :name (default) points the method/variable name that the exception occurred. - # :args points the arguments of the method call that the exception occurred. + # :name (default) points to the method/variable name where the exception occurred. + # :args points to the arguments of the method call where the exception occurred. # # backtrace_location: Thread::Backtrace::Location # It locates the code fragment of the given backtrace_location. @@ -113,7 +113,7 @@ module ErrorHighlight snippet = @node.script_lines[lineno - 1 .. last_lineno - 1].join("") snippet += "\n" unless snippet.end_with?("\n") - # It require some work to support Unicode (or multibyte) characters. + # It requires some work to support Unicode (or multibyte) characters. # Tentatively, we stop highlighting if the code snippet has non-ascii characters. # See https://github.com/ruby/error_highlight/issues/4 raise NonAscii unless snippet.ascii_only? @@ -122,56 +122,51 @@ module ErrorHighlight end end - OPT_GETCONSTANT_PATH = (RUBY_VERSION.split(".").map {|s| s.to_i } <=> [3, 2]) >= 0 - private_constant :OPT_GETCONSTANT_PATH - def spot return nil unless @node - if OPT_GETCONSTANT_PATH - # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`) - # is compiled to one instruction (opt_getconstant_path). - # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo` - # or `Foo::Bar` causes NameError. - # So we try to spot the sub-node that causes the NameError by using - # `NameError#name`. - case @node.type - when :COLON2 - subnodes = [] - node = @node - while node.type == :COLON2 - node2, const = node.children - subnodes << node if const == @name - node = node2 - end - if node.type == :CONST || node.type == :COLON3 - if node.children.first == @name - subnodes << node - end - - # If we found only one sub-node whose name is equal to @name, use it - return nil if subnodes.size != 1 - @node = subnodes.first - else - # Do nothing; opt_getconstant_path is used only when the const base is - # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`) - end - when :constant_path_node - subnodes = [] - node = @node - - begin - subnodes << node if node.name == @name - end while (node = node.parent).is_a?(Prism::ConstantPathNode) - - if node.is_a?(Prism::ConstantReadNode) && node.name == @name + # In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`) + # is compiled to one instruction (opt_getconstant_path). + # @node points to the node of the whole `Foo::Bar::Baz` even if `Foo` + # or `Foo::Bar` causes NameError. + # So we try to spot the sub-node that causes the NameError by using + # `NameError#name`. + case @node.type + when :COLON2 + subnodes = [] + node = @node + while node.type == :COLON2 + node2, const = node.children + subnodes << node if const == @name + node = node2 + end + if node.type == :CONST || node.type == :COLON3 + if node.children.first == @name subnodes << node end # If we found only one sub-node whose name is equal to @name, use it return nil if subnodes.size != 1 @node = subnodes.first + else + # Do nothing; opt_getconstant_path is used only when the const base is + # NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`) end + when :constant_path_node + subnodes = [] + node = @node + + begin + subnodes << node if node.name == @name + end while (node = node.parent).is_a?(Prism::ConstantPathNode) + + if node.is_a?(Prism::ConstantReadNode) && node.name == @name + subnodes << node + end + + # If we found only one sub-node whose name is equal to @name, use it + return nil if subnodes.size != 1 + @node = subnodes.first end case @node.type @@ -239,6 +234,20 @@ module ErrorHighlight when :OP_CDECL spot_op_cdecl + when :DEFN + raise NotImplementedError if @point_type != :name + spot_defn + + when :DEFS + raise NotImplementedError if @point_type != :name + spot_defs + + when :LAMBDA + spot_lambda + + when :ITER + spot_iter + when :call_node case @point_type when :name @@ -280,6 +289,30 @@ module ErrorHighlight when :constant_path_operator_write_node prism_spot_constant_path_operator_write + when :def_node + case @point_type + when :name + prism_spot_def_for_name + when :args + raise NotImplementedError + end + + when :lambda_node + case @point_type + when :name + prism_spot_lambda_for_name + when :args + raise NotImplementedError + end + + when :block_node + case @point_type + when :name + prism_spot_block_for_name + when :args + raise NotImplementedError + end + end if @snippet && @beg_column && @end_column && @beg_column < @end_column @@ -344,6 +377,7 @@ module ErrorHighlight end elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column) @snippet = $` + $& + @beg_lineno = @end_lineno = lineno @beg_column = $~.begin(1) @end_column = $~.end(1) end @@ -470,7 +504,6 @@ module ErrorHighlight def spot_fcall_for_args _mid, nd_args = @node.children if nd_args && nd_args.first_lineno == nd_args.last_lineno - # binary operator fetch_line(nd_args.first_lineno) @beg_column = nd_args.first_column @end_column = nd_args.last_column @@ -582,8 +615,9 @@ module ErrorHighlight @beg_column = nd_parent.last_column @end_column = @node.last_column else - @snippet = @fetch[@node.last_lineno] + fetch_line(@node.last_lineno) if @snippet[...@node.last_column].match(/#{ Regexp.quote(const) }\z/) + @beg_lineno = @end_lineno = @node.last_lineno @beg_column = $~.begin(0) @end_column = $~.end(0) end @@ -597,7 +631,7 @@ module ErrorHighlight nd_lhs, op, _nd_rhs = @node.children *nd_parent_lhs, _const = nd_lhs.children if @name == op - @snippet = @fetch[nd_lhs.last_lineno] + fetch_line(nd_lhs.last_lineno) if @snippet.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column) @beg_column = $~.begin(1) @end_column = $~.end(1) @@ -607,18 +641,67 @@ module ErrorHighlight @end_column = nd_lhs.last_column if nd_parent_lhs.empty? # example: ::C += 1 if nd_lhs.first_lineno == nd_lhs.last_lineno - @snippet = @fetch[nd_lhs.last_lineno] + fetch_line(nd_lhs.last_lineno) @beg_column = nd_lhs.first_column end else # example: Foo::Bar::C += 1 if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno - @snippet = @fetch[nd_lhs.last_lineno] + fetch_line(nd_lhs.last_lineno) @beg_column = nd_parent_lhs.last.last_column end end end end + # Example: + # def bar; end + # ^^^ + def spot_defn + mid, = @node.children + fetch_line(@node.first_lineno) + if @snippet.match(/\Gdef\s+(#{ Regexp.quote(mid) }\b)/, @node.first_column) + @beg_column = $~.begin(1) + @end_column = $~.end(1) + end + end + + # Example: + # def Foo.bar; end + # ^^^^ + def spot_defs + nd_recv, mid, = @node.children + fetch_line(nd_recv.last_lineno) + if @snippet.match(/\G\s*(\.\s*#{ Regexp.quote(mid) }\b)/, nd_recv.last_column) + @beg_column = $~.begin(1) + @end_column = $~.end(1) + end + end + + # Example: + # -> { ... } + # ^^ + def spot_lambda + fetch_line(@node.first_lineno) + if @snippet.match(/\G->/, @node.first_column) + @beg_column = $~.begin(0) + @end_column = $~.end(0) + end + end + + # Example: + # lambda { ... } + # ^ + # define_method :foo do + # ^^ + def spot_iter + _nd_fcall, nd_scope = @node.children + fetch_line(nd_scope.first_lineno) + if @snippet.match(/\G(?:do\b|\{)/, nd_scope.first_column) + @beg_column = $~.begin(0) + @end_column = $~.end(0) + end + end + def fetch_line(lineno) @beg_lineno = @end_lineno = lineno @snippet = @fetch[lineno] @@ -824,6 +907,31 @@ module ErrorHighlight prism_location(@node.binary_operator_loc.chop) end end + + # Example: + # def foo() + # ^^^ + def prism_spot_def_for_name + location = @node.name_loc + location = @node.operator_loc.join(location) if @node.operator_loc + prism_location(location) + end + + # Example: + # -> x, y { } + # ^^ + def prism_spot_lambda_for_name + prism_location(@node.operator_loc) + end + + # Example: + # lambda { } + # ^ + # define_method :foo do |x, y| + # ^ + def prism_spot_block_for_name + prism_location(@node.opening_loc) + end end private_constant :Spotter diff --git a/lib/error_highlight/core_ext.rb b/lib/error_highlight/core_ext.rb index b69093f74e..c3354f46cd 100644 --- a/lib/error_highlight/core_ext.rb +++ b/lib/error_highlight/core_ext.rb @@ -3,9 +3,38 @@ require_relative "formatter" module ErrorHighlight module CoreExt private def generate_snippet - spot = ErrorHighlight.spot(self) - return "" unless spot - return ErrorHighlight.formatter.message_for(spot) + if ArgumentError === self && message =~ /\A(?:wrong number of arguments|missing keyword[s]?|unknown keyword[s]?|no keywords accepted)\b/ + locs = self.backtrace_locations + return "" if locs.size < 2 + callee_loc, caller_loc = locs + callee_spot = ErrorHighlight.spot(self, backtrace_location: callee_loc, point_type: :name) + caller_spot = ErrorHighlight.spot(self, backtrace_location: caller_loc, point_type: :name) + if caller_spot && callee_spot && + caller_loc.path == callee_loc.path && + caller_loc.lineno == callee_loc.lineno && + caller_spot == callee_spot + callee_loc = callee_spot = nil + end + ret = +"\n" + [["caller", caller_loc, caller_spot], ["callee", callee_loc, callee_spot]].each do |header, loc, spot| + out = nil + if loc + out = " #{ header }: #{ loc.path }:#{ loc.lineno }" + if spot + _, _, snippet, highlight = ErrorHighlight.formatter.message_for(spot).lines + out += "\n | #{ snippet } #{ highlight }" + else + # do nothing + end + end + ret << "\n" + out if out + end + ret + else + spot = ErrorHighlight.spot(self) + return "" unless spot + return ErrorHighlight.formatter.message_for(spot) + end end if Exception.method_defined?(:detailed_message) diff --git a/lib/error_highlight/error_highlight.gemspec b/lib/error_highlight/error_highlight.gemspec index b2da18df83..edfc4b776f 100644 --- a/lib/error_highlight/error_highlight.gemspec +++ b/lib/error_highlight/error_highlight.gemspec @@ -18,7 +18,7 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/error_highlight" spec.license = "MIT" - spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0.dev") + spec.required_ruby_version = Gem::Requirement.new(">= 3.2.0") spec.files = Dir.chdir(File.expand_path(__dir__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) } diff --git a/lib/error_highlight/formatter.rb b/lib/error_highlight/formatter.rb index ba0093d3e5..d2fad9e75c 100644 --- a/lib/error_highlight/formatter.rb +++ b/lib/error_highlight/formatter.rb @@ -56,11 +56,11 @@ module ErrorHighlight end def self.terminal_width - # lazy load io/console, so it's not loaded when 'max_snippet_width' is set + # lazy load io/console to avoid loading it when 'max_snippet_width' is manually set require "io/console" - STDERR.winsize[1] if STDERR.tty? + $stderr.winsize[1] if $stderr.tty? rescue LoadError, NoMethodError, SystemCallError - # do not truncate when window size is not available + # skip truncation when terminal window size is unavailable end end diff --git a/lib/error_highlight/version.rb b/lib/error_highlight/version.rb index 506d37fbc1..f0a5376b14 100644 --- a/lib/error_highlight/version.rb +++ b/lib/error_highlight/version.rb @@ -1,3 +1,3 @@ module ErrorHighlight - VERSION = "0.6.0" + VERSION = "0.7.1" end diff --git a/lib/fileutils.rb b/lib/fileutils.rb index b9d683797a..0706e007ca 100644 --- a/lib/fileutils.rb +++ b/lib/fileutils.rb @@ -181,7 +181,7 @@ end # module FileUtils # The version number. - VERSION = "1.7.3" + VERSION = "1.8.0" def self.private_module_function(name) #:nodoc: module_function name @@ -706,11 +706,12 @@ module FileUtils # def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil) if relative - return ln_sr(src, dest, force: force, noop: noop, verbose: verbose) + return ln_sr(src, dest, force: force, target_directory: target_directory, noop: noop, verbose: verbose) end - fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose + fu_output_message "ln -s#{force ? 'f' : ''}#{ + target_directory ? '' : 'T'} #{[src,dest].flatten.join ' '}" if verbose return if noop - fu_each_src_dest0(src, dest) do |s,d| + fu_each_src_dest0(src, dest, target_directory) do |s,d| remove_file d, true if force File.symlink s, d end @@ -730,42 +731,37 @@ module FileUtils # Like FileUtils.ln_s, but create links relative to +dest+. # def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil) - options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" - dest = File.path(dest) - srcs = Array(src) - link = proc do |s, target_dir_p = true| - s = File.path(s) - if target_dir_p - d = File.join(destdirs = dest, File.basename(s)) + cmd = "ln -s#{force ? 'f' : ''}#{target_directory ? '' : 'T'}" if verbose + fu_each_src_dest0(src, dest, target_directory) do |s,d| + if target_directory + parent = File.dirname(d) + destdirs = fu_split_path(parent) + real_ddirs = fu_split_path(File.realpath(parent)) else - destdirs = File.dirname(d = dest) + destdirs ||= fu_split_path(dest) + real_ddirs ||= fu_split_path(File.realdirpath(dest)) end - destdirs = fu_split_path(File.realpath(destdirs)) - if fu_starting_path?(s) - srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s))) - base = fu_relative_components_from(srcdirs, destdirs) - s = File.join(*base) + srcdirs = fu_split_path(s) + i = fu_common_components(srcdirs, destdirs) + n = destdirs.size - i + n -= 1 unless target_directory + link1 = fu_clean_components(*Array.new([n, 0].max, '..'), *srcdirs[i..-1]) + begin + real_sdirs = fu_split_path(File.realdirpath(s)) rescue nil + rescue else - srcdirs = fu_clean_components(*fu_split_path(s)) - base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs) - while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last) - srcdirs.shift - base.pop - end - s = File.join(*base, *srcdirs) + i = fu_common_components(real_sdirs, real_ddirs) + n = real_ddirs.size - i + n -= 1 unless target_directory + link2 = fu_clean_components(*Array.new([n, 0].max, '..'), *real_sdirs[i..-1]) + link1 = link2 if link1.size > link2.size end - fu_output_message "ln -s#{options} #{s} #{d}" if verbose + s = File.join(link1) + fu_output_message [cmd, s, d].flatten.join(' ') if verbose next if noop remove_file d, true if force File.symlink s, d end - case srcs.size - when 0 - when 1 - link[srcs[0], target_directory && File.directory?(dest)] - else - srcs.each(&link) - end end module_function :ln_sr @@ -800,13 +796,13 @@ module FileUtils # File.file?('dest1/dir1/t2.txt') # => true # File.file?('dest1/dir1/t3.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link. - # - <tt>remove_destination: true</tt> - removes +dest+ before creating links. + # - +dereference_root+ - dereferences +src+ if it is a symbolic link (+false+ by default). + # - +remove_destination+ - removes +dest+ before creating links (+false+ by default). # # Raises an exception if +dest+ is the path to an existing file or directory - # and keyword argument <tt>remove_destination: true</tt> is not given. + # and optional argument +remove_destination+ is not given. # # Related: FileUtils.ln (has different options). # @@ -1029,12 +1025,12 @@ module FileUtils # directories, and symbolic links; # other file types (FIFO streams, device files, etc.) are not supported. # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference_root: true</tt> - if +src+ is a symbolic link, - # follows the link. - # - <tt>preserve: true</tt> - preserves file times. - # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - +dereference_root+ - if +src+ is a symbolic link, + # follows the link (+false+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1065,12 +1061,12 @@ module FileUtils # FileUtils.copy_file('src0.txt', 'dest0.txt') # File.file?('dest0.txt') # => true # - # Keyword arguments: + # Optional arguments: # - # - <tt>dereference: false</tt> - if +src+ is a symbolic link, - # does not follow the link. - # - <tt>preserve: true</tt> - preserves file times. - # - <tt>remove_destination: true</tt> - removes +dest+ before copying files. + # - +dereference+ - if +src+ is a symbolic link, + # follows the link (+true+ by default). + # - +preserve+ - preserves file times (+false+ by default). + # - +remove_destination+ - removes +dest+ before copying files (+false+ by default). # # Related: {methods for copying}[rdoc-ref:FileUtils@Copying]. # @@ -1491,7 +1487,8 @@ module FileUtils # Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting]. # def remove_dir(path, force = false) - remove_entry path, force # FIXME?? check if it is a directory + raise Errno::ENOTDIR, path unless force or File.directory?(path) + remove_entry path, force end module_function :remove_dir @@ -2475,6 +2472,10 @@ module FileUtils def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc: if tmp = Array.try_convert(src) + unless target_directory or tmp.size <= 1 + tmp = tmp.map {|f| File.path(f)} # A workaround for RBS + raise ArgumentError, "extra target #{tmp}" + end tmp.each do |s| s = File.path(s) yield s, (target_directory ? File.join(dest, File.basename(s)) : dest) @@ -2509,7 +2510,11 @@ module FileUtils path = File.path(path) list = [] until (parent, base = File.split(path); parent == path or parent == ".") - list << base + if base != '..' and list.last == '..' and !(fu_have_symlink? && File.symlink?(path)) + list.pop + else + list << base + end path = parent end list << path @@ -2517,14 +2522,14 @@ module FileUtils end private_module_function :fu_split_path - def fu_relative_components_from(target, base) #:nodoc: + def fu_common_components(target, base) #:nodoc: i = 0 while target[i]&.== base[i] i += 1 end - Array.new(base.size-i, '..').concat(target[i..-1]) + i end - private_module_function :fu_relative_components_from + private_module_function :fu_common_components def fu_clean_components(*comp) #:nodoc: comp.shift while comp.first == "." @@ -2534,7 +2539,7 @@ module FileUtils while c = comp.shift if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path)) clean.pop - path.chomp!(%r((?<=\A|/)[^/]+/\z), "") + path.sub!(%r((?<=\A|/)[^/]+/\z), "") else clean << c path << c << "/" diff --git a/lib/find.rb b/lib/find.rb index 98a79cc76d..8223eea456 100644 --- a/lib/find.rb +++ b/lib/find.rb @@ -27,6 +27,7 @@ # module Find + # The version string VERSION = "0.2.0" # diff --git a/lib/forwardable.rb b/lib/forwardable.rb index 71b4e6adad..175d6d9c6b 100644 --- a/lib/forwardable.rb +++ b/lib/forwardable.rb @@ -109,11 +109,11 @@ # +delegate.rb+. # module Forwardable - require 'forwardable/impl' - # Version of +forwardable.rb+ - VERSION = "1.3.3" + VERSION = "1.4.0" VERSION.freeze + + # Version for backward compatibility FORWARDABLE_VERSION = VERSION FORWARDABLE_VERSION.freeze @@ -190,9 +190,7 @@ module Forwardable # If it's not a class or module, it's an instance mod = Module === self ? self : singleton_class - ret = mod.module_eval(&gen) - mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + mod.module_eval(&gen) end alias delegate instance_delegate @@ -206,36 +204,33 @@ module Forwardable if Module === obj ? obj.method_defined?(accessor) || obj.private_method_defined?(accessor) : obj.respond_to?(accessor, true) - accessor = "#{accessor}()" + accessor = "(#{accessor}())" end - method_call = ".__send__(:#{method}, *args, &block)" - if _valid_method?(method) + args = RUBY_VERSION >= '2.7' ? '...' : '*args, &block' + method_call = ".__send__(:#{method}, #{args})" + if method.match?(/\A[_a-zA-Z]\w*[?!]?\z/) loc, = caller_locations(2,1) pre = "_ =" mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method " - method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}" - begin; - unless defined? _.#{method} - ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 - _#{method_call} - else - _.#{method}(*args, &block) - end - end; + method_call = <<~RUBY.chomp + if defined?(_.#{method}) + _.#{method}(#{args}) + else + ::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1 + _#{method_call} + end + RUBY end - _compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1) - begin; + eval(<<~RUBY, nil, __FILE__, __LINE__ + 1) proc do - def #{ali}(*args, &block) - #{pre} - begin - #{accessor} - end#{method_call} + def #{ali}(#{args}) + #{pre}#{accessor} + #{method_call} end end - end; + RUBY end end @@ -310,9 +305,7 @@ module SingleForwardable def def_single_delegator(accessor, method, ali = method) gen = Forwardable._delegator_method(self, accessor, method, ali) - ret = instance_eval(&gen) - singleton_class.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7' - ret + instance_eval(&gen) end alias delegate single_delegate diff --git a/lib/forwardable/forwardable.gemspec b/lib/forwardable/forwardable.gemspec index 9ad59c5f8a..1b539bcfcb 100644 --- a/lib/forwardable/forwardable.gemspec +++ b/lib/forwardable/forwardable.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| spec.licenses = ["Ruby", "BSD-2-Clause"] spec.required_ruby_version = '>= 2.4.0' - spec.files = ["forwardable.gemspec", "lib/forwardable.rb", "lib/forwardable/impl.rb"] + spec.files = ["forwardable.gemspec", "lib/forwardable.rb"] spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/forwardable/impl.rb b/lib/forwardable/impl.rb deleted file mode 100644 index 0322c136db..0000000000 --- a/lib/forwardable/impl.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Forwardable - # :stopdoc: - - def self._valid_method?(method) - catch {|tag| - eval("BEGIN{throw tag}; ().#{method}", binding, __FILE__, __LINE__) - } - rescue SyntaxError - false - else - true - end - - def self._compile_method(src, file, line) - eval(src, nil, file, line) - end -end diff --git a/lib/ipaddr.gemspec b/lib/ipaddr.gemspec index 5719f83fc4..cabc9161ba 100644 --- a/lib/ipaddr.gemspec +++ b/lib/ipaddr.gemspec @@ -29,7 +29,7 @@ Both IPv4 and IPv6 are supported. spec.homepage = "https://github.com/ruby/ipaddr" spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.files = ["LICENSE.txt", "README.md", "ipaddr.gemspec", "lib/ipaddr.rb"] + spec.files = ["LICENSE.txt", "README.md", "lib/ipaddr.rb"] spec.require_paths = ["lib"] spec.required_ruby_version = ">= 2.4" diff --git a/lib/ipaddr.rb b/lib/ipaddr.rb index a45055496c..6b67d7eec6 100644 --- a/lib/ipaddr.rb +++ b/lib/ipaddr.rb @@ -40,7 +40,8 @@ require 'socket' # p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0> class IPAddr - VERSION = "1.2.7" + # The version string + VERSION = "1.2.8" # 32 bit mask for IPv4 IN4MASK = 0xffffffff @@ -151,6 +152,16 @@ class IPAddr return self.clone.set(addr_mask(~@addr)) end + # Returns a new ipaddr greater than the original address by offset + def +(offset) + self.clone.set(@addr + offset, @family) + end + + # Returns a new ipaddr less than the original address by offset + def -(offset) + self.clone.set(@addr - offset, @family) + end + # Returns true if two ipaddrs are equal. def ==(other) other = coerce_other(other) @@ -343,7 +354,7 @@ class IPAddr _ipv4_compat? end - def _ipv4_compat? + def _ipv4_compat? # :nodoc: if !ipv6? || (@addr >> 32) != 0 return false end @@ -357,7 +368,7 @@ class IPAddr # into an IPv4-mapped IPv6 address. def ipv4_mapped if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6) clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) @@ -369,9 +380,11 @@ class IPAddr def ipv4_compat warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE if !ipv4? - raise InvalidAddressError, "not an IPv4 address: #{@addr}" + raise InvalidAddressError, "not an IPv4 address: #{to_s}" end - return self.clone.set(@addr, Socket::AF_INET6) + clone = self.clone.set(@addr, Socket::AF_INET6) + clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000) + clone end # Returns a new ipaddr built by converting the IPv6 address into a @@ -400,7 +413,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC3172. def ip6_arpa if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.arpa" end @@ -408,7 +421,7 @@ class IPAddr # Returns a string for DNS reverse lookup compatible with RFC1886. def ip6_int if !ipv6? - raise InvalidAddressError, "not an IPv6 address: #{@addr}" + raise InvalidAddressError, "not an IPv6 address: #{to_s}" end return _reverse + ".ip6.int" end @@ -533,6 +546,7 @@ class IPAddr end protected + # :stopdoc: def begin_addr @addr & @mask_addr @@ -548,6 +562,7 @@ class IPAddr raise AddressFamilyError, "unsupported address family" end end + #:startdoc: # Set +@addr+, the internal stored ip address, to given +addr+. The # parameter +addr+ is validated using the first +family+ member, @@ -689,6 +704,7 @@ class IPAddr end end + # :stopdoc: def coerce_other(other) case other when IPAddr @@ -709,8 +725,8 @@ class IPAddr octets = addr.split('.') end octets.inject(0) { |i, s| - (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}" - (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}" + (n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{addr}" + (s != '0') && s.start_with?('0') and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{addr}" i << 8 | n } end @@ -727,19 +743,19 @@ class IPAddr right = '' when RE_IPV6ADDRLIKE_COMPRESSED if $4 - left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}" + left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{left}" addr = in_addr($~[4,4]) left = $1 right = $3 + '0:0' else left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" left = $1 right = $2 addr = 0 end else - raise InvalidAddressError, "invalid address: #{@addr}" + raise InvalidAddressError, "invalid address: #{left}" end l = left.split(':') r = right.split(':') @@ -800,7 +816,7 @@ unless Socket.const_defined? :AF_INET6 class << IPSocket private - def valid_v6?(addr) + def valid_v6?(addr) # :nodoc: case addr when IPAddr::RE_IPV6ADDRLIKE_FULL if $2 diff --git a/lib/irb.rb b/lib/irb.rb deleted file mode 100644 index 528892797f..0000000000 --- a/lib/irb.rb +++ /dev/null @@ -1,1593 +0,0 @@ -# frozen_string_literal: true - -# :markup: markdown -# irb.rb - irb main module -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require "ripper" -require "reline" - -require_relative "irb/init" -require_relative "irb/context" -require_relative "irb/default_commands" - -require_relative "irb/ruby-lex" -require_relative "irb/statement" -require_relative "irb/history" -require_relative "irb/input-method" -require_relative "irb/locale" -require_relative "irb/color" - -require_relative "irb/version" -require_relative "irb/easter-egg" -require_relative "irb/debug" -require_relative "irb/pager" - -# ## IRB -# -# Module IRB ("Interactive Ruby") provides a shell-like interface that supports -# user interaction with the Ruby interpreter. -# -# It operates as a *read-eval-print loop* -# ([REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)) -# that: -# -# * ***Reads*** each character as you type. You can modify the IRB context to -# change the way input works. See [Input](rdoc-ref:IRB@Input). -# * ***Evaluates*** the code each time it has read a syntactically complete -# passage. -# * ***Prints*** after evaluating. You can modify the IRB context to change -# the way output works. See [Output](rdoc-ref:IRB@Output). -# -# -# Example: -# -# $ irb -# irb(main):001> File.basename(Dir.pwd) -# => "irb" -# irb(main):002> Dir.entries('.').size -# => 25 -# irb(main):003* Dir.entries('.').select do |entry| -# irb(main):004* entry.start_with?('R') -# irb(main):005> end -# => ["README.md", "Rakefile"] -# -# The typed input may also include [\IRB-specific -# commands](rdoc-ref:IRB@IRB-Specific+Commands). -# -# As seen above, you can start IRB by using the shell command `irb`. -# -# You can stop an IRB session by typing command `exit`: -# -# irb(main):006> exit -# $ -# -# At that point, IRB calls any hooks found in array `IRB.conf[:AT_EXIT]`, then -# exits. -# -# ## Startup -# -# At startup, IRB: -# -# 1. Interprets (as Ruby code) the content of the [configuration -# file](rdoc-ref:IRB@Configuration+File) (if given). -# 2. Constructs the initial session context from [hash -# IRB.conf](rdoc-ref:IRB@Hash+IRB.conf) and from default values; the hash -# content may have been affected by [command-line -# options](rdoc-ref:IB@Command-Line+Options), and by direct assignments in -# the configuration file. -# 3. Assigns the context to variable `conf`. -# 4. Assigns command-line arguments to variable `ARGV`. -# 5. Prints the [prompt](rdoc-ref:IRB@Prompt+and+Return+Formats). -# 6. Puts the content of the [initialization -# script](rdoc-ref:IRB@Initialization+Script) onto the IRB shell, just as if -# it were user-typed commands. -# -# -# ### The Command Line -# -# On the command line, all options precede all arguments; the first item that is -# not recognized as an option is treated as an argument, as are all items that -# follow. -# -# #### Command-Line Options -# -# Many command-line options affect entries in hash `IRB.conf`, which in turn -# affect the initial configuration of the IRB session. -# -# Details of the options are described in the relevant subsections below. -# -# A cursory list of the IRB command-line options may be seen in the [help -# message](https://raw.githubusercontent.com/ruby/irb/master/lib/irb/lc/help-message), -# which is also displayed if you use command-line option `--help`. -# -# If you are interested in a specific option, consult the -# [index](rdoc-ref:doc/irb/indexes.md@Index+of+Command-Line+Options). -# -# #### Command-Line Arguments -# -# Command-line arguments are passed to IRB in array `ARGV`: -# -# $ irb --noscript Foo Bar Baz -# irb(main):001> ARGV -# => ["Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ -# -# Command-line option `--` causes everything that follows to be treated as -# arguments, even those that look like options: -# -# $ irb --noscript -- --noscript -- Foo Bar Baz -# irb(main):001> ARGV -# => ["--noscript", "--", "Foo", "Bar", "Baz"] -# irb(main):002> exit -# $ -# -# ### Configuration File -# -# You can initialize IRB via a *configuration file*. -# -# If command-line option `-f` is given, no configuration file is looked for. -# -# Otherwise, IRB reads and interprets a configuration file if one is available. -# -# The configuration file can contain any Ruby code, and can usefully include -# user code that: -# -# * Can then be debugged in IRB. -# * Configures IRB itself. -# * Requires or loads files. -# -# -# The path to the configuration file is the first found among: -# -# * The value of variable `$IRBRC`, if defined. -# * The value of variable `$XDG_CONFIG_HOME/irb/irbrc`, if defined. -# * File `$HOME/.irbrc`, if it exists. -# * File `$HOME/.config/irb/irbrc`, if it exists. -# * File `.irbrc` in the current directory, if it exists. -# * File `irb.rc` in the current directory, if it exists. -# * File `_irbrc` in the current directory, if it exists. -# * File `$irbrc` in the current directory, if it exists. -# -# -# If the search fails, there is no configuration file. -# -# If the search succeeds, the configuration file is read as Ruby code, and so -# can contain any Ruby programming you like. -# -# Method `conf.rc?` returns `true` if a configuration file was read, `false` -# otherwise. Hash entry `IRB.conf[:RC]` also contains that value. -# -# ### Hash `IRB.conf` -# -# The initial entries in hash `IRB.conf` are determined by: -# -# * Default values. -# * Command-line options, which may override defaults. -# * Direct assignments in the configuration file. -# -# -# You can see the hash by typing `IRB.conf`. -# -# Details of the entries' meanings are described in the relevant subsections -# below. -# -# If you are interested in a specific entry, consult the -# [index](rdoc-ref:doc/irb/indexes.md@Index+of+IRB.conf+Entries). -# -# ### Notes on Initialization Precedence -# -# * Any conflict between an entry in hash `IRB.conf` and a command-line option -# is resolved in favor of the hash entry. -# * Hash `IRB.conf` affects the context only once, when the configuration file -# is interpreted; any subsequent changes to it do not affect the context and -# are therefore essentially meaningless. -# -# -# ### Initialization Script -# -# By default, the first command-line argument (after any options) is the path to -# a Ruby initialization script. -# -# IRB reads the initialization script and puts its content onto the IRB shell, -# just as if it were user-typed commands. -# -# Command-line option `--noscript` causes the first command-line argument to be -# treated as an ordinary argument (instead of an initialization script); -# `--script` is the default. -# -# ## Input -# -# This section describes the features that allow you to change the way IRB input -# works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). -# -# ### Input Command History -# -# By default, IRB stores a history of up to 1000 input commands in a file named -# `.irb_history`. The history file will be in the same directory as the -# [configuration file](rdoc-ref:IRB@Configuration+File) if one is found, or in -# `~/` otherwise. -# -# A new IRB session creates the history file if it does not exist, and appends -# to the file if it does exist. -# -# You can change the filepath by adding to your configuration file: -# `IRB.conf[:HISTORY_FILE] = *filepath*`, where *filepath* is a string filepath. -# -# During the session, method `conf.history_file` returns the filepath, and -# method `conf.history_file = *new_filepath*` copies the history to the file at -# *new_filepath*, which becomes the history file for the session. -# -# You can change the number of commands saved by adding to your configuration -# file: `IRB.conf[:SAVE_HISTORY] = *n*`, where *n* is one of: -# -# * Positive integer: the number of commands to be saved. -# * Negative integer: all commands are to be saved. -# * Zero or `nil`: no commands are to be saved. -# -# -# During the session, you can use methods `conf.save_history` or -# `conf.save_history=` to retrieve or change the count. -# -# ### Command Aliases -# -# By default, IRB defines several command aliases: -# -# irb(main):001> conf.command_aliases -# => {:"$"=>:show_source, :"@"=>:whereami} -# -# You can change the initial aliases in the configuration file with: -# -# IRB.conf[:COMMAND_ALIASES] = {foo: :show_source, bar: :whereami} -# -# You can replace the current aliases at any time with configuration method -# `conf.command_aliases=`; Because `conf.command_aliases` is a hash, you can -# modify it. -# -# ### End-of-File -# -# By default, `IRB.conf[:IGNORE_EOF]` is `false`, which means that typing the -# end-of-file character `Ctrl-D` causes the session to exit. -# -# You can reverse that behavior by adding `IRB.conf[:IGNORE_EOF] = true` to the -# configuration file. -# -# During the session, method `conf.ignore_eof?` returns the setting, and method -# `conf.ignore_eof = *boolean*` sets it. -# -# ### SIGINT -# -# By default, `IRB.conf[:IGNORE_SIGINT]` is `true`, which means that typing the -# interrupt character `Ctrl-C` causes the session to exit. -# -# You can reverse that behavior by adding `IRB.conf[:IGNORE_SIGING] = false` to -# the configuration file. -# -# During the session, method `conf.ignore_siging?` returns the setting, and -# method `conf.ignore_sigint = *boolean*` sets it. -# -# ### Automatic Completion -# -# By default, IRB enables [automatic -# completion](https://en.wikipedia.org/wiki/Autocomplete#In_command-line_interpr -# eters): -# -# You can disable it by either of these: -# -# * Adding `IRB.conf[:USE_AUTOCOMPLETE] = false` to the configuration file. -# * Giving command-line option `--noautocomplete` (`--autocomplete` is the -# default). -# -# -# Method `conf.use_autocomplete?` returns `true` if automatic completion is -# enabled, `false` otherwise. -# -# The setting may not be changed during the session. -# -# ### Automatic Indentation -# -# By default, IRB automatically indents lines of code to show structure (e.g., -# it indent the contents of a block). -# -# The current setting is returned by the configuration method -# `conf.auto_indent_mode`. -# -# The default initial setting is `true`: -# -# irb(main):001> conf.auto_indent_mode -# => true -# irb(main):002* Dir.entries('.').select do |entry| -# irb(main):003* entry.start_with?('R') -# irb(main):004> end -# => ["README.md", "Rakefile"] -# -# You can change the initial setting in the configuration file with: -# -# IRB.conf[:AUTO_INDENT] = false -# -# Note that the *current* setting *may not* be changed in the IRB session. -# -# ### Input Method -# -# The IRB input method determines how command input is to be read; by default, -# the input method for a session is IRB::RelineInputMethod. Unless the -# value of the TERM environment variable is 'dumb', in which case the -# most simplistic input method is used. -# -# You can set the input method by: -# -# * Adding to the configuration file: -# -# * `IRB.conf[:USE_SINGLELINE] = true` or `IRB.conf[:USE_MULTILINE]= -# false` sets the input method to IRB::ReadlineInputMethod. -# * `IRB.conf[:USE_SINGLELINE] = false` or `IRB.conf[:USE_MULTILINE] = -# true` sets the input method to IRB::RelineInputMethod. -# -# -# * Giving command-line options: -# -# * `--singleline` or `--nomultiline` sets the input method to -# IRB::ReadlineInputMethod. -# * `--nosingleline` or `--multiline` sets the input method to -# IRB::RelineInputMethod. -# * `--nosingleline` together with `--nomultiline` sets the -# input to IRB::StdioInputMethod. -# -# -# Method `conf.use_multiline?` and its synonym `conf.use_reline` return: -# -# * `true` if option `--multiline` was given. -# * `false` if option `--nomultiline` was given. -# * `nil` if neither was given. -# -# -# Method `conf.use_singleline?` and its synonym `conf.use_readline` return: -# -# * `true` if option `--singleline` was given. -# * `false` if option `--nosingleline` was given. -# * `nil` if neither was given. -# -# -# ## Output -# -# This section describes the features that allow you to change the way IRB -# output works; see also [Input and Output](rdoc-ref:IRB@Input+and+Output). -# -# ### Return-Value Printing (Echoing) -# -# By default, IRB prints (echoes) the values returned by all input commands. -# -# You can change the initial behavior and suppress all echoing by: -# -# * Adding to the configuration file: `IRB.conf[:ECHO] = false`. (The default -# value for this entry is `nil`, which means the same as `true`.) -# * Giving command-line option `--noecho`. (The default is `--echo`.) -# -# -# During the session, you can change the current setting with configuration -# method `conf.echo=` (set to `true` or `false`). -# -# As stated above, by default IRB prints the values returned by all input -# commands; but IRB offers special treatment for values returned by assignment -# statements, which may be: -# -# * Printed with truncation (to fit on a single line of output), which is the -# default; an ellipsis (`...` is suffixed, to indicate the truncation): -# -# irb(main):001> x = 'abc' * 100 -# -# -# > "abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc... -# -# * Printed in full (regardless of the length). -# * Suppressed (not printed at all) -# -# -# You can change the initial behavior by: -# -# * Adding to the configuration file: `IRB.conf[:ECHO_ON_ASSIGNMENT] = false`. -# (The default value for this entry is `niL`, which means the same as -# `:truncate`.) -# * Giving command-line option `--noecho-on-assignment` or -# `--echo-on-assignment`. (The default is `--truncate-echo-on-assignment`.) -# -# -# During the session, you can change the current setting with configuration -# method `conf.echo_on_assignment=` (set to `true`, `false`, or `:truncate`). -# -# By default, IRB formats returned values by calling method `inspect`. -# -# You can change the initial behavior by: -# -# * Adding to the configuration file: `IRB.conf[:INSPECT_MODE] = false`. (The -# default value for this entry is `true`.) -# * Giving command-line option `--noinspect`. (The default is `--inspect`.) -# -# -# During the session, you can change the setting using method -# `conf.inspect_mode=`. -# -# ### Multiline Output -# -# By default, IRB prefixes a newline to a multiline response. -# -# You can change the initial default value by adding to the configuration file: -# -# IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] = false -# -# During a session, you can retrieve or set the value using methods -# `conf.newline_before_multiline_output?` and -# `conf.newline_before_multiline_output=`. -# -# Examples: -# -# irb(main):001> conf.inspect_mode = false -# => false -# irb(main):002> "foo\nbar" -# => -# foo -# bar -# irb(main):003> conf.newline_before_multiline_output = false -# => false -# irb(main):004> "foo\nbar" -# => foo -# bar -# -# ### Evaluation History -# -# By default, IRB saves no history of evaluations (returned values), and the -# related methods `conf.eval_history`, `_`, and `__` are undefined. -# -# You can turn on that history, and set the maximum number of evaluations to be -# stored: -# -# * In the configuration file: add `IRB.conf[:EVAL_HISTORY] = *n*`. (Examples -# below assume that we've added `IRB.conf[:EVAL_HISTORY] = 5`.) -# * In the session (at any time): `conf.eval_history = *n*`. -# -# -# If `n` is zero, all evaluation history is stored. -# -# Doing either of the above: -# -# * Sets the maximum size of the evaluation history; defines method -# `conf.eval_history`, which returns the maximum size `n` of the evaluation -# history: -# -# irb(main):001> conf.eval_history = 5 -# => 5 -# irb(main):002> conf.eval_history -# => 5 -# -# * Defines variable `_`, which contains the most recent evaluation, or `nil` -# if none; same as method `conf.last_value`: -# -# irb(main):003> _ -# => 5 -# irb(main):004> :foo -# => :foo -# irb(main):005> :bar -# => :bar -# irb(main):006> _ -# => :bar -# irb(main):007> _ -# => :bar -# -# * Defines variable `__`: -# -# * `__` unadorned: contains all evaluation history: -# -# irb(main):008> :foo -# => :foo -# irb(main):009> :bar -# => :bar -# irb(main):010> :baz -# => :baz -# irb(main):011> :bat -# => :bat -# irb(main):012> :bam -# => :bam -# irb(main):013> __ -# => -# 9 :bar -# 10 :baz -# 11 :bat -# 12 :bam -# irb(main):014> __ -# => -# 10 :baz -# 11 :bat -# 12 :bam -# 13 ...self-history... -# -# Note that when the evaluation is multiline, it is displayed -# differently. -# -# * `__[`*m*`]`: -# -# * Positive *m*: contains the evaluation for the given line number, -# or `nil` if that line number is not in the evaluation history: -# -# irb(main):015> __[12] -# => :bam -# irb(main):016> __[1] -# => nil -# -# * Negative *m*: contains the `mth`-from-end evaluation, or `nil` if -# that evaluation is not in the evaluation history: -# -# irb(main):017> __[-3] -# => :bam -# irb(main):018> __[-13] -# => nil -# -# * Zero *m*: contains `nil`: -# -# irb(main):019> __[0] -# => nil -# -# -# -# -# ### Prompt and Return Formats -# -# By default, IRB uses the prompt and return value formats defined in its -# `:DEFAULT` prompt mode. -# -# #### The Default Prompt and Return Format -# -# The default prompt and return values look like this: -# -# irb(main):001> 1 + 1 -# => 2 -# irb(main):002> 2 + 2 -# => 4 -# -# The prompt includes: -# -# * The name of the running program (`irb`); see [IRB -# Name](rdoc-ref:IRB@IRB+Name). -# * The name of the current session (`main`); See [IRB -# Sessions](rdoc-ref:IRB@IRB+Sessions). -# * A 3-digit line number (1-based). -# -# -# The default prompt actually defines three formats: -# -# * One for most situations (as above): -# -# irb(main):003> Dir -# => Dir -# -# * One for when the typed command is a statement continuation (adds trailing -# asterisk): -# -# irb(main):004* Dir. -# -# * One for when the typed command is a string continuation (adds trailing -# single-quote): -# -# irb(main):005' Dir.entries('. -# -# -# You can see the prompt change as you type the characters in the following: -# -# irb(main):001* Dir.entries('.').select do |entry| -# irb(main):002* entry.start_with?('R') -# irb(main):003> end -# => ["README.md", "Rakefile"] -# -# #### Pre-Defined Prompts -# -# IRB has several pre-defined prompts, stored in hash `IRB.conf[:PROMPT]`: -# -# irb(main):001> IRB.conf[:PROMPT].keys -# => [:NULL, :DEFAULT, :CLASSIC, :SIMPLE, :INF_RUBY, :XMP] -# -# To see the full data for these, type `IRB.conf[:PROMPT]`. -# -# Most of these prompt definitions include specifiers that represent values like -# the IRB name, session name, and line number; see [Prompt -# Specifiers](rdoc-ref:IRB@Prompt+Specifiers). -# -# You can change the initial prompt and return format by: -# -# * Adding to the configuration file: `IRB.conf[:PROMPT] = *mode*` where -# *mode* is the symbol name of a prompt mode. -# * Giving a command-line option: -# -# * `--prompt *mode*`: sets the prompt mode to *mode*. where *mode* is the -# symbol name of a prompt mode. -# * `--simple-prompt` or `--sample-book-mode`: sets the prompt mode to -# `:SIMPLE`. -# * `--inf-ruby-mode`: sets the prompt mode to `:INF_RUBY` and suppresses -# both `--multiline` and `--singleline`. -# * `--noprompt`: suppresses prompting; does not affect echoing. -# -# -# -# You can retrieve or set the current prompt mode with methods -# -# `conf.prompt_mode` and `conf.prompt_mode=`. -# -# If you're interested in prompts and return formats other than the defaults, -# you might experiment by trying some of the others. -# -# #### Custom Prompts -# -# You can also define custom prompts and return formats, which may be done -# either in an IRB session or in the configuration file. -# -# A prompt in IRB actually defines three prompts, as seen above. For simple -# custom data, we'll make all three the same: -# -# irb(main):001* IRB.conf[:PROMPT][:MY_PROMPT] = { -# irb(main):002* PROMPT_I: ': ', -# irb(main):003* PROMPT_C: ': ', -# irb(main):004* PROMPT_S: ': ', -# irb(main):005* RETURN: '=> ' -# irb(main):006> } -# => {:PROMPT_I=>": ", :PROMPT_C=>": ", :PROMPT_S=>": ", :RETURN=>"=> "} -# -# If you define the custom prompt in the configuration file, you can also make -# it the current prompt by adding: -# -# IRB.conf[:PROMPT_MODE] = :MY_PROMPT -# -# Regardless of where it's defined, you can make it the current prompt in a -# session: -# -# conf.prompt_mode = :MY_PROMPT -# -# You can view or modify the current prompt data with various configuration -# methods: -# -# * `conf.prompt_mode`, `conf.prompt_mode=`. -# * `conf.prompt_c`, `conf.c=`. -# * `conf.prompt_i`, `conf.i=`. -# * `conf.prompt_s`, `conf.s=`. -# * `conf.return_format`, `return_format=`. -# -# -# #### Prompt Specifiers -# -# A prompt's definition can include specifiers for which certain values are -# substituted: -# -# * `%N`: the name of the running program. -# * `%m`: the value of `self.to_s`. -# * `%M`: the value of `self.inspect`. -# * `%l`: an indication of the type of string; one of `"`, `'`, `/`, `]`. -# * `%NNi`: Indentation level. NN is a 2-digit number that specifies the number -# of digits of the indentation level (03 will result in 001). -# * `%NNn`: Line number. NN is a 2-digit number that specifies the number -# of digits of the line number (03 will result in 001). -# * `%%`: Literal `%`. -# -# -# ### Verbosity -# -# By default, IRB verbosity is disabled, which means that output is smaller -# rather than larger. -# -# You can enable verbosity by: -# -# * Adding to the configuration file: `IRB.conf[:VERBOSE] = true` (the default -# is `nil`). -# * Giving command-line options `--verbose` (the default is `--noverbose`). -# -# -# During a session, you can retrieve or set verbosity with methods -# `conf.verbose` and `conf.verbose=`. -# -# ### Help -# -# Command-line option `--version` causes IRB to print its help text and exit. -# -# ### Version -# -# Command-line option `--version` causes IRB to print its version text and exit. -# -# ## Input and Output -# -# ### Color Highlighting -# -# By default, IRB color highlighting is enabled, and is used for both: -# -# * Input: As you type, IRB reads the typed characters and highlights elements -# that it recognizes; it also highlights errors such as mismatched -# parentheses. -# * Output: IRB highlights syntactical elements. -# -# -# You can disable color highlighting by: -# -# * Adding to the configuration file: `IRB.conf[:USE_COLORIZE] = false` (the -# default value is `true`). -# * Giving command-line option `--nocolorize` -# -# -# ## Debugging -# -# Command-line option `-d` sets variables `$VERBOSE` and `$DEBUG` to `true`; -# these have no effect on IRB output. -# -# ### Warnings -# -# Command-line option `-w` suppresses warnings. -# -# Command-line option `-W[*level*]` sets warning level; -# -# * 0=silence -# * 1=medium -# * 2=verbose -# -# ## Other Features -# -# ### Load Modules -# -# You can specify the names of modules that are to be required at startup. -# -# Array `conf.load_modules` determines the modules (if any) that are to be -# required during session startup. The array is used only during session -# startup, so the initial value is the only one that counts. -# -# The default initial value is `[]` (load no modules): -# -# irb(main):001> conf.load_modules -# => [] -# -# You can set the default initial value via: -# -# * Command-line option `-r` -# -# $ irb -r csv -r json -# irb(main):001> conf.load_modules -# => ["csv", "json"] -# -# * Hash entry `IRB.conf[:LOAD_MODULES] = *array*`: -# -# IRB.conf[:LOAD_MODULES] = %w[csv, json] -# -# -# Note that the configuration file entry overrides the command-line options. -# -# ### RI Documentation Directories -# -# You can specify the paths to RI documentation directories that are to be -# loaded (in addition to the default directories) at startup; see details about -# RI by typing `ri --help`. -# -# Array `conf.extra_doc_dirs` determines the directories (if any) that are to be -# loaded during session startup. The array is used only during session startup, -# so the initial value is the only one that counts. -# -# The default initial value is `[]` (load no extra documentation): -# -# irb(main):001> conf.extra_doc_dirs -# => [] -# -# You can set the default initial value via: -# -# * Command-line option `--extra_doc_dir` -# -# $ irb --extra-doc-dir your_doc_dir --extra-doc-dir my_doc_dir -# irb(main):001> conf.extra_doc_dirs -# => ["your_doc_dir", "my_doc_dir"] -# -# * Hash entry `IRB.conf[:EXTRA_DOC_DIRS] = *array*`: -# -# IRB.conf[:EXTRA_DOC_DIRS] = %w[your_doc_dir my_doc_dir] -# -# -# Note that the configuration file entry overrides the command-line options. -# -# ### IRB Name -# -# You can specify a name for IRB. -# -# The default initial value is `'irb'`: -# -# irb(main):001> conf.irb_name -# => "irb" -# -# You can set the default initial value via hash entry `IRB.conf[:IRB_NAME] = -# *string*`: -# -# IRB.conf[:IRB_NAME] = 'foo' -# -# ### Application Name -# -# You can specify an application name for the IRB session. -# -# The default initial value is `'irb'`: -# -# irb(main):001> conf.ap_name -# => "irb" -# -# You can set the default initial value via hash entry `IRB.conf[:AP_NAME] = -# *string*`: -# -# IRB.conf[:AP_NAME] = 'my_ap_name' -# -# ### Configuration Monitor -# -# You can monitor changes to the configuration by assigning a proc to -# `IRB.conf[:IRB_RC]` in the configuration file: -# -# IRB.conf[:IRB_RC] = proc {|conf| puts conf.class } -# -# Each time the configuration is changed, that proc is called with argument -# `conf`: -# -# ### Encodings -# -# Command-line option `-E *ex*[:*in*]` sets initial external (ex) and internal -# (in) encodings. -# -# Command-line option `-U` sets both to UTF-8. -# -# ### Commands -# -# Please use the `help` command to see the list of available commands. -# -# ### IRB Sessions -# -# IRB has a special feature, that allows you to manage many sessions at once. -# -# You can create new sessions with Irb.irb, and get a list of current sessions -# with the `jobs` command in the prompt. -# -# #### Configuration -# -# The command line options, or IRB.conf, specify the default behavior of -# Irb.irb. -# -# On the other hand, each conf in IRB@Command-Line+Options is used to -# individually configure IRB.irb. -# -# If a proc is set for `IRB.conf[:IRB_RC]`, its will be invoked after execution -# of that proc with the context of the current session as its argument. Each -# session can be configured using this mechanism. -# -# #### Session variables -# -# There are a few variables in every Irb session that can come in handy: -# -# `_` -# : The value command executed, as a local variable -# `__` -# : The history of evaluated commands. Available only if -# `IRB.conf[:EVAL_HISTORY]` is not `nil` (which is the default). See also -# IRB::Context#eval_history= and IRB::History. -# `__[line_no]` -# : Returns the evaluation value at the given line number, `line_no`. If -# `line_no` is a negative, the return value `line_no` many lines before the -# most recent return value. -# -# -# ## Restrictions -# -# Ruby code typed into IRB behaves the same as Ruby code in a file, except that: -# -# * Because IRB evaluates input immediately after it is syntactically -# complete, some results may be slightly different. -# * Forking may not be well behaved. -# -module IRB - - # An exception raised by IRB.irb_abort - class Abort < Exception;end - - class << self - # The current IRB::Context of the session, see IRB.conf - # - # irb - # irb(main):001:0> IRB.CurrentContext.irb_name = "foo" - # foo(main):002:0> IRB.conf[:MAIN_CONTEXT].irb_name #=> "foo" - def CurrentContext # :nodoc: - conf[:MAIN_CONTEXT] - end - - # Initializes IRB and creates a new Irb.irb object at the `TOPLEVEL_BINDING` - def start(ap_path = nil) - STDOUT.sync = true - $0 = File::basename(ap_path, ".rb") if ap_path - - setup(ap_path) - - if @CONF[:SCRIPT] - irb = Irb.new(nil, @CONF[:SCRIPT]) - else - irb = Irb.new - end - irb.run(@CONF) - end - - # Quits irb - def irb_exit(*) # :nodoc: - throw :IRB_EXIT, false - end - - # Aborts then interrupts irb. - # - # Will raise an Abort exception, or the given `exception`. - def irb_abort(irb, exception = Abort) # :nodoc: - irb.context.thread.raise exception, "abort then interrupt!" - end - end - - class Irb - # Note: instance and index assignment expressions could also be written like: - # "foo.bar=(1)" and "foo.[]=(1, bar)", when expressed that way, the former be - # parsed as :assign and echo will be suppressed, but the latter is parsed as a - # :method_add_arg and the output won't be suppressed - - PROMPT_MAIN_TRUNCATE_LENGTH = 32 - PROMPT_MAIN_TRUNCATE_OMISSION = '...' - CONTROL_CHARACTERS_PATTERN = "\x00-\x1F" - - # Returns the current context of this irb session - attr_reader :context - # The lexer used by this irb session - attr_accessor :scanner - - attr_reader :from_binding - - # Creates a new irb session - def initialize(workspace = nil, input_method = nil, from_binding: false) - @from_binding = from_binding - @context = Context.new(self, workspace, input_method) - @context.workspace.load_helper_methods_to_main - @signal_status = :IN_IRB - @scanner = RubyLex.new - @line_no = 1 - end - - # A hook point for `debug` command's breakpoint after :IRB_EXIT as well as its - # clean-up - def debug_break - # it means the debug integration has been activated - if defined?(DEBUGGER__) && DEBUGGER__.respond_to?(:capture_frames_without_irb) - # after leaving this initial breakpoint, revert the capture_frames patch - DEBUGGER__.singleton_class.send(:alias_method, :capture_frames, :capture_frames_without_irb) - # and remove the redundant method - DEBUGGER__.singleton_class.send(:undef_method, :capture_frames_without_irb) - end - end - - def debug_readline(binding) - workspace = IRB::WorkSpace.new(binding) - context.replace_workspace(workspace) - context.workspace.load_helper_methods_to_main - @line_no += 1 - - # When users run: - # 1. Debugging commands, like `step 2` - # 2. Any input that's not irb-command, like `foo = 123` - # - # - # Irb#eval_input will simply return the input, and we need to pass it to the - # debugger. - input = nil - forced_exit = catch(:IRB_EXIT) do - if History.save_history? && context.io.support_history_saving? - # Previous IRB session's history has been saved when `Irb#run` is exited We need - # to make sure the saved history is not saved again by resetting the counter - context.io.reset_history_counter - - begin - input = eval_input - ensure - context.io.save_history - end - else - input = eval_input - end - false - end - - Kernel.exit if forced_exit - - if input&.include?("\n") - @line_no += input.count("\n") - 1 - end - - input - end - - def run(conf = IRB.conf) - in_nested_session = !!conf[:MAIN_CONTEXT] - conf[:IRB_RC].call(context) if conf[:IRB_RC] - prev_context = conf[:MAIN_CONTEXT] - conf[:MAIN_CONTEXT] = context - - load_history = !in_nested_session && context.io.support_history_saving? - save_history = load_history && History.save_history? - - if load_history - context.io.load_history - end - - prev_trap = trap("SIGINT") do - signal_handle - end - - begin - if defined?(RubyVM.keep_script_lines) - keep_script_lines_backup = RubyVM.keep_script_lines - RubyVM.keep_script_lines = true - end - - forced_exit = catch(:IRB_EXIT) do - eval_input - end - ensure - # Do not restore to nil. It will cause IRB crash when used with threads. - IRB.conf[:MAIN_CONTEXT] = prev_context if prev_context - - RubyVM.keep_script_lines = keep_script_lines_backup if defined?(RubyVM.keep_script_lines) - trap("SIGINT", prev_trap) - conf[:AT_EXIT].each{|hook| hook.call} - - context.io.save_history if save_history - Kernel.exit if forced_exit - end - end - - # Evaluates input for this session. - def eval_input - configure_io - - each_top_level_statement do |statement, line_no| - signal_status(:IN_EVAL) do - begin - # If the integration with debugger is activated, we return certain input if it - # should be dealt with by debugger - if @context.with_debugger && statement.should_be_handled_by_debugger? - return statement.code - end - - @context.evaluate(statement, line_no) - - if @context.echo? && !statement.suppresses_echo? - if statement.is_assignment? - if @context.echo_on_assignment? - output_value(@context.echo_on_assignment? == :truncate) - end - else - output_value - end - end - rescue SystemExit, SignalException - raise - rescue Interrupt, Exception => exc - handle_exception(exc) - @context.workspace.local_variable_set(:_, exc) - end - end - end - end - - def read_input(prompt) - signal_status(:IN_INPUT) do - @context.io.prompt = prompt - if l = @context.io.gets - print l if @context.verbose? - else - if @context.ignore_eof? and @context.io.readable_after_eof? - l = "\n" - if @context.verbose? - printf "Use \"exit\" to leave %s\n", @context.ap_name - end - else - print "\n" if @context.prompting? - end - end - l - end - end - - def readmultiline - prompt = generate_prompt([], false, 0) - - # multiline - return read_input(prompt) if @context.io.respond_to?(:check_termination) - - # nomultiline - code = +'' - line_offset = 0 - loop do - line = read_input(prompt) - unless line - return code.empty? ? nil : code - end - - code << line - return code if command?(code) - - tokens, opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) - return code if terminated - - line_offset += 1 - continue = @scanner.should_continue?(tokens) - prompt = generate_prompt(opens, continue, line_offset) - end - end - - def each_top_level_statement - loop do - code = readmultiline - break unless code - yield build_statement(code), @line_no - @line_no += code.count("\n") - rescue RubyLex::TerminateLineInput - end - end - - def build_statement(code) - if code.match?(/\A\n*\z/) - return Statement::EmptyInput.new - end - - code = code.dup.force_encoding(@context.io.encoding) - if (command, arg = @context.parse_command(code)) - command_class = Command.load_command(command) - Statement::Command.new(code, command_class, arg) - else - is_assignment_expression = @scanner.assignment_expression?(code, local_variables: @context.local_variables) - Statement::Expression.new(code, is_assignment_expression) - end - end - - def command?(code) - !!@context.parse_command(code) - end - - def configure_io - if @context.io.respond_to?(:check_termination) - @context.io.check_termination do |code| - if Reline::IOGate.in_pasting? - rest = @scanner.check_termination_in_prev_line(code, local_variables: @context.local_variables) - if rest - Reline.delete_text - rest.bytes.reverse_each do |c| - Reline.ungetc(c) - end - true - else - false - end - else - next true if command?(code) - - _tokens, _opens, terminated = @scanner.check_code_state(code, local_variables: @context.local_variables) - terminated - end - end - end - if @context.io.respond_to?(:dynamic_prompt) - @context.io.dynamic_prompt do |lines| - tokens = RubyLex.ripper_lex_without_warning(lines.map{ |l| l + "\n" }.join, local_variables: @context.local_variables) - line_results = IRB::NestingParser.parse_by_line(tokens) - tokens_until_line = [] - line_results.map.with_index do |(line_tokens, _prev_opens, next_opens, _min_depth), line_num_offset| - line_tokens.each do |token, _s| - # Avoid appending duplicated token. Tokens that include "n" like multiline - # tstring_content can exist in multiple lines. - tokens_until_line << token if token != tokens_until_line.last - end - continue = @scanner.should_continue?(tokens_until_line) - generate_prompt(next_opens, continue, line_num_offset) - end - end - end - - if @context.io.respond_to?(:auto_indent) and @context.auto_indent_mode - @context.io.auto_indent do |lines, line_index, byte_pointer, is_newline| - next nil if lines == [nil] # Workaround for exit IRB with CTRL+d - next nil if !is_newline && lines[line_index]&.byteslice(0, byte_pointer)&.match?(/\A\s*\z/) - - code = lines[0..line_index].map { |l| "#{l}\n" }.join - tokens = RubyLex.ripper_lex_without_warning(code, local_variables: @context.local_variables) - @scanner.process_indent_level(tokens, lines, line_index, is_newline) - end - end - end - - def convert_invalid_byte_sequence(str, enc) - str.force_encoding(enc) - str.scrub { |c| - c.bytes.map{ |b| "\\x#{b.to_s(16).upcase}" }.join - } - end - - def encode_with_invalid_byte_sequence(str, enc) - conv = Encoding::Converter.new(str.encoding, enc) - dst = String.new - begin - ret = conv.primitive_convert(str, dst) - case ret - when :invalid_byte_sequence - conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) - redo - when :undefined_conversion - c = conv.primitive_errinfo[3].dup.force_encoding(conv.primitive_errinfo[1]) - conv.insert_output(c.dump[1..-2]) - redo - when :incomplete_input - conv.insert_output(conv.primitive_errinfo[3].dump[1..-2]) - when :finished - end - break - end while nil - dst - end - - def handle_exception(exc) - if exc.backtrace[0] =~ /\/irb(2)?(\/.*|-.*|\.rb)?:/ && exc.class.to_s !~ /^IRB/ && - !(SyntaxError === exc) && !(EncodingError === exc) - # The backtrace of invalid encoding hash (ex. {"\xAE": 1}) raises EncodingError without lineno. - irb_bug = true - else - irb_bug = false - # To support backtrace filtering while utilizing Exception#full_message, we need to clone - # the exception to avoid modifying the original exception's backtrace. - exc = exc.clone - filtered_backtrace = exc.backtrace.map { |l| @context.workspace.filter_backtrace(l) }.compact - backtrace_filter = IRB.conf[:BACKTRACE_FILTER] - - if backtrace_filter - if backtrace_filter.respond_to?(:call) - filtered_backtrace = backtrace_filter.call(filtered_backtrace) - else - warn "IRB.conf[:BACKTRACE_FILTER] #{backtrace_filter} should respond to `call` method" - end - end - - exc.set_backtrace(filtered_backtrace) - end - - highlight = Color.colorable? - - order = - if RUBY_VERSION < '3.0.0' - STDOUT.tty? ? :bottom : :top - else # '3.0.0' <= RUBY_VERSION - :top - end - - message = exc.full_message(order: order, highlight: highlight) - message = convert_invalid_byte_sequence(message, exc.message.encoding) - message = encode_with_invalid_byte_sequence(message, IRB.conf[:LC_MESSAGES].encoding) unless message.encoding.to_s.casecmp?(IRB.conf[:LC_MESSAGES].encoding.to_s) - message = message.gsub(/((?:^\t.+$\n)+)/) { |m| - case order - when :top - lines = m.split("\n") - when :bottom - lines = m.split("\n").reverse - end - unless irb_bug - if lines.size > @context.back_trace_limit - omit = lines.size - @context.back_trace_limit - lines = lines[0..(@context.back_trace_limit - 1)] - lines << "\t... %d levels..." % omit - end - end - lines = lines.reverse if order == :bottom - lines.map{ |l| l + "\n" }.join - } - # The "<top (required)>" in "(irb)" may be the top level of IRB so imitate the main object. - message = message.gsub(/\(irb\):(?<num>\d+):in (?<open_quote>[`'])<(?<frame>top \(required\))>'/) { "(irb):#{$~[:num]}:in #{$~[:open_quote]}<main>'" } - puts message - puts 'Maybe IRB bug!' if irb_bug - rescue Exception => handler_exc - begin - puts exc.inspect - puts "backtraces are hidden because #{handler_exc} was raised when processing them" - rescue Exception - puts 'Uninspectable exception occurred' - end - end - - # Evaluates the given block using the given `path` as the Context#irb_path and - # `name` as the Context#irb_name. - # - # Used by the irb command `source`, see IRB@IRB+Sessions for more information. - def suspend_name(path = nil, name = nil) - @context.irb_path, back_path = path, @context.irb_path if path - @context.irb_name, back_name = name, @context.irb_name if name - begin - yield back_path, back_name - ensure - @context.irb_path = back_path if path - @context.irb_name = back_name if name - end - end - - # Evaluates the given block using the given `workspace` as the - # Context#workspace. - # - # Used by the irb command `irb_load`, see IRB@IRB+Sessions for more information. - def suspend_workspace(workspace) - current_workspace = @context.workspace - @context.replace_workspace(workspace) - yield - ensure - @context.replace_workspace current_workspace - end - - # Evaluates the given block using the given `input_method` as the Context#io. - # - # Used by the irb commands `source` and `irb_load`, see IRB@IRB+Sessions for - # more information. - def suspend_input_method(input_method) - back_io = @context.io - @context.instance_eval{@io = input_method} - begin - yield back_io - ensure - @context.instance_eval{@io = back_io} - end - end - - # Handler for the signal SIGINT, see Kernel#trap for more information. - def signal_handle - unless @context.ignore_sigint? - print "\nabort!\n" if @context.verbose? - exit - end - - case @signal_status - when :IN_INPUT - print "^C\n" - raise RubyLex::TerminateLineInput - when :IN_EVAL - IRB.irb_abort(self) - when :IN_LOAD - IRB.irb_abort(self, LoadAbort) - when :IN_IRB - # ignore - else - # ignore other cases as well - end - end - - # Evaluates the given block using the given `status`. - def signal_status(status) - return yield if @signal_status == :IN_LOAD - - signal_status_back = @signal_status - @signal_status = status - begin - yield - ensure - @signal_status = signal_status_back - end - end - - def output_value(omit = false) # :nodoc: - str = @context.inspect_last_value - multiline_p = str.include?("\n") - if omit - winwidth = @context.io.winsize.last - if multiline_p - first_line = str.split("\n").first - result = @context.newline_before_multiline_output? ? (@context.return_format % first_line) : first_line - output_width = Reline::Unicode.calculate_width(result, true) - diff_size = output_width - Reline::Unicode.calculate_width(first_line, true) - if diff_size.positive? and output_width > winwidth - lines, _ = Reline::Unicode.split_by_width(first_line, winwidth - diff_size - 3) - str = "%s..." % lines.first - str += "\e[0m" if Color.colorable? - multiline_p = false - else - str = str.gsub(/(\A.*?\n).*/m, "\\1...") - str += "\e[0m" if Color.colorable? - end - else - output_width = Reline::Unicode.calculate_width(@context.return_format % str, true) - diff_size = output_width - Reline::Unicode.calculate_width(str, true) - if diff_size.positive? and output_width > winwidth - lines, _ = Reline::Unicode.split_by_width(str, winwidth - diff_size - 3) - str = "%s..." % lines.first - str += "\e[0m" if Color.colorable? - end - end - end - - if multiline_p && @context.newline_before_multiline_output? - str = "\n" + str - end - - Pager.page_content(format(@context.return_format, str), retain_content: true) - end - - # Outputs the local variables to this current session, including #signal_status - # and #context, using IRB::Locale. - def inspect - ary = [] - for iv in instance_variables - case (iv = iv.to_s) - when "@signal_status" - ary.push format("%s=:%s", iv, @signal_status.id2name) - when "@context" - ary.push format("%s=%s", iv, eval(iv).__to_s__) - else - ary.push format("%s=%s", iv, eval(iv)) - end - end - format("#<%s: %s>", self.class, ary.join(", ")) - end - - private - - def generate_prompt(opens, continue, line_offset) - ltype = @scanner.ltype_from_open_tokens(opens) - indent = @scanner.calc_indent_level(opens) - continue = opens.any? || continue - line_no = @line_no + line_offset - - if ltype - f = @context.prompt_s - elsif continue - f = @context.prompt_c - else - f = @context.prompt_i - end - f = "" unless f - if @context.prompting? - p = format_prompt(f, ltype, indent, line_no) - else - p = "" - end - if @context.auto_indent_mode and !@context.io.respond_to?(:auto_indent) - unless ltype - prompt_i = @context.prompt_i.nil? ? "" : @context.prompt_i - ind = format_prompt(prompt_i, ltype, indent, line_no)[/.*\z/].size + - indent * 2 - p.size - p += " " * ind if ind > 0 - end - end - p - end - - def truncate_prompt_main(str) # :nodoc: - str = str.tr(CONTROL_CHARACTERS_PATTERN, ' ') - if str.size <= PROMPT_MAIN_TRUNCATE_LENGTH - str - else - str[0, PROMPT_MAIN_TRUNCATE_LENGTH - PROMPT_MAIN_TRUNCATE_OMISSION.size] + PROMPT_MAIN_TRUNCATE_OMISSION - end - end - - def format_prompt(format, ltype, indent, line_no) # :nodoc: - format.gsub(/%([0-9]+)?([a-zA-Z%])/) do - case $2 - when "N" - @context.irb_name - when "m" - main_str = @context.main.to_s rescue "!#{$!.class}" - truncate_prompt_main(main_str) - when "M" - main_str = @context.main.inspect rescue "!#{$!.class}" - truncate_prompt_main(main_str) - when "l" - ltype - when "i" - if indent < 0 - if $1 - "-".rjust($1.to_i) - else - "-" - end - else - if $1 - format("%" + $1 + "d", indent) - else - indent.to_s - end - end - when "n" - if $1 - format("%" + $1 + "d", line_no) - else - line_no.to_s - end - when "%" - "%" unless $1 - end - end - end - end -end - -class Binding - # Opens an IRB session where `binding.irb` is called which allows for - # interactive debugging. You can call any methods or variables available in the - # current scope, and mutate state if you need to. - # - # Given a Ruby file called `potato.rb` containing the following code: - # - # class Potato - # def initialize - # @cooked = false - # binding.irb - # puts "Cooked potato: #{@cooked}" - # end - # end - # - # Potato.new - # - # Running `ruby potato.rb` will open an IRB session where `binding.irb` is - # called, and you will see the following: - # - # $ ruby potato.rb - # - # From: potato.rb @ line 4 : - # - # 1: class Potato - # 2: def initialize - # 3: @cooked = false - # => 4: binding.irb - # 5: puts "Cooked potato: #{@cooked}" - # 6: end - # 7: end - # 8: - # 9: Potato.new - # - # irb(#<Potato:0x00007feea1916670>):001:0> - # - # You can type any valid Ruby code and it will be evaluated in the current - # context. This allows you to debug without having to run your code repeatedly: - # - # irb(#<Potato:0x00007feea1916670>):001:0> @cooked - # => false - # irb(#<Potato:0x00007feea1916670>):002:0> self.class - # => Potato - # irb(#<Potato:0x00007feea1916670>):003:0> caller.first - # => ".../2.5.1/lib/ruby/2.5.0/irb/workspace.rb:85:in `eval'" - # irb(#<Potato:0x00007feea1916670>):004:0> @cooked = true - # => true - # - # You can exit the IRB session with the `exit` command. Note that exiting will - # resume execution where `binding.irb` had paused it, as you can see from the - # output printed to standard output in this example: - # - # irb(#<Potato:0x00007feea1916670>):005:0> exit - # Cooked potato: true - # - # See IRB for more information. - def irb(show_code: true) - # Setup IRB with the current file's path and no command line arguments - IRB.setup(source_location[0], argv: []) unless IRB.initialized? - # Create a new workspace using the current binding - workspace = IRB::WorkSpace.new(self) - # Print the code around the binding if show_code is true - STDOUT.print(workspace.code_around_binding) if show_code - # Get the original IRB instance - debugger_irb = IRB.instance_variable_get(:@debugger_irb) - - irb_path = File.expand_path(source_location[0]) - - if debugger_irb - # If we're already in a debugger session, set the workspace and irb_path for the original IRB instance - debugger_irb.context.replace_workspace(workspace) - debugger_irb.context.irb_path = irb_path - # If we've started a debugger session and hit another binding.irb, we don't want - # to start an IRB session instead, we want to resume the irb:rdbg session. - IRB::Debug.setup(debugger_irb) - IRB::Debug.insert_debug_break - debugger_irb.debug_break - else - # If we're not in a debugger session, create a new IRB instance with the current - # workspace - binding_irb = IRB::Irb.new(workspace, from_binding: true) - binding_irb.context.irb_path = irb_path - binding_irb.run(IRB.conf) - binding_irb.debug_break - end - end -end diff --git a/lib/irb/.document b/lib/irb/.document deleted file mode 100644 index 3b0d6fa4ed..0000000000 --- a/lib/irb/.document +++ /dev/null @@ -1 +0,0 @@ -**/*.rb diff --git a/lib/irb/cmd/nop.rb b/lib/irb/cmd/nop.rb deleted file mode 100644 index 9d2e3c4d47..0000000000 --- a/lib/irb/cmd/nop.rb +++ /dev/null @@ -1,4 +0,0 @@ -# frozen_string_literal: true - -# This file is just a placeholder for backward-compatibility. -# Please require 'irb' and inherit your command from `IRB::Command::Base` instead. diff --git a/lib/irb/color.rb b/lib/irb/color.rb deleted file mode 100644 index fca942b28b..0000000000 --- a/lib/irb/color.rb +++ /dev/null @@ -1,262 +0,0 @@ -# frozen_string_literal: true -require 'reline' -require 'ripper' -require_relative 'ruby-lex' - -module IRB # :nodoc: - module Color - CLEAR = 0 - BOLD = 1 - UNDERLINE = 4 - REVERSE = 7 - BLACK = 30 - RED = 31 - GREEN = 32 - YELLOW = 33 - BLUE = 34 - MAGENTA = 35 - CYAN = 36 - WHITE = 37 - - TOKEN_KEYWORDS = { - on_kw: ['nil', 'self', 'true', 'false', '__FILE__', '__LINE__', '__ENCODING__'], - on_const: ['ENV'], - } - private_constant :TOKEN_KEYWORDS - - # A constant of all-bit 1 to match any Ripper's state in #dispatch_seq - ALL = -1 - private_constant :ALL - - begin - # Following pry's colors where possible, but sometimes having a compromise like making - # backtick and regexp as red (string's color, because they're sharing tokens). - TOKEN_SEQ_EXPRS = { - on_CHAR: [[BLUE, BOLD], ALL], - on_backtick: [[RED, BOLD], ALL], - on_comment: [[BLUE, BOLD], ALL], - on_const: [[BLUE, BOLD, UNDERLINE], ALL], - on_embexpr_beg: [[RED], ALL], - on_embexpr_end: [[RED], ALL], - on_embvar: [[RED], ALL], - on_float: [[MAGENTA, BOLD], ALL], - on_gvar: [[GREEN, BOLD], ALL], - on_heredoc_beg: [[RED], ALL], - on_heredoc_end: [[RED], ALL], - on_ident: [[BLUE, BOLD], Ripper::EXPR_ENDFN], - on_imaginary: [[BLUE, BOLD], ALL], - on_int: [[BLUE, BOLD], ALL], - on_kw: [[GREEN], ALL], - on_label: [[MAGENTA], ALL], - on_label_end: [[RED, BOLD], ALL], - on_qsymbols_beg: [[RED, BOLD], ALL], - on_qwords_beg: [[RED, BOLD], ALL], - on_rational: [[BLUE, BOLD], ALL], - on_regexp_beg: [[RED, BOLD], ALL], - on_regexp_end: [[RED, BOLD], ALL], - on_symbeg: [[YELLOW], ALL], - on_symbols_beg: [[RED, BOLD], ALL], - on_tstring_beg: [[RED, BOLD], ALL], - on_tstring_content: [[RED], ALL], - on_tstring_end: [[RED, BOLD], ALL], - on_words_beg: [[RED, BOLD], ALL], - on_parse_error: [[RED, REVERSE], ALL], - compile_error: [[RED, REVERSE], ALL], - on_assign_error: [[RED, REVERSE], ALL], - on_alias_error: [[RED, REVERSE], ALL], - on_class_name_error:[[RED, REVERSE], ALL], - on_param_error: [[RED, REVERSE], ALL], - on___end__: [[GREEN], ALL], - } - rescue NameError - # Give up highlighting Ripper-incompatible older Ruby - TOKEN_SEQ_EXPRS = {} - end - private_constant :TOKEN_SEQ_EXPRS - - ERROR_TOKENS = TOKEN_SEQ_EXPRS.keys.select { |k| k.to_s.end_with?('error') } - private_constant :ERROR_TOKENS - - class << self - def colorable? - supported = $stdout.tty? && (/mswin|mingw/.match?(RUBY_PLATFORM) || (ENV.key?('TERM') && ENV['TERM'] != 'dumb')) - - # because ruby/debug also uses irb's color module selectively, - # irb won't be activated in that case. - if IRB.respond_to?(:conf) - supported && !!IRB.conf.fetch(:USE_COLORIZE, true) - else - supported - end - end - - def inspect_colorable?(obj, seen: {}.compare_by_identity) - case obj - when String, Symbol, Regexp, Integer, Float, FalseClass, TrueClass, NilClass - true - when Hash - without_circular_ref(obj, seen: seen) do - obj.all? { |k, v| inspect_colorable?(k, seen: seen) && inspect_colorable?(v, seen: seen) } - end - when Array - without_circular_ref(obj, seen: seen) do - obj.all? { |o| inspect_colorable?(o, seen: seen) } - end - when Range - inspect_colorable?(obj.begin, seen: seen) && inspect_colorable?(obj.end, seen: seen) - when Module - !obj.name.nil? - else - false - end - end - - def clear(colorable: colorable?) - return '' unless colorable - "\e[#{CLEAR}m" - end - - def colorize(text, seq, colorable: colorable?) - return text unless colorable - seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('') - "#{seq}#{text}#{clear(colorable: colorable)}" - end - - # If `complete` is false (code is incomplete), this does not warn compile_error. - # This option is needed to avoid warning a user when the compile_error is happening - # because the input is not wrong but just incomplete. - def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?, local_variables: []) - return code unless colorable - - symbol_state = SymbolState.new - colored = +'' - lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) - code_with_lvars = lvars_code ? "#{lvars_code}\n#{code}" : code - - scan(code_with_lvars, allow_last_error: !complete) do |token, str, expr| - # handle uncolorable code - if token.nil? - colored << Reline::Unicode.escape_for_print(str) - next - end - - # IRB::ColorPrinter skips colorizing fragments with any invalid token - if ignore_error && ERROR_TOKENS.include?(token) - return Reline::Unicode.escape_for_print(code) - end - - in_symbol = symbol_state.scan_token(token) - str.each_line do |line| - line = Reline::Unicode.escape_for_print(line) - if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol) - colored << seq.map { |s| "\e[#{s}m" }.join('') - colored << line.sub(/\Z/, clear(colorable: colorable)) - else - colored << line - end - end - end - - if lvars_code - raise "#{lvars_code.dump} should have no \\n" if lvars_code.include?("\n") - colored.sub!(/\A.+\n/, '') # delete_prefix lvars_code with colors - end - colored - end - - private - - def without_circular_ref(obj, seen:, &block) - return false if seen.key?(obj) - seen[obj] = true - block.call - ensure - seen.delete(obj) - end - - def scan(code, allow_last_error:) - verbose, $VERBOSE = $VERBOSE, nil - RubyLex.compile_with_errors_suppressed(code) do |inner_code, line_no| - lexer = Ripper::Lexer.new(inner_code, '(ripper)', line_no) - byte_pos = 0 - line_positions = [0] - inner_code.lines.each do |line| - line_positions << line_positions.last + line.bytesize - end - - on_scan = proc do |elem| - start_pos = line_positions[elem.pos[0] - 1] + elem.pos[1] - - # yield uncolorable code - if byte_pos < start_pos - yield(nil, inner_code.byteslice(byte_pos...start_pos), nil) - end - - if byte_pos <= start_pos - str = elem.tok - yield(elem.event, str, elem.state) - byte_pos = start_pos + str.bytesize - end - end - - lexer.scan.each do |elem| - next if allow_last_error and /meets end of file|unexpected end-of-input/ =~ elem.message - on_scan.call(elem) - end - # yield uncolorable DATA section - yield(nil, inner_code.byteslice(byte_pos...inner_code.bytesize), nil) if byte_pos < inner_code.bytesize - end - ensure - $VERBOSE = verbose - end - - def dispatch_seq(token, expr, str, in_symbol:) - if ERROR_TOKENS.include?(token) - TOKEN_SEQ_EXPRS[token][0] - elsif in_symbol - [YELLOW] - elsif TOKEN_KEYWORDS.fetch(token, []).include?(str) - [CYAN, BOLD] - elsif (seq, exprs = TOKEN_SEQ_EXPRS[token]; (expr & (exprs || 0)) != 0) - seq - else - nil - end - end - end - - # A class to manage a state to know whether the current token is for Symbol or not. - class SymbolState - def initialize - # Push `true` to detect Symbol. `false` to increase the nest level for non-Symbol. - @stack = [] - end - - # Return true if the token is a part of Symbol. - def scan_token(token) - prev_state = @stack.last - case token - when :on_symbeg, :on_symbols_beg, :on_qsymbols_beg - @stack << true - when :on_ident, :on_op, :on_const, :on_ivar, :on_cvar, :on_gvar, :on_kw, :on_backtick - if @stack.last # Pop only when it's Symbol - @stack.pop - return prev_state - end - when :on_tstring_beg - @stack << false - when :on_embexpr_beg - @stack << false - return prev_state - when :on_tstring_end # :on_tstring_end may close Symbol - @stack.pop - return prev_state - when :on_embexpr_end - @stack.pop - end - @stack.last - end - end - private_constant :SymbolState - end -end diff --git a/lib/irb/color_printer.rb b/lib/irb/color_printer.rb deleted file mode 100644 index 31644aa7f9..0000000000 --- a/lib/irb/color_printer.rb +++ /dev/null @@ -1,55 +0,0 @@ -# frozen_string_literal: true -require 'pp' -require_relative 'color' - -module IRB - class ColorPrinter < ::PP - METHOD_RESPOND_TO = Object.instance_method(:respond_to?) - METHOD_INSPECT = Object.instance_method(:inspect) - - class << self - def pp(obj, out = $>, width = screen_width) - q = ColorPrinter.new(out, width) - q.guard_inspect_key {q.pp obj} - q.flush - out << "\n" - end - - private - - def screen_width - Reline.get_screen_size.last - rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN> - 79 - end - end - - def pp(obj) - if String === obj - # Avoid calling Ruby 2.4+ String#pretty_print that splits a string by "\n" - text(obj.inspect) - elsif !METHOD_RESPOND_TO.bind(obj).call(:inspect) - text(METHOD_INSPECT.bind(obj).call) - else - super - end - end - - def text(str, width = nil) - unless str.is_a?(String) - str = str.inspect - end - width ||= str.length - - case str - when '' - when ',', '=>', '[', ']', '{', '}', '..', '...', /\A@\w+\z/ - super(str, width) - when /\A#</, '=', '>' - super(Color.colorize(str, [:GREEN]), width) - else - super(Color.colorize_code(str, ignore_error: true), width) - end - end - end -end diff --git a/lib/irb/command.rb b/lib/irb/command.rb deleted file mode 100644 index 68a4b52727..0000000000 --- a/lib/irb/command.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true -# -# irb/command.rb - irb command -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative "command/base" - -module IRB # :nodoc: - module Command - @commands = {} - - class << self - attr_reader :commands - - # Registers a command with the given name. - # Aliasing is intentionally not supported at the moment. - def register(name, command_class) - @commands[name.to_sym] = [command_class, []] - end - end - end -end diff --git a/lib/irb/command/backtrace.rb b/lib/irb/command/backtrace.rb deleted file mode 100644 index 687bb075ac..0000000000 --- a/lib/irb/command/backtrace.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Backtrace < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "backtrace #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/base.rb b/lib/irb/command/base.rb deleted file mode 100644 index af810ed343..0000000000 --- a/lib/irb/command/base.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true -# -# nop.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - # :stopdoc: - - module Command - class CommandArgumentError < StandardError; end - - class << self - def extract_ruby_args(*args, **kwargs) - throw :EXTRACT_RUBY_ARGS, [args, kwargs] - end - end - - class Base - class << self - def category(category = nil) - @category = category if category - @category || "No category" - end - - def description(description = nil) - @description = description if description - @description || "No description provided." - end - - def help_message(help_message = nil) - @help_message = help_message if help_message - @help_message - end - - def execute(irb_context, arg) - new(irb_context).execute(arg) - rescue CommandArgumentError => e - puts e.message - end - - private - - def highlight(text) - Color.colorize(text, [:BOLD, :BLUE]) - end - end - - def initialize(irb_context) - @irb_context = irb_context - end - - attr_reader :irb_context - - def execute(arg) - #nop - end - end - - Nop = Base - end - - # :startdoc: -end diff --git a/lib/irb/command/break.rb b/lib/irb/command/break.rb deleted file mode 100644 index a8f81fe665..0000000000 --- a/lib/irb/command/break.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Break < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "break #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/catch.rb b/lib/irb/command/catch.rb deleted file mode 100644 index 529dcbca5a..0000000000 --- a/lib/irb/command/catch.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Catch < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "catch #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/cd.rb b/lib/irb/command/cd.rb deleted file mode 100644 index b83c8689ae..0000000000 --- a/lib/irb/command/cd.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class CD < Base - category "Workspace" - description "Move into the given object or leave the current context." - - help_message(<<~HELP) - Usage: cd ([target]|..) - - IRB uses a stack of workspaces to keep track of context(s), with `pushws` and `popws` commands to manipulate the stack. - The `cd` command is an attempt to simplify the operation and will be subject to change. - - When given: - - an object, cd will use that object as the new context by pushing it onto the workspace stack. - - "..", cd will leave the current context by popping the top workspace off the stack. - - no arguments, cd will move to the top workspace on the stack by popping off all workspaces. - - Examples: - - cd Foo - cd Foo.new - cd @ivar - cd .. - cd - HELP - - def execute(arg) - case arg - when ".." - irb_context.pop_workspace - when "" - # TODO: decide what workspace commands should be kept, and underlying APIs should look like, - # and perhaps add a new API to clear the workspace stack. - prev_workspace = irb_context.pop_workspace - while prev_workspace - prev_workspace = irb_context.pop_workspace - end - else - begin - obj = eval(arg, irb_context.workspace.binding) - irb_context.push_workspace(obj) - rescue StandardError => e - warn "Error: #{e}" - end - end - end - end - end -end diff --git a/lib/irb/command/chws.rb b/lib/irb/command/chws.rb deleted file mode 100644 index ef456d0961..0000000000 --- a/lib/irb/command/chws.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true -# -# change-ws.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# -require_relative "../ext/change-ws" - -module IRB - # :stopdoc: - - module Command - - class CurrentWorkingWorkspace < Base - category "Workspace" - description "Show the current workspace." - - def execute(_arg) - puts "Current workspace: #{irb_context.main}" - end - end - - class ChangeWorkspace < Base - category "Workspace" - description "Change the current workspace to an object." - - def execute(arg) - if arg.empty? - irb_context.change_workspace - else - obj = eval(arg, irb_context.workspace.binding) - irb_context.change_workspace(obj) - end - - puts "Current workspace: #{irb_context.main}" - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/context.rb b/lib/irb/command/context.rb deleted file mode 100644 index b4fc807343..0000000000 --- a/lib/irb/command/context.rb +++ /dev/null @@ -1,16 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class Context < Base - category "IRB" - description "Displays current configuration." - - def execute(_arg) - # This command just displays the configuration. - # Modifying the configuration is achieved by sending a message to IRB.conf. - Pager.page_content(IRB.CurrentContext.inspect) - end - end - end -end diff --git a/lib/irb/command/continue.rb b/lib/irb/command/continue.rb deleted file mode 100644 index 0daa029b15..0000000000 --- a/lib/irb/command/continue.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Continue < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "continue #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/debug.rb b/lib/irb/command/debug.rb deleted file mode 100644 index 3ebb57fe54..0000000000 --- a/lib/irb/command/debug.rb +++ /dev/null @@ -1,73 +0,0 @@ -require_relative "../debug" - -module IRB - # :stopdoc: - - module Command - class Debug < Base - category "Debugging" - description "Start the debugger of debug.gem." - - def execute(_arg) - execute_debug_command - end - - def execute_debug_command(pre_cmds: nil, do_cmds: nil) - pre_cmds = pre_cmds&.rstrip - do_cmds = do_cmds&.rstrip - - if irb_context.with_debugger - # If IRB is already running with a debug session, throw the command and IRB.debug_readline will pass it to the debugger. - if cmd = pre_cmds || do_cmds - throw :IRB_EXIT, cmd - else - puts "IRB is already running with a debug session." - return - end - else - # If IRB is not running with a debug session yet, then: - # 1. Check if the debugging command is run from a `binding.irb` call. - # 2. If so, try setting up the debug gem. - # 3. Insert a debug breakpoint at `Irb#debug_break` with the intended command. - # 4. Exit the current Irb#run call via `throw :IRB_EXIT`. - # 5. `Irb#debug_break` will be called and trigger the breakpoint, which will run the intended command. - unless irb_context.from_binding? - puts "Debugging commands are only available when IRB is started with binding.irb" - return - end - - if IRB.respond_to?(:JobManager) - warn "Can't start the debugger when IRB is running in a multi-IRB session." - return - end - - unless IRB::Debug.setup(irb_context.irb) - puts <<~MSG - You need to install the debug gem before using this command. - If you use `bundle exec`, please add `gem "debug"` into your Gemfile. - MSG - return - end - - IRB::Debug.insert_debug_break(pre_cmds: pre_cmds, do_cmds: do_cmds) - - # exit current Irb#run call - throw :IRB_EXIT - end - end - end - - class DebugCommand < Debug - class << self - def category - "Debugging" - end - - def description - command_name = self.name.split("::").last.downcase - "Start the debugger of debug.gem and run its `#{command_name}` command." - end - end - end - end -end diff --git a/lib/irb/command/delete.rb b/lib/irb/command/delete.rb deleted file mode 100644 index 2a57a4a3de..0000000000 --- a/lib/irb/command/delete.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Delete < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "delete #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/disable_irb.rb b/lib/irb/command/disable_irb.rb deleted file mode 100644 index 0b00d0302b..0000000000 --- a/lib/irb/command/disable_irb.rb +++ /dev/null @@ -1,19 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class DisableIrb < Base - category "IRB" - description "Disable binding.irb." - - def execute(*) - ::Binding.define_method(:irb) {} - IRB.irb_exit - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/edit.rb b/lib/irb/command/edit.rb deleted file mode 100644 index cb7e0c4873..0000000000 --- a/lib/irb/command/edit.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'shellwords' - -require_relative "../color" -require_relative "../source_finder" - -module IRB - # :stopdoc: - - module Command - class Edit < Base - include RubyArgsExtractor - - category "Misc" - description 'Open a file or source location.' - help_message <<~HELP_MESSAGE - Usage: edit [FILE or constant or method signature] - - Open a file in the editor specified in #{highlight('ENV["VISUAL"]')} or #{highlight('ENV["EDITOR"]')} - - - If no arguments are provided, IRB will attempt to open the file the current context was defined in. - - If FILE is provided, IRB will open the file. - - If a constant or method signature is provided, IRB will attempt to locate the source file and open it. - - Examples: - - edit - edit foo.rb - edit Foo - edit Foo#bar - HELP_MESSAGE - - def execute(arg) - # Accept string literal for backward compatibility - path = unwrap_string_literal(arg) - - if path.nil? - path = @irb_context.irb_path - elsif !File.exist?(path) - source = SourceFinder.new(@irb_context).find_source(path) - - if source&.file_exist? && !source.binary_file? - path = source.file - end - end - - unless File.exist?(path) - puts "Can not find file: #{path}" - return - end - - if editor = (ENV['VISUAL'] || ENV['EDITOR']) - puts "command: '#{editor}'" - puts " path: #{path}" - system(*Shellwords.split(editor), path) - else - puts "Can not find editor setting: ENV['VISUAL'] or ENV['EDITOR']" - end - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/exit.rb b/lib/irb/command/exit.rb deleted file mode 100644 index b4436f0343..0000000000 --- a/lib/irb/command/exit.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class Exit < Base - category "IRB" - description "Exit the current irb session." - - def execute(_arg) - IRB.irb_exit - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/finish.rb b/lib/irb/command/finish.rb deleted file mode 100644 index 3311a0e6e9..0000000000 --- a/lib/irb/command/finish.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Finish < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "finish #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/force_exit.rb b/lib/irb/command/force_exit.rb deleted file mode 100644 index 14086aa849..0000000000 --- a/lib/irb/command/force_exit.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class ForceExit < Base - category "IRB" - description "Exit the current process." - - def execute(_arg) - throw :IRB_EXIT, true - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/help.rb b/lib/irb/command/help.rb deleted file mode 100644 index 12b468fefc..0000000000 --- a/lib/irb/command/help.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class Help < Base - category "Help" - description "List all available commands. Use `help <command>` to get information about a specific command." - - def execute(command_name) - content = - if command_name.empty? - help_message - else - if command_class = Command.load_command(command_name) - command_class.help_message || command_class.description - else - "Can't find command `#{command_name}`. Please check the command name and try again.\n\n" - end - end - Pager.page_content(content) - end - - private - - def help_message - commands_info = IRB::Command.all_commands_info - helper_methods_info = IRB::HelperMethod.all_helper_methods_info - commands_grouped_by_categories = commands_info.group_by { |cmd| cmd[:category] } - commands_grouped_by_categories["Helper methods"] = helper_methods_info - - if irb_context.with_debugger - # Remove the original "Debugging" category - commands_grouped_by_categories.delete("Debugging") - end - - longest_cmd_name_length = commands_info.map { |c| c[:display_name].length }.max - - output = StringIO.new - - help_cmds = commands_grouped_by_categories.delete("Help") - no_category_cmds = commands_grouped_by_categories.delete("No category") - aliases = irb_context.instance_variable_get(:@command_aliases).map do |alias_name, target| - { display_name: alias_name, description: "Alias for `#{target}`" } - end - - # Display help commands first - add_category_to_output("Help", help_cmds, output, longest_cmd_name_length) - - # Display the rest of the commands grouped by categories - commands_grouped_by_categories.each do |category, cmds| - add_category_to_output(category, cmds, output, longest_cmd_name_length) - end - - # Display commands without a category - if no_category_cmds - add_category_to_output("No category", no_category_cmds, output, longest_cmd_name_length) - end - - # Display aliases - add_category_to_output("Aliases", aliases, output, longest_cmd_name_length) - - # Append the debugger help at the end - if irb_context.with_debugger - # Add "Debugging (from debug.gem)" category as title - add_category_to_output("Debugging (from debug.gem)", [], output, longest_cmd_name_length) - output.puts DEBUGGER__.help - end - - output.string - end - - def add_category_to_output(category, cmds, output, longest_cmd_name_length) - output.puts Color.colorize(category, [:BOLD]) - - cmds.each do |cmd| - output.puts " #{cmd[:display_name].to_s.ljust(longest_cmd_name_length)} #{cmd[:description]}" - end - - output.puts - end - end - end -end diff --git a/lib/irb/command/history.rb b/lib/irb/command/history.rb deleted file mode 100644 index 90f87f9102..0000000000 --- a/lib/irb/command/history.rb +++ /dev/null @@ -1,45 +0,0 @@ -# frozen_string_literal: true - -require "stringio" - -require_relative "../pager" - -module IRB - # :stopdoc: - - module Command - class History < Base - category "IRB" - description "Shows the input history. `-g [query]` or `-G [query]` allows you to filter the output." - - def execute(arg) - - if (match = arg&.match(/(-g|-G)\s+(?<grep>.+)\s*\n\z/)) - grep = Regexp.new(match[:grep]) - end - - formatted_inputs = irb_context.io.class::HISTORY.each_with_index.reverse_each.filter_map do |input, index| - next if grep && !input.match?(grep) - - header = "#{index}: " - - first_line, *other_lines = input.split("\n") - first_line = "#{header}#{first_line}" - - truncated_lines = other_lines.slice!(1..) # Show 1 additional line (2 total) - other_lines << "..." if truncated_lines&.any? - - other_lines.map! do |line| - " " * header.length + line - end - - [first_line, *other_lines].join("\n") + "\n" - end - - Pager.page_content(formatted_inputs.join) - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/info.rb b/lib/irb/command/info.rb deleted file mode 100644 index d08ce00a32..0000000000 --- a/lib/irb/command/info.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Info < DebugCommand - def execute(arg) - execute_debug_command(pre_cmds: "info #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/internal_helpers.rb b/lib/irb/command/internal_helpers.rb deleted file mode 100644 index 249b5cdede..0000000000 --- a/lib/irb/command/internal_helpers.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - # Internal use only, for default command's backward compatibility. - module RubyArgsExtractor # :nodoc: - def unwrap_string_literal(str) - return if str.empty? - - sexp = Ripper.sexp(str) - if sexp && sexp.size == 2 && sexp.last&.first&.first == :string_literal - @irb_context.workspace.binding.eval(str).to_s - else - str - end - end - - def ruby_args(arg) - # Use throw and catch to handle arg that includes `;` - # For example: "1, kw: (2; 3); 4" will be parsed to [[1], { kw: 3 }] - catch(:EXTRACT_RUBY_ARGS) do - @irb_context.workspace.binding.eval "IRB::Command.extract_ruby_args #{arg}" - end || [[], {}] - end - end - end -end diff --git a/lib/irb/command/irb_info.rb b/lib/irb/command/irb_info.rb deleted file mode 100644 index 6d868de94c..0000000000 --- a/lib/irb/command/irb_info.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class IrbInfo < Base - category "IRB" - description "Show information about IRB." - - def execute(_arg) - str = "Ruby version: #{RUBY_VERSION}\n" - str += "IRB version: #{IRB.version}\n" - str += "InputMethod: #{IRB.CurrentContext.io.inspect}\n" - str += "Completion: #{IRB.CurrentContext.io.respond_to?(:completion_info) ? IRB.CurrentContext.io.completion_info : 'off'}\n" - rc_files = IRB.irbrc_files - str += ".irbrc paths: #{rc_files.join(", ")}\n" if rc_files.any? - str += "RUBY_PLATFORM: #{RUBY_PLATFORM}\n" - str += "LANG env: #{ENV["LANG"]}\n" if ENV["LANG"] && !ENV["LANG"].empty? - str += "LC_ALL env: #{ENV["LC_ALL"]}\n" if ENV["LC_ALL"] && !ENV["LC_ALL"].empty? - str += "East Asian Ambiguous Width: #{Reline.ambiguous_width.inspect}\n" - if RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - codepage = `chcp`.b.sub(/.*: (\d+)\n/, '\1') - str += "Code page: #{codepage}\n" - end - puts str - nil - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/load.rb b/lib/irb/command/load.rb deleted file mode 100644 index 1cd3f279d1..0000000000 --- a/lib/irb/command/load.rb +++ /dev/null @@ -1,91 +0,0 @@ -# frozen_string_literal: true -# -# load.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# -require_relative "../ext/loader" - -module IRB - # :stopdoc: - - module Command - class LoaderCommand < Base - include RubyArgsExtractor - include IrbLoader - - def raise_cmd_argument_error - raise CommandArgumentError.new("Please specify the file name.") - end - end - - class Load < LoaderCommand - category "IRB" - description "Load a Ruby file." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(file_name = nil, priv = nil) - raise_cmd_argument_error unless file_name - irb_load(file_name, priv) - end - end - - class Require < LoaderCommand - category "IRB" - description "Require a Ruby file." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(file_name = nil) - raise_cmd_argument_error unless file_name - - rex = Regexp.new("#{Regexp.quote(file_name)}(\.o|\.rb)?") - return false if $".find{|f| f =~ rex} - - case file_name - when /\.rb$/ - begin - if irb_load(file_name) - $".push file_name - return true - end - rescue LoadError - end - when /\.(so|o|sl)$/ - return ruby_require(file_name) - end - - begin - irb_load(f = file_name + ".rb") - $".push f - return true - rescue LoadError - return ruby_require(file_name) - end - end - end - - class Source < LoaderCommand - category "IRB" - description "Loads a given file in the current session." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(file_name = nil) - raise_cmd_argument_error unless file_name - - source_file(file_name) - end - end - end - # :startdoc: -end diff --git a/lib/irb/command/ls.rb b/lib/irb/command/ls.rb deleted file mode 100644 index cbd9998bc4..0000000000 --- a/lib/irb/command/ls.rb +++ /dev/null @@ -1,155 +0,0 @@ -# frozen_string_literal: true - -require "reline" -require "stringio" - -require_relative "../pager" -require_relative "../color" - -module IRB - # :stopdoc: - - module Command - class Ls < Base - include RubyArgsExtractor - - category "Context" - description "Show methods, constants, and variables." - - help_message <<~HELP_MESSAGE - Usage: ls [obj] [-g [query]] - - -g [query] Filter the output with a query. - HELP_MESSAGE - - def execute(arg) - if match = arg.match(/\A(?<target>.+\s|)(-g|-G)\s+(?<grep>.+)$/) - if match[:target].empty? - use_main = true - else - obj = @irb_context.workspace.binding.eval(match[:target]) - end - grep = Regexp.new(match[:grep]) - else - args, kwargs = ruby_args(arg) - use_main = args.empty? - obj = args.first - grep = kwargs[:grep] - end - - if use_main - obj = irb_context.workspace.main - locals = irb_context.workspace.binding.local_variables - end - - o = Output.new(grep: grep) - - klass = (obj.class == Class || obj.class == Module ? obj : obj.class) - - o.dump("constants", obj.constants) if obj.respond_to?(:constants) - dump_methods(o, klass, obj) - o.dump("instance variables", obj.instance_variables) - o.dump("class variables", klass.class_variables) - o.dump("locals", locals) if locals - o.print_result - end - - def dump_methods(o, klass, obj) - singleton_class = begin obj.singleton_class; rescue TypeError; nil end - dumped_mods = Array.new - ancestors = klass.ancestors - ancestors = ancestors.reject { |c| c >= Object } if klass < Object - singleton_ancestors = (singleton_class&.ancestors || []).reject { |c| c >= Class } - - # singleton_class' ancestors should be at the front - maps = class_method_map(singleton_ancestors, dumped_mods) + class_method_map(ancestors, dumped_mods) - maps.each do |mod, methods| - name = mod == singleton_class ? "#{klass}.methods" : "#{mod}#methods" - o.dump(name, methods) - end - end - - def class_method_map(classes, dumped_mods) - dumped_methods = Array.new - classes.map do |mod| - next if dumped_mods.include? mod - - dumped_mods << mod - - methods = mod.public_instance_methods(false).select do |method| - if dumped_methods.include? method - false - else - dumped_methods << method - true - end - end - - [mod, methods] - end.compact - end - - class Output - MARGIN = " " - - def initialize(grep: nil) - @grep = grep - @line_width = screen_width - MARGIN.length # right padding - @io = StringIO.new - end - - def print_result - Pager.page_content(@io.string) - end - - def dump(name, strs) - strs = strs.grep(@grep) if @grep - strs = strs.sort - return if strs.empty? - - # Attempt a single line - @io.print "#{Color.colorize(name, [:BOLD, :BLUE])}: " - if fits_on_line?(strs, cols: strs.size, offset: "#{name}: ".length) - @io.puts strs.join(MARGIN) - return - end - @io.puts - - # Dump with the largest # of columns that fits on a line - cols = strs.size - until fits_on_line?(strs, cols: cols, offset: MARGIN.length) || cols == 1 - cols -= 1 - end - widths = col_widths(strs, cols: cols) - strs.each_slice(cols) do |ss| - @io.puts ss.map.with_index { |s, i| "#{MARGIN}%-#{widths[i]}s" % s }.join - end - end - - private - - def fits_on_line?(strs, cols:, offset: 0) - width = col_widths(strs, cols: cols).sum + MARGIN.length * (cols - 1) - width <= @line_width - offset - end - - def col_widths(strs, cols:) - cols.times.map do |col| - (col...strs.size).step(cols).map do |i| - strs[i].length - end.max - end - end - - def screen_width - Reline.get_screen_size.last - rescue Errno::EINVAL # in `winsize': Invalid argument - <STDIN> - 80 - end - end - private_constant :Output - end - end - - # :startdoc: -end diff --git a/lib/irb/command/measure.rb b/lib/irb/command/measure.rb deleted file mode 100644 index f96be20de8..0000000000 --- a/lib/irb/command/measure.rb +++ /dev/null @@ -1,49 +0,0 @@ -module IRB - # :stopdoc: - - module Command - class Measure < Base - include RubyArgsExtractor - - category "Misc" - description "`measure` enables the mode to measure processing time. `measure :off` disables it." - - def initialize(*args) - super(*args) - end - - def execute(arg) - if arg&.match?(/^do$|^do[^\w]|^\{/) - warn 'Configure IRB.conf[:MEASURE_PROC] to add custom measure methods.' - return - end - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(type = nil, arg = nil) - # Please check IRB.init_config in lib/irb/init.rb that sets - # IRB.conf[:MEASURE_PROC] to register default "measure" methods, - # "measure :time" (abbreviated as "measure") and "measure :stackprof". - - case type - when :off - IRB.unset_measure_callback(arg) - when :list - IRB.conf[:MEASURE_CALLBACKS].each do |type_name, _, arg_val| - puts "- #{type_name}" + (arg_val ? "(#{arg_val.inspect})" : '') - end - when :on - added = IRB.set_measure_callback(arg) - puts "#{added[0]} is added." if added - else - added = IRB.set_measure_callback(type, arg) - puts "#{added[0]} is added." if added - end - nil - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/next.rb b/lib/irb/command/next.rb deleted file mode 100644 index 3fc6b68d21..0000000000 --- a/lib/irb/command/next.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Next < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "next #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/pushws.rb b/lib/irb/command/pushws.rb deleted file mode 100644 index b51928c650..0000000000 --- a/lib/irb/command/pushws.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true -# -# change-ws.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative "../ext/workspaces" - -module IRB - # :stopdoc: - - module Command - class Workspaces < Base - category "Workspace" - description "Show workspaces." - - def execute(_arg) - inspection_resuls = irb_context.instance_variable_get(:@workspace_stack).map do |ws| - truncated_inspect(ws.main) - end - - puts "[" + inspection_resuls.join(", ") + "]" - end - - private - - def truncated_inspect(obj) - obj_inspection = obj.inspect - - if obj_inspection.size > 20 - obj_inspection = obj_inspection[0, 19] + "...>" - end - - obj_inspection - end - end - - class PushWorkspace < Workspaces - category "Workspace" - description "Push an object to the workspace stack." - - def execute(arg) - if arg.empty? - irb_context.push_workspace - else - obj = eval(arg, irb_context.workspace.binding) - irb_context.push_workspace(obj) - end - super - end - end - - class PopWorkspace < Workspaces - category "Workspace" - description "Pop a workspace from the workspace stack." - - def execute(_arg) - irb_context.pop_workspace - super - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/show_doc.rb b/lib/irb/command/show_doc.rb deleted file mode 100644 index 8a2188e4eb..0000000000 --- a/lib/irb/command/show_doc.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Command - class ShowDoc < Base - include RubyArgsExtractor - - category "Context" - description "Look up documentation with RI." - - help_message <<~HELP_MESSAGE - Usage: show_doc [name] - - When name is provided, IRB will look up the documentation for the given name. - When no name is provided, a RI session will be started. - - Examples: - - show_doc - show_doc Array - show_doc Array#each - - HELP_MESSAGE - - def execute(arg) - # Accept string literal for backward compatibility - name = unwrap_string_literal(arg) - require 'rdoc/ri/driver' - - unless ShowDoc.const_defined?(:Ri) - opts = RDoc::RI::Driver.process_args([]) - ShowDoc.const_set(:Ri, RDoc::RI::Driver.new(opts)) - end - - if name.nil? - Ri.interactive - else - begin - Ri.display_name(name) - rescue RDoc::RI::Error - puts $!.message - end - end - - nil - rescue LoadError, SystemExit - warn "Can't display document because `rdoc` is not installed." - end - end - end -end diff --git a/lib/irb/command/show_source.rb b/lib/irb/command/show_source.rb deleted file mode 100644 index f4c6f104a2..0000000000 --- a/lib/irb/command/show_source.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true - -require_relative "../source_finder" -require_relative "../pager" -require_relative "../color" - -module IRB - module Command - class ShowSource < Base - include RubyArgsExtractor - - category "Context" - description "Show the source code of a given method, class/module, or constant." - - help_message <<~HELP_MESSAGE - Usage: show_source [target] [-s] - - -s Show the super method. You can stack it like `-ss` to show the super of the super, etc. - - Examples: - - show_source Foo - show_source Foo#bar - show_source Foo#bar -s - show_source Foo.baz - show_source Foo::BAR - HELP_MESSAGE - - def execute(arg) - # Accept string literal for backward compatibility - str = unwrap_string_literal(arg) - unless str.is_a?(String) - puts "Error: Expected a string but got #{str.inspect}" - return - end - - str, esses = str.split(" -") - super_level = esses ? esses.count("s") : 0 - source = SourceFinder.new(@irb_context).find_source(str, super_level) - - if source - show_source(source) - elsif super_level > 0 - puts "Error: Couldn't locate a super definition for #{str}" - else - puts "Error: Couldn't locate a definition for #{str}" - end - nil - end - - private - - def show_source(source) - if source.binary_file? - content = "\n#{bold('Defined in binary file')}: #{source.file}\n\n" - else - code = source.colorized_content || 'Source not available' - content = <<~CONTENT - - #{bold("From")}: #{source.file}:#{source.line} - - #{code.chomp} - - CONTENT - end - Pager.page_content(content) - end - - def bold(str) - Color.colorize(str, [:BOLD]) - end - end - end -end diff --git a/lib/irb/command/step.rb b/lib/irb/command/step.rb deleted file mode 100644 index 29e5e35ac0..0000000000 --- a/lib/irb/command/step.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require_relative "debug" - -module IRB - # :stopdoc: - - module Command - class Step < DebugCommand - def execute(arg) - execute_debug_command(do_cmds: "step #{arg}") - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/subirb.rb b/lib/irb/command/subirb.rb deleted file mode 100644 index 85af28c1a5..0000000000 --- a/lib/irb/command/subirb.rb +++ /dev/null @@ -1,123 +0,0 @@ -# frozen_string_literal: true -# -# multi.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - # :stopdoc: - - module Command - class MultiIRBCommand < Base - include RubyArgsExtractor - - private - - def print_deprecated_warning - warn <<~MSG - Multi-irb commands are deprecated and will be removed in IRB 2.0.0. Please use workspace commands instead. - If you have any use case for multi-irb, please leave a comment at https://github.com/ruby/irb/issues/653 - MSG - end - - def extend_irb_context - # this extension patches IRB context like IRB.CurrentContext - require_relative "../ext/multi-irb" - end - - def print_debugger_warning - warn "Multi-IRB commands are not available when the debugger is enabled." - end - end - - class IrbCommand < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Start a child IRB." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(*obj) - print_deprecated_warning - - if irb_context.with_debugger - print_debugger_warning - return - end - - extend_irb_context - IRB.irb(nil, *obj) - puts IRB.JobManager.inspect - end - end - - class Jobs < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "List of current sessions." - - def execute(_arg) - print_deprecated_warning - - if irb_context.with_debugger - print_debugger_warning - return - end - - extend_irb_context - puts IRB.JobManager.inspect - end - end - - class Foreground < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Switches to the session of the given number." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(key = nil) - print_deprecated_warning - - if irb_context.with_debugger - print_debugger_warning - return - end - - extend_irb_context - - raise CommandArgumentError.new("Please specify the id of target IRB job (listed in the `jobs` command).") unless key - IRB.JobManager.switch(key) - puts IRB.JobManager.inspect - end - end - - class Kill < MultiIRBCommand - category "Multi-irb (DEPRECATED)" - description "Kills the session with the given number." - - def execute(arg) - args, kwargs = ruby_args(arg) - execute_internal(*args, **kwargs) - end - - def execute_internal(*keys) - print_deprecated_warning - - if irb_context.with_debugger - print_debugger_warning - return - end - - extend_irb_context - IRB.JobManager.kill(*keys) - puts IRB.JobManager.inspect - end - end - end - - # :startdoc: -end diff --git a/lib/irb/command/whereami.rb b/lib/irb/command/whereami.rb deleted file mode 100644 index c8439f1212..0000000000 --- a/lib/irb/command/whereami.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module IRB - # :stopdoc: - - module Command - class Whereami < Base - category "Context" - description "Show the source code around binding.irb again." - - def execute(_arg) - code = irb_context.workspace.code_around_binding - if code - puts code - else - puts "The current context doesn't have code." - end - end - end - end - - # :startdoc: -end diff --git a/lib/irb/completion.rb b/lib/irb/completion.rb deleted file mode 100644 index 7f102dcdf4..0000000000 --- a/lib/irb/completion.rb +++ /dev/null @@ -1,497 +0,0 @@ -# frozen_string_literal: true -# -# irb/completion.rb - -# by Keiju ISHITSUKA(keiju@ishitsuka.com) -# From Original Idea of shugo@ruby-lang.org -# - -require_relative 'ruby-lex' - -module IRB - class BaseCompletor # :nodoc: - - # Set of reserved words used by Ruby, you should not use these for - # constants or variables - ReservedWords = %w[ - __ENCODING__ __LINE__ __FILE__ - BEGIN END - alias and - begin break - case class - def defined? do - else elsif end ensure - false for - if in - module - next nil not - or - redo rescue retry return - self super - then true - undef unless until - when while - yield - ] - - HELP_COMMAND_PREPOSING = /\Ahelp\s+/ - - def completion_candidates(preposing, target, postposing, bind:) - raise NotImplementedError - end - - def doc_namespace(preposing, matched, postposing, bind:) - raise NotImplementedError - end - - GEM_PATHS = - if defined?(Gem::Specification) - Gem::Specification.latest_specs(true).map { |s| - s.require_paths.map { |p| - if File.absolute_path?(p) - p - else - File.join(s.full_gem_path, p) - end - } - }.flatten - else - [] - end.freeze - - def retrieve_gem_and_system_load_path - candidates = (GEM_PATHS | $LOAD_PATH) - candidates.map do |p| - if p.respond_to?(:to_path) - p.to_path - else - String(p) rescue nil - end - end.compact.sort - end - - def retrieve_files_to_require_from_load_path - @files_from_load_path ||= - ( - shortest = [] - rest = retrieve_gem_and_system_load_path.each_with_object([]) { |path, result| - begin - names = Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: path) - rescue Errno::ENOENT - nil - end - next if names.empty? - names.map! { |n| n.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') }.sort! - shortest << names.shift - result.concat(names) - } - shortest.sort! | rest - ) - end - - def command_candidates(target) - if !target.empty? - IRB::Command.command_names.select { _1.start_with?(target) } - else - [] - end - end - - def retrieve_files_to_require_relative_from_current_dir - @files_from_current_dir ||= Dir.glob("**/*.{rb,#{RbConfig::CONFIG['DLEXT']}}", base: '.').map { |path| - path.sub(/\.(rb|#{RbConfig::CONFIG['DLEXT']})\z/, '') - } - end - end - - class TypeCompletor < BaseCompletor # :nodoc: - def initialize(context) - @context = context - end - - def inspect - ReplTypeCompletor.info - end - - def completion_candidates(preposing, target, _postposing, bind:) - # When completing the argument of `help` command, only commands should be candidates - return command_candidates(target) if preposing.match?(HELP_COMMAND_PREPOSING) - - commands = if preposing.empty? - command_candidates(target) - # It doesn't make sense to propose commands with other preposing - else - [] - end - - result = ReplTypeCompletor.analyze(preposing + target, binding: bind, filename: @context.irb_path) - - return commands unless result - - commands | result.completion_candidates.map { target + _1 } - end - - def doc_namespace(preposing, matched, _postposing, bind:) - result = ReplTypeCompletor.analyze(preposing + matched, binding: bind, filename: @context.irb_path) - result&.doc_namespace('') - end - end - - class RegexpCompletor < BaseCompletor # :nodoc: - using Module.new { - refine ::Binding do - def eval_methods - ::Kernel.instance_method(:methods).bind(eval("self")).call - end - - def eval_private_methods - ::Kernel.instance_method(:private_methods).bind(eval("self")).call - end - - def eval_instance_variables - ::Kernel.instance_method(:instance_variables).bind(eval("self")).call - end - - def eval_global_variables - ::Kernel.instance_method(:global_variables).bind(eval("self")).call - end - - def eval_class_constants - ::Module.instance_method(:constants).bind(eval("self.class")).call - end - end - } - - def inspect - 'RegexpCompletor' - end - - def complete_require_path(target, preposing, postposing) - if target =~ /\A(['"])([^'"]+)\Z/ - quote = $1 - actual_target = $2 - else - return nil # It's not String literal - end - tokens = RubyLex.ripper_lex_without_warning(preposing.gsub(/\s*\z/, '')) - tok = nil - tokens.reverse_each do |t| - unless [:on_lparen, :on_sp, :on_ignored_sp, :on_nl, :on_ignored_nl, :on_comment].include?(t.event) - tok = t - break - end - end - return unless tok&.event == :on_ident && tok.state == Ripper::EXPR_CMDARG - - case tok.tok - when 'require' - retrieve_files_to_require_from_load_path.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path - } - when 'require_relative' - retrieve_files_to_require_relative_from_current_dir.select { |path| - path.start_with?(actual_target) - }.map { |path| - quote + path - } - end - end - - def completion_candidates(preposing, target, postposing, bind:) - if result = complete_require_path(target, preposing, postposing) - return result - end - - commands = command_candidates(target) - - # When completing the argument of `help` command, only commands should be candidates - return commands if preposing.match?(HELP_COMMAND_PREPOSING) - - # It doesn't make sense to propose commands with other preposing - commands = [] unless preposing.empty? - - completion_data = retrieve_completion_data(target, bind: bind, doc_namespace: false).compact.map{ |i| i.encode(Encoding.default_external) } - commands | completion_data - end - - def doc_namespace(_preposing, matched, _postposing, bind:) - retrieve_completion_data(matched, bind: bind, doc_namespace: true) - end - - def retrieve_completion_data(input, bind:, doc_namespace:) - case input - # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting - # details are described in: https://github.com/ruby/irb/pull/523 - when /^(.*["'`])\.([^.]*)$/ - # String - receiver = $1 - message = $2 - - if doc_namespace - "String.#{message}" - else - candidates = String.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - # this regexp only matches the closing character because of irb's Reline.completer_quote_characters setting - # details are described in: https://github.com/ruby/irb/pull/523 - when /^(.*\/)\.([^.]*)$/ - # Regexp - receiver = $1 - message = $2 - - if doc_namespace - "Regexp.#{message}" - else - candidates = Regexp.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - when /^([^\]]*\])\.([^.]*)$/ - # Array - receiver = $1 - message = $2 - - if doc_namespace - "Array.#{message}" - else - candidates = Array.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates) - end - - when /^([^\}]*\})\.([^.]*)$/ - # Hash or Proc - receiver = $1 - message = $2 - - if doc_namespace - ["Hash.#{message}", "Proc.#{message}"] - else - hash_candidates = Hash.instance_methods.collect{|m| m.to_s} - proc_candidates = Proc.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, hash_candidates | proc_candidates) - end - - when /^(:[^:.]+)$/ - # Symbol - if doc_namespace - nil - else - sym = $1 - candidates = Symbol.all_symbols.collect do |s| - s.inspect - rescue EncodingError - # ignore - end - candidates.grep(/^#{Regexp.quote(sym)}/) - end - when /^::([A-Z][^:\.\(\)]*)$/ - # Absolute Constant or class methods - receiver = $1 - - candidates = Object.constants.collect{|m| m.to_s} - - if doc_namespace - candidates.find { |i| i == receiver } - else - candidates.grep(/^#{Regexp.quote(receiver)}/).collect{|e| "::" + e} - end - - when /^([A-Z].*)::([^:.]*)$/ - # Constant or class methods - receiver = $1 - message = $2 - - if doc_namespace - "#{receiver}::#{message}" - else - begin - candidates = eval("#{receiver}.constants.collect{|m| m.to_s}", bind) - candidates |= eval("#{receiver}.methods.collect{|m| m.to_s}", bind) - rescue Exception - candidates = [] - end - - select_message(receiver, message, candidates.sort, "::") - end - - when /^(:[^:.]+)(\.|::)([^.]*)$/ - # Symbol - receiver = $1 - sep = $2 - message = $3 - - if doc_namespace - "Symbol.#{message}" - else - candidates = Symbol.instance_methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - - when /^(?<num>-?(?:0[dbo])?[0-9_]+(?:\.[0-9_]+)?(?:(?:[eE][+-]?[0-9]+)?i?|r)?)(?<sep>\.|::)(?<mes>[^.]*)$/ - # Numeric - receiver = $~[:num] - sep = $~[:sep] - message = $~[:mes] - - begin - instance = eval(receiver, bind) - - if doc_namespace - "#{instance.class.name}.#{message}" - else - candidates = instance.methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - rescue Exception - if doc_namespace - nil - else - [] - end - end - - when /^(-?0x[0-9a-fA-F_]+)(\.|::)([^.]*)$/ - # Numeric(0xFFFF) - receiver = $1 - sep = $2 - message = $3 - - begin - instance = eval(receiver, bind) - if doc_namespace - "#{instance.class.name}.#{message}" - else - candidates = instance.methods.collect{|m| m.to_s} - select_message(receiver, message, candidates, sep) - end - rescue Exception - if doc_namespace - nil - else - [] - end - end - - when /^(\$[^.]*)$/ - # global var - gvar = $1 - all_gvars = global_variables.collect{|m| m.to_s} - - if doc_namespace - all_gvars.find{ |i| i == gvar } - else - all_gvars.grep(Regexp.new(Regexp.quote(gvar))) - end - - when /^([^.:"].*)(\.|::)([^.]*)$/ - # variable.func or func.func - receiver = $1 - sep = $2 - message = $3 - - gv = bind.eval_global_variables.collect{|m| m.to_s}.push("true", "false", "nil") - lv = bind.local_variables.collect{|m| m.to_s} - iv = bind.eval_instance_variables.collect{|m| m.to_s} - cv = bind.eval_class_constants.collect{|m| m.to_s} - - if (gv | lv | iv | cv).include?(receiver) or /^[A-Z]/ =~ receiver && /\./ !~ receiver - # foo.func and foo is var. OR - # foo::func and foo is var. OR - # foo::Const and foo is var. OR - # Foo::Bar.func - begin - candidates = [] - rec = eval(receiver, bind) - if sep == "::" and rec.kind_of?(Module) - candidates = rec.constants.collect{|m| m.to_s} - end - candidates |= rec.methods.collect{|m| m.to_s} - rescue Exception - candidates = [] - end - else - # func1.func2 - candidates = [] - end - - if doc_namespace - rec_class = rec.is_a?(Module) ? rec : rec.class - "#{rec_class.name}#{sep}#{candidates.find{ |i| i == message }}" rescue nil - else - select_message(receiver, message, candidates, sep) - end - - when /^\.([^.]*)$/ - # unknown(maybe String) - - receiver = "" - message = $1 - - candidates = String.instance_methods(true).collect{|m| m.to_s} - - if doc_namespace - "String.#{candidates.find{ |i| i == message }}" - else - select_message(receiver, message, candidates.sort) - end - when /^\s*$/ - # empty input - if doc_namespace - nil - else - [] - end - else - if doc_namespace - vars = (bind.local_variables | bind.eval_instance_variables).collect{|m| m.to_s} - perfect_match_var = vars.find{|m| m.to_s == input} - if perfect_match_var - eval("#{perfect_match_var}.class.name", bind) rescue nil - else - candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} - candidates |= ReservedWords - candidates.find{ |i| i == input } - end - else - candidates = (bind.eval_methods | bind.eval_private_methods | bind.local_variables | bind.eval_instance_variables | bind.eval_class_constants).collect{|m| m.to_s} - candidates |= ReservedWords - candidates.grep(/^#{Regexp.quote(input)}/).sort - end - end - end - - # Set of available operators in Ruby - Operators = %w[% & * ** + - / < << <= <=> == === =~ > >= >> [] []= ^ ! != !~] - - def select_message(receiver, message, candidates, sep = ".") - candidates.grep(/^#{Regexp.quote(message)}/).collect do |e| - case e - when /^[a-zA-Z_]/ - receiver + sep + e - when /^[0-9]/ - when *Operators - #receiver + " " + e - end - end - end - end - - module InputCompletor - class << self - private def regexp_completor - @regexp_completor ||= RegexpCompletor.new - end - - def retrieve_completion_data(input, bind: IRB.conf[:MAIN_CONTEXT].workspace.binding, doc_namespace: false) - regexp_completor.retrieve_completion_data(input, bind: bind, doc_namespace: doc_namespace) - end - end - CompletionProc = ->(target, preposing = nil, postposing = nil) { - regexp_completor.completion_candidates(preposing || '', target, postposing || '', bind: IRB.conf[:MAIN_CONTEXT].workspace.binding) - } - end - deprecate_constant :InputCompletor -end diff --git a/lib/irb/context.rb b/lib/irb/context.rb deleted file mode 100644 index 505bed80a1..0000000000 --- a/lib/irb/context.rb +++ /dev/null @@ -1,708 +0,0 @@ -# frozen_string_literal: true -# -# irb/context.rb - irb context -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative "workspace" -require_relative "inspector" -require_relative "input-method" -require_relative "output-method" - -module IRB - # A class that wraps the current state of the irb session, including the - # configuration of IRB.conf. - class Context - ASSIGN_OPERATORS_REGEXP = Regexp.union(%w[= += -= *= /= %= **= &= |= &&= ||= ^= <<= >>=]) - # Creates a new IRB context. - # - # The optional +input_method+ argument: - # - # +nil+:: uses stdin or Reline or Readline - # +String+:: uses a File - # +other+:: uses this as InputMethod - def initialize(irb, workspace = nil, input_method = nil) - @irb = irb - @workspace_stack = [] - if workspace - @workspace_stack << workspace - else - @workspace_stack << WorkSpace.new - end - @thread = Thread.current - - # copy of default configuration - @ap_name = IRB.conf[:AP_NAME] - @rc = IRB.conf[:RC] - @load_modules = IRB.conf[:LOAD_MODULES] - - if IRB.conf.has_key?(:USE_SINGLELINE) - @use_singleline = IRB.conf[:USE_SINGLELINE] - elsif IRB.conf.has_key?(:USE_READLINE) # backward compatibility - @use_singleline = IRB.conf[:USE_READLINE] - else - @use_singleline = nil - end - if IRB.conf.has_key?(:USE_MULTILINE) - @use_multiline = IRB.conf[:USE_MULTILINE] - elsif IRB.conf.has_key?(:USE_RELINE) # backward compatibility - warn <<~MSG.strip - USE_RELINE is deprecated, please use USE_MULTILINE instead. - MSG - @use_multiline = IRB.conf[:USE_RELINE] - elsif IRB.conf.has_key?(:USE_REIDLINE) - warn <<~MSG.strip - USE_REIDLINE is deprecated, please use USE_MULTILINE instead. - MSG - @use_multiline = IRB.conf[:USE_REIDLINE] - else - @use_multiline = nil - end - @use_autocomplete = IRB.conf[:USE_AUTOCOMPLETE] - @verbose = IRB.conf[:VERBOSE] - @io = nil - - self.inspect_mode = IRB.conf[:INSPECT_MODE] - self.use_tracer = IRB.conf[:USE_TRACER] - self.use_loader = IRB.conf[:USE_LOADER] if IRB.conf[:USE_LOADER] - self.eval_history = IRB.conf[:EVAL_HISTORY] if IRB.conf[:EVAL_HISTORY] - - @ignore_sigint = IRB.conf[:IGNORE_SIGINT] - @ignore_eof = IRB.conf[:IGNORE_EOF] - - @back_trace_limit = IRB.conf[:BACK_TRACE_LIMIT] - - self.prompt_mode = IRB.conf[:PROMPT_MODE] - - @irb_name = IRB.conf[:IRB_NAME] - - unless IRB.conf[:SINGLE_IRB] or !defined?(IRB::JobManager) - @irb_name = @irb_name + "#" + IRB.JobManager.n_jobs.to_s - end - - self.irb_path = "(" + @irb_name + ")" - - case input_method - when nil - @io = nil - case use_multiline? - when nil - if term_interactive? && IRB.conf[:PROMPT_MODE] != :INF_RUBY && !use_singleline? - # Both of multiline mode and singleline mode aren't specified. - @io = RelineInputMethod.new(build_completor) - else - @io = nil - end - when false - @io = nil - when true - @io = RelineInputMethod.new(build_completor) - end - unless @io - case use_singleline? - when nil - if (defined?(ReadlineInputMethod) && term_interactive? && - IRB.conf[:PROMPT_MODE] != :INF_RUBY) - @io = ReadlineInputMethod.new - else - @io = nil - end - when false - @io = nil - when true - if defined?(ReadlineInputMethod) - @io = ReadlineInputMethod.new - else - @io = nil - end - else - @io = nil - end - end - @io = StdioInputMethod.new unless @io - - when '-' - @io = FileInputMethod.new($stdin) - @irb_name = '-' - self.irb_path = '-' - when String - @io = FileInputMethod.new(input_method) - @irb_name = File.basename(input_method) - self.irb_path = input_method - else - @io = input_method - end - @extra_doc_dirs = IRB.conf[:EXTRA_DOC_DIRS] - - @echo = IRB.conf[:ECHO] - if @echo.nil? - @echo = true - end - - @echo_on_assignment = IRB.conf[:ECHO_ON_ASSIGNMENT] - if @echo_on_assignment.nil? - @echo_on_assignment = :truncate - end - - @newline_before_multiline_output = IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT] - if @newline_before_multiline_output.nil? - @newline_before_multiline_output = true - end - - @command_aliases = IRB.conf[:COMMAND_ALIASES].dup - end - - private def term_interactive? - return true if ENV['TEST_IRB_FORCE_INTERACTIVE'] - STDIN.tty? && ENV['TERM'] != 'dumb' - end - - def use_tracer=(val) - require_relative "ext/tracer" if val - IRB.conf[:USE_TRACER] = val - end - - def eval_history=(val) - self.class.remove_method(__method__) - require_relative "ext/eval_history" - __send__(__method__, val) - end - - def use_loader=(val) - self.class.remove_method(__method__) - require_relative "ext/use-loader" - __send__(__method__, val) - end - - private def build_completor - completor_type = IRB.conf[:COMPLETOR] - - # Gem repl_type_completor is added to bundled gems in Ruby 3.4. - # Use :type as default completor only in Ruby 3.4 or later. - verbose = !!completor_type - completor_type ||= RUBY_VERSION >= '3.4' ? :type : :regexp - - case completor_type - when :regexp - return RegexpCompletor.new - when :type - completor = build_type_completor(verbose: verbose) - return completor if completor - else - warn "Invalid value for IRB.conf[:COMPLETOR]: #{completor_type}" - end - # Fallback to RegexpCompletor - RegexpCompletor.new - end - - private def build_type_completor(verbose:) - if RUBY_ENGINE == 'truffleruby' - # Avoid SyntaxError. truffleruby does not support endless method definition yet. - warn 'TypeCompletor is not supported on TruffleRuby yet' if verbose - return - end - - begin - require 'repl_type_completor' - rescue LoadError => e - warn "TypeCompletor requires `gem repl_type_completor`: #{e.message}" if verbose - return - end - - ReplTypeCompletor.preload_rbs - TypeCompletor.new(self) - end - - def save_history=(val) - IRB.conf[:SAVE_HISTORY] = val - end - - def save_history - IRB.conf[:SAVE_HISTORY] - end - - # A copy of the default <code>IRB.conf[:HISTORY_FILE]</code> - def history_file - IRB.conf[:HISTORY_FILE] - end - - # Set <code>IRB.conf[:HISTORY_FILE]</code> to the given +hist+. - def history_file=(hist) - IRB.conf[:HISTORY_FILE] = hist - end - - # Workspace in the current context. - def workspace - @workspace_stack.last - end - - # Replace the current workspace with the given +workspace+. - def replace_workspace(workspace) - @workspace_stack.pop - @workspace_stack.push(workspace) - end - - # The top-level workspace, see WorkSpace#main - def main - workspace.main - end - - # The toplevel workspace, see #home_workspace - attr_reader :workspace_home - # The current thread in this context. - attr_reader :thread - # The current input method. - # - # Can be either StdioInputMethod, ReadlineInputMethod, - # RelineInputMethod, FileInputMethod or other specified when the - # context is created. See ::new for more # information on +input_method+. - attr_accessor :io - - # Current irb session. - attr_accessor :irb - # A copy of the default <code>IRB.conf[:AP_NAME]</code> - attr_accessor :ap_name - # A copy of the default <code>IRB.conf[:RC]</code> - attr_accessor :rc - # A copy of the default <code>IRB.conf[:LOAD_MODULES]</code> - attr_accessor :load_modules - # Can be either name from <code>IRB.conf[:IRB_NAME]</code>, or the number of - # the current job set by JobManager, such as <code>irb#2</code> - attr_accessor :irb_name - - # Can be one of the following: - # - the #irb_name surrounded by parenthesis - # - the +input_method+ passed to Context.new - # - the file path of the current IRB context in a binding.irb session - attr_reader :irb_path - - # Sets @irb_path to the given +path+ as well as @eval_path - # @eval_path is used for evaluating code in the context of IRB session - # It's the same as irb_path, but with the IRB name postfix - # This makes sure users can distinguish the methods defined in the IRB session - # from the methods defined in the current file's context, especially with binding.irb - def irb_path=(path) - @irb_path = path - - if File.exist?(path) - @eval_path = "#{path}(#{IRB.conf[:IRB_NAME]})" - else - @eval_path = path - end - end - - # Whether multiline editor mode is enabled or not. - # - # A copy of the default <code>IRB.conf[:USE_MULTILINE]</code> - attr_reader :use_multiline - # Whether singleline editor mode is enabled or not. - # - # A copy of the default <code>IRB.conf[:USE_SINGLELINE]</code> - attr_reader :use_singleline - # Whether colorization is enabled or not. - # - # A copy of the default <code>IRB.conf[:USE_AUTOCOMPLETE]</code> - attr_reader :use_autocomplete - # A copy of the default <code>IRB.conf[:INSPECT_MODE]</code> - attr_reader :inspect_mode - - # A copy of the default <code>IRB.conf[:PROMPT_MODE]</code> - attr_reader :prompt_mode - # Standard IRB prompt. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_i - # IRB prompt for continuated strings. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_s - # IRB prompt for continuated statement. (e.g. immediately after an +if+) - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - attr_accessor :prompt_c - - # TODO: Remove this when developing v2.0 - def prompt_n - warn "IRB::Context#prompt_n is deprecated and will be removed in the next major release." - "" - end - - # TODO: Remove this when developing v2.0 - def prompt_n=(_) - warn "IRB::Context#prompt_n= is deprecated and will be removed in the next major release." - "" - end - - # Can be either the default <code>IRB.conf[:AUTO_INDENT]</code>, or the - # mode set by #prompt_mode= - # - # To disable auto-indentation in irb: - # - # IRB.conf[:AUTO_INDENT] = false - # - # or - # - # irb_context.auto_indent_mode = false - # - # or - # - # IRB.CurrentContext.auto_indent_mode = false - # - # See IRB@Configuration for more information. - attr_accessor :auto_indent_mode - # The format of the return statement, set by #prompt_mode= using the - # +:RETURN+ of the +mode+ passed to set the current #prompt_mode. - attr_accessor :return_format - - # Whether <code>^C</code> (+control-c+) will be ignored or not. - # - # If set to +false+, <code>^C</code> will quit irb. - # - # If set to +true+, - # - # * during input: cancel input then return to top level. - # * during execute: abandon current execution. - attr_accessor :ignore_sigint - # Whether <code>^D</code> (+control-d+) will be ignored or not. - # - # If set to +false+, <code>^D</code> will quit irb. - attr_accessor :ignore_eof - # Specify the installation locations of the ri file to be displayed in the - # document dialog. - attr_accessor :extra_doc_dirs - # Whether to echo the return value to output or not. - # - # Uses <code>IRB.conf[:ECHO]</code> if available, or defaults to +true+. - # - # puts "hello" - # # hello - # #=> nil - # IRB.CurrentContext.echo = false - # puts "omg" - # # omg - attr_accessor :echo - # Whether to echo for assignment expressions. - # - # If set to +false+, the value of assignment will not be shown. - # - # If set to +true+, the value of assignment will be shown. - # - # If set to +:truncate+, the value of assignment will be shown and truncated. - # - # It defaults to +:truncate+. - # - # a = "omg" - # #=> omg - # - # a = "omg" * 10 - # #=> omgomgomgomgomgomgomg... - # - # IRB.CurrentContext.echo_on_assignment = false - # a = "omg" - # - # IRB.CurrentContext.echo_on_assignment = true - # a = "omg" * 10 - # #=> omgomgomgomgomgomgomgomgomgomg - # - # To set the behaviour of showing on assignment in irb: - # - # IRB.conf[:ECHO_ON_ASSIGNMENT] = :truncate or true or false - # - # or - # - # irb_context.echo_on_assignment = :truncate or true or false - # - # or - # - # IRB.CurrentContext.echo_on_assignment = :truncate or true or false - attr_accessor :echo_on_assignment - # Whether a newline is put before multiline output. - # - # Uses <code>IRB.conf[:NEWLINE_BEFORE_MULTILINE_OUTPUT]</code> if available, - # or defaults to +true+. - # - # "abc\ndef" - # #=> - # abc - # def - # IRB.CurrentContext.newline_before_multiline_output = false - # "abc\ndef" - # #=> abc - # def - attr_accessor :newline_before_multiline_output - # Whether verbose messages are displayed or not. - # - # A copy of the default <code>IRB.conf[:VERBOSE]</code> - attr_accessor :verbose - - # The limit of backtrace lines displayed as top +n+ and tail +n+. - # - # The default value is 16. - # - # Can also be set using the +--back-trace-limit+ command line option. - attr_accessor :back_trace_limit - - # User-defined IRB command aliases - attr_accessor :command_aliases - - attr_accessor :with_debugger - - # Alias for #use_multiline - alias use_multiline? use_multiline - # Alias for #use_singleline - alias use_singleline? use_singleline - # backward compatibility - alias use_reline use_multiline - # backward compatibility - alias use_reline? use_multiline - # backward compatibility - alias use_readline use_singleline - # backward compatibility - alias use_readline? use_singleline - # Alias for #use_autocomplete - alias use_autocomplete? use_autocomplete - # Alias for #rc - alias rc? rc - alias ignore_sigint? ignore_sigint - alias ignore_eof? ignore_eof - alias echo? echo - alias echo_on_assignment? echo_on_assignment - alias newline_before_multiline_output? newline_before_multiline_output - - # Returns whether messages are displayed or not. - def verbose? - if @verbose.nil? - if @io.kind_of?(RelineInputMethod) - false - elsif defined?(ReadlineInputMethod) && @io.kind_of?(ReadlineInputMethod) - false - elsif !STDIN.tty? or @io.kind_of?(FileInputMethod) - true - else - false - end - else - @verbose - end - end - - # Whether #verbose? is +true+, and +input_method+ is either - # StdioInputMethod or RelineInputMethod or ReadlineInputMethod, see #io - # for more information. - def prompting? - verbose? || @io.prompting? - end - - # The return value of the last statement evaluated. - attr_reader :last_value - - # Sets the return value from the last statement evaluated in this context - # to #last_value. - def set_last_value(value) - @last_value = value - workspace.local_variable_set :_, value - end - - # Sets the +mode+ of the prompt in this context. - # - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - def prompt_mode=(mode) - @prompt_mode = mode - pconf = IRB.conf[:PROMPT][mode] - @prompt_i = pconf[:PROMPT_I] - @prompt_s = pconf[:PROMPT_S] - @prompt_c = pconf[:PROMPT_C] - @return_format = pconf[:RETURN] - @return_format = "%s\n" if @return_format == nil - if ai = pconf.include?(:AUTO_INDENT) - @auto_indent_mode = ai - else - @auto_indent_mode = IRB.conf[:AUTO_INDENT] - end - end - - # Whether #inspect_mode is set or not, see #inspect_mode= for more detail. - def inspect? - @inspect_mode.nil? or @inspect_mode - end - - # Whether #io uses a File for the +input_method+ passed when creating the - # current context, see ::new - def file_input? - @io.class == FileInputMethod - end - - # Specifies the inspect mode with +opt+: - # - # +true+:: display +inspect+ - # +false+:: display +to_s+ - # +nil+:: inspect mode in non-math mode, - # non-inspect mode in math mode - # - # See IRB::Inspector for more information. - # - # Can also be set using the +--inspect+ and +--noinspect+ command line - # options. - def inspect_mode=(opt) - - if i = Inspector::INSPECTORS[opt] - @inspect_mode = opt - @inspect_method = i - i.init - else - case opt - when nil - if Inspector.keys_with_inspector(Inspector::INSPECTORS[true]).include?(@inspect_mode) - self.inspect_mode = false - elsif Inspector.keys_with_inspector(Inspector::INSPECTORS[false]).include?(@inspect_mode) - self.inspect_mode = true - else - puts "Can't switch inspect mode." - return - end - when /^\s*\{.*\}\s*$/ - begin - inspector = eval "proc#{opt}" - rescue Exception - puts "Can't switch inspect mode(#{opt})." - return - end - self.inspect_mode = inspector - when Proc - self.inspect_mode = IRB::Inspector(opt) - when Inspector - prefix = "usr%d" - i = 1 - while Inspector::INSPECTORS[format(prefix, i)]; i += 1; end - @inspect_mode = format(prefix, i) - @inspect_method = opt - Inspector.def_inspector(format(prefix, i), @inspect_method) - else - puts "Can't switch inspect mode(#{opt})." - return - end - end - print "Switch to#{unless @inspect_mode; ' non';end} inspect mode.\n" if verbose? - @inspect_mode - end - - def evaluate(statement, line_no) # :nodoc: - @line_no = line_no - - case statement - when Statement::EmptyInput - return - when Statement::Expression - result = evaluate_expression(statement.code, line_no) - set_last_value(result) - when Statement::Command - statement.command_class.execute(self, statement.arg) - end - - nil - end - - def from_binding? - @irb.from_binding - end - - def evaluate_expression(code, line_no) # :nodoc: - result = nil - if IRB.conf[:MEASURE] && IRB.conf[:MEASURE_CALLBACKS].empty? - IRB.set_measure_callback - end - - if IRB.conf[:MEASURE] && !IRB.conf[:MEASURE_CALLBACKS].empty? - last_proc = proc do - result = workspace.evaluate(code, @eval_path, line_no) - end - IRB.conf[:MEASURE_CALLBACKS].inject(last_proc) do |chain, item| - _name, callback, arg = item - proc do - callback.(self, code, line_no, arg) do - chain.call - end - end - end.call - else - result = workspace.evaluate(code, @eval_path, line_no) - end - result - end - - def parse_command(code) - command_name, arg = code.strip.split(/\s+/, 2) - return unless code.lines.size == 1 && command_name - - arg ||= '' - command = command_name.to_sym - # Command aliases are always command. example: $, @ - if (alias_name = command_aliases[command]) - return [alias_name, arg] - end - - # Assignment-like expression is not a command - return if arg.start_with?(ASSIGN_OPERATORS_REGEXP) && !arg.start_with?(/==|=~/) - - # Local variable have precedence over command - return if local_variables.include?(command) - - # Check visibility - public_method = !!Kernel.instance_method(:public_method).bind_call(main, command) rescue false - private_method = !public_method && !!Kernel.instance_method(:method).bind_call(main, command) rescue false - if Command.execute_as_command?(command, public_method: public_method, private_method: private_method) - [command, arg] - end - end - - def colorize_input(input, complete:) - if IRB.conf[:USE_COLORIZE] && IRB::Color.colorable? - lvars = local_variables || [] - if parse_command(input) - name, sep, arg = input.split(/(\s+)/, 2) - arg = IRB::Color.colorize_code(arg, complete: complete, local_variables: lvars) - "#{IRB::Color.colorize(name, [:BOLD])}\e[m#{sep}#{arg}" - else - IRB::Color.colorize_code(input, complete: complete, local_variables: lvars) - end - else - Reline::Unicode.escape_for_print(input) - end - end - - def inspect_last_value # :nodoc: - @inspect_method.inspect_value(@last_value) - end - - NOPRINTING_IVARS = ["@last_value"] # :nodoc: - NO_INSPECTING_IVARS = ["@irb", "@io"] # :nodoc: - IDNAME_IVARS = ["@prompt_mode"] # :nodoc: - - alias __inspect__ inspect - def inspect # :nodoc: - array = [] - for ivar in instance_variables.sort{|e1, e2| e1 <=> e2} - ivar = ivar.to_s - name = ivar.sub(/^@(.*)$/, '\1') - val = instance_eval(ivar) - case ivar - when *NOPRINTING_IVARS - array.push format("conf.%s=%s", name, "...") - when *NO_INSPECTING_IVARS - array.push format("conf.%s=%s", name, val.to_s) - when *IDNAME_IVARS - array.push format("conf.%s=:%s", name, val.id2name) - else - array.push format("conf.%s=%s", name, val.inspect) - end - end - array.join("\n") - end - alias __to_s__ to_s - alias to_s inspect - - def local_variables # :nodoc: - workspace.binding.local_variables - end - end -end diff --git a/lib/irb/debug.rb b/lib/irb/debug.rb deleted file mode 100644 index cd64b77ad7..0000000000 --- a/lib/irb/debug.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true - -module IRB - module Debug - IRB_DIR = File.expand_path('..', __dir__) - - class << self - def insert_debug_break(pre_cmds: nil, do_cmds: nil) - options = { oneshot: true, hook_call: false } - - if pre_cmds || do_cmds - options[:command] = ['irb', pre_cmds, do_cmds] - end - if DEBUGGER__::LineBreakpoint.instance_method(:initialize).parameters.include?([:key, :skip_src]) - options[:skip_src] = true - end - - # To make debugger commands like `next` or `continue` work without asking - # the user to quit IRB after that, we need to exit IRB first and then hit - # a TracePoint on #debug_break. - file, lineno = IRB::Irb.instance_method(:debug_break).source_location - DEBUGGER__::SESSION.add_line_breakpoint(file, lineno + 1, **options) - end - - def setup(irb) - # When debug session is not started at all - unless defined?(DEBUGGER__::SESSION) - begin - require "debug/session" - rescue LoadError # debug.gem is not written in Gemfile - return false unless load_bundled_debug_gem - end - DEBUGGER__::CONFIG.set_config - configure_irb_for_debugger(irb) - - DEBUGGER__.initialize_session{ IRB::Debug::UI.new(irb) } - end - - # When debug session was previously started but not by IRB - if defined?(DEBUGGER__::SESSION) && !irb.context.with_debugger - configure_irb_for_debugger(irb) - DEBUGGER__::SESSION.reset_ui(IRB::Debug::UI.new(irb)) - end - - # Apply patches to debug gem so it skips IRB frames - unless DEBUGGER__.respond_to?(:capture_frames_without_irb) - DEBUGGER__.singleton_class.send(:alias_method, :capture_frames_without_irb, :capture_frames) - - def DEBUGGER__.capture_frames(*args) - frames = capture_frames_without_irb(*args) - frames.reject! do |frame| - frame.realpath&.start_with?(IRB_DIR) || frame.path == "<internal:prelude>" - end - frames - end - - DEBUGGER__::ThreadClient.prepend(SkipPathHelperForIRB) - end - - if !DEBUGGER__::CONFIG[:no_hint] && irb.context.io.is_a?(RelineInputMethod) - Reline.output_modifier_proc = proc do |input, complete:| - unless input.strip.empty? - cmd = input.split(/\s/, 2).first - - if !complete && DEBUGGER__.commands.key?(cmd) - input = input.sub(/\n$/, " # debug command\n") - end - end - - irb.context.colorize_input(input, complete: complete) - end - end - - true - end - - private - - def configure_irb_for_debugger(irb) - require 'irb/debug/ui' - IRB.instance_variable_set(:@debugger_irb, irb) - irb.context.with_debugger = true - irb.context.irb_name += ":rdbg" - end - - module SkipPathHelperForIRB - def skip_internal_path?(path) - # The latter can be removed once https://github.com/ruby/debug/issues/866 is resolved - super || path.match?(IRB_DIR) || path.match?('<internal:prelude>') - end - end - - # This is used when debug.gem is not written in Gemfile. Even if it's not - # installed by `bundle install`, debug.gem is installed by default because - # it's a bundled gem. This method tries to activate and load that. - def load_bundled_debug_gem - # Discover latest debug.gem under GEM_PATH - debug_gem = Gem.paths.path.flat_map { |path| Dir.glob("#{path}/gems/debug-*") }.select do |path| - File.basename(path).match?(/\Adebug-\d+\.\d+\.\d+(\w+)?\z/) - end.sort_by do |path| - Gem::Version.new(File.basename(path).delete_prefix('debug-')) - end.last - return false unless debug_gem - - # Discover debug/debug.so under extensions for Ruby 3.2+ - ext_name = "/debug/debug.#{RbConfig::CONFIG['DLEXT']}" - ext_path = Gem.paths.path.flat_map do |path| - Dir.glob("#{path}/extensions/**/#{File.basename(debug_gem)}#{ext_name}") - end.first - - # Attempt to forcibly load the bundled gem - if ext_path - $LOAD_PATH << ext_path.delete_suffix(ext_name) - end - $LOAD_PATH << "#{debug_gem}/lib" - begin - require "debug/session" - puts "Loaded #{File.basename(debug_gem)}" - true - rescue LoadError - false - end - end - end - end -end diff --git a/lib/irb/debug/ui.rb b/lib/irb/debug/ui.rb deleted file mode 100644 index 7a1cd6dd16..0000000000 --- a/lib/irb/debug/ui.rb +++ /dev/null @@ -1,103 +0,0 @@ -require 'io/console/size' -require 'debug/console' - -module IRB - module Debug - class UI < DEBUGGER__::UI_Base - def initialize(irb) - @irb = irb - end - - def remote? - false - end - - def activate session, on_fork: false - end - - def deactivate - end - - def width - if (w = IO.console_size[1]) == 0 # for tests PTY - 80 - else - w - end - end - - def quit n - yield - exit n - end - - def ask prompt - setup_interrupt do - print prompt - ($stdin.gets || '').strip - end - end - - def puts str = nil - case str - when Array - str.each{|line| - $stdout.puts line.chomp - } - when String - str.each_line{|line| - $stdout.puts line.chomp - } - when nil - $stdout.puts - end - end - - def readline _ - setup_interrupt do - tc = DEBUGGER__::SESSION.instance_variable_get(:@tc) - cmd = @irb.debug_readline(tc.current_frame.eval_binding || TOPLEVEL_BINDING) - - case cmd - when nil # when user types C-d - "continue" - else - cmd - end - end - end - - def setup_interrupt - DEBUGGER__::SESSION.intercept_trap_sigint false do - current_thread = Thread.current # should be session_server thread - - prev_handler = trap(:INT){ - current_thread.raise Interrupt - } - - yield - ensure - trap(:INT, prev_handler) - end - end - - def after_fork_parent - parent_pid = Process.pid - - at_exit{ - DEBUGGER__::SESSION.intercept_trap_sigint_end - trap(:SIGINT, :IGNORE) - - if Process.pid == parent_pid - # only check child process from its parent - begin - # wait for all child processes to keep terminal - Process.waitpid - rescue Errno::ESRCH, Errno::ECHILD - end - end - } - end - end - end -end diff --git a/lib/irb/default_commands.rb b/lib/irb/default_commands.rb deleted file mode 100644 index 768fbee9d7..0000000000 --- a/lib/irb/default_commands.rb +++ /dev/null @@ -1,276 +0,0 @@ -# frozen_string_literal: true - -require_relative "command" -require_relative "command/internal_helpers" -require_relative "command/backtrace" -require_relative "command/break" -require_relative "command/catch" -require_relative "command/cd" -require_relative "command/chws" -require_relative "command/context" -require_relative "command/continue" -require_relative "command/debug" -require_relative "command/delete" -require_relative "command/disable_irb" -require_relative "command/edit" -require_relative "command/exit" -require_relative "command/finish" -require_relative "command/force_exit" -require_relative "command/help" -require_relative "command/history" -require_relative "command/info" -require_relative "command/irb_info" -require_relative "command/load" -require_relative "command/ls" -require_relative "command/measure" -require_relative "command/next" -require_relative "command/pushws" -require_relative "command/show_doc" -require_relative "command/show_source" -require_relative "command/step" -require_relative "command/subirb" -require_relative "command/whereami" - -module IRB - module Command - NO_OVERRIDE = 0 - OVERRIDE_PRIVATE_ONLY = 0x01 - OVERRIDE_ALL = 0x02 - - class << self - # This API is for IRB's internal use only and may change at any time. - # Please do NOT use it. - def _register_with_aliases(name, command_class, *aliases) - @commands[name.to_sym] = [command_class, aliases] - end - - def all_commands_info - user_aliases = IRB.CurrentContext.command_aliases.each_with_object({}) do |(alias_name, target), result| - result[target] ||= [] - result[target] << alias_name - end - - commands.map do |command_name, (command_class, aliases)| - aliases = aliases.map { |a| a.first } - - if additional_aliases = user_aliases[command_name] - aliases += additional_aliases - end - - display_name = aliases.shift || command_name - { - display_name: display_name, - description: command_class.description, - category: command_class.category - } - end - end - - def command_override_policies - @@command_override_policies ||= commands.flat_map do |cmd_name, (cmd_class, aliases)| - [[cmd_name, OVERRIDE_ALL]] + aliases - end.to_h - end - - def execute_as_command?(name, public_method:, private_method:) - case command_override_policies[name] - when OVERRIDE_ALL - true - when OVERRIDE_PRIVATE_ONLY - !public_method - when NO_OVERRIDE - !public_method && !private_method - end - end - - def command_names - command_override_policies.keys.map(&:to_s) - end - - # Convert a command name to its implementation class if such command exists - def load_command(command) - command = command.to_sym - commands.each do |command_name, (command_class, aliases)| - if command_name == command || aliases.any? { |alias_name, _| alias_name == command } - return command_class - end - end - nil - end - end - - _register_with_aliases(:irb_context, Command::Context, - [:context, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_exit, Command::Exit, - [:exit, OVERRIDE_PRIVATE_ONLY], - [:quit, OVERRIDE_PRIVATE_ONLY], - [:irb_quit, OVERRIDE_PRIVATE_ONLY] - ) - - _register_with_aliases(:irb_exit!, Command::ForceExit, - [:exit!, OVERRIDE_PRIVATE_ONLY] - ) - - _register_with_aliases(:irb_current_working_workspace, Command::CurrentWorkingWorkspace, - [:cwws, NO_OVERRIDE], - [:pwws, NO_OVERRIDE], - [:irb_print_working_workspace, OVERRIDE_ALL], - [:irb_cwws, OVERRIDE_ALL], - [:irb_pwws, OVERRIDE_ALL], - [:irb_current_working_binding, OVERRIDE_ALL], - [:irb_print_working_binding, OVERRIDE_ALL], - [:irb_cwb, OVERRIDE_ALL], - [:irb_pwb, OVERRIDE_ALL], - ) - - _register_with_aliases(:irb_change_workspace, Command::ChangeWorkspace, - [:chws, NO_OVERRIDE], - [:cws, NO_OVERRIDE], - [:irb_chws, OVERRIDE_ALL], - [:irb_cws, OVERRIDE_ALL], - [:irb_change_binding, OVERRIDE_ALL], - [:irb_cb, OVERRIDE_ALL], - [:cb, NO_OVERRIDE], - ) - - _register_with_aliases(:irb_workspaces, Command::Workspaces, - [:workspaces, NO_OVERRIDE], - [:irb_bindings, OVERRIDE_ALL], - [:bindings, NO_OVERRIDE], - ) - - _register_with_aliases(:irb_push_workspace, Command::PushWorkspace, - [:pushws, NO_OVERRIDE], - [:irb_pushws, OVERRIDE_ALL], - [:irb_push_binding, OVERRIDE_ALL], - [:irb_pushb, OVERRIDE_ALL], - [:pushb, NO_OVERRIDE], - ) - - _register_with_aliases(:irb_pop_workspace, Command::PopWorkspace, - [:popws, NO_OVERRIDE], - [:irb_popws, OVERRIDE_ALL], - [:irb_pop_binding, OVERRIDE_ALL], - [:irb_popb, OVERRIDE_ALL], - [:popb, NO_OVERRIDE], - ) - - _register_with_aliases(:irb_load, Command::Load) - _register_with_aliases(:irb_require, Command::Require) - _register_with_aliases(:irb_source, Command::Source, - [:source, NO_OVERRIDE] - ) - - _register_with_aliases(:irb, Command::IrbCommand) - _register_with_aliases(:irb_jobs, Command::Jobs, - [:jobs, NO_OVERRIDE] - ) - _register_with_aliases(:irb_fg, Command::Foreground, - [:fg, NO_OVERRIDE] - ) - _register_with_aliases(:irb_kill, Command::Kill, - [:kill, OVERRIDE_PRIVATE_ONLY] - ) - - _register_with_aliases(:irb_debug, Command::Debug, - [:debug, NO_OVERRIDE] - ) - _register_with_aliases(:irb_edit, Command::Edit, - [:edit, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_break, Command::Break, - [:break, OVERRIDE_ALL] - ) - _register_with_aliases(:irb_catch, Command::Catch, - [:catch, OVERRIDE_PRIVATE_ONLY] - ) - _register_with_aliases(:irb_next, Command::Next, - [:next, OVERRIDE_ALL] - ) - _register_with_aliases(:irb_delete, Command::Delete, - [:delete, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_step, Command::Step, - [:step, NO_OVERRIDE] - ) - _register_with_aliases(:irb_continue, Command::Continue, - [:continue, NO_OVERRIDE] - ) - _register_with_aliases(:irb_finish, Command::Finish, - [:finish, NO_OVERRIDE] - ) - _register_with_aliases(:irb_backtrace, Command::Backtrace, - [:backtrace, NO_OVERRIDE], - [:bt, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_debug_info, Command::Info, - [:info, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_help, Command::Help, - [:help, NO_OVERRIDE], - [:show_cmds, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_show_doc, Command::ShowDoc, - [:show_doc, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_info, Command::IrbInfo) - - _register_with_aliases(:irb_ls, Command::Ls, - [:ls, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_measure, Command::Measure, - [:measure, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_show_source, Command::ShowSource, - [:show_source, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_whereami, Command::Whereami, - [:whereami, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_history, Command::History, - [:history, NO_OVERRIDE], - [:hist, NO_OVERRIDE] - ) - - _register_with_aliases(:irb_disable_irb, Command::DisableIrb, - [:disable_irb, NO_OVERRIDE] - ) - - register(:cd, Command::CD) - end - - ExtendCommand = Command - - # For backward compatibility, we need to keep this module: - # - As a container of helper methods - # - As a place to register commands with the deprecated def_extend_command method - module ExtendCommandBundle - # For backward compatibility - NO_OVERRIDE = Command::NO_OVERRIDE - OVERRIDE_PRIVATE_ONLY = Command::OVERRIDE_PRIVATE_ONLY - OVERRIDE_ALL = Command::OVERRIDE_ALL - - # Deprecated. Doesn't have any effect. - @EXTEND_COMMANDS = [] - - class << self - # Drepcated. Use Command.regiser instead. - def def_extend_command(cmd_name, cmd_class, _, *aliases) - Command._register_with_aliases(cmd_name, cmd_class, *aliases) - Command.class_variable_set(:@@command_override_policies, nil) - end - end - end -end diff --git a/lib/irb/easter-egg.rb b/lib/irb/easter-egg.rb deleted file mode 100644 index bf88848382..0000000000 --- a/lib/irb/easter-egg.rb +++ /dev/null @@ -1,150 +0,0 @@ -require "reline" - -module IRB - class << self - class Vec - def initialize(x, y, z) - @x, @y, @z = x, y, z - end - - attr_reader :x, :y, :z - - def sub(other) - Vec.new(@x - other.x, @y - other.y, @z - other.z) - end - - def dot(other) - @x*other.x + @y*other.y + @z*other.z - end - - def cross(other) - ox, oy, oz = other.x, other.y, other.z - Vec.new(@y*oz-@z*oy, @z*ox-@x*oz, @x*oy-@y*ox) - end - - def normalize - r = Math.sqrt(self.dot(self)) - Vec.new(@x / r, @y / r, @z / r) - end - end - - class Canvas - def initialize((h, w)) - @data = (0..h-2).map { [0] * w } - @scale = [w / 2.0, h-2].min - @center = Complex(w / 2, h-2) - end - - def line((x1, y1), (x2, y2)) - p1 = Complex(x1, y1) / 2 * @scale + @center - p2 = Complex(x2, y2) / 2 * @scale + @center - line0(p1, p2) - end - - private def line0(p1, p2) - mid = (p1 + p2) / 2 - if (p1 - p2).abs < 1 - x, y = mid.rect - @data[y / 2][x] |= (y % 2 > 1 ? 2 : 1) - else - line0(p1, mid) - line0(p2, mid) - end - end - - def draw - @data.each {|row| row.fill(0) } - yield - @data.map {|row| row.map {|n| " ',;"[n] }.join }.join("\n") - end - end - - class RubyModel - def initialize - @faces = init_ruby_model - end - - def init_ruby_model - cap_vertices = (0..5).map {|i| Vec.new(*Complex.polar(1, i * Math::PI / 3).rect, 1) } - middle_vertices = (0..5).map {|i| Vec.new(*Complex.polar(2, (i + 0.5) * Math::PI / 3).rect, 0) } - bottom_vertex = Vec.new(0, 0, -2) - - faces = [cap_vertices] - 6.times do |j| - i = j-1 - faces << [cap_vertices[i], middle_vertices[i], cap_vertices[j]] - faces << [cap_vertices[j], middle_vertices[i], middle_vertices[j]] - faces << [middle_vertices[i], bottom_vertex, middle_vertices[j]] - end - - faces - end - - def render_frame(i) - angle = i / 10.0 - dir = Vec.new(*Complex.polar(1, angle).rect, Math.sin(angle)).normalize - dir2 = Vec.new(*Complex.polar(1, angle - Math::PI/2).rect, 0) - up = dir.cross(dir2) - nm = dir.cross(up) - @faces.each do |vertices| - v0, v1, v2, = vertices - if v1.sub(v0).cross(v2.sub(v0)).dot(dir) > 0 - points = vertices.map {|p| [nm.dot(p), up.dot(p)] } - (points + [points[0]]).each_cons(2) do |p1, p2| - yield p1, p2 - end - end - end - end - end - - private def easter_egg_logo(type) - @easter_egg_logos ||= File.read(File.join(__dir__, 'ruby_logo.aa'), encoding: 'UTF-8:UTF-8') - .split(/TYPE: ([A-Z_]+)\n/)[1..] - .each_slice(2) - .to_h - @easter_egg_logos[type.to_s.upcase] - end - - private def easter_egg(type = nil) - print "\e[?1049h" - type ||= [:logo, :dancing].sample - case type - when :logo - Pager.page do |io| - logo_type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode_large : :ascii_large - io.write easter_egg_logo(logo_type) - STDIN.raw { STDIN.getc } if io == STDOUT - end - when :dancing - STDOUT.cooked do - interrupted = false - prev_trap = trap("SIGINT") { interrupted = true } - canvas = Canvas.new(Reline.get_screen_size) - Reline::IOGate.set_winch_handler do - canvas = Canvas.new(Reline.get_screen_size) - end - ruby_model = RubyModel.new - 0.step do |i| # TODO (0..).each needs Ruby 2.6 or later - buff = canvas.draw do - ruby_model.render_frame(i) do |p1, p2| - canvas.line(p1, p2) - end - end - buff[0, 20] = "\e[0mPress Ctrl+C to stop\e[31m\e[1m" - print "\e[H" + buff - sleep 0.05 - break if interrupted - end - rescue Interrupt - ensure - trap("SIGINT", prev_trap) - end - end - ensure - print "\e[0m\e[?1049l" - end - end -end - -IRB.__send__(:easter_egg, ARGV[0]&.to_sym) if $0 == __FILE__ diff --git a/lib/irb/ext/change-ws.rb b/lib/irb/ext/change-ws.rb deleted file mode 100644 index 60e8afe31f..0000000000 --- a/lib/irb/ext/change-ws.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true -# -# irb/ext/cb.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - class Context - - # Inherited from +TOPLEVEL_BINDING+. - def home_workspace - if defined? @home_workspace - @home_workspace - else - @home_workspace = workspace - end - end - - # Changes the current workspace to given object or binding. - # - # If the optional argument is omitted, the workspace will be - # #home_workspace which is inherited from +TOPLEVEL_BINDING+ or the main - # object, <code>IRB.conf[:MAIN_CONTEXT]</code> when irb was initialized. - # - # See IRB::WorkSpace.new for more information. - def change_workspace(*_main) - if _main.empty? - replace_workspace(home_workspace) - return main - end - - workspace = WorkSpace.new(_main[0]) - replace_workspace(workspace) - workspace.load_helper_methods_to_main - end - end -end diff --git a/lib/irb/ext/eval_history.rb b/lib/irb/ext/eval_history.rb deleted file mode 100644 index 6c21ff00ee..0000000000 --- a/lib/irb/ext/eval_history.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true -# -# history.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - - class Context - - NOPRINTING_IVARS.push "@eval_history_values" - - # See #set_last_value - alias _set_last_value set_last_value - - def set_last_value(value) - _set_last_value(value) - - if defined?(@eval_history) && @eval_history - @eval_history_values.push @line_no, @last_value - workspace.evaluate "__ = IRB.CurrentContext.instance_eval{@eval_history_values}" - end - - @last_value - end - - remove_method :eval_history= if method_defined?(:eval_history=) - # The command result history limit. This method is not available until - # #eval_history= was called with non-nil value (directly or via - # setting <code>IRB.conf[:EVAL_HISTORY]</code> in <code>.irbrc</code>). - attr_reader :eval_history - # Sets command result history limit. Default value is set from - # <code>IRB.conf[:EVAL_HISTORY]</code>. - # - # +no+ is an Integer or +nil+. - # - # Returns +no+ of history items if greater than 0. - # - # If +no+ is 0, the number of history items is unlimited. - # - # If +no+ is +nil+, execution result history isn't used (default). - # - # EvalHistory values are available via <code>__</code> variable, see - # IRB::EvalHistory. - def eval_history=(no) - if no - if defined?(@eval_history) && @eval_history - @eval_history_values.size(no) - else - @eval_history_values = EvalHistory.new(no) - IRB.conf[:__TMP__EHV__] = @eval_history_values - workspace.evaluate("__ = IRB.conf[:__TMP__EHV__]") - IRB.conf.delete(:__TMP_EHV__) - end - else - @eval_history_values = nil - end - @eval_history = no - end - end - - # Represents history of results of previously evaluated commands. - # - # Available via <code>__</code> variable, only if <code>IRB.conf[:EVAL_HISTORY]</code> - # or <code>IRB::CurrentContext().eval_history</code> is non-nil integer value - # (by default it is +nil+). - # - # Example (in `irb`): - # - # # Initialize history - # IRB::CurrentContext().eval_history = 10 - # # => 10 - # - # # Perform some commands... - # 1 + 2 - # # => 3 - # puts 'x' - # # x - # # => nil - # raise RuntimeError - # # ...error raised - # - # # Inspect history (format is "<item number> <evaluated value>": - # __ - # # => 1 10 - # # 2 3 - # # 3 nil - # - # __[1] - # # => 10 - # - class EvalHistory - - def initialize(size = 16) # :nodoc: - @size = size - @contents = [] - end - - def size(size) # :nodoc: - if size != 0 && size < @size - @contents = @contents[@size - size .. @size] - end - @size = size - end - - # Get one item of the content (both positive and negative indexes work). - def [](idx) - begin - if idx >= 0 - @contents.find{|no, val| no == idx}[1] - else - @contents[idx][1] - end - rescue NameError - nil - end - end - - def push(no, val) # :nodoc: - @contents.push [no, val] - @contents.shift if @size != 0 && @contents.size > @size - end - - alias real_inspect inspect - - def inspect # :nodoc: - if @contents.empty? - return real_inspect - end - - unless (last = @contents.pop)[1].equal?(self) - @contents.push last - last = nil - end - str = @contents.collect{|no, val| - if val.equal?(self) - "#{no} ...self-history..." - else - "#{no} #{val.inspect}" - end - }.join("\n") - if str == "" - str = "Empty." - end - @contents.push last if last - str - end - end -end diff --git a/lib/irb/ext/loader.rb b/lib/irb/ext/loader.rb deleted file mode 100644 index df5aaa8e5a..0000000000 --- a/lib/irb/ext/loader.rb +++ /dev/null @@ -1,127 +0,0 @@ -# frozen_string_literal: true -# -# loader.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - # Raised in the event of an exception in a file loaded from an Irb session - class LoadAbort < Exception;end - - # Provides a few commands for loading files within an irb session. - # - # See ExtendCommandBundle for more information. - module IrbLoader - - alias ruby_load load - alias ruby_require require - - # Loads the given file similarly to Kernel#load - def irb_load(fn, priv = nil) - path = search_file_from_ruby_path(fn) - raise LoadError, "No such file to load -- #{fn}" unless path - - load_file(path, priv) - end - - def search_file_from_ruby_path(fn) # :nodoc: - if File.absolute_path?(fn) - return fn if File.exist?(fn) - return nil - end - - for path in $: - if File.exist?(f = File.join(path, fn)) - return f - end - end - return nil - end - - # Loads a given file in the current session and displays the source lines - # - # See Irb#suspend_input_method for more information. - def source_file(path) - irb = irb_context.irb - irb.suspend_name(path, File.basename(path)) do - FileInputMethod.open(path) do |io| - irb.suspend_input_method(io) do - |back_io| - irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - irb.eval_input - else - begin - irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - end - end - end - end - - # Loads the given file in the current session's context and evaluates it. - # - # See Irb#suspend_input_method for more information. - def load_file(path, priv = nil) - irb = irb_context.irb - irb.suspend_name(path, File.basename(path)) do - - if priv - ws = WorkSpace.new(Module.new) - else - ws = WorkSpace.new - end - irb.suspend_workspace(ws) do - FileInputMethod.open(path) do |io| - irb.suspend_input_method(io) do - |back_io| - irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - irb.eval_input - else - begin - irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - end - end - end - end - end - - def old # :nodoc: - back_io = @io - back_path = irb_path - back_name = @irb_name - back_scanner = @irb.scanner - begin - @io = FileInputMethod.new(path) - @irb_name = File.basename(path) - self.irb_path = path - @irb.signal_status(:IN_LOAD) do - if back_io.kind_of?(FileInputMethod) - @irb.eval_input - else - begin - @irb.eval_input - rescue LoadAbort - print "load abort!!\n" - end - end - end - ensure - @io = back_io - @irb_name = back_name - self.irb_path = back_path - @irb.scanner = back_scanner - end - end - end -end diff --git a/lib/irb/ext/multi-irb.rb b/lib/irb/ext/multi-irb.rb deleted file mode 100644 index 9f234f0cdc..0000000000 --- a/lib/irb/ext/multi-irb.rb +++ /dev/null @@ -1,258 +0,0 @@ -# frozen_string_literal: true -# -# irb/multi-irb.rb - multiple irb module -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - class JobManager # :nodoc: - - # Creates a new JobManager object - def initialize - @jobs = [] - @current_job = nil - end - - # The active irb session - attr_accessor :current_job - - # The total number of irb sessions, used to set +irb_name+ of the current - # Context. - def n_jobs - @jobs.size - end - - # Returns the thread for the given +key+ object, see #search for more - # information. - def thread(key) - th, = search(key) - th - end - - # Returns the irb session for the given +key+ object, see #search for more - # information. - def irb(key) - _, irb = search(key) - irb - end - - # Returns the top level thread. - def main_thread - @jobs[0][0] - end - - # Returns the top level irb session. - def main_irb - @jobs[0][1] - end - - # Add the given +irb+ session to the jobs Array. - def insert(irb) - @jobs.push [Thread.current, irb] - end - - # Changes the current active irb session to the given +key+ in the jobs - # Array. - # - # Raises an IrbAlreadyDead exception if the given +key+ is no longer alive. - # - # If the given irb session is already active, an IrbSwitchedToCurrentThread - # exception is raised. - def switch(key) - th, irb = search(key) - fail IrbAlreadyDead unless th.alive? - fail IrbSwitchedToCurrentThread if th == Thread.current - @current_job = irb - th.run - Thread.stop - @current_job = irb(Thread.current) - end - - # Terminates the irb sessions specified by the given +keys+. - # - # Raises an IrbAlreadyDead exception if one of the given +keys+ is already - # terminated. - # - # See Thread#exit for more information. - def kill(*keys) - for key in keys - th, _ = search(key) - fail IrbAlreadyDead unless th.alive? - th.exit - end - end - - # Returns the associated job for the given +key+. - # - # If given an Integer, it will return the +key+ index for the jobs Array. - # - # When an instance of Irb is given, it will return the irb session - # associated with +key+. - # - # If given an instance of Thread, it will return the associated thread - # +key+ using Object#=== on the jobs Array. - # - # Otherwise returns the irb session with the same top-level binding as the - # given +key+. - # - # Raises a NoSuchJob exception if no job can be found with the given +key+. - def search(key) - job = case key - when Integer - @jobs[key] - when Irb - @jobs.find{|k, v| v.equal?(key)} - when Thread - @jobs.assoc(key) - else - @jobs.find{|k, v| v.context.main.equal?(key)} - end - fail NoSuchJob, key if job.nil? - job - end - - # Deletes the job at the given +key+. - def delete(key) - case key - when Integer - fail NoSuchJob, key unless @jobs[key] - @jobs[key] = nil - else - catch(:EXISTS) do - @jobs.each_index do - |i| - if @jobs[i] and (@jobs[i][0] == key || - @jobs[i][1] == key || - @jobs[i][1].context.main.equal?(key)) - @jobs[i] = nil - throw :EXISTS - end - end - fail NoSuchJob, key - end - end - until assoc = @jobs.pop; end unless @jobs.empty? - @jobs.push assoc - end - - # Outputs a list of jobs, see the irb command +irb_jobs+, or +jobs+. - def inspect - ary = [] - @jobs.each_index do - |i| - th, irb = @jobs[i] - next if th.nil? - - if th.alive? - if th.stop? - t_status = "stop" - else - t_status = "running" - end - else - t_status = "exited" - end - ary.push format("#%d->%s on %s (%s: %s)", - i, - irb.context.irb_name, - irb.context.main, - th, - t_status) - end - ary.join("\n") - end - end - - @JobManager = JobManager.new - - # The current JobManager in the session - def IRB.JobManager # :nodoc: - @JobManager - end - - # The current Context in this session - def IRB.CurrentContext # :nodoc: - IRB.JobManager.irb(Thread.current).context - end - - # Creates a new IRB session, see Irb.new. - # - # The optional +file+ argument is given to Context.new, along with the - # workspace created with the remaining arguments, see WorkSpace.new - def IRB.irb(file = nil, *main) # :nodoc: - workspace = WorkSpace.new(*main) - parent_thread = Thread.current - Thread.start do - begin - irb = Irb.new(workspace, file) - rescue - print "Subirb can't start with context(self): ", workspace.main.inspect, "\n" - print "return to main irb\n" - Thread.pass - Thread.main.wakeup - Thread.exit - end - @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] - @JobManager.insert(irb) - @JobManager.current_job = irb - begin - system_exit = false - catch(:IRB_EXIT) do - irb.eval_input - end - rescue SystemExit - system_exit = true - raise - #fail - ensure - unless system_exit - @JobManager.delete(irb) - if @JobManager.current_job == irb - if parent_thread.alive? - @JobManager.current_job = @JobManager.irb(parent_thread) - parent_thread.run - else - @JobManager.current_job = @JobManager.main_irb - @JobManager.main_thread.run - end - end - end - end - end - Thread.stop - @JobManager.current_job = @JobManager.irb(Thread.current) - end - - @CONF[:SINGLE_IRB_MODE] = false - @JobManager.insert(@CONF[:MAIN_CONTEXT].irb) - @JobManager.current_job = @CONF[:MAIN_CONTEXT].irb - - class Irb - def signal_handle - unless @context.ignore_sigint? - print "\nabort!!\n" if @context.verbose? - exit - end - - case @signal_status - when :IN_INPUT - print "^C\n" - IRB.JobManager.thread(self).raise RubyLex::TerminateLineInput - when :IN_EVAL - IRB.irb_abort(self) - when :IN_LOAD - IRB.irb_abort(self, LoadAbort) - when :IN_IRB - # ignore - else - # ignore other cases as well - end - end - end - - trap("SIGINT") do - @JobManager.current_job.signal_handle - Thread.stop - end - -end diff --git a/lib/irb/ext/tracer.rb b/lib/irb/ext/tracer.rb deleted file mode 100644 index fd6daa88ae..0000000000 --- a/lib/irb/ext/tracer.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true -# -# irb/lib/tracer.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# -# Loading the gem "tracer" will cause it to extend IRB commands with: -# https://github.com/ruby/tracer/blob/v0.2.2/lib/tracer/irb.rb -begin - require "tracer" -rescue LoadError - $stderr.puts "Tracer extension of IRB is enabled but tracer gem wasn't found." - return # This is about to disable loading below -end - -module IRB - class CallTracer < ::CallTracer - IRB_DIR = File.expand_path('../..', __dir__) - - def skip?(tp) - super || tp.path.match?(IRB_DIR) || tp.path.match?('<internal:prelude>') - end - end - class WorkSpace - alias __evaluate__ evaluate - # Evaluate the context of this workspace and use the Tracer library to - # output the exact lines of code are being executed in chronological order. - # - # See https://github.com/ruby/tracer for more information. - def evaluate(statements, file = __FILE__, line = __LINE__) - if IRB.conf[:USE_TRACER] == true - CallTracer.new(colorize: Color.colorable?).start do - __evaluate__(statements, file, line) - end - else - __evaluate__(statements, file, line) - end - end - end -end diff --git a/lib/irb/ext/use-loader.rb b/lib/irb/ext/use-loader.rb deleted file mode 100644 index c8a3ea1fe8..0000000000 --- a/lib/irb/ext/use-loader.rb +++ /dev/null @@ -1,67 +0,0 @@ -# frozen_string_literal: true -# -# use-loader.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative "../command/load" -require_relative "loader" - -class Object - alias __original__load__IRB_use_loader__ load - alias __original__require__IRB_use_loader__ require -end - -module IRB - module ExtendCommandBundle - remove_method :irb_load if method_defined?(:irb_load) - # Loads the given file similarly to Kernel#load, see IrbLoader#irb_load - def irb_load(*opts, &b) - Command::Load.execute(irb_context, *opts, &b) - end - remove_method :irb_require if method_defined?(:irb_require) - # Loads the given file similarly to Kernel#require - def irb_require(*opts, &b) - Command::Require.execute(irb_context, *opts, &b) - end - end - - class Context - - IRB.conf[:USE_LOADER] = false - - # Returns whether +irb+'s own file reader method is used by - # +load+/+require+ or not. - # - # This mode is globally affected (irb-wide). - def use_loader - IRB.conf[:USE_LOADER] - end - - alias use_loader? use_loader - - remove_method :use_loader= if method_defined?(:use_loader=) - # Sets <code>IRB.conf[:USE_LOADER]</code> - # - # See #use_loader for more information. - def use_loader=(opt) - - if IRB.conf[:USE_LOADER] != opt - IRB.conf[:USE_LOADER] = opt - if opt - (class<<workspace.main;self;end).instance_eval { - alias_method :load, :irb_load - alias_method :require, :irb_require - } - else - (class<<workspace.main;self;end).instance_eval { - alias_method :load, :__original__load__IRB_use_loader__ - alias_method :require, :__original__require__IRB_use_loader__ - } - end - end - print "Switch to load/require#{unless use_loader; ' non';end} trace mode.\n" if verbose? - opt - end - end -end diff --git a/lib/irb/ext/workspaces.rb b/lib/irb/ext/workspaces.rb deleted file mode 100644 index da09faa83e..0000000000 --- a/lib/irb/ext/workspaces.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true -# -# push-ws.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - class Context - # Creates a new workspace with the given object or binding, and appends it - # onto the current #workspaces stack. - # - # See IRB::Context#change_workspace and IRB::WorkSpace.new for more - # information. - def push_workspace(*_main) - if _main.empty? - if @workspace_stack.size > 1 - # swap the top two workspaces - previous_workspace, current_workspace = @workspace_stack.pop(2) - @workspace_stack.push current_workspace, previous_workspace - end - else - new_workspace = WorkSpace.new(workspace.binding, _main[0]) - @workspace_stack.push new_workspace - new_workspace.load_helper_methods_to_main - end - end - - # Removes the last element from the current #workspaces stack and returns - # it, or +nil+ if the current workspace stack is empty. - # - # Also, see #push_workspace. - def pop_workspace - @workspace_stack.pop if @workspace_stack.size > 1 - end - end -end diff --git a/lib/irb/frame.rb b/lib/irb/frame.rb deleted file mode 100644 index 4b697c8719..0000000000 --- a/lib/irb/frame.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true -# -# frame.rb - -# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd) -# - -module IRB - class Frame - class FrameOverflow < StandardError - def initialize - super("frame overflow") - end - end - class FrameUnderflow < StandardError - def initialize - super("frame underflow") - end - end - - # Default number of stack frames - INIT_STACK_TIMES = 3 - # Default number of frames offset - CALL_STACK_OFFSET = 3 - - # Creates a new stack frame - def initialize - @frames = [TOPLEVEL_BINDING] * INIT_STACK_TIMES - end - - # Used by Kernel#set_trace_func to register each event in the call stack - def trace_func(event, file, line, id, binding) - case event - when 'call', 'class' - @frames.push binding - when 'return', 'end' - @frames.pop - end - end - - # Returns the +n+ number of frames on the call stack from the last frame - # initialized. - # - # Raises FrameUnderflow if there are no frames in the given stack range. - def top(n = 0) - bind = @frames[-(n + CALL_STACK_OFFSET)] - fail FrameUnderflow unless bind - bind - end - - # Returns the +n+ number of frames on the call stack from the first frame - # initialized. - # - # Raises FrameOverflow if there are no frames in the given stack range. - def bottom(n = 0) - bind = @frames[n] - fail FrameOverflow unless bind - bind - end - - # Convenience method for Frame#bottom - def Frame.bottom(n = 0) - @backtrace.bottom(n) - end - - # Convenience method for Frame#top - def Frame.top(n = 0) - @backtrace.top(n) - end - - # Returns the binding context of the caller from the last frame initialized - def Frame.sender - eval "self", @backtrace.top - end - - @backtrace = Frame.new - set_trace_func proc{|event, file, line, id, binding, klass| - @backtrace.trace_func(event, file, line, id, binding) - } - end -end diff --git a/lib/irb/help.rb b/lib/irb/help.rb deleted file mode 100644 index a24bc10a15..0000000000 --- a/lib/irb/help.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true -# -# irb/help.rb - print usage module -# by Keiju ISHITSUKA(keiju@ishitsuka.com) -# - -module IRB - # Outputs the irb help message, see IRB@Command-Line+Options. - def IRB.print_usage # :nodoc: - lc = IRB.conf[:LC_MESSAGES] - path = lc.find("irb/help-message") - space_line = false - File.open(path){|f| - f.each_line do |l| - if /^\s*$/ =~ l - lc.puts l unless space_line - space_line = true - next - end - space_line = false - - l.sub!(/#.*$/, "") - next if /^\s*$/ =~ l - lc.puts l - end - } - end -end diff --git a/lib/irb/helper_method.rb b/lib/irb/helper_method.rb deleted file mode 100644 index f1f6fff915..0000000000 --- a/lib/irb/helper_method.rb +++ /dev/null @@ -1,29 +0,0 @@ -require_relative "helper_method/base" - -module IRB - module HelperMethod - @helper_methods = {} - - class << self - attr_reader :helper_methods - - def register(name, helper_class) - @helper_methods[name] = helper_class - - if defined?(HelpersContainer) - HelpersContainer.install_helper_methods - end - end - - def all_helper_methods_info - @helper_methods.map do |name, helper_class| - { display_name: name, description: helper_class.description } - end - end - end - - # Default helper_methods - require_relative "helper_method/conf" - register(:conf, HelperMethod::Conf) - end -end diff --git a/lib/irb/helper_method/base.rb b/lib/irb/helper_method/base.rb deleted file mode 100644 index a68001ed28..0000000000 --- a/lib/irb/helper_method/base.rb +++ /dev/null @@ -1,16 +0,0 @@ -require "singleton" - -module IRB - module HelperMethod - class Base - include Singleton - - class << self - def description(description = nil) - @description = description if description - @description - end - end - end - end -end diff --git a/lib/irb/helper_method/conf.rb b/lib/irb/helper_method/conf.rb deleted file mode 100644 index 718ed279c0..0000000000 --- a/lib/irb/helper_method/conf.rb +++ /dev/null @@ -1,11 +0,0 @@ -module IRB - module HelperMethod - class Conf < Base - description "Returns the current IRB context." - - def execute - IRB.CurrentContext - end - end - end -end diff --git a/lib/irb/history.rb b/lib/irb/history.rb deleted file mode 100644 index 25fa71b9c3..0000000000 --- a/lib/irb/history.rb +++ /dev/null @@ -1,112 +0,0 @@ -require "pathname" - -module IRB - module History - class << self - # Integer representation of <code>IRB.conf[:HISTORY_FILE]</code>. - def save_history - IRB.conf[:SAVE_HISTORY].to_i - end - - def save_history? - !save_history.zero? - end - - def infinite? - save_history.negative? - end - - # Might be nil when HOME and XDG_CONFIG_HOME are not available. - def history_file - if (history_file = IRB.conf[:HISTORY_FILE]) - File.expand_path(history_file) - else - IRB.rc_file("_history") - end - end - end - end - - module HistorySavingAbility # :nodoc: - def support_history_saving? - true - end - - def reset_history_counter - @loaded_history_lines = self.class::HISTORY.size - end - - def load_history - history_file = History.history_file - return unless File.exist?(history_file.to_s) - - history = self.class::HISTORY - - File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| - f.each { |l| - l = l.chomp - if self.class == RelineInputMethod and history.last&.end_with?("\\") - history.last.delete_suffix!("\\") - history.last << "\n" << l - else - history << l - end - } - end - @loaded_history_lines = history.size - @loaded_history_mtime = File.mtime(history_file) - end - - def save_history - return unless History.save_history? - return unless (history_file = History.history_file) - unless ensure_history_file_writable(history_file) - warn <<~WARN - Can't write history to #{History.history_file.inspect} due to insufficient permissions. - Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. - WARN - return - end - - history = self.class::HISTORY.to_a - - if File.exist?(history_file) && - File.mtime(history_file) != @loaded_history_mtime - history = history[@loaded_history_lines..-1] if @loaded_history_lines - append_history = true - end - - File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| - hist = history.map { |l| l.scrub.split("\n").join("\\\n") } - - unless append_history || History.infinite? - # Check size before slicing because array.last(huge_number) raises RangeError. - hist = hist.last(History.save_history) if hist.size > History.save_history - end - - f.puts(hist) - end - end - - private - - # Returns boolean whether writing to +history_file+ will be possible. - # Permissions of already existing +history_file+ are changed to - # owner-only-readable if necessary [BUG #7694]. - def ensure_history_file_writable(history_file) - history_file = Pathname.new(history_file) - - return false unless history_file.dirname.writable? - return true unless history_file.exist? - - begin - if history_file.stat.mode & 0o66 != 0 - history_file.chmod 0o600 - end - true - rescue Errno::EPERM # no permissions - false - end - end - end -end diff --git a/lib/irb/init.rb b/lib/irb/init.rb deleted file mode 100644 index d474bd41d6..0000000000 --- a/lib/irb/init.rb +++ /dev/null @@ -1,538 +0,0 @@ -# frozen_string_literal: true -# -# irb/init.rb - irb initialize module -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - @CONF = {} - @INITIALIZED = false - # Displays current configuration. - # - # Modifying the configuration is achieved by sending a message to IRB.conf. - # - # See IRB@Configuration for more information. - def IRB.conf - @CONF - end - - def @CONF.inspect - array = [] - for k, v in sort{|a1, a2| a1[0].id2name <=> a2[0].id2name} - case k - when :MAIN_CONTEXT, :__TMP__EHV__ - array.push format("CONF[:%s]=...myself...", k.id2name) - when :PROMPT - s = v.collect{ - |kk, vv| - ss = vv.collect{|kkk, vvv| ":#{kkk.id2name}=>#{vvv.inspect}"} - format(":%s=>{%s}", kk.id2name, ss.join(", ")) - } - array.push format("CONF[:%s]={%s}", k.id2name, s.join(", ")) - else - array.push format("CONF[:%s]=%s", k.id2name, v.inspect) - end - end - array.join("\n") - end - - # Returns the current version of IRB, including release version and last - # updated date. - def IRB.version - format("irb %s (%s)", @RELEASE_VERSION, @LAST_UPDATE_DATE) - end - - def IRB.initialized? - !!@INITIALIZED - end - - # initialize config - def IRB.setup(ap_path, argv: ::ARGV) - IRB.init_config(ap_path) - IRB.init_error - IRB.parse_opts(argv: argv) - IRB.run_config - IRB.validate_config - IRB.load_modules - - unless @CONF[:PROMPT][@CONF[:PROMPT_MODE]] - fail UndefinedPromptMode, @CONF[:PROMPT_MODE] - end - @INITIALIZED = true - end - - # @CONF default setting - def IRB.init_config(ap_path) - # default configurations - unless ap_path and @CONF[:AP_NAME] - ap_path = File.join(File.dirname(File.dirname(__FILE__)), "irb.rb") - end - @CONF[:VERSION] = version - @CONF[:AP_NAME] = File::basename(ap_path, ".rb") - - @CONF[:IRB_NAME] = "irb" - @CONF[:IRB_LIB_PATH] = File.dirname(__FILE__) - - @CONF[:RC] = true - @CONF[:LOAD_MODULES] = [] - @CONF[:IRB_RC] = nil - - @CONF[:USE_SINGLELINE] = false unless defined?(ReadlineInputMethod) - @CONF[:USE_COLORIZE] = (nc = ENV['NO_COLOR']).nil? || nc.empty? - @CONF[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "true") != "false" - @CONF[:COMPLETOR] = ENV["IRB_COMPLETOR"]&.to_sym - @CONF[:INSPECT_MODE] = true - @CONF[:USE_TRACER] = false - @CONF[:USE_LOADER] = false - @CONF[:IGNORE_SIGINT] = true - @CONF[:IGNORE_EOF] = false - @CONF[:USE_PAGER] = true - @CONF[:EXTRA_DOC_DIRS] = [] - @CONF[:ECHO] = nil - @CONF[:ECHO_ON_ASSIGNMENT] = nil - @CONF[:VERBOSE] = nil - - @CONF[:EVAL_HISTORY] = nil - @CONF[:SAVE_HISTORY] = 1000 - - @CONF[:BACK_TRACE_LIMIT] = 16 - - @CONF[:PROMPT] = { - :NULL => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n" - }, - :DEFAULT => { - :PROMPT_I => "%N(%m):%03n> ", - :PROMPT_S => "%N(%m):%03n%l ", - :PROMPT_C => "%N(%m):%03n* ", - :RETURN => "=> %s\n" - }, - :CLASSIC => { - :PROMPT_I => "%N(%m):%03n:%i> ", - :PROMPT_S => "%N(%m):%03n:%i%l ", - :PROMPT_C => "%N(%m):%03n:%i* ", - :RETURN => "%s\n" - }, - :SIMPLE => { - :PROMPT_I => ">> ", - :PROMPT_S => "%l> ", - :PROMPT_C => "?> ", - :RETURN => "=> %s\n" - }, - :INF_RUBY => { - :PROMPT_I => "%N(%m):%03n> ", - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => "%s\n", - :AUTO_INDENT => true - }, - :XMP => { - :PROMPT_I => nil, - :PROMPT_S => nil, - :PROMPT_C => nil, - :RETURN => " ==>%s\n" - } - } - - @CONF[:PROMPT_MODE] = (STDIN.tty? ? :DEFAULT : :NULL) - @CONF[:AUTO_INDENT] = true - - @CONF[:CONTEXT_MODE] = 4 # use a copy of TOPLEVEL_BINDING - @CONF[:SINGLE_IRB] = false - - @CONF[:MEASURE] = false - @CONF[:MEASURE_PROC] = {} - @CONF[:MEASURE_PROC][:TIME] = proc { |context, code, line_no, &block| - time = Time.now - result = block.() - now = Time.now - puts 'processing time: %fs' % (now - time) if IRB.conf[:MEASURE] - result - } - # arg can be either a symbol for the mode (:cpu, :wall, ..) or a hash for - # a more complete configuration. - # See https://github.com/tmm1/stackprof#all-options. - @CONF[:MEASURE_PROC][:STACKPROF] = proc { |context, code, line_no, arg, &block| - return block.() unless IRB.conf[:MEASURE] - success = false - begin - require 'stackprof' - success = true - rescue LoadError - puts 'Please run "gem install stackprof" before measuring by StackProf.' - end - if success - result = nil - arg = { mode: arg || :cpu } unless arg.is_a?(Hash) - stackprof_result = StackProf.run(**arg) do - result = block.() - end - case stackprof_result - when File - puts "StackProf report saved to #{stackprof_result.path}" - when Hash - StackProf::Report.new(stackprof_result).print_text - else - puts "Stackprof ran with #{arg.inspect}" - end - result - else - block.() - end - } - @CONF[:MEASURE_CALLBACKS] = [] - - @CONF[:LC_MESSAGES] = Locale.new - - @CONF[:AT_EXIT] = [] - - @CONF[:COMMAND_ALIASES] = { - # Symbol aliases - :'$' => :show_source, - :'@' => :whereami, - } - end - - def IRB.set_measure_callback(type = nil, arg = nil, &block) - added = nil - if type - type_sym = type.upcase.to_sym - if IRB.conf[:MEASURE_PROC][type_sym] - added = [type_sym, IRB.conf[:MEASURE_PROC][type_sym], arg] - end - elsif IRB.conf[:MEASURE_PROC][:CUSTOM] - added = [:CUSTOM, IRB.conf[:MEASURE_PROC][:CUSTOM], arg] - elsif block_given? - added = [:BLOCK, block, arg] - found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } - if found - found[1] = block - return added - else - IRB.conf[:MEASURE_CALLBACKS] << added - return added - end - else - added = [:TIME, IRB.conf[:MEASURE_PROC][:TIME], arg] - end - if added - IRB.conf[:MEASURE] = true - found = IRB.conf[:MEASURE_CALLBACKS].find{ |m| m[0] == added[0] && m[2] == added[2] } - if found - # already added - nil - else - IRB.conf[:MEASURE_CALLBACKS] << added if added - added - end - else - nil - end - end - - def IRB.unset_measure_callback(type = nil) - if type.nil? - IRB.conf[:MEASURE_CALLBACKS].clear - else - type_sym = type.upcase.to_sym - IRB.conf[:MEASURE_CALLBACKS].reject!{ |t, | t == type_sym } - end - IRB.conf[:MEASURE] = nil if IRB.conf[:MEASURE_CALLBACKS].empty? - end - - def IRB.init_error - @CONF[:LC_MESSAGES].load("irb/error.rb") - end - - # option analyzing - def IRB.parse_opts(argv: ::ARGV) - load_path = [] - while opt = argv.shift - case opt - when "-f" - @CONF[:RC] = false - when "-d" - $DEBUG = true - $VERBOSE = true - when "-w" - Warning[:deprecated] = $VERBOSE = true - when /^-W(.+)?/ - opt = $1 || argv.shift - case opt - when "0" - $VERBOSE = nil - when "1" - $VERBOSE = false - else - Warning[:deprecated] = $VERBOSE = true - end - when /^-r(.+)?/ - opt = $1 || argv.shift - @CONF[:LOAD_MODULES].push opt if opt - when /^-I(.+)?/ - opt = $1 || argv.shift - load_path.concat(opt.split(File::PATH_SEPARATOR)) if opt - when '-U' - set_encoding("UTF-8", "UTF-8") - when /^-E(.+)?/, /^--encoding(?:=(.+))?/ - opt = $1 || argv.shift - set_encoding(*opt.split(':', 2)) - when "--inspect" - if /^-/ !~ argv.first - @CONF[:INSPECT_MODE] = argv.shift - else - @CONF[:INSPECT_MODE] = true - end - when "--noinspect" - @CONF[:INSPECT_MODE] = false - when "--no-pager" - @CONF[:USE_PAGER] = false - when "--singleline", "--readline", "--legacy" - @CONF[:USE_SINGLELINE] = true - when "--nosingleline", "--noreadline" - @CONF[:USE_SINGLELINE] = false - when "--multiline", "--reidline" - if opt == "--reidline" - warn <<~MSG.strip - --reidline is deprecated, please use --multiline instead. - MSG - end - - @CONF[:USE_MULTILINE] = true - when "--nomultiline", "--noreidline" - if opt == "--noreidline" - warn <<~MSG.strip - --noreidline is deprecated, please use --nomultiline instead. - MSG - end - - @CONF[:USE_MULTILINE] = false - when /^--extra-doc-dir(?:=(.+))?/ - opt = $1 || argv.shift - @CONF[:EXTRA_DOC_DIRS] << opt - when "--echo" - @CONF[:ECHO] = true - when "--noecho" - @CONF[:ECHO] = false - when "--echo-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = true - when "--noecho-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = false - when "--truncate-echo-on-assignment" - @CONF[:ECHO_ON_ASSIGNMENT] = :truncate - when "--verbose" - @CONF[:VERBOSE] = true - when "--noverbose" - @CONF[:VERBOSE] = false - when "--colorize" - @CONF[:USE_COLORIZE] = true - when "--nocolorize" - @CONF[:USE_COLORIZE] = false - when "--autocomplete" - @CONF[:USE_AUTOCOMPLETE] = true - when "--noautocomplete" - @CONF[:USE_AUTOCOMPLETE] = false - when "--regexp-completor" - @CONF[:COMPLETOR] = :regexp - when "--type-completor" - @CONF[:COMPLETOR] = :type - when /^--prompt-mode(?:=(.+))?/, /^--prompt(?:=(.+))?/ - opt = $1 || argv.shift - prompt_mode = opt.upcase.tr("-", "_").intern - @CONF[:PROMPT_MODE] = prompt_mode - when "--noprompt" - @CONF[:PROMPT_MODE] = :NULL - when "--script" - noscript = false - when "--noscript" - noscript = true - when "--inf-ruby-mode" - @CONF[:PROMPT_MODE] = :INF_RUBY - when "--sample-book-mode", "--simple-prompt" - @CONF[:PROMPT_MODE] = :SIMPLE - when "--tracer" - @CONF[:USE_TRACER] = true - when /^--back-trace-limit(?:=(.+))?/ - @CONF[:BACK_TRACE_LIMIT] = ($1 || argv.shift).to_i - when /^--context-mode(?:=(.+))?/ - @CONF[:CONTEXT_MODE] = ($1 || argv.shift).to_i - when "--single-irb" - @CONF[:SINGLE_IRB] = true - when "-v", "--version" - print IRB.version, "\n" - exit 0 - when "-h", "--help" - require_relative "help" - IRB.print_usage - exit 0 - when "--" - if !noscript && (opt = argv.shift) - @CONF[:SCRIPT] = opt - $0 = opt - end - break - when /^-./ - fail UnrecognizedSwitch, opt - else - if noscript - argv.unshift(opt) - else - @CONF[:SCRIPT] = opt - $0 = opt - end - break - end - end - - load_path.collect! do |path| - /\A\.\// =~ path ? path : File.expand_path(path) - end - $LOAD_PATH.unshift(*load_path) - end - - # Run the config file - def IRB.run_config - if @CONF[:RC] - irbrc_files.each do |rc| - load rc - rescue StandardError, ScriptError => e - warn "Error loading RC file '#{rc}':\n#{e.full_message(highlight: false)}" - end - end - end - - IRBRC_EXT = "rc" - - def IRB.rc_file(ext) - prepare_irbrc_name_generators - - # When irbrc exist in default location - if (rcgen = @existing_rc_name_generators.first) - return rcgen.call(ext) - end - - # When irbrc does not exist in default location - rc_file_generators do |rcgen| - return rcgen.call(ext) - end - - # When HOME and XDG_CONFIG_HOME are not available - nil - end - - def IRB.irbrc_files - prepare_irbrc_name_generators - @irbrc_files - end - - def IRB.validate_config - conf[:IRB_NAME] = conf[:IRB_NAME].to_s - - irb_rc = conf[:IRB_RC] - unless irb_rc.nil? || irb_rc.respond_to?(:call) - raise_validation_error "IRB.conf[:IRB_RC] should be a callable object. Got #{irb_rc.inspect}." - end - - back_trace_limit = conf[:BACK_TRACE_LIMIT] - unless back_trace_limit.is_a?(Integer) - raise_validation_error "IRB.conf[:BACK_TRACE_LIMIT] should be an integer. Got #{back_trace_limit.inspect}." - end - - prompt = conf[:PROMPT] - unless prompt.is_a?(Hash) - msg = "IRB.conf[:PROMPT] should be a Hash. Got #{prompt.inspect}." - - if prompt.is_a?(Symbol) - msg += " Did you mean to set `IRB.conf[:PROMPT_MODE]`?" - end - - raise_validation_error msg - end - - eval_history = conf[:EVAL_HISTORY] - unless eval_history.nil? || eval_history.is_a?(Integer) - raise_validation_error "IRB.conf[:EVAL_HISTORY] should be an integer. Got #{eval_history.inspect}." - end - end - - def IRB.raise_validation_error(msg) - raise TypeError, msg, @irbrc_files - end - - # loading modules - def IRB.load_modules - for m in @CONF[:LOAD_MODULES] - begin - require m - rescue LoadError => err - warn "#{err.class}: #{err}", uplevel: 0 - end - end - end - - class << IRB - private - - def prepare_irbrc_name_generators - return if @existing_rc_name_generators - - @existing_rc_name_generators = [] - @irbrc_files = [] - rc_file_generators do |rcgen| - irbrc = rcgen.call(IRBRC_EXT) - if File.exist?(irbrc) - @irbrc_files << irbrc - @existing_rc_name_generators << rcgen - end - end - generate_current_dir_irbrc_files.each do |irbrc| - @irbrc_files << irbrc if File.exist?(irbrc) - end - @irbrc_files.uniq! - end - - # enumerate possible rc-file base name generators - def rc_file_generators - if irbrc = ENV["IRBRC"] - yield proc{|rc| rc == "rc" ? irbrc : irbrc+rc} - end - if xdg_config_home = ENV["XDG_CONFIG_HOME"] - irb_home = File.join(xdg_config_home, "irb") - if File.directory?(irb_home) - yield proc{|rc| irb_home + "/irb#{rc}"} - end - end - if home = ENV["HOME"] - yield proc{|rc| home+"/.irb#{rc}"} - if xdg_config_home.nil? || xdg_config_home.empty? - yield proc{|rc| home+"/.config/irb/irb#{rc}"} - end - end - end - - # possible irbrc files in current directory - def generate_current_dir_irbrc_files - current_dir = Dir.pwd - %w[.irbrc irbrc _irbrc $irbrc].map { |file| "#{current_dir}/#{file}" } - end - - def set_encoding(extern, intern = nil, override: true) - verbose, $VERBOSE = $VERBOSE, nil - Encoding.default_external = extern unless extern.nil? || extern.empty? - Encoding.default_internal = intern unless intern.nil? || intern.empty? - [$stdin, $stdout, $stderr].each do |io| - io.set_encoding(extern, intern) - end - if override - @CONF[:LC_MESSAGES].instance_variable_set(:@override_encoding, extern) - else - @CONF[:LC_MESSAGES].instance_variable_set(:@encoding, extern) - end - ensure - $VERBOSE = verbose - end - end -end diff --git a/lib/irb/input-method.rb b/lib/irb/input-method.rb deleted file mode 100644 index 260d9a1cbf..0000000000 --- a/lib/irb/input-method.rb +++ /dev/null @@ -1,515 +0,0 @@ -# frozen_string_literal: true -# -# irb/input-method.rb - input methods used irb -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative 'completion' -require_relative "history" -require 'io/console' -require 'reline' - -module IRB - class InputMethod - BASIC_WORD_BREAK_CHARACTERS = " \t\n`><=;|&{(" - - # The irb prompt associated with this input method - attr_accessor :prompt - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - fail NotImplementedError - end - public :gets - - def winsize - if instance_variable_defined?(:@stdout) && @stdout.tty? - @stdout.winsize - else - [24, 80] - end - end - - # Whether this input method is still readable when there is no more data to - # read. - # - # See IO#eof for more information. - def readable_after_eof? - false - end - - def support_history_saving? - false - end - - def prompting? - false - end - - # For debug message - def inspect - 'Abstract InputMethod' - end - end - - class StdioInputMethod < InputMethod - # Creates a new input method object - def initialize - @line_no = 0 - @line = [] - @stdin = IO.open(STDIN.to_i, :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") - @stdout = IO.open(STDOUT.to_i, 'w', :external_encoding => IRB.conf[:LC_MESSAGES].encoding, :internal_encoding => "-") - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - # Workaround for debug compatibility test https://github.com/ruby/debug/pull/1100 - puts if ENV['RUBY_DEBUG_TEST_UI'] - - print @prompt - line = @stdin.gets - @line[@line_no += 1] = line - end - - # Whether the end of this input method has been reached, returns +true+ if - # there is no more data to read. - # - # See IO#eof? for more information. - def eof? - if @stdin.wait_readable(0.00001) - c = @stdin.getc - result = c.nil? ? true : false - @stdin.ungetc(c) unless c.nil? - result - else # buffer is empty - false - end - end - - # Whether this input method is still readable when there is no more data to - # read. - # - # See IO#eof for more information. - def readable_after_eof? - true - end - - def prompting? - STDIN.tty? - end - - # Returns the current line number for #io. - # - # #line counts the number of times #gets is called. - # - # See IO#lineno for more information. - def line(line_no) - @line[line_no] - end - - # The external encoding for standard input. - def encoding - @stdin.external_encoding - end - - # For debug message - def inspect - 'StdioInputMethod' - end - end - - # Use a File for IO with irb, see InputMethod - class FileInputMethod < InputMethod - class << self - def open(file, &block) - begin - io = new(file) - block.call(io) - ensure - io&.close - end - end - end - - # Creates a new input method object - def initialize(file) - @io = file.is_a?(IO) ? file : File.open(file) - @external_encoding = @io.external_encoding - end - - # Whether the end of this input method has been reached, returns +true+ if - # there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @io.closed? || @io.eof? - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - print @prompt - @io.gets - end - - # The external encoding for standard input. - def encoding - @external_encoding - end - - # For debug message - def inspect - 'FileInputMethod' - end - - def close - @io.close - end - end - - class ReadlineInputMethod < StdioInputMethod - class << self - def initialize_readline - require "readline" - rescue LoadError - else - include ::Readline - end - end - - include HistorySavingAbility - - # Creates a new input method object using Readline - def initialize - self.class.initialize_readline - if Readline.respond_to?(:encoding_system_needs) - IRB.__send__(:set_encoding, Readline.encoding_system_needs.name, override: false) - end - - super - - @eof = false - @completor = RegexpCompletor.new - - if Readline.respond_to?("basic_word_break_characters=") - Readline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS - end - Readline.completion_append_character = nil - Readline.completion_proc = ->(target) { - bind = IRB.conf[:MAIN_CONTEXT].workspace.binding - @completor.completion_candidates('', target, '', bind: bind) - } - end - - def completion_info - 'RegexpCompletor' - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - Readline.input = @stdin - Readline.output = @stdout - if l = readline(@prompt, false) - HISTORY.push(l) if !l.empty? - @line[@line_no += 1] = l + "\n" - else - @eof = true - l - end - end - - # Whether the end of this input method has been reached, returns +true+ - # if there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @eof - end - - def prompting? - true - end - - # For debug message - def inspect - readline_impl = (defined?(Reline) && Readline == Reline) ? 'Reline' : 'ext/readline' - str = "ReadlineInputMethod with #{readline_impl} #{Readline::VERSION}" - inputrc_path = File.expand_path(ENV['INPUTRC'] || '~/.inputrc') - str += " and #{inputrc_path}" if File.exist?(inputrc_path) - str - end - end - - class RelineInputMethod < StdioInputMethod - HISTORY = Reline::HISTORY - include HistorySavingAbility - # Creates a new input method object using Reline - def initialize(completor) - IRB.__send__(:set_encoding, Reline.encoding_system_needs.name, override: false) - - super() - - @eof = false - @completor = completor - - Reline.basic_word_break_characters = BASIC_WORD_BREAK_CHARACTERS - Reline.completion_append_character = nil - Reline.completer_quote_characters = '' - Reline.completion_proc = ->(target, preposing, postposing) { - bind = IRB.conf[:MAIN_CONTEXT].workspace.binding - @completion_params = [preposing, target, postposing, bind] - @completor.completion_candidates(preposing, target, postposing, bind: bind) - } - Reline.output_modifier_proc = proc do |input, complete:| - IRB.CurrentContext.colorize_input(input, complete: complete) - end - Reline.dig_perfect_match_proc = ->(matched) { display_document(matched) } - Reline.autocompletion = IRB.conf[:USE_AUTOCOMPLETE] - - if IRB.conf[:USE_AUTOCOMPLETE] - begin - require 'rdoc' - Reline.add_dialog_proc(:show_doc, show_doc_dialog_proc, Reline::DEFAULT_DIALOG_CONTEXT) - rescue LoadError - end - end - end - - def completion_info - autocomplete_message = Reline.autocompletion ? 'Autocomplete' : 'Tab Complete' - "#{autocomplete_message}, #{@completor.inspect}" - end - - def check_termination(&block) - @check_termination_proc = block - end - - def dynamic_prompt(&block) - @prompt_proc = block - end - - def auto_indent(&block) - @auto_indent_proc = block - end - - def retrieve_doc_namespace(matched) - preposing, _target, postposing, bind = @completion_params - @completor.doc_namespace(preposing, matched, postposing, bind: bind) - end - - def rdoc_ri_driver - return @rdoc_ri_driver if defined?(@rdoc_ri_driver) - - begin - require 'rdoc' - rescue LoadError - @rdoc_ri_driver = nil - else - options = {} - options[:extra_doc_dirs] = IRB.conf[:EXTRA_DOC_DIRS] unless IRB.conf[:EXTRA_DOC_DIRS].empty? - @rdoc_ri_driver = RDoc::RI::Driver.new(options) - end - end - - def show_doc_dialog_proc - input_method = self # self is changed in the lambda below. - ->() { - dialog.trap_key = nil - alt_d = [ - [27, 100], # Normal Alt+d when convert-meta isn't used. - # When option/alt is not configured as a meta key in terminal emulator, - # option/alt + d will send a unicode character depend on OS keyboard setting. - [195, 164], # "ä" in somewhere (FIXME: environment information is unknown). - [226, 136, 130] # "∂" Alt+d on Mac keyboard. - ] - - if just_cursor_moving and completion_journey_data.nil? - return nil - end - cursor_pos_to_render, result, pointer, autocomplete_dialog = context.pop(4) - return nil if result.nil? or pointer.nil? or pointer < 0 - - name = input_method.retrieve_doc_namespace(result[pointer]) - # Use first one because document dialog does not support multiple namespaces. - name = name.first if name.is_a?(Array) - - show_easter_egg = name&.match?(/\ARubyVM/) && !ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] - - driver = input_method.rdoc_ri_driver - - if key.match?(dialog.name) - if show_easter_egg - IRB.__send__(:easter_egg) - else - # RDoc::RI::Driver#display_names uses pager command internally. - # Some pager command like `more` doesn't use alternate screen - # so we need to turn on and off alternate screen manually. - begin - print "\e[?1049h" - driver.display_names([name]) - rescue RDoc::RI::Driver::NotFoundError - ensure - print "\e[?1049l" - end - end - end - - begin - name = driver.expand_name(name) - rescue RDoc::RI::Driver::NotFoundError - return nil - rescue - return nil # unknown error - end - doc = nil - used_for_class = false - if not name =~ /#|\./ - found, klasses, includes, extends = driver.classes_and_includes_and_extends_for(name) - if not found.empty? - doc = driver.class_document(name, found, klasses, includes, extends) - used_for_class = true - end - end - unless used_for_class - doc = RDoc::Markup::Document.new - begin - driver.add_method(doc, name) - rescue RDoc::RI::Driver::NotFoundError - doc = nil - rescue - return nil # unknown error - end - end - return nil if doc.nil? - width = 40 - - right_x = cursor_pos_to_render.x + autocomplete_dialog.width - if right_x + width > screen_width - right_width = screen_width - (right_x + 1) - left_x = autocomplete_dialog.column - width - left_x = 0 if left_x < 0 - left_width = width > autocomplete_dialog.column ? autocomplete_dialog.column : width - if right_width.positive? and left_width.positive? - if right_width >= left_width - width = right_width - x = right_x - else - width = left_width - x = left_x - end - elsif right_width.positive? and left_width <= 0 - width = right_width - x = right_x - elsif right_width <= 0 and left_width.positive? - width = left_width - x = left_x - else # Both are negative width. - return nil - end - else - x = right_x - end - formatter = RDoc::Markup::ToAnsi.new - formatter.width = width - dialog.trap_key = alt_d - mod_key = RUBY_PLATFORM.match?(/darwin/) ? "Option" : "Alt" - if show_easter_egg - type = STDOUT.external_encoding == Encoding::UTF_8 ? :unicode : :ascii - contents = IRB.send(:easter_egg_logo, type).split("\n") - message = "Press #{mod_key}+d to see more" - contents[0][0, message.size] = message - else - message = "Press #{mod_key}+d to read the full document" - contents = [message] + doc.accept(formatter).split("\n") - end - contents = contents.take(preferred_dialog_height) - - y = cursor_pos_to_render.y - Reline::DialogRenderInfo.new(pos: Reline::CursorPos.new(x, y), contents: contents, width: width, bg_color: '49') - } - end - - def display_document(matched) - driver = rdoc_ri_driver - return unless driver - - if matched =~ /\A(?:::)?RubyVM/ and not ENV['RUBY_YES_I_AM_NOT_A_NORMAL_USER'] - IRB.__send__(:easter_egg) - return - end - - namespace = retrieve_doc_namespace(matched) - return unless namespace - - if namespace.is_a?(Array) - out = RDoc::Markup::Document.new - namespace.each do |m| - begin - driver.add_method(out, m) - rescue RDoc::RI::Driver::NotFoundError - end - end - driver.display(out) - else - begin - driver.display_names([namespace]) - rescue RDoc::RI::Driver::NotFoundError - end - end - end - - # Reads the next line from this input method. - # - # See IO#gets for more information. - def gets - Reline.input = @stdin - Reline.output = @stdout - Reline.prompt_proc = @prompt_proc - Reline.auto_indent_proc = @auto_indent_proc if @auto_indent_proc - if l = Reline.readmultiline(@prompt, false, &@check_termination_proc) - Reline::HISTORY.push(l) if !l.empty? - @line[@line_no += 1] = l + "\n" - else - @eof = true - l - end - end - - # Whether the end of this input method has been reached, returns +true+ - # if there is no more data to read. - # - # See IO#eof? for more information. - def eof? - @eof - end - - def prompting? - true - end - - # For debug message - def inspect - config = Reline::Config.new - str = "RelineInputMethod with Reline #{Reline::VERSION}" - inputrc_path = File.expand_path(config.inputrc_path) - str += " and #{inputrc_path}" if File.exist?(inputrc_path) - str - end - end - - class ReidlineInputMethod < RelineInputMethod - def initialize - warn <<~MSG.strip - IRB::ReidlineInputMethod is deprecated, please use IRB::RelineInputMethod instead. - MSG - super - end - end -end diff --git a/lib/irb/inspector.rb b/lib/irb/inspector.rb deleted file mode 100644 index 8046744f88..0000000000 --- a/lib/irb/inspector.rb +++ /dev/null @@ -1,131 +0,0 @@ -# frozen_string_literal: true -# -# irb/inspector.rb - inspect methods -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - - # Convenience method to create a new Inspector, using the given +inspect+ - # proc, and optional +init+ proc and passes them to Inspector.new - # - # irb(main):001:0> ins = IRB::Inspector(proc{ |v| "omg! #{v}" }) - # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28> - # irb(main):001:0> "what?" #=> omg! what? - # - def IRB::Inspector(inspect, init = nil) - Inspector.new(inspect, init) - end - - # An irb inspector - # - # In order to create your own custom inspector there are two things you - # should be aware of: - # - # Inspector uses #inspect_value, or +inspect_proc+, for output of return values. - # - # This also allows for an optional #init+, or +init_proc+, which is called - # when the inspector is activated. - # - # Knowing this, you can create a rudimentary inspector as follows: - # - # irb(main):001:0> ins = IRB::Inspector.new(proc{ |v| "omg! #{v}" }) - # irb(main):001:0> IRB.CurrentContext.inspect_mode = ins # => omg! #<IRB::Inspector:0x007f46f7ba7d28> - # irb(main):001:0> "what?" #=> omg! what? - # - class Inspector - KERNEL_INSPECT = Object.instance_method(:inspect) - # Default inspectors available to irb, this includes: - # - # +:pp+:: Using Kernel#pretty_inspect - # +:yaml+:: Using YAML.dump - # +:marshal+:: Using Marshal.dump - INSPECTORS = {} - - class << self - # Determines the inspector to use where +inspector+ is one of the keys passed - # during inspector definition. - def keys_with_inspector(inspector) - INSPECTORS.select{|k, v| v == inspector}.collect{|k, v| k} - end - - # Example - # - # Inspector.def_inspector(key, init_p=nil){|v| v.inspect} - # Inspector.def_inspector([key1,..], init_p=nil){|v| v.inspect} - # Inspector.def_inspector(key, inspector) - # Inspector.def_inspector([key1,...], inspector) - def def_inspector(key, arg=nil, &block) - if block_given? - inspector = IRB::Inspector(block, arg) - else - inspector = arg - end - - case key - when Array - for k in key - def_inspector(k, inspector) - end - when Symbol - INSPECTORS[key] = inspector - INSPECTORS[key.to_s] = inspector - when String - INSPECTORS[key] = inspector - INSPECTORS[key.intern] = inspector - else - INSPECTORS[key] = inspector - end - end - end - - # Creates a new inspector object, using the given +inspect_proc+ when - # output return values in irb. - def initialize(inspect_proc, init_proc = nil) - @init = init_proc - @inspect = inspect_proc - end - - # Proc to call when the inspector is activated, good for requiring - # dependent libraries. - def init - @init.call if @init - end - - # Proc to call when the input is evaluated and output in irb. - def inspect_value(v) - @inspect.call(v) - rescue => e - puts "An error occurred when inspecting the object: #{e.inspect}" - - begin - puts "Result of Kernel#inspect: #{KERNEL_INSPECT.bind_call(v)}" - '' - rescue => e - puts "An error occurred when running Kernel#inspect: #{e.inspect}" - puts e.backtrace.join("\n") - '' - end - end - end - - Inspector.def_inspector([false, :to_s, :raw]){|v| v.to_s} - Inspector.def_inspector([:p, :inspect]){|v| - Color.colorize_code(v.inspect, colorable: Color.colorable? && Color.inspect_colorable?(v)) - } - Inspector.def_inspector([true, :pp, :pretty_inspect], proc{require_relative "color_printer"}){|v| - IRB::ColorPrinter.pp(v, +'').chomp - } - Inspector.def_inspector([:yaml, :YAML], proc{require "yaml"}){|v| - begin - YAML.dump(v) - rescue - puts "(can't dump yaml. use inspect)" - v.inspect - end - } - - Inspector.def_inspector([:marshal, :Marshal, :MARSHAL, Marshal]){|v| - Marshal.dump(v) - } -end diff --git a/lib/irb/irb.gemspec b/lib/irb/irb.gemspec deleted file mode 100644 index b29002f593..0000000000 --- a/lib/irb/irb.gemspec +++ /dev/null @@ -1,46 +0,0 @@ -begin - require_relative "lib/irb/version" -rescue LoadError - # for Ruby core repository - require_relative "version" -end - -Gem::Specification.new do |spec| - spec.name = "irb" - spec.version = IRB::VERSION - spec.authors = ["aycabta", "Keiju ISHITSUKA"] - spec.email = ["aycabta@gmail.com", "keiju@ruby-lang.org"] - - spec.summary = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} - spec.description = %q{Interactive Ruby command-line tool for REPL (Read Eval Print Loop).} - spec.homepage = "https://github.com/ruby/irb" - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["documentation_uri"] = spec.homepage - spec.metadata["changelog_uri"] = "#{spec.homepage}/releases" - - spec.files = [ - ".document", - "Gemfile", - "LICENSE.txt", - "README.md", - "Rakefile", - "bin/console", - "bin/setup", - "doc/irb/irb-tools.rd.ja", - "doc/irb/irb.rd.ja", - "exe/irb", - "irb.gemspec", - "man/irb.1", - ] + Dir.glob("lib/**/*") - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] - - spec.required_ruby_version = Gem::Requirement.new(">= 2.7") - - spec.add_dependency "reline", ">= 0.4.2" - spec.add_dependency "rdoc", ">= 4.0.0" -end diff --git a/lib/irb/lc/error.rb b/lib/irb/lc/error.rb deleted file mode 100644 index ee0f047822..0000000000 --- a/lib/irb/lc/error.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true -# -# irb/lc/error.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - # :stopdoc: - - class UnrecognizedSwitch < StandardError - def initialize(val) - super("Unrecognized switch: #{val}") - end - end - class CantReturnToNormalMode < StandardError - def initialize - super("Can't return to normal mode.") - end - end - class IllegalParameter < StandardError - def initialize(val) - super("Invalid parameter(#{val}).") - end - end - class IrbAlreadyDead < StandardError - def initialize - super("Irb is already dead.") - end - end - class IrbSwitchedToCurrentThread < StandardError - def initialize - super("Switched to current thread.") - end - end - class NoSuchJob < StandardError - def initialize(val) - super("No such job(#{val}).") - end - end - class CantChangeBinding < StandardError - def initialize(val) - super("Can't change binding to (#{val}).") - end - end - class UndefinedPromptMode < StandardError - def initialize(val) - super("Undefined prompt mode(#{val}).") - end - end - - # :startdoc: -end diff --git a/lib/irb/lc/help-message b/lib/irb/lc/help-message deleted file mode 100644 index 37347306e8..0000000000 --- a/lib/irb/lc/help-message +++ /dev/null @@ -1,55 +0,0 @@ -Usage: irb.rb [options] [programfile] [arguments] - -f Don't initialize from configuration file. - -d Set $DEBUG and $VERBOSE to true (same as 'ruby -d'). - -r load-module Require load-module (same as 'ruby -r'). - -I path Specify $LOAD_PATH directory (same as 'ruby -I'). - -U Set external and internal encodings to UTF-8. - -E ex[:in] Set default external (ex) and internal (in) encodings - (same as 'ruby -E'). - -w Suppress warnings (same as 'ruby -w'). - -W[level=2] Set warning level: 0=silence, 1=medium, 2=verbose - (same as 'ruby -W'). - --context-mode n Set n[0-4] to method to create Binding Object, - when new workspace was created. - --extra-doc-dir Add an extra doc dir for the doc dialog. - --echo Show result (default). - --noecho Don't show result. - --echo-on-assignment - Show result on assignment. - --noecho-on-assignment - Don't show result on assignment. - --truncate-echo-on-assignment - Show truncated result on assignment (default). - --inspect Use 'inspect' for output. - --noinspect Don't use 'inspect' for output. - --no-pager Don't use pager. - --multiline Use multiline editor module (default). - --nomultiline Don't use multiline editor module. - --singleline Use single line editor module. - --nosingleline Don't use single line editor module (default). - --colorize Use color-highlighting (default). - --nocolorize Don't use color-highlighting. - --autocomplete Use auto-completion (default). - --noautocomplete Don't use auto-completion. - --regexp-completor - Use regexp based completion (default). - --type-completor Use type based completion. - --prompt prompt-mode, --prompt-mode prompt-mode - Set prompt mode. Pre-defined prompt modes are: - 'default', 'classic', 'simple', 'inf-ruby', 'xmp', 'null'. - --inf-ruby-mode Use prompt appropriate for inf-ruby-mode on emacs. - Suppresses --multiline and --singleline. - --sample-book-mode, --simple-prompt - Set prompt mode to 'simple'. - --noprompt Don't output prompt. - --script Script mode (default, treat first argument as script) - --noscript No script mode (leave arguments in argv) - --single-irb Share self with sub-irb. - --tracer Show stack trace for each command. - --back-trace-limit n[=16] - Display backtrace top n and bottom n. - --verbose Show details. - --noverbose Don't show details. - -v, --version Print the version of irb. - -h, --help Print help. - -- Separate options of irb from the list of command-line args. diff --git a/lib/irb/lc/ja/error.rb b/lib/irb/lc/ja/error.rb deleted file mode 100644 index 9e2e5b8870..0000000000 --- a/lib/irb/lc/ja/error.rb +++ /dev/null @@ -1,53 +0,0 @@ -# frozen_string_literal: true -# -# irb/lc/ja/error.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - # :stopdoc: - - class UnrecognizedSwitch < StandardError - def initialize(val) - super("スイッチ(#{val})が分りません") - end - end - class CantReturnToNormalMode < StandardError - def initialize - super("Normalモードに戻れません.") - end - end - class IllegalParameter < StandardError - def initialize(val) - super("パラメータ(#{val})が間違っています.") - end - end - class IrbAlreadyDead < StandardError - def initialize - super("Irbは既に死んでいます.") - end - end - class IrbSwitchedToCurrentThread < StandardError - def initialize - super("カレントスレッドに切り替わりました.") - end - end - class NoSuchJob < StandardError - def initialize(val) - super("そのようなジョブ(#{val})はありません.") - end - end - class CantChangeBinding < StandardError - def initialize(val) - super("バインディング(#{val})に変更できません.") - end - end - class UndefinedPromptMode < StandardError - def initialize(val) - super("プロンプトモード(#{val})は定義されていません.") - end - end - - # :startdoc: -end -# vim:fileencoding=utf-8 diff --git a/lib/irb/lc/ja/help-message b/lib/irb/lc/ja/help-message deleted file mode 100644 index 99f4449b3b..0000000000 --- a/lib/irb/lc/ja/help-message +++ /dev/null @@ -1,58 +0,0 @@ -Usage: irb.rb [options] [programfile] [arguments] - -f ~/.irbrc を読み込まない. - -d $DEBUG をtrueにする(ruby -d と同じ) - -r load-module ruby -r と同じ. - -I path $LOAD_PATH に path を追加する. - -U ruby -U と同じ. - -E enc ruby -E と同じ. - -w ruby -w と同じ. - -W[level=2] ruby -W と同じ. - --context-mode n 新しいワークスペースを作成した時に関連する Binding - オブジェクトの作成方法を 0 から 3 のいずれかに設定する. - --extra-doc-dir 指定したディレクトリのドキュメントを追加で読み込む. - --echo 実行結果を表示する(デフォルト). - --noecho 実行結果を表示しない. - --echo-on-assignment - 代入結果を表示する. - --noecho-on-assignment - 代入結果を表示しない. - --truncate-echo-on-assignment - truncateされた代入結果を表示する(デフォルト). - --inspect 結果出力にinspectを用いる. - --noinspect 結果出力にinspectを用いない. - --no-pager ページャを使用しない. - --multiline マルチラインエディタを利用する. - --nomultiline マルチラインエディタを利用しない. - --singleline シングルラインエディタを利用する. - --nosingleline シングルラインエディタを利用しない. - --colorize 色付けを利用する. - --nocolorize 色付けを利用しない. - --autocomplete オートコンプリートを利用する. - --noautocomplete オートコンプリートを利用しない. - --regexp-completor - 補完に正規表現を利用する. - --type-completor 補完に型情報を利用する. - --prompt prompt-mode/--prompt-mode prompt-mode - プロンプトモードを切替えます. 現在定義されているプ - ロンプトモードは, default, simple, xmp, inf-rubyが - 用意されています. - --inf-ruby-mode emacsのinf-ruby-mode用のプロンプト表示を行なう. 特 - に指定がない限り, シングルラインエディタとマルチラ - インエディタは使わなくなる. - --sample-book-mode/--simple-prompt - 非常にシンプルなプロンプトを用いるモードです. - --noprompt プロンプト表示を行なわない. - --script スクリプトモード(最初の引数をスクリプトファイルとして扱う、デフォルト) - --noscript 引数をargvとして扱う. - --single-irb irb 中で self を実行して得られるオブジェクトをサ - ブ irb と共有する. - --tracer コマンド実行時にトレースを行なう. - --back-trace-limit n - バックトレース表示をバックトレースの頭から n, 後ろ - からnだけ行なう. デフォルトは16 - - --verbose 詳細なメッセージを出力する. - --noverbose 詳細なメッセージを出力しない(デフォルト). - -v, --version irbのバージョンを表示する. - -h, --help irb のヘルプを表示する. - -- 以降のコマンドライン引数をオプションとして扱わない. diff --git a/lib/irb/locale.rb b/lib/irb/locale.rb deleted file mode 100644 index 2abcc7354b..0000000000 --- a/lib/irb/locale.rb +++ /dev/null @@ -1,153 +0,0 @@ -# frozen_string_literal: true -# -# irb/locale.rb - internationalization module -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB # :nodoc: - class Locale - - LOCALE_NAME_RE = %r[ - (?<language>[[:alpha:]]{2,3}) - (?:_ (?<territory>[[:alpha:]]{2,3}) )? - (?:\. (?<codeset>[^@]+) )? - (?:@ (?<modifier>.*) )? - ]x - LOCALE_DIR = "/lc/" - - LEGACY_ENCODING_ALIAS_MAP = { - 'ujis' => Encoding::EUC_JP, - 'euc' => Encoding::EUC_JP - } - - @@loaded = [] - - def initialize(locale = nil) - @override_encoding = nil - @lang = @territory = @encoding_name = @modifier = nil - @locale = locale || ENV["IRB_LANG"] || ENV["LC_MESSAGES"] || ENV["LC_ALL"] || ENV["LANG"] || "C" - if m = LOCALE_NAME_RE.match(@locale) - @lang, @territory, @encoding_name, @modifier = m[:language], m[:territory], m[:codeset], m[:modifier] - - if @encoding_name - if @encoding = LEGACY_ENCODING_ALIAS_MAP[@encoding_name] - warn(("%s is obsolete. use %s" % ["#{@lang}_#{@territory}.#{@encoding_name}", "#{@lang}_#{@territory}.#{@encoding.name}"]), uplevel: 1) - else - @encoding = Encoding.find(@encoding_name) rescue nil - end - end - end - @encoding ||= (Encoding.find('locale') rescue Encoding::ASCII_8BIT) - end - - attr_reader :lang, :territory, :modifier - - def encoding - @override_encoding || @encoding - end - - def String(mes) - mes = super(mes) - if encoding - mes.encode(encoding, undef: :replace) - else - mes - end - end - - def format(*opts) - String(super(*opts)) - end - - def gets(*rs) - String(super(*rs)) - end - - def readline(*rs) - String(super(*rs)) - end - - def print(*opts) - ary = opts.collect{|opt| String(opt)} - super(*ary) - end - - def printf(*opts) - s = format(*opts) - print s - end - - def puts(*opts) - ary = opts.collect{|opt| String(opt)} - super(*ary) - end - - def load(file) - found = find(file) - if found - unless @@loaded.include?(found) - @@loaded << found # cache - Kernel.load(found) - end - else - raise LoadError, "No such file to load -- #{file}" - end - end - - def find(file, paths = $:) - dir = File.dirname(file) - dir = "" if dir == "." - base = File.basename(file) - - if dir.start_with?('/') - return each_localized_path(dir, base).find{|full_path| File.readable? full_path} - else - return search_file(paths, dir, base) - end - end - - # @param paths load paths in which IRB find a localized file. - # @param dir directory - # @param file basename to be localized - # - # typically, for the parameters and a <path> in paths, it searches - # <path>/<dir>/<locale>/<file> - def search_file(lib_paths, dir, file) - each_localized_path(dir, file) do |lc_path| - lib_paths.each do |libpath| - full_path = File.join(libpath, lc_path) - return full_path if File.readable?(full_path) - end - redo if defined?(Gem) and Gem.try_activate(lc_path) - end - nil - end - - def each_localized_path(dir, file) - return enum_for(:each_localized_path) unless block_given? - each_sublocale do |lc| - yield lc.nil? ? File.join(dir, LOCALE_DIR, file) : File.join(dir, LOCALE_DIR, lc, file) - end - end - - def each_sublocale - if @lang - if @territory - if @encoding_name - yield "#{@lang}_#{@territory}.#{@encoding_name}@#{@modifier}" if @modifier - yield "#{@lang}_#{@territory}.#{@encoding_name}" - end - yield "#{@lang}_#{@territory}@#{@modifier}" if @modifier - yield "#{@lang}_#{@territory}" - end - if @encoding_name - yield "#{@lang}.#{@encoding_name}@#{@modifier}" if @modifier - yield "#{@lang}.#{@encoding_name}" - end - yield "#{@lang}@#{@modifier}" if @modifier - yield "#{@lang}" - end - yield nil - end - end -end diff --git a/lib/irb/nesting_parser.rb b/lib/irb/nesting_parser.rb deleted file mode 100644 index fc71d64aee..0000000000 --- a/lib/irb/nesting_parser.rb +++ /dev/null @@ -1,239 +0,0 @@ -# frozen_string_literal: true -module IRB - module NestingParser - IGNORE_TOKENS = %i[on_sp on_ignored_nl on_comment on_embdoc_beg on_embdoc on_embdoc_end] - - class << self - # Scan each token and call the given block with array of token and other information for parsing - def scan_opens(tokens) - opens = [] - pending_heredocs = [] - first_token_on_line = true - tokens.each do |t| - skip = false - last_tok, state, args = opens.last - case state - when :in_alias_undef - skip = t.event == :on_kw - when :in_unquoted_symbol - unless IGNORE_TOKENS.include?(t.event) - opens.pop - skip = true - end - when :in_lambda_head - opens.pop if t.event == :on_tlambeg || (t.event == :on_kw && t.tok == 'do') - when :in_method_head - unless IGNORE_TOKENS.include?(t.event) - next_args = [] - body = nil - if args.include?(:receiver) - case t.event - when :on_lparen, :on_ivar, :on_gvar, :on_cvar - # def (receiver). | def @ivar. | def $gvar. | def @@cvar. - next_args << :dot - when :on_kw - case t.tok - when 'self', 'true', 'false', 'nil' - # def self(arg) | def self. - next_args.push(:arg, :dot) - else - # def if(arg) - skip = true - next_args << :arg - end - when :on_op, :on_backtick - # def +(arg) - skip = true - next_args << :arg - when :on_ident, :on_const - # def a(arg) | def a. - next_args.push(:arg, :dot) - end - end - if args.include?(:dot) - # def receiver.name - next_args << :name if t.event == :on_period || (t.event == :on_op && t.tok == '::') - end - if args.include?(:name) - if %i[on_ident on_const on_op on_kw on_backtick].include?(t.event) - # def name(arg) | def receiver.name(arg) - next_args << :arg - skip = true - end - end - if args.include?(:arg) - case t.event - when :on_nl, :on_semicolon - # def receiver.f; - body = :normal - when :on_lparen - # def receiver.f() - next_args << :eq - else - if t.event == :on_op && t.tok == '=' - # def receiver.f = - body = :oneliner - else - # def receiver.f arg - next_args << :arg_without_paren - end - end - end - if args.include?(:eq) - if t.event == :on_op && t.tok == '=' - body = :oneliner - else - body = :normal - end - end - if args.include?(:arg_without_paren) - if %i[on_semicolon on_nl].include?(t.event) - # def f a; - body = :normal - else - # def f a, b - next_args << :arg_without_paren - end - end - if body == :oneliner - opens.pop - elsif body - opens[-1] = [last_tok, nil] - else - opens[-1] = [last_tok, :in_method_head, next_args] - end - end - when :in_for_while_until_condition - if t.event == :on_semicolon || t.event == :on_nl || (t.event == :on_kw && t.tok == 'do') - skip = true if t.event == :on_kw && t.tok == 'do' - opens[-1] = [last_tok, nil] - end - end - - unless skip - case t.event - when :on_kw - case t.tok - when 'begin', 'class', 'module', 'do', 'case' - opens << [t, nil] - when 'end' - opens.pop - when 'def' - opens << [t, :in_method_head, [:receiver, :name]] - when 'if', 'unless' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens << [t, nil] - end - when 'while', 'until' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens << [t, :in_for_while_until_condition] - end - when 'ensure', 'rescue' - unless t.state.allbits?(Ripper::EXPR_LABEL) - opens.pop - opens << [t, nil] - end - when 'alias' - opens << [t, :in_alias_undef, 2] - when 'undef' - opens << [t, :in_alias_undef, 1] - when 'elsif', 'else', 'when' - opens.pop - opens << [t, nil] - when 'for' - opens << [t, :in_for_while_until_condition] - when 'in' - if last_tok&.event == :on_kw && %w[case in].include?(last_tok.tok) && first_token_on_line - opens.pop - opens << [t, nil] - end - end - when :on_tlambda - opens << [t, :in_lambda_head] - when :on_lparen, :on_lbracket, :on_lbrace, :on_tlambeg, :on_embexpr_beg, :on_embdoc_beg - opens << [t, nil] - when :on_rparen, :on_rbracket, :on_rbrace, :on_embexpr_end, :on_embdoc_end - opens.pop - when :on_heredoc_beg - pending_heredocs << t - when :on_heredoc_end - opens.pop - when :on_backtick - opens << [t, nil] if t.state.allbits?(Ripper::EXPR_BEG) - when :on_tstring_beg, :on_words_beg, :on_qwords_beg, :on_symbols_beg, :on_qsymbols_beg, :on_regexp_beg - opens << [t, nil] - when :on_tstring_end, :on_regexp_end, :on_label_end - opens.pop - when :on_symbeg - if t.tok == ':' - opens << [t, :in_unquoted_symbol] - else - opens << [t, nil] - end - end - end - if t.event == :on_nl || t.event == :on_semicolon - first_token_on_line = true - elsif t.event != :on_sp - first_token_on_line = false - end - if pending_heredocs.any? && t.tok.include?("\n") - pending_heredocs.reverse_each { |t| opens << [t, nil] } - pending_heredocs = [] - end - if opens.last && opens.last[1] == :in_alias_undef && !IGNORE_TOKENS.include?(t.event) && t.event != :on_heredoc_end - tok, state, arg = opens.pop - opens << [tok, state, arg - 1] if arg >= 1 - end - yield t, opens if block_given? - end - opens.map(&:first) + pending_heredocs.reverse - end - - def open_tokens(tokens) - # scan_opens without block will return a list of open tokens at last token position - scan_opens(tokens) - end - - # Calculates token information [line_tokens, prev_opens, next_opens, min_depth] for each line. - # Example code - # ["hello - # world"+( - # First line - # line_tokens: [[lbracket, '['], [tstring_beg, '"'], [tstring_content("hello\nworld"), "hello\n"]] - # prev_opens: [] - # next_tokens: [lbracket, tstring_beg] - # min_depth: 0 (minimum at beginning of line) - # Second line - # line_tokens: [[tstring_content("hello\nworld"), "world"], [tstring_end, '"'], [op, '+'], [lparen, '(']] - # prev_opens: [lbracket, tstring_beg] - # next_tokens: [lbracket, lparen] - # min_depth: 1 (minimum just after tstring_end) - def parse_by_line(tokens) - line_tokens = [] - prev_opens = [] - min_depth = 0 - output = [] - last_opens = scan_opens(tokens) do |t, opens| - depth = t == opens.last&.first ? opens.size - 1 : opens.size - min_depth = depth if depth < min_depth - if t.tok.include?("\n") - t.tok.each_line do |line| - line_tokens << [t, line] - next if line[-1] != "\n" - next_opens = opens.map(&:first) - output << [line_tokens, prev_opens, next_opens, min_depth] - prev_opens = next_opens - min_depth = prev_opens.size - line_tokens = [] - end - else - line_tokens << [t, t.tok] - end - end - output << [line_tokens, prev_opens, last_opens, min_depth] if line_tokens.any? - output - end - end - end -end diff --git a/lib/irb/notifier.rb b/lib/irb/notifier.rb deleted file mode 100644 index dc1b9ef14b..0000000000 --- a/lib/irb/notifier.rb +++ /dev/null @@ -1,230 +0,0 @@ -# frozen_string_literal: true -# -# notifier.rb - output methods used by irb -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require_relative "output-method" - -module IRB - # An output formatter used internally by the lexer. - module Notifier - class ErrUndefinedNotifier < StandardError - def initialize(val) - super("undefined notifier level: #{val} is specified") - end - end - class ErrUnrecognizedLevel < StandardError - def initialize(val) - super("unrecognized notifier level: #{val} is specified") - end - end - - # Define a new Notifier output source, returning a new CompositeNotifier - # with the given +prefix+ and +output_method+. - # - # The optional +prefix+ will be appended to all objects being inspected - # during output, using the given +output_method+ as the output source. If - # no +output_method+ is given, StdioOutputMethod will be used, and all - # expressions will be sent directly to STDOUT without any additional - # formatting. - def def_notifier(prefix = "", output_method = StdioOutputMethod.new) - CompositeNotifier.new(prefix, output_method) - end - module_function :def_notifier - - # An abstract class, or superclass, for CompositeNotifier and - # LeveledNotifier to inherit. It provides several wrapper methods for the - # OutputMethod object used by the Notifier. - class AbstractNotifier - # Creates a new Notifier object - def initialize(prefix, base_notifier) - @prefix = prefix - @base_notifier = base_notifier - end - - # The +prefix+ for this Notifier, which is appended to all objects being - # inspected during output. - attr_reader :prefix - - # A wrapper method used to determine whether notifications are enabled. - # - # Defaults to +true+. - def notify? - true - end - - # See OutputMethod#print for more detail. - def print(*opts) - @base_notifier.print prefix, *opts if notify? - end - - # See OutputMethod#printn for more detail. - def printn(*opts) - @base_notifier.printn prefix, *opts if notify? - end - - # See OutputMethod#printf for more detail. - def printf(format, *opts) - @base_notifier.printf(prefix + format, *opts) if notify? - end - - # See OutputMethod#puts for more detail. - def puts(*objs) - if notify? - @base_notifier.puts(*objs.collect{|obj| prefix + obj.to_s}) - end - end - - # Same as #ppx, except it uses the #prefix given during object - # initialization. - # See OutputMethod#ppx for more detail. - def pp(*objs) - if notify? - @base_notifier.ppx @prefix, *objs - end - end - - # Same as #pp, except it concatenates the given +prefix+ with the #prefix - # given during object initialization. - # - # See OutputMethod#ppx for more detail. - def ppx(prefix, *objs) - if notify? - @base_notifier.ppx @prefix+prefix, *objs - end - end - - # Execute the given block if notifications are enabled. - def exec_if - yield(@base_notifier) if notify? - end - end - - # A class that can be used to create a group of notifier objects with the - # intent of representing a leveled notification system for irb. - # - # This class will allow you to generate other notifiers, and assign them - # the appropriate level for output. - # - # The Notifier class provides a class-method Notifier.def_notifier to - # create a new composite notifier. Using the first composite notifier - # object you create, sibling notifiers can be initialized with - # #def_notifier. - class CompositeNotifier < AbstractNotifier - # Create a new composite notifier object with the given +prefix+, and - # +base_notifier+ to use for output. - def initialize(prefix, base_notifier) - super - - @notifiers = [D_NOMSG] - @level_notifier = D_NOMSG - end - - # List of notifiers in the group - attr_reader :notifiers - - # Creates a new LeveledNotifier in the composite #notifiers group. - # - # The given +prefix+ will be assigned to the notifier, and +level+ will - # be used as the index of the #notifiers Array. - # - # This method returns the newly created instance. - def def_notifier(level, prefix = "") - notifier = LeveledNotifier.new(self, level, prefix) - @notifiers[level] = notifier - notifier - end - - # Returns the leveled notifier for this object - attr_reader :level_notifier - alias level level_notifier - - # Sets the leveled notifier for this object. - # - # When the given +value+ is an instance of AbstractNotifier, - # #level_notifier is set to the given object. - # - # When an Integer is given, #level_notifier is set to the notifier at the - # index +value+ in the #notifiers Array. - # - # If no notifier exists at the index +value+ in the #notifiers Array, an - # ErrUndefinedNotifier exception is raised. - # - # An ErrUnrecognizedLevel exception is raised if the given +value+ is not - # found in the existing #notifiers Array, or an instance of - # AbstractNotifier - def level_notifier=(value) - case value - when AbstractNotifier - @level_notifier = value - when Integer - l = @notifiers[value] - raise ErrUndefinedNotifier, value unless l - @level_notifier = l - else - raise ErrUnrecognizedLevel, value unless l - end - end - - alias level= level_notifier= - end - - # A leveled notifier is comparable to the composite group from - # CompositeNotifier#notifiers. - class LeveledNotifier < AbstractNotifier - include Comparable - - # Create a new leveled notifier with the given +base+, and +prefix+ to - # send to AbstractNotifier.new - # - # The given +level+ is used to compare other leveled notifiers in the - # CompositeNotifier group to determine whether or not to output - # notifications. - def initialize(base, level, prefix) - super(prefix, base) - - @level = level - end - - # The current level of this notifier object - attr_reader :level - - # Compares the level of this notifier object with the given +other+ - # notifier. - # - # See the Comparable module for more information. - def <=>(other) - @level <=> other.level - end - - # Whether to output messages to the output method, depending on the level - # of this notifier object. - def notify? - @base_notifier.level >= self - end - end - - # NoMsgNotifier is a LeveledNotifier that's used as the default notifier - # when creating a new CompositeNotifier. - # - # This notifier is used as the +zero+ index, or level +0+, for - # CompositeNotifier#notifiers, and will not output messages of any sort. - class NoMsgNotifier < LeveledNotifier - # Creates a new notifier that should not be used to output messages. - def initialize - @base_notifier = nil - @level = 0 - @prefix = "" - end - - # Ensures notifications are ignored, see AbstractNotifier#notify? for - # more information. - def notify? - false - end - end - - D_NOMSG = NoMsgNotifier.new # :nodoc: - end -end diff --git a/lib/irb/output-method.rb b/lib/irb/output-method.rb deleted file mode 100644 index 69942f47a2..0000000000 --- a/lib/irb/output-method.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true -# -# output-method.rb - output methods used by irb -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -module IRB - # An abstract output class for IO in irb. This is mainly used internally by - # IRB::Notifier. You can define your own output method to use with Irb.new, - # or Context.new - class OutputMethod - # Open this method to implement your own output method, raises a - # NotImplementedError if you don't define #print in your own class. - def print(*opts) - raise NotImplementedError - end - - # Prints the given +opts+, with a newline delimiter. - def printn(*opts) - print opts.join(" "), "\n" - end - - # Extends IO#printf to format the given +opts+ for Kernel#sprintf using - # #parse_printf_format - def printf(format, *opts) - if /(%*)%I/ =~ format - format, opts = parse_printf_format(format, opts) - end - print sprintf(format, *opts) - end - - # Returns an array of the given +format+ and +opts+ to be used by - # Kernel#sprintf, if there was a successful Regexp match in the given - # +format+ from #printf - # - # % - # <flag> [#0- +] - # <minimum field width> (\*|\*[1-9][0-9]*\$|[1-9][0-9]*) - # <precision>.(\*|\*[1-9][0-9]*\$|[1-9][0-9]*|)? - # #<length modifier>(hh|h|l|ll|L|q|j|z|t) - # <conversion specifier>[diouxXeEfgGcsb%] - def parse_printf_format(format, opts) - return format, opts if $1.size % 2 == 1 - end - - # Calls #print on each element in the given +objs+, followed by a newline - # character. - def puts(*objs) - for obj in objs - print(*obj) - print "\n" - end - end - - # Prints the given +objs+ calling Object#inspect on each. - # - # See #puts for more detail. - def pp(*objs) - puts(*objs.collect{|obj| obj.inspect}) - end - - # Prints the given +objs+ calling Object#inspect on each and appending the - # given +prefix+. - # - # See #puts for more detail. - def ppx(prefix, *objs) - puts(*objs.collect{|obj| prefix+obj.inspect}) - end - - end - - # A standard output printer - class StdioOutputMethod < OutputMethod - # Prints the given +opts+ to standard output, see IO#print for more - # information. - def print(*opts) - STDOUT.print(*opts) - end - end -end diff --git a/lib/irb/pager.rb b/lib/irb/pager.rb deleted file mode 100644 index 558318cdb8..0000000000 --- a/lib/irb/pager.rb +++ /dev/null @@ -1,95 +0,0 @@ -# frozen_string_literal: true - -module IRB - # The implementation of this class is borrowed from RDoc's lib/rdoc/ri/driver.rb. - # Please do NOT use this class directly outside of IRB. - class Pager - PAGE_COMMANDS = [ENV['RI_PAGER'], ENV['PAGER'], 'less', 'more'].compact.uniq - - class << self - def page_content(content, **options) - if content_exceeds_screen_height?(content) - page(**options) do |io| - io.puts content - end - else - $stdout.puts content - end - end - - def page(retain_content: false) - if should_page? && pager = setup_pager(retain_content: retain_content) - begin - pid = pager.pid - yield pager - ensure - pager.close - end - else - yield $stdout - end - # When user presses Ctrl-C, IRB would raise `IRB::Abort` - # But since Pager is implemented by running paging commands like `less` in another process with `IO.popen`, - # the `IRB::Abort` exception only interrupts IRB's execution but doesn't affect the pager - # So to properly terminate the pager with Ctrl-C, we need to catch `IRB::Abort` and kill the pager process - rescue IRB::Abort - begin - Process.kill("TERM", pid) if pid - rescue Errno::ESRCH - # Pager process already terminated - end - nil - rescue Errno::EPIPE - end - - private - - def should_page? - IRB.conf[:USE_PAGER] && STDIN.tty? && (ENV.key?("TERM") && ENV["TERM"] != "dumb") - end - - def content_exceeds_screen_height?(content) - screen_height, screen_width = begin - Reline.get_screen_size - rescue Errno::EINVAL - [24, 80] - end - - pageable_height = screen_height - 3 # leave some space for previous and the current prompt - - # If the content has more lines than the pageable height - content.lines.count > pageable_height || - # Or if the content is a few long lines - pageable_height * screen_width < Reline::Unicode.calculate_width(content, true) - end - - def setup_pager(retain_content:) - require 'shellwords' - - PAGE_COMMANDS.each do |pager_cmd| - cmd = Shellwords.split(pager_cmd) - next if cmd.empty? - - if cmd.first == 'less' - cmd << '-R' unless cmd.include?('-R') - cmd << '-X' if retain_content && !cmd.include?('-X') - end - - begin - io = IO.popen(cmd, 'w') - rescue - next - end - - if $? && $?.pid == io.pid && $?.exited? # pager didn't work - next - end - - return io - end - - nil - end - end - end -end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb deleted file mode 100644 index 3abb53b4ea..0000000000 --- a/lib/irb/ruby-lex.rb +++ /dev/null @@ -1,476 +0,0 @@ -# frozen_string_literal: true -# -# irb/ruby-lex.rb - ruby lexcal analyzer -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require "ripper" -require "jruby" if RUBY_ENGINE == "jruby" -require_relative "nesting_parser" - -module IRB - # :stopdoc: - class RubyLex - ASSIGNMENT_NODE_TYPES = [ - # Local, instance, global, class, constant, instance, and index assignment: - # "foo = bar", - # "@foo = bar", - # "$foo = bar", - # "@@foo = bar", - # "::Foo = bar", - # "a::Foo = bar", - # "Foo = bar" - # "foo.bar = 1" - # "foo[1] = bar" - :assign, - - # Operation assignment: - # "foo += bar" - # "foo -= bar" - # "foo ||= bar" - # "foo &&= bar" - :opassign, - - # Multiple assignment: - # "foo, bar = 1, 2 - :massign, - ] - - ERROR_TOKENS = [ - :on_parse_error, - :compile_error, - :on_assign_error, - :on_alias_error, - :on_class_name_error, - :on_param_error - ] - - LTYPE_TOKENS = %i[ - on_heredoc_beg on_tstring_beg - on_regexp_beg on_symbeg on_backtick - on_symbols_beg on_qsymbols_beg - on_words_beg on_qwords_beg - ] - - class TerminateLineInput < StandardError - def initialize - super("Terminate Line Input") - end - end - - class << self - def compile_with_errors_suppressed(code, line_no: 1) - begin - result = yield code, line_no - rescue ArgumentError - # Ruby can issue an error for the code if there is an - # incomplete magic comment for encoding in it. Force an - # expression with a new line before the code in this - # case to prevent magic comment handling. To make sure - # line numbers in the lexed code remain the same, - # decrease the line number by one. - code = ";\n#{code}" - line_no -= 1 - result = yield code, line_no - end - result - end - - def generate_local_variables_assign_code(local_variables) - "#{local_variables.join('=')}=nil;" unless local_variables.empty? - end - - # Some part of the code is not included in Ripper's token. - # Example: DATA part, token after heredoc_beg when heredoc has unclosed embexpr. - # With interpolated tokens, tokens.map(&:tok).join will be equal to code. - def interpolate_ripper_ignored_tokens(code, tokens) - line_positions = [0] - code.lines.each do |line| - line_positions << line_positions.last + line.bytesize - end - prev_byte_pos = 0 - interpolated = [] - prev_line = 1 - tokens.each do |t| - line, col = t.pos - byte_pos = line_positions[line - 1] + col - if prev_byte_pos < byte_pos - tok = code.byteslice(prev_byte_pos...byte_pos) - pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] - interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) - prev_line += tok.count("\n") - end - interpolated << t - prev_byte_pos = byte_pos + t.tok.bytesize - prev_line += t.tok.count("\n") - end - if prev_byte_pos < code.bytesize - tok = code.byteslice(prev_byte_pos..) - pos = [prev_line, prev_byte_pos - line_positions[prev_line - 1]] - interpolated << Ripper::Lexer::Elem.new(pos, :on_ignored_by_ripper, tok, 0) - end - interpolated - end - - def ripper_lex_without_warning(code, local_variables: []) - verbose, $VERBOSE = $VERBOSE, nil - lvars_code = generate_local_variables_assign_code(local_variables) - original_code = code - if lvars_code - code = "#{lvars_code}\n#{code}" - line_no = 0 - else - line_no = 1 - end - - compile_with_errors_suppressed(code, line_no: line_no) do |inner_code, line_no| - lexer = Ripper::Lexer.new(inner_code, '-', line_no) - tokens = [] - lexer.scan.each do |t| - next if t.pos.first == 0 - prev_tk = tokens.last - position_overlapped = prev_tk && t.pos[0] == prev_tk.pos[0] && t.pos[1] < prev_tk.pos[1] + prev_tk.tok.bytesize - if position_overlapped - tokens[-1] = t if ERROR_TOKENS.include?(prev_tk.event) && !ERROR_TOKENS.include?(t.event) - else - tokens << t - end - end - interpolate_ripper_ignored_tokens(original_code, tokens) - end - ensure - $VERBOSE = verbose - end - end - - def check_code_state(code, local_variables:) - tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) - opens = NestingParser.open_tokens(tokens) - [tokens, opens, code_terminated?(code, tokens, opens, local_variables: local_variables)] - end - - def code_terminated?(code, tokens, opens, local_variables:) - case check_code_syntax(code, local_variables: local_variables) - when :unrecoverable_error - true - when :recoverable_error - false - when :other_error - opens.empty? && !should_continue?(tokens) - when :valid - !should_continue?(tokens) - end - end - - def assignment_expression?(code, local_variables:) - # Try to parse the code and check if the last of possibly multiple - # expressions is an assignment type. - - # If the expression is invalid, Ripper.sexp should return nil which will - # result in false being returned. Any valid expression should return an - # s-expression where the second element of the top level array is an - # array of parsed expressions. The first element of each expression is the - # expression's type. - verbose, $VERBOSE = $VERBOSE, nil - code = "#{RubyLex.generate_local_variables_assign_code(local_variables) || 'nil;'}\n#{code}" - # Get the last node_type of the line. drop(1) is to ignore the local_variables_assign_code part. - node_type = Ripper.sexp(code)&.dig(1)&.drop(1)&.dig(-1, 0) - ASSIGNMENT_NODE_TYPES.include?(node_type) - ensure - $VERBOSE = verbose - end - - def should_continue?(tokens) - # Look at the last token and check if IRB need to continue reading next line. - # Example code that should continue: `a\` `a +` `a.` - # Trailing spaces, newline, comments are skipped - return true if tokens.last&.event == :on_sp && tokens.last.tok == "\\\n" - - tokens.reverse_each do |token| - case token.event - when :on_sp, :on_nl, :on_ignored_nl, :on_comment, :on_embdoc_beg, :on_embdoc, :on_embdoc_end - # Skip - when :on_regexp_end, :on_heredoc_end, :on_semicolon - # State is EXPR_BEG but should not continue - return false - else - # Endless range should not continue - return false if token.event == :on_op && token.tok.match?(/\A\.\.\.?\z/) - - # EXPR_DOT and most of the EXPR_BEG should continue - return token.state.anybits?(Ripper::EXPR_BEG | Ripper::EXPR_DOT) - end - end - false - end - - def check_code_syntax(code, local_variables:) - lvars_code = RubyLex.generate_local_variables_assign_code(local_variables) - code = "#{lvars_code}\n#{code}" - - begin # check if parser error are available - verbose, $VERBOSE = $VERBOSE, nil - case RUBY_ENGINE - when 'ruby' - self.class.compile_with_errors_suppressed(code) do |inner_code, line_no| - RubyVM::InstructionSequence.compile(inner_code, nil, nil, line_no) - end - when 'jruby' - JRuby.compile_ir(code) - else - catch(:valid) do - eval("BEGIN { throw :valid, true }\n#{code}") - false - end - end - rescue EncodingError - # This is for a hash with invalid encoding symbol, {"\xAE": 1} - :unrecoverable_error - rescue SyntaxError => e - case e.message - when /unexpected keyword_end/ - # "syntax error, unexpected keyword_end" - # - # example: - # if ( - # end - # - # example: - # end - return :unrecoverable_error - when /unexpected '\.'/ - # "syntax error, unexpected '.'" - # - # example: - # . - return :unrecoverable_error - when /unexpected tREGEXP_BEG/ - # "syntax error, unexpected tREGEXP_BEG, expecting keyword_do or '{' or '('" - # - # example: - # method / f / - return :unrecoverable_error - when /unterminated (?:string|regexp) meets end of file/ - # "unterminated regexp meets end of file" - # - # example: - # / - # - # "unterminated string meets end of file" - # - # example: - # ' - return :recoverable_error - when /unexpected end-of-input/ - # "syntax error, unexpected end-of-input, expecting keyword_end" - # - # example: - # if true - # hoge - # if false - # fuga - # end - return :recoverable_error - else - return :other_error - end - ensure - $VERBOSE = verbose - end - :valid - end - - def calc_indent_level(opens) - indent_level = 0 - opens.each_with_index do |t, index| - case t.event - when :on_heredoc_beg - if opens[index + 1]&.event != :on_heredoc_beg - if t.tok.match?(/^<<[~-]/) - indent_level += 1 - else - indent_level = 0 - end - end - when :on_tstring_beg, :on_regexp_beg, :on_symbeg, :on_backtick - # No indent: "", //, :"", `` - # Indent: %(), %r(), %i(), %x() - indent_level += 1 if t.tok.start_with? '%' - when :on_embdoc_beg - indent_level = 0 - else - indent_level += 1 unless t.tok == 'alias' || t.tok == 'undef' - end - end - indent_level - end - - FREE_INDENT_TOKENS = %i[on_tstring_beg on_backtick on_regexp_beg on_symbeg] - - def free_indent_token?(token) - FREE_INDENT_TOKENS.include?(token&.event) - end - - # Calculates the difference of pasted code's indent and indent calculated from tokens - def indent_difference(lines, line_results, line_index) - loop do - _tokens, prev_opens, _next_opens, min_depth = line_results[line_index] - open_token = prev_opens.last - if !open_token || (open_token.event != :on_heredoc_beg && !free_indent_token?(open_token)) - # If the leading whitespace is an indent, return the difference - indent_level = calc_indent_level(prev_opens.take(min_depth)) - calculated_indent = 2 * indent_level - actual_indent = lines[line_index][/^ */].size - return actual_indent - calculated_indent - elsif open_token.event == :on_heredoc_beg && open_token.tok.match?(/^<<[^-~]/) - return 0 - end - # If the leading whitespace is not an indent but part of a multiline token - # Calculate base_indent of the multiline token's beginning line - line_index = open_token.pos[0] - 1 - end - end - - def process_indent_level(tokens, lines, line_index, is_newline) - line_results = NestingParser.parse_by_line(tokens) - result = line_results[line_index] - if result - _tokens, prev_opens, next_opens, min_depth = result - else - # When last line is empty - prev_opens = next_opens = line_results.last[2] - min_depth = next_opens.size - end - - # To correctly indent line like `end.map do`, we use shortest open tokens on each line for indent calculation. - # Shortest open tokens can be calculated by `opens.take(min_depth)` - indent = 2 * calc_indent_level(prev_opens.take(min_depth)) - - preserve_indent = lines[line_index - (is_newline ? 1 : 0)][/^ */].size - - prev_open_token = prev_opens.last - next_open_token = next_opens.last - - # Calculates base indent for pasted code on the line where prev_open_token is located - # irb(main):001:1* if a # base_indent is 2, indent calculated from tokens is 0 - # irb(main):002:1* if b # base_indent is 6, indent calculated from tokens is 2 - # irb(main):003:0> c # base_indent is 6, indent calculated from tokens is 4 - if prev_open_token - base_indent = [0, indent_difference(lines, line_results, prev_open_token.pos[0] - 1)].max - else - base_indent = 0 - end - - if free_indent_token?(prev_open_token) - if is_newline && prev_open_token.pos[0] == line_index - # First newline inside free-indent token - base_indent + indent - else - # Accept any number of indent inside free-indent token - preserve_indent - end - elsif prev_open_token&.event == :on_embdoc_beg || next_open_token&.event == :on_embdoc_beg - if prev_open_token&.event == next_open_token&.event - # Accept any number of indent inside embdoc content - preserve_indent - else - # =begin or =end - 0 - end - elsif prev_open_token&.event == :on_heredoc_beg - tok = prev_open_token.tok - if prev_opens.size <= next_opens.size - if is_newline && lines[line_index].empty? && line_results[line_index - 1][1].last != next_open_token - # First line in heredoc - tok.match?(/^<<[-~]/) ? base_indent + indent : indent - elsif tok.match?(/^<<~/) - # Accept extra indent spaces inside `<<~` heredoc - [base_indent + indent, preserve_indent].max - else - # Accept any number of indent inside other heredoc - preserve_indent - end - else - # Heredoc close - prev_line_indent_level = calc_indent_level(prev_opens) - tok.match?(/^<<[~-]/) ? base_indent + 2 * (prev_line_indent_level - 1) : 0 - end - else - base_indent + indent - end - end - - def ltype_from_open_tokens(opens) - start_token = opens.reverse_each.find do |tok| - LTYPE_TOKENS.include?(tok.event) - end - return nil unless start_token - - case start_token&.event - when :on_tstring_beg - case start_token&.tok - when ?" then ?" - when /^%.$/ then ?" - when /^%Q.$/ then ?" - when ?' then ?' - when /^%q.$/ then ?' - end - when :on_regexp_beg then ?/ - when :on_symbeg then ?: - when :on_backtick then ?` - when :on_qwords_beg then ?] - when :on_words_beg then ?] - when :on_qsymbols_beg then ?] - when :on_symbols_beg then ?] - when :on_heredoc_beg - start_token&.tok =~ /<<[-~]?(['"`])\w+\1/ - $1 || ?" - else - nil - end - end - - def check_termination_in_prev_line(code, local_variables:) - tokens = self.class.ripper_lex_without_warning(code, local_variables: local_variables) - past_first_newline = false - index = tokens.rindex do |t| - # traverse first token before last line - if past_first_newline - if t.tok.include?("\n") - true - end - elsif t.tok.include?("\n") - past_first_newline = true - false - else - false - end - end - - if index - first_token = nil - last_line_tokens = tokens[(index + 1)..(tokens.size - 1)] - last_line_tokens.each do |t| - unless [:on_sp, :on_ignored_sp, :on_comment].include?(t.event) - first_token = t - break - end - end - - if first_token && first_token.state != Ripper::EXPR_DOT - tokens_without_last_line = tokens[0..index] - code_without_last_line = tokens_without_last_line.map(&:tok).join - opens_without_last_line = NestingParser.open_tokens(tokens_without_last_line) - if code_terminated?(code_without_last_line, tokens_without_last_line, opens_without_last_line, local_variables: local_variables) - return last_line_tokens.map(&:tok).join - end - end - end - false - end - end - # :startdoc: -end - -RubyLex = IRB::RubyLex -Object.deprecate_constant(:RubyLex) diff --git a/lib/irb/ruby_logo.aa b/lib/irb/ruby_logo.aa deleted file mode 100644 index d0143a448b..0000000000 --- a/lib/irb/ruby_logo.aa +++ /dev/null @@ -1,118 +0,0 @@ -TYPE: ASCII_LARGE - - ,,,;;;;;;;;;;;;;;;;;;;;;;,, - ,,,;;;;;;;;;,, ,;;;' ''';;, - ,,;;;''' ';;;, ,,;;'' '';, - ,;;'' ;;;;;;;;,,,,,, ';; - ,;;'' ;;;;';;;'''';;;;;;;;;,,,;; - ,,;'' ;;;; ';;, ''''';;, - ,;;' ;;;' ';;, ;; - ,;;' ,;;; '';,, ;; - ,;;' ;;; ';;, ,;; - ;;' ;;;' '';,, ;;; - ,;' ;;;; ';;, ;;' - ,;;' ,;;;;' ,,,,,,,,,,,,;;;;; - ,;' ,;;;;;;;;;;;;;;;;;;;;'''''''';;; - ;;' ,;;;;;;;;;,, ;;;; - ;;' ,;;;'' ;;, ';;,, ,;;;; - ;;' ,;;;' ;; '';;, ,;';;; - ;;' ,;;;' ;;, '';;,, ,;',;;; - ,;;; ,;;;' ;; '';;,, ,;' ;;;' - ;;;; ,,;;;' ;;, ';;;' ;;; -,;;; ,;;;;' ;; ,;;; ;;; -;;;;; ,,;;;;;' ;;, ,;';; ;;; -;;;;;, ,,;;;;;;;' ;; ,;;' ;;; ;;; -;;;;;;;,,,,,,,;;;;;;;;;;;;;;,,, ;;, ,;' ;; ;;; -;;' ;;;;;;;;;;'''' ,;';; ''';;;;,,, ;; ,;; ;; ;;; -;; ;;;'' ;; ';; ''';;;;,,,, ;;, ,;;' ;;, ;; -;; ;;;;, ;;' ';; ''';;;;,,;;;;' ';; ;; -;;;;;;';, ,;; ;; '';;;;, ;;,;; -;;; ;; ;;, ;; ;; ,;;' ';;, ;;;;; -;; ;;; ;;, ;;' ;; ,,;'' ';;, ;;;;; -;; ;; ;; ;; ;; ,;;' '';, ;;;; -;;,;; ;; ;;' ;; ,;;'' ';,, ;;;' - ;;;; ';; ,;; ;;,,;;'' ';;, ;;; - ';;; ';; ;; ,;;;;;;;;;;;;;,,,,,,,,,,,, ';;;;; - ';, ';,;;' ,,,;;'' '''''''';;;;;;;;;;;;;;;;;;; - ';;,,, ;;;; ,,,,;;;;;;,,,,,;;;;;;;;;;;;;;;;;;;'''''''''''''' - ''';;;;;;;;;;;;;;''''''''''''''' -TYPE: ASCII - ,,,;;;;''''';;;'';, - ,,;'' ';;,;;; ', - ,,'' ;;'';'''';;;;;; - ,;' ;; ',, ; - ,;' ,;' ';, ; - ;' ,;; ',,,; - ,' ,;;,,,,,,,,,,,;;;; - ;' ;;';;;; ,;; - ;' ,;' ;; '',, ,;;; - ;; ,;' ; '';, ,; ;' -;; ,;;' ;; ;; ;; -;;, ,,;;' ; ;'; ;; -;';;,,,,;;;;;;;,,, ;; ,' ; ;; -; ;;''' ,;'; ''';,,, ; ,;' ;;;; -;;;;, ; '; ''';;;' ';;; -;'; ;, ;' '; ,;' ', ;;; -;;; ; ,; '; ,,' ',, ;; -;;; '; ;' ';,,'' ';,;; - '; ';,; ,,;''''''''';;;;;;,,;;; - ';,,;;,,;;;;;;;;;;'''''''''''''' -TYPE: UNICODE_LARGE - - ⣀⣤⣴⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣶⣤⣄⡀ - ⢀⣀⣤⣴⣾⣿⣿⣿⠿⣿⣿⣿⣿⣦⣀ ⢀⣤⣶⣿⠿⠛⠁⠈⠉⠙⠻⢿⣷⣦⡀ - ⢀⣠⣴⣾⡿⠿⠛⠉⠉ ⠈⠙⢿⣿⣷⣤⡀ ⣠⣴⣾⡿⠟⠉ ⠉⠻⣿⣦ - ⢀⣤⣶⣿⠟⠋⠁ ⢿⣿⣿⣿⣿⣿⣿⣧⣤⣤⣤⣀⣀⣀⡀ ⠘⢿⣷⡀ - ⢀⣠⣾⡿⠟⠉ ⢸⣿⣿⣿⠟⢿⣿⣯⡙⠛⠛⠛⠿⠿⠿⢿⣿⣿⣶⣶⣶⣦⣤⣬⣿⣧ - ⣠⣴⣿⠟⠋ ⢸⣿⣿⡿ ⠈⠻⣿⣶⣄ ⠉⠉⠉⠙⠛⢻⣿⡆ - ⣠⣾⡿⠛⠁ ⣼⣿⣿⠃ ⠈⠙⢿⣷⣤⡀ ⠈⣿⡇ - ⣠⣾⡿⠋ ⢠⣿⣿⡏ ⠙⠻⣿⣦⣀ ⣿⡇ - ⣠⣾⡿⠋ ⢀⣿⣿⣿ ⠈⠛⢿⣷⣄⡀ ⢠⣿⡇ - ⢀⣾⡿⠋ ⢀⣾⣿⣿⠇ ⠙⠻⣿⣦⣀ ⢸⣿⡇ - ⢀⣴⣿⠟⠁ ⢀⣾⣿⣿⡟ ⠈⠻⢿⣷⣄ ⣾⣿⠇ - ⢠⣾⡿⠃ ⣠⣿⣿⣿⣿⠃ ⣀⣀⣀⣀⣀⣀⣀⣀⣤⣤⣤⣤⣽⣿⣿⣿⣿ - ⣰⣿⠟ ⣴⣿⣿⣿⣿⣿⣶⣶⣿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠛⣿⣿⣿ - ⣼⣿⠏ ⢠⣾⣿⣿⣿⡿⣿⣿⢿⣷⣦⣄ ⣼⣿⣿⣿ - ⣼⣿⠃ ⢀⣴⣿⣿⣿⠟⠋ ⢸⣿⡆⠈⠛⠿⣿⣦⣄⡀ ⣰⣿⣿⣿⡇ - ⢀⣾⣿⠃ ⢀⣴⣿⣿⣿⠟⠁ ⣿⣷ ⠈⠙⠻⣿⣶⣄⡀ ⣰⣿⠟⣿⣿⡇ - ⢀⣾⣿⠇ ⢀⣴⣿⣿⣿⠟⠁ ⢸⣿⡆ ⠙⠻⢿⣷⣤⣀ ⣰⣿⠏⢠⣿⣿⡇ - ⢠⣿⣿⡟ ⢀⣴⣿⣿⡿⠛⠁ ⣿⣷ ⠉⠻⢿⣷⣦⣀ ⣴⣿⠏ ⢸⣿⣿⠃ - ⣿⣿⣿⡇ ⣠⣴⣿⣿⡿⠋ ⢸⣿⡆ ⠈⠛⢿⣿⣿⠃ ⢸⣿⣿ -⢠⣿⣿⣿ ⢀⣴⣾⣿⣿⡿⠋ ⠈⣿⣧ ⢠⣾⣿⣿ ⢸⣿⣿ -⢸⣿⣿⣿⡇ ⣀⣴⣾⣿⣿⣿⡿⠋ ⢹⣿⡆ ⣴⣿⠟⢹⣿⡀ ⢸⣿⡿ -⢸⣿⡟⣿⣿⣄ ⣀⣤⣶⣿⣿⣿⣿⣿⡟⠉ ⠈⣿⣷ ⢠⣾⡿⠋ ⢸⣿⡇ ⣼⣿⡇ -⢸⣿⡇⢹⣿⣿⣷⣦⣤⣤⣤⣤⣤⣴⣶⣾⣿⣿⣿⣿⡿⠿⣿⣿⣿⣿⣷⣶⣤⣤⣀⡀ ⢹⣿⡆ ⢀⣴⣿⠟ ⣿⣧ ⣿⣿⡇ -⢸⣿⠃ ⢿⣿⣿⣿⣿⣿⣿⡿⠿⠿⠛⠛⠉⠉⠁ ⢰⣿⠟⣿⣷⡀⠉⠙⠛⠿⢿⣿⣶⣦⣤⣀⡀ ⠈⣿⣷ ⣠⣿⡿⠁ ⢿⣿ ⣿⣿⡇ -⢸⣿ ⢀⣾⣿⣿⠋⠉⠁ ⢀⣿⡿ ⠘⣿⣷⡀ ⠉⠙⠛⠿⠿⣿⣶⣦⣤⣄⣀ ⢹⣿⡄ ⣠⣾⡿⠋ ⢸⣿⡆ ⣿⣿ -⣸⣿⢀⣾⣿⣿⣿⣆ ⣸⣿⠃ ⠘⢿⣷⡀ ⠈⠉⠛⠻⠿⣿⣷⣶⣤⣌⣿⣷⣾⡿⠋ ⠘⣿⡇ ⣿⣿ -⣿⣿⣾⡿⣿⡿⠹⣿⡆ ⢠⣿⡏ ⠈⢿⣷⡀ ⠈⠉⠙⣻⣿⣿⣿⣀ ⣿⣷⢰⣿⣿ -⣿⣿⡿⢁⣿⡇ ⢻⣿⡄ ⣾⣿ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠋⠈⠻⢿⣷⣄ ⢻⣿⢸⣿⡟ -⣿⣿⠁⢸⣿⡇ ⢻⣿⡄ ⢸⣿⠇ ⠈⢿⣷⡀ ⣀⣴⣿⠟⠋ ⠙⢿⣷⣤⡀ ⢸⣿⣿⣿⡇ -⣿⣿ ⢸⣿⠁ ⠈⢿⣷⡀ ⢀⣿⡟ ⠈⢿⣷⡀ ⢀⣤⣾⡿⠛⠁ ⠙⠻⣿⣦⡀ ⠈⣿⣿⣿⡇ -⢸⣿⡄⣿⣿ ⠈⣿⣷⡀ ⣼⣿⠃ ⠈⢿⣷⡀ ⢀⣠⣶⣿⠟⠋ ⠈⠻⣿⣦⣄ ⣿⣿⣿⠇ -⠈⣿⣷⣿⡿ ⠘⣿⣧ ⢠⣿⡏ ⠈⢿⣷⣄⣤⣶⣿⠟⠋ ⠈⠛⢿⣷⣄ ⢸⣿⣿ - ⠘⣿⣿⡇ ⠘⣿⣧ ⣾⣿ ⢀⣠⣼⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣤⣤⣤⣤⣤⣤⣀⣀⣀⣀⣀⣀⡀ ⠙⢿⣷⣼⣿⣿ - ⠈⠻⣿⣦⡀ ⠹⣿⣆⢸⣿⠇ ⣀⣠⣴⣾⡿⠟⠋⠁ ⠉⠉⠉⠉⠉⠉⠛⠛⣛⣛⣛⣻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣿⣿⣿⡿ - ⠈⠻⢿⣷⣦⣄⣀⡀ ⢹⣿⣿⡟ ⢀⣀⣀⣤⣤⣶⣾⣿⣿⣿⣯⣥⣤⣤⣤⣤⣶⣶⣶⣶⣶⣶⣶⣾⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠟⠛⠛⠛⠛⠛⠛⠛⠉⠉⠉⠉⠉⠉ - ⠉⠙⠛⠿⠿⠿⣿⣿⣿⣿⠿⠿⠿⠿⠿⠿⠿⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠉ -TYPE: UNICODE - ⣀⣤⣴⣾⣿⣿⣿⡛⠛⠛⠛⠛⣻⣿⠿⠛⠛⠶⣤⡀ - ⣀⣴⠾⠛⠉⠁ ⠙⣿⣶⣤⣶⣟⣉ ⠈⠻⣦ - ⣀⣴⠟⠋ ⢸⣿⠟⠻⣯⡙⠛⠛⠛⠶⠶⠶⢶⣽⣇ - ⣠⡾⠋⠁ ⣾⡿ ⠈⠛⢦⣄ ⣿ - ⣠⡾⠋ ⣰⣿⠃ ⠙⠷⣤⡀ ⣿ - ⢀⡾⠋ ⣰⣿⡏ ⠈⠻⣦⣄⢠⣿ - ⣰⠟⠁ ⣴⣿⣿⣁⣀⣠⣤⣤⣤⣤⣤⣤⣤⣴⠶⠿⣿⡏ - ⣼⠏ ⢀⣾⣿⠟⣿⠿⣯⣍⠁ ⣰⣿⡇ - ⢀⣼⠋ ⢀⣴⣿⠟⠁ ⢸⡇ ⠙⠻⢦⣄⡀ ⢠⡿⣿⡇ -⢀⣾⡏ ⢀⣴⣿⠟⠁ ⣿ ⠉⠻⢶⣄⡀⣰⡟ ⣿⠃ -⣾⣿⠁ ⣠⣶⡿⠋⠁ ⢹⡇ ⠈⣿⡏ ⢸⣿ -⣿⣿⡆ ⢀⣠⣴⣿⡿⠋ ⠈⣿ ⢀⡾⠋⣿ ⢸⣿ -⣿⠸⣿⣶⣤⣤⣤⣤⣶⣾⠿⠿⣿⣿⠶⣤⣤⣀⡀ ⢹⡇ ⣴⠟⠁ ⣿⡀⢸⣿ -⣿⢀⣿⣟⠛⠋⠉⠁ ⢰⡟⠹⣧ ⠈⠉⠛⠻⠶⢦⣤⣀⡀ ⠈⣿ ⣠⡾⠃ ⢸⡇⢸⡇ -⣿⣾⣿⢿⡄ ⣿⠁ ⠘⣧ ⠉⠙⠛⠷⣿⣿⡋ ⠸⣇⣸⡇ -⣿⠃⣿⠈⢿⡄ ⣸⠇ ⠘⣧ ⢀⣤⠾⠋⠈⠻⣦⡀ ⣿⣿⡇ -⣿⢸⡏ ⠈⣷⡀ ⢠⡿ ⠘⣧⡀ ⣠⡴⠟⠁ ⠈⠻⣦⣀ ⢿⣿⠁ -⢻⣾⡇ ⠘⣷ ⣼⠃ ⠘⣷⣠⣴⠟⠋ ⠙⢷⣄⢸⣿ - ⠻⣧⡀ ⠘⣧⣰⡏ ⢀⣠⣤⠶⠛⠉⠛⠛⠛⠛⠛⠛⠻⢶⣶⣶⣶⣶⣶⣤⣤⣽⣿⣿ - ⠈⠛⠷⢦⣤⣽⣿⣥⣤⣶⣶⡿⠿⠿⠶⠶⠶⠶⠾⠛⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⠉⠉⠉⠁ diff --git a/lib/irb/source_finder.rb b/lib/irb/source_finder.rb deleted file mode 100644 index c515da5702..0000000000 --- a/lib/irb/source_finder.rb +++ /dev/null @@ -1,139 +0,0 @@ -# frozen_string_literal: true - -require_relative "ruby-lex" - -module IRB - class SourceFinder - class EvaluationError < StandardError; end - - class Source - attr_reader :file, :line - def initialize(file, line, ast_source = nil) - @file = file - @line = line - @ast_source = ast_source - end - - def file_exist? - File.exist?(@file) - end - - def binary_file? - # If the line is zero, it means that the target's source is probably in a binary file. - @line.zero? - end - - def file_content - @file_content ||= File.read(@file) - end - - def colorized_content - if !binary_file? && file_exist? - end_line = find_end - # To correctly colorize, we need to colorize full content and extract the relevant lines. - colored = IRB::Color.colorize_code(file_content) - colored.lines[@line - 1...end_line].join - elsif @ast_source - IRB::Color.colorize_code(@ast_source) - end - end - - private - - def find_end - lex = RubyLex.new - code = file_content - lines = code.lines[(@line - 1)..-1] - tokens = RubyLex.ripper_lex_without_warning(lines.join) - prev_tokens = [] - - # chunk with line number - tokens.chunk { |tok| tok.pos[0] }.each do |lnum, chunk| - code = lines[0..lnum].join - prev_tokens.concat chunk - continue = lex.should_continue?(prev_tokens) - syntax = lex.check_code_syntax(code, local_variables: []) - if !continue && syntax == :valid - return @line + lnum - end - end - @line - end - end - - private_constant :Source - - def initialize(irb_context) - @irb_context = irb_context - end - - def find_source(signature, super_level = 0) - case signature - when /\A(::)?[A-Z]\w*(::[A-Z]\w*)*\z/ # ConstName, ::ConstName, ConstPath::ConstName - eval_receiver_or_owner(signature) # trigger autoload - *parts, name = signature.split('::', -1) - base = - if parts.empty? # ConstName - find_const_owner(name) - elsif parts == [''] # ::ConstName - Object - else # ConstPath::ConstName - eval_receiver_or_owner(parts.join('::')) - end - file, line = base.const_source_location(name) - when /\A(?<owner>[A-Z]\w*(::[A-Z]\w*)*)#(?<method>[^ :.]+)\z/ # Class#method - owner = eval_receiver_or_owner(Regexp.last_match[:owner]) - method = Regexp.last_match[:method] - return unless owner.respond_to?(:instance_method) - method = method_target(owner, super_level, method, "owner") - file, line = method&.source_location - when /\A((?<receiver>.+)(\.|::))?(?<method>[^ :.]+)\z/ # method, receiver.method, receiver::method - receiver = eval_receiver_or_owner(Regexp.last_match[:receiver] || 'self') - method = Regexp.last_match[:method] - return unless receiver.respond_to?(method, true) - method = method_target(receiver, super_level, method, "receiver") - file, line = method&.source_location - end - return unless file && line - - if File.exist?(file) - Source.new(file, line) - elsif method - # Method defined with eval, probably in IRB session - source = RubyVM::InstructionSequence.of(method)&.script_lines&.join rescue nil - Source.new(file, line, source) - end - rescue EvaluationError - nil - end - - private - - def method_target(owner_receiver, super_level, method, type) - case type - when "owner" - target_method = owner_receiver.instance_method(method) - when "receiver" - target_method = owner_receiver.method(method) - end - super_level.times do |s| - target_method = target_method.super_method if target_method - end - target_method - rescue NameError - nil - end - - def eval_receiver_or_owner(code) - context_binding = @irb_context.workspace.binding - eval(code, context_binding) - rescue NameError - raise EvaluationError - end - - def find_const_owner(name) - module_nesting = @irb_context.workspace.binding.eval('::Module.nesting') - module_nesting.find { |mod| mod.const_defined?(name, false) } || module_nesting.find { |mod| mod.const_defined?(name) } || Object - end - end -end diff --git a/lib/irb/statement.rb b/lib/irb/statement.rb deleted file mode 100644 index 9591a40357..0000000000 --- a/lib/irb/statement.rb +++ /dev/null @@ -1,80 +0,0 @@ -# frozen_string_literal: true - -module IRB - class Statement - attr_reader :code - - def is_assignment? - raise NotImplementedError - end - - def suppresses_echo? - raise NotImplementedError - end - - def should_be_handled_by_debugger? - raise NotImplementedError - end - - class EmptyInput < Statement - def is_assignment? - false - end - - def suppresses_echo? - true - end - - # Debugger takes empty input to repeat the last command - def should_be_handled_by_debugger? - true - end - - def code - "" - end - end - - class Expression < Statement - def initialize(code, is_assignment) - @code = code - @is_assignment = is_assignment - end - - def suppresses_echo? - @code.match?(/;\s*\z/) - end - - def should_be_handled_by_debugger? - true - end - - def is_assignment? - @is_assignment - end - end - - class Command < Statement - attr_reader :command_class, :arg - - def initialize(original_code, command_class, arg) - @code = original_code - @command_class = command_class - @arg = arg - end - - def is_assignment? - false - end - - def suppresses_echo? - true - end - - def should_be_handled_by_debugger? - require_relative 'command/debug' - IRB::Command::DebugCommand > @command_class - end - end - end -end diff --git a/lib/irb/version.rb b/lib/irb/version.rb deleted file mode 100644 index 955a3a81da..0000000000 --- a/lib/irb/version.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true -# -# irb/version.rb - irb version definition file -# by Keiju ISHITSUKA(keiju@ishitsuka.com) -# - -module IRB # :nodoc: - VERSION = "1.14.1" - @RELEASE_VERSION = VERSION - @LAST_UPDATE_DATE = "2024-09-25" -end diff --git a/lib/irb/workspace.rb b/lib/irb/workspace.rb deleted file mode 100644 index 632b432439..0000000000 --- a/lib/irb/workspace.rb +++ /dev/null @@ -1,191 +0,0 @@ -# frozen_string_literal: true -# -# irb/workspace-binding.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -require "delegate" - -require_relative "helper_method" - -IRB::TOPLEVEL_BINDING = binding -module IRB # :nodoc: - class WorkSpace - # Creates a new workspace. - # - # set self to main if specified, otherwise - # inherit main from TOPLEVEL_BINDING. - def initialize(*main) - if main[0].kind_of?(Binding) - @binding = main.shift - elsif IRB.conf[:SINGLE_IRB] - @binding = TOPLEVEL_BINDING - else - case IRB.conf[:CONTEXT_MODE] - when 0 # binding in proc on TOPLEVEL_BINDING - @binding = eval("proc{binding}.call", - TOPLEVEL_BINDING, - __FILE__, - __LINE__) - when 1 # binding in loaded file - require "tempfile" - f = Tempfile.open("irb-binding") - f.print <<EOF - $binding = binding -EOF - f.close - load f.path - @binding = $binding - - when 2 # binding in loaded file(thread use) - unless defined? BINDING_QUEUE - IRB.const_set(:BINDING_QUEUE, Thread::SizedQueue.new(1)) - Thread.abort_on_exception = true - Thread.start do - eval "require \"irb/ws-for-case-2\"", TOPLEVEL_BINDING, __FILE__, __LINE__ - end - Thread.pass - end - @binding = BINDING_QUEUE.pop - - when 3 # binding in function on TOPLEVEL_BINDING - @binding = eval("self.class.remove_method(:irb_binding) if defined?(irb_binding); private; def irb_binding; binding; end; irb_binding", - TOPLEVEL_BINDING, - __FILE__, - __LINE__ - 3) - when 4 # binding is a copy of TOPLEVEL_BINDING (default) - # Note that this will typically be IRB::TOPLEVEL_BINDING - # This is to avoid RubyGems' local variables (see issue #17623) - @binding = TOPLEVEL_BINDING.dup - end - end - - if main.empty? - @main = eval("self", @binding) - else - @main = main[0] - end - IRB.conf[:__MAIN__] = @main - - unless main.empty? - case @main - when Module - @binding = eval("IRB.conf[:__MAIN__].module_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) - else - begin - @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, __FILE__, __LINE__) - rescue TypeError - fail CantChangeBinding, @main.inspect - end - end - end - - case @main - when Object - use_delegator = @main.frozen? - else - use_delegator = true - end - - if use_delegator - @main = SimpleDelegator.new(@main) - IRB.conf[:__MAIN__] = @main - @main.singleton_class.class_eval do - private - define_method(:binding, Kernel.instance_method(:binding)) - define_method(:local_variables, Kernel.instance_method(:local_variables)) - # Define empty method to avoid delegator warning, will be overridden. - define_method(:exit) {|*a, &b| } - define_method(:exit!) {|*a, &b| } - end - @binding = eval("IRB.conf[:__MAIN__].instance_eval('binding', __FILE__, __LINE__)", @binding, *@binding.source_location) - end - - @binding.local_variable_set(:_, nil) - end - - # The Binding of this workspace - attr_reader :binding - # The top-level workspace of this context, also available as - # <code>IRB.conf[:__MAIN__]</code> - attr_reader :main - - def load_helper_methods_to_main - ancestors = class<<main;ancestors;end - main.extend ExtendCommandBundle if !ancestors.include?(ExtendCommandBundle) - main.extend HelpersContainer if !ancestors.include?(HelpersContainer) - end - - # Evaluate the given +statements+ within the context of this workspace. - def evaluate(statements, file = __FILE__, line = __LINE__) - eval(statements, @binding, file, line) - end - - def local_variable_set(name, value) - @binding.local_variable_set(name, value) - end - - def local_variable_get(name) - @binding.local_variable_get(name) - end - - # error message manipulator - # WARN: Rails patches this method to filter its own backtrace. Be cautious when changing it. - # See: https://github.com/rails/rails/blob/main/railties/lib/rails/commands/console/console_command.rb#L8:~:text=def,filter_backtrace - def filter_backtrace(bt) - return nil if bt =~ /\/irb\/.*\.rb/ - return nil if bt =~ /\/irb\.rb/ - return nil if bt =~ /tool\/lib\/.*\.rb|runner\.rb/ # for tests in Ruby repository - case IRB.conf[:CONTEXT_MODE] - when 1 - return nil if bt =~ %r!/tmp/irb-binding! - when 3 - bt = bt.sub(/:\s*in `irb_binding'/, '') - end - bt - end - - def code_around_binding - file, pos = @binding.source_location - - if defined?(::SCRIPT_LINES__[file]) && lines = ::SCRIPT_LINES__[file] - code = ::SCRIPT_LINES__[file].join('') - else - begin - code = File.read(file) - rescue SystemCallError - return - end - end - - lines = Color.colorize_code(code).lines - pos -= 1 - - start_pos = [pos - 5, 0].max - end_pos = [pos + 5, lines.size - 1].min - - line_number_fmt = Color.colorize("%#{end_pos.to_s.length}d", [:BLUE, :BOLD]) - fmt = " %2s #{line_number_fmt}: %s" - - body = (start_pos..end_pos).map do |current_pos| - sprintf(fmt, pos == current_pos ? '=>' : '', current_pos + 1, lines[current_pos]) - end.join("") - - "\nFrom: #{file} @ line #{pos + 1} :\n\n#{body}#{Color.clear}\n" - end - end - - module HelpersContainer - class << self - def install_helper_methods - HelperMethod.helper_methods.each do |name, helper_method_class| - define_method name do |*args, **opts, &block| - helper_method_class.instance.execute(*args, **opts, &block) - end unless method_defined?(name) - end - end - end - - install_helper_methods - end -end diff --git a/lib/irb/ws-for-case-2.rb b/lib/irb/ws-for-case-2.rb deleted file mode 100644 index 03f42d73d9..0000000000 --- a/lib/irb/ws-for-case-2.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true -# -# irb/ws-for-case-2.rb - -# by Keiju ISHITSUKA(keiju@ruby-lang.org) -# - -while true - IRB::BINDING_QUEUE.push _ = binding -end diff --git a/lib/irb/xmp.rb b/lib/irb/xmp.rb deleted file mode 100644 index b1bc53283e..0000000000 --- a/lib/irb/xmp.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true -# -# xmp.rb - irb version of gotoken xmp -# by Keiju ISHITSUKA(Nippon Rational Inc.) -# - -require_relative "../irb" -require_relative "frame" - -# An example printer for irb. -# -# It's much like the standard library PrettyPrint, that shows the value of each -# expression as it runs. -# -# In order to use this library, you must first require it: -# -# require 'irb/xmp' -# -# Now, you can take advantage of the Object#xmp convenience method. -# -# xmp <<END -# foo = "bar" -# baz = 42 -# END -# #=> foo = "bar" -# #==>"bar" -# #=> baz = 42 -# #==>42 -# -# You can also create an XMP object, with an optional binding to print -# expressions in the given binding: -# -# ctx = binding -# x = XMP.new ctx -# x.puts -# #=> today = "a good day" -# #==>"a good day" -# ctx.eval 'today # is what?' -# #=> "a good day" -class XMP - - # Creates a new XMP object. - # - # The top-level binding or, optional +bind+ parameter will be used when - # creating the workspace. See WorkSpace.new for more information. - # - # This uses the +:XMP+ prompt mode. - # See {Custom Prompts}[rdoc-ref:IRB@Custom+Prompts] for more information. - def initialize(bind = nil) - IRB.init_config(nil) - - IRB.conf[:PROMPT_MODE] = :XMP - - bind = IRB::Frame.top(1) unless bind - ws = IRB::WorkSpace.new(bind) - @io = StringInputMethod.new - @irb = IRB::Irb.new(ws, @io) - @irb.context.ignore_sigint = false - - IRB.conf[:MAIN_CONTEXT] = @irb.context - end - - # Evaluates the given +exps+, for example: - # - # require 'irb/xmp' - # x = XMP.new - # - # x.puts '{:a => 1, :b => 2, :c => 3}' - # #=> {:a => 1, :b => 2, :c => 3} - # # ==>{:a=>1, :b=>2, :c=>3} - # x.puts 'foo = "bar"' - # # => foo = "bar" - # # ==>"bar" - def puts(exps) - @io.puts exps - - if @irb.context.ignore_sigint - begin - trap_proc_b = trap("SIGINT"){@irb.signal_handle} - catch(:IRB_EXIT) do - @irb.eval_input - end - ensure - trap("SIGINT", trap_proc_b) - end - else - catch(:IRB_EXIT) do - @irb.eval_input - end - end - end - - # A custom InputMethod class used by XMP for evaluating string io. - class StringInputMethod < IRB::InputMethod - # Creates a new StringInputMethod object - def initialize - super - @exps = [] - end - - # Whether there are any expressions left in this printer. - def eof? - @exps.empty? - end - - # Reads the next expression from this printer. - # - # See IO#gets for more information. - def gets - while l = @exps.shift - next if /^\s+$/ =~ l - l.concat "\n" - print @prompt, l - break - end - l - end - - # Concatenates all expressions in this printer, separated by newlines. - # - # An Encoding::CompatibilityError is raised of the given +exps+'s encoding - # doesn't match the previous expression evaluated. - def puts(exps) - if @encoding and exps.encoding != @encoding - enc = Encoding.compatible?(@exps.join("\n"), exps) - if enc.nil? - raise Encoding::CompatibilityError, "Encoding in which the passed expression is encoded is not compatible to the preceding's one" - else - @encoding = enc - end - else - @encoding = exps.encoding - end - @exps.concat exps.split(/\n/) - end - - # Returns the encoding of last expression printed by #puts. - attr_reader :encoding - end -end - -# A convenience method that's only available when the you require the IRB::XMP standard library. -# -# Creates a new XMP object, using the given expressions as the +exps+ -# parameter, and optional binding as +bind+ or uses the top-level binding. Then -# evaluates the given expressions using the +:XMP+ prompt mode. -# -# For example: -# -# require 'irb/xmp' -# ctx = binding -# xmp 'foo = "bar"', ctx -# #=> foo = "bar" -# #==>"bar" -# ctx.eval 'foo' -# #=> "bar" -# -# See XMP.new for more information. -def xmp(exps, bind = nil) - bind = IRB::Frame.top(1) unless bind - xmp = XMP.new(bind) - xmp.puts exps - xmp -end diff --git a/lib/json/ext/generator/state.rb b/lib/json/ext/generator/state.rb deleted file mode 100644 index 6cd9496e67..0000000000 --- a/lib/json/ext/generator/state.rb +++ /dev/null @@ -1,105 +0,0 @@ -# frozen_string_literal: true - -module JSON - module Ext - module Generator - class State - # call-seq: new(opts = {}) - # - # Instantiates a new State object, configured by _opts_. - # - # _opts_ can have the following keys: - # - # * *indent*: a string used to indent levels (default: ''), - # * *space*: a string that is put after, a : or , delimiter (default: ''), - # * *space_before*: a string that is put before a : pair delimiter (default: ''), - # * *object_nl*: a string that is put at the end of a JSON object (default: ''), - # * *array_nl*: a string that is put at the end of a JSON array (default: ''), - # * *allow_nan*: true if NaN, Infinity, and -Infinity should be - # generated, otherwise an exception is thrown, if these values are - # encountered. This options defaults to false. - # * *ascii_only*: true if only ASCII characters should be generated. This - # option defaults to false. - # * *buffer_initial_length*: sets the initial length of the generator's - # internal buffer. - def initialize(opts = nil) - if opts && !opts.empty? - configure(opts) - end - end - - # call-seq: configure(opts) - # - # Configure this State instance with the Hash _opts_, and return - # itself. - def configure(opts) - unless opts.is_a?(Hash) - if opts.respond_to?(:to_hash) - opts = opts.to_hash - elsif opts.respond_to?(:to_h) - opts = opts.to_h - else - raise TypeError, "can't convert #{opts.class} into Hash" - end - end - _configure(opts) - end - - alias_method :merge, :configure - - # call-seq: to_h - # - # Returns the configuration instance variables as a hash, that can be - # passed to the configure method. - def to_h - result = { - indent: indent, - space: space, - space_before: space_before, - object_nl: object_nl, - array_nl: array_nl, - allow_nan: allow_nan?, - ascii_only: ascii_only?, - max_nesting: max_nesting, - script_safe: script_safe?, - strict: strict?, - depth: depth, - buffer_initial_length: buffer_initial_length, - } - - instance_variables.each do |iv| - iv = iv.to_s[1..-1] - result[iv.to_sym] = self[iv] - end - - result - end - - alias_method :to_hash, :to_h - - # call-seq: [](name) - # - # Returns the value returned by method +name+. - def [](name) - if respond_to?(name) - __send__(name) - else - instance_variable_get("@#{name}") if - instance_variables.include?("@#{name}".to_sym) # avoid warning - end - end - - # call-seq: []=(name, value) - # - # Sets the attribute name to value. - def []=(name, value) - if respond_to?(name_writer = "#{name}=") - __send__ name_writer, value - else - instance_variable_set "@#{name}", value - end - end - end - end - end -end diff --git a/lib/logger.rb b/lib/logger.rb deleted file mode 100644 index 63179ec671..0000000000 --- a/lib/logger.rb +++ /dev/null @@ -1,757 +0,0 @@ -# frozen_string_literal: true -# logger.rb - simple logging utility -# Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>. -# -# Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair -# License:: -# You can redistribute it and/or modify it under the same terms of Ruby's -# license; either the dual license version in 2003, or any later version. -# Revision:: $Id$ -# -# A simple system for logging messages. See Logger for more documentation. - -require 'fiber' -require 'monitor' -require 'rbconfig' - -require_relative 'logger/version' -require_relative 'logger/formatter' -require_relative 'logger/log_device' -require_relative 'logger/severity' -require_relative 'logger/errors' - -# \Class \Logger provides a simple but sophisticated logging utility that -# you can use to create one or more -# {event logs}[https://en.wikipedia.org/wiki/Logging_(software)#Event_logs] -# for your program. -# Each such log contains a chronological sequence of entries -# that provides a record of the program's activities. -# -# == About the Examples -# -# All examples on this page assume that \Logger has been required: -# -# require 'logger' -# -# == Synopsis -# -# Create a log with Logger.new: -# -# # Single log file. -# logger = Logger.new('t.log') -# # Size-based rotated logging: 3 10-megabyte files. -# logger = Logger.new('t.log', 3, 10485760) -# # Period-based rotated logging: daily (also allowed: 'weekly', 'monthly'). -# logger = Logger.new('t.log', 'daily') -# # Log to an IO stream. -# logger = Logger.new($stdout) -# -# Add entries (level, message) with Logger#add: -# -# logger.add(Logger::DEBUG, 'Maximal debugging info') -# logger.add(Logger::INFO, 'Non-error information') -# logger.add(Logger::WARN, 'Non-error warning') -# logger.add(Logger::ERROR, 'Non-fatal error') -# logger.add(Logger::FATAL, 'Fatal error') -# logger.add(Logger::UNKNOWN, 'Most severe') -# -# Close the log with Logger#close: -# -# logger.close -# -# == Entries -# -# You can add entries with method Logger#add: -# -# logger.add(Logger::DEBUG, 'Maximal debugging info') -# logger.add(Logger::INFO, 'Non-error information') -# logger.add(Logger::WARN, 'Non-error warning') -# logger.add(Logger::ERROR, 'Non-fatal error') -# logger.add(Logger::FATAL, 'Fatal error') -# logger.add(Logger::UNKNOWN, 'Most severe') -# -# These shorthand methods also add entries: -# -# logger.debug('Maximal debugging info') -# logger.info('Non-error information') -# logger.warn('Non-error warning') -# logger.error('Non-fatal error') -# logger.fatal('Fatal error') -# logger.unknown('Most severe') -# -# When you call any of these methods, -# the entry may or may not be written to the log, -# depending on the entry's severity and on the log level; -# see {Log Level}[rdoc-ref:Logger@Log+Level] -# -# An entry always has: -# -# - A severity (the required argument to #add). -# - An automatically created timestamp. -# -# And may also have: -# -# - A message. -# - A program name. -# -# Example: -# -# logger = Logger.new($stdout) -# logger.add(Logger::INFO, 'My message.', 'mung') -# # => I, [2022-05-07T17:21:46.536234 #20536] INFO -- mung: My message. -# -# The default format for an entry is: -# -# "%s, [%s #%d] %5s -- %s: %s\n" -# -# where the values to be formatted are: -# -# - \Severity (one letter). -# - Timestamp. -# - Process id. -# - \Severity (word). -# - Program name. -# - Message. -# -# You can use a different entry format by: -# -# - Setting a custom format proc (affects following entries); -# see {formatter=}[Logger.html#attribute-i-formatter]. -# - Calling any of the methods above with a block -# (affects only the one entry). -# Doing so can have two benefits: -# -# - Context: the block can evaluate the entire program context -# and create a context-dependent message. -# - Performance: the block is not evaluated unless the log level -# permits the entry actually to be written: -# -# logger.error { my_slow_message_generator } -# -# Contrast this with the string form, where the string is -# always evaluated, regardless of the log level: -# -# logger.error("#{my_slow_message_generator}") -# -# === \Severity -# -# The severity of a log entry has two effects: -# -# - Determines whether the entry is selected for inclusion in the log; -# see {Log Level}[rdoc-ref:Logger@Log+Level]. -# - Indicates to any log reader (whether a person or a program) -# the relative importance of the entry. -# -# === Timestamp -# -# The timestamp for a log entry is generated automatically -# when the entry is created. -# -# The logged timestamp is formatted by method -# {Time#strftime}[rdoc-ref:Time#strftime] -# using this format string: -# -# '%Y-%m-%dT%H:%M:%S.%6N' -# -# Example: -# -# logger = Logger.new($stdout) -# logger.add(Logger::INFO) -# # => I, [2022-05-07T17:04:32.318331 #20536] INFO -- : nil -# -# You can set a different format using method #datetime_format=. -# -# === Message -# -# The message is an optional argument to an entry method: -# -# logger = Logger.new($stdout) -# logger.add(Logger::INFO, 'My message') -# # => I, [2022-05-07T18:15:37.647581 #20536] INFO -- : My message -# -# For the default entry formatter, <tt>Logger::Formatter</tt>, -# the message object may be: -# -# - A string: used as-is. -# - An Exception: <tt>message.message</tt> is used. -# - Anything else: <tt>message.inspect</tt> is used. -# -# *Note*: Logger::Formatter does not escape or sanitize -# the message passed to it. -# Developers should be aware that malicious data (user input) -# may be in the message, and should explicitly escape untrusted data. -# -# You can use a custom formatter to escape message data; -# see the example at {formatter=}[Logger.html#attribute-i-formatter]. -# -# === Program Name -# -# The program name is an optional argument to an entry method: -# -# logger = Logger.new($stdout) -# logger.add(Logger::INFO, 'My message', 'mung') -# # => I, [2022-05-07T18:17:38.084716 #20536] INFO -- mung: My message -# -# The default program name for a new logger may be set in the call to -# Logger.new via optional keyword argument +progname+: -# -# logger = Logger.new('t.log', progname: 'mung') -# -# The default program name for an existing logger may be set -# by a call to method #progname=: -# -# logger.progname = 'mung' -# -# The current program name may be retrieved with method -# {progname}[Logger.html#attribute-i-progname]: -# -# logger.progname # => "mung" -# -# == Log Level -# -# The log level setting determines whether an entry is actually -# written to the log, based on the entry's severity. -# -# These are the defined severities (least severe to most severe): -# -# logger = Logger.new($stdout) -# logger.add(Logger::DEBUG, 'Maximal debugging info') -# # => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info -# logger.add(Logger::INFO, 'Non-error information') -# # => I, [2022-05-07T17:59:14.349167 #20536] INFO -- : Non-error information -# logger.add(Logger::WARN, 'Non-error warning') -# # => W, [2022-05-07T18:00:45.337538 #20536] WARN -- : Non-error warning -# logger.add(Logger::ERROR, 'Non-fatal error') -# # => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error -# logger.add(Logger::FATAL, 'Fatal error') -# # => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error -# logger.add(Logger::UNKNOWN, 'Most severe') -# # => A, [2022-05-07T18:07:54.657491 #20536] ANY -- : Most severe -# -# The default initial level setting is Logger::DEBUG, the lowest level, -# which means that all entries are to be written, regardless of severity: -# -# logger = Logger.new($stdout) -# logger.level # => 0 -# logger.add(0, "My message") -# # => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message -# -# You can specify a different setting in a new logger -# using keyword argument +level+ with an appropriate value: -# -# logger = Logger.new($stdout, level: Logger::ERROR) -# logger = Logger.new($stdout, level: 'error') -# logger = Logger.new($stdout, level: :error) -# logger.level # => 3 -# -# With this level, entries with severity Logger::ERROR and higher -# are written, while those with lower severities are not written: -# -# logger = Logger.new($stdout, level: Logger::ERROR) -# logger.add(3) -# # => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil -# logger.add(2) # Silent. -# -# You can set the log level for an existing logger -# with method #level=: -# -# logger.level = Logger::ERROR -# -# These shorthand methods also set the level: -# -# logger.debug! # => 0 -# logger.info! # => 1 -# logger.warn! # => 2 -# logger.error! # => 3 -# logger.fatal! # => 4 -# -# You can retrieve the log level with method #level. -# -# logger.level = Logger::ERROR -# logger.level # => 3 -# -# These methods return whether a given -# level is to be written: -# -# logger.level = Logger::ERROR -# logger.debug? # => false -# logger.info? # => false -# logger.warn? # => false -# logger.error? # => true -# logger.fatal? # => true -# -# == Log File Rotation -# -# By default, a log file is a single file that grows indefinitely -# (until explicitly closed); there is no file rotation. -# -# To keep log files to a manageable size, -# you can use _log_ _file_ _rotation_, which uses multiple log files: -# -# - Each log file has entries for a non-overlapping -# time interval. -# - Only the most recent log file is open and active; -# the others are closed and inactive. -# -# === Size-Based Rotation -# -# For size-based log file rotation, call Logger.new with: -# -# - Argument +logdev+ as a file path. -# - Argument +shift_age+ with a positive integer: -# the number of log files to be in the rotation. -# - Argument +shift_size+ as a positive integer: -# the maximum size (in bytes) of each log file; -# defaults to 1048576 (1 megabyte). -# -# Examples: -# -# logger = Logger.new('t.log', 3) # Three 1-megabyte files. -# logger = Logger.new('t.log', 5, 10485760) # Five 10-megabyte files. -# -# For these examples, suppose: -# -# logger = Logger.new('t.log', 3) -# -# Logging begins in the new log file, +t.log+; -# the log file is "full" and ready for rotation -# when a new entry would cause its size to exceed +shift_size+. -# -# The first time +t.log+ is full: -# -# - +t.log+ is closed and renamed to +t.log.0+. -# - A new file +t.log+ is opened. -# -# The second time +t.log+ is full: -# -# - +t.log.0 is renamed as +t.log.1+. -# - +t.log+ is closed and renamed to +t.log.0+. -# - A new file +t.log+ is opened. -# -# Each subsequent time that +t.log+ is full, -# the log files are rotated: -# -# - +t.log.1+ is removed. -# - +t.log.0 is renamed as +t.log.1+. -# - +t.log+ is closed and renamed to +t.log.0+. -# - A new file +t.log+ is opened. -# -# === Periodic Rotation -# -# For periodic rotation, call Logger.new with: -# -# - Argument +logdev+ as a file path. -# - Argument +shift_age+ as a string period indicator. -# -# Examples: -# -# logger = Logger.new('t.log', 'daily') # Rotate log files daily. -# logger = Logger.new('t.log', 'weekly') # Rotate log files weekly. -# logger = Logger.new('t.log', 'monthly') # Rotate log files monthly. -# -# Example: -# -# logger = Logger.new('t.log', 'daily') -# -# When the given period expires: -# -# - The base log file, +t.log+ is closed and renamed -# with a date-based suffix such as +t.log.20220509+. -# - A new log file +t.log+ is opened. -# - Nothing is removed. -# -# The default format for the suffix is <tt>'%Y%m%d'</tt>, -# which produces a suffix similar to the one above. -# You can set a different format using create-time option -# +shift_period_suffix+; -# see details and suggestions at -# {Time#strftime}[rdoc-ref:Time#strftime]. -# -class Logger - _, name, rev = %w$Id$ - if name - name = name.chomp(",v") - else - name = File.basename(__FILE__) - end - rev ||= "v#{VERSION}" - ProgName = "#{name}/#{rev}" - - include Severity - - # Logging severity threshold (e.g. <tt>Logger::INFO</tt>). - def level - level_override[Fiber.current] || @level - end - - # Sets the log level; returns +severity+. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - # Argument +severity+ may be an integer, a string, or a symbol: - # - # logger.level = Logger::ERROR # => 3 - # logger.level = 3 # => 3 - # logger.level = 'error' # => "error" - # logger.level = :error # => :error - # - # Logger#sev_threshold= is an alias for Logger#level=. - # - def level=(severity) - @level = Severity.coerce(severity) - end - - # Adjust the log level during the block execution for the current Fiber only - # - # logger.with_level(:debug) do - # logger.debug { "Hello" } - # end - def with_level(severity) - prev, level_override[Fiber.current] = level, Severity.coerce(severity) - begin - yield - ensure - if prev - level_override[Fiber.current] = prev - else - level_override.delete(Fiber.current) - end - end - end - - # Program name to include in log messages. - attr_accessor :progname - - # Sets the date-time format. - # - # Argument +datetime_format+ should be either of these: - # - # - A string suitable for use as a format for method - # {Time#strftime}[rdoc-ref:Time#strftime]. - # - +nil+: the logger uses <tt>'%Y-%m-%dT%H:%M:%S.%6N'</tt>. - # - def datetime_format=(datetime_format) - @default_formatter.datetime_format = datetime_format - end - - # Returns the date-time format; see #datetime_format=. - # - def datetime_format - @default_formatter.datetime_format - end - - # Sets or retrieves the logger entry formatter proc. - # - # When +formatter+ is +nil+, the logger uses Logger::Formatter. - # - # When +formatter+ is a proc, a new entry is formatted by the proc, - # which is called with four arguments: - # - # - +severity+: The severity of the entry. - # - +time+: A Time object representing the entry's timestamp. - # - +progname+: The program name for the entry. - # - +msg+: The message for the entry (string or string-convertible object). - # - # The proc should return a string containing the formatted entry. - # - # This custom formatter uses - # {String#dump}[rdoc-ref:String#dump] - # to escape the message string: - # - # logger = Logger.new($stdout, progname: 'mung') - # original_formatter = logger.formatter || Logger::Formatter.new - # logger.formatter = proc { |severity, time, progname, msg| - # original_formatter.call(severity, time, progname, msg.dump) - # } - # logger.add(Logger::INFO, "hello \n ''") - # logger.add(Logger::INFO, "\f\x00\xff\\\"") - # - # Output: - # - # I, [2022-05-13T13:16:29.637488 #8492] INFO -- mung: "hello \n ''" - # I, [2022-05-13T13:16:29.637610 #8492] INFO -- mung: "\f\x00\xFF\\\"" - # - attr_accessor :formatter - - alias sev_threshold level - alias sev_threshold= level= - - # Returns +true+ if the log level allows entries with severity - # Logger::DEBUG to be written, +false+ otherwise. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def debug?; level <= DEBUG; end - - # Sets the log level to Logger::DEBUG. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def debug!; self.level = DEBUG; end - - # Returns +true+ if the log level allows entries with severity - # Logger::INFO to be written, +false+ otherwise. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def info?; level <= INFO; end - - # Sets the log level to Logger::INFO. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def info!; self.level = INFO; end - - # Returns +true+ if the log level allows entries with severity - # Logger::WARN to be written, +false+ otherwise. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def warn?; level <= WARN; end - - # Sets the log level to Logger::WARN. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def warn!; self.level = WARN; end - - # Returns +true+ if the log level allows entries with severity - # Logger::ERROR to be written, +false+ otherwise. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def error?; level <= ERROR; end - - # Sets the log level to Logger::ERROR. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def error!; self.level = ERROR; end - - # Returns +true+ if the log level allows entries with severity - # Logger::FATAL to be written, +false+ otherwise. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def fatal?; level <= FATAL; end - - # Sets the log level to Logger::FATAL. - # See {Log Level}[rdoc-ref:Logger@Log+Level]. - # - def fatal!; self.level = FATAL; end - - # :call-seq: - # Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options) - # - # With the single argument +logdev+, - # returns a new logger with all default options: - # - # Logger.new('t.log') # => #<Logger:0x000001e685dc6ac8> - # - # Argument +logdev+ must be one of: - # - # - A string filepath: entries are to be written - # to the file at that path; if the file at that path exists, - # new entries are appended. - # - An IO stream (typically +$stdout+, +$stderr+. or an open file): - # entries are to be written to the given stream. - # - +nil+ or +File::NULL+: no entries are to be written. - # - # Examples: - # - # Logger.new('t.log') - # Logger.new($stdout) - # - # The keyword options are: - # - # - +level+: sets the log level; default value is Logger::DEBUG. - # See {Log Level}[rdoc-ref:Logger@Log+Level]: - # - # Logger.new('t.log', level: Logger::ERROR) - # - # - +progname+: sets the default program name; default is +nil+. - # See {Program Name}[rdoc-ref:Logger@Program+Name]: - # - # Logger.new('t.log', progname: 'mung') - # - # - +formatter+: sets the entry formatter; default is +nil+. - # See {formatter=}[Logger.html#attribute-i-formatter]. - # - +datetime_format+: sets the format for entry timestamp; - # default is +nil+. - # See #datetime_format=. - # - +binmode+: sets whether the logger writes in binary mode; - # default is +false+. - # - +shift_period_suffix+: sets the format for the filename suffix - # for periodic log file rotation; default is <tt>'%Y%m%d'</tt>. - # See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation]. - # - +reraise_write_errors+: An array of exception classes, which will - # be reraised if there is an error when writing to the log device. - # The default is to swallow all exceptions raised. - # - def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG, - progname: nil, formatter: nil, datetime_format: nil, - binmode: false, shift_period_suffix: '%Y%m%d', - reraise_write_errors: []) - self.level = level - self.progname = progname - @default_formatter = Formatter.new - self.datetime_format = datetime_format - self.formatter = formatter - @logdev = nil - @level_override = {} - if logdev && logdev != File::NULL - @logdev = LogDevice.new(logdev, shift_age: shift_age, - shift_size: shift_size, - shift_period_suffix: shift_period_suffix, - binmode: binmode, - reraise_write_errors: reraise_write_errors) - end - end - - # Sets the logger's output stream: - # - # - If +logdev+ is +nil+, reopens the current output stream. - # - If +logdev+ is a filepath, opens the indicated file for append. - # - If +logdev+ is an IO stream - # (usually <tt>$stdout</tt>, <tt>$stderr</tt>, or an open File object), - # opens the stream for append. - # - # Example: - # - # logger = Logger.new('t.log') - # logger.add(Logger::ERROR, 'one') - # logger.close - # logger.add(Logger::ERROR, 'two') # Prints 'log writing failed. closed stream' - # logger.reopen - # logger.add(Logger::ERROR, 'three') - # logger.close - # File.readlines('t.log') - # # => - # # ["# Logfile created on 2022-05-12 14:21:19 -0500 by logger.rb/v1.5.0\n", - # # "E, [2022-05-12T14:21:27.596726 #22428] ERROR -- : one\n", - # # "E, [2022-05-12T14:23:05.847241 #22428] ERROR -- : three\n"] - # - def reopen(logdev = nil) - @logdev&.reopen(logdev) - self - end - - # Creates a log entry, which may or may not be written to the log, - # depending on the entry's severity and on the log level. - # See {Log Level}[rdoc-ref:Logger@Log+Level] - # and {Entries}[rdoc-ref:Logger@Entries] for details. - # - # Examples: - # - # logger = Logger.new($stdout, progname: 'mung') - # logger.add(Logger::INFO) - # logger.add(Logger::ERROR, 'No good') - # logger.add(Logger::ERROR, 'No good', 'gnum') - # - # Output: - # - # I, [2022-05-12T16:25:31.469726 #36328] INFO -- mung: mung - # E, [2022-05-12T16:25:55.349414 #36328] ERROR -- mung: No good - # E, [2022-05-12T16:26:35.841134 #36328] ERROR -- gnum: No good - # - # These convenience methods have implicit severity: - # - # - #debug. - # - #info. - # - #warn. - # - #error. - # - #fatal. - # - #unknown. - # - def add(severity, message = nil, progname = nil) - severity ||= UNKNOWN - if @logdev.nil? or severity < level - return true - end - if progname.nil? - progname = @progname - end - if message.nil? - if block_given? - message = yield - else - message = progname - progname = @progname - end - end - @logdev.write( - format_message(format_severity(severity), Time.now, progname, message)) - true - end - alias log add - - # Writes the given +msg+ to the log with no formatting; - # returns the number of characters written, - # or +nil+ if no log device exists: - # - # logger = Logger.new($stdout) - # logger << 'My message.' # => 10 - # - # Output: - # - # My message. - # - def <<(msg) - @logdev&.write(msg) - end - - # Equivalent to calling #add with severity <tt>Logger::DEBUG</tt>. - # - def debug(progname = nil, &block) - add(DEBUG, nil, progname, &block) - end - - # Equivalent to calling #add with severity <tt>Logger::INFO</tt>. - # - def info(progname = nil, &block) - add(INFO, nil, progname, &block) - end - - # Equivalent to calling #add with severity <tt>Logger::WARN</tt>. - # - def warn(progname = nil, &block) - add(WARN, nil, progname, &block) - end - - # Equivalent to calling #add with severity <tt>Logger::ERROR</tt>. - # - def error(progname = nil, &block) - add(ERROR, nil, progname, &block) - end - - # Equivalent to calling #add with severity <tt>Logger::FATAL</tt>. - # - def fatal(progname = nil, &block) - add(FATAL, nil, progname, &block) - end - - # Equivalent to calling #add with severity <tt>Logger::UNKNOWN</tt>. - # - def unknown(progname = nil, &block) - add(UNKNOWN, nil, progname, &block) - end - - # Closes the logger; returns +nil+: - # - # logger = Logger.new('t.log') - # logger.close # => nil - # logger.info('foo') # Prints "log writing failed. closed stream" - # - # Related: Logger#reopen. - def close - @logdev&.close - end - -private - - # \Severity label for logging (max 5 chars). - SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY).freeze - - def format_severity(severity) - SEV_LABEL[severity] || 'ANY' - end - - # Guarantee the existence of this ivar even when subclasses don't call the superclass constructor. - def level_override - @level_override ||= {} - end - - def format_message(severity, datetime, progname, msg) - (@formatter || @default_formatter).call(severity, datetime, progname, msg) - end -end diff --git a/lib/logger/errors.rb b/lib/logger/errors.rb deleted file mode 100644 index 88581793f0..0000000000 --- a/lib/logger/errors.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -class Logger - # not used after 1.2.7. just for compat. - class Error < RuntimeError # :nodoc: - end - class ShiftingError < Error # :nodoc: - end -end diff --git a/lib/logger/formatter.rb b/lib/logger/formatter.rb deleted file mode 100644 index c634dbf34d..0000000000 --- a/lib/logger/formatter.rb +++ /dev/null @@ -1,36 +0,0 @@ -# frozen_string_literal: true - -class Logger - # Default formatter for log messages. - class Formatter - Format = "%.1s, [%s #%d] %5s -- %s: %s\n" - DatetimeFormat = "%Y-%m-%dT%H:%M:%S.%6N" - - attr_accessor :datetime_format - - def initialize - @datetime_format = nil - end - - def call(severity, time, progname, msg) - sprintf(Format, severity, format_datetime(time), Process.pid, severity, progname, msg2str(msg)) - end - - private - - def format_datetime(time) - time.strftime(@datetime_format || DatetimeFormat) - end - - def msg2str(msg) - case msg - when ::String - msg - when ::Exception - "#{ msg.message } (#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }" - else - msg.inspect - end - end - end -end diff --git a/lib/logger/log_device.rb b/lib/logger/log_device.rb deleted file mode 100644 index 4876adf0b7..0000000000 --- a/lib/logger/log_device.rb +++ /dev/null @@ -1,214 +0,0 @@ -# frozen_string_literal: true - -require_relative 'period' - -class Logger - # Device used for logging messages. - class LogDevice - include Period - - attr_reader :dev - attr_reader :filename - include MonitorMixin - - def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false, reraise_write_errors: []) - @dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil - @binmode = binmode - @reraise_write_errors = reraise_write_errors - mon_initialize - set_dev(log) - if @filename - @shift_age = shift_age || 7 - @shift_size = shift_size || 1048576 - @shift_period_suffix = shift_period_suffix || '%Y%m%d' - - unless @shift_age.is_a?(Integer) - base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now - @next_rotate_time = next_rotate_time(base_time, @shift_age) - end - end - end - - def write(message) - begin - synchronize do - if @shift_age and @dev.respond_to?(:stat) - begin - check_shift_log - rescue *@reraise_write_errors - raise - rescue - warn("log shifting failed. #{$!}") - end - end - begin - @dev.write(message) - rescue *@reraise_write_errors - raise - rescue - warn("log writing failed. #{$!}") - end - end - rescue *@reraise_write_errors - raise - rescue Exception => ignored - warn("log writing failed. #{ignored}") - end - end - - def close - begin - synchronize do - @dev.close rescue nil - end - rescue Exception - @dev.close rescue nil - end - end - - def reopen(log = nil) - # reopen the same filename if no argument, do nothing for IO - log ||= @filename if @filename - if log - synchronize do - if @filename and @dev - @dev.close rescue nil # close only file opened by Logger - @filename = nil - end - set_dev(log) - end - end - self - end - - private - - def set_dev(log) - if log.respond_to?(:write) and log.respond_to?(:close) - @dev = log - if log.respond_to?(:path) and path = log.path - if File.exist?(path) - @filename = path - end - end - else - @dev = open_logfile(log) - @dev.sync = true - @dev.binmode if @binmode - @filename = log - end - end - - def open_logfile(filename) - begin - File.open(filename, (File::WRONLY | File::APPEND)) - rescue Errno::ENOENT - create_logfile(filename) - end - end - - def create_logfile(filename) - begin - logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL)) - logdev.flock(File::LOCK_EX) - logdev.sync = true - logdev.binmode if @binmode - add_log_header(logdev) - logdev.flock(File::LOCK_UN) - rescue Errno::EEXIST - # file is created by another process - logdev = open_logfile(filename) - logdev.sync = true - end - logdev - end - - def add_log_header(file) - file.write( - "# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName] - ) if file.size == 0 - end - - def check_shift_log - if @shift_age.is_a?(Integer) - # Note: always returns false if '0'. - if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size) - lock_shift_log { shift_log_age } - end - else - now = Time.now - if now >= @next_rotate_time - @next_rotate_time = next_rotate_time(now, @shift_age) - lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) } - end - end - end - - if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os'] - def lock_shift_log - yield - end - else - def lock_shift_log - retry_limit = 8 - retry_sleep = 0.1 - begin - File.open(@filename, File::WRONLY | File::APPEND) do |lock| - lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file - if File.identical?(@filename, lock) and File.identical?(lock, @dev) - yield # log shifting - else - # log shifted by another process (i-node before locking and i-node after locking are different) - @dev.close rescue nil - @dev = open_logfile(@filename) - @dev.sync = true - end - end - rescue Errno::ENOENT - # @filename file would not exist right after #rename and before #create_logfile - if retry_limit <= 0 - warn("log rotation inter-process lock failed. #{$!}") - else - sleep retry_sleep - retry_limit -= 1 - retry_sleep *= 2 - retry - end - end - rescue - warn("log rotation inter-process lock failed. #{$!}") - end - end - - def shift_log_age - (@shift_age-3).downto(0) do |i| - if FileTest.exist?("#{@filename}.#{i}") - File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}") - end - end - @dev.close rescue nil - File.rename("#{@filename}", "#{@filename}.0") - @dev = create_logfile(@filename) - return true - end - - def shift_log_period(period_end) - suffix = period_end.strftime(@shift_period_suffix) - age_file = "#{@filename}.#{suffix}" - if FileTest.exist?(age_file) - # try to avoid filename crash caused by Timestamp change. - idx = 0 - # .99 can be overridden; avoid too much file search with 'loop do' - while idx < 100 - idx += 1 - age_file = "#{@filename}.#{suffix}.#{idx}" - break unless FileTest.exist?(age_file) - end - end - @dev.close rescue nil - File.rename("#{@filename}", age_file) - @dev = create_logfile(@filename) - return true - end - end -end diff --git a/lib/logger/logger.gemspec b/lib/logger/logger.gemspec deleted file mode 100644 index 5e8232e4ab..0000000000 --- a/lib/logger/logger.gemspec +++ /dev/null @@ -1,22 +0,0 @@ -begin - require_relative "lib/logger/version" -rescue LoadError # Fallback to load version file in ruby core repository - require_relative "version" -end - -Gem::Specification.new do |spec| - spec.name = "logger" - spec.version = Logger::VERSION - spec.authors = ["Naotoshi Seo", "SHIBATA Hiroshi"] - spec.email = ["sonots@gmail.com", "hsbt@ruby-lang.org"] - - spec.summary = %q{Provides a simple logging utility for outputting messages.} - spec.description = %q{Provides a simple logging utility for outputting messages.} - spec.homepage = "https://github.com/ruby/logger" - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.files = Dir.glob("lib/**/*.rb") + ["logger.gemspec"] - spec.require_paths = ["lib"] - - spec.required_ruby_version = ">= 2.5.0" -end diff --git a/lib/logger/period.rb b/lib/logger/period.rb deleted file mode 100644 index a0359defe3..0000000000 --- a/lib/logger/period.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -class Logger - module Period - module_function - - SiD = 24 * 60 * 60 - - def next_rotate_time(now, shift_age) - case shift_age - when 'daily', :daily - t = Time.mktime(now.year, now.month, now.mday) + SiD - when 'weekly', :weekly - t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday) - when 'monthly', :monthly - t = Time.mktime(now.year, now.month, 1) + SiD * 32 - return Time.mktime(t.year, t.month, 1) - when 'now', 'everytime', :now, :everytime - return now - else - raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" - end - if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero? - hour = t.hour - t = Time.mktime(t.year, t.month, t.mday) - t += SiD if hour > 12 - end - t - end - - def previous_period_end(now, shift_age) - case shift_age - when 'daily', :daily - t = Time.mktime(now.year, now.month, now.mday) - SiD / 2 - when 'weekly', :weekly - t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2) - when 'monthly', :monthly - t = Time.mktime(now.year, now.month, 1) - SiD / 2 - when 'now', 'everytime', :now, :everytime - return now - else - raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime" - end - Time.mktime(t.year, t.month, t.mday, 23, 59, 59) - end - end -end diff --git a/lib/logger/severity.rb b/lib/logger/severity.rb deleted file mode 100644 index e96fb0d320..0000000000 --- a/lib/logger/severity.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -class Logger - # Logging severity. - module Severity - # Low-level information, mostly for developers. - DEBUG = 0 - # Generic (useful) information about system operation. - INFO = 1 - # A warning. - WARN = 2 - # A handleable error condition. - ERROR = 3 - # An unhandleable error that results in a program crash. - FATAL = 4 - # An unknown message that should always be logged. - UNKNOWN = 5 - - LEVELS = { - "debug" => DEBUG, - "info" => INFO, - "warn" => WARN, - "error" => ERROR, - "fatal" => FATAL, - "unknown" => UNKNOWN, - } - private_constant :LEVELS - - def self.coerce(severity) - if severity.is_a?(Integer) - severity - else - key = severity.to_s.downcase - LEVELS[key] || raise(ArgumentError, "invalid log level: #{severity}") - end - end - end -end diff --git a/lib/logger/version.rb b/lib/logger/version.rb deleted file mode 100644 index 2a0801be63..0000000000 --- a/lib/logger/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -class Logger - VERSION = "1.6.1" -end diff --git a/lib/mkmf.rb b/lib/mkmf.rb index d970e9a6ad..38a5a15fb5 100644 --- a/lib/mkmf.rb +++ b/lib/mkmf.rb @@ -40,7 +40,7 @@ class Array # :nodoc: end ## -# mkmf.rb is used by Ruby C extensions to generate a Makefile which will +# \Module \MakeMakefile is used by Ruby C extensions to generate a Makefile which will # correctly compile and link the C extension to Ruby and a third-party # library. module MakeMakefile @@ -419,7 +419,7 @@ MESSAGE # disable ASAN leak reporting - conftest programs almost always don't bother # to free their memory. - envs['ASAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('ASAN_OPTIONS') + envs['LSAN_OPTIONS'] = "detect_leaks=0" unless ENV.key?('LSAN_OPTIONS') return envs, expand[commands] end @@ -604,9 +604,9 @@ MSG yield(opt, opts) end - def try_link0(src, opt = "", **opts, &b) # :nodoc: + def try_link0(src, opt = "", ldflags: "", **opts, &b) # :nodoc: exe = CONFTEST+$EXEEXT - cmd = link_command("", opt) + cmd = link_command(ldflags, opt) if $universal require 'tmpdir' Dir.mktmpdir("mkmf_", oldtmpdir = ENV["TMPDIR"]) do |tmpdir| @@ -750,7 +750,7 @@ MSG # :nodoc: def try_ldflags(flags, werror: $mswin, **opts) - try_link(MAIN_DOES_NOTHING, flags, werror: werror, **opts) + try_link(MAIN_DOES_NOTHING, "", ldflags: flags, werror: werror, **opts) end # :startdoc: @@ -860,7 +860,7 @@ int main() {printf("%"PRI_CONFTEST_PREFIX"#{neg ? 'd' : 'u'}\\n", conftest_const v } unless strvars.empty? - prepare << "char " << strvars.map {|v| "#{v}[1024]"}.join(", ") << "; " + prepare << "char " << strvars.map {|v| %[#{v}[1024] = ""]}.join(", ") << "; " end when nil call = "" @@ -1968,7 +1968,7 @@ SRC if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig) # if and only if package specific config command is given elsif ($PKGCONFIG ||= - (pkgconfig = with_config("pkg-config") {config_string("PKG_CONFIG") || "pkg-config"}) && + (pkgconfig = with_config("pkg-config") {config_string("PKG_CONFIG") || ENV["PKG_CONFIG"] || "pkg-config"}) && find_executable0(pkgconfig) && pkgconfig) and xsystem([*envs, $PKGCONFIG, "--exists", pkg]) # default to pkg-config command @@ -1984,7 +1984,20 @@ SRC opts = Array(opts).map { |o| "--#{o}" } opts = xpopen([*envs, pkgconfig, *opts, *args], err:[:child, :out], &:read) Logging.open {puts opts.each_line.map{|s|"=> #{s.inspect}"}} - opts.strip if $?.success? + if $?.success? + opts = opts.strip + libarg, libpath = LIBARG, LIBPATHFLAG.strip + opts = opts.shellsplit.map { |s| + if s.start_with?('-l') + libarg % s[2..] + elsif s.start_with?('-L') + libpath % s[2..] + else + s + end + }.quote.join(" ") + opts + end } end orig_ldflags = $LDFLAGS @@ -2368,6 +2381,19 @@ RULES # directory, i.e. the current directory. It is included as part of the # +VPATH+ and added to the list of +INCFLAGS+. # + # Yields the configuration part of the makefile to be generated, as an array + # of strings, if the block is given. The returned value will be used the + # new configuration part. + # + # create_makefile('foo') {|conf| + # [ + # *conf, + # "MACRO_YOU_NEED = something", + # ] + # } + # + # If "depend" file exist in the source directory, that content will be + # included in the generated makefile, with formatted by depend_rules method. def create_makefile(target, srcprefix = nil) $target = target libpath = $DEFLIBPATH|$LIBPATH @@ -2484,16 +2510,19 @@ TIMESTAMP_DIR = #{$extout && $extmk ? '$(extout)/.timestamp' : '.'} sodir = $extout ? '$(TARGET_SO_DIR)' : '$(RUBYARCHDIR)' n = '$(TARGET_SO_DIR)$(TARGET)' cleanobjs = ["$(OBJS)"] + cleanlibs = [] if $extmk %w[bc i s].each {|ex| cleanobjs << "$(OBJS:.#{$OBJEXT}=.#{ex})"} end if target config_string('cleanobjs') {|t| cleanobjs << t.gsub(/\$\*/, "$(TARGET)#{deffile ? '-$(arch)': ''}")} + cleanlibs << '$(TARGET_SO)' end + config_string('cleanlibs') {|t| cleanlibs << t.gsub(/\$\*/) {n}} conf << "\ TARGET_SO_DIR =#{$extout ? " $(RUBYARCHDIR)/" : ''} TARGET_SO = $(TARGET_SO_DIR)$(DLLIB) -CLEANLIBS = #{'$(TARGET_SO) ' if target}#{config_string('cleanlibs') {|t| t.gsub(/\$\*/) {n}}} +CLEANLIBS = #{cleanlibs.join(' ')} CLEANOBJS = #{cleanobjs.join(' ')} *.bak TARGET_SO_DIR_TIMESTAMP = #{timestamp_file(sodir, target_prefix)} " #" @@ -2565,7 +2594,7 @@ static: #{$extmk && !$static ? "all" : %[$(STATIC_LIB)#{$extout ? " install-rb" dest = "#{dir}/#{File.basename(f)}" mfile.print("do-install-rb#{sfx}: #{dest}\n") mfile.print("#{dest}: #{f} #{timestamp_file(dir, target_prefix)}\n") - mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $(@D)\n") + mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $@\n") if defined?($installed_list) and !$extout mfile.print("\t@echo #{dest}>>$(INSTALLED_LIST)\n") end diff --git a/lib/net/http.rb b/lib/net/http.rb index 5cc9d2ce88..98d6793aee 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -475,8 +475,7 @@ module Net #:nodoc: # # - {::start}[rdoc-ref:Net::HTTP.start]: # Begins a new session in a new \Net::HTTP object. - # - {#started?}[rdoc-ref:Net::HTTP#started?] - # (aliased as {#active?}[rdoc-ref:Net::HTTP#active?]): + # - {#started?}[rdoc-ref:Net::HTTP#started?]: # Returns whether in a session. # - {#finish}[rdoc-ref:Net::HTTP#finish]: # Ends an active session. @@ -556,18 +555,15 @@ module Net #:nodoc: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Net::HTTP#request]: # Sends a request and returns a response object. - # - {#request_get}[rdoc-ref:Net::HTTP#request_get] - # (aliased as {#get2}[rdoc-ref:Net::HTTP#get2]): + # - {#request_get}[rdoc-ref:Net::HTTP#request_get]: # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_head}[rdoc-ref:Net::HTTP#request_head] - # (aliased as {#head2}[rdoc-ref:Net::HTTP#head2]): + # - {#request_head}[rdoc-ref:Net::HTTP#request_head]: # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_post}[rdoc-ref:Net::HTTP#request_post] - # (aliased as {#post2}[rdoc-ref:Net::HTTP#post2]): + # - {#request_post}[rdoc-ref:Net::HTTP#request_post]: # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. @@ -605,8 +601,7 @@ module Net #:nodoc: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. - # - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address] - # (aliased as {#proxyaddr}[rdoc-ref:Net::HTTP#proxyaddr]): + # - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address]: # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. @@ -718,8 +713,7 @@ module Net #:nodoc: # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Net::HTTP.version_1_2?] - # (aliased as {::is_version_1_2?}[rdoc-ref:Net::HTTP.is_version_1_2?] - # and {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]): + # (aliased as {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging @@ -730,7 +724,7 @@ module Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.4.1" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -1185,6 +1179,7 @@ module Net #:nodoc: @debug_output = options[:debug_output] @response_body_encoding = options[:response_body_encoding] @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1327,6 +1322,9 @@ module Net #:nodoc: # Sets the proxy password; # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server]. attr_writer :proxy_use_ssl # Returns the IP address for the connection. @@ -1533,9 +1531,9 @@ module Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym } # :nodoc: + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1552,11 +1550,11 @@ module Net #:nodoc: attr_accessor :cert_store # Sets or returns the available SSL ciphers. - # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. + # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. - # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. + # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. @@ -1566,15 +1564,15 @@ module Net #:nodoc: attr_accessor :ssl_timeout # Sets or returns the SSL version. - # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. + # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. - # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. + # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. attr_accessor :min_version # Sets or returns the maximum SSL version. - # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. + # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. @@ -1590,7 +1588,7 @@ module Net #:nodoc: # Sets or returns whether to verify that the server certificate is valid # for the hostname. - # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. + # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) @@ -1638,6 +1636,21 @@ module Net #:nodoc: self end + # Finishes the \HTTP session: + # + # http = Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1660,14 +1673,15 @@ module Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? @@ -1760,23 +1774,30 @@ module Net #:nodoc: end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Timeout.timeout(@open_timeout, Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1827,6 +1848,8 @@ module Net #:nodoc: } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1921,9 +1944,11 @@ module Net #:nodoc: alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) - require 'cgi/util' + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end @@ -1948,6 +1973,7 @@ module Net #:nodoc: path end end + # :startdoc: # # HTTP operations @@ -2402,7 +2428,9 @@ module Net #:nodoc: res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2559,6 +2587,11 @@ module Net #:nodoc: alias_method :D, :debug end + # for backward compatibility until Ruby 4.0 + # https://bugs.ruby-lang.org/issues/20900 + # https://github.com/bblimke/webmock/pull/1081 + HTTPSession = HTTP + deprecate_constant :HTTPSession end require_relative 'http/exceptions' @@ -2573,5 +2606,3 @@ require_relative 'http/response' require_relative 'http/responses' require_relative 'http/proxy_delta' - -require_relative 'http/backward' diff --git a/lib/net/http/backward.rb b/lib/net/http/backward.rb deleted file mode 100644 index b44577edbd..0000000000 --- a/lib/net/http/backward.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true -# for backward compatibility - -# :enddoc: - -class Net::HTTP - ProxyMod = ProxyDelta - deprecate_constant :ProxyMod -end - -module Net::NetPrivate - HTTPRequest = ::Net::HTTPRequest - deprecate_constant :HTTPRequest -end - -module Net - HTTPSession = HTTP - - HTTPInformationCode = HTTPInformation - HTTPSuccessCode = HTTPSuccess - HTTPRedirectionCode = HTTPRedirection - HTTPRetriableCode = HTTPRedirection - HTTPClientErrorCode = HTTPClientError - HTTPFatalErrorCode = HTTPClientError - HTTPServerErrorCode = HTTPServerError - HTTPResponseReceiver = HTTPResponse - - HTTPResponceReceiver = HTTPResponse # Typo since 2001 - - deprecate_constant :HTTPSession, - :HTTPInformationCode, - :HTTPSuccessCode, - :HTTPRedirectionCode, - :HTTPRetriableCode, - :HTTPClientErrorCode, - :HTTPFatalErrorCode, - :HTTPServerErrorCode, - :HTTPResponseReceiver, - :HTTPResponceReceiver -end diff --git a/lib/net/http/exceptions.rb b/lib/net/http/exceptions.rb index ceec8f7b0a..4342cfc0ef 100644 --- a/lib/net/http/exceptions.rb +++ b/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Net # Net::HTTP exception class. # You cannot use Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ module Net alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/net/http/generic_request.rb b/lib/net/http/generic_request.rb index 44e329a0c8..5b01ea4abd 100644 --- a/lib/net/http/generic_request.rb +++ b/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ class Net::HTTPGenericRequest if URI === uri_or_path then raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ class Net::HTTPGenericRequest initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -102,6 +99,31 @@ class Net::HTTPGenericRequest "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Net::HTTP::Post.new(uri) + # post.inspect # => "#<Net::HTTP::Post POST>" + # post.pretty_inspect + # # => #<Net::HTTP::Post + # POST + # path="/" + # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -220,7 +242,7 @@ class Net::HTTPGenericRequest end if host = self['host'] - host.sub!(/:.*/m, '') + host = URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -239,6 +261,8 @@ class Net::HTTPGenericRequest private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -260,7 +284,6 @@ class Net::HTTPGenericRequest def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +294,6 @@ class Net::HTTPGenericRequest raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +395,6 @@ class Net::HTTPGenericRequest buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +427,3 @@ class Net::HTTPGenericRequest end end - diff --git a/lib/net/http/header.rb b/lib/net/http/header.rb index f6c36f1b5e..5dcdcc7d74 100644 --- a/lib/net/http/header.rb +++ b/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ module Net::HTTPHeader end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ module Net::HTTPHeader ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,7 +494,7 @@ module Net::HTTPHeader alias canonical_each each_capitalized - def capitalize(name) + def capitalize(name) # :nodoc: name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ module Net::HTTPHeader @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ module Net::HTTPHeader false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/net/http/net-http.gemspec b/lib/net/http/net-http.gemspec index 0021136793..80e94c7bb6 100644 --- a/lib/net/http/net-http.gemspec +++ b/lib/net/http/net-http.gemspec @@ -21,19 +21,19 @@ Gem::Specification.new do |spec| spec.summary = %q{HTTP client api for Ruby.} spec.description = %q{HTTP client api for Ruby.} spec.homepage = "https://github.com/ruby/net-http" - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") + spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0") spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.metadata["changelog_uri"] = spec.homepage + "/releases" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git)}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.require_paths = ["lib"] - spec.add_dependency "uri" + spec.add_dependency "uri", ">= 0.11.1" end diff --git a/lib/net/http/requests.rb b/lib/net/http/requests.rb index e58057adf1..939d413f91 100644 --- a/lib/net/http/requests.rb +++ b/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Net::HTTP#get: sends +GET+ request, returns response object. # class Net::HTTP::Get < Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ end # - Net::HTTP#head: sends +HEAD+ request, returns response object. # class Net::HTTP::Head < Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ end # - Net::HTTP#post: sends +POST+ request, returns response object. # class Net::HTTP::Post < Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -130,6 +133,7 @@ end # - Net::HTTP#put: sends +PUT+ request, returns response object. # class Net::HTTP::Put < Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -162,6 +166,7 @@ end # - Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Net::HTTP::Delete < Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -193,6 +198,7 @@ end # - Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Net::HTTP::Options < Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -224,6 +230,7 @@ end # - Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Net::HTTP::Trace < Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -258,6 +265,7 @@ end # - Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Net::HTTP::Patch < Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -285,6 +293,7 @@ end # - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Net::HTTP::Propfind < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -308,6 +317,7 @@ end # - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Net::HTTP::Proppatch < Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -331,6 +341,7 @@ end # - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Net::HTTP::Mkcol < Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -354,6 +365,7 @@ end # - Net::HTTP#copy: sends +COPY+ request, returns response object. # class Net::HTTP::Copy < Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -377,6 +389,7 @@ end # - Net::HTTP#move: sends +MOVE+ request, returns response object. # class Net::HTTP::Move < Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -400,6 +413,7 @@ end # - Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Net::HTTP::Lock < Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -423,8 +437,8 @@ end # - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Net::HTTP::Unlock < Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/net/http/response.rb b/lib/net/http/response.rb index 40de963868..8804a99c9e 100644 --- a/lib/net/http/response.rb +++ b/lib/net/http/response.rb @@ -153,6 +153,7 @@ class Net::HTTPResponse end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ class Net::HTTPResponse # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/net/http/responses.rb b/lib/net/http/responses.rb index 6f6fb8d055..941a6fed80 100644 --- a/lib/net/http/responses.rb +++ b/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,19 +1158,21 @@ module Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Net::HTTPInformation, '2' => Net::HTTPSuccess, '3' => Net::HTTPRedirection, '4' => Net::HTTPClientError, '5' => Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Net::HTTPContinue, '101' => Net::HTTPSwitchProtocol, @@ -1170,5 +1238,5 @@ class Net::HTTPResponse '508' => Net::HTTPLoopDetected, '510' => Net::HTTPNotExtended, '511' => Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/lib/net/net-protocol.gemspec b/lib/net/net-protocol.gemspec index f9fd83f12b..2d911a966c 100644 --- a/lib/net/net-protocol.gemspec +++ b/lib/net/net-protocol.gemspec @@ -25,9 +25,8 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.require_paths = ["lib"] spec.add_dependency "timeout" diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb index 197ea09089..8c81298c0e 100644 --- a/lib/net/protocol.rb +++ b/lib/net/protocol.rb @@ -54,9 +54,20 @@ module Net # :nodoc: s.connect end end + + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) + end + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT end + # :stopdoc: class ProtocolError < StandardError; end class ProtoSyntaxError < ProtocolError; end class ProtoFatalError < ProtocolError; end @@ -66,6 +77,7 @@ module Net # :nodoc: class ProtoCommandError < ProtocolError; end class ProtoRetriableError < ProtocolError; end ProtocRetryError = ProtoRetriableError + # :startdoc: ## # OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot @@ -78,6 +90,7 @@ module Net # :nodoc: # response cannot be read within the read_timeout. class ReadTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -97,6 +110,7 @@ module Net # :nodoc: # response cannot be written within the write_timeout. Not raised on Windows. class WriteTimeout < Timeout::Error + # :stopdoc: def initialize(io = nil) @io = io end @@ -484,6 +498,7 @@ module Net # :nodoc: # The writer adapter class # class WriteAdapter + # :stopdoc: def initialize(writer) @writer = writer end diff --git a/lib/open-uri.rb b/lib/open-uri.rb index f2eddbcd2b..5983c7368b 100644 --- a/lib/open-uri.rb +++ b/lib/open-uri.rb @@ -91,8 +91,10 @@ end module OpenURI - VERSION = "0.4.1" + # The version string + VERSION = "0.5.0" + # The default options Options = { :proxy => true, :proxy_http_basic_authentication => true, @@ -394,24 +396,28 @@ module OpenURI end end + # Raised on HTTP session failure class HTTPError < StandardError - def initialize(message, io) + def initialize(message, io) # :nodoc: super(message) @io = io end + # StringIO having the received data attr_reader :io end # Raised on redirection, # only occurs when +redirect+ option for HTTP is +false+. class HTTPRedirect < HTTPError - def initialize(message, io, uri) + def initialize(message, io, uri) # :nodoc: super(message, io) @uri = uri end + # URI to redirect attr_reader :uri end + # Raised on too many redirection, class TooManyRedirects < HTTPError end diff --git a/lib/open3/version.rb b/lib/open3/version.rb index bfcec44ccc..322dd71e2a 100644 --- a/lib/open3/version.rb +++ b/lib/open3/version.rb @@ -1,3 +1,4 @@ module Open3 + # The version string VERSION = "0.2.1" end diff --git a/lib/optparse.rb b/lib/optparse.rb index 50641867f0..97178e284b 100644 --- a/lib/optparse.rb +++ b/lib/optparse.rb @@ -7,6 +7,7 @@ # # See OptionParser for documentation. # +require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) @@ -142,7 +143,7 @@ # Used: # # $ ruby optparse-test.rb -r -# optparse-test.rb:9:in `<main>': missing argument: -r (OptionParser::MissingArgument) +# optparse-test.rb:9:in '<main>': missing argument: -r (OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # @@ -235,7 +236,7 @@ # $ ruby optparse-test.rb --user 2 # #<struct User id=2, name="Gandalf"> # $ ruby optparse-test.rb --user 3 -# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # @@ -425,7 +426,9 @@ # class OptionParser # The version string - OptionParser::Version = "0.5.0" + VERSION = "0.8.1" + # An alias for compatibility + Version = VERSION # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -461,11 +464,14 @@ class OptionParser candidates end + def self.completable?(key) + String.try_convert(key) or defined?(key.id2name) + end + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end - public def complete(key, icase = false, pat = nil) candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size} if candidates.size == 1 @@ -496,7 +502,6 @@ class OptionParser end end - # # Map from option/keyword string to object with completion. # @@ -504,7 +509,6 @@ class OptionParser include Completion end - # # Individual switch class. Not important to the user. # @@ -546,18 +550,18 @@ class OptionParser def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, - desc = ([] if short or long), block = nil, &_block) + desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern block ||= _block - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # # Parses +arg+ and returns rest of +arg+ and matched portion to the # argument pattern. Yields when the pattern doesn't match substring. # - def parse_arg(arg) # :nodoc: + private def parse_arg(arg) # :nodoc: pattern or return nil, [arg] unless m = pattern.match(arg) yield(InvalidArgument, arg) @@ -575,22 +579,24 @@ class OptionParser yield(InvalidArgument, arg) # didn't match whole arg return arg[s.length..-1], m end - private :parse_arg # # Parses argument, converts and returns +arg+, +block+ and result of # conversion. Yields at semi-error condition instead of raising an # exception. # - def conv_arg(arg, val = []) # :nodoc: + private def conv_arg(arg, val = []) # :nodoc: + v, = *val if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end + if @values + @values.include?(val) or raise InvalidArgument, v + end return arg, block, val end - private :conv_arg # # Produces the summary text. Each line of the summary is yielded to the @@ -668,7 +674,7 @@ class OptionParser (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" - if /^--\[no-\](.+)$/ =~ opt + if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) @@ -874,14 +880,13 @@ class OptionParser # +lopts+:: Long style option list. # +nlopts+:: Negated long style options list. # - def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: + private def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc: sopts.each {|o| @short[o] = sw} if sopts lopts.each {|o| @long[o] = sw} if lopts nlopts.each {|o| @long[o] = nsw} if nsw and nlopts used = @short.invert.update(@long.invert) @list.delete_if {|o| Switch === o and !used[o]} end - private :update # # Inserts +switch+ at the head of the list, and associates short, long @@ -1032,7 +1037,6 @@ class OptionParser DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} - COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args @@ -1051,16 +1055,16 @@ XXX end def help_exit - if STDOUT.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) less = ENV["LESS"] - args = [{"LESS" => "#{!less || less.empty? ? '-' : less}Fe"}, pager, "w"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] print = proc do |f| f.puts help rescue Errno::EPIPE # pager terminated end if Process.respond_to?(:fork) and false - IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call(STDOUT)} + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} # unreachable end IO.popen(*args, &print) @@ -1102,7 +1106,7 @@ XXX # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| - parser.compsys(STDOUT, arg) + parser.compsys($stdout, arg) exit end end @@ -1288,7 +1292,15 @@ XXX # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1443,14 +1455,13 @@ XXX # +prv+:: Previously specified argument. # +msg+:: Exception message. # - def notwice(obj, prv, msg) # :nodoc: + private def notwice(obj, prv, msg) # :nodoc: unless !prv or prv == obj raise(ArgumentError, "argument #{msg} given twice: #{obj}", ParseError.filter_backtrace(caller(2))) end obj end - private :notwice SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc: @@ -1467,6 +1478,7 @@ XXX klass = nil q, a = nil has_arg = false + values = nil opts.each do |o| # argument class @@ -1480,7 +1492,7 @@ XXX end # directly specified pattern(any object possible to match) - if (!(String === o || Symbol === o)) and o.respond_to?(:match) + if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc @@ -1494,7 +1506,12 @@ XXX case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set + if Array === o + o, v = o.partition {|v,| Completion.completable?(v)} + values = notwice(v, values, 'values') unless v.empty? + next if o.empty? + end case pattern when CompletingHash when nil @@ -1504,11 +1521,13 @@ XXX raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Range + values = notwice(o, values, 'values') when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') - when /^--no-([^\[\]=\s]*)(.+)?/ + when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style @@ -1519,7 +1538,7 @@ XXX (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q - when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a @@ -1532,7 +1551,7 @@ XXX not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" - when /^--([^\[\]=\s]*)(.+)?/ + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1542,7 +1561,7 @@ XXX ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a @@ -1553,7 +1572,7 @@ XXX end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1562,7 +1581,7 @@ XXX end sdesc << "-#{q}" short << q - when /^=/ + when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else @@ -1571,12 +1590,18 @@ XXX end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern + if Range === values and klass + unless (!values.begin or klass === values.begin) and + (!values.end or klass === values.end) + raise ArgumentError, "range does not match class" + end + end if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, - conv, sdesc, ldesc, arg, desc, block) + conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) @@ -1585,7 +1610,7 @@ XXX else short << pattern s = (style || default_style).new(pattern, - conv, nil, nil, arg, desc, block) + conv, nil, nil, arg, desc, block, values) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), @@ -1703,7 +1728,7 @@ XXX parse_in_order(argv, setter, **keywords, &nonopt) end - def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: + private def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { @@ -1794,10 +1819,9 @@ XXX argv end - private :parse_in_order # Calls callback with _val_. - def callback!(cb, max_arity, *args) # :nodoc: + private def callback!(cb, max_arity, *args) # :nodoc: args.compact! if (size = args.size) < max_arity and cb.to_proc.lambda? @@ -1807,7 +1831,6 @@ XXX end cb.call(*args) end - private :callback! # # Parses command line arguments +argv+ in permutation mode and returns @@ -1827,7 +1850,7 @@ XXX # def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, **keywords, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1880,13 +1903,16 @@ XXX single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1895,16 +1921,16 @@ XXX arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=), **keywords) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1918,24 +1944,22 @@ XXX # Traverses @stack, sending each element method +id+ with +args+ and # +block+. # - def visit(id, *args, &block) # :nodoc: + private def visit(id, *args, &block) # :nodoc: @stack.reverse_each do |el| el.__send__(id, *args, &block) end nil end - private :visit # # Searches +key+ in @stack for +id+ hash and returns or yields the result. # - def search(id, key) # :nodoc: + private def search(id, key) # :nodoc: block_given = block_given? visit(:search, id, key) do |k| return block_given ? yield(k) : k end end - private :search # # Completes shortened long style option switch and returns pair of @@ -1946,7 +1970,7 @@ XXX # +icase+:: Search case insensitive if true. # +pat+:: Optional pattern for completion. # - def complete(typ, opt, icase = false, *pat) # :nodoc: + private def complete(typ, opt, icase = false, *pat) # :nodoc: if pat.empty? search(typ, opt) {|sw| return [sw, opt]} # exact match or... end @@ -1954,9 +1978,8 @@ XXX visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end - private :complete # # Returns additional info. @@ -2019,19 +2042,27 @@ XXX def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), **keywords) rescue nil + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end return [ - # XDG - ENV['XDG_CONFIG_HOME'], - '~/.config', + xdg, + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku - '~/config/settings', - ].any? {|dir| + ['~/config/settings', true], + ].any? {|dir, expand| next if !dir or dir.empty? - load(File.expand_path(basename, dir), **keywords) rescue nil + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil } end begin @@ -2237,9 +2268,10 @@ XXX argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end @@ -2282,42 +2314,42 @@ XXX # Raises when ambiguously completable string is encountered. # class AmbiguousOption < ParseError - const_set(:Reason, 'ambiguous option') + Reason = 'ambiguous option' # :nodoc: end # # Raises when there is an argument for a switch which takes no argument. # class NeedlessArgument < ParseError - const_set(:Reason, 'needless argument') + Reason = 'needless argument' # :nodoc: end # # Raises when a switch with mandatory argument has no argument. # class MissingArgument < ParseError - const_set(:Reason, 'missing argument') + Reason = 'missing argument' # :nodoc: end # # Raises when switch is undefined. # class InvalidOption < ParseError - const_set(:Reason, 'invalid option') + Reason = 'invalid option' # :nodoc: end # # Raises when the given argument does not match required format. # class InvalidArgument < ParseError - const_set(:Reason, 'invalid argument') + Reason = 'invalid argument' # :nodoc: end # # Raises when the given argument word can't be completed uniquely. # class AmbiguousArgument < InvalidArgument - const_set(:Reason, 'ambiguous argument') + Reason = 'ambiguous argument' # :nodoc: end # @@ -2416,9 +2448,11 @@ XXX # and DecimalNumeric. See Acceptable argument classes (in source code). # module Acceptables - const_set(:DecimalInteger, OptionParser::DecimalInteger) - const_set(:OctalInteger, OptionParser::OctalInteger) - const_set(:DecimalNumeric, OptionParser::DecimalNumeric) + # :stopdoc: + DecimalInteger = OptionParser::DecimalInteger + OctalInteger = OptionParser::OctalInteger + DecimalNumeric = OptionParser::DecimalNumeric + # :startdoc: end end diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 1aa54aa781..885b0ec380 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -3,7 +3,7 @@ name = File.basename(__FILE__, ".gemspec") version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*OptionParser::Version\s*=\s*"(.*)"/ =~ line and break $1 + /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 end rescue nil end @@ -14,7 +14,10 @@ Gem::Specification.new do |spec| spec.email = ["nobu@ruby-lang.org"] spec.summary = %q{OptionParser is a class for command-line option analysis.} - spec.description = %q{OptionParser is a class for command-line option analysis.} + spec.description = File.open(File.join(__dir__, "README.md")) do |readme| + readme.gets("") # heading + readme.gets("").chomp + end rescue spec.summary spec.homepage = "https://github.com/ruby/optparse" spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0") spec.licenses = ["Ruby", "BSD-2-Clause"] @@ -22,9 +25,9 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + - %w[README.md ChangeLog COPYING .document .rdoc_options] - spec.rdoc_options = ["--main=README.md", "--op=rdoc", "--page-dir=doc"] + dir, gemspec = File.split(__FILE__) + excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n} + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/ostruct.gemspec b/lib/ostruct.gemspec deleted file mode 100644 index 08a7aefb05..0000000000 --- a/lib/ostruct.gemspec +++ /dev/null @@ -1,24 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Marc-Andre Lafortune"] - spec.email = ["ruby-core@marc-andre.ca"] - - spec.summary = %q{Class to build custom data structures, similar to a Hash.} - spec.description = %q{Class to build custom data structures, similar to a Hash.} - spec.homepage = "https://github.com/ruby/ostruct" - spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = ">= 2.5.0" - - spec.files = [".gitignore", "Gemfile", "LICENSE.txt", "README.md", "Rakefile", "bin/console", "bin/setup", "lib/ostruct.rb", "ostruct.gemspec"] - spec.require_paths = ["lib"] -end diff --git a/lib/ostruct.rb b/lib/ostruct.rb deleted file mode 100644 index c762baa5a5..0000000000 --- a/lib/ostruct.rb +++ /dev/null @@ -1,489 +0,0 @@ -# frozen_string_literal: true -# -# = ostruct.rb: OpenStruct implementation -# -# Author:: Yukihiro Matsumoto -# Documentation:: Gavin Sinclair -# -# OpenStruct allows the creation of data objects with arbitrary attributes. -# See OpenStruct for an example. -# - -# -# An OpenStruct is a data structure, similar to a Hash, that allows the -# definition of arbitrary attributes with their accompanying values. This is -# accomplished by using Ruby's metaprogramming to define methods on the class -# itself. -# -# == Examples -# -# require "ostruct" -# -# person = OpenStruct.new -# person.name = "John Smith" -# person.age = 70 -# -# person.name # => "John Smith" -# person.age # => 70 -# person.address # => nil -# -# An OpenStruct employs a Hash internally to store the attributes and values -# and can even be initialized with one: -# -# australia = OpenStruct.new(:country => "Australia", :capital => "Canberra") -# # => #<OpenStruct country="Australia", capital="Canberra"> -# -# Hash keys with spaces or characters that could normally not be used for -# method calls (e.g. <code>()[]*</code>) will not be immediately available -# on the OpenStruct object as a method for retrieval or assignment, but can -# still be reached through the Object#send method or using []. -# -# measurements = OpenStruct.new("length (in inches)" => 24) -# measurements[:"length (in inches)"] # => 24 -# measurements.send("length (in inches)") # => 24 -# -# message = OpenStruct.new(:queued? => true) -# message.queued? # => true -# message.send("queued?=", false) -# message.queued? # => false -# -# Removing the presence of an attribute requires the execution of the -# delete_field method as setting the property value to +nil+ will not -# remove the attribute. -# -# first_pet = OpenStruct.new(:name => "Rowdy", :owner => "John Smith") -# second_pet = OpenStruct.new(:name => "Rowdy") -# -# first_pet.owner = nil -# first_pet # => #<OpenStruct name="Rowdy", owner=nil> -# first_pet == second_pet # => false -# -# first_pet.delete_field(:owner) -# first_pet # => #<OpenStruct name="Rowdy"> -# first_pet == second_pet # => true -# -# Ractor compatibility: A frozen OpenStruct with shareable values is itself shareable. -# -# == Caveats -# -# An OpenStruct utilizes Ruby's method lookup structure to find and define the -# necessary methods for properties. This is accomplished through the methods -# method_missing and define_singleton_method. -# -# This should be a consideration if there is a concern about the performance of -# the objects that are created, as there is much more overhead in the setting -# of these properties compared to using a Hash or a Struct. -# Creating an open struct from a small Hash and accessing a few of the -# entries can be 200 times slower than accessing the hash directly. -# -# This is a potential security issue; building OpenStruct from untrusted user data -# (e.g. JSON web request) may be susceptible to a "symbol denial of service" attack -# since the keys create methods and names of methods are never garbage collected. -# -# This may also be the source of incompatibilities between Ruby versions: -# -# o = OpenStruct.new -# o.then # => nil in Ruby < 2.6, enumerator for Ruby >= 2.6 -# -# Builtin methods may be overwritten this way, which may be a source of bugs -# or security issues: -# -# o = OpenStruct.new -# o.methods # => [:to_h, :marshal_load, :marshal_dump, :each_pair, ... -# o.methods = [:foo, :bar] -# o.methods # => [:foo, :bar] -# -# To help remedy clashes, OpenStruct uses only protected/private methods ending with <code>!</code> -# and defines aliases for builtin public methods by adding a <code>!</code>: -# -# o = OpenStruct.new(make: 'Bentley', class: :luxury) -# o.class # => :luxury -# o.class! # => OpenStruct -# -# It is recommended (but not enforced) to not use fields ending in <code>!</code>; -# Note that a subclass' methods may not be overwritten, nor can OpenStruct's own methods -# ending with <code>!</code>. -# -# For all these reasons, consider not using OpenStruct at all. -# -class OpenStruct - VERSION = "0.6.0" - - HAS_PERFORMANCE_WARNINGS = begin - Warning[:performance] - true - rescue NoMethodError, ArgumentError - false - end - private_constant :HAS_PERFORMANCE_WARNINGS - - # - # Creates a new OpenStruct object. By default, the resulting OpenStruct - # object will have no attributes. - # - # The optional +hash+, if given, will generate attributes and values - # (can be a Hash, an OpenStruct or a Struct). - # For example: - # - # require "ostruct" - # hash = { "country" => "Australia", :capital => "Canberra" } - # data = OpenStruct.new(hash) - # - # data # => #<OpenStruct country="Australia", capital="Canberra"> - # - def initialize(hash=nil) - if HAS_PERFORMANCE_WARNINGS && Warning[:performance] - warn "OpenStruct use is discouraged for performance reasons", uplevel: 1, category: :performance - end - - if hash - update_to_values!(hash) - else - @table = {} - end - end - - # Duplicates an OpenStruct object's Hash table. - private def initialize_clone(orig) # :nodoc: - super # clones the singleton class for us - @table = @table.dup unless @table.frozen? - end - - private def initialize_dup(orig) # :nodoc: - super - update_to_values!(@table) - end - - private def update_to_values!(hash) # :nodoc: - @table = {} - hash.each_pair do |k, v| - set_ostruct_member_value!(k, v) - end - end - - # - # call-seq: - # ostruct.to_h -> hash - # ostruct.to_h {|name, value| block } -> hash - # - # Converts the OpenStruct to a hash with keys representing - # each attribute (as symbols) and their corresponding values. - # - # If a block is given, the results of the block on each pair of - # the receiver will be used as pairs. - # - # require "ostruct" - # data = OpenStruct.new("country" => "Australia", :capital => "Canberra") - # data.to_h # => {:country => "Australia", :capital => "Canberra" } - # data.to_h {|name, value| [name.to_s, value.upcase] } - # # => {"country" => "AUSTRALIA", "capital" => "CANBERRA" } - # - if {test: :to_h}.to_h{ [:works, true] }[:works] # RUBY_VERSION < 2.6 compatibility - def to_h(&block) - if block - @table.to_h(&block) - else - @table.dup - end - end - else - def to_h(&block) - if block - @table.map(&block).to_h - else - @table.dup - end - end - end - - # - # :call-seq: - # ostruct.each_pair {|name, value| block } -> ostruct - # ostruct.each_pair -> Enumerator - # - # Yields all attributes (as symbols) along with the corresponding values - # or returns an enumerator if no block is given. - # - # require "ostruct" - # data = OpenStruct.new("country" => "Australia", :capital => "Canberra") - # data.each_pair.to_a # => [[:country, "Australia"], [:capital, "Canberra"]] - # - def each_pair - return to_enum(__method__) { @table.size } unless defined?(yield) - @table.each_pair{|p| yield p} - self - end - - # - # Provides marshalling support for use by the Marshal library. - # - def marshal_dump # :nodoc: - @table - end - - # - # Provides marshalling support for use by the Marshal library. - # - alias_method :marshal_load, :update_to_values! # :nodoc: - - # - # Used internally to defined properties on the - # OpenStruct. It does this by using the metaprogramming function - # define_singleton_method for both the getter method and the setter method. - # - def new_ostruct_member!(name) # :nodoc: - unless @table.key?(name) || is_method_protected!(name) - if defined?(::Ractor) - getter_proc = nil.instance_eval{ Proc.new { @table[name] } } - setter_proc = nil.instance_eval{ Proc.new {|x| @table[name] = x} } - ::Ractor.make_shareable(getter_proc) - ::Ractor.make_shareable(setter_proc) - else - getter_proc = Proc.new { @table[name] } - setter_proc = Proc.new {|x| @table[name] = x} - end - define_singleton_method!(name, &getter_proc) - define_singleton_method!("#{name}=", &setter_proc) - end - end - private :new_ostruct_member! - - private def is_method_protected!(name) # :nodoc: - if !respond_to?(name, true) - false - elsif name.match?(/!$/) - true - else - owner = method!(name).owner - if owner.class == ::Class - owner < ::OpenStruct - else - self.class!.ancestors.any? do |mod| - return false if mod == ::OpenStruct - mod == owner - end - end - end - end - - def freeze - @table.freeze - super - end - - private def method_missing(mid, *args) # :nodoc: - len = args.length - if mname = mid[/.*(?==\z)/m] - if len != 1 - raise! ArgumentError, "wrong number of arguments (given #{len}, expected 1)", caller(1) - end - set_ostruct_member_value!(mname, args[0]) - elsif len == 0 - @table[mid] - else - begin - super - rescue NoMethodError => err - err.backtrace.shift - raise! - end - end - end - - # - # :call-seq: - # ostruct[name] -> object - # - # Returns the value of an attribute, or +nil+ if there is no such attribute. - # - # require "ostruct" - # person = OpenStruct.new("name" => "John Smith", "age" => 70) - # person[:age] # => 70, same as person.age - # - def [](name) - @table[name.to_sym] - end - - # - # :call-seq: - # ostruct[name] = obj -> obj - # - # Sets the value of an attribute. - # - # require "ostruct" - # person = OpenStruct.new("name" => "John Smith", "age" => 70) - # person[:age] = 42 # equivalent to person.age = 42 - # person.age # => 42 - # - def []=(name, value) - name = name.to_sym - new_ostruct_member!(name) - @table[name] = value - end - alias_method :set_ostruct_member_value!, :[]= - private :set_ostruct_member_value! - - # :call-seq: - # ostruct.dig(name, *identifiers) -> object - # - # Finds and returns the object in nested objects - # that is specified by +name+ and +identifiers+. - # The nested objects may be instances of various classes. - # See {Dig Methods}[rdoc-ref:dig_methods.rdoc]. - # - # Examples: - # require "ostruct" - # address = OpenStruct.new("city" => "Anytown NC", "zip" => 12345) - # person = OpenStruct.new("name" => "John Smith", "address" => address) - # person.dig(:address, "zip") # => 12345 - # person.dig(:business_address, "zip") # => nil - def dig(name, *names) - begin - name = name.to_sym - rescue NoMethodError - raise! TypeError, "#{name} is not a symbol nor a string" - end - @table.dig(name, *names) - end - - # - # Removes the named field from the object and returns the value the field - # contained if it was defined. You may optionally provide a block. - # If the field is not defined, the result of the block is returned, - # or a NameError is raised if no block was given. - # - # require "ostruct" - # - # person = OpenStruct.new(name: "John", age: 70, pension: 300) - # - # person.delete_field!("age") # => 70 - # person # => #<OpenStruct name="John", pension=300> - # - # Setting the value to +nil+ will not remove the attribute: - # - # person.pension = nil - # person # => #<OpenStruct name="John", pension=nil> - # - # person.delete_field('number') # => NameError - # - # person.delete_field('number') { 8675_309 } # => 8675309 - # - def delete_field(name, &block) - sym = name.to_sym - begin - singleton_class.remove_method(sym, "#{sym}=") - rescue NameError - end - @table.delete(sym) do - return yield if block - raise! NameError.new("no field '#{sym}' in #{self}", sym) - end - end - - InspectKey = :__inspect_key__ # :nodoc: - - # - # Returns a string containing a detailed summary of the keys and values. - # - def inspect - ids = (Thread.current[InspectKey] ||= []) - if ids.include?(object_id) - detail = ' ...' - else - ids << object_id - begin - detail = @table.map do |key, value| - " #{key}=#{value.inspect}" - end.join(',') - ensure - ids.pop - end - end - ['#<', self.class!, detail, '>'].join - end - alias :to_s :inspect - - attr_reader :table # :nodoc: - alias table! table - protected :table! - - # - # Compares this object and +other+ for equality. An OpenStruct is equal to - # +other+ when +other+ is an OpenStruct and the two objects' Hash tables are - # equal. - # - # require "ostruct" - # first_pet = OpenStruct.new("name" => "Rowdy") - # second_pet = OpenStruct.new(:name => "Rowdy") - # third_pet = OpenStruct.new("name" => "Rowdy", :age => nil) - # - # first_pet == second_pet # => true - # first_pet == third_pet # => false - # - def ==(other) - return false unless other.kind_of?(OpenStruct) - @table == other.table! - end - - # - # Compares this object and +other+ for equality. An OpenStruct is eql? to - # +other+ when +other+ is an OpenStruct and the two objects' Hash tables are - # eql?. - # - def eql?(other) - return false unless other.kind_of?(OpenStruct) - @table.eql?(other.table!) - end - - # Computes a hash code for this OpenStruct. - def hash # :nodoc: - @table.hash - end - - # - # Provides marshalling support for use by the YAML library. - # - def encode_with(coder) # :nodoc: - @table.each_pair do |key, value| - coder[key.to_s] = value - end - if @table.size == 1 && @table.key?(:table) # support for legacy format - # in the very unlikely case of a single entry called 'table' - coder['legacy_support!'] = true # add a bogus second entry - end - end - - # - # Provides marshalling support for use by the YAML library. - # - def init_with(coder) # :nodoc: - h = coder.map - if h.size == 1 # support for legacy format - key, val = h.first - if key == 'table' - h = val - end - end - update_to_values!(h) - end - - # Make all public methods (builtin or our own) accessible with <code>!</code>: - give_access = instance_methods - # See https://github.com/ruby/ostruct/issues/30 - give_access -= %i[instance_exec instance_eval eval] if RUBY_ENGINE == 'jruby' - give_access.each do |method| - next if method.match(/\W$/) - - new_name = "#{method}!" - alias_method new_name, method - end - # Other builtin private methods we use: - alias_method :raise!, :raise - private :raise! - - # See https://github.com/ruby/ostruct/issues/40 - if RUBY_ENGINE != 'jruby' - alias_method :block_given!, :block_given? - private :block_given! - end -end diff --git a/lib/pathname.rb b/lib/pathname.rb new file mode 100644 index 0000000000..b19e379cd4 --- /dev/null +++ b/lib/pathname.rb @@ -0,0 +1,74 @@ +# frozen_string_literal: true +# +# = pathname.rb +# +# Object-Oriented Pathname Class +# +# Author:: Tanaka Akira <akr@m17n.org> +# Documentation:: Author and Gavin Sinclair +# +# For documentation, see class Pathname. +# + +class Pathname # * Find * + # + # Iterates over the directory tree in a depth first manner, yielding a + # Pathname for each file under "this" directory. + # + # Note that you need to require 'pathname' to use this method. + # + # Returns an Enumerator if no block is given. + # + # Since it is implemented by the standard library module Find, Find.prune can + # be used to control the traversal. + # + # If +self+ is +.+, yielded pathnames begin with a filename in the + # current directory, not +./+. + # + # See Find.find + # + def find(ignore_error: true) # :yield: pathname + return to_enum(__method__, ignore_error: ignore_error) unless block_given? + require 'find' + if @path == '.' + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.delete_prefix('./')) } + else + Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) } + end + end +end + + +class Pathname # * FileUtils * + # Recursively deletes a directory, including all directories beneath it. + # + # Note that you need to require 'pathname' to use this method. + # + # See FileUtils.rm_rf + def rmtree(noop: nil, verbose: nil, secure: nil) + # The name "rmtree" is borrowed from File::Path of Perl. + # File::Path provides "mkpath" and "rmtree". + require 'fileutils' + FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure) + self + end +end + +class Pathname # * tmpdir * + # Creates a tmp directory and wraps the returned path in a Pathname object. + # + # Note that you need to require 'pathname' to use this method. + # + # See Dir.mktmpdir + def self.mktmpdir + require 'tmpdir' unless defined?(Dir.mktmpdir) + if block_given? + Dir.mktmpdir do |dir| + dir = self.new(dir) + yield dir + end + else + self.new(Dir.mktmpdir) + end + end +end diff --git a/lib/pp.gemspec b/lib/pp.gemspec index 27a92a8ce4..15a3b4dc6c 100644 --- a/lib/pp.gemspec +++ b/lib/pp.gemspec @@ -22,7 +22,8 @@ Gem::Specification.new do |spec| spec.metadata["source_code_uri"] = spec.homepage spec.files = %w[ - LICENSE.txt + BSDL + COPYING lib/pp.rb pp.gemspec ] @@ -63,7 +63,8 @@ require 'prettyprint' class PP < PrettyPrint - VERSION = "0.5.0" + # The version string + VERSION = "0.6.3" # Returns the usable width for +out+. # As the width of +out+: @@ -138,26 +139,19 @@ class PP < PrettyPrint end end + # Module that defines helper methods for pretty_print. module PPMethods # Yields to a block # and preserves the previous set of objects being printed. def guard_inspect_key - if Thread.current[:__recursive_key__] == nil - Thread.current[:__recursive_key__] = {}.compare_by_identity - end - - if Thread.current[:__recursive_key__][:inspect] == nil - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity - end - - save = Thread.current[:__recursive_key__][:inspect] - + recursive_state = Thread.current[:__recursive_key__] ||= {}.compare_by_identity + save = recursive_state[:inspect] ||= {}.compare_by_identity begin - Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity + recursive_state[:inspect] = {}.compare_by_identity yield ensure - Thread.current[:__recursive_key__][:inspect] = save + recursive_state[:inspect] = save end end @@ -165,9 +159,8 @@ class PP < PrettyPrint # to be pretty printed. Used to break cycles in chains of objects to be # pretty printed. def check_inspect_key(id) - Thread.current[:__recursive_key__] && - Thread.current[:__recursive_key__][:inspect] && - Thread.current[:__recursive_key__][:inspect].include?(id) + recursive_state = Thread.current[:__recursive_key__] or return false + recursive_state[:inspect]&.include?(id) end # Adds the object_id +id+ to the set of objects being pretty printed, so @@ -181,6 +174,24 @@ class PP < PrettyPrint Thread.current[:__recursive_key__][:inspect].delete id end + private def guard_inspect(object) # :nodoc: + recursive_state = Thread.current[:__recursive_key__] + + if recursive_state&.key?(:inspect) + begin + push_inspect_key(object) + yield + ensure + pop_inspect_key(object) unless PP.sharing_detection + end + else + guard_inspect_key do + push_inspect_key(object) + yield + end + end + end + # Adds +obj+ to the pretty printing buffer # using Object#pretty_print or Object#pretty_print_cycle. # @@ -189,18 +200,19 @@ class PP < PrettyPrint def pp(obj) # If obj is a Delegator then use the object being delegated to for cycle # detection - obj = obj.__getobj__ if defined?(::Delegator) and obj.is_a?(::Delegator) + obj = obj.__getobj__ if defined?(::Delegator) and ::Delegator === obj if check_inspect_key(obj) group {obj.pretty_print_cycle self} return end - begin - push_inspect_key(obj) - group {obj.pretty_print self} - ensure - pop_inspect_key(obj) unless PP.sharing_detection + guard_inspect(obj) do + group do + obj.pretty_print self + rescue NoMethodError + text Kernel.instance_method(:inspect).bind_call(obj) + end end end @@ -255,15 +267,20 @@ class PP < PrettyPrint def seplist(list, sep=nil, iter_method=:each) # :yield: element sep ||= lambda { comma_breakable } first = true + kwsplat = EMPTY_KWHASH list.__send__(iter_method) {|*v| if first first = false else sep.call end - RUBY_VERSION >= "3.0" ? yield(*v, **{}) : yield(*v) + kwsplat ? yield(*v, **kwsplat) : yield(*v) } end + EMPTY_KWHASH = if RUBY_VERSION >= "3.0" # :nodoc: + {}.freeze + end + private_constant :EMPTY_KWHASH # A present standard failsafe for pretty printing any given Object def pp_object(obj) @@ -296,12 +313,10 @@ class PP < PrettyPrint # A pretty print for a pair of Hash def pp_hash_pair(k, v) if Symbol === k - sym_s = k.inspect - if sym_s[1].match?(/["$@!]/) || sym_s[-1].match?(/[%&*+\-\/<=>@\]^`|~]/) - text "#{k.to_s.inspect}:" - else - text "#{k}:" + if k.inspect.match?(%r[\A:["$@!]|[%&*+\-\/<=>@\]^`|~]\z]) + k = k.to_s.inspect end + text "#{k}:" else pp k text ' ' @@ -373,7 +388,8 @@ class PP < PrettyPrint # This method should return an array of names of instance variables as symbols or strings as: # +[:@a, :@b]+. def pretty_print_instance_variables - instance_variables.sort + ivars = respond_to?(:instance_variables_to_inspect, true) ? instance_variables_to_inspect || instance_variables : instance_variables + ivars.sort end # Is #inspect implementation using #pretty_print. @@ -416,6 +432,28 @@ class Hash # :nodoc: end end +if defined?(Set) + if set_pp = Set.instance_method(:initialize).source_location + set_pp = !set_pp.first.end_with?("/set.rb") # not defined in set.rb + else + set_pp = true # defined in C + end +end +class Set # :nodoc: + def pretty_print(pp) # :nodoc: + pp.group(1, "#{self.class.name}[", ']') { + pp.seplist(self) { |o| + pp.pp o + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + name = self.class.name + pp.text(empty? ? "#{name}[]" : "#{name}[...]") + end +end if set_pp + class << ENV # :nodoc: def pretty_print(q) # :nodoc: h = {} @@ -446,18 +484,37 @@ class Struct # :nodoc: end end +verbose, $VERBOSE = $VERBOSE, nil +begin + has_data_define = defined?(Data.define) +ensure + $VERBOSE = verbose +end + class Data # :nodoc: def pretty_print(q) # :nodoc: class_name = PP.mcall(self, Kernel, :class).name class_name = " #{class_name}" if class_name q.group(1, "#<data#{class_name}", '>') { - q.seplist(PP.mcall(self, Kernel, :class).members, lambda { q.text "," }) {|member| + + members = PP.mcall(self, Kernel, :class).members + values = [] + members.select! do |member| + begin + values << __send__(member) + true + rescue NoMethodError + false + end + end + + q.seplist(members.zip(values), lambda { q.text "," }) {|(member, value)| q.breakable q.text member.to_s q.text '=' q.group(1) { q.breakable '' - q.pp public_send(member) + q.pp value } } } @@ -466,15 +523,17 @@ class Data # :nodoc: def pretty_print_cycle(q) # :nodoc: q.text sprintf("#<data %s:...>", PP.mcall(self, Kernel, :class).name) end -end if defined?(Data.define) +end if has_data_define class Range # :nodoc: def pretty_print(q) # :nodoc: - q.pp self.begin if self.begin + begin_nil = self.begin == nil + end_nil = self.end == nil + q.pp self.begin if !begin_nil || end_nil q.breakable '' q.text(self.exclude_end? ? '...' : '..') q.breakable '' - q.pp self.end if self.end + q.pp self.end if !end_nil || begin_nil end end @@ -603,7 +662,7 @@ class MatchData # :nodoc: end if defined?(RubyVM::AbstractSyntaxTree) - class RubyVM::AbstractSyntaxTree::Node + class RubyVM::AbstractSyntaxTree::Node # :nodoc: def pretty_print_children(q, names = []) children.zip(names) do |c, n| if n @@ -668,7 +727,7 @@ module Kernel # prints arguments in pretty form. # - # pp returns argument(s). + # +#pp+ returns argument(s). def pp(*objs) objs.each {|obj| PP.pp(obj) diff --git a/lib/prettyprint.rb b/lib/prettyprint.rb index 6f50192f5d..a65407c130 100644 --- a/lib/prettyprint.rb +++ b/lib/prettyprint.rb @@ -33,6 +33,7 @@ # class PrettyPrint + # The version string VERSION = "0.2.0" # This is a convenience method which is same as follows: diff --git a/lib/prism.rb b/lib/prism.rb index 94f4c8ca5f..dab3420377 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # The Prism Ruby parser. # @@ -19,7 +20,7 @@ module Prism autoload :DSL, "prism/dsl" autoload :InspectVisitor, "prism/inspect_visitor" autoload :LexCompat, "prism/lex_compat" - autoload :LexRipper, "prism/lex_compat" + autoload :LexRipper, "prism/lex_ripper" autoload :MutationCompiler, "prism/mutation_compiler" autoload :Pack, "prism/pack" autoload :Pattern, "prism/pattern" @@ -36,12 +37,31 @@ module Prism private_constant :LexCompat private_constant :LexRipper + # Raised when requested to parse as the currently running Ruby version but Prism has no support for it. + class CurrentVersionError < ArgumentError + # Initialize a new exception for the given ruby version string. + def initialize(version) + message = +"invalid version: Requested to parse as `version: 'current'`; " + segments = + if version.match?(/\A\d+\.\d+.\d+\z/) + version.split(".").map(&:to_i) + end + + if segments && ((segments[0] < 3) || (segments[0] == 3 && segments[1] < 3)) + message << " #{version} is below the minimum supported syntax." + else + message << " #{version} is unknown. Please update the `prism` gem." + end + + super(message) + end + end + # :call-seq: # Prism::lex_compat(source, **options) -> LexCompat::Result # # Returns a parse result whose value is an array of tokens that closely - # resembles the return value of Ripper::lex. The main difference is that the - # `:on_sp` token is not emitted. + # resembles the return value of Ripper::lex. # # For supported options, see Prism::parse. def self.lex_compat(source, **options) @@ -51,23 +71,23 @@ module Prism # :call-seq: # Prism::lex_ripper(source) -> Array # - # This lexes with the Ripper lex. It drops any space events but otherwise - # returns the same tokens. Raises SyntaxError if the syntax in source is - # invalid. + # This wraps the result of Ripper.lex. It produces almost exactly the + # same tokens. Raises SyntaxError if the syntax in source is invalid. def self.lex_ripper(source) LexRipper.new(source).result # steep:ignore end # :call-seq: - # Prism::load(source, serialized) -> ParseResult + # Prism::load(source, serialized, freeze) -> ParseResult # # Load the serialized AST using the source as a reference into a tree. - def self.load(source, serialized) - Serialize.load(source, serialized) + def self.load(source, serialized, freeze = false) + Serialize.load_parse(source, serialized, freeze) end end require_relative "prism/polyfill/byteindex" +require_relative "prism/polyfill/warn" require_relative "prism/node" require_relative "prism/node_ext" require_relative "prism/parse_result" diff --git a/lib/prism/desugar_compiler.rb b/lib/prism/desugar_compiler.rb index e3b15fc3b0..5d7d38d841 100644 --- a/lib/prism/desugar_compiler.rb +++ b/lib/prism/desugar_compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class DesugarAndWriteNode # :nodoc: diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 5caae440f4..d4c9d60c9a 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore # This file is responsible for mirroring the API provided by the C extension by @@ -7,13 +8,26 @@ require "rbconfig" require "ffi" +# We want to eagerly load this file if there are Ractors so that it does not get +# autoloaded from within a non-main Ractor. +require "prism/serialize" if defined?(Ractor) + module Prism module LibRubyParser # :nodoc: extend FFI::Library # Define the library that we will be pulling functions from. Note that this # must align with the build shared library from make/rake. - ffi_lib File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__) + libprism_in_build = File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__) + libprism_in_libdir = "#{RbConfig::CONFIG["libdir"]}/prism/libprism.#{RbConfig::CONFIG["SOEXT"]}" + + if File.exist?(libprism_in_build) + INCLUDE_DIR = File.expand_path("../../include", __dir__) + ffi_lib libprism_in_build + else + INCLUDE_DIR = "#{RbConfig::CONFIG["libdir"]}/prism/include" + ffi_lib libprism_in_libdir + end # Convert a native C type declaration into a symbol that FFI understands. # For example: @@ -38,7 +52,7 @@ module Prism # given functions. For each one, define a function with the same name and # signature as the C function. def self.load_exported_functions_from(header, *functions, callbacks) - File.foreach(File.expand_path("../../include/#{header}", __dir__)) do |line| + File.foreach("#{INCLUDE_DIR}/#{header}") do |line| # We only want to attempt to load exported functions. next unless line.start_with?("PRISM_EXPORTED_FUNCTION ") @@ -72,6 +86,7 @@ module Prism end callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer + callback :pm_parse_stream_feof_t, [:pointer], :int enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY] enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE] @@ -87,7 +102,7 @@ module Prism "pm_string_query_local", "pm_string_query_constant", "pm_string_query_method_name", - [:pm_parse_stream_fgets_t] + [:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t] ) load_exported_functions_from( @@ -150,6 +165,9 @@ module Prism class PrismString # :nodoc: SIZEOF = LibRubyParser.pm_string_sizeof + PLATFORM_EXPECTS_UTF8 = + RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) + attr_reader :pointer, :length def initialize(pointer, length, from_string) @@ -184,8 +202,7 @@ module Prism # On Windows and Mac, it's expected that filepaths will be encoded in # UTF-8. If they are not, we need to convert them to UTF-8 before # passing them into pm_string_mapped_init. - if RbConfig::CONFIG["host_os"].match?(/bccwin|cygwin|djgpp|mingw|mswin|wince|darwin/i) && - (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8 + if PLATFORM_EXPECTS_UTF8 && (encoding = filepath.encoding) != Encoding::ASCII_8BIT && encoding != Encoding::UTF_8 filepath = filepath.encode(Encoding::UTF_8) end @@ -214,7 +231,7 @@ module Prism private_constant :LibRubyParser # The version constant is set by reading the result of calling pm_version. - VERSION = LibRubyParser.pm_version.read_string + VERSION = LibRubyParser.pm_version.read_string.freeze class << self # Mirror the Prism.dump API by using the serialization API. @@ -265,13 +282,15 @@ module Prism end } + eof_callback = -> (_) { stream.eof? } + # In the pm_serialize_parse_stream function it accepts a pointer to the # IO object as a void* and then passes it through to the callback as the # third argument, but it never touches it itself. As such, since we have # access to the IO object already through the closure of the lambda, we # can pass a null pointer here and not worry. - LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options)) - Prism.load(source, buffer.read) + LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options)) + Prism.load(source, buffer.read, options.fetch(:freeze, false)) end end @@ -346,50 +365,37 @@ module Prism def dump_common(string, options) # :nodoc: LibRubyParser::PrismBuffer.with do |buffer| LibRubyParser.pm_serialize_parse(buffer.pointer, string.pointer, string.length, dump_options(options)) - buffer.read + + dumped = buffer.read + dumped.freeze if options.fetch(:freeze, false) + + dumped end end def lex_common(string, code, options) # :nodoc: - serialized = LibRubyParser::PrismBuffer.with do |buffer| + LibRubyParser::PrismBuffer.with do |buffer| LibRubyParser.pm_serialize_lex(buffer.pointer, string.pointer, string.length, dump_options(options)) - buffer.read + Serialize.load_lex(code, buffer.read, options.fetch(:freeze, false)) end - - Serialize.load_tokens(Source.for(code), serialized) end def parse_common(string, code, options) # :nodoc: serialized = dump_common(string, options) - Prism.load(code, serialized) + Serialize.load_parse(code, serialized, options.fetch(:freeze, false)) end def parse_comments_common(string, code, options) # :nodoc: LibRubyParser::PrismBuffer.with do |buffer| LibRubyParser.pm_serialize_parse_comments(buffer.pointer, string.pointer, string.length, dump_options(options)) - - source = Source.for(code) - loader = Serialize::Loader.new(source, buffer.read) - - loader.load_header - loader.load_encoding - loader.load_start_line - loader.load_comments + Serialize.load_parse_comments(code, buffer.read, options.fetch(:freeze, false)) end end def parse_lex_common(string, code, options) # :nodoc: LibRubyParser::PrismBuffer.with do |buffer| LibRubyParser.pm_serialize_parse_lex(buffer.pointer, string.pointer, string.length, dump_options(options)) - - source = Source.for(code) - loader = Serialize::Loader.new(source, buffer.read) - - tokens = loader.load_tokens - node, comments, magic_comments, data_loc, errors, warnings = loader.load_nodes - tokens.each { |token,| token.value.force_encoding(loader.encoding) } - - ParseLexResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source) + Serialize.load_parse_lex(code, buffer.read, options.fetch(:freeze, false)) end end @@ -417,15 +423,25 @@ module Prism # Return the value that should be dumped for the version option. def dump_options_version(version) - case version + current = version == "current" + + case current ? RUBY_VERSION : version when nil, "latest" - 0 + 0 # Handled in pm_parser_init when /\A3\.3(\.\d+)?\z/ 1 when /\A3\.4(\.\d+)?\z/ - 0 + 2 + when /\A3\.5(\.\d+)?\z/, /\A4\.0(\.\d+)?\z/ + 3 + when /\A4\.1(\.\d+)?\z/ + 4 else - raise ArgumentError, "invalid version: #{version}" + if current + raise CurrentVersionError, RUBY_VERSION + else + raise ArgumentError, "invalid version: #{version}" + end end end @@ -472,15 +488,43 @@ module Prism template << "C" values << (options.fetch(:partial_script, false) ? 1 : 0) + template << "C" + values << (options.fetch(:freeze, false) ? 1 : 0) + template << "L" if (scopes = options[:scopes]) values << scopes.length scopes.each do |scope| + locals = nil + forwarding = 0 + + case scope + when Array + locals = scope + when Scope + locals = scope.locals + + scope.forwarding.each do |forward| + case forward + when :* then forwarding |= 0x1 + when :** then forwarding |= 0x2 + when :& then forwarding |= 0x4 + when :"..." then forwarding |= 0x8 + else raise ArgumentError, "invalid forwarding value: #{forward}" + end + end + else + raise TypeError, "wrong argument type #{scope.class.inspect} (expected Array or Prism::Scope)" + end + template << "L" - values << scope.length + values << locals.length + + template << "C" + values << forwarding - scope.each do |local| + locals.each do |local| name = local.name template << "L" values << name.bytesize diff --git a/lib/prism/lex_compat.rb b/lib/prism/lex_compat.rb index a83c24cb41..597e63c73e 100644 --- a/lib/prism/lex_compat.rb +++ b/lib/prism/lex_compat.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true +# :markup: markdown require "delegate" -require "ripper" module Prism # This class is responsible for lexing the source using prism and then @@ -225,16 +225,8 @@ module Prism end end - # Ripper doesn't include the rest of the token in the event, so we need to - # trim it down to just the content on the first line when comparing. - class EndContentToken < Token - def ==(other) # :nodoc: - [self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other - end - end - # Tokens where state should be ignored - # used for :on_comment, :on_heredoc_end, :on_embexpr_end + # used for :on_sp, :on_comment, :on_heredoc_end, :on_embexpr_end class IgnoreStateToken < Token def ==(other) # :nodoc: self[0...-1] == other[0...-1] @@ -248,8 +240,8 @@ module Prism class IdentToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) || - (other[3] & Ripper::EXPR_ARG_ANY != 0) + (other[3] == Translation::Ripper::EXPR_LABEL | Translation::Ripper::EXPR_END) || + (other[3] & (Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_CMDARG) != 0) ) end end @@ -260,8 +252,8 @@ module Prism def ==(other) # :nodoc: return false unless self[0...-1] == other[0...-1] - if self[3] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED - other[3] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED != 0 + if self[3] == Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED + other[3] & Translation::Ripper::EXPR_ARG | Translation::Ripper::EXPR_LABELED != 0 else self[3] == other[3] end @@ -279,8 +271,8 @@ module Prism class ParamToken < Token def ==(other) # :nodoc: (self[0...-1] == other[0...-1]) && ( - (other[3] == Ripper::EXPR_END) || - (other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL) + (other[3] == Translation::Ripper::EXPR_END) || + (other[3] == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL) ) end end @@ -614,10 +606,15 @@ module Prism private_constant :Heredoc - attr_reader :source, :options + # In previous versions of Ruby, Ripper wouldn't flush the bom before the + # first token, so we had to have a hack in place to account for that. + BOM_FLUSHED = RUBY_VERSION >= "3.3.0" + private_constant :BOM_FLUSHED + + attr_reader :options - def initialize(source, **options) - @source = source + def initialize(code, **options) + @code = code @options = options end @@ -627,16 +624,14 @@ module Prism state = :default heredoc_stack = [[]] #: Array[Array[Heredoc::PlainHeredoc | Heredoc::DashHeredoc | Heredoc::DedentingHeredoc]] - result = Prism.lex(source, **options) + result = Prism.lex(@code, **options) + source = result.source result_value = result.value - previous_state = nil #: Ripper::Lexer::State? + previous_state = nil #: State? last_heredoc_end = nil #: Integer? + eof_token = nil - # In previous versions of Ruby, Ripper wouldn't flush the bom before the - # first token, so we had to have a hack in place to account for that. This - # checks for that behavior. - bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0 - bom = source.byteslice(0..2) == "\xEF\xBB\xBF" + bom = source.slice(0, 3) == "\xEF\xBB\xBF" result_value.each_with_index do |(token, lex_state), index| lineno = token.location.start_line @@ -650,7 +645,7 @@ module Prism if bom && lineno == 1 column -= 3 - if index == 0 && column == 0 && !bom_flushed + if index == 0 && column == 0 && !BOM_FLUSHED flushed = case token.type when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE, @@ -674,12 +669,15 @@ module Prism event = RIPPER.fetch(token.type) value = token.value - lex_state = Ripper::Lexer::State.new(lex_state) + lex_state = Translation::Ripper::Lexer::State.cached(lex_state) token = case event when :on___end__ - EndContentToken.new([[lineno, column], event, value, lex_state]) + # Ripper doesn't include the rest of the token in the event, so we need to + # trim it down to just the content on the first line. + value = value[0..value.index("\n")] + Token.new([[lineno, column], event, value, lex_state]) when :on_comment IgnoreStateToken.new([[lineno, column], event, value, lex_state]) when :on_heredoc_end @@ -688,7 +686,7 @@ module Prism last_heredoc_end = token.location.end_offset IgnoreStateToken.new([[lineno, column], event, value, lex_state]) when :on_ident - if lex_state == Ripper::EXPR_END + if lex_state == Translation::Ripper::EXPR_END # If we have an identifier that follows a method name like: # # def foo bar @@ -698,7 +696,7 @@ module Prism # yet. We do this more accurately, so we need to allow comparing # against both END and END|LABEL. ParamToken.new([[lineno, column], event, value, lex_state]) - elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL + elsif lex_state == Translation::Ripper::EXPR_END | Translation::Ripper::EXPR_LABEL # In the event that we're comparing identifiers, we're going to # allow a little divergence. Ripper doesn't account for local # variables introduced through named captures in regexes, and we @@ -738,13 +736,14 @@ module Prism counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0 end - Ripper::Lexer::State.new(result_value[current_index][1]) + Translation::Ripper::Lexer::State.cached(result_value[current_index][1]) else previous_state end Token.new([[lineno, column], event, value, lex_state]) when :on_eof + eof_token = token previous_token = result_value[index - 1][0] # If we're at the end of the file and the previous token was a @@ -767,7 +766,7 @@ module Prism end_offset += 3 end - tokens << Token.new([[lineno, 0], :on_nl, source.byteslice(start_offset...end_offset), lex_state]) + tokens << Token.new([[lineno, 0], :on_nl, source.slice(start_offset, end_offset - start_offset), lex_state]) end end @@ -861,67 +860,91 @@ module Prism # We sort by location to compare against Ripper's output tokens.sort_by!(&:location) - Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, Source.for(source)) - end - end - - private_constant :LexCompat - - # This is a class that wraps the Ripper lexer to produce almost exactly the - # same tokens. - class LexRipper # :nodoc: - attr_reader :source + # Add :on_sp tokens + tokens = add_on_sp_tokens(tokens, source, result.data_loc, bom, eof_token) - def initialize(source) - @source = source + Result.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, source) end - def result - previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] - results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] - - lex(source).each do |token| - case token[1] - when :on_sp - # skip - when :on_tstring_content - if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) - previous[2] << token[2] - else - results << token - previous = token - end - when :on_words_sep - if previous[1] == :on_words_sep - previous[2] << token[2] + def add_on_sp_tokens(tokens, source, data_loc, bom, eof_token) + new_tokens = [] + + prev_token_state = Translation::Ripper::Lexer::State.cached(Translation::Ripper::EXPR_BEG) + prev_token_end = bom ? 3 : 0 + + tokens.each do |token| + line, column = token.location + start_offset = source.line_to_byte_offset(line) + column + # Ripper reports columns on line 1 without counting the BOM, so we adjust to get the real offset + start_offset += 3 if line == 1 && bom + + if start_offset > prev_token_end + sp_value = source.slice(prev_token_end, start_offset - prev_token_end) + sp_line = source.line(prev_token_end) + sp_column = source.column(prev_token_end) + # Ripper reports columns on line 1 without counting the BOM + sp_column -= 3 if sp_line == 1 && bom + continuation_index = sp_value.byteindex("\\") + + # ripper emits up to three :on_sp tokens when line continuations are used + if continuation_index + next_whitespace_index = continuation_index + 1 + next_whitespace_index += 1 if sp_value.byteslice(next_whitespace_index) == "\r" + next_whitespace_index += 1 + first_whitespace = sp_value[0...continuation_index] + continuation = sp_value[continuation_index...next_whitespace_index] + second_whitespace = sp_value[next_whitespace_index..] + + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column], + :on_sp, + first_whitespace, + prev_token_state + ]) unless first_whitespace.empty? + + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column + continuation_index], + :on_sp, + continuation, + prev_token_state + ]) + + new_tokens << IgnoreStateToken.new([ + [sp_line + 1, 0], + :on_sp, + second_whitespace, + prev_token_state + ]) unless second_whitespace.empty? else - results << token - previous = token + new_tokens << IgnoreStateToken.new([ + [sp_line, sp_column], + :on_sp, + sp_value, + prev_token_state + ]) end - else - results << token - previous = token end - end - results - end - - private - - if Ripper.method(:lex).parameters.assoc(:keyrest) - def lex(source) - Ripper.lex(source, raise_errors: true) + new_tokens << token + prev_token_state = token.state + prev_token_end = start_offset + token.value.bytesize end - else - def lex(source) - ripper = Ripper::Lexer.new(source) - ripper.lex.tap do |result| - raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + + unless data_loc # no trailing :on_sp with __END__ as it is always preceded by :on_nl + end_offset = eof_token.location.end_offset + if prev_token_end < end_offset + new_tokens << IgnoreStateToken.new([ + [source.line(prev_token_end), source.column(prev_token_end)], + :on_sp, + source.slice(prev_token_end, end_offset - prev_token_end), + prev_token_state + ]) end end + + new_tokens end end - private_constant :LexRipper + private_constant :LexCompat end diff --git a/lib/prism/lex_ripper.rb b/lib/prism/lex_ripper.rb new file mode 100644 index 0000000000..2054cf55ac --- /dev/null +++ b/lib/prism/lex_ripper.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +# :markup: markdown + +require "ripper" + +module Prism + # This is a class that wraps the Ripper lexer to produce almost exactly the + # same tokens. + class LexRipper # :nodoc: + attr_reader :source + + def initialize(source) + @source = source + end + + def result + previous = [] #: [[Integer, Integer], Symbol, String, untyped] | [] + results = [] #: Array[[[Integer, Integer], Symbol, String, untyped]] + + lex(source).each do |token| + case token[1] + when :on_tstring_content + if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@")) + previous[2] << token[2] + else + results << token + previous = token + end + when :on_words_sep + if previous[1] == :on_words_sep + previous[2] << token[2] + else + results << token + previous = token + end + else + results << token + previous = token + end + end + + results + end + + private + + if Ripper.method(:lex).parameters.assoc(:keyrest) + def lex(source) + Ripper.lex(source, raise_errors: true) + end + else + def lex(source) + ripper = Ripper::Lexer.new(source) + ripper.lex.tap do |result| + raise SyntaxError, ripper.errors.map(&:message).join(' ;') if ripper.errors.any? + end + end + end + end + + private_constant :LexRipper +end diff --git a/lib/prism/node_ext.rb b/lib/prism/node_ext.rb index 4dfcebd638..469e54ca0c 100644 --- a/lib/prism/node_ext.rb +++ b/lib/prism/node_ext.rb @@ -1,7 +1,10 @@ # frozen_string_literal: true +# :markup: markdown +#-- # Here we are reopening the prism module to provide methods on nodes that aren't # templated and are meant as convenience methods. +#++ module Prism class Node def deprecated(*replacements) # :nodoc: @@ -9,7 +12,7 @@ module Prism location = location[0].label if location suggest = replacements.map { |replacement| "#{self.class}##{replacement}" } - warn(<<~MSG, category: :deprecated) + warn(<<~MSG, uplevel: 1, category: :deprecated) [deprecation]: #{self.class}##{location} is deprecated and will be \ removed in the next major version. Use #{suggest.join("/")} instead. #{(caller(1, 3) || []).join("\n")} diff --git a/lib/prism/pack.rb b/lib/prism/pack.rb index c0de8ab8b7..166c04c3c0 100644 --- a/lib/prism/pack.rb +++ b/lib/prism/pack.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true +# :markup: markdown # typed: ignore +# module Prism # A parser for the pack template language. module Pack diff --git a/lib/prism/parse_result.rb b/lib/prism/parse_result.rb index 46bd33d1db..12d19da562 100644 --- a/lib/prism/parse_result.rb +++ b/lib/prism/parse_result.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This represents a source of Ruby code that has been parsed. It is used in @@ -48,6 +49,16 @@ module Prism @offsets = offsets # set after parsing is done end + # Replace the value of start_line with the given value. + def replace_start_line(start_line) + @start_line = start_line + end + + # Replace the value of offsets with the given value. + def replace_offsets(offsets) + @offsets.replace(offsets) + end + # Returns the encoding of the source code, which is set by parameters to the # parser or by the encoding magic comment. def encoding @@ -65,6 +76,15 @@ module Prism source.byteslice(byte_offset, length) or raise end + # Converts the line number to a byte offset corresponding to the start of that line + def line_to_byte_offset(line) + l = line - @start_line + if l < 0 || l >= offsets.size + raise ArgumentError, "line #{line} is out of range" + end + offsets[l] + end + # Binary search through the offsets to find the line number for the given # byte offset. def line(byte_offset) @@ -132,26 +152,20 @@ module Prism code_units_offset(byte_offset, encoding) - code_units_offset(line_start(byte_offset), encoding) end + # Freeze this object and the objects it contains. + def deep_freeze + source.freeze + offsets.freeze + freeze + end + private # Binary search through the offsets to find the line number for the given # byte offset. def find_line(byte_offset) - left = 0 - right = offsets.length - 1 - - while left <= right - mid = left + (right - left) / 2 - return mid if (offset = offsets[mid]) == byte_offset - - if offset < byte_offset - left = mid + 1 - else - right = mid - 1 - end - end - - left - 1 + index = offsets.bsearch_index { |offset| offset > byte_offset } || offsets.length + index - 1 end end @@ -204,8 +218,8 @@ module Prism LengthCounter.new(source, encoding) end - @cache = {} - @offsets = [] + @cache = {} #: Hash[Integer, Integer] + @offsets = [] #: Array[Integer] end # Retrieve the code units offset from the given byte offset. @@ -854,5 +868,40 @@ module Prism location super end + + # Freeze this object and the objects it contains. + def deep_freeze + value.freeze + location.freeze + freeze + end + end + + # This object is passed to the various Prism.* methods that accept the + # `scopes` option as an element of the list. It defines both the local + # variables visible at that scope as well as the forwarding parameters + # available at that scope. + class Scope + # The list of local variables that are defined in this scope. This should be + # defined as an array of symbols. + attr_reader :locals + + # The list of local variables that are forwarded to the next scope. This + # should by defined as an array of symbols containing the specific values of + # :*, :**, :&, or :"...". + attr_reader :forwarding + + # Create a new scope object with the given locals and forwarding. + def initialize(locals, forwarding) + @locals = locals + @forwarding = forwarding + end + end + + # Create a new scope with the given locals and forwarding options that is + # suitable for passing into one of the Prism.* methods that accepts the + # `scopes` option. + def self.scope(locals: [], forwarding: []) + Scope.new(locals, forwarding) end end diff --git a/lib/prism/parse_result/comments.rb b/lib/prism/parse_result/comments.rb index 22c4148b2c..3e93316aff 100644 --- a/lib/prism/parse_result/comments.rb +++ b/lib/prism/parse_result/comments.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result diff --git a/lib/prism/parse_result/errors.rb b/lib/prism/parse_result/errors.rb index 847a8442fe..26c376b3ce 100644 --- a/lib/prism/parse_result/errors.rb +++ b/lib/prism/parse_result/errors.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require "stringio" @@ -17,7 +18,7 @@ module Prism # Formats the errors in a human-readable way and return them as a string. def format - error_lines = {} + error_lines = {} #: Hash[Integer, Array[ParseError]] parse_result.errors.each do |error| location = error.location (location.start_line..location.end_line).each do |line| diff --git a/lib/prism/parse_result/newlines.rb b/lib/prism/parse_result/newlines.rb index a04fa78a75..e7fd62cafe 100644 --- a/lib/prism/parse_result/newlines.rb +++ b/lib/prism/parse_result/newlines.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism class ParseResult < Result @@ -63,7 +64,7 @@ module Prism class Node def newline_flag? # :nodoc: - @newline_flag ? true : false + !!defined?(@newline_flag) end def newline_flag!(lines) # :nodoc: diff --git a/lib/prism/pattern.rb b/lib/prism/pattern.rb index 03fec26789..6ad2d9e5b9 100644 --- a/lib/prism/pattern.rb +++ b/lib/prism/pattern.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # A pattern is an object that wraps a Ruby pattern matching expression. The diff --git a/lib/prism/polyfill/append_as_bytes.rb b/lib/prism/polyfill/append_as_bytes.rb new file mode 100644 index 0000000000..24218bd171 --- /dev/null +++ b/lib/prism/polyfill/append_as_bytes.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# Polyfill for String#append_as_bytes, which didn't exist until Ruby 3.4. +if !("".respond_to?(:append_as_bytes)) + String.include( + Module.new { + def append_as_bytes(*args) + args.each do |arg| + arg = Integer === arg ? [arg].pack("C") : arg.b + self.<<(arg) # steep:ignore + end + end + } + ) +end diff --git a/lib/prism/polyfill/scan_byte.rb b/lib/prism/polyfill/scan_byte.rb new file mode 100644 index 0000000000..9276e509fc --- /dev/null +++ b/lib/prism/polyfill/scan_byte.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: true + +require "strscan" + +# Polyfill for StringScanner#scan_byte, which didn't exist until Ruby 3.4. +if !(StringScanner.method_defined?(:scan_byte)) + StringScanner.include( + Module.new { + def scan_byte # :nodoc: + get_byte&.b&.ord + end + } + ) +end diff --git a/lib/prism/polyfill/warn.rb b/lib/prism/polyfill/warn.rb new file mode 100644 index 0000000000..76a4264623 --- /dev/null +++ b/lib/prism/polyfill/warn.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +# Polyfill for Kernel#warn with the category parameter. Not all Ruby engines +# have Method#parameters implemented, so we check the arity instead if +# necessary. +if (method = Kernel.instance_method(:warn)).respond_to?(:parameters) ? method.parameters.none? { |_, name| name == :category } : (method.arity == -1) + Kernel.prepend( + Module.new { + def warn(*msgs, uplevel: nil, category: nil) # :nodoc: + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end + end + } + ) + + Object.prepend( + Module.new { + def warn(*msgs, uplevel: nil, category: nil) # :nodoc: + case uplevel + when nil + super(*msgs) + when Integer + super(*msgs, uplevel: uplevel + 1) + else + super(*msgs, uplevel: uplevel.to_int + 1) + end + end + } + ) +end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 0f8881f24c..283c7b04aa 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "prism" - spec.version = "1.0.0" + spec.version = "1.8.0" spec.authors = ["Shopify"] spec.email = ["ruby@shopify.com"] @@ -77,6 +77,7 @@ Gem::Specification.new do |spec| "lib/prism/ffi.rb", "lib/prism/inspect_visitor.rb", "lib/prism/lex_compat.rb", + "lib/prism/lex_ripper.rb", "lib/prism/mutation_compiler.rb", "lib/prism/node_ext.rb", "lib/prism/node.rb", @@ -86,19 +87,25 @@ Gem::Specification.new do |spec| "lib/prism/parse_result/errors.rb", "lib/prism/parse_result/newlines.rb", "lib/prism/pattern.rb", + "lib/prism/polyfill/append_as_bytes.rb", "lib/prism/polyfill/byteindex.rb", + "lib/prism/polyfill/scan_byte.rb", "lib/prism/polyfill/unpack1.rb", + "lib/prism/polyfill/warn.rb", "lib/prism/reflection.rb", "lib/prism/relocation.rb", "lib/prism/serialize.rb", "lib/prism/string_query.rb", "lib/prism/translation.rb", "lib/prism/translation/parser.rb", - "lib/prism/translation/parser33.rb", - "lib/prism/translation/parser34.rb", + "lib/prism/translation/parser_current.rb", + "lib/prism/translation/parser_versions.rb", + "lib/prism/translation/parser/builder.rb", "lib/prism/translation/parser/compiler.rb", "lib/prism/translation/parser/lexer.rb", "lib/prism/translation/ripper.rb", + "lib/prism/translation/ripper/filter.rb", + "lib/prism/translation/ripper/lexer.rb", "lib/prism/translation/ripper/sexp.rb", "lib/prism/translation/ripper/shim.rb", "lib/prism/translation/ruby_parser.rb", @@ -114,8 +121,7 @@ Gem::Specification.new do |spec| "rbi/prism/reflection.rbi", "rbi/prism/string_query.rbi", "rbi/prism/translation/parser.rbi", - "rbi/prism/translation/parser33.rbi", - "rbi/prism/translation/parser34.rbi", + "rbi/prism/translation/parser_versions.rbi", "rbi/prism/translation/ripper.rbi", "rbi/prism/visitor.rbi", "sig/prism.rbs", @@ -130,6 +136,7 @@ Gem::Specification.new do |spec| "sig/prism/node.rbs", "sig/prism/pack.rbs", "sig/prism/parse_result.rbs", + "sig/prism/parse_result/comments.rbs", "sig/prism/pattern.rbs", "sig/prism/reflection.rbs", "sig/prism/relocation.rbs", diff --git a/lib/prism/relocation.rb b/lib/prism/relocation.rb index ad914396f6..3e9210a785 100644 --- a/lib/prism/relocation.rb +++ b/lib/prism/relocation.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Prism parses deterministically for the same input. This provides a nice @@ -465,7 +466,7 @@ module Prism while (node = queue.shift) @entries[node.node_id].each do |field_name, entry| value = node.public_send(field_name) - values = {} + values = {} #: Hash[Symbol, untyped] fields.each_value do |field| values.merge!(field.fields(value)) diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb index 9011051d2b..547f58d2fa 100644 --- a/lib/prism/string_query.rb +++ b/lib/prism/string_query.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism # Query methods that allow categorizing strings based on their context for diff --git a/lib/prism/translation.rb b/lib/prism/translation.rb index 8b75e8a3ab..57b57135bc 100644 --- a/lib/prism/translation.rb +++ b/lib/prism/translation.rb @@ -1,12 +1,17 @@ # frozen_string_literal: true +# :markup: markdown module Prism # This module is responsible for converting the prism syntax tree into other # syntax trees. module Translation # steep:ignore autoload :Parser, "prism/translation/parser" - autoload :Parser33, "prism/translation/parser33" - autoload :Parser34, "prism/translation/parser34" + autoload :ParserCurrent, "prism/translation/parser_current" + autoload :Parser33, "prism/translation/parser_versions" + autoload :Parser34, "prism/translation/parser_versions" + autoload :Parser35, "prism/translation/parser_versions" + autoload :Parser40, "prism/translation/parser_versions" + autoload :Parser41, "prism/translation/parser_versions" autoload :Ripper, "prism/translation/ripper" autoload :RubyParser, "prism/translation/ruby_parser" end diff --git a/lib/prism/translation/parser.rb b/lib/prism/translation/parser.rb index 969f2b95b0..fed4ac4cd1 100644 --- a/lib/prism/translation/parser.rb +++ b/lib/prism/translation/parser.rb @@ -1,9 +1,15 @@ # frozen_string_literal: true +# :markup: markdown begin + required_version = ">= 3.3.7.2" + gem "parser", required_version require "parser" rescue LoadError - warn(%q{Error: Unable to load parser. Add `gem "parser"` to your Gemfile.}) + warn(<<~MSG) + Error: Unable to load parser #{required_version}. \ + Add `gem "parser"` to your Gemfile or run `bundle update parser`. + MSG exit(1) end @@ -13,6 +19,13 @@ module Prism # whitequark/parser gem's syntax tree. It inherits from the base parser for # the parser gem, and overrides the parse* methods to parse with prism and # then translate. + # + # Note that this version of the parser always parses using the latest + # version of Ruby syntax supported by Prism. If you want specific version + # support, use one of the version-specific subclasses, such as + # `Prism::Translation::Parser34`. If you want to parse using the same + # version of Ruby syntax as the currently running version of Ruby, use + # `Prism::Translation::ParserCurrent`. class Parser < ::Parser::Base Diagnostic = ::Parser::Diagnostic # :nodoc: private_constant :Diagnostic @@ -33,8 +46,45 @@ module Prism Racc_debug_parser = false # :nodoc: + # The `builder` argument is used to create the parser using our custom builder class by default. + # + # By using the `:parser` keyword argument, you can translate in a way that is compatible with + # the Parser gem using any parser. + # + # For example, in RuboCop for Ruby LSP, the following approach can be used to improve performance + # by reusing a pre-parsed `Prism::ParseLexResult`: + # + # class PrismPreparsed + # def initialize(prism_result) + # @prism_result = prism_result + # end + # + # def parse_lex(source, **options) + # @prism_result + # end + # end + # + # prism_preparsed = PrismPreparsed.new(prism_result) + # + # Prism::Translation::Ruby34.new(builder, parser: prism_preparsed) + # + # In an object passed to the `:parser` keyword argument, the `parse` and `parse_lex` methods + # should be implemented as needed. + # + def initialize(builder = Prism::Translation::Parser::Builder.new, parser: Prism) + if !builder.is_a?(Prism::Translation::Parser::Builder) + warn(<<~MSG, uplevel: 1, category: :deprecated) + [deprecation]: The builder passed to `Prism::Translation::Parser.new` is not a \ + `Prism::Translation::Parser::Builder` subclass. This will raise in the next major version. + MSG + end + @parser = parser + + super(builder) + end + def version # :nodoc: - 34 + 41 end # The default encoding for Ruby files is UTF-8. @@ -51,7 +101,7 @@ module Prism source = source_buffer.source offset_cache = build_offset_cache(source) - result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), partial_script: true, encoding: false), offset_cache) + result = unwrap(@parser.parse(source, **prism_options), offset_cache) build_ast(result.value, offset_cache) ensure @@ -64,7 +114,7 @@ module Prism source = source_buffer.source offset_cache = build_offset_cache(source) - result = unwrap(Prism.parse(source, filepath: source_buffer.name, version: convert_for_prism(version), partial_script: true, encoding: false), offset_cache) + result = unwrap(@parser.parse(source, **prism_options), offset_cache) [ build_ast(result.value, offset_cache), @@ -83,7 +133,7 @@ module Prism offset_cache = build_offset_cache(source) result = begin - unwrap(Prism.parse_lex(source, filepath: source_buffer.name, version: convert_for_prism(version), partial_script: true, encoding: false), offset_cache) + unwrap(@parser.parse_lex(source, **prism_options), offset_cache) rescue ::Parser::SyntaxError raise if !recover end @@ -285,6 +335,20 @@ module Prism ) end + # Options for how prism should parse/lex the source. + def prism_options + options = { + filepath: @source_buffer.name, + version: convert_for_prism(version), + partial_script: true, + } + # The parser gem always encodes to UTF-8, unless it is binary. + # https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/source/buffer.rb#L80-L107 + options[:encoding] = false if @source_buffer.source.encoding != Encoding::BINARY + + options + end + # Converts the version format handled by Parser to the format handled by Prism. def convert_for_prism(version) case version @@ -292,11 +356,16 @@ module Prism "3.3.1" when 34 "3.4.0" + when 35, 40 + "4.0.0" + when 41 + "4.1.0" else "latest" end end + require_relative "parser/builder" require_relative "parser/compiler" require_relative "parser/lexer" diff --git a/lib/prism/translation/parser/builder.rb b/lib/prism/translation/parser/builder.rb new file mode 100644 index 0000000000..6b620c25bc --- /dev/null +++ b/lib/prism/translation/parser/builder.rb @@ -0,0 +1,62 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + class Parser + # A builder that knows how to convert more modern Ruby syntax + # into whitequark/parser gem's syntax tree. + class Builder < ::Parser::Builders::Default + # It represents the `it` block argument, which is not yet implemented in the Parser gem. + def itarg + n(:itarg, [:it], nil) + end + + # The following three lines have been added to support the `it` block parameter syntax in the source code below. + # + # if args.type == :itarg + # block_type = :itblock + # args = :it + # + # https://github.com/whitequark/parser/blob/v3.3.7.1/lib/parser/builders/default.rb#L1122-L1155 + def block(method_call, begin_t, args, body, end_t) + _receiver, _selector, *call_args = *method_call + + if method_call.type == :yield + diagnostic :error, :block_given_to_yield, nil, method_call.loc.keyword, [loc(begin_t)] + end + + last_arg = call_args.last + if last_arg && (last_arg.type == :block_pass || last_arg.type == :forwarded_args) + diagnostic :error, :block_and_blockarg, nil, last_arg.loc.expression, [loc(begin_t)] + end + + if args.type == :itarg + block_type = :itblock + args = :it + elsif args.type == :numargs + block_type = :numblock + args = args.children[0] + else + block_type = :block + end + + if [:send, :csend, :index, :super, :zsuper, :lambda].include?(method_call.type) + n(block_type, [ method_call, args, body ], + block_map(method_call.loc.expression, begin_t, end_t)) + else + # Code like "return foo 1 do end" is reduced in a weird sequence. + # Here, method_call is actually (return). + actual_send, = *method_call + block = + n(block_type, [ actual_send, args, body ], + block_map(actual_send.loc.expression, begin_t, end_t)) + + n(method_call.type, [ block ], + method_call.loc.with_expression(join_exprs(method_call, block))) + end + end + end + end + end +end diff --git a/lib/prism/translation/parser/compiler.rb b/lib/prism/translation/parser/compiler.rb index d57b5757d7..8805614603 100644 --- a/lib/prism/translation/parser/compiler.rb +++ b/lib/prism/translation/parser/compiler.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown module Prism module Translation @@ -74,7 +75,29 @@ module Prism # [] # ^^ def visit_array_node(node) - builder.array(token(node.opening_loc), visit_all(node.elements), token(node.closing_loc)) + if node.opening&.start_with?("%w", "%W", "%i", "%I") + elements = node.elements.flat_map do |element| + if element.is_a?(StringNode) + if element.content.include?("\n") + string_nodes_from_line_continuations(element.unescaped, element.content, element.content_loc.start_offset, node.opening) + else + [builder.string_internal([element.unescaped, srange(element.content_loc)])] + end + elsif element.is_a?(InterpolatedStringNode) + builder.string_compose( + token(element.opening_loc), + string_nodes_from_interpolation(element, node.opening), + token(element.closing_loc) + ) + else + [visit(element)] + end + end + else + elements = visit_all(node.elements) + end + + builder.array(token(node.opening_loc), elements, token(node.closing_loc)) end # foo => [bar] @@ -111,8 +134,8 @@ module Prism def visit_assoc_node(node) key = node.key - if in_pattern - if node.value.is_a?(ImplicitNode) + if node.value.is_a?(ImplicitNode) + if in_pattern if key.is_a?(SymbolNode) if key.opening.nil? builder.match_hash_var([key.unescaped, srange(key.location)]) @@ -122,19 +145,18 @@ module Prism else builder.match_hash_var_from_str(token(key.opening_loc), visit_all(key.parts), token(key.closing_loc)) end - elsif key.opening.nil? - builder.pair_keyword([key.unescaped, srange(key.location)], visit(node.value)) else - builder.pair_quoted(token(key.opening_loc), [builder.string_internal([key.unescaped, srange(key.value_loc)])], token(key.closing_loc), visit(node.value)) - end - elsif node.value.is_a?(ImplicitNode) - if (value = node.value.value).is_a?(LocalVariableReadNode) - builder.pair_keyword( - [key.unescaped, srange(key)], + value = node.value.value + + implicit_value = if value.is_a?(CallNode) + builder.call_method(nil, nil, [value.name, srange(value.message_loc)]) + elsif value.is_a?(ConstantReadNode) + builder.const([value.name, srange(key.value_loc)]) + else builder.ident([value.name, srange(key.value_loc)]).updated(:lvar) - ) - else - builder.pair_label([key.unescaped, srange(key.location)]) + end + + builder.pair_keyword([key.unescaped, srange(key)], implicit_value) end elsif node.operator_loc builder.pair(visit(key), token(node.operator_loc), visit(node.value)) @@ -181,14 +203,21 @@ module Prism if (rescue_clause = node.rescue_clause) begin find_start_offset = (rescue_clause.reference&.location || rescue_clause.exceptions.last&.location || rescue_clause.keyword_loc).end_offset - find_end_offset = (rescue_clause.statements&.location&.start_offset || rescue_clause.subsequent&.location&.start_offset || (find_start_offset + 1)) + find_end_offset = ( + rescue_clause.statements&.location&.start_offset || + rescue_clause.subsequent&.location&.start_offset || + node.else_clause&.location&.start_offset || + node.ensure_clause&.location&.start_offset || + node.end_keyword_loc&.start_offset || + find_start_offset + 1 + ) rescue_bodies << builder.rescue_body( token(rescue_clause.keyword_loc), rescue_clause.exceptions.any? ? builder.array(nil, visit_all(rescue_clause.exceptions), nil) : nil, token(rescue_clause.operator_loc), visit(rescue_clause.reference), - srange_find(find_start_offset, find_end_offset, [";"]), + srange_semicolon(find_start_offset, find_end_offset), visit(rescue_clause.statements) ) end until (rescue_clause = rescue_clause.subsequent).nil? @@ -294,7 +323,7 @@ module Prism visit_all(arguments), token(node.closing_loc), ), - srange_find(node.message_loc.end_offset, node.arguments.arguments.last.location.start_offset, ["="]), + token(node.equal_loc), visit(node.arguments.arguments.last) ), block @@ -311,7 +340,7 @@ module Prism if name.end_with?("=") && !message_loc.slice.end_with?("=") && node.arguments && block.nil? builder.assign( builder.attr_asgn(visit(node.receiver), call_operator, token(message_loc)), - srange_find(message_loc.end_offset, node.arguments.location.start_offset, ["="]), + token(node.equal_loc), visit(node.arguments.arguments.last) ) else @@ -664,13 +693,37 @@ module Prism # defined?(a) # ^^^^^^^^^^^ def visit_defined_node(node) - builder.keyword_cmd( - :defined?, - token(node.keyword_loc), - token(node.lparen_loc), - [visit(node.value)], - token(node.rparen_loc) - ) + # Very weird circumstances here where something like: + # + # defined? + # (1) + # + # gets parsed in Ruby as having only the `1` expression but in parser + # it gets parsed as having a begin. In this case we need to synthesize + # that begin to match parser's behavior. + if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n") + builder.keyword_cmd( + :defined?, + token(node.keyword_loc), + nil, + [ + builder.begin( + token(node.lparen_loc), + visit(node.value), + token(node.rparen_loc) + ) + ], + nil + ) + else + builder.keyword_cmd( + :defined?, + token(node.keyword_loc), + token(node.lparen_loc), + [visit(node.value)], + token(node.rparen_loc) + ) + end end # if foo then bar else baz end @@ -733,10 +786,10 @@ module Prism visit(node.index), token(node.in_keyword_loc), visit(node.collection), - if node.do_keyword_loc - token(node.do_keyword_loc) + if (do_keyword_loc = node.do_keyword_loc) + token(do_keyword_loc) else - srange_find(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset, [";"]) + srange_semicolon(node.collection.location.end_offset, (node.statements&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), token(node.end_keyword_loc) @@ -865,10 +918,10 @@ module Prism builder.condition( token(node.if_keyword_loc), visit(node.predicate), - if node.then_keyword_loc - token(node.then_keyword_loc) + if (then_keyword_loc = node.then_keyword_loc) + token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset, [";"]) + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.subsequent&.location || node.end_keyword_loc).start_offset) end, visit(node.statements), case node.subsequent @@ -931,7 +984,11 @@ module Prism token(node.in_loc), pattern, guard, - srange_find(node.pattern.location.end_offset, node.statements&.location&.start_offset, [";", "then"]), + if (then_loc = node.then_loc) + token(then_loc) + else + srange_semicolon(node.pattern.location.end_offset, node.statements&.location&.start_offset) + end, visit(node.statements) ) end @@ -996,7 +1053,7 @@ module Prism builder.index_asgn( visit(node.receiver), token(node.opening_loc), - visit_all(node.arguments.arguments), + visit_all(node.arguments&.arguments || []), token(node.closing_loc), ) end @@ -1064,7 +1121,7 @@ module Prism def visit_interpolated_regular_expression_node(node) builder.regexp_compose( token(node.opening_loc), - visit_all(node.parts), + string_nodes_from_interpolation(node, node.opening), [node.closing[0], srange_offsets(node.closing_loc.start_offset, node.closing_loc.start_offset + 1)], builder.regexp_options([node.closing[1..], srange_offsets(node.closing_loc.start_offset + 1, node.closing_loc.end_offset)]) ) @@ -1081,29 +1138,9 @@ module Prism return visit_heredoc(node) { |children, closing| builder.string_compose(token(node.opening_loc), children, closing) } end - parts = if node.parts.one? { |part| part.type == :string_node } - node.parts.flat_map do |node| - if node.type == :string_node && node.unescaped.lines.count >= 2 - start_offset = node.content_loc.start_offset - - node.unescaped.lines.map do |line| - end_offset = start_offset + line.length - offsets = srange_offsets(start_offset, end_offset) - start_offset = end_offset - - builder.string_internal([line, offsets]) - end - else - visit(node) - end - end - else - visit_all(node.parts) - end - builder.string_compose( token(node.opening_loc), - parts, + string_nodes_from_interpolation(node, node.opening), token(node.closing_loc) ) end @@ -1113,7 +1150,7 @@ module Prism def visit_interpolated_symbol_node(node) builder.symbol_compose( token(node.opening_loc), - visit_all(node.parts), + string_nodes_from_interpolation(node, node.opening), token(node.closing_loc) ) end @@ -1122,14 +1159,14 @@ module Prism # ^^^^^^^^^^^^ def visit_interpolated_x_string_node(node) if node.heredoc? - visit_heredoc(node) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } - else - builder.xstring_compose( - token(node.opening_loc), - visit_all(node.parts), - token(node.closing_loc) - ) + return visit_heredoc(node) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } end + + builder.xstring_compose( + token(node.opening_loc), + string_nodes_from_interpolation(node, node.opening), + token(node.closing_loc) + ) end # -> { it } @@ -1141,7 +1178,17 @@ module Prism # -> { it } # ^^^^^^^^^ def visit_it_parameters_node(node) - builder.args(nil, [], nil, false) + # FIXME: The builder _should_ always be a subclass of the prism builder. + # Currently RuboCop passes in its own builder that always inherits from the + # parser builder (which is lacking the `itarg` method). Once rubocop-ast + # opts in to use the custom prism builder a warning can be emitted when + # it is not the expected class, and eventually raise. + # https://github.com/rubocop/rubocop-ast/pull/354 + if builder.is_a?(Translation::Parser::Builder) + builder.itarg + else + builder.args(nil, [], nil, false) + end end # foo(bar: baz) @@ -1183,7 +1230,7 @@ module Prism false ) end, - node.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))), + visit(node.body), [node.closing, srange(node.closing_loc)] ) end @@ -1307,7 +1354,7 @@ module Prism def visit_multi_write_node(node) elements = multi_target_elements(node) - if elements.length == 1 && elements.first.is_a?(MultiTargetNode) + if elements.length == 1 && elements.first.is_a?(MultiTargetNode) && !node.rest elements = multi_target_elements(elements.first) end @@ -1435,7 +1482,8 @@ module Prism # foo => ^(bar) # ^^^^^^ def visit_pinned_expression_node(node) - expression = builder.begin(token(node.lparen_loc), visit(node.expression), token(node.rparen_loc)) + parts = node.expression.accept(copy_compiler(in_pattern: false)) # Don't treat * and similar as match_rest + expression = builder.begin(token(node.lparen_loc), parts, token(node.rparen_loc)) builder.pin(token(node.operator_loc), expression) end @@ -1507,15 +1555,13 @@ module Prism # /foo/ # ^^^^^ def visit_regular_expression_node(node) - content = node.content parts = - if content.include?("\n") - offset = node.content_loc.start_offset - content.lines.map do |line| - builder.string_internal([line, srange_offsets(offset, offset += line.bytesize)]) - end + if node.content == "" + [] + elsif node.content.include?("\n") + string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) else - [builder.string_internal(token(node.content_loc))] + [builder.string_internal([node.unescaped, srange(node.content_loc)])] end builder.regexp_compose( @@ -1672,28 +1718,11 @@ module Prism elsif node.opening&.start_with?("%") && node.unescaped.empty? builder.string_compose(token(node.opening_loc), [], token(node.closing_loc)) else - content_lines = node.content.lines - unescaped_lines = node.unescaped.lines - parts = - if content_lines.length <= 1 || unescaped_lines.length <= 1 - [builder.string_internal([node.unescaped, srange(node.content_loc)])] - elsif content_lines.length != unescaped_lines.length - # This occurs when we have line continuations in the string. We - # need to come back and fix this, but for now this stops the - # code from breaking when we encounter it because of trying to - # transpose arrays of different lengths. - [builder.string_internal([node.unescaped, srange(node.content_loc)])] + if node.content.include?("\n") + string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) else - start_offset = node.content_loc.start_offset - - [content_lines, unescaped_lines].transpose.map do |content_line, unescaped_line| - end_offset = start_offset + content_line.length - offsets = srange_offsets(start_offset, end_offset) - start_offset = end_offset - - builder.string_internal([unescaped_line, offsets]) - end + [builder.string_internal([node.unescaped, srange(node.content_loc)])] end builder.string_compose( @@ -1737,19 +1766,14 @@ module Prism builder.symbol([node.unescaped, srange(node.location)]) end else - parts = if node.value.lines.one? - [builder.string_internal([node.unescaped, srange(node.value_loc)])] - else - start_offset = node.value_loc.start_offset - - node.value.lines.map do |line| - end_offset = start_offset + line.length - offsets = srange_offsets(start_offset, end_offset) - start_offset = end_offset - - builder.string_internal([line, offsets]) + parts = + if node.value == "" + [] + elsif node.value.include?("\n") + string_nodes_from_line_continuations(node.unescaped, node.value, node.value_loc.start_offset, node.opening) + else + [builder.string_internal([node.unescaped, srange(node.value_loc)])] end - end builder.symbol_compose( token(node.opening_loc), @@ -1781,10 +1805,10 @@ module Prism builder.condition( token(node.keyword_loc), visit(node.predicate), - if node.then_keyword_loc - token(node.then_keyword_loc) + if (then_keyword_loc = node.then_keyword_loc) + token(then_keyword_loc) else - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset, [";"]) + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.else_clause&.location || node.end_keyword_loc).start_offset) end, visit(node.else_clause), token(node.else_clause&.else_keyword_loc), @@ -1812,7 +1836,11 @@ module Prism :until, token(node.keyword_loc), visit(node.predicate), - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]), + if (do_keyword_loc = node.do_keyword_loc) + token(do_keyword_loc) + else + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) + end, visit(node.statements), token(node.closing_loc) ) @@ -1832,10 +1860,10 @@ module Prism builder.when( token(node.keyword_loc), visit_all(node.conditions), - if node.then_keyword_loc - token(node.then_keyword_loc) + if (then_keyword_loc = node.then_keyword_loc) + token(then_keyword_loc) else - srange_find(node.conditions.last.location.end_offset, node.statements&.location&.start_offset, [";"]) + srange_semicolon(node.conditions.last.location.end_offset, node.statements&.location&.start_offset) end, visit(node.statements) ) @@ -1852,7 +1880,11 @@ module Prism :while, token(node.keyword_loc), visit(node.predicate), - srange_find(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset, [";", "do"]), + if (do_keyword_loc = node.do_keyword_loc) + token(do_keyword_loc) + else + srange_semicolon(node.predicate.location.end_offset, (node.statements&.location || node.closing_loc).start_offset) + end, visit(node.statements), token(node.closing_loc) ) @@ -1870,28 +1902,23 @@ module Prism # ^^^^^ def visit_x_string_node(node) if node.heredoc? - visit_heredoc(node.to_interpolated) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } - else - parts = if node.unescaped.lines.one? - [builder.string_internal([node.unescaped, srange(node.content_loc)])] - else - start_offset = node.content_loc.start_offset - - node.unescaped.lines.map do |line| - end_offset = start_offset + line.length - offsets = srange_offsets(start_offset, end_offset) - start_offset = end_offset + return visit_heredoc(node.to_interpolated) { |children, closing| builder.xstring_compose(token(node.opening_loc), children, closing) } + end - builder.string_internal([line, offsets]) - end + parts = + if node.content == "" + [] + elsif node.content.include?("\n") + string_nodes_from_line_continuations(node.unescaped, node.content, node.content_loc.start_offset, node.opening) + else + [builder.string_internal([node.unescaped, srange(node.content_loc)])] end - builder.xstring_compose( - token(node.opening_loc), - parts, - token(node.closing_loc) - ) - end + builder.xstring_compose( + token(node.opening_loc), + parts, + token(node.closing_loc) + ) end # yield @@ -1985,18 +2012,16 @@ module Prism Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) end - # Constructs a new source range by finding the given tokens between the - # given start offset and end offset. If the needle is not found, it - # returns nil. Importantly it does not search past newlines or comments. + # Constructs a new source range by finding a semicolon between the given + # start offset and end offset. If the semicolon is not found, it returns + # nil. Importantly it does not search past newlines or comments. # # Note that end_offset is allowed to be nil, in which case this will # search until the end of the string. - def srange_find(start_offset, end_offset, tokens) - if (match = source_buffer.source.byteslice(start_offset...end_offset).match(/\A(\s*)(#{tokens.join("|")})/)) - _, whitespace, token = *match - token_offset = start_offset + whitespace.bytesize - - [token, Range.new(source_buffer, offset_cache[token_offset], offset_cache[token_offset + token.bytesize])] + def srange_semicolon(start_offset, end_offset) + if (match = source_buffer.source.byteslice(start_offset...end_offset)[/\A\s*;/]) + final_offset = start_offset + match.bytesize + [";", Range.new(source_buffer, offset_cache[final_offset - 1], offset_cache[final_offset])] end end @@ -2032,7 +2057,7 @@ module Prism false ) end, - block.body&.accept(copy_compiler(forwarding: implicit_parameters ? [] : find_forwarding(parameters&.parameters))), + visit(block.body), token(block.closing_loc) ) else @@ -2040,13 +2065,6 @@ module Prism end end - # The parser gem automatically converts \r\n to \n, meaning our offsets - # need to be adjusted to always subtract 1 from the length. - def chomped_bytesize(line) - chomped = line.chomp - chomped.bytesize + (chomped == line ? 0 : 1) - end - # Visit a heredoc that can be either a string or an xstring. def visit_heredoc(node) children = Array.new @@ -2063,34 +2081,8 @@ module Prism node.parts.each do |part| pushing = - if part.is_a?(StringNode) && part.unescaped.include?("\n") - unescaped = part.unescaped.lines - escaped = part.content.lines - - escaped_lengths = [] - normalized_lengths = [] - - if node.opening.end_with?("'") - escaped.each do |line| - escaped_lengths << line.bytesize - normalized_lengths << chomped_bytesize(line) - end - else - escaped - .chunk_while { |before, after| before.match?(/(?<!\\)\\\r?\n$/) } - .each do |lines| - escaped_lengths << lines.sum(&:bytesize) - normalized_lengths << lines.sum { |line| chomped_bytesize(line) } - end - end - - start_offset = part.location.start_offset - - unescaped.map.with_index do |unescaped_line, index| - inner_part = builder.string_internal([unescaped_line, srange_offsets(start_offset, start_offset + normalized_lengths.fetch(index, 0))]) - start_offset += escaped_lengths.fetch(index, 0) - inner_part - end + if part.is_a?(StringNode) && part.content.include?("\n") + string_nodes_from_line_continuations(part.unescaped, part.content, part.location.start_offset, node.opening) else [visit(part)] end @@ -2104,7 +2096,7 @@ module Prism location = appendee.loc location = location.with_expression(location.expression.join(child.loc.expression)) - children[-1] = appendee.updated(:str, [appendee.children.first << child.children.first], location: location) + children[-1] = appendee.updated(:str, ["#{appendee.children.first}#{child.children.first}"], location: location) else children << child end @@ -2140,6 +2132,102 @@ module Prism parser.pattern_variables.pop end end + + # When the content of a string node is split across multiple lines, the + # parser gem creates individual string nodes for each line the content is part of. + def string_nodes_from_interpolation(node, opening) + node.parts.flat_map do |part| + if part.type == :string_node && part.content.include?("\n") && part.opening_loc.nil? + string_nodes_from_line_continuations(part.unescaped, part.content, part.content_loc.start_offset, opening) + else + visit(part) + end + end + end + + # Create parser string nodes from a single prism node. The parser gem + # "glues" strings together when a line continuation is encountered. + def string_nodes_from_line_continuations(unescaped, escaped, start_offset, opening) + unescaped = unescaped.lines + escaped = escaped.lines + percent_array = opening&.start_with?("%w", "%W", "%i", "%I") + regex = opening == "/" || opening&.start_with?("%r") + + # Non-interpolating strings + if opening&.end_with?("'") || opening&.start_with?("%q", "%s", "%w", "%i") + current_length = 0 + current_line = +"" + + escaped.filter_map.with_index do |escaped_line, index| + unescaped_line = unescaped.fetch(index, "") + current_length += escaped_line.bytesize + current_line << unescaped_line + + # Glue line continuations together. Only %w and %i arrays can contain these. + if percent_array && escaped_line[/(\\)*\n$/, 1]&.length&.odd? + next unless index == escaped.count - 1 + end + s = builder.string_internal([current_line, srange_offsets(start_offset, start_offset + current_length)]) + start_offset += escaped_line.bytesize + current_line = +"" + current_length = 0 + s + end + else + escaped_lengths = [] + normalized_lengths = [] + # Keeps track of where an unescaped line should start a new token. An unescaped + # \n would otherwise be indistinguishable from the actual newline at the end of + # of the line. The parser gem only emits a new string node at "real" newlines, + # line continuations don't start a new node as well. + do_next_tokens = [] + + escaped + .chunk_while { |before, after| before[/(\\*)\r?\n$/, 1]&.length&.odd? || false } + .each do |lines| + escaped_lengths << lines.sum(&:bytesize) + + unescaped_lines_count = + if regex + 0 # Will always be preserved as is + else + lines.sum do |line| + count = line.scan(/(\\*)n/).count { |(backslashes)| backslashes&.length&.odd? } + count -= 1 if !line.end_with?("\n") && count > 0 + count + end + end + + extra = 1 + extra = lines.count if percent_array # Account for line continuations in percent arrays + + normalized_lengths.concat(Array.new(unescaped_lines_count + extra, 0)) + normalized_lengths[-1] = lines.sum { |line| line.bytesize } + do_next_tokens.concat(Array.new(unescaped_lines_count + extra, false)) + do_next_tokens[-1] = true + end + + current_line = +"" + current_normalized_length = 0 + + emitted_count = 0 + unescaped.filter_map.with_index do |unescaped_line, index| + current_line << unescaped_line + current_normalized_length += normalized_lengths.fetch(index, 0) + + if do_next_tokens[index] + inner_part = builder.string_internal([current_line, srange_offsets(start_offset, start_offset + current_normalized_length)]) + start_offset += escaped_lengths.fetch(emitted_count, 0) + current_line = +"" + current_normalized_length = 0 + emitted_count += 1 + inner_part + else + nil + end + end + end + end end end end diff --git a/lib/prism/translation/parser/lexer.rb b/lib/prism/translation/parser/lexer.rb index db7dbb1c87..75c48ef667 100644 --- a/lib/prism/translation/parser/lexer.rb +++ b/lib/prism/translation/parser/lexer.rb @@ -1,4 +1,9 @@ # frozen_string_literal: true +# :markup: markdown + +require "strscan" +require_relative "../../polyfill/append_as_bytes" +require_relative "../../polyfill/scan_byte" module Prism module Translation @@ -6,16 +11,17 @@ module Prism # Accepts a list of prism tokens and converts them into the expected # format for the parser gem. class Lexer + # These tokens are always skipped + TYPES_ALWAYS_SKIP = Set.new(%i[IGNORED_NEWLINE __END__ EOF]) + private_constant :TYPES_ALWAYS_SKIP + # The direct translating of types between the two lexers. TYPES = { # These tokens should never appear in the output of the lexer. - EOF: nil, MISSING: nil, NOT_PROVIDED: nil, - IGNORED_NEWLINE: nil, EMBDOC_END: nil, EMBDOC_LINE: nil, - __END__: nil, # These tokens have more or less direct mappings. AMPERSAND: :tAMPER2, @@ -191,16 +197,24 @@ module Prism # # NOTE: In edge cases like `-> (foo = -> (bar) {}) do end`, please note that `kDO` is still returned # instead of `kDO_LAMBDA`, which is expected: https://github.com/ruby/prism/pull/3046 - LAMBDA_TOKEN_TYPES = [:kDO_LAMBDA, :tLAMBDA, :tLAMBEG] + LAMBDA_TOKEN_TYPES = Set.new([:kDO_LAMBDA, :tLAMBDA, :tLAMBEG]) # The `PARENTHESIS_LEFT` token in Prism is classified as either `tLPAREN` or `tLPAREN2` in the Parser gem. # The following token types are listed as those classified as `tLPAREN`. - LPAREN_CONVERSION_TOKEN_TYPES = [ - :kBREAK, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3, - :tEQL, :tLPAREN, :tLPAREN2, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS - ] + LPAREN_CONVERSION_TOKEN_TYPES = Set.new([ + :kBREAK, :tCARET, :kCASE, :tDIVIDE, :kFOR, :kIF, :kNEXT, :kRETURN, :kUNTIL, :kWHILE, :tAMPER, :tANDOP, :tBANG, :tCOMMA, :tDOT2, :tDOT3, + :tEQL, :tLPAREN, :tLPAREN2, :tLPAREN_ARG, :tLSHFT, :tNL, :tOP_ASGN, :tOROP, :tPIPE, :tSEMI, :tSTRING_DBEG, :tUMINUS, :tUPLUS, :tLCURLY + ]) + + # Types of tokens that are allowed to continue a method call with comments in-between. + # For these, the parser gem doesn't emit a newline token after the last comment. + COMMENT_CONTINUATION_TYPES = Set.new([:COMMENT, :AMPERSAND_DOT, :DOT]) + private_constant :COMMENT_CONTINUATION_TYPES + + # Heredocs are complex and require us to keep track of a bit of info to refer to later + HeredocData = Struct.new(:identifier, :common_whitespace, keyword_init: true) - private_constant :TYPES, :EXPR_BEG, :EXPR_LABEL, :LAMBDA_TOKEN_TYPES, :LPAREN_CONVERSION_TOKEN_TYPES + private_constant :TYPES, :EXPR_BEG, :EXPR_LABEL, :LAMBDA_TOKEN_TYPES, :LPAREN_CONVERSION_TOKEN_TYPES, :HeredocData # The Parser::Source::Buffer that the tokens were lexed from. attr_reader :source_buffer @@ -230,46 +244,78 @@ module Prism index = 0 length = lexed.length - heredoc_identifier_stack = [] + heredoc_stack = [] + quote_stack = [] + + # The parser gem emits the newline tokens for comments out of order. This saves + # that token location to emit at a later time to properly line everything up. + # https://github.com/whitequark/parser/issues/1025 + comment_newline_location = nil while index < length token, state = lexed[index] index += 1 - next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type) + next if TYPES_ALWAYS_SKIP.include?(token.type) type = TYPES.fetch(token.type) value = token.value - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset]) + location = range(token.location.start_offset, token.location.end_offset) case type when :kDO - types = tokens.map(&:first) - nearest_lambda_token_type = types.reverse.find { |type| LAMBDA_TOKEN_TYPES.include?(type) } + nearest_lambda_token = tokens.reverse_each.find do |token| + LAMBDA_TOKEN_TYPES.include?(token.first) + end - if nearest_lambda_token_type == :tLAMBDA + if nearest_lambda_token&.first == :tLAMBDA type = :kDO_LAMBDA end when :tCHARACTER value.delete_prefix!("?") + # Character literals behave similar to double-quoted strings. We can use the same escaping mechanism. + value = unescape_string(value, "?") when :tCOMMENT if token.type == :EMBDOC_BEGIN - start_index = index - while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1) + while !((next_token = lexed[index]&.first) && next_token.type == :EMBDOC_END) && (index < length - 1) value += next_token.value index += 1 end - if start_index != index - value += next_token.value - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset]) - index += 1 - end + value += next_token.value + location = range(token.location.start_offset, next_token.location.end_offset) + index += 1 else - value.chomp! - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - 1]) + is_at_eol = value.chomp!.nil? + location = range(token.location.start_offset, token.location.end_offset + (is_at_eol ? 0 : -1)) + + prev_token, _ = lexed[index - 2] if index - 2 >= 0 + next_token, _ = lexed[index] + + is_inline_comment = prev_token&.location&.start_line == token.location.start_line + if is_inline_comment && !is_at_eol && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type) + tokens << [:tCOMMENT, [value, location]] + + nl_location = range(token.location.end_offset - 1, token.location.end_offset) + tokens << [:tNL, [nil, nl_location]] + next + elsif is_inline_comment && next_token&.type == :COMMENT + comment_newline_location = range(token.location.end_offset - 1, token.location.end_offset) + elsif comment_newline_location && !COMMENT_CONTINUATION_TYPES.include?(next_token&.type) + tokens << [:tCOMMENT, [value, location]] + tokens << [:tNL, [nil, comment_newline_location]] + comment_newline_location = nil + next + end end when :tNL + next_token, _ = lexed[index] + # Newlines after comments are emitted out of order. + if next_token&.type == :COMMENT + comment_newline_location = location + next + end + value = nil when :tFLOAT value = parse_float(value) @@ -277,8 +323,8 @@ module Prism value = parse_complex(value) when :tINTEGER if value.start_with?("+") - tokens << [:tUNARY_NUM, ["+", Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.start_offset + 1])]] - location = Range.new(source_buffer, offset_cache[token.location.start_offset + 1], offset_cache[token.location.end_offset]) + tokens << [:tUNARY_NUM, ["+", range(token.location.start_offset, token.location.start_offset + 1)]] + location = range(token.location.start_offset + 1, token.location.end_offset) end value = parse_integer(value) @@ -297,92 +343,196 @@ module Prism when :tRATIONAL value = parse_rational(value) when :tSPACE + location = range(token.location.start_offset, token.location.start_offset + percent_array_leading_whitespace(value)) value = nil when :tSTRING_BEG - if token.type == :HEREDOC_START - heredoc_identifier_stack.push(value.match(/<<[-~]?["'`]?(?<heredoc_identifier>.*?)["'`]?\z/)[:heredoc_identifier]) - end - if ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_END + next_token, _ = lexed[index] + next_next_token, _ = lexed[index + 1] + basic_quotes = value == '"' || value == "'" + + if basic_quotes && next_token&.type == :STRING_END next_location = token.location.join(next_token.location) type = :tSTRING value = "" - location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) + location = range(next_location.start_offset, next_location.end_offset) index += 1 - elsif ["\"", "'"].include?(value) && (next_token = lexed[index][0]) && next_token.type == :STRING_CONTENT && next_token.value.lines.count <= 1 && (next_next_token = lexed[index + 1][0]) && next_next_token.type == :STRING_END - next_location = token.location.join(next_next_token.location) - type = :tSTRING - value = next_token.value.gsub("\\\\", "\\") - location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) - index += 2 - elsif value.start_with?("<<") + elsif value.start_with?("'", '"', "%") + if next_token&.type == :STRING_CONTENT && next_next_token&.type == :STRING_END + string_value = next_token.value + if simplify_string?(string_value, value) + next_location = token.location.join(next_next_token.location) + if percent_array?(value) + value = percent_array_unescape(string_value) + else + value = unescape_string(string_value, value) + end + type = :tSTRING + location = range(next_location.start_offset, next_location.end_offset) + index += 2 + tokens << [type, [value, location]] + + next + end + end + + quote_stack.push(value) + elsif token.type == :HEREDOC_START quote = value[2] == "-" || value[2] == "~" ? value[3] : value[2] + heredoc_type = value[2] == "-" || value[2] == "~" ? value[2] : "" + heredoc = HeredocData.new( + identifier: value.match(/<<[-~]?["'`]?(?<heredoc_identifier>.*?)["'`]?\z/)[:heredoc_identifier], + common_whitespace: 0, + ) + if quote == "`" type = :tXSTRING_BEG - value = "<<`" + end + + # The parser gem trims whitespace from squiggly heredocs. We must record + # the most common whitespace to later remove. + if heredoc_type == "~" || heredoc_type == "`" + heredoc.common_whitespace = calculate_heredoc_whitespace(index) + end + + if quote == "'" || quote == '"' || quote == "`" + value = "<<#{quote}" else - value = "<<#{quote == "'" || quote == "\"" ? quote : "\""}" + value = '<<"' end + + heredoc_stack.push(heredoc) + quote_stack.push(value) end when :tSTRING_CONTENT - unless (lines = token.value.lines).one? - start_offset = offset_cache[token.location.start_offset] - lines.map do |line| - newline = line.end_with?("\r\n") ? "\r\n" : "\n" + is_percent_array = percent_array?(quote_stack.last) + + if (lines = token.value.lines).one? + # Prism usually emits a single token for strings with line continuations. + # For squiggly heredocs they are not joined so we do that manually here. + current_string = +"" + current_length = 0 + start_offset = token.location.start_offset + while token.type == :STRING_CONTENT + current_length += token.value.bytesize + # Heredoc interpolation can have multiple STRING_CONTENT nodes on the same line. + prev_token, _ = lexed[index - 2] if index - 2 >= 0 + is_first_token_on_line = prev_token && token.location.start_line != prev_token.location.start_line + # The parser gem only removes indentation when the heredoc is not nested + not_nested = heredoc_stack.size == 1 + if is_percent_array + value = percent_array_unescape(token.value) + elsif is_first_token_on_line && not_nested && (current_heredoc = heredoc_stack.last).common_whitespace > 0 + value = trim_heredoc_whitespace(token.value, current_heredoc) + end + + current_string << unescape_string(value, quote_stack.last) + relevant_backslash_count = if quote_stack.last.start_with?("%W", "%I") + 0 # the last backslash escapes the newline + else + token.value[/(\\{1,})\n/, 1]&.length || 0 + end + if relevant_backslash_count.even? || !interpolation?(quote_stack.last) + tokens << [:tSTRING_CONTENT, [current_string, range(start_offset, start_offset + current_length)]] + break + end + token, _ = lexed[index] + index += 1 + end + else + # When the parser gem encounters a line continuation inside of a multiline string, + # it emits a single string node. The backslash (and remaining newline) is removed. + current_line = +"" + adjustment = 0 + start_offset = token.location.start_offset + emit = false + + lines.each.with_index do |line, index| chomped_line = line.chomp - if match = chomped_line.match(/(?<backslashes>\\+)\z/) - adjustment = match[:backslashes].size / 2 - adjusted_line = chomped_line.delete_suffix("\\" * adjustment) - if match[:backslashes].size.odd? - adjusted_line.delete_suffix!("\\") - adjustment += 2 + backslash_count = chomped_line[/\\{1,}\z/]&.length || 0 + is_interpolation = interpolation?(quote_stack.last) + + if backslash_count.odd? && (is_interpolation || is_percent_array) + if is_percent_array + current_line << percent_array_unescape(line) + adjustment += 1 else - adjusted_line << newline + chomped_line.delete_suffix!("\\") + current_line << chomped_line + adjustment += 2 end + # If the string ends with a line continuation emit the remainder + emit = index == lines.count - 1 else - adjusted_line = line - adjustment = 0 + current_line << line + emit = true end - end_offset = start_offset + adjusted_line.length + adjustment - tokens << [:tSTRING_CONTENT, [adjusted_line, Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset])]] - start_offset = end_offset + if emit + end_offset = start_offset + current_line.bytesize + adjustment + tokens << [:tSTRING_CONTENT, [unescape_string(current_line, quote_stack.last), range(start_offset, end_offset)]] + start_offset = end_offset + current_line = +"" + adjustment = 0 + end end - next end + next when :tSTRING_DVAR value = nil when :tSTRING_END if token.type == :HEREDOC_END && value.end_with?("\n") newline_length = value.end_with?("\r\n") ? 2 : 1 - value = heredoc_identifier_stack.pop - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - newline_length]) + value = heredoc_stack.pop.identifier + location = range(token.location.start_offset, token.location.end_offset - newline_length) elsif token.type == :REGEXP_END value = value[0] - location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.start_offset + 1]) + location = range(token.location.start_offset, token.location.start_offset + 1) + end + + if percent_array?(quote_stack.pop) + prev_token, _ = lexed[index - 2] if index - 2 >= 0 + empty = %i[PERCENT_LOWER_I PERCENT_LOWER_W PERCENT_UPPER_I PERCENT_UPPER_W].include?(prev_token&.type) + ends_with_whitespace = prev_token&.type == :WORDS_SEP + # parser always emits a space token after content in a percent array, even if no actual whitespace is present. + if !empty && !ends_with_whitespace + tokens << [:tSPACE, [nil, range(token.location.start_offset, token.location.start_offset)]] + end end when :tSYMBEG - if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END + if (next_token = lexed[index]&.first) && next_token.type != :STRING_CONTENT && next_token.type != :EMBEXPR_BEGIN && next_token.type != :EMBVAR && next_token.type != :STRING_END next_location = token.location.join(next_token.location) type = :tSYMBOL value = next_token.value value = { "~@" => "~", "!@" => "!" }.fetch(value, value) - location = Range.new(source_buffer, offset_cache[next_location.start_offset], offset_cache[next_location.end_offset]) + location = range(next_location.start_offset, next_location.end_offset) index += 1 + else + quote_stack.push(value) end when :tFID if !tokens.empty? && tokens.dig(-1, 0) == :kDEF type = :tIDENTIFIER end when :tXSTRING_BEG - if (next_token = lexed[index][0]) && next_token.type != :STRING_CONTENT && next_token.type != :STRING_END + if (next_token = lexed[index]&.first) && !%i[STRING_CONTENT STRING_END EMBEXPR_BEGIN].include?(next_token.type) + # self.`() type = :tBACK_REF2 end + quote_stack.push(value) + when :tSYMBOLS_BEG, :tQSYMBOLS_BEG, :tWORDS_BEG, :tQWORDS_BEG + if (next_token = lexed[index]&.first) && next_token.type == :WORDS_SEP + index += 1 + end + + quote_stack.push(value) + when :tREGEXP_BEG + quote_stack.push(value) end tokens << [type, [value, location]] if token.type == :REGEXP_END - tokens << [:tREGEXP_OPT, [token.value[1..], Range.new(source_buffer, offset_cache[token.location.start_offset + 1], offset_cache[token.location.end_offset])]] + tokens << [:tREGEXP_OPT, [token.value[1..], range(token.location.start_offset + 1, token.location.end_offset)]] end end @@ -391,6 +541,11 @@ module Prism private + # Creates a new parser range, taking prisms byte offsets into account + def range(start_offset, end_offset) + Range.new(source_buffer, offset_cache[start_offset], offset_cache[end_offset]) + end + # Parse an integer from the string representation. def parse_integer(value) Integer(value) @@ -432,6 +587,233 @@ module Prism rescue ArgumentError 0r end + + # Wonky heredoc tab/spaces rules. + # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L10548-L10558 + def calculate_heredoc_whitespace(heredoc_token_index) + next_token_index = heredoc_token_index + nesting_level = 0 + previous_line = -1 + result = Float::MAX + + while (next_token = lexed[next_token_index]&.first) + next_token_index += 1 + next_next_token, _ = lexed[next_token_index] + first_token_on_line = next_token.location.start_column == 0 + + # String content inside nested heredocs and interpolation is ignored + if next_token.type == :HEREDOC_START || next_token.type == :EMBEXPR_BEGIN + # When interpolation is the first token of a line there is no string + # content to check against. There will be no common whitespace. + if nesting_level == 0 && first_token_on_line + result = 0 + end + nesting_level += 1 + elsif next_token.type == :HEREDOC_END || next_token.type == :EMBEXPR_END + nesting_level -= 1 + # When we encountered the matching heredoc end, we can exit + break if nesting_level == -1 + elsif next_token.type == :STRING_CONTENT && nesting_level == 0 && first_token_on_line + common_whitespace = 0 + next_token.value[/^\s*/].each_char do |char| + if char == "\t" + common_whitespace = (common_whitespace / 8 + 1) * 8; + else + common_whitespace += 1 + end + end + + is_first_token_on_line = next_token.location.start_line != previous_line + # Whitespace is significant if followed by interpolation + whitespace_only = common_whitespace == next_token.value.length && next_next_token&.location&.start_line != next_token.location.start_line + if is_first_token_on_line && !whitespace_only && common_whitespace < result + result = common_whitespace + previous_line = next_token.location.start_line + end + end + end + result + end + + # Wonky heredoc tab/spaces rules. + # https://github.com/ruby/prism/blob/v1.3.0/src/prism.c#L16528-L16545 + def trim_heredoc_whitespace(string, heredoc) + trimmed_whitespace = 0 + trimmed_characters = 0 + while (string[trimmed_characters] == "\t" || string[trimmed_characters] == " ") && trimmed_whitespace < heredoc.common_whitespace + if string[trimmed_characters] == "\t" + trimmed_whitespace = (trimmed_whitespace / 8 + 1) * 8; + break if trimmed_whitespace > heredoc.common_whitespace + else + trimmed_whitespace += 1 + end + trimmed_characters += 1 + end + + string[trimmed_characters..] + end + + # Escape sequences that have special and should appear unescaped in the resulting string. + ESCAPES = { + "a" => "\a", "b" => "\b", "e" => "\e", "f" => "\f", + "n" => "\n", "r" => "\r", "s" => "\s", "t" => "\t", + "v" => "\v", "\\" => "\\" + }.freeze + private_constant :ESCAPES + + # When one of these delimiters is encountered, then the other + # one is allowed to be escaped as well. + DELIMITER_SYMETRY = { "[" => "]", "(" => ")", "{" => "}", "<" => ">" }.freeze + private_constant :DELIMITER_SYMETRY + + + # https://github.com/whitequark/parser/blob/v3.3.6.0/lib/parser/lexer-strings.rl#L14 + REGEXP_META_CHARACTERS = ["\\", "$", "(", ")", "*", "+", ".", "<", ">", "?", "[", "]", "^", "{", "|", "}"] + private_constant :REGEXP_META_CHARACTERS + + # Apply Ruby string escaping rules + def unescape_string(string, quote) + # In single-quoted heredocs, everything is taken literally. + return string if quote == "<<'" + + # OPTIMIZATION: Assume that few strings need escaping to speed up the common case. + return string unless string.include?("\\") + + # Enclosing character for the string. `"` for `"foo"`, `{` for `%w{foo}`, etc. + delimiter = quote[-1] + + if regexp?(quote) + # Should be escaped handled to single-quoted heredocs. The only character that is + # allowed to be escaped is the delimiter, except when that also has special meaning + # in the regexp. Since all the symetry delimiters have special meaning, they don't need + # to be considered separately. + if REGEXP_META_CHARACTERS.include?(delimiter) + string + else + # There can never be an even amount of backslashes. It would be a syntax error. + string.gsub(/\\(#{Regexp.escape(delimiter)})/, '\1') + end + elsif interpolation?(quote) + # Appending individual escape sequences may force the string out of its intended + # encoding. Start out with binary and force it back later. + result = "".b + + scanner = StringScanner.new(string) + while (skipped = scanner.skip_until(/\\/)) + # Append what was just skipped over, excluding the found backslash. + result.append_as_bytes(string.byteslice(scanner.pos - skipped, skipped - 1)) + escape_read(result, scanner, false, false) + end + + # Add remaining chars + result.append_as_bytes(string.byteslice(scanner.pos..)) + result.force_encoding(source_buffer.source.encoding) + else + delimiters = Regexp.escape("#{delimiter}#{DELIMITER_SYMETRY[delimiter]}") + string.gsub(/\\([\\#{delimiters}])/, '\1') + end + end + + # Certain strings are merged into a single string token. + def simplify_string?(value, quote) + case quote + when "'" + # Only simplify 'foo' + !value.include?("\n") + when '"' + # Simplify when every line ends with a line continuation, or it is the last line + value.lines.all? do |line| + !line.end_with?("\n") || line[/(\\*)$/, 1]&.length&.odd? + end + else + # %q and similar are never simplified + false + end + end + + # Escape a byte value, given the control and meta flags. + def escape_build(value, control, meta) + value &= 0x9f if control + value |= 0x80 if meta + value + end + + # Read an escape out of the string scanner, given the control and meta + # flags, and push the unescaped value into the result. + def escape_read(result, scanner, control, meta) + if scanner.skip("\n") + # Line continuation + elsif (value = ESCAPES[scanner.peek(1)]) + # Simple single-character escape sequences like \n + result.append_as_bytes(value) + scanner.pos += 1 + elsif (value = scanner.scan(/[0-7]{1,3}/)) + # \nnn + result.append_as_bytes(escape_build(value.to_i(8), control, meta)) + elsif (value = scanner.scan(/x[0-9a-fA-F]{1,2}/)) + # \xnn + result.append_as_bytes(escape_build(value[1..].to_i(16), control, meta)) + elsif (value = scanner.scan(/u[0-9a-fA-F]{4}/)) + # \unnnn + result.append_as_bytes(value[1..].hex.chr(Encoding::UTF_8)) + elsif scanner.skip("u{}") + # https://github.com/whitequark/parser/issues/856 + elsif (value = scanner.scan(/u{.*?}/)) + # \u{nnnn ...} + value[2..-2].split.each do |unicode| + result.append_as_bytes(unicode.hex.chr(Encoding::UTF_8)) + end + elsif (value = scanner.scan(/c\\?(?=[[:print:]])|C-\\?(?=[[:print:]])/)) + # \cx or \C-x where x is an ASCII printable character + escape_read(result, scanner, true, meta) + elsif (value = scanner.scan(/M-\\?(?=[[:print:]])/)) + # \M-x where x is an ASCII printable character + escape_read(result, scanner, control, true) + elsif (byte = scanner.scan_byte) + # Something else after an escape. + if control && byte == 0x3f # ASCII '?' + result.append_as_bytes(escape_build(0x7f, false, meta)) + else + result.append_as_bytes(escape_build(byte, control, meta)) + end + end + end + + # In a percent array, certain whitespace can be preceeded with a backslash, + # causing the following characters to be part of the previous element. + def percent_array_unescape(string) + string.gsub(/(\\)+[ \f\n\r\t\v]/) do |full_match| + full_match.delete_prefix!("\\") if Regexp.last_match[1].length.odd? + full_match + end + end + + # For %-arrays whitespace, the parser gem only considers whitespace before the newline. + def percent_array_leading_whitespace(string) + return 1 if string.start_with?("\n") + + leading_whitespace = 0 + string.each_char do |c| + break if c == "\n" + leading_whitespace += 1 + end + leading_whitespace + end + + # Determine if characters preceeded by a backslash should be escaped or not + def interpolation?(quote) + !quote.end_with?("'") && !quote.start_with?("%q", "%w", "%i", "%s") + end + + # Regexp allow interpolation but are handled differently during unescaping + def regexp?(quote) + quote == "/" || quote.start_with?("%r") + end + + # Determine if the string is part of a %-style array. + def percent_array?(quote) + quote.start_with?("%w", "%W", "%i", "%I") + end end end end diff --git a/lib/prism/translation/parser33.rb b/lib/prism/translation/parser33.rb deleted file mode 100644 index b09266e06a..0000000000 --- a/lib/prism/translation/parser33.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`. - class Parser33 < Parser - def version # :nodoc: - 33 - end - end - end -end diff --git a/lib/prism/translation/parser34.rb b/lib/prism/translation/parser34.rb deleted file mode 100644 index 0ead70ad3c..0000000000 --- a/lib/prism/translation/parser34.rb +++ /dev/null @@ -1,12 +0,0 @@ -# frozen_string_literal: true - -module Prism - module Translation - # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`. - class Parser34 < Parser - def version # :nodoc: - 34 - end - end - end -end diff --git a/lib/prism/translation/parser_current.rb b/lib/prism/translation/parser_current.rb new file mode 100644 index 0000000000..f13eff6bbe --- /dev/null +++ b/lib/prism/translation/parser_current.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true +# :markup: markdown +# typed: ignore + +# +module Prism + module Translation + case RUBY_VERSION + when /^3\.3\./ + ParserCurrent = Parser33 + when /^3\.4\./ + ParserCurrent = Parser34 + when /^3\.5\./, /^4\.0\./ + ParserCurrent = Parser40 + when /^4\.1\./ + ParserCurrent = Parser41 + else + # Keep this in sync with released Ruby. + parser = Parser40 + major, minor, _patch = Gem::Version.new(RUBY_VERSION).segments + warn "warning: `Prism::Translation::Current` is loading #{parser.name}, " \ + "but you are running #{major}.#{minor}." + ParserCurrent = parser + end + end +end diff --git a/lib/prism/translation/parser_versions.rb b/lib/prism/translation/parser_versions.rb new file mode 100644 index 0000000000..720c7d548c --- /dev/null +++ b/lib/prism/translation/parser_versions.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true +# :markup: markdown + +module Prism + module Translation + # This class is the entry-point for Ruby 3.3 of `Prism::Translation::Parser`. + class Parser33 < Parser + def version # :nodoc: + 33 + end + end + + # This class is the entry-point for Ruby 3.4 of `Prism::Translation::Parser`. + class Parser34 < Parser + def version # :nodoc: + 34 + end + end + + # This class is the entry-point for Ruby 4.0 of `Prism::Translation::Parser`. + class Parser40 < Parser + def version # :nodoc: + 40 + end + end + + Parser35 = Parser40 # :nodoc: + + # This class is the entry-point for Ruby 4.1 of `Prism::Translation::Parser`. + class Parser41 < Parser + def version # :nodoc: + 41 + end + end + end +end diff --git a/lib/prism/translation/ripper.rb b/lib/prism/translation/ripper.rb index 018842715b..735217d2e0 100644 --- a/lib/prism/translation/ripper.rb +++ b/lib/prism/translation/ripper.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - -require "ripper" +# :markup: markdown module Prism module Translation @@ -70,7 +69,7 @@ module Prism # [[1, 13], :on_kw, "end", END ]] # def self.lex(src, filename = "-", lineno = 1, raise_errors: false) - result = Prism.lex_compat(src, filepath: filename, line: lineno) + result = Prism.lex_compat(src, filepath: filename, line: lineno, version: "current") if result.failure? && raise_errors raise SyntaxError, result.errors.first.message @@ -79,6 +78,19 @@ module Prism end end + # Tokenizes the Ruby program and returns an array of strings. + # The +filename+ and +lineno+ arguments are mostly ignored, since the + # return value is just the tokenized input. + # By default, this method does not handle syntax errors in +src+, + # use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+. + # + # p Ripper.tokenize("def m(a) nil end") + # # => ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"] + # + def self.tokenize(...) + lex(...).map(&:value) + end + # This contains a table of all of the parser events and their # corresponding arity. PARSER_EVENT_TABLE = { @@ -425,9 +437,35 @@ module Prism end end + autoload :Filter, "prism/translation/ripper/filter" + autoload :Lexer, "prism/translation/ripper/lexer" autoload :SexpBuilder, "prism/translation/ripper/sexp" autoload :SexpBuilderPP, "prism/translation/ripper/sexp" + # :stopdoc: + # This is not part of the public API but used by some gems. + + # Ripper-internal bitflags. + LEX_STATE_NAMES = %i[ + BEG END ENDARG ENDFN ARG CMDARG MID FNAME DOT CLASS LABEL LABELED FITEM + ].map.with_index.to_h { |name, i| [2 ** i, name] }.freeze + private_constant :LEX_STATE_NAMES + + LEX_STATE_NAMES.each do |value, key| + const_set("EXPR_#{key}", value) + end + EXPR_NONE = 0 + EXPR_VALUE = EXPR_BEG + EXPR_BEG_ANY = EXPR_BEG | EXPR_MID | EXPR_CLASS + EXPR_ARG_ANY = EXPR_ARG | EXPR_CMDARG + EXPR_END_ANY = EXPR_END | EXPR_ENDARG | EXPR_ENDFN + + def self.lex_state_name(state) + LEX_STATE_NAMES.filter_map { |flag, name| name if state & flag != 0 }.join("|") + end + + # :startdoc: + # The source that is being parsed. attr_reader :source @@ -794,7 +832,7 @@ module Prism # foo(bar) # ^^^ def visit_arguments_node(node) - arguments, _ = visit_call_node_arguments(node, nil, false) + arguments, _, _ = visit_call_node_arguments(node, nil, false) arguments end @@ -1004,16 +1042,16 @@ module Prism case node.name when :[] receiver = visit(node.receiver) - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) call = on_aref(receiver, arguments) - if block.nil? - call - else + if has_ripper_block bounds(node.location) on_method_add_block(call, block) + else + call end when :[]= receiver = visit(node.receiver) @@ -1045,10 +1083,20 @@ module Prism bounds(node.location) on_unary(node.name, receiver) when :! - receiver = visit(node.receiver) + if node.message == "not" + receiver = + if !node.receiver.is_a?(ParenthesesNode) || !node.receiver.body.nil? + visit(node.receiver) + end - bounds(node.location) - on_unary(node.message == "not" ? :not : :!, receiver) + bounds(node.location) + on_unary(:not, receiver) + else + receiver = visit(node.receiver) + + bounds(node.location) + on_unary(:!, receiver) + end when *BINARY_OPERATORS receiver = visit(node.receiver) value = visit(node.arguments.arguments.first) @@ -1062,9 +1110,9 @@ module Prism if node.variable_call? on_vcall(message) else - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) call = - if node.opening_loc.nil? && arguments&.any? + if node.opening_loc.nil? && get_arguments_and_block(node.arguments, node.block).first.any? bounds(node.location) on_command(message, arguments) elsif !node.opening_loc.nil? @@ -1075,11 +1123,11 @@ module Prism on_method_add_arg(on_fcall(message), on_args_new) end - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end end @@ -1103,7 +1151,7 @@ module Prism bounds(node.location) on_assign(on_field(receiver, call_operator, message), value) else - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc || node.location)) call = if node.opening_loc.nil? bounds(node.location) @@ -1121,27 +1169,35 @@ module Prism on_method_add_arg(on_call(receiver, call_operator, message), arguments) end - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end end end - # Visit the arguments and block of a call node and return the arguments - # and block as they should be used. - private def visit_call_node_arguments(arguments_node, block_node, trailing_comma) + # Extract the arguments and block Ripper-style, which means if the block + # is like `&b` then it's moved to arguments. + private def get_arguments_and_block(arguments_node, block_node) arguments = arguments_node&.arguments || [] block = block_node if block.is_a?(BlockArgumentNode) - arguments << block + arguments += [block] block = nil end + [arguments, block] + end + + # Visit the arguments and block of a call node and return the arguments + # and block as they should be used. + private def visit_call_node_arguments(arguments_node, block_node, trailing_comma) + arguments, block = get_arguments_and_block(arguments_node, block_node) + [ if arguments.length == 1 && arguments.first.is_a?(ForwardingArgumentsNode) visit(arguments.first) @@ -1155,7 +1211,8 @@ module Prism on_args_add_block(args, false) end end, - visit(block) + visit(block), + block != nil, ] end @@ -1592,10 +1649,10 @@ module Prism end bounds(node.location) - if receiver.nil? - on_def(name, parameters, bodystmt) - else + if receiver on_defs(receiver, operator, name, parameters, bodystmt) + else + on_def(name, parameters, bodystmt) end end @@ -1605,8 +1662,23 @@ module Prism # defined?(a) # ^^^^^^^^^^^ def visit_defined_node(node) + expression = visit(node.value) + + # Very weird circumstances here where something like: + # + # defined? + # (1) + # + # gets parsed in Ruby as having only the `1` expression but in Ripper it + # gets parsed as having a parentheses node. In this case we need to + # synthesize that node to match Ripper's behavior. + if node.lparen_loc && node.keyword_loc.join(node.lparen_loc).slice.include?("\n") + bounds(node.lparen_loc.join(node.rparen_loc)) + expression = on_paren(on_stmts_add(on_stmts_new, expression)) + end + bounds(node.location) - on_defined(visit(node.value)) + on_defined(expression) end # if foo then bar else baz end @@ -1978,7 +2050,7 @@ module Prism # ^^^^^^^^^^^^^^^ def visit_index_operator_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -1995,7 +2067,7 @@ module Prism # ^^^^^^^^^^^^^^^^ def visit_index_and_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -2012,7 +2084,7 @@ module Prism # ^^^^^^^^^^^^^^^^ def visit_index_or_write_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) target = on_aref_field(receiver, arguments) @@ -2029,7 +2101,7 @@ module Prism # ^^^^^^^^ def visit_index_target_node(node) receiver = visit(node.receiver) - arguments, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) + arguments, _, _ = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.closing_loc)) bounds(node.location) on_aref_field(receiver, arguments) @@ -3059,7 +3131,7 @@ module Prism # super(foo) # ^^^^^^^^^^ def visit_super_node(node) - arguments, block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location)) + arguments, block, has_ripper_block = visit_call_node_arguments(node.arguments, node.block, trailing_comma?(node.arguments&.location || node.location, node.rparen_loc || node.location)) if !node.lparen_loc.nil? bounds(node.lparen_loc) @@ -3069,11 +3141,11 @@ module Prism bounds(node.location) call = on_super(arguments) - if block.nil? - call - else + if has_ripper_block bounds(node.block.location) on_method_add_block(call, block) + else + call end end @@ -3269,7 +3341,7 @@ module Prism # Lazily initialize the parse result. def result - @result ||= Prism.parse(source, partial_script: true) + @result ||= Prism.parse(source, partial_script: true, version: "current") end ########################################################################## @@ -3383,12 +3455,12 @@ module Prism # :stopdoc: def _dispatch_0; end - def _dispatch_1(_); end - def _dispatch_2(_, _); end - def _dispatch_3(_, _, _); end - def _dispatch_4(_, _, _, _); end - def _dispatch_5(_, _, _, _, _); end - def _dispatch_7(_, _, _, _, _, _, _); end + def _dispatch_1(arg); arg end + def _dispatch_2(arg, _); arg end + def _dispatch_3(arg, _, _); arg end + def _dispatch_4(arg, _, _, _); arg end + def _dispatch_5(arg, _, _, _, _); arg end + def _dispatch_7(arg, _, _, _, _, _, _); arg end # :startdoc: # diff --git a/lib/prism/translation/ripper/filter.rb b/lib/prism/translation/ripper/filter.rb new file mode 100644 index 0000000000..19deef2d37 --- /dev/null +++ b/lib/prism/translation/ripper/filter.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Prism + module Translation + class Ripper + class Filter # :nodoc: + # :stopdoc: + def initialize(src, filename = '-', lineno = 1) + @__lexer = Lexer.new(src, filename, lineno) + @__line = nil + @__col = nil + @__state = nil + end + + def filename + @__lexer.filename + end + + def lineno + @__line + end + + def column + @__col + end + + def state + @__state + end + + def parse(init = nil) + data = init + @__lexer.lex.each do |pos, event, tok, state| + @__line, @__col = *pos + @__state = state + data = if respond_to?(event, true) + then __send__(event, tok, data) + else on_default(event, tok, data) + end + end + data + end + + private + + def on_default(event, token, data) + data + end + # :startdoc: + end + end + end +end diff --git a/lib/prism/translation/ripper/lexer.rb b/lib/prism/translation/ripper/lexer.rb new file mode 100644 index 0000000000..bed863af08 --- /dev/null +++ b/lib/prism/translation/ripper/lexer.rb @@ -0,0 +1,135 @@ +# frozen_string_literal: true +# :markup: markdown + +require_relative "../ripper" + +module Prism + module Translation + class Ripper + class Lexer < Ripper # :nodoc: + # :stopdoc: + class State + + attr_reader :to_int, :to_s + + def initialize(i) + @to_int = i + @to_s = Ripper.lex_state_name(i) + freeze + end + + def [](index) + case index + when 0, :to_int + @to_int + when 1, :to_s + @to_s + else + nil + end + end + + alias to_i to_int + alias inspect to_s + def pretty_print(q) q.text(to_s) end + def ==(i) super or to_int == i end + def &(i) self.class.new(to_int & i) end + def |(i) self.class.new(to_int | i) end + def allbits?(i) to_int.allbits?(i) end + def anybits?(i) to_int.anybits?(i) end + def nobits?(i) to_int.nobits?(i) end + + # Instances are frozen and there are only a handful of them so we cache them here. + STATES = Hash.new { |h,k| h[k] = State.new(k) } + + def self.cached(i) + STATES[i] + end + end + + class Elem + attr_accessor :pos, :event, :tok, :state, :message + + def initialize(pos, event, tok, state, message = nil) + @pos = pos + @event = event + @tok = tok + @state = State.cached(state) + @message = message + end + + def [](index) + case index + when 0, :pos + @pos + when 1, :event + @event + when 2, :tok + @tok + when 3, :state + @state + when 4, :message + @message + else + nil + end + end + + def inspect + "#<#{self.class}: #{event}@#{pos[0]}:#{pos[1]}:#{state}: #{tok.inspect}#{": " if message}#{message}>" + end + + alias to_s inspect + + def pretty_print(q) + q.group(2, "#<#{self.class}:", ">") { + q.breakable + q.text("#{event}@#{pos[0]}:#{pos[1]}") + q.breakable + state.pretty_print(q) + q.breakable + q.text("token: ") + tok.pretty_print(q) + if message + q.breakable + q.text("message: ") + q.text(message) + end + } + end + + def to_a + if @message + [@pos, @event, @tok, @state, @message] + else + [@pos, @event, @tok, @state] + end + end + end + + # Pretty much just the same as Prism.lex_compat. + def lex(raise_errors: false) + Ripper.lex(@source, filename, lineno, raise_errors: raise_errors) + end + + # Returns the lex_compat result wrapped in `Elem`. Errors are omitted. + # Since ripper is a streaming parser, tokens are expected to be emitted in the order + # that the parser encounters them. This is not implemented. + def parse(...) + lex(...).map do |position, event, token, state| + Elem.new(position, event, token, state.to_int) + end + end + + # Similar to parse but ripper sorts the elements by position in the source. Also + # includes errors. Since prism does error recovery, in cases of syntax errors + # the result may differ greatly compared to ripper. + def scan(...) + parse(...) + end + + # :startdoc: + end + end + end +end diff --git a/lib/prism/translation/ripper/sexp.rb b/lib/prism/translation/ripper/sexp.rb index dc26a639a3..8cfefc8472 100644 --- a/lib/prism/translation/ripper/sexp.rb +++ b/lib/prism/translation/ripper/sexp.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# :markup: markdown require_relative "../ripper" diff --git a/lib/prism/translation/ruby_parser.rb b/lib/prism/translation/ruby_parser.rb index 189038d008..c026c4ad9c 100644 --- a/lib/prism/translation/ruby_parser.rb +++ b/lib/prism/translation/ruby_parser.rb @@ -1,12 +1,18 @@ # frozen_string_literal: true +# :markup: markdown begin - require "ruby_parser" + require "sexp" rescue LoadError - warn(%q{Error: Unable to load ruby_parser. Add `gem "ruby_parser"` to your Gemfile.}) + warn(%q{Error: Unable to load sexp. Add `gem "sexp_processor"` to your Gemfile.}) exit(1) end +class RubyParser # :nodoc: + class SyntaxError < RuntimeError # :nodoc: + end +end + module Prism module Translation # This module is the entry-point for converting a prism syntax tree into the @@ -15,7 +21,7 @@ module Prism # A prism visitor that builds Sexp objects. class Compiler < ::Prism::Compiler # This is the name of the file that we are compiling. We set it on every - # Sexp object that is generated, and also use it to compile __FILE__ + # Sexp object that is generated, and also use it to compile `__FILE__` # nodes. attr_reader :file @@ -34,26 +40,34 @@ module Prism @in_pattern = in_pattern end + # ``` # alias foo bar # ^^^^^^^^^^^^^ + # ``` def visit_alias_method_node(node) s(node, :alias, visit(node.new_name), visit(node.old_name)) end + # ``` # alias $foo $bar # ^^^^^^^^^^^^^^^ + # ``` def visit_alias_global_variable_node(node) s(node, :valias, node.new_name.name, node.old_name.name) end + # ``` # foo => bar | baz # ^^^^^^^^^ + # ``` def visit_alternation_pattern_node(node) s(node, :or, visit(node.left), visit(node.right)) end + # ``` # a and b # ^^^^^^^ + # ``` def visit_and_node(node) left = visit(node.left) @@ -70,8 +84,10 @@ module Prism end end + # ``` # [] # ^^ + # ``` def visit_array_node(node) if in_pattern s(node, :array_pat, nil).concat(visit_all(node.elements)) @@ -80,8 +96,10 @@ module Prism end end + # ``` # foo => [bar] # ^^^^^ + # ``` def visit_array_pattern_node(node) if node.constant.nil? && node.requireds.empty? && node.rest.nil? && node.posts.empty? s(node, :array_pat) @@ -103,23 +121,29 @@ module Prism end end + # ``` # foo(bar) # ^^^ + # ``` def visit_arguments_node(node) raise "Cannot visit arguments directly" end + # ``` # { a: 1 } # ^^^^ + # ``` def visit_assoc_node(node) [visit(node.key), visit(node.value)] end + # ``` # def foo(**); bar(**); end # ^^ # # { **foo } # ^^^^^ + # ``` def visit_assoc_splat_node(node) if node.value.nil? [s(node, :kwsplat)] @@ -128,14 +152,18 @@ module Prism end end + # ``` # $+ # ^^ + # ``` def visit_back_reference_read_node(node) - s(node, :back_ref, node.name.name.delete_prefix("$").to_sym) + s(node, :back_ref, node.name.to_s.delete_prefix("$").to_sym) end + # ``` # begin end # ^^^^^^^^^ + # ``` def visit_begin_node(node) result = node.statements.nil? ? s(node, :nil) : visit(node.statements) @@ -167,16 +195,20 @@ module Prism result end + # ``` # foo(&bar) # ^^^^ + # ``` def visit_block_argument_node(node) s(node, :block_pass).tap do |result| result << visit(node.expression) unless node.expression.nil? end end + # ``` # foo { |; bar| } # ^^^ + # ``` def visit_block_local_variable_node(node) node.name end @@ -186,8 +218,10 @@ module Prism s(node, :block_pass, visit(node.expression)) end + # ``` # def foo(&bar); end # ^^^^ + # ``` def visit_block_parameter_node(node) :"&#{node.name}" end @@ -228,11 +262,13 @@ module Prism result end + # ``` # break # ^^^^^ # # break foo # ^^^^^^^^^ + # ``` def visit_break_node(node) if node.arguments.nil? s(node, :break) @@ -243,6 +279,7 @@ module Prism end end + # ``` # foo # ^^^ # @@ -251,6 +288,7 @@ module Prism # # foo.bar() {} # ^^^^^^^^^^^^ + # ``` def visit_call_node(node) case node.name when :!~ @@ -289,8 +327,10 @@ module Prism visit_block(node, result, block) end + # ``` # foo.bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_operator_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, node.binary_operator) @@ -299,8 +339,10 @@ module Prism end end + # ``` # foo.bar &&= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_and_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"&&") @@ -309,8 +351,10 @@ module Prism end end + # ``` # foo.bar ||= baz # ^^^^^^^^^^^^^^^ + # ``` def visit_call_or_write_node(node) if op_asgn?(node) s(node, op_asgn_type(node, :op_asgn), visit(node.receiver), visit_write_value(node.value), node.read_name, :"||") @@ -332,32 +376,42 @@ module Prism node.safe_navigation? ? :"safe_#{type}" : type end + # ``` # foo.bar, = 1 # ^^^^^^^ + # ``` def visit_call_target_node(node) s(node, :attrasgn, visit(node.receiver), node.name) end + # ``` # foo => bar => baz # ^^^^^^^^^^ + # ``` def visit_capture_pattern_node(node) visit(node.target) << visit(node.value) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_case_match_node(node) s(node, :case, visit(node.predicate)).concat(visit_all(node.conditions)) << visit(node.else_clause) end + # ``` # class Foo; end # ^^^^^^^^^^^^^^ + # ``` def visit_class_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -366,51 +420,67 @@ module Prism visit(node.constant_path) end - if node.body.nil? - s(node, :class, name, visit(node.superclass)) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :class, name, visit(node.superclass)) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :class, name, visit(node.superclass)).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :class, name, visit(node.superclass), node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end + # ``` # @@foo # ^^^^^ + # ``` def visit_class_variable_read_node(node) s(node, :cvar, node.name) end + # ``` # @@foo = 1 # ^^^^^^^^^ # # @@foo, @@bar = 1 # ^^^^^ ^^^^^ + # ``` def visit_class_variable_write_node(node) s(node, class_variable_write_type, node.name, visit_write_value(node.value)) end + # ``` # @@foo += bar # ^^^^^^^^^^^^ + # ``` def visit_class_variable_operator_write_node(node) s(node, class_variable_write_type, node.name, s(node, :call, s(node, :cvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @@foo &&= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo ||= bar # ^^^^^^^^^^^^^ + # ``` def visit_class_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :cvar, node.name), s(node, class_variable_write_type, node.name, visit_write_value(node.value))) end + # ``` # @@foo, = bar # ^^^^^ + # ``` def visit_class_variable_target_node(node) s(node, class_variable_write_type, node.name) end @@ -421,47 +491,61 @@ module Prism in_def ? :cvasgn : :cvdecl end + # ``` # Foo # ^^^ + # ``` def visit_constant_read_node(node) s(node, :const, node.name) end + # ``` # Foo = 1 # ^^^^^^^ # # Foo, Bar = 1 # ^^^ ^^^ + # ``` def visit_constant_write_node(node) s(node, :cdecl, node.name, visit_write_value(node.value)) end + # ``` # Foo += bar # ^^^^^^^^^^^ + # ``` def visit_constant_operator_write_node(node) s(node, :cdecl, node.name, s(node, :call, s(node, :const, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # Foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_and_write_node(node) s(node, :op_asgn_and, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_constant_or_write_node(node) s(node, :op_asgn_or, s(node, :const, node.name), s(node, :cdecl, node.name, visit(node.value))) end + # ``` # Foo, = bar # ^^^ + # ``` def visit_constant_target_node(node) s(node, :cdecl, node.name) end + # ``` # Foo::Bar # ^^^^^^^^ + # ``` def visit_constant_path_node(node) if node.parent.nil? s(node, :colon3, node.name) @@ -470,35 +554,45 @@ module Prism end end + # ``` # Foo::Bar = 1 # ^^^^^^^^^^^^ # # Foo::Foo, Bar::Bar = 1 # ^^^^^^^^ ^^^^^^^^ + # ``` def visit_constant_path_write_node(node) s(node, :cdecl, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_operator_write_node(node) s(node, :op_asgn, visit(node.target), node.binary_operator, visit_write_value(node.value)) end + # ``` # Foo::Bar &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_and_write_node(node) s(node, :op_asgn_and, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_constant_path_or_write_node(node) s(node, :op_asgn_or, visit(node.target), visit_write_value(node.value)) end + # ``` # Foo::Bar, = baz # ^^^^^^^^ + # ``` def visit_constant_path_target_node(node) inner = if node.parent.nil? @@ -510,11 +604,13 @@ module Prism s(node, :const, inner) end + # ``` # def foo; end # ^^^^^^^^^^^^ # # def self.foo; end # ^^^^^^^^^^^^^^^^^ + # ``` def visit_def_node(node) name = node.name_loc.slice.to_sym result = @@ -524,7 +620,9 @@ module Prism s(node, :defs, visit(node.receiver), name) end + attach_comments(result, node) result.line(node.name_loc.start_line) + if node.parameters.nil? result << s(node, :args).line(node.name_loc.start_line) else @@ -541,55 +639,71 @@ module Prism end end + # ``` # defined? a # ^^^^^^^^^^ # # defined?(a) # ^^^^^^^^^^^ + # ``` def visit_defined_node(node) s(node, :defined, visit(node.value)) end + # ``` # if foo then bar else baz end # ^^^^^^^^^^^^ + # ``` def visit_else_node(node) visit(node.statements) end + # ``` # "foo #{bar}" # ^^^^^^ + # ``` def visit_embedded_statements_node(node) result = s(node, :evstr) result << visit(node.statements) unless node.statements.nil? result end + # ``` # "foo #@bar" # ^^^^^ + # ``` def visit_embedded_variable_node(node) s(node, :evstr, visit(node.variable)) end + # ``` # begin; foo; ensure; bar; end # ^^^^^^^^^^^^ + # ``` def visit_ensure_node(node) node.statements.nil? ? s(node, :nil) : visit(node.statements) end + # ``` # false # ^^^^^ + # ``` def visit_false_node(node) s(node, :false) end + # ``` # foo => [*, bar, *] # ^^^^^^^^^^^ + # ``` def visit_find_pattern_node(node) s(node, :find_pat, visit_pattern_constant(node.constant), :"*#{node.left.expression&.name}", *visit_all(node.requireds), :"*#{node.right.expression&.name}") end + # ``` # if foo .. bar; end # ^^^^^^^^^^ + # ``` def visit_flip_flop_node(node) if node.left.is_a?(IntegerNode) && node.right.is_a?(IntegerNode) s(node, :lit, Range.new(node.left.value, node.right.value, node.exclude_end?)) @@ -598,86 +712,112 @@ module Prism end end + # ``` # 1.0 # ^^^ + # ``` def visit_float_node(node) s(node, :lit, node.value) end + # ``` # for foo in bar do end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_for_node(node) s(node, :for, visit(node.collection), visit(node.index), visit(node.statements)) end + # ``` # def foo(...); bar(...); end # ^^^ + # ``` def visit_forwarding_arguments_node(node) s(node, :forward_args) end + # ``` # def foo(...); end # ^^^ + # ``` def visit_forwarding_parameter_node(node) s(node, :forward_args) end + # ``` # super # ^^^^^ # # super {} # ^^^^^^^^ + # ``` def visit_forwarding_super_node(node) visit_block(node, s(node, :zsuper), node.block) end + # ``` # $foo # ^^^^ + # ``` def visit_global_variable_read_node(node) s(node, :gvar, node.name) end + # ``` # $foo = 1 # ^^^^^^^^ # # $foo, $bar = 1 # ^^^^ ^^^^ + # ``` def visit_global_variable_write_node(node) s(node, :gasgn, node.name, visit_write_value(node.value)) end + # ``` # $foo += bar # ^^^^^^^^^^^ + # ``` def visit_global_variable_operator_write_node(node) s(node, :gasgn, node.name, s(node, :call, s(node, :gvar, node.name), node.binary_operator, visit(node.value))) end + # ``` # $foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_global_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :gvar, node.name), s(node, :gasgn, node.name, visit_write_value(node.value))) end + # ``` # $foo, = bar # ^^^^ + # ``` def visit_global_variable_target_node(node) s(node, :gasgn, node.name) end + # ``` # {} # ^^ + # ``` def visit_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # foo => {} # ^^ + # ``` def visit_hash_pattern_node(node) result = s(node, :hash_pat, visit_pattern_constant(node.constant)).concat(node.elements.flat_map { |element| visit(element) }) @@ -691,6 +831,7 @@ module Prism result end + # ``` # if foo then bar end # ^^^^^^^^^^^^^^^^^^^ # @@ -699,6 +840,7 @@ module Prism # # foo ? bar : baz # ^^^^^^^^^^^^^^^ + # ``` def visit_if_node(node) s(node, :if, visit(node.predicate), visit(node.statements), visit(node.subsequent)) end @@ -708,18 +850,24 @@ module Prism s(node, :lit, node.value) end + # ``` # { foo: } # ^^^^ + # ``` def visit_implicit_node(node) end + # ``` # foo { |bar,| } # ^ + # ``` def visit_implicit_rest_node(node) end + # ``` # case foo; in bar; end # ^^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_in_node(node) pattern = if node.pattern.is_a?(ConstantPathNode) @@ -731,8 +879,10 @@ module Prism s(node, :in, pattern).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # foo[bar] += baz # ^^^^^^^^^^^^^^^ + # ``` def visit_index_operator_write_node(node) arglist = nil @@ -744,8 +894,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, node.binary_operator, visit_write_value(node.value)) end + # ``` # foo[bar] &&= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_and_write_node(node) arglist = nil @@ -757,8 +909,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"&&", visit_write_value(node.value)) end + # ``` # foo[bar] ||= baz # ^^^^^^^^^^^^^^^^ + # ``` def visit_index_or_write_node(node) arglist = nil @@ -770,8 +924,10 @@ module Prism s(node, :op_asgn1, visit(node.receiver), arglist, :"||", visit_write_value(node.value)) end + # ``` # foo[bar], = 1 # ^^^^^^^^ + # ``` def visit_index_target_node(node) arguments = visit_all(node.arguments&.arguments || []) arguments << visit(node.block) unless node.block.nil? @@ -779,53 +935,69 @@ module Prism s(node, :attrasgn, visit(node.receiver), :[]=).concat(arguments) end + # ``` # @foo # ^^^^ + # ``` def visit_instance_variable_read_node(node) s(node, :ivar, node.name) end + # ``` # @foo = 1 # ^^^^^^^^ # # @foo, @bar = 1 # ^^^^ ^^^^ + # ``` def visit_instance_variable_write_node(node) s(node, :iasgn, node.name, visit_write_value(node.value)) end + # ``` # @foo += bar # ^^^^^^^^^^^ + # ``` def visit_instance_variable_operator_write_node(node) s(node, :iasgn, node.name, s(node, :call, s(node, :ivar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # @foo &&= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo ||= bar # ^^^^^^^^^^^^ + # ``` def visit_instance_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :ivar, node.name), s(node, :iasgn, node.name, visit(node.value))) end + # ``` # @foo, = bar # ^^^^ + # ``` def visit_instance_variable_target_node(node) s(node, :iasgn, node.name) end + # ``` # 1 # ^ + # ``` def visit_integer_node(node) s(node, :lit, node.value) end + # ``` # if /foo #{bar}/ then end # ^^^^^^^^^^^^ + # ``` def visit_interpolated_match_last_line_node(node) parts = visit_interpolated_parts(node.parts) regexp = @@ -841,8 +1013,10 @@ module Prism s(node, :match, regexp) end + # ``` # /foo #{bar}/ # ^^^^^^^^^^^^ + # ``` def visit_interpolated_regular_expression_node(node) parts = visit_interpolated_parts(node.parts) @@ -856,22 +1030,28 @@ module Prism end end + # ``` # "foo #{bar}" # ^^^^^^^^^^^^ + # ``` def visit_interpolated_string_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :str, parts.first) : s(node, :dstr).concat(parts) end + # ``` # :"foo #{bar}" # ^^^^^^^^^^^^^ + # ``` def visit_interpolated_symbol_node(node) parts = visit_interpolated_parts(node.parts) parts.length == 1 ? s(node, :lit, parts.first.to_sym) : s(node, :dsym).concat(parts) end + # ``` # `foo #{bar}` # ^^^^^^^^^^^^ + # ``` def visit_interpolated_x_string_node(node) source = node.heredoc? ? node.parts.first : node parts = visit_interpolated_parts(node.parts) @@ -881,6 +1061,7 @@ module Prism # Visit the interpolated content of the string-like node. private def visit_interpolated_parts(parts) visited = [] + parts.each do |part| result = visit(part) @@ -892,6 +1073,7 @@ module Prism else visited << result end + visited << :space elsif result[0] == :dstr if !visited.empty? && part.parts[0].is_a?(StringNode) # If we are in the middle of an implicitly concatenated string, @@ -907,8 +1089,9 @@ module Prism end state = :beginning #: :beginning | :string_content | :interpolated_content + results = [] - visited.each_with_object([]) do |result, results| + visited.each_with_index do |result, index| case state when :beginning if result.is_a?(String) @@ -923,42 +1106,54 @@ module Prism state = :interpolated_content end when :string_content - if result.is_a?(String) - results[0] << result + if result == :space + # continue + elsif result.is_a?(String) + results[0] = "#{results[0]}#{result}" elsif result.is_a?(Array) && result[0] == :str - results[0] << result[1] + results[0] = "#{results[0]}#{result[1]}" else results << result state = :interpolated_content end when :interpolated_content - if result.is_a?(Array) && result[0] == :str && results[-1][0] == :str && (results[-1].line_max == result.line) - results[-1][1] << result[1] + if result == :space + # continue + elsif visited[index - 1] != :space && result.is_a?(Array) && result[0] == :str && results[-1][0] == :str && (results[-1].line_max == result.line) + results[-1][1] = "#{results[-1][1]}#{result[1]}" results[-1].line_max = result.line_max else results << result end end end + + results end + # ``` # -> { it } # ^^ + # ``` def visit_it_local_variable_read_node(node) s(node, :call, nil, :it) end + # ``` # foo(bar: baz) # ^^^^^^^^ + # ``` def visit_keyword_hash_node(node) s(node, :hash).concat(node.elements.flat_map { |element| visit(element) }) end + # ``` # def foo(**bar); end # ^^^^^ # # def foo(**); end # ^^ + # ``` def visit_keyword_rest_parameter_node(node) :"**#{node.name}" end @@ -967,8 +1162,8 @@ module Prism def visit_lambda_node(node) parameters = case node.parameters - when nil, NumberedParametersNode - s(node, :args) + when nil, ItParametersNode, NumberedParametersNode + 0 else visit(node.parameters) end @@ -980,8 +1175,10 @@ module Prism end end + # ``` # foo # ^^^ + # ``` def visit_local_variable_read_node(node) if node.name.match?(/^_\d$/) s(node, :call, nil, node.name) @@ -990,59 +1187,77 @@ module Prism end end + # ``` # foo = 1 # ^^^^^^^ # # foo, bar = 1 # ^^^ ^^^ + # ``` def visit_local_variable_write_node(node) s(node, :lasgn, node.name, visit_write_value(node.value)) end + # ``` # foo += bar # ^^^^^^^^^^ + # ``` def visit_local_variable_operator_write_node(node) s(node, :lasgn, node.name, s(node, :call, s(node, :lvar, node.name), node.binary_operator, visit_write_value(node.value))) end + # ``` # foo &&= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_and_write_node(node) s(node, :op_asgn_and, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo ||= bar # ^^^^^^^^^^^ + # ``` def visit_local_variable_or_write_node(node) s(node, :op_asgn_or, s(node, :lvar, node.name), s(node, :lasgn, node.name, visit_write_value(node.value))) end + # ``` # foo, = bar # ^^^ + # ``` def visit_local_variable_target_node(node) s(node, :lasgn, node.name) end + # ``` # if /foo/ then end # ^^^^^ + # ``` def visit_match_last_line_node(node) s(node, :match, s(node, :lit, Regexp.new(node.unescaped, node.options))) end + # ``` # foo in bar # ^^^^^^^^^^ + # ``` def visit_match_predicate_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # foo => bar # ^^^^^^^^^^ + # ``` def visit_match_required_node(node) s(node, :case, visit(node.value), s(node, :in, node.pattern.accept(copy_compiler(in_pattern: true)), nil), nil) end + # ``` # /(?<foo>foo)/ =~ bar # ^^^^^^^^^^^^^^^^^^^^ + # ``` def visit_match_write_node(node) s(node, :match2, visit(node.call.receiver), visit(node.call.arguments.arguments.first)) end @@ -1054,8 +1269,10 @@ module Prism raise "Cannot visit missing node directly" end + # ``` # module Foo; end # ^^^^^^^^^^^^^^^ + # ``` def visit_module_node(node) name = if node.constant_path.is_a?(ConstantReadNode) @@ -1064,18 +1281,24 @@ module Prism visit(node.constant_path) end - if node.body.nil? - s(node, :module, name) - elsif node.body.is_a?(StatementsNode) - compiler = copy_compiler(in_def: false) - s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) - else - s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) - end + result = + if node.body.nil? + s(node, :module, name) + elsif node.body.is_a?(StatementsNode) + compiler = copy_compiler(in_def: false) + s(node, :module, name).concat(node.body.body.map { |child| child.accept(compiler) }) + else + s(node, :module, name, node.body.accept(copy_compiler(in_def: false))) + end + + attach_comments(result, node) + result end + # ``` # foo, bar = baz # ^^^^^^^^ + # ``` def visit_multi_target_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1084,8 +1307,10 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets))) end + # ``` # foo, bar = baz # ^^^^^^^^^^^^^^ + # ``` def visit_multi_write_node(node) targets = [*node.lefts] targets << node.rest if !node.rest.nil? && !node.rest.is_a?(ImplicitRestNode) @@ -1105,11 +1330,13 @@ module Prism s(node, :masgn, s(node, :array).concat(visit_all(targets)), value) end + # ``` # next # ^^^^ # # next foo # ^^^^^^^^ + # ``` def visit_next_node(node) if node.arguments.nil? s(node, :next) @@ -1121,44 +1348,58 @@ module Prism end end + # ``` # nil # ^^^ + # ``` def visit_nil_node(node) s(node, :nil) end + # ``` # def foo(**nil); end # ^^^^^ + # ``` def visit_no_keywords_parameter_node(node) in_pattern ? s(node, :kwrest, :"**nil") : :"**nil" end + # ``` # -> { _1 + _2 } # ^^^^^^^^^^^^^^ + # ``` def visit_numbered_parameters_node(node) raise "Cannot visit numbered parameters directly" end + # ``` # $1 # ^^ + # ``` def visit_numbered_reference_read_node(node) s(node, :nth_ref, node.number) end + # ``` # def foo(bar: baz); end # ^^^^^^^^ + # ``` def visit_optional_keyword_parameter_node(node) s(node, :kwarg, node.name, visit(node.value)) end + # ``` # def foo(bar = 1); end # ^^^^^^^ + # ``` def visit_optional_parameter_node(node) s(node, :lasgn, node.name, visit(node.value)) end + # ``` # a or b # ^^^^^^ + # ``` def visit_or_node(node) left = visit(node.left) @@ -1175,11 +1416,13 @@ module Prism end end + # ``` # def foo(bar, *baz); end # ^^^^^^^^^ + # ``` def visit_parameters_node(node) children = - node.compact_child_nodes.map do |element| + node.each_child_node.map do |element| if element.is_a?(MultiTargetNode) visit_destructured_parameter(element) else @@ -1190,8 +1433,10 @@ module Prism s(node, :args).concat(children) end + # ``` # def foo((bar, baz)); end # ^^^^^^^^^^ + # ``` private def visit_destructured_parameter(node) children = [*node.lefts, *node.rest, *node.rights].map do |child| @@ -1210,11 +1455,13 @@ module Prism s(node, :masgn).concat(children) end + # ``` # () # ^^ # # (1) # ^^^ + # ``` def visit_parentheses_node(node) if node.body.nil? s(node, :nil) @@ -1223,14 +1470,18 @@ module Prism end end + # ``` # foo => ^(bar) # ^^^^^^ + # ``` def visit_pinned_expression_node(node) node.expression.accept(copy_compiler(in_pattern: false)) end + # ``` # foo = 1 and bar => ^foo # ^^^^ + # ``` def visit_pinned_variable_node(node) if node.variable.is_a?(LocalVariableReadNode) && node.variable.name.match?(/^_\d$/) s(node, :lvar, node.variable.name) @@ -1254,8 +1505,10 @@ module Prism visit(node.statements) end + # ``` # 0..5 # ^^^^ + # ``` def visit_range_node(node) if !in_pattern && !node.left.nil? && !node.right.nil? && ([node.left.type, node.right.type] - %i[nil_node integer_node]).empty? left = node.left.value if node.left.is_a?(IntegerNode) @@ -1276,44 +1529,58 @@ module Prism end end + # ``` # 1r # ^^ + # ``` def visit_rational_node(node) s(node, :lit, node.value) end + # ``` # redo # ^^^^ + # ``` def visit_redo_node(node) s(node, :redo) end + # ``` # /foo/ # ^^^^^ + # ``` def visit_regular_expression_node(node) s(node, :lit, Regexp.new(node.unescaped, node.options)) end + # ``` # def foo(bar:); end # ^^^^ + # ``` def visit_required_keyword_parameter_node(node) s(node, :kwarg, node.name) end + # ``` # def foo(bar); end # ^^^ + # ``` def visit_required_parameter_node(node) node.name end + # ``` # foo rescue bar # ^^^^^^^^^^^^^^ + # ``` def visit_rescue_modifier_node(node) s(node, :rescue, visit(node.expression), s(node.rescue_expression, :resbody, s(node.rescue_expression, :array), visit(node.rescue_expression))) end + # ``` # begin; rescue; end # ^^^^^^^ + # ``` def visit_rescue_node(node) exceptions = if node.exceptions.length == 1 && node.exceptions.first.is_a?(SplatNode) @@ -1329,26 +1596,32 @@ module Prism s(node, :resbody, exceptions).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # def foo(*bar); end # ^^^^ # # def foo(*); end # ^ + # ``` def visit_rest_parameter_node(node) :"*#{node.name}" end + # ``` # retry # ^^^^^ + # ``` def visit_retry_node(node) s(node, :retry) end + # ``` # return # ^^^^^^ # # return 1 # ^^^^^^^^ + # ``` def visit_return_node(node) if node.arguments.nil? s(node, :return) @@ -1360,8 +1633,10 @@ module Prism end end + # ``` # self # ^^^^ + # ``` def visit_self_node(node) s(node, :self) end @@ -1371,33 +1646,42 @@ module Prism visit(node.write) end + # ``` # class << self; end # ^^^^^^^^^^^^^^^^^^ + # ``` def visit_singleton_class_node(node) s(node, :sclass, visit(node.expression)).tap do |sexp| sexp << node.body.accept(copy_compiler(in_def: false)) unless node.body.nil? end end + # ``` # __ENCODING__ # ^^^^^^^^^^^^ + # ``` def visit_source_encoding_node(node) # TODO s(node, :colon2, s(node, :const, :Encoding), :UTF_8) end + # ``` # __FILE__ # ^^^^^^^^ + # ``` def visit_source_file_node(node) s(node, :str, node.filepath) end + # ``` # __LINE__ # ^^^^^^^^ + # ``` def visit_source_line_node(node) s(node, :lit, node.location.start_line) end + # ``` # foo(*bar) # ^^^^ # @@ -1406,6 +1690,7 @@ module Prism # # def foo(*); bar(*); end # ^ + # ``` def visit_splat_node(node) if node.expression.nil? s(node, :splat) @@ -1425,20 +1710,25 @@ module Prism end end + # ``` # "foo" # ^^^^^ + # ``` def visit_string_node(node) unescaped = node.unescaped if node.forced_binary_encoding? + unescaped = unescaped.dup unescaped.force_encoding(Encoding::BINARY) end s(node, :str, unescaped) end + # ``` # super(foo) # ^^^^^^^^^^ + # ``` def visit_super_node(node) arguments = node.arguments&.arguments || [] block = node.block @@ -1451,60 +1741,76 @@ module Prism visit_block(node, s(node, :super).concat(visit_all(arguments)), block) end + # ``` # :foo # ^^^^ + # ``` def visit_symbol_node(node) node.value == "!@" ? s(node, :lit, :"!@") : s(node, :lit, node.unescaped.to_sym) end + # ``` # true # ^^^^ + # ``` def visit_true_node(node) s(node, :true) end + # ``` # undef foo # ^^^^^^^^^ + # ``` def visit_undef_node(node) names = node.names.map { |name| s(node, :undef, visit(name)) } names.length == 1 ? names.first : s(node, :block).concat(names) end + # ``` # unless foo; bar end # ^^^^^^^^^^^^^^^^^^^ # # bar unless foo # ^^^^^^^^^^^^^^ + # ``` def visit_unless_node(node) s(node, :if, visit(node.predicate), visit(node.else_clause), visit(node.statements)) end + # ``` # until foo; bar end # ^^^^^^^^^^^^^^^^^ # # bar until foo # ^^^^^^^^^^^^^ + # ``` def visit_until_node(node) s(node, :until, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # case foo; when bar; end # ^^^^^^^^^^^^^ + # ``` def visit_when_node(node) s(node, :when, s(node, :array).concat(visit_all(node.conditions))).concat(node.statements.nil? ? [nil] : visit_all(node.statements.body)) end + # ``` # while foo; bar end # ^^^^^^^^^^^^^^^^^^ # # bar while foo # ^^^^^^^^^^^^^ + # ``` def visit_while_node(node) s(node, :while, visit(node.predicate), visit(node.statements), !node.begin_modifier?) end + # ``` # `foo` # ^^^^^ + # ``` def visit_x_string_node(node) result = s(node, :xstr, node.unescaped) @@ -1516,17 +1822,30 @@ module Prism result end + # ``` # yield # ^^^^^ # # yield 1 # ^^^^^^^ + # ``` def visit_yield_node(node) s(node, :yield).concat(visit_all(node.arguments&.arguments || [])) end private + # Attach prism comments to the given sexp. + def attach_comments(sexp, node) + return unless node.comments + return if node.comments.empty? + + extra = node.location.start_line - node.comments.last.location.start_line + comments = node.comments.map(&:slice) + comments.concat([nil] * [0, extra].max) + sexp.comments = comments.join("\n") + end + # Create a new compiler with the given options. def copy_compiler(in_def: self.in_def, in_pattern: self.in_pattern) Compiler.new(file, in_def: in_def, in_pattern: in_pattern) @@ -1549,7 +1868,7 @@ module Prism else parameters = case block.parameters - when nil, NumberedParametersNode + when nil, ItParametersNode, NumberedParametersNode 0 else visit(block.parameters) @@ -1605,6 +1924,14 @@ module Prism translate(Prism.parse_file(filepath, partial_script: true), filepath) end + # Parse the give file and translate it into the + # seattlerb/ruby_parser gem's Sexp format. This method is + # provided for API compatibility to RubyParser and takes an + # optional +timeout+ argument. + def process(ruby, file = "(string)", timeout = nil) + Timeout.timeout(timeout) { parse(ruby, file) } + end + class << self # Parse the given source and translate it into the seattlerb/ruby_parser # gem's Sexp format. @@ -1629,6 +1956,7 @@ module Prism raise ::RubyParser::SyntaxError, "#{filepath}:#{error.location.start_line} :: #{error.message}" end + result.attach_comments! result.value.accept(Compiler.new(filepath)) end end diff --git a/lib/pstore.gemspec b/lib/pstore.gemspec deleted file mode 100644 index 86051d2f43..0000000000 --- a/lib/pstore.gemspec +++ /dev/null @@ -1,32 +0,0 @@ -# frozen_string_literal: true - -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, ".").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Yukihiro Matsumoto"] - spec.email = ["matz@ruby-lang.org"] - - spec.summary = %q{Transactional File Storage for Ruby Objects} - spec.description = spec.summary - spec.homepage = "https://github.com/ruby/pstore" - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = "https://github.com/ruby/pstore" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] -end diff --git a/lib/pstore.rb b/lib/pstore.rb deleted file mode 100644 index 57ecb0ef5c..0000000000 --- a/lib/pstore.rb +++ /dev/null @@ -1,731 +0,0 @@ -# frozen_string_literal: true -# = PStore -- Transactional File Storage for Ruby Objects -# -# pstore.rb - -# originally by matz -# documentation by Kev Jackson and James Edward Gray II -# improved by Hongli Lai -# -# See PStore for documentation. - -require "digest" - -# \PStore implements a file based persistence mechanism based on a Hash. -# User code can store hierarchies of Ruby objects (values) -# into the data store by name (keys). -# An object hierarchy may be just a single object. -# User code may later read values back from the data store -# or even update data, as needed. -# -# The transactional behavior ensures that any changes succeed or fail together. -# This can be used to ensure that the data store is not left in a transitory state, -# where some values were updated but others were not. -# -# Behind the scenes, Ruby objects are stored to the data store file with Marshal. -# That carries the usual limitations. Proc objects cannot be marshalled, -# for example. -# -# There are three important concepts here (details at the links): -# -# - {Store}[rdoc-ref:PStore@The+Store]: a store is an instance of \PStore. -# - {Entries}[rdoc-ref:PStore@Entries]: the store is hash-like; -# each entry is the key for a stored object. -# - {Transactions}[rdoc-ref:PStore@Transactions]: each transaction is a collection -# of prospective changes to the store; -# a transaction is defined in the block given with a call -# to PStore#transaction. -# -# == About the Examples -# -# Examples on this page need a store that has known properties. -# They can get a new (and populated) store by calling thus: -# -# example_store do |store| -# # Example code using store goes here. -# end -# -# All we really need to know about +example_store+ -# is that it yields a fresh store with a known population of entries; -# its implementation: -# -# require 'pstore' -# require 'tempfile' -# # Yield a pristine store for use in examples. -# def example_store -# # Create the store in a temporary file. -# Tempfile.create do |file| -# store = PStore.new(file) -# # Populate the store. -# store.transaction do -# store[:foo] = 0 -# store[:bar] = 1 -# store[:baz] = 2 -# end -# yield store -# end -# end -# -# == The Store -# -# The contents of the store are maintained in a file whose path is specified -# when the store is created (see PStore.new). -# The objects are stored and retrieved using -# module Marshal, which means that certain objects cannot be added to the store; -# see {Marshal::dump}[rdoc-ref:Marshal.dump]. -# -# == Entries -# -# A store may have any number of entries. -# Each entry has a key and a value, just as in a hash: -# -# - Key: as in a hash, the key can be (almost) any object; -# see {Hash Keys}[rdoc-ref:Hash@Hash+Keys]. -# You may find it convenient to keep it simple by using only -# symbols or strings as keys. -# - Value: the value may be any object that can be marshalled by \Marshal -# (see {Marshal::dump}[rdoc-ref:Marshal.dump]) -# and in fact may be a collection -# (e.g., an array, a hash, a set, a range, etc). -# That collection may in turn contain nested objects, -# including collections, to any depth; -# those objects must also be \Marshal-able. -# See {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values]. -# -# == Transactions -# -# === The Transaction Block -# -# The block given with a call to method #transaction# -# contains a _transaction_, -# which consists of calls to \PStore methods that -# read from or write to the store -# (that is, all \PStore methods except #transaction itself, -# #path, and Pstore.new): -# -# example_store do |store| -# store.transaction do -# store.keys # => [:foo, :bar, :baz] -# store[:bat] = 3 -# store.keys # => [:foo, :bar, :baz, :bat] -# end -# end -# -# Execution of the transaction is deferred until the block exits, -# and is executed _atomically_ (all-or-nothing): -# either all transaction calls are executed, or none are. -# This maintains the integrity of the store. -# -# Other code in the block (including even calls to #path and PStore.new) -# is executed immediately, not deferred. -# -# The transaction block: -# -# - May not contain a nested call to #transaction. -# - Is the only context where methods that read from or write to -# the store are allowed. -# -# As seen above, changes in a transaction are made automatically -# when the block exits. -# The block may be exited early by calling method #commit or #abort. -# -# - Method #commit triggers the update to the store and exits the block: -# -# example_store do |store| -# store.transaction do -# store.keys # => [:foo, :bar, :baz] -# store[:bat] = 3 -# store.commit -# fail 'Cannot get here' -# end -# store.transaction do -# # Update was completed. -# store.keys # => [:foo, :bar, :baz, :bat] -# end -# end -# -# - Method #abort discards the update to the store and exits the block: -# -# example_store do |store| -# store.transaction do -# store.keys # => [:foo, :bar, :baz] -# store[:bat] = 3 -# store.abort -# fail 'Cannot get here' -# end -# store.transaction do -# # Update was not completed. -# store.keys # => [:foo, :bar, :baz] -# end -# end -# -# === Read-Only Transactions -# -# By default, a transaction allows both reading from and writing to -# the store: -# -# store.transaction do -# # Read-write transaction. -# # Any code except a call to #transaction is allowed here. -# end -# -# If argument +read_only+ is passed as +true+, -# only reading is allowed: -# -# store.transaction(true) do -# # Read-only transaction: -# # Calls to #transaction, #[]=, and #delete are not allowed here. -# end -# -# == Hierarchical Values -# -# The value for an entry may be a simple object (as seen above). -# It may also be a hierarchy of objects nested to any depth: -# -# deep_store = PStore.new('deep.store') -# deep_store.transaction do -# array_of_hashes = [{}, {}, {}] -# deep_store[:array_of_hashes] = array_of_hashes -# deep_store[:array_of_hashes] # => [{}, {}, {}] -# hash_of_arrays = {foo: [], bar: [], baz: []} -# deep_store[:hash_of_arrays] = hash_of_arrays -# deep_store[:hash_of_arrays] # => {:foo=>[], :bar=>[], :baz=>[]} -# deep_store[:hash_of_arrays][:foo].push(:bat) -# deep_store[:hash_of_arrays] # => {:foo=>[:bat], :bar=>[], :baz=>[]} -# end -# -# And recall that you can use -# {dig methods}[rdoc-ref:dig_methods.rdoc] -# in a returned hierarchy of objects. -# -# == Working with the Store -# -# === Creating a Store -# -# Use method PStore.new to create a store. -# The new store creates or opens its containing file: -# -# store = PStore.new('t.store') -# -# === Modifying the Store -# -# Use method #[]= to update or create an entry: -# -# example_store do |store| -# store.transaction do -# store[:foo] = 1 # Update. -# store[:bam] = 1 # Create. -# end -# end -# -# Use method #delete to remove an entry: -# -# example_store do |store| -# store.transaction do -# store.delete(:foo) -# store[:foo] # => nil -# end -# end -# -# === Retrieving Values -# -# Use method #fetch (allows default) or #[] (defaults to +nil+) -# to retrieve an entry: -# -# example_store do |store| -# store.transaction do -# store[:foo] # => 0 -# store[:nope] # => nil -# store.fetch(:baz) # => 2 -# store.fetch(:nope, nil) # => nil -# store.fetch(:nope) # Raises exception. -# end -# end -# -# === Querying the Store -# -# Use method #key? to determine whether a given key exists: -# -# example_store do |store| -# store.transaction do -# store.key?(:foo) # => true -# end -# end -# -# Use method #keys to retrieve keys: -# -# example_store do |store| -# store.transaction do -# store.keys # => [:foo, :bar, :baz] -# end -# end -# -# Use method #path to retrieve the path to the store's underlying file; -# this method may be called from outside a transaction block: -# -# store = PStore.new('t.store') -# store.path # => "t.store" -# -# == Transaction Safety -# -# For transaction safety, see: -# -# - Optional argument +thread_safe+ at method PStore.new. -# - Attribute #ultra_safe. -# -# Needless to say, if you're storing valuable data with \PStore, then you should -# backup the \PStore file from time to time. -# -# == An Example Store -# -# require "pstore" -# -# # A mock wiki object. -# class WikiPage -# -# attr_reader :page_name -# -# def initialize(page_name, author, contents) -# @page_name = page_name -# @revisions = Array.new -# add_revision(author, contents) -# end -# -# def add_revision(author, contents) -# @revisions << {created: Time.now, -# author: author, -# contents: contents} -# end -# -# def wiki_page_references -# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/) -# end -# -# end -# -# # Create a new wiki page. -# home_page = WikiPage.new("HomePage", "James Edward Gray II", -# "A page about the JoysOfDocumentation..." ) -# -# wiki = PStore.new("wiki_pages.pstore") -# # Update page data and the index together, or not at all. -# wiki.transaction do -# # Store page. -# wiki[home_page.page_name] = home_page -# # Create page index. -# wiki[:wiki_index] ||= Array.new -# # Update wiki index. -# wiki[:wiki_index].push(*home_page.wiki_page_references) -# end -# -# # Read wiki data, setting argument read_only to true. -# wiki.transaction(true) do -# wiki.keys.each do |key| -# puts key -# puts wiki[key] -# end -# end -# -class PStore - VERSION = "0.1.3" - - RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze - RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze - WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze - - # The error type thrown by all PStore methods. - class Error < StandardError - end - - # Whether \PStore should do its best to prevent file corruptions, - # even when an unlikely error (such as memory-error or filesystem error) occurs: - # - # - +true+: changes are posted by creating a temporary file, - # writing the updated data to it, then renaming the file to the given #path. - # File integrity is maintained. - # Note: has effect only if the filesystem has atomic file rename - # (as do POSIX platforms Linux, MacOS, FreeBSD and others). - # - # - +false+ (the default): changes are posted by rewinding the open file - # and writing the updated data. - # File integrity is maintained if the filesystem raises - # no unexpected I/O error; - # if such an error occurs during a write to the store, - # the file may become corrupted. - # - attr_accessor :ultra_safe - - # Returns a new \PStore object. - # - # Argument +file+ is the path to the file in which objects are to be stored; - # if the file exists, it should be one that was written by \PStore. - # - # path = 't.store' - # store = PStore.new(path) - # - # A \PStore object is - # {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)]. - # If argument +thread_safe+ is given as +true+, - # the object is also thread-safe (at the cost of a small performance penalty): - # - # store = PStore.new(path, true) - # - def initialize(file, thread_safe = false) - dir = File::dirname(file) - unless File::directory? dir - raise PStore::Error, format("directory %s does not exist", dir) - end - if File::exist? file and not File::readable? file - raise PStore::Error, format("file %s not readable", file) - end - @filename = file - @abort = false - @ultra_safe = false - @thread_safe = thread_safe - @lock = Thread::Mutex.new - end - - # Raises PStore::Error if the calling code is not in a PStore#transaction. - def in_transaction - raise PStore::Error, "not in transaction" unless @lock.locked? - end - # - # Raises PStore::Error if the calling code is not in a PStore#transaction or - # if the code is in a read-only PStore#transaction. - # - def in_transaction_wr - in_transaction - raise PStore::Error, "in read-only transaction" if @rdonly - end - private :in_transaction, :in_transaction_wr - - # Returns the value for the given +key+ if the key exists. - # +nil+ otherwise; - # if not +nil+, the returned value is an object or a hierarchy of objects: - # - # example_store do |store| - # store.transaction do - # store[:foo] # => 0 - # store[:nope] # => nil - # end - # end - # - # Returns +nil+ if there is no such key. - # - # See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values]. - # - # Raises an exception if called outside a transaction block. - def [](key) - in_transaction - @table[key] - end - - # Like #[], except that it accepts a default value for the store. - # If the +key+ does not exist: - # - # - Raises an exception if +default+ is +PStore::Error+. - # - Returns the value of +default+ otherwise: - # - # example_store do |store| - # store.transaction do - # store.fetch(:nope, nil) # => nil - # store.fetch(:nope) # Raises an exception. - # end - # end - # - # Raises an exception if called outside a transaction block. - def fetch(key, default=PStore::Error) - in_transaction - unless @table.key? key - if default == PStore::Error - raise PStore::Error, format("undefined key '%s'", key) - else - return default - end - end - @table[key] - end - - # Creates or replaces the value for the given +key+: - # - # example_store do |store| - # temp.transaction do - # temp[:bat] = 3 - # end - # end - # - # See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values]. - # - # Raises an exception if called outside a transaction block. - def []=(key, value) - in_transaction_wr - @table[key] = value - end - - # Removes and returns the value at +key+ if it exists: - # - # example_store do |store| - # store.transaction do - # store[:bat] = 3 - # store.delete(:bat) - # end - # end - # - # Returns +nil+ if there is no such key. - # - # Raises an exception if called outside a transaction block. - def delete(key) - in_transaction_wr - @table.delete key - end - - # Returns an array of the existing keys: - # - # example_store do |store| - # store.transaction do - # store.keys # => [:foo, :bar, :baz] - # end - # end - # - # Raises an exception if called outside a transaction block. - def keys - in_transaction - @table.keys - end - alias roots keys - - # Returns +true+ if +key+ exists, +false+ otherwise: - # - # example_store do |store| - # store.transaction do - # store.key?(:foo) # => true - # end - # end - # - # Raises an exception if called outside a transaction block. - def key?(key) - in_transaction - @table.key? key - end - alias root? key? - - # Returns the string file path used to create the store: - # - # store.path # => "flat.store" - # - def path - @filename - end - - # Exits the current transaction block, committing any changes - # specified in the - # {transaction block}[rdoc-ref:PStore@The+Transaction+Block]. - # - # Raises an exception if called outside a transaction block. - def commit - in_transaction - @abort = false - throw :pstore_abort_transaction - end - - # Exits the current transaction block, discarding any changes - # specified in the - # {transaction block}[rdoc-ref:PStore@The+Transaction+Block]. - # - # Raises an exception if called outside a transaction block. - def abort - in_transaction - @abort = true - throw :pstore_abort_transaction - end - - # Opens a transaction block for the store. - # See {Transactions}[rdoc-ref:PStore@Transactions]. - # - # With argument +read_only+ as +false+, the block may both read from - # and write to the store. - # - # With argument +read_only+ as +true+, the block may not include calls - # to #transaction, #[]=, or #delete. - # - # Raises an exception if called within a transaction block. - def transaction(read_only = false) # :yields: pstore - value = nil - if !@thread_safe - raise PStore::Error, "nested transaction" unless @lock.try_lock - else - begin - @lock.lock - rescue ThreadError - raise PStore::Error, "nested transaction" - end - end - begin - @rdonly = read_only - @abort = false - file = open_and_lock_file(@filename, read_only) - if file - begin - @table, checksum, original_data_size = load_data(file, read_only) - - catch(:pstore_abort_transaction) do - value = yield(self) - end - - if !@abort && !read_only - save_data(checksum, original_data_size, file) - end - ensure - file.close - end - else - # This can only occur if read_only == true. - @table = {} - catch(:pstore_abort_transaction) do - value = yield(self) - end - end - ensure - @lock.unlock - end - value - end - - private - # Constant for relieving Ruby's garbage collector. - CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo| - begin - break Digest(algo) - rescue LoadError - end - end - EMPTY_STRING = "" - EMPTY_MARSHAL_DATA = Marshal.dump({}) - EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA) - - # - # Open the specified filename (either in read-only mode or in - # read-write mode) and lock it for reading or writing. - # - # The opened File object will be returned. If _read_only_ is true, - # and the file does not exist, then nil will be returned. - # - # All exceptions are propagated. - # - def open_and_lock_file(filename, read_only) - if read_only - begin - file = File.new(filename, **RD_ACCESS) - begin - file.flock(File::LOCK_SH) - return file - rescue - file.close - raise - end - rescue Errno::ENOENT - return nil - end - else - file = File.new(filename, **RDWR_ACCESS) - file.flock(File::LOCK_EX) - return file - end - end - - # Load the given PStore file. - # If +read_only+ is true, the unmarshalled Hash will be returned. - # If +read_only+ is false, a 3-tuple will be returned: the unmarshalled - # Hash, a checksum of the data, and the size of the data. - def load_data(file, read_only) - if read_only - begin - table = load(file) - raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) - rescue EOFError - # This seems to be a newly-created file. - table = {} - end - table - else - data = file.read - if data.empty? - # This seems to be a newly-created file. - table = {} - checksum = empty_marshal_checksum - size = empty_marshal_data.bytesize - else - table = load(data) - checksum = CHECKSUM_ALGO.digest(data) - size = data.bytesize - raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash) - end - data.replace(EMPTY_STRING) - [table, checksum, size] - end - end - - def on_windows? - is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/ - self.class.__send__(:define_method, :on_windows?) do - is_windows - end - is_windows - end - - def save_data(original_checksum, original_file_size, file) - new_data = dump(@table) - - if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum - if @ultra_safe && !on_windows? - # Windows doesn't support atomic file renames. - save_data_with_atomic_file_rename_strategy(new_data, file) - else - save_data_with_fast_strategy(new_data, file) - end - end - - new_data.replace(EMPTY_STRING) - end - - def save_data_with_atomic_file_rename_strategy(data, file) - temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}" - temp_file = File.new(temp_filename, **WR_ACCESS) - begin - temp_file.flock(File::LOCK_EX) - temp_file.write(data) - temp_file.flush - File.rename(temp_filename, @filename) - rescue - File.unlink(temp_file) rescue nil - raise - ensure - temp_file.close - end - end - - def save_data_with_fast_strategy(data, file) - file.rewind - file.write(data) - file.truncate(data.bytesize) - end - - - # This method is just a wrapped around Marshal.dump - # to allow subclass overriding used in YAML::Store. - def dump(table) # :nodoc: - Marshal::dump(table) - end - - # This method is just a wrapped around Marshal.load. - # to allow subclass overriding used in YAML::Store. - def load(content) # :nodoc: - Marshal::load(content) - end - - def empty_marshal_data - EMPTY_MARSHAL_DATA - end - def empty_marshal_checksum - EMPTY_MARSHAL_CHECKSUM - end -end diff --git a/lib/random/formatter.rb b/lib/random/formatter.rb index 2b5cf718ad..4ecd6ad027 100644 --- a/lib/random/formatter.rb +++ b/lib/random/formatter.rb @@ -340,7 +340,7 @@ module Random::Formatter end # The default character list for #alphanumeric. - ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] + ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'].map(&:freeze).freeze # Generate a random alphanumeric string. # diff --git a/lib/rdoc.rb b/lib/rdoc.rb deleted file mode 100644 index 3821569f45..0000000000 --- a/lib/rdoc.rb +++ /dev/null @@ -1,213 +0,0 @@ -# frozen_string_literal: true -$DEBUG_RDOC = nil - -# :main: README.rdoc - -## -# RDoc produces documentation for Ruby source files by parsing the source and -# extracting the definition for classes, modules, methods, includes and -# requires. It associates these with optional documentation contained in an -# immediately preceding comment block then renders the result using an output -# formatter. -# -# For a simple introduction to writing or generating documentation using RDoc -# see the README. -# -# == Roadmap -# -# If you think you found a bug in RDoc see CONTRIBUTING@Bugs -# -# If you want to use RDoc to create documentation for your Ruby source files, -# see RDoc::Markup and refer to <tt>rdoc --help</tt> for command line usage. -# -# If you want to set the default markup format see -# RDoc::Markup@Markup+Formats -# -# If you want to store rdoc configuration in your gem (such as the default -# markup format) see RDoc::Options@Saved+Options -# -# If you want to write documentation for Ruby files see RDoc::Parser::Ruby -# -# If you want to write documentation for extensions written in C see -# RDoc::Parser::C -# -# If you want to generate documentation using <tt>rake</tt> see RDoc::Task. -# -# If you want to drive RDoc programmatically, see RDoc::RDoc. -# -# If you want to use the library to format text blocks into HTML or other -# formats, look at RDoc::Markup. -# -# If you want to make an RDoc plugin such as a generator or directive handler -# see RDoc::RDoc. -# -# If you want to write your own output generator see RDoc::Generator. -# -# If you want an overview of how RDoc works see CONTRIBUTING -# -# == Credits -# -# RDoc is currently being maintained by Eric Hodel <drbrain@segment7.net>. -# -# Dave Thomas <dave@pragmaticprogrammer.com> is the original author of RDoc. -# -# * The Ruby parser in rdoc/parse.rb is based heavily on the outstanding -# work of Keiju ISHITSUKA of Nippon Rational Inc, who produced the Ruby -# parser for irb and the rtags package. - -module RDoc - - ## - # Exception thrown by any rdoc error. - - class Error < RuntimeError; end - - require_relative 'rdoc/version' - - ## - # Method visibilities - - VISIBILITIES = [:public, :protected, :private] - - ## - # Name of the dotfile that contains the description of files to be processed - # in the current directory - - DOT_DOC_FILENAME = ".document" - - ## - # General RDoc modifiers - - GENERAL_MODIFIERS = %w[nodoc].freeze - - ## - # RDoc modifiers for classes - - CLASS_MODIFIERS = GENERAL_MODIFIERS - - ## - # RDoc modifiers for attributes - - ATTR_MODIFIERS = GENERAL_MODIFIERS - - ## - # RDoc modifiers for constants - - CONSTANT_MODIFIERS = GENERAL_MODIFIERS - - ## - # RDoc modifiers for methods - - METHOD_MODIFIERS = GENERAL_MODIFIERS + - %w[arg args yield yields notnew not-new not_new doc] - - ## - # Loads the best available YAML library. - - def self.load_yaml - begin - gem 'psych' - rescue NameError => e # --disable-gems - raise unless e.name == :gem - rescue Gem::LoadError - end - - begin - require 'psych' - rescue ::LoadError - ensure - require 'yaml' - end - end - - ## - # Searches and returns the directory for settings. - # - # 1. <tt>$HOME/.rdoc</tt> directory, if it exists. - # 2. The +rdoc+ directory under the path specified by the - # +XDG_DATA_HOME+ environment variable, if it is set. - # 3. <tt>$HOME/.local/share/rdoc</tt> directory. - # - # Other than the home directory, the containing directory will be - # created automatically. - - def self.home - rdoc_dir = begin - File.expand_path('~/.rdoc') - rescue ArgumentError - end - - if File.directory?(rdoc_dir) - rdoc_dir - else - require 'fileutils' - begin - # XDG - xdg_data_home = ENV["XDG_DATA_HOME"] || File.join(File.expand_path("~"), '.local', 'share') - unless File.exist?(xdg_data_home) - FileUtils.mkdir_p xdg_data_home - end - File.join xdg_data_home, "rdoc" - rescue Errno::EACCES - end - end - end - - autoload :RDoc, "#{__dir__}/rdoc/rdoc" - - autoload :CrossReference, "#{__dir__}/rdoc/cross_reference" - autoload :ERBIO, "#{__dir__}/rdoc/erbio" - autoload :ERBPartial, "#{__dir__}/rdoc/erb_partial" - autoload :Encoding, "#{__dir__}/rdoc/encoding" - autoload :Generator, "#{__dir__}/rdoc/generator" - autoload :Options, "#{__dir__}/rdoc/options" - autoload :Parser, "#{__dir__}/rdoc/parser" - autoload :Servlet, "#{__dir__}/rdoc/servlet" - autoload :RI, "#{__dir__}/rdoc/ri" - autoload :Stats, "#{__dir__}/rdoc/stats" - autoload :Store, "#{__dir__}/rdoc/store" - autoload :Task, "#{__dir__}/rdoc/task" - autoload :Text, "#{__dir__}/rdoc/text" - - autoload :Markdown, "#{__dir__}/rdoc/markdown" - autoload :Markup, "#{__dir__}/rdoc/markup" - autoload :RD, "#{__dir__}/rdoc/rd" - autoload :TomDoc, "#{__dir__}/rdoc/tom_doc" - - autoload :KNOWN_CLASSES, "#{__dir__}/rdoc/known_classes" - - autoload :TokenStream, "#{__dir__}/rdoc/token_stream" - - autoload :Comment, "#{__dir__}/rdoc/comment" - - require_relative 'rdoc/i18n' - - # code objects - # - # We represent the various high-level code constructs that appear in Ruby - # programs: classes, modules, methods, and so on. - autoload :CodeObject, "#{__dir__}/rdoc/code_object" - - autoload :Context, "#{__dir__}/rdoc/code_object/context" - autoload :TopLevel, "#{__dir__}/rdoc/code_object/top_level" - - autoload :AnonClass, "#{__dir__}/rdoc/code_object/anon_class" - autoload :ClassModule, "#{__dir__}/rdoc/code_object/class_module" - autoload :NormalClass, "#{__dir__}/rdoc/code_object/normal_class" - autoload :NormalModule, "#{__dir__}/rdoc/code_object/normal_module" - autoload :SingleClass, "#{__dir__}/rdoc/code_object/single_class" - - autoload :Alias, "#{__dir__}/rdoc/code_object/alias" - autoload :AnyMethod, "#{__dir__}/rdoc/code_object/any_method" - autoload :MethodAttr, "#{__dir__}/rdoc/code_object/method_attr" - autoload :GhostMethod, "#{__dir__}/rdoc/code_object/ghost_method" - autoload :MetaMethod, "#{__dir__}/rdoc/code_object/meta_method" - autoload :Attr, "#{__dir__}/rdoc/code_object/attr" - - autoload :Constant, "#{__dir__}/rdoc/code_object/constant" - autoload :Mixin, "#{__dir__}/rdoc/code_object/mixin" - autoload :Include, "#{__dir__}/rdoc/code_object/include" - autoload :Extend, "#{__dir__}/rdoc/code_object/extend" - autoload :Require, "#{__dir__}/rdoc/code_object/require" - -end diff --git a/lib/rdoc/.document b/lib/rdoc/.document deleted file mode 100644 index 6b5e1b21a5..0000000000 --- a/lib/rdoc/.document +++ /dev/null @@ -1,2 +0,0 @@ -*.rb -parser diff --git a/lib/rdoc/code_object.rb b/lib/rdoc/code_object.rb deleted file mode 100644 index 83997c2580..0000000000 --- a/lib/rdoc/code_object.rb +++ /dev/null @@ -1,427 +0,0 @@ -# frozen_string_literal: true -## -# Base class for the RDoc code tree. -# -# We contain the common stuff for contexts (which are containers) and other -# elements (methods, attributes and so on) -# -# Here's the tree of the CodeObject subclasses: -# -# * RDoc::Context -# * RDoc::TopLevel -# * RDoc::ClassModule -# * RDoc::AnonClass (never used so far) -# * RDoc::NormalClass -# * RDoc::NormalModule -# * RDoc::SingleClass -# * RDoc::MethodAttr -# * RDoc::Attr -# * RDoc::AnyMethod -# * RDoc::GhostMethod -# * RDoc::MetaMethod -# * RDoc::Alias -# * RDoc::Constant -# * RDoc::Mixin -# * RDoc::Require -# * RDoc::Include - -class RDoc::CodeObject - - include RDoc::Text - - ## - # Our comment - - attr_reader :comment - - ## - # Do we document our children? - - attr_reader :document_children - - ## - # Do we document ourselves? - - attr_reader :document_self - - ## - # Are we done documenting (ie, did we come across a :enddoc:)? - - attr_reader :done_documenting - - ## - # Which file this code object was defined in - - attr_reader :file - - ## - # Force documentation of this CodeObject - - attr_reader :force_documentation - - ## - # Line in #file where this CodeObject was defined - - attr_accessor :line - - ## - # Hash of arbitrary metadata for this CodeObject - - attr_reader :metadata - - ## - # Sets the parent CodeObject - - attr_writer :parent - - ## - # Did we ever receive a +:nodoc:+ directive? - - attr_reader :received_nodoc - - ## - # Set the section this CodeObject is in - - attr_writer :section - - ## - # The RDoc::Store for this object. - - attr_reader :store - - ## - # We are the model of the code, but we know that at some point we will be - # worked on by viewers. By implementing the Viewable protocol, viewers can - # associated themselves with these objects. - - attr_accessor :viewer - - ## - # When mixed-in to a class, this points to the Context in which it was originally defined. - - attr_accessor :mixin_from - - ## - # Creates a new CodeObject that will document itself and its children - - def initialize - @metadata = {} - @comment = '' - @parent = nil - @parent_name = nil # for loading - @parent_class = nil # for loading - @section = nil - @section_title = nil # for loading - @file = nil - @full_name = nil - @store = nil - @track_visibility = true - @mixin_from = nil - - initialize_visibility - end - - ## - # Initializes state for visibility of this CodeObject and its children. - - def initialize_visibility # :nodoc: - @document_children = true - @document_self = true - @done_documenting = false - @force_documentation = false - @received_nodoc = false - @ignored = false - @suppressed = false - @track_visibility = true - end - - ## - # Replaces our comment with +comment+, unless it is empty. - - def comment=(comment) - @comment = case comment - when NilClass then '' - when RDoc::Markup::Document then comment - when RDoc::Comment then comment.normalize - else - if comment and not comment.empty? then - normalize_comment comment - else - # HACK correct fix is to have #initialize create @comment - # with the correct encoding - if String === @comment and @comment.empty? then - @comment = RDoc::Encoding.change_encoding @comment, comment.encoding - end - @comment - end - end - end - - ## - # Should this CodeObject be displayed in output? - # - # A code object should be displayed if: - # - # * The item didn't have a nodoc or wasn't in a container that had nodoc - # * The item wasn't ignored - # * The item has documentation and was not suppressed - - def display? - @document_self and not @ignored and - (documented? or not @suppressed) - end - - ## - # Enables or disables documentation of this CodeObject's children unless it - # has been turned off by :enddoc: - - def document_children=(document_children) - return unless @track_visibility - - @document_children = document_children unless @done_documenting - end - - ## - # Enables or disables documentation of this CodeObject unless it has been - # turned off by :enddoc:. If the argument is +nil+ it means the - # documentation is turned off by +:nodoc:+. - - def document_self=(document_self) - return unless @track_visibility - return if @done_documenting - - @document_self = document_self - @received_nodoc = true if document_self.nil? - end - - ## - # Does this object have a comment with content or is #received_nodoc true? - - def documented? - @received_nodoc or !@comment.empty? - end - - ## - # Turns documentation on/off, and turns on/off #document_self - # and #document_children. - # - # Once documentation has been turned off (by +:enddoc:+), - # the object will refuse to turn #document_self or - # #document_children on, so +:doc:+ and +:start_doc:+ directives - # will have no effect in the current file. - - def done_documenting=(value) - return unless @track_visibility - @done_documenting = value - @document_self = !value - @document_children = @document_self - end - - ## - # Yields each parent of this CodeObject. See also - # RDoc::ClassModule#each_ancestor - - def each_parent - code_object = self - - while code_object = code_object.parent do - yield code_object - end - - self - end - - ## - # File name where this CodeObject was found. - # - # See also RDoc::Context#in_files - - def file_name - return unless @file - - @file.absolute_name - end - - ## - # Force the documentation of this object unless documentation - # has been turned off by :enddoc: - #-- - # HACK untested, was assigning to an ivar - - def force_documentation=(value) - @force_documentation = value unless @done_documenting - end - - ## - # Sets the full_name overriding any computed full name. - # - # Set to +nil+ to clear RDoc's cached value - - def full_name= full_name - @full_name = full_name - end - - ## - # Use this to ignore a CodeObject and all its children until found again - # (#record_location is called). An ignored item will not be displayed in - # documentation. - # - # See github issue #55 - # - # The ignored status is temporary in order to allow implementation details - # to be hidden. At the end of processing a file RDoc allows all classes - # and modules to add new documentation to previously created classes. - # - # If a class was ignored (via stopdoc) then reopened later with additional - # documentation it should be displayed. If a class was ignored and never - # reopened it should not be displayed. The ignore flag allows this to - # occur. - - def ignore - return unless @track_visibility - - @ignored = true - - stop_doc - end - - ## - # Has this class been ignored? - # - # See also #ignore - - def ignored? - @ignored - end - - ## - # The options instance from the store this CodeObject is attached to, or a - # default options instance if the CodeObject is not attached. - # - # This is used by Text#snippet - - def options - if @store and @store.rdoc then - @store.rdoc.options - else - RDoc::Options.new - end - end - - ## - # Our parent CodeObject. The parent may be missing for classes loaded from - # legacy RI data stores. - - def parent - return @parent if @parent - return nil unless @parent_name - - if @parent_class == RDoc::TopLevel then - @parent = @store.add_file @parent_name - else - @parent = @store.find_class_or_module @parent_name - - return @parent if @parent - - begin - @parent = @store.load_class @parent_name - rescue RDoc::Store::MissingFileError - nil - end - end - end - - ## - # File name of our parent - - def parent_file_name - @parent ? @parent.base_name : '(unknown)' - end - - ## - # Name of our parent - - def parent_name - @parent ? @parent.full_name : '(unknown)' - end - - ## - # Records the RDoc::TopLevel (file) where this code object was defined - - def record_location top_level - @ignored = false - @suppressed = false - @file = top_level - end - - ## - # The section this CodeObject is in. Sections allow grouping of constants, - # attributes and methods inside a class or module. - - def section - return @section if @section - - @section = parent.add_section @section_title if parent - end - - ## - # Enable capture of documentation unless documentation has been - # turned off by :enddoc: - - def start_doc - return if @done_documenting - - @document_self = true - @document_children = true - @ignored = false - @suppressed = false - end - - ## - # Disable capture of documentation - - def stop_doc - return unless @track_visibility - - @document_self = false - @document_children = false - end - - ## - # Sets the +store+ that contains this CodeObject - - def store= store - @store = store - - return unless @track_visibility - - if :nodoc == options.visibility then - initialize_visibility - @track_visibility = false - end - end - - ## - # Use this to suppress a CodeObject and all its children until the next file - # it is seen in or documentation is discovered. A suppressed item with - # documentation will be displayed while an ignored item with documentation - # may not be displayed. - - def suppress - return unless @track_visibility - - @suppressed = true - - stop_doc - end - - ## - # Has this class been suppressed? - # - # See also #suppress - - def suppressed? - @suppressed - end - -end diff --git a/lib/rdoc/code_object/alias.rb b/lib/rdoc/code_object/alias.rb deleted file mode 100644 index 92df7e448f..0000000000 --- a/lib/rdoc/code_object/alias.rb +++ /dev/null @@ -1,111 +0,0 @@ -# frozen_string_literal: true -## -# Represent an alias, which is an old_name/new_name pair associated with a -# particular context -#-- -# TODO implement Alias as a proxy to a method/attribute, inheriting from -# MethodAttr - -class RDoc::Alias < RDoc::CodeObject - - ## - # Aliased method's name - - attr_reader :new_name - - alias name new_name - - ## - # Aliasee method's name - - attr_reader :old_name - - ## - # Is this an alias declared in a singleton context? - - attr_accessor :singleton - - ## - # Source file token stream - - attr_reader :text - - ## - # Creates a new Alias with a token stream of +text+ that aliases +old_name+ - # to +new_name+, has +comment+ and is a +singleton+ context. - - def initialize(text, old_name, new_name, comment, singleton = false) - super() - - @text = text - @singleton = singleton - @old_name = old_name - @new_name = new_name - self.comment = comment - end - - ## - # Order by #singleton then #new_name - - def <=>(other) - [@singleton ? 0 : 1, new_name] <=> [other.singleton ? 0 : 1, other.new_name] - end - - ## - # HTML fragment reference for this alias - - def aref - type = singleton ? 'c' : 'i' - "#alias-#{type}-#{html_name}" - end - - ## - # Full old name including namespace - - def full_old_name - @full_name || "#{parent.name}#{pretty_old_name}" - end - - ## - # HTML id-friendly version of +#new_name+. - - def html_name - CGI.escape(@new_name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') - end - - def inspect # :nodoc: - parent_name = parent ? parent.name : '(unknown)' - "#<%s:0x%x %s.alias_method %s, %s>" % [ - self.class, object_id, - parent_name, @old_name, @new_name, - ] - end - - ## - # '::' for the alias of a singleton method/attribute, '#' for instance-level. - - def name_prefix - singleton ? '::' : '#' - end - - ## - # Old name with prefix '::' or '#'. - - def pretty_old_name - "#{singleton ? '::' : '#'}#{@old_name}" - end - - ## - # New name with prefix '::' or '#'. - - def pretty_new_name - "#{singleton ? '::' : '#'}#{@new_name}" - end - - alias pretty_name pretty_new_name - - def to_s # :nodoc: - "alias: #{self.new_name} -> #{self.pretty_old_name} in: #{parent}" - end - -end diff --git a/lib/rdoc/code_object/anon_class.rb b/lib/rdoc/code_object/anon_class.rb deleted file mode 100644 index 3c2f0e1877..0000000000 --- a/lib/rdoc/code_object/anon_class.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true -## -# An anonymous class like: -# -# c = Class.new do end -# -# AnonClass is currently not used. - -class RDoc::AnonClass < RDoc::ClassModule -end diff --git a/lib/rdoc/code_object/any_method.rb b/lib/rdoc/code_object/any_method.rb deleted file mode 100644 index 465c4a4fb2..0000000000 --- a/lib/rdoc/code_object/any_method.rb +++ /dev/null @@ -1,379 +0,0 @@ -# frozen_string_literal: true -## -# AnyMethod is the base class for objects representing methods - -class RDoc::AnyMethod < RDoc::MethodAttr - - ## - # 2:: - # RDoc 4 - # Added calls_super - # Added parent name and class - # Added section title - # 3:: - # RDoc 4.1 - # Added is_alias_for - - MARSHAL_VERSION = 3 # :nodoc: - - ## - # Don't rename \#initialize to \::new - - attr_accessor :dont_rename_initialize - - ## - # The C function that implements this method (if it was defined in a C file) - - attr_accessor :c_function - - # The section title of the method (if defined in a C file via +:category:+) - attr_accessor :section_title - - # Parameters for this method - - attr_accessor :params - - ## - # If true this method uses +super+ to call a superclass version - - attr_accessor :calls_super - - include RDoc::TokenStream - - ## - # Creates a new AnyMethod with a token stream +text+ and +name+ - - def initialize text, name - super - - @c_function = nil - @dont_rename_initialize = false - @token_stream = nil - @calls_super = false - @superclass_method = nil - end - - ## - # Adds +an_alias+ as an alias for this method in +context+. - - def add_alias an_alias, context = nil - method = self.class.new an_alias.text, an_alias.new_name - - method.record_location an_alias.file - method.singleton = self.singleton - method.params = self.params - method.visibility = self.visibility - method.comment = an_alias.comment - method.is_alias_for = self - @aliases << method - context.add_method method if context - method - end - - ## - # Prefix for +aref+ is 'method'. - - def aref_prefix - 'method' - end - - ## - # The call_seq or the param_seq with method name, if there is no call_seq. - # - # Use this for displaying a method's argument lists. - - def arglists - if @call_seq then - @call_seq - elsif @params then - "#{name}#{param_seq}" - end - end - - ## - # Different ways to call this method - - def call_seq - unless call_seq = _call_seq - call_seq = is_alias_for._call_seq if is_alias_for - end - - return unless call_seq - - deduplicate_call_seq(call_seq) - end - - ## - # Sets the different ways you can call this method. If an empty +call_seq+ - # is given nil is assumed. - # - # See also #param_seq - - def call_seq= call_seq - return if call_seq.empty? - - @call_seq = call_seq - end - - ## - # Whether the method has a call-seq. - - def has_call_seq? - !!(@call_seq || is_alias_for&._call_seq) - end - - ## - # Loads is_alias_for from the internal name. Returns nil if the alias - # cannot be found. - - def is_alias_for # :nodoc: - case @is_alias_for - when RDoc::MethodAttr then - @is_alias_for - when Array then - return nil unless @store - - klass_name, singleton, method_name = @is_alias_for - - return nil unless klass = @store.find_class_or_module(klass_name) - - @is_alias_for = klass.find_method method_name, singleton - end - end - - ## - # Dumps this AnyMethod for use by ri. See also #marshal_load - - def marshal_dump - aliases = @aliases.map do |a| - [a.name, parse(a.comment)] - end - - is_alias_for = [ - @is_alias_for.parent.full_name, - @is_alias_for.singleton, - @is_alias_for.name - ] if @is_alias_for - - [ MARSHAL_VERSION, - @name, - full_name, - @singleton, - @visibility, - parse(@comment), - @call_seq, - @block_params, - aliases, - @params, - @file.relative_name, - @calls_super, - @parent.name, - @parent.class, - @section.title, - is_alias_for, - ] - end - - ## - # Loads this AnyMethod from +array+. For a loaded AnyMethod the following - # methods will return cached values: - # - # * #full_name - # * #parent_name - - def marshal_load array - initialize_visibility - - @dont_rename_initialize = nil - @token_stream = nil - @aliases = [] - @parent = nil - @parent_name = nil - @parent_class = nil - @section = nil - @file = nil - - version = array[0] - @name = array[1] - @full_name = array[2] - @singleton = array[3] - @visibility = array[4] - @comment = array[5] - @call_seq = array[6] - @block_params = array[7] - # 8 handled below - @params = array[9] - # 10 handled below - @calls_super = array[11] - @parent_name = array[12] - @parent_title = array[13] - @section_title = array[14] - @is_alias_for = array[15] - - array[8].each do |new_name, comment| - add_alias RDoc::Alias.new(nil, @name, new_name, comment, @singleton) - end - - @parent_name ||= if @full_name =~ /#/ then - $` - else - name = @full_name.split('::') - name.pop - name.join '::' - end - - @file = RDoc::TopLevel.new array[10] if version > 0 - end - - ## - # Method name - # - # If the method has no assigned name, it extracts it from #call_seq. - - def name - return @name if @name - - @name = - @call_seq[/^.*?\.(\w+)/, 1] || - @call_seq[/^.*?(\w+)/, 1] || - @call_seq if @call_seq - end - - ## - # A list of this method's method and yield parameters. +call-seq+ params - # are preferred over parsed method and block params. - - def param_list - if @call_seq then - params = @call_seq.split("\n").last - params = params.sub(/.*?\((.*)\)/, '\1') - params = params.sub(/(\{|do)\s*\|([^|]*)\|.*/, ',\2') - elsif @params then - params = @params.sub(/\((.*)\)/, '\1') - - params << ",#{@block_params}" if @block_params - elsif @block_params then - params = @block_params - else - return [] - end - - if @block_params then - # If this method has explicit block parameters, remove any explicit - # &block - params = params.sub(/,?\s*&\w+/, '') - else - params = params.sub(/\&(\w+)/, '\1') - end - - params = params.gsub(/\s+/, '').split(',').reject(&:empty?) - - params.map { |param| param.sub(/=.*/, '') } - end - - ## - # Pretty parameter list for this method. If the method's parameters were - # given by +call-seq+ it is preferred over the parsed values. - - def param_seq - if @call_seq then - params = @call_seq.split("\n").last - params = params.sub(/[^( ]+/, '') - params = params.sub(/(\|[^|]+\|)\s*\.\.\.\s*(end|\})/, '\1 \2') - elsif @params then - params = @params.gsub(/\s*\#.*/, '') - params = params.tr_s("\n ", " ") - params = "(#{params})" unless params[0] == ?( - else - params = '' - end - - if @block_params then - # If this method has explicit block parameters, remove any explicit - # &block - params = params.sub(/,?\s*&\w+/, '') - - block = @block_params.tr_s("\n ", " ") - if block[0] == ?( - block = block.sub(/^\(/, '').sub(/\)/, '') - end - params << " { |#{block}| ... }" - end - - params - end - - ## - # Whether to skip the method description, true for methods that have - # aliases with a call-seq that doesn't include the method name. - - def skip_description? - has_call_seq? && call_seq.nil? && !!(is_alias_for || !aliases.empty?) - end - - ## - # Sets the store for this method and its referenced code objects. - - def store= store - super - - @file = @store.add_file @file.full_name if @file - end - - ## - # For methods that +super+, find the superclass method that would be called. - - def superclass_method - return unless @calls_super - return @superclass_method if @superclass_method - - parent.each_ancestor do |ancestor| - if method = ancestor.method_list.find { |m| m.name == @name } then - @superclass_method = method - break - end - end - - @superclass_method - end - - protected - - ## - # call_seq without deduplication and alias lookup. - - def _call_seq - @call_seq if defined?(@call_seq) && @call_seq - end - - private - - ## - # call_seq with alias examples information removed, if this - # method is an alias method. - - def deduplicate_call_seq(call_seq) - return call_seq unless is_alias_for || !aliases.empty? - - method_name = self.name - method_name = method_name[0, 1] if method_name =~ /\A\[/ - - entries = call_seq.split "\n" - - ignore = aliases.map(&:name) - if is_alias_for - ignore << is_alias_for.name - ignore.concat is_alias_for.aliases.map(&:name) - end - ignore.map! { |n| n =~ /\A\[/ ? /\[.*\]/ : n} - ignore.delete(method_name) - ignore = Regexp.union(ignore) - - matching = entries.reject do |entry| - entry =~ /^\w*\.?#{ignore}[$\(\s]/ or - entry =~ /\s#{ignore}\s/ - end - - matching.empty? ? nil : matching.join("\n") - end -end diff --git a/lib/rdoc/code_object/attr.rb b/lib/rdoc/code_object/attr.rb deleted file mode 100644 index a403235933..0000000000 --- a/lib/rdoc/code_object/attr.rb +++ /dev/null @@ -1,175 +0,0 @@ -# frozen_string_literal: true -## -# An attribute created by \#attr, \#attr_reader, \#attr_writer or -# \#attr_accessor - -class RDoc::Attr < RDoc::MethodAttr - - ## - # 3:: - # RDoc 4 - # Added parent name and class - # Added section title - - MARSHAL_VERSION = 3 # :nodoc: - - ## - # Is the attribute readable ('R'), writable ('W') or both ('RW')? - - attr_accessor :rw - - ## - # Creates a new Attr with body +text+, +name+, read/write status +rw+ and - # +comment+. +singleton+ marks this as a class attribute. - - def initialize(text, name, rw, comment, singleton = false) - super text, name - - @rw = rw - @singleton = singleton - self.comment = comment - end - - ## - # Attributes are equal when their names, singleton and rw are identical - - def == other - self.class == other.class and - self.name == other.name and - self.rw == other.rw and - self.singleton == other.singleton - end - - ## - # Add +an_alias+ as an attribute in +context+. - - def add_alias(an_alias, context) - new_attr = self.class.new(self.text, an_alias.new_name, self.rw, - self.comment, self.singleton) - - new_attr.record_location an_alias.file - new_attr.visibility = self.visibility - new_attr.is_alias_for = self - @aliases << new_attr - context.add_attribute new_attr - new_attr - end - - ## - # The #aref prefix for attributes - - def aref_prefix - 'attribute' - end - - ## - # Attributes never call super. See RDoc::AnyMethod#calls_super - # - # An RDoc::Attr can show up in the method list in some situations (see - # Gem::ConfigFile) - - def calls_super # :nodoc: - false - end - - ## - # Returns attr_reader, attr_writer or attr_accessor as appropriate. - - def definition - case @rw - when 'RW' then 'attr_accessor' - when 'R' then 'attr_reader' - when 'W' then 'attr_writer' - end - end - - def inspect # :nodoc: - alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil - visibility = self.visibility - visibility = "forced #{visibility}" if force_documentation - "#<%s:0x%x %s %s (%s)%s>" % [ - self.class, object_id, - full_name, - rw, - visibility, - alias_for, - ] - end - - ## - # Dumps this Attr for use by ri. See also #marshal_load - - def marshal_dump - [ MARSHAL_VERSION, - @name, - full_name, - @rw, - @visibility, - parse(@comment), - singleton, - @file.relative_name, - @parent.full_name, - @parent.class, - @section.title - ] - end - - ## - # Loads this Attr from +array+. For a loaded Attr the following - # methods will return cached values: - # - # * #full_name - # * #parent_name - - def marshal_load array - initialize_visibility - - @aliases = [] - @parent = nil - @parent_name = nil - @parent_class = nil - @section = nil - @file = nil - - version = array[0] - @name = array[1] - @full_name = array[2] - @rw = array[3] - @visibility = array[4] - @comment = array[5] - @singleton = array[6] || false # MARSHAL_VERSION == 0 - # 7 handled below - @parent_name = array[8] - @parent_class = array[9] - @section_title = array[10] - - @file = RDoc::TopLevel.new array[7] if version > 1 - - @parent_name ||= @full_name.split('#', 2).first - end - - def pretty_print q # :nodoc: - q.group 2, "[#{self.class.name} #{full_name} #{rw} #{visibility}", "]" do - unless comment.empty? then - q.breakable - q.text "comment:" - q.breakable - q.pp @comment - end - end - end - - def to_s # :nodoc: - "#{definition} #{name} in: #{parent}" - end - - ## - # Attributes do not have token streams. - # - # An RDoc::Attr can show up in the method list in some situations (see - # Gem::ConfigFile) - - def token_stream # :nodoc: - end - -end diff --git a/lib/rdoc/code_object/class_module.rb b/lib/rdoc/code_object/class_module.rb deleted file mode 100644 index a99acb8956..0000000000 --- a/lib/rdoc/code_object/class_module.rb +++ /dev/null @@ -1,841 +0,0 @@ -# frozen_string_literal: true -## -# ClassModule is the base class for objects representing either a class or a -# module. - -class RDoc::ClassModule < RDoc::Context - - ## - # 1:: - # RDoc 3.7 - # * Added visibility, singleton and file to attributes - # * Added file to constants - # * Added file to includes - # * Added file to methods - # 2:: - # RDoc 3.13 - # * Added extends - # 3:: - # RDoc 4.0 - # * Added sections - # * Added in_files - # * Added parent name - # * Complete Constant dump - - MARSHAL_VERSION = 3 # :nodoc: - - ## - # Constants that are aliases for this class or module - - attr_accessor :constant_aliases - - ## - # Comment and the location it came from. Use #add_comment to add comments - - attr_accessor :comment_location - - attr_accessor :diagram # :nodoc: - - ## - # Class or module this constant is an alias for - - attr_accessor :is_alias_for - - ## - # Return a RDoc::ClassModule of class +class_type+ that is a copy - # of module +module+. Used to promote modules to classes. - #-- - # TODO move to RDoc::NormalClass (I think) - - def self.from_module class_type, mod - klass = class_type.new mod.name - - mod.comment_location.each do |comment, location| - klass.add_comment comment, location - end - - klass.parent = mod.parent - klass.section = mod.section - klass.viewer = mod.viewer - - klass.attributes.concat mod.attributes - klass.method_list.concat mod.method_list - klass.aliases.concat mod.aliases - klass.external_aliases.concat mod.external_aliases - klass.constants.concat mod.constants - klass.includes.concat mod.includes - klass.extends.concat mod.extends - - klass.methods_hash.update mod.methods_hash - klass.constants_hash.update mod.constants_hash - - klass.current_section = mod.current_section - klass.in_files.concat mod.in_files - klass.sections.concat mod.sections - klass.unmatched_alias_lists = mod.unmatched_alias_lists - klass.current_section = mod.current_section - klass.visibility = mod.visibility - - klass.classes_hash.update mod.classes_hash - klass.modules_hash.update mod.modules_hash - klass.metadata.update mod.metadata - - klass.document_self = mod.received_nodoc ? nil : mod.document_self - klass.document_children = mod.document_children - klass.force_documentation = mod.force_documentation - klass.done_documenting = mod.done_documenting - - # update the parent of all children - - (klass.attributes + - klass.method_list + - klass.aliases + - klass.external_aliases + - klass.constants + - klass.includes + - klass.extends + - klass.classes + - klass.modules).each do |obj| - obj.parent = klass - obj.full_name = nil - end - - klass - end - - ## - # Creates a new ClassModule with +name+ with optional +superclass+ - # - # This is a constructor for subclasses, and must never be called directly. - - def initialize(name, superclass = nil) - @constant_aliases = [] - @diagram = nil - @is_alias_for = nil - @name = name - @superclass = superclass - @comment_location = [] # [[comment, location]] - - super() - end - - ## - # Adds +comment+ to this ClassModule's list of comments at +location+. This - # method is preferred over #comment= since it allows ri data to be updated - # across multiple runs. - - def add_comment comment, location - return unless document_self - - original = comment - - comment = case comment - when RDoc::Comment then - comment.normalize - else - normalize_comment comment - end - - if location.parser == RDoc::Parser::C - @comment_location.delete_if { |(_, l)| l == location } - end - - @comment_location << [comment, location] - - self.comment = original - end - - def add_things my_things, other_things # :nodoc: - other_things.each do |group, things| - my_things[group].each { |thing| yield false, thing } if - my_things.include? group - - things.each do |thing| - yield true, thing - end - end - end - - ## - # Ancestors list for this ClassModule: the list of included modules - # (classes will add their superclass if any). - # - # Returns the included classes or modules, not the includes - # themselves. The returned values are either String or - # RDoc::NormalModule instances (see RDoc::Include#module). - # - # The values are returned in reverse order of their inclusion, - # which is the order suitable for searching methods/attributes - # in the ancestors. The superclass, if any, comes last. - - def ancestors - includes.map { |i| i.module }.reverse - end - - def aref_prefix # :nodoc: - raise NotImplementedError, "missing aref_prefix for #{self.class}" - end - - ## - # HTML fragment reference for this module or class. See - # RDoc::NormalClass#aref and RDoc::NormalModule#aref - - def aref - "#{aref_prefix}-#{full_name}" - end - - ## - # Ancestors of this class or module only - - alias direct_ancestors ancestors - - ## - # Clears the comment. Used by the Ruby parser. - - def clear_comment - @comment = '' - end - - ## - # This method is deprecated, use #add_comment instead. - # - # Appends +comment+ to the current comment, but separated by a rule. Works - # more like <tt>+=</tt>. - - def comment= comment # :nodoc: - comment = case comment - when RDoc::Comment then - comment.normalize - else - normalize_comment comment - end - - comment = "#{@comment.to_s}\n---\n#{comment.to_s}" unless @comment.empty? - - super comment - end - - ## - # Prepares this ClassModule for use by a generator. - # - # See RDoc::Store#complete - - def complete min_visibility - update_aliases - remove_nodoc_children - embed_mixins - update_includes - remove_invisible min_visibility - end - - ## - # Does this ClassModule or any of its methods have document_self set? - - def document_self_or_methods - document_self || method_list.any?{ |m| m.document_self } - end - - ## - # Does this class or module have a comment with content or is - # #received_nodoc true? - - def documented? - return true if @received_nodoc - return false if @comment_location.empty? - @comment_location.any? { |comment, _| not comment.empty? } - end - - ## - # Iterates the ancestors of this class or module for which an - # RDoc::ClassModule exists. - - def each_ancestor # :yields: module - return enum_for __method__ unless block_given? - - ancestors.each do |mod| - next if String === mod - next if self == mod - yield mod - end - end - - ## - # Looks for a symbol in the #ancestors. See Context#find_local_symbol. - - def find_ancestor_local_symbol symbol - each_ancestor do |m| - res = m.find_local_symbol(symbol) - return res if res - end - - nil - end - - ## - # Finds a class or module with +name+ in this namespace or its descendants - - def find_class_named name - return self if full_name == name - return self if @name == name - - @classes.values.find do |klass| - next if klass == self - klass.find_class_named name - end - end - - ## - # Return the fully qualified name of this class or module - - def full_name - @full_name ||= if RDoc::ClassModule === parent then - "#{parent.full_name}::#{@name}" - else - @name - end - end - - ## - # TODO: filter included items by #display? - - def marshal_dump # :nodoc: - attrs = attributes.sort.map do |attr| - next unless attr.display? - [ attr.name, attr.rw, - attr.visibility, attr.singleton, attr.file_name, - ] - end.compact - - method_types = methods_by_type.map do |type, visibilities| - visibilities = visibilities.map do |visibility, methods| - method_names = methods.map do |method| - next unless method.display? - [method.name, method.file_name] - end.compact - - [visibility, method_names.uniq] - end - - [type, visibilities] - end - - [ MARSHAL_VERSION, - @name, - full_name, - @superclass, - parse(@comment_location), - attrs, - constants.select { |constant| constant.display? }, - includes.map do |incl| - next unless incl.display? - [incl.name, parse(incl.comment), incl.file_name] - end.compact, - method_types, - extends.map do |ext| - next unless ext.display? - [ext.name, parse(ext.comment), ext.file_name] - end.compact, - @sections.values, - @in_files.map do |tl| - tl.relative_name - end, - parent.full_name, - parent.class, - ] - end - - def marshal_load array # :nodoc: - initialize_visibility - initialize_methods_etc - @current_section = nil - @document_self = true - @done_documenting = false - @parent = nil - @temporary_section = nil - @visibility = nil - @classes = {} - @modules = {} - - @name = array[1] - @full_name = array[2] - @superclass = array[3] - @comment = array[4] - - @comment_location = if RDoc::Markup::Document === @comment.parts.first then - @comment - else - RDoc::Markup::Document.new @comment - end - - array[5].each do |name, rw, visibility, singleton, file| - singleton ||= false - visibility ||= :public - - attr = RDoc::Attr.new nil, name, rw, nil, singleton - - add_attribute attr - attr.visibility = visibility - attr.record_location RDoc::TopLevel.new file - end - - array[6].each do |constant, comment, file| - case constant - when RDoc::Constant then - add_constant constant - else - constant = add_constant RDoc::Constant.new(constant, nil, comment) - constant.record_location RDoc::TopLevel.new file - end - end - - array[7].each do |name, comment, file| - incl = add_include RDoc::Include.new(name, comment) - incl.record_location RDoc::TopLevel.new file - end - - array[8].each do |type, visibilities| - visibilities.each do |visibility, methods| - @visibility = visibility - - methods.each do |name, file| - method = RDoc::AnyMethod.new nil, name - method.singleton = true if type == 'class' - method.record_location RDoc::TopLevel.new file - add_method method - end - end - end - - array[9].each do |name, comment, file| - ext = add_extend RDoc::Extend.new(name, comment) - ext.record_location RDoc::TopLevel.new file - end if array[9] # Support Marshal version 1 - - sections = (array[10] || []).map do |section| - [section.title, section] - end - - @sections = Hash[*sections.flatten] - @current_section = add_section nil - - @in_files = [] - - (array[11] || []).each do |filename| - record_location RDoc::TopLevel.new filename - end - - @parent_name = array[12] - @parent_class = array[13] - end - - ## - # Merges +class_module+ into this ClassModule. - # - # The data in +class_module+ is preferred over the receiver. - - def merge class_module - @parent = class_module.parent - @parent_name = class_module.parent_name - - other_document = parse class_module.comment_location - - if other_document then - document = parse @comment_location - - document = document.merge other_document - - @comment = @comment_location = document - end - - cm = class_module - other_files = cm.in_files - - merge_collections attributes, cm.attributes, other_files do |add, attr| - if add then - add_attribute attr - else - @attributes.delete attr - @methods_hash.delete attr.pretty_name - end - end - - merge_collections constants, cm.constants, other_files do |add, const| - if add then - add_constant const - else - @constants.delete const - @constants_hash.delete const.name - end - end - - merge_collections includes, cm.includes, other_files do |add, incl| - if add then - add_include incl - else - @includes.delete incl - end - end - - @includes.uniq! # clean up - - merge_collections extends, cm.extends, other_files do |add, ext| - if add then - add_extend ext - else - @extends.delete ext - end - end - - @extends.uniq! # clean up - - merge_collections method_list, cm.method_list, other_files do |add, meth| - if add then - add_method meth - else - @method_list.delete meth - @methods_hash.delete meth.pretty_name - end - end - - merge_sections cm - - self - end - - ## - # Merges collection +mine+ with +other+ preferring other. +other_files+ is - # used to help determine which items should be deleted. - # - # Yields whether the item should be added or removed (true or false) and the - # item to be added or removed. - # - # merge_collections things, other.things, other.in_files do |add, thing| - # if add then - # # add the thing - # else - # # remove the thing - # end - # end - - def merge_collections mine, other, other_files, &block # :nodoc: - my_things = mine. group_by { |thing| thing.file } - other_things = other.group_by { |thing| thing.file } - - remove_things my_things, other_files, &block - add_things my_things, other_things, &block - end - - ## - # Merges the comments in this ClassModule with the comments in the other - # ClassModule +cm+. - - def merge_sections cm # :nodoc: - my_sections = sections.group_by { |section| section.title } - other_sections = cm.sections.group_by { |section| section.title } - - other_files = cm.in_files - - remove_things my_sections, other_files do |_, section| - @sections.delete section.title - end - - other_sections.each do |group, sections| - if my_sections.include? group - my_sections[group].each do |my_section| - other_section = cm.sections_hash[group] - - my_comments = my_section.comments - other_comments = other_section.comments - - other_files = other_section.in_files - - merge_collections my_comments, other_comments, other_files do |add, comment| - if add then - my_section.add_comment comment - else - my_section.remove_comment comment - end - end - end - else - sections.each do |section| - add_section group, section.comments - end - end - end - end - - ## - # Does this object represent a module? - - def module? - false - end - - ## - # Allows overriding the initial name. - # - # Used for modules and classes that are constant aliases. - - def name= new_name - @name = new_name - end - - ## - # Parses +comment_location+ into an RDoc::Markup::Document composed of - # multiple RDoc::Markup::Documents with their file set. - - def parse comment_location - case comment_location - when String then - super - when Array then - docs = comment_location.map do |comment, location| - doc = super comment - doc.file = location - doc - end - - RDoc::Markup::Document.new(*docs) - when RDoc::Comment then - doc = super comment_location.text, comment_location.format - doc.file = comment_location.location - doc - when RDoc::Markup::Document then - return comment_location - else - raise ArgumentError, "unknown comment class #{comment_location.class}" - end - end - - ## - # Path to this class or module for use with HTML generator output. - - def path - http_url @store.rdoc.generator.class_dir - end - - ## - # Name to use to generate the url: - # modules and classes that are aliases for another - # module or class return the name of the latter. - - def name_for_path - is_alias_for ? is_alias_for.full_name : full_name - end - - ## - # Returns the classes and modules that are not constants - # aliasing another class or module. For use by formatters - # only (caches its result). - - def non_aliases - @non_aliases ||= classes_and_modules.reject { |cm| cm.is_alias_for } - end - - ## - # Updates the child modules or classes of class/module +parent+ by - # deleting the ones that have been removed from the documentation. - # - # +parent_hash+ is either <tt>parent.modules_hash</tt> or - # <tt>parent.classes_hash</tt> and +all_hash+ is ::all_modules_hash or - # ::all_classes_hash. - - def remove_nodoc_children - prefix = self.full_name + '::' - - modules_hash.each_key do |name| - full_name = prefix + name - modules_hash.delete name unless @store.modules_hash[full_name] - end - - classes_hash.each_key do |name| - full_name = prefix + name - classes_hash.delete name unless @store.classes_hash[full_name] - end - end - - def remove_things my_things, other_files # :nodoc: - my_things.delete_if do |file, things| - next false unless other_files.include? file - - things.each do |thing| - yield false, thing - end - - true - end - end - - ## - # Search record used by RDoc::Generator::JsonIndex - - def search_record - [ - name, - full_name, - full_name, - '', - path, - '', - snippet(@comment_location), - ] - end - - ## - # Sets the store for this class or module and its contained code objects. - - def store= store - super - - @attributes .each do |attr| attr.store = store end - @constants .each do |const| const.store = store end - @includes .each do |incl| incl.store = store end - @extends .each do |ext| ext.store = store end - @method_list.each do |meth| meth.store = store end - end - - ## - # Get the superclass of this class. Attempts to retrieve the superclass - # object, returns the name if it is not known. - - def superclass - @store.find_class_named(@superclass) || @superclass - end - - ## - # Set the superclass of this class to +superclass+ - - def superclass=(superclass) - raise NoMethodError, "#{full_name} is a module" if module? - @superclass = superclass - end - - def to_s # :nodoc: - if is_alias_for then - "#{self.class.name} #{self.full_name} -> #{is_alias_for}" - else - super - end - end - - ## - # 'module' or 'class' - - def type - module? ? 'module' : 'class' - end - - ## - # Updates the child modules & classes by replacing the ones that are - # aliases through a constant. - # - # The aliased module/class is replaced in the children and in - # RDoc::Store#modules_hash or RDoc::Store#classes_hash - # by a copy that has <tt>RDoc::ClassModule#is_alias_for</tt> set to - # the aliased module/class, and this copy is added to <tt>#aliases</tt> - # of the aliased module/class. - # - # Formatters can use the #non_aliases method to retrieve children that - # are not aliases, for instance to list the namespace content, since - # the aliased modules are included in the constants of the class/module, - # that are listed separately. - - def update_aliases - constants.each do |const| - next unless cm = const.is_alias_for - cm_alias = cm.dup - cm_alias.name = const.name - - # Don't move top-level aliases under Object, they look ugly there - unless RDoc::TopLevel === cm_alias.parent then - cm_alias.parent = self - cm_alias.full_name = nil # force update for new parent - end - - cm_alias.aliases.clear - cm_alias.is_alias_for = cm - - if cm.module? then - @store.modules_hash[cm_alias.full_name] = cm_alias - modules_hash[const.name] = cm_alias - else - @store.classes_hash[cm_alias.full_name] = cm_alias - classes_hash[const.name] = cm_alias - end - - cm.aliases << cm_alias - end - end - - ## - # Deletes from #includes those whose module has been removed from the - # documentation. - #-- - # FIXME: includes are not reliably removed, see _possible_bug test case - - def update_includes - includes.reject! do |include| - mod = include.module - !(String === mod) && @store.modules_hash[mod.full_name].nil? - end - - includes.uniq! - end - - ## - # Deletes from #extends those whose module has been removed from the - # documentation. - #-- - # FIXME: like update_includes, extends are not reliably removed - - def update_extends - extends.reject! do |ext| - mod = ext.module - - !(String === mod) && @store.modules_hash[mod.full_name].nil? - end - - extends.uniq! - end - - def embed_mixins - return unless options.embed_mixins - - includes.each do |include| - next if String === include.module - include.module.method_list.each do |code_object| - add_method(prepare_to_embed(code_object)) - end - include.module.constants.each do |code_object| - add_constant(prepare_to_embed(code_object)) - end - include.module.attributes.each do |code_object| - add_attribute(prepare_to_embed(code_object)) - end - end - - extends.each do |ext| - next if String === ext.module - ext.module.method_list.each do |code_object| - add_method(prepare_to_embed(code_object, true)) - end - ext.module.attributes.each do |code_object| - add_attribute(prepare_to_embed(code_object, true)) - end - end - end - - private - - def prepare_to_embed(code_object, singleton=false) - code_object = code_object.dup - code_object.mixin_from = code_object.parent - code_object.singleton = true if singleton - set_current_section(code_object.section.title, code_object.section.comment) - # add_method and add_attribute will reassign self's visibility back to the method/attribute - # so we need to sync self's visibility with the object's to properly retain that information - self.visibility = code_object.visibility - code_object - end -end diff --git a/lib/rdoc/code_object/constant.rb b/lib/rdoc/code_object/constant.rb deleted file mode 100644 index 12b8be775c..0000000000 --- a/lib/rdoc/code_object/constant.rb +++ /dev/null @@ -1,186 +0,0 @@ -# frozen_string_literal: true -## -# A constant - -class RDoc::Constant < RDoc::CodeObject - - MARSHAL_VERSION = 0 # :nodoc: - - ## - # Sets the module or class this is constant is an alias for. - - attr_writer :is_alias_for - - ## - # The constant's name - - attr_accessor :name - - ## - # The constant's value - - attr_accessor :value - - ## - # The constant's visibility - - attr_accessor :visibility - - ## - # Creates a new constant with +name+, +value+ and +comment+ - - def initialize(name, value, comment) - super() - - @name = name - @value = value - - @is_alias_for = nil - @visibility = :public - - self.comment = comment - end - - ## - # Constants are ordered by name - - def <=> other - return unless self.class === other - - [parent_name, name] <=> [other.parent_name, other.name] - end - - ## - # Constants are equal when their #parent and #name is the same - - def == other - self.class == other.class and - @parent == other.parent and - @name == other.name - end - - ## - # A constant is documented if it has a comment, or is an alias - # for a documented class or module. - - def documented? - return true if super - return false unless @is_alias_for - case @is_alias_for - when String then - found = @store.find_class_or_module @is_alias_for - return false unless found - @is_alias_for = found - end - @is_alias_for.documented? - end - - ## - # Full constant name including namespace - - def full_name - @full_name ||= "#{parent_name}::#{@name}" - end - - ## - # The module or class this constant is an alias for - - def is_alias_for - case @is_alias_for - when String then - found = @store.find_class_or_module @is_alias_for - @is_alias_for = found if found - @is_alias_for - else - @is_alias_for - end - end - - def inspect # :nodoc: - "#<%s:0x%x %s::%s>" % [ - self.class, object_id, - parent_name, @name, - ] - end - - ## - # Dumps this Constant for use by ri. See also #marshal_load - - def marshal_dump - alias_name = case found = is_alias_for - when RDoc::CodeObject then found.full_name - else found - end - - [ MARSHAL_VERSION, - @name, - full_name, - @visibility, - alias_name, - parse(@comment), - @file.relative_name, - parent.name, - parent.class, - section.title, - ] - end - - ## - # Loads this Constant from +array+. For a loaded Constant the following - # methods will return cached values: - # - # * #full_name - # * #parent_name - - def marshal_load array - initialize array[1], nil, array[5] - - @full_name = array[2] - @visibility = array[3] || :public - @is_alias_for = array[4] - # 5 handled above - # 6 handled below - @parent_name = array[7] - @parent_class = array[8] - @section_title = array[9] - - @file = RDoc::TopLevel.new array[6] - end - - ## - # Path to this constant for use with HTML generator output. - - def path - "#{@parent.path}##{@name}" - end - - def pretty_print q # :nodoc: - q.group 2, "[#{self.class.name} #{full_name}", "]" do - unless comment.empty? then - q.breakable - q.text "comment:" - q.breakable - q.pp @comment - end - end - end - - ## - # Sets the store for this class or module and its contained code objects. - - def store= store - super - - @file = @store.add_file @file.full_name if @file - end - - def to_s # :nodoc: - parent_name = parent ? parent.full_name : '(unknown)' - if is_alias_for - "constant #{parent_name}::#@name -> #{is_alias_for}" - else - "constant #{parent_name}::#@name" - end - end - -end diff --git a/lib/rdoc/code_object/context.rb b/lib/rdoc/code_object/context.rb deleted file mode 100644 index c688d562c3..0000000000 --- a/lib/rdoc/code_object/context.rb +++ /dev/null @@ -1,1264 +0,0 @@ -# frozen_string_literal: true -## -# A Context is something that can hold modules, classes, methods, attributes, -# aliases, requires, and includes. Classes, modules, and files are all -# Contexts. - -class RDoc::Context < RDoc::CodeObject - - include Comparable - - ## - # Types of methods - - TYPES = %w[class instance] - - ## - # If a context has these titles it will be sorted in this order. - - TOMDOC_TITLES = [nil, 'Public', 'Internal', 'Deprecated'] # :nodoc: - TOMDOC_TITLES_SORT = TOMDOC_TITLES.sort_by { |title| title.to_s } # :nodoc: - - ## - # Class/module aliases - - attr_reader :aliases - - ## - # All attr* methods - - attr_reader :attributes - - ## - # Block params to be used in the next MethodAttr parsed under this context - - attr_accessor :block_params - - ## - # Constants defined - - attr_reader :constants - - ## - # Sets the current documentation section of documentation - - attr_writer :current_section - - ## - # Files this context is found in - - attr_reader :in_files - - ## - # Modules this context includes - - attr_reader :includes - - ## - # Modules this context is extended with - - attr_reader :extends - - ## - # Methods defined in this context - - attr_reader :method_list - - ## - # Name of this class excluding namespace. See also full_name - - attr_reader :name - - ## - # Files this context requires - - attr_reader :requires - - ## - # Use this section for the next method, attribute or constant added. - - attr_accessor :temporary_section - - ## - # Hash <tt>old_name => [aliases]</tt>, for aliases - # that haven't (yet) been resolved to a method/attribute. - # (Not to be confused with the aliases of the context.) - - attr_accessor :unmatched_alias_lists - - ## - # Aliases that could not be resolved. - - attr_reader :external_aliases - - ## - # Current visibility of this context - - attr_accessor :visibility - - ## - # Current visibility of this line - - attr_writer :current_line_visibility - - ## - # Hash of registered methods. Attributes are also registered here, - # twice if they are RW. - - attr_reader :methods_hash - - ## - # Params to be used in the next MethodAttr parsed under this context - - attr_accessor :params - - ## - # Hash of registered constants. - - attr_reader :constants_hash - - ## - # Creates an unnamed empty context with public current visibility - - def initialize - super - - @in_files = [] - - @name ||= "unknown" - @parent = nil - @visibility = :public - - @current_section = Section.new self, nil, nil - @sections = { nil => @current_section } - @temporary_section = nil - - @classes = {} - @modules = {} - - initialize_methods_etc - end - - ## - # Sets the defaults for methods and so-forth - - def initialize_methods_etc - @method_list = [] - @attributes = [] - @aliases = [] - @requires = [] - @includes = [] - @extends = [] - @constants = [] - @external_aliases = [] - @current_line_visibility = nil - - # This Hash maps a method name to a list of unmatched aliases (aliases of - # a method not yet encountered). - @unmatched_alias_lists = {} - - @methods_hash = {} - @constants_hash = {} - - @params = nil - - @store ||= nil - end - - ## - # Contexts are sorted by full_name - - def <=>(other) - return nil unless RDoc::CodeObject === other - - full_name <=> other.full_name - end - - ## - # Adds an item of type +klass+ with the given +name+ and +comment+ to the - # context. - # - # Currently only RDoc::Extend and RDoc::Include are supported. - - def add klass, name, comment - if RDoc::Extend == klass then - ext = RDoc::Extend.new name, comment - add_extend ext - elsif RDoc::Include == klass then - incl = RDoc::Include.new name, comment - add_include incl - else - raise NotImplementedError, "adding a #{klass} is not implemented" - end - end - - ## - # Adds +an_alias+ that is automatically resolved - - def add_alias an_alias - return an_alias unless @document_self - - method_attr = find_method(an_alias.old_name, an_alias.singleton) || - find_attribute(an_alias.old_name, an_alias.singleton) - - if method_attr then - method_attr.add_alias an_alias, self - else - add_to @external_aliases, an_alias - unmatched_alias_list = - @unmatched_alias_lists[an_alias.pretty_old_name] ||= [] - unmatched_alias_list.push an_alias - end - - an_alias - end - - ## - # Adds +attribute+ if not already there. If it is (as method(s) or attribute), - # updates the comment if it was empty. - # - # The attribute is registered only if it defines a new method. - # For instance, <tt>attr_reader :foo</tt> will not be registered - # if method +foo+ exists, but <tt>attr_accessor :foo</tt> will be registered - # if method +foo+ exists, but <tt>foo=</tt> does not. - - def add_attribute attribute - return attribute unless @document_self - - # mainly to check for redefinition of an attribute as a method - # TODO find a policy for 'attr_reader :foo' + 'def foo=()' - register = false - - key = nil - - if attribute.rw.index 'R' then - key = attribute.pretty_name - known = @methods_hash[key] - - if known then - known.comment = attribute.comment if known.comment.empty? - elsif registered = @methods_hash[attribute.pretty_name + '='] and - RDoc::Attr === registered then - registered.rw = 'RW' - else - @methods_hash[key] = attribute - register = true - end - end - - if attribute.rw.index 'W' then - key = attribute.pretty_name + '=' - known = @methods_hash[key] - - if known then - known.comment = attribute.comment if known.comment.empty? - elsif registered = @methods_hash[attribute.pretty_name] and - RDoc::Attr === registered then - registered.rw = 'RW' - else - @methods_hash[key] = attribute - register = true - end - end - - if register then - attribute.visibility = @visibility - add_to @attributes, attribute - resolve_aliases attribute - end - - attribute - end - - ## - # Adds a class named +given_name+ with +superclass+. - # - # Both +given_name+ and +superclass+ may contain '::', and are - # interpreted relative to the +self+ context. This allows handling correctly - # examples like these: - # class RDoc::Gauntlet < Gauntlet - # module Mod - # class Object # implies < ::Object - # class SubObject < Object # this is _not_ ::Object - # - # Given <tt>class Container::Item</tt> RDoc assumes +Container+ is a module - # unless it later sees <tt>class Container</tt>. +add_class+ automatically - # upgrades +given_name+ to a class in this case. - - def add_class class_type, given_name, superclass = '::Object' - # superclass +nil+ is passed by the C parser in the following cases: - # - registering Object in 1.8 (correct) - # - registering BasicObject in 1.9 (correct) - # - registering RubyVM in 1.9 in iseq.c (incorrect: < Object in vm.c) - # - # If we later find a superclass for a registered class with a nil - # superclass, we must honor it. - - # find the name & enclosing context - if given_name =~ /^:+(\w+)$/ then - full_name = $1 - enclosing = top_level - name = full_name.split(/:+/).last - else - full_name = child_name given_name - - if full_name =~ /^(.+)::(\w+)$/ then - name = $2 - ename = $1 - enclosing = @store.classes_hash[ename] || @store.modules_hash[ename] - # HACK: crashes in actionpack/lib/action_view/helpers/form_helper.rb (metaprogramming) - unless enclosing then - # try the given name at top level (will work for the above example) - enclosing = @store.classes_hash[given_name] || - @store.modules_hash[given_name] - return enclosing if enclosing - # not found: create the parent(s) - names = ename.split('::') - enclosing = self - names.each do |n| - enclosing = enclosing.classes_hash[n] || - enclosing.modules_hash[n] || - enclosing.add_module(RDoc::NormalModule, n) - end - end - else - name = full_name - enclosing = self - end - end - - # fix up superclass - if full_name == 'BasicObject' then - superclass = nil - elsif full_name == 'Object' then - superclass = '::BasicObject' - end - - # find the superclass full name - if superclass then - if superclass =~ /^:+/ then - superclass = $' #' - else - if superclass =~ /^(\w+):+(.+)$/ then - suffix = $2 - mod = find_module_named($1) - superclass = mod.full_name + '::' + suffix if mod - else - mod = find_module_named(superclass) - superclass = mod.full_name if mod - end - end - - # did we believe it was a module? - mod = @store.modules_hash.delete superclass - - upgrade_to_class mod, RDoc::NormalClass, mod.parent if mod - - # e.g., Object < Object - superclass = nil if superclass == full_name - end - - klass = @store.classes_hash[full_name] - - if klass then - # if TopLevel, it may not be registered in the classes: - enclosing.classes_hash[name] = klass - - # update the superclass if needed - if superclass then - existing = klass.superclass - existing = existing.full_name unless existing.is_a?(String) if existing - if existing.nil? || - (existing == 'Object' && superclass != 'Object') then - klass.superclass = superclass - end - end - else - # this is a new class - mod = @store.modules_hash.delete full_name - - if mod then - klass = upgrade_to_class mod, RDoc::NormalClass, enclosing - - klass.superclass = superclass unless superclass.nil? - else - klass = class_type.new name, superclass - - enclosing.add_class_or_module(klass, enclosing.classes_hash, - @store.classes_hash) - end - end - - klass.parent = self - - klass - end - - ## - # Adds the class or module +mod+ to the modules or - # classes Hash +self_hash+, and to +all_hash+ (either - # <tt>TopLevel::modules_hash</tt> or <tt>TopLevel::classes_hash</tt>), - # unless #done_documenting is +true+. Sets the #parent of +mod+ - # to +self+, and its #section to #current_section. Returns +mod+. - - def add_class_or_module mod, self_hash, all_hash - mod.section = current_section # TODO declaring context? something is - # wrong here... - mod.parent = self - mod.full_name = nil - mod.store = @store - - unless @done_documenting then - self_hash[mod.name] = mod - # this must be done AFTER adding mod to its parent, so that the full - # name is correct: - all_hash[mod.full_name] = mod - if @store.unmatched_constant_alias[mod.full_name] then - to, file = @store.unmatched_constant_alias[mod.full_name] - add_module_alias mod, mod.name, to, file - end - end - - mod - end - - ## - # Adds +constant+ if not already there. If it is, updates the comment, - # value and/or is_alias_for of the known constant if they were empty/nil. - - def add_constant constant - return constant unless @document_self - - # HACK: avoid duplicate 'PI' & 'E' in math.c (1.8.7 source code) - # (this is a #ifdef: should be handled by the C parser) - known = @constants_hash[constant.name] - - if known then - known.comment = constant.comment if known.comment.empty? - - known.value = constant.value if - known.value.nil? or known.value.strip.empty? - - known.is_alias_for ||= constant.is_alias_for - else - @constants_hash[constant.name] = constant - add_to @constants, constant - end - - constant - end - - ## - # Adds included module +include+ which should be an RDoc::Include - - def add_include include - add_to @includes, include - - include - end - - ## - # Adds extension module +ext+ which should be an RDoc::Extend - - def add_extend ext - add_to @extends, ext - - ext - end - - ## - # Adds +method+ if not already there. If it is (as method or attribute), - # updates the comment if it was empty. - - def add_method method - return method unless @document_self - - # HACK: avoid duplicate 'new' in io.c & struct.c (1.8.7 source code) - key = method.pretty_name - known = @methods_hash[key] - - if known then - if @store then # otherwise we are loading - known.comment = method.comment if known.comment.empty? - previously = ", previously in #{known.file}" unless - method.file == known.file - @store.rdoc.options.warn \ - "Duplicate method #{known.full_name} in #{method.file}#{previously}" - end - else - @methods_hash[key] = method - if @current_line_visibility - method.visibility, @current_line_visibility = @current_line_visibility, nil - else - method.visibility = @visibility - end - add_to @method_list, method - resolve_aliases method - end - - method - end - - ## - # Adds a module named +name+. If RDoc already knows +name+ is a class then - # that class is returned instead. See also #add_class. - - def add_module(class_type, name) - mod = @classes[name] || @modules[name] - return mod if mod - - full_name = child_name name - mod = @store.modules_hash[full_name] || class_type.new(name) - - add_class_or_module mod, @modules, @store.modules_hash - end - - ## - # Adds a module by +RDoc::NormalModule+ instance. See also #add_module. - - def add_module_by_normal_module(mod) - add_class_or_module mod, @modules, @store.modules_hash - end - - ## - # Adds an alias from +from+ (a class or module) to +name+ which was defined - # in +file+. - - def add_module_alias from, from_name, to, file - return from if @done_documenting - - to_full_name = child_name to.name - - # if we already know this name, don't register an alias: - # see the metaprogramming in lib/active_support/basic_object.rb, - # where we already know BasicObject is a class when we find - # BasicObject = BlankSlate - return from if @store.find_class_or_module to_full_name - - unless from - @store.unmatched_constant_alias[child_name(from_name)] = [to, file] - return to - end - - new_to = from.dup - new_to.name = to.name - new_to.full_name = nil - - if new_to.module? then - @store.modules_hash[to_full_name] = new_to - @modules[to.name] = new_to - else - @store.classes_hash[to_full_name] = new_to - @classes[to.name] = new_to - end - - # Registers a constant for this alias. The constant value and comment - # will be updated later, when the Ruby parser adds the constant - const = RDoc::Constant.new to.name, nil, new_to.comment - const.record_location file - const.is_alias_for = from - add_constant const - - new_to - end - - ## - # Adds +require+ to this context's top level - - def add_require(require) - return require unless @document_self - - if RDoc::TopLevel === self then - add_to @requires, require - else - parent.add_require require - end - end - - ## - # Returns a section with +title+, creating it if it doesn't already exist. - # +comment+ will be appended to the section's comment. - # - # A section with a +title+ of +nil+ will return the default section. - # - # See also RDoc::Context::Section - - def add_section title, comment = nil - if section = @sections[title] then - section.add_comment comment if comment - else - section = Section.new self, title, comment - @sections[title] = section - end - - section - end - - ## - # Adds +thing+ to the collection +array+ - - def add_to array, thing - array << thing if @document_self - - thing.parent = self - thing.store = @store if @store - thing.section = current_section - end - - ## - # Is there any content? - # - # This means any of: comment, aliases, methods, attributes, external - # aliases, require, constant. - # - # Includes and extends are also checked unless <tt>includes == false</tt>. - - def any_content(includes = true) - @any_content ||= !( - @comment.empty? && - @method_list.empty? && - @attributes.empty? && - @aliases.empty? && - @external_aliases.empty? && - @requires.empty? && - @constants.empty? - ) - @any_content || (includes && !(@includes + @extends).empty? ) - end - - ## - # Creates the full name for a child with +name+ - - def child_name name - if name =~ /^:+/ - $' #' - elsif RDoc::TopLevel === self then - name - else - "#{self.full_name}::#{name}" - end - end - - ## - # Class attributes - - def class_attributes - @class_attributes ||= attributes.select { |a| a.singleton } - end - - ## - # Class methods - - def class_method_list - @class_method_list ||= method_list.select { |a| a.singleton } - end - - ## - # Array of classes in this context - - def classes - @classes.values - end - - ## - # All classes and modules in this namespace - - def classes_and_modules - classes + modules - end - - ## - # Hash of classes keyed by class name - - def classes_hash - @classes - end - - ## - # The current documentation section that new items will be added to. If - # temporary_section is available it will be used. - - def current_section - if section = @temporary_section then - @temporary_section = nil - else - section = @current_section - end - - section - end - - ## - # Is part of this thing was defined in +file+? - - def defined_in?(file) - @in_files.include?(file) - end - - def display(method_attr) # :nodoc: - if method_attr.is_a? RDoc::Attr - "#{method_attr.definition} #{method_attr.pretty_name}" - else - "method #{method_attr.pretty_name}" - end - end - - ## - # Iterator for ancestors for duck-typing. Does nothing. See - # RDoc::ClassModule#each_ancestor. - # - # This method exists to make it easy to work with Context subclasses that - # aren't part of RDoc. - - def each_ancestor(&_) # :nodoc: - end - - ## - # Iterator for attributes - - def each_attribute # :yields: attribute - @attributes.each { |a| yield a } - end - - ## - # Iterator for classes and modules - - def each_classmodule(&block) # :yields: module - classes_and_modules.sort.each(&block) - end - - ## - # Iterator for constants - - def each_constant # :yields: constant - @constants.each {|c| yield c} - end - - ## - # Iterator for included modules - - def each_include # :yields: include - @includes.each do |i| yield i end - end - - ## - # Iterator for extension modules - - def each_extend # :yields: extend - @extends.each do |e| yield e end - end - - ## - # Iterator for methods - - def each_method # :yields: method - return enum_for __method__ unless block_given? - - @method_list.sort.each { |m| yield m } - end - - ## - # Iterator for each section's contents sorted by title. The +section+, the - # section's +constants+ and the sections +attributes+ are yielded. The - # +constants+ and +attributes+ collections are sorted. - # - # To retrieve methods in a section use #methods_by_type with the optional - # +section+ parameter. - # - # NOTE: Do not edit collections yielded by this method - - def each_section # :yields: section, constants, attributes - return enum_for __method__ unless block_given? - - constants = @constants.group_by do |constant| constant.section end - attributes = @attributes.group_by do |attribute| attribute.section end - - constants.default = [] - attributes.default = [] - - sort_sections.each do |section| - yield section, constants[section].select(&:display?).sort, attributes[section].select(&:display?).sort - end - end - - ## - # Finds an attribute +name+ with singleton value +singleton+. - - def find_attribute(name, singleton) - name = $1 if name =~ /^(.*)=$/ - @attributes.find { |a| a.name == name && a.singleton == singleton } - end - - ## - # Finds an attribute with +name+ in this context - - def find_attribute_named(name) - case name - when /\A#/ then - find_attribute name[1..-1], false - when /\A::/ then - find_attribute name[2..-1], true - else - @attributes.find { |a| a.name == name } - end - end - - ## - # Finds a class method with +name+ in this context - - def find_class_method_named(name) - @method_list.find { |meth| meth.singleton && meth.name == name } - end - - ## - # Finds a constant with +name+ in this context - - def find_constant_named(name) - @constants.find do |m| - m.name == name || m.full_name == name - end - end - - ## - # Find a module at a higher scope - - def find_enclosing_module_named(name) - parent && parent.find_module_named(name) - end - - ## - # Finds an external alias +name+ with singleton value +singleton+. - - def find_external_alias(name, singleton) - @external_aliases.find { |m| m.name == name && m.singleton == singleton } - end - - ## - # Finds an external alias with +name+ in this context - - def find_external_alias_named(name) - case name - when /\A#/ then - find_external_alias name[1..-1], false - when /\A::/ then - find_external_alias name[2..-1], true - else - @external_aliases.find { |a| a.name == name } - end - end - - ## - # Finds a file with +name+ in this context - - def find_file_named name - @store.find_file_named name - end - - ## - # Finds an instance method with +name+ in this context - - def find_instance_method_named(name) - @method_list.find { |meth| !meth.singleton && meth.name == name } - end - - ## - # Finds a method, constant, attribute, external alias, module or file - # named +symbol+ in this context. - - def find_local_symbol(symbol) - find_method_named(symbol) or - find_constant_named(symbol) or - find_attribute_named(symbol) or - find_external_alias_named(symbol) or - find_module_named(symbol) or - find_file_named(symbol) - end - - ## - # Finds a method named +name+ with singleton value +singleton+. - - def find_method(name, singleton) - @method_list.find { |m| - if m.singleton - m.name == name && m.singleton == singleton - else - m.name == name && !m.singleton && !singleton - end - } - end - - ## - # Finds a instance or module method with +name+ in this context - - def find_method_named(name) - case name - when /\A#/ then - find_method name[1..-1], false - when /\A::/ then - find_method name[2..-1], true - else - @method_list.find { |meth| meth.name == name } - end - end - - ## - # Find a module with +name+ using ruby's scoping rules - - def find_module_named(name) - res = @modules[name] || @classes[name] - return res if res - return self if self.name == name - find_enclosing_module_named name - end - - ## - # Look up +symbol+, first as a module, then as a local symbol. - - def find_symbol(symbol) - find_symbol_module(symbol) || find_local_symbol(symbol) - end - - ## - # Look up a module named +symbol+. - - def find_symbol_module(symbol) - result = nil - - # look for a class or module 'symbol' - case symbol - when /^::/ then - result = @store.find_class_or_module symbol - when /^(\w+):+(.+)$/ - suffix = $2 - top = $1 - searched = self - while searched do - mod = searched.find_module_named(top) - break unless mod - result = @store.find_class_or_module "#{mod.full_name}::#{suffix}" - break if result || searched.is_a?(RDoc::TopLevel) - searched = searched.parent - end - else - searched = self - while searched do - result = searched.find_module_named(symbol) - break if result || searched.is_a?(RDoc::TopLevel) - searched = searched.parent - end - end - - result - end - - ## - # The full name for this context. This method is overridden by subclasses. - - def full_name - '(unknown)' - end - - ## - # Does this context and its methods and constants all have documentation? - # - # (Yes, fully documented doesn't mean everything.) - - def fully_documented? - documented? and - attributes.all? { |a| a.documented? } and - method_list.all? { |m| m.documented? } and - constants.all? { |c| c.documented? } - end - - ## - # URL for this with a +prefix+ - - def http_url(prefix) - path = name_for_path - path = path.gsub(/<<\s*(\w*)/, 'from-\1') if path =~ /<</ - path = [prefix] + path.split('::') - - File.join(*path.compact) + '.html' - end - - ## - # Instance attributes - - def instance_attributes - @instance_attributes ||= attributes.reject { |a| a.singleton } - end - - ## - # Instance methods - - def instance_methods - @instance_methods ||= method_list.reject { |a| a.singleton } - end - - ## - # Instance methods - #-- - # TODO remove this later - - def instance_method_list - warn '#instance_method_list is obsoleted, please use #instance_methods' - @instance_methods ||= method_list.reject { |a| a.singleton } - end - - ## - # Breaks method_list into a nested hash by type (<tt>'class'</tt> or - # <tt>'instance'</tt>) and visibility (+:public+, +:protected+, +:private+). - # - # If +section+ is provided only methods in that RDoc::Context::Section will - # be returned. - - def methods_by_type section = nil - methods = {} - - TYPES.each do |type| - visibilities = {} - RDoc::VISIBILITIES.each do |vis| - visibilities[vis] = [] - end - - methods[type] = visibilities - end - - each_method do |method| - next if section and not method.section == section - methods[method.type][method.visibility] << method - end - - methods - end - - ## - # Yields AnyMethod and Attr entries matching the list of names in +methods+. - - def methods_matching(methods, singleton = false, &block) - (@method_list + @attributes).each do |m| - yield m if methods.include?(m.name) and m.singleton == singleton - end - - each_ancestor do |parent| - parent.methods_matching(methods, singleton, &block) - end - end - - ## - # Array of modules in this context - - def modules - @modules.values - end - - ## - # Hash of modules keyed by module name - - def modules_hash - @modules - end - - ## - # Name to use to generate the url. - # <tt>#full_name</tt> by default. - - def name_for_path - full_name - end - - ## - # Changes the visibility for new methods to +visibility+ - - def ongoing_visibility=(visibility) - @visibility = visibility - end - - ## - # Record +top_level+ as a file +self+ is in. - - def record_location(top_level) - @in_files << top_level unless @in_files.include?(top_level) - end - - ## - # Should we remove this context from the documentation? - # - # The answer is yes if: - # * #received_nodoc is +true+ - # * #any_content is +false+ (not counting includes) - # * All #includes are modules (not a string), and their module has - # <tt>#remove_from_documentation? == true</tt> - # * All classes and modules have <tt>#remove_from_documentation? == true</tt> - - def remove_from_documentation? - @remove_from_documentation ||= - @received_nodoc && - !any_content(false) && - @includes.all? { |i| !i.module.is_a?(String) && i.module.remove_from_documentation? } && - classes_and_modules.all? { |cm| cm.remove_from_documentation? } - end - - ## - # Removes methods and attributes with a visibility less than +min_visibility+. - #-- - # TODO mark the visibility of attributes in the template (if not public?) - - def remove_invisible min_visibility - return if [:private, :nodoc].include? min_visibility - remove_invisible_in @method_list, min_visibility - remove_invisible_in @attributes, min_visibility - remove_invisible_in @constants, min_visibility - end - - ## - # Only called when min_visibility == :public or :private - - def remove_invisible_in array, min_visibility # :nodoc: - if min_visibility == :public then - array.reject! { |e| - e.visibility != :public and not e.force_documentation - } - else - array.reject! { |e| - e.visibility == :private and not e.force_documentation - } - end - end - - ## - # Tries to resolve unmatched aliases when a method or attribute has just - # been added. - - def resolve_aliases added - # resolve any pending unmatched aliases - key = added.pretty_name - unmatched_alias_list = @unmatched_alias_lists[key] - return unless unmatched_alias_list - unmatched_alias_list.each do |unmatched_alias| - added.add_alias unmatched_alias, self - @external_aliases.delete unmatched_alias - end - @unmatched_alias_lists.delete key - end - - ## - # Returns RDoc::Context::Section objects referenced in this context for use - # in a table of contents. - - def section_contents - used_sections = {} - - each_method do |method| - next unless method.display? - - used_sections[method.section] = true - end - - # order found sections - sections = sort_sections.select do |section| - used_sections[section] - end - - # only the default section is used - return [] if - sections.length == 1 and not sections.first.title - - sections - end - - ## - # Sections in this context - - def sections - @sections.values - end - - def sections_hash # :nodoc: - @sections - end - - ## - # Sets the current section to a section with +title+. See also #add_section - - def set_current_section title, comment - @current_section = add_section title, comment - end - - ## - # Given an array +methods+ of method names, set the visibility of each to - # +visibility+ - - def set_visibility_for(methods, visibility, singleton = false) - methods_matching methods, singleton do |m| - m.visibility = visibility - end - end - - ## - # Given an array +names+ of constants, set the visibility of each constant to - # +visibility+ - - def set_constant_visibility_for(names, visibility) - names.each do |name| - constant = @constants_hash[name] or next - constant.visibility = visibility - end - end - - ## - # Sorts sections alphabetically (default) or in TomDoc fashion (none, - # Public, Internal, Deprecated) - - def sort_sections - titles = @sections.map { |title, _| title } - - if titles.length > 1 and - TOMDOC_TITLES_SORT == - (titles | TOMDOC_TITLES).sort_by { |title| title.to_s } then - @sections.values_at(*TOMDOC_TITLES).compact - else - @sections.sort_by { |title, _| - title.to_s - }.map { |_, section| - section - } - end - end - - def to_s # :nodoc: - "#{self.class.name} #{self.full_name}" - end - - ## - # Return the TopLevel that owns us - #-- - # FIXME we can be 'owned' by several TopLevel (see #record_location & - # #in_files) - - def top_level - return @top_level if defined? @top_level - @top_level = self - @top_level = @top_level.parent until RDoc::TopLevel === @top_level - @top_level - end - - ## - # Upgrades NormalModule +mod+ in +enclosing+ to a +class_type+ - - def upgrade_to_class mod, class_type, enclosing - enclosing.modules_hash.delete mod.name - - klass = RDoc::ClassModule.from_module class_type, mod - klass.store = @store - - # if it was there, then we keep it even if done_documenting - @store.classes_hash[mod.full_name] = klass - enclosing.classes_hash[mod.name] = klass - - klass - end - - autoload :Section, "#{__dir__}/context/section" - -end diff --git a/lib/rdoc/code_object/context/section.rb b/lib/rdoc/code_object/context/section.rb deleted file mode 100644 index aecd4e0213..0000000000 --- a/lib/rdoc/code_object/context/section.rb +++ /dev/null @@ -1,233 +0,0 @@ -# frozen_string_literal: true -require 'cgi/util' - -## -# A section of documentation like: -# -# # :section: The title -# # The body -# -# Sections can be referenced multiple times and will be collapsed into a -# single section. - -class RDoc::Context::Section - - include RDoc::Text - - MARSHAL_VERSION = 0 # :nodoc: - - ## - # Section comment - - attr_reader :comment - - ## - # Section comments - - attr_reader :comments - - ## - # Context this Section lives in - - attr_reader :parent - - ## - # Section title - - attr_reader :title - - ## - # Creates a new section with +title+ and +comment+ - - def initialize parent, title, comment - @parent = parent - @title = title ? title.strip : title - - @comments = [] - - add_comment comment - end - - ## - # Sections are equal when they have the same #title - - def == other - self.class === other and @title == other.title - end - - alias eql? == - - ## - # Adds +comment+ to this section - - def add_comment comment - comment = extract_comment comment - - return if comment.empty? - - case comment - when RDoc::Comment then - @comments << comment - when RDoc::Markup::Document then - @comments.concat comment.parts - when Array then - @comments.concat comment - else - raise TypeError, "unknown comment type: #{comment.inspect}" - end - end - - ## - # Anchor reference for linking to this section - - def aref - title = @title || '[untitled]' - - CGI.escape(title).gsub('%', '-').sub(/^-/, '') - end - - ## - # Extracts the comment for this section from the original comment block. - # If the first line contains :section:, strip it and use the rest. - # Otherwise remove lines up to the line containing :section:, and look - # for those lines again at the end and remove them. This lets us write - # - # # :section: The title - # # The body - - def extract_comment comment - case comment - when Array then - comment.map do |c| - extract_comment c - end - when nil - RDoc::Comment.new '' - when RDoc::Comment then - if comment.text =~ /^#[ \t]*:section:.*\n/ then - start = $` - rest = $' - - comment.text = if start.empty? then - rest - else - rest.sub(/#{start.chomp}\Z/, '') - end - end - - comment - when RDoc::Markup::Document then - comment - else - raise TypeError, "unknown comment #{comment.inspect}" - end - end - - def inspect # :nodoc: - "#<%s:0x%x %p>" % [self.class, object_id, title] - end - - def hash # :nodoc: - @title.hash - end - - ## - # The files comments in this section come from - - def in_files - return [] if @comments.empty? - - case @comments - when Array then - @comments.map do |comment| - comment.file - end - when RDoc::Markup::Document then - @comment.parts.map do |document| - document.file - end - else - raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" - end - end - - ## - # Serializes this Section. The title and parsed comment are saved, but not - # the section parent which must be restored manually. - - def marshal_dump - [ - MARSHAL_VERSION, - @title, - parse, - ] - end - - ## - # De-serializes this Section. The section parent must be restored manually. - - def marshal_load array - @parent = nil - - @title = array[1] - @comments = array[2] - end - - ## - # Parses +comment_location+ into an RDoc::Markup::Document composed of - # multiple RDoc::Markup::Documents with their file set. - - def parse - case @comments - when String then - super - when Array then - docs = @comments.map do |comment, location| - doc = super comment - doc.file = location if location - doc - end - - RDoc::Markup::Document.new(*docs) - when RDoc::Comment then - doc = super @comments.text, comments.format - doc.file = @comments.location - doc - when RDoc::Markup::Document then - return @comments - else - raise ArgumentError, "unknown comment class #{comments.class}" - end - end - - ## - # The section's title, or 'Top Section' if the title is nil. - # - # This is used by the table of contents template so the name is silly. - - def plain_html - @title || 'Top Section' - end - - ## - # Removes a comment from this section if it is from the same file as - # +comment+ - - def remove_comment comment - return if @comments.empty? - - case @comments - when Array then - @comments.delete_if do |my_comment| - my_comment.file == comment.file - end - when RDoc::Markup::Document then - @comments.parts.delete_if do |document| - document.file == comment.file.name - end - else - raise RDoc::Error, "BUG: unknown comment class #{@comments.class}" - end - end - -end diff --git a/lib/rdoc/code_object/extend.rb b/lib/rdoc/code_object/extend.rb deleted file mode 100644 index 7d57433de6..0000000000 --- a/lib/rdoc/code_object/extend.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true -## -# A Module extension to a class with \#extend -# -# RDoc::Extend.new 'Enumerable', 'comment ...' - -class RDoc::Extend < RDoc::Mixin - -end diff --git a/lib/rdoc/code_object/ghost_method.rb b/lib/rdoc/code_object/ghost_method.rb deleted file mode 100644 index 25f951e35e..0000000000 --- a/lib/rdoc/code_object/ghost_method.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -## -# GhostMethod represents a method referenced only by a comment - -class RDoc::GhostMethod < RDoc::AnyMethod -end diff --git a/lib/rdoc/code_object/include.rb b/lib/rdoc/code_object/include.rb deleted file mode 100644 index c3e0d45e47..0000000000 --- a/lib/rdoc/code_object/include.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true -## -# A Module included in a class with \#include -# -# RDoc::Include.new 'Enumerable', 'comment ...' - -class RDoc::Include < RDoc::Mixin - -end diff --git a/lib/rdoc/code_object/meta_method.rb b/lib/rdoc/code_object/meta_method.rb deleted file mode 100644 index 8c95a0f78c..0000000000 --- a/lib/rdoc/code_object/meta_method.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -## -# MetaMethod represents a meta-programmed method - -class RDoc::MetaMethod < RDoc::AnyMethod -end diff --git a/lib/rdoc/code_object/method_attr.rb b/lib/rdoc/code_object/method_attr.rb deleted file mode 100644 index 27e6599bc1..0000000000 --- a/lib/rdoc/code_object/method_attr.rb +++ /dev/null @@ -1,418 +0,0 @@ -# frozen_string_literal: true -## -# Abstract class representing either a method or an attribute. - -class RDoc::MethodAttr < RDoc::CodeObject - - include Comparable - - ## - # Name of this method/attribute. - - attr_accessor :name - - ## - # public, protected, private - - attr_accessor :visibility - - ## - # Is this a singleton method/attribute? - - attr_accessor :singleton - - ## - # Source file token stream - - attr_reader :text - - ## - # Array of other names for this method/attribute - - attr_reader :aliases - - ## - # The method/attribute we're aliasing - - attr_accessor :is_alias_for - - #-- - # The attributes below are for AnyMethod only. - # They are left here for the time being to - # allow ri to operate. - # TODO modify ri to avoid calling these on attributes. - #++ - - ## - # Parameters yielded by the called block - - attr_reader :block_params - - ## - # Parameters for this method - - attr_accessor :params - - ## - # Different ways to call this method - - attr_accessor :call_seq - - ## - # The call_seq or the param_seq with method name, if there is no call_seq. - - attr_reader :arglists - - ## - # Pretty parameter list for this method - - attr_reader :param_seq - - - ## - # Creates a new MethodAttr from token stream +text+ and method or attribute - # name +name+. - # - # Usually this is called by super from a subclass. - - def initialize text, name - super() - - @text = text - @name = name - - @aliases = [] - @is_alias_for = nil - @parent_name = nil - @singleton = nil - @visibility = :public - @see = false - - @arglists = nil - @block_params = nil - @call_seq = nil - @param_seq = nil - @params = nil - end - - ## - # Resets cached data for the object so it can be rebuilt by accessor methods - - def initialize_copy other # :nodoc: - @full_name = nil - end - - def initialize_visibility # :nodoc: - super - @see = nil - end - - ## - # Order by #singleton then #name - - def <=>(other) - return unless other.respond_to?(:singleton) && - other.respond_to?(:name) - - [ @singleton ? 0 : 1, name] <=> - [other.singleton ? 0 : 1, other.name] - end - - def == other # :nodoc: - equal?(other) or self.class == other.class and full_name == other.full_name - end - - ## - # A method/attribute is documented if any of the following is true: - # - it was marked with :nodoc:; - # - it has a comment; - # - it is an alias for a documented method; - # - it has a +#see+ method that is documented. - - def documented? - super or - (is_alias_for and is_alias_for.documented?) or - (see and see.documented?) - end - - ## - # A method/attribute to look at, - # in particular if this method/attribute has no documentation. - # - # It can be a method/attribute of the superclass or of an included module, - # including the Kernel module, which is always appended to the included - # modules. - # - # Returns +nil+ if there is no such method/attribute. - # The +#is_alias_for+ method/attribute, if any, is not included. - # - # Templates may generate a "see also ..." if this method/attribute - # has documentation, and "see ..." if it does not. - - def see - @see = find_see if @see == false - @see - end - - ## - # Sets the store for this class or module and its contained code objects. - - def store= store - super - - @file = @store.add_file @file.full_name if @file - end - - def find_see # :nodoc: - return nil if singleton || is_alias_for - - # look for the method - other = find_method_or_attribute name - return other if other - - # if it is a setter, look for a getter - return nil unless name =~ /[a-z_]=$/i # avoid == or === - return find_method_or_attribute name[0..-2] - end - - def find_method_or_attribute name # :nodoc: - return nil unless parent.respond_to? :ancestors - - searched = parent.ancestors - kernel = @store.modules_hash['Kernel'] - - searched << kernel if kernel && - parent != kernel && !searched.include?(kernel) - - searched.each do |ancestor| - next if String === ancestor - next if parent == ancestor - - other = ancestor.find_method_named('#' + name) || - ancestor.find_attribute_named(name) - - return other if other - end - - nil - end - - ## - # Abstract method. Contexts in their building phase call this - # to register a new alias for this known method/attribute. - # - # - creates a new AnyMethod/Attribute named <tt>an_alias.new_name</tt>; - # - adds +self+ as an alias for the new method or attribute - # - adds the method or attribute to #aliases - # - adds the method or attribute to +context+. - - def add_alias(an_alias, context) - raise NotImplementedError - end - - ## - # HTML fragment reference for this method - - def aref - type = singleton ? 'c' : 'i' - # % characters are not allowed in html names => dash instead - "#{aref_prefix}-#{type}-#{html_name}" - end - - ## - # Prefix for +aref+, defined by subclasses. - - def aref_prefix - raise NotImplementedError - end - - ## - # Attempts to sanitize the content passed by the Ruby parser: - # remove outer parentheses, etc. - - def block_params=(value) - # 'yield.to_s' or 'assert yield, msg' - return @block_params = '' if value =~ /^[\.,]/ - - # remove trailing 'if/unless ...' - return @block_params = '' if value =~ /^(if|unless)\s/ - - value = $1.strip if value =~ /^(.+)\s(if|unless)\s/ - - # outer parentheses - value = $1 if value =~ /^\s*\((.*)\)\s*$/ - value = value.strip - - # proc/lambda - return @block_params = $1 if value =~ /^(proc|lambda)(\s*\{|\sdo)/ - - # surrounding +...+ or [...] - value = $1.strip if value =~ /^\+(.*)\+$/ - value = $1.strip if value =~ /^\[(.*)\]$/ - - return @block_params = '' if value.empty? - - # global variable - return @block_params = 'str' if value =~ /^\$[&0-9]$/ - - # wipe out array/hash indices - value.gsub!(/(\w)\[[^\[]+\]/, '\1') - - # remove @ from class/instance variables - value.gsub!(/@@?([a-z0-9_]+)/, '\1') - - # method calls => method name - value.gsub!(/([A-Z:a-z0-9_]+)\.([a-z0-9_]+)(\s*\(\s*[a-z0-9_.,\s]*\s*\)\s*)?/) do - case $2 - when 'to_s' then $1 - when 'const_get' then 'const' - when 'new' then - $1.split('::').last. # ClassName => class_name - gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). - gsub(/([a-z\d])([A-Z])/, '\1_\2'). - downcase - else - $2 - end - end - - # class prefixes - value.gsub!(/[A-Za-z0-9_:]+::/, '') - - # simple expressions - value = $1 if value =~ /^([a-z0-9_]+)\s*[-*+\/]/ - - @block_params = value.strip - end - - ## - # HTML id-friendly method/attribute name - - def html_name - require 'cgi/util' - - CGI.escape(@name.gsub('-', '-2D')).gsub('%', '-').sub(/^-/, '') - end - - ## - # Full method/attribute name including namespace - - def full_name - @full_name ||= "#{parent_name}#{pretty_name}" - end - - def inspect # :nodoc: - alias_for = @is_alias_for ? " (alias for #{@is_alias_for.name})" : nil - visibility = self.visibility - visibility = "forced #{visibility}" if force_documentation - "#<%s:0x%x %s (%s)%s>" % [ - self.class, object_id, - full_name, - visibility, - alias_for, - ] - end - - ## - # '::' for a class method/attribute, '#' for an instance method. - - def name_prefix - @singleton ? '::' : '#' - end - - ## - # Name for output to HTML. For class methods the full name with a "." is - # used like +SomeClass.method_name+. For instance methods the class name is - # used if +context+ does not match the parent. - # - # This is to help prevent people from using :: to call class methods. - - def output_name context - return "#{name_prefix}#{@name}" if context == parent - - "#{parent_name}#{@singleton ? '.' : '#'}#{@name}" - end - - ## - # Method/attribute name with class/instance indicator - - def pretty_name - "#{name_prefix}#{@name}" - end - - ## - # Type of method/attribute (class or instance) - - def type - singleton ? 'class' : 'instance' - end - - ## - # Path to this method for use with HTML generator output. - - def path - "#{@parent.path}##{aref}" - end - - ## - # Name of our parent with special handling for un-marshaled methods - - def parent_name - @parent_name || super - end - - def pretty_print q # :nodoc: - alias_for = - if @is_alias_for.respond_to? :name then - "alias for #{@is_alias_for.name}" - elsif Array === @is_alias_for then - "alias for #{@is_alias_for.last}" - end - - q.group 2, "[#{self.class.name} #{full_name} #{visibility}", "]" do - if alias_for then - q.breakable - q.text alias_for - end - - if text then - q.breakable - q.text "text:" - q.breakable - q.pp @text - end - - unless comment.empty? then - q.breakable - q.text "comment:" - q.breakable - q.pp @comment - end - end - end - - ## - # Used by RDoc::Generator::JsonIndex to create a record for the search - # engine. - - def search_record - [ - @name, - full_name, - @name, - @parent.full_name, - path, - params, - snippet(@comment), - ] - end - - def to_s # :nodoc: - if @is_alias_for - "#{self.class.name}: #{full_name} -> #{is_alias_for}" - else - "#{self.class.name}: #{full_name}" - end - end - -end diff --git a/lib/rdoc/code_object/mixin.rb b/lib/rdoc/code_object/mixin.rb deleted file mode 100644 index fa8faefc15..0000000000 --- a/lib/rdoc/code_object/mixin.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true -## -# A Mixin adds features from a module into another context. RDoc::Include and -# RDoc::Extend are both mixins. - -class RDoc::Mixin < RDoc::CodeObject - - ## - # Name of included module - - attr_accessor :name - - ## - # Creates a new Mixin for +name+ with +comment+ - - def initialize(name, comment) - super() - @name = name - self.comment = comment - @module = nil # cache for module if found - end - - ## - # Mixins are sorted by name - - def <=> other - return unless self.class === other - - name <=> other.name - end - - def == other # :nodoc: - self.class === other and @name == other.name - end - - alias eql? == # :nodoc: - - ## - # Full name based on #module - - def full_name - m = self.module - RDoc::ClassModule === m ? m.full_name : @name - end - - def hash # :nodoc: - [@name, self.module].hash - end - - def inspect # :nodoc: - "#<%s:0x%x %s.%s %s>" % [ - self.class, - object_id, - parent_name, self.class.name.downcase, @name, - ] - end - - ## - # Attempts to locate the included module object. Returns the name if not - # known. - # - # The scoping rules of Ruby to resolve the name of an included module are: - # - first look into the children of the current context; - # - if not found, look into the children of included modules, - # in reverse inclusion order; - # - if still not found, go up the hierarchy of names. - # - # This method has <code>O(n!)</code> behavior when the module calling - # include is referencing nonexistent modules. Avoid calling #module until - # after all the files are parsed. This behavior is due to ruby's constant - # lookup behavior. - # - # As of the beginning of October, 2011, no gem includes nonexistent modules. - - def module - return @module if @module - - # search the current context - return @name unless parent - full_name = parent.child_name(@name) - @module = @store.modules_hash[full_name] - return @module if @module - return @name if @name =~ /^::/ - - # search the includes before this one, in reverse order - searched = parent.includes.take_while { |i| i != self }.reverse - searched.each do |i| - inc = i.module - next if String === inc - full_name = inc.child_name(@name) - @module = @store.modules_hash[full_name] - return @module if @module - end - - # go up the hierarchy of names - up = parent.parent - while up - full_name = up.child_name(@name) - @module = @store.modules_hash[full_name] - return @module if @module - up = up.parent - end - - @name - end - - ## - # Sets the store for this class or module and its contained code objects. - - def store= store - super - - @file = @store.add_file @file.full_name if @file - end - - def to_s # :nodoc: - "#{self.class.name.downcase} #@name in: #{parent}" - end - -end diff --git a/lib/rdoc/code_object/normal_class.rb b/lib/rdoc/code_object/normal_class.rb deleted file mode 100644 index aa340b5d15..0000000000 --- a/lib/rdoc/code_object/normal_class.rb +++ /dev/null @@ -1,92 +0,0 @@ -# frozen_string_literal: true -## -# A normal class, neither singleton nor anonymous - -class RDoc::NormalClass < RDoc::ClassModule - - ## - # The ancestors of this class including modules. Unlike Module#ancestors, - # this class is not included in the result. The result will contain both - # RDoc::ClassModules and Strings. - - def ancestors - if String === superclass then - super << superclass - elsif superclass then - ancestors = super - ancestors << superclass - ancestors.concat superclass.ancestors - else - super - end - end - - def aref_prefix # :nodoc: - 'class' - end - - ## - # The definition of this class, <tt>class MyClassName</tt> - - def definition - "class #{full_name}" - end - - def direct_ancestors - superclass ? super + [superclass] : super - end - - def inspect # :nodoc: - superclass = @superclass ? " < #{@superclass}" : nil - "<%s:0x%x class %s%s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ - self.class, object_id, - full_name, superclass, @includes, @extends, @attributes, @method_list, @aliases - ] - end - - def to_s # :nodoc: - display = "#{self.class.name} #{self.full_name}" - if superclass - display += ' < ' + (superclass.is_a?(String) ? superclass : superclass.full_name) - end - display += ' -> ' + is_alias_for.to_s if is_alias_for - display - end - - def pretty_print q # :nodoc: - superclass = @superclass ? " < #{@superclass}" : nil - - q.group 2, "[class #{full_name}#{superclass}", "]" do - q.breakable - q.text "includes:" - q.breakable - q.seplist @includes do |inc| q.pp inc end - - q.breakable - q.text "constants:" - q.breakable - q.seplist @constants do |const| q.pp const end - - q.breakable - q.text "attributes:" - q.breakable - q.seplist @attributes do |attr| q.pp attr end - - q.breakable - q.text "methods:" - q.breakable - q.seplist @method_list do |meth| q.pp meth end - - q.breakable - q.text "aliases:" - q.breakable - q.seplist @aliases do |aliaz| q.pp aliaz end - - q.breakable - q.text "comment:" - q.breakable - q.pp comment - end - end - -end diff --git a/lib/rdoc/code_object/normal_module.rb b/lib/rdoc/code_object/normal_module.rb deleted file mode 100644 index 498ec4dde2..0000000000 --- a/lib/rdoc/code_object/normal_module.rb +++ /dev/null @@ -1,73 +0,0 @@ -# frozen_string_literal: true -## -# A normal module, like NormalClass - -class RDoc::NormalModule < RDoc::ClassModule - - def aref_prefix # :nodoc: - 'module' - end - - def inspect # :nodoc: - "#<%s:0x%x module %s includes: %p extends: %p attributes: %p methods: %p aliases: %p>" % [ - self.class, object_id, - full_name, @includes, @extends, @attributes, @method_list, @aliases - ] - end - - ## - # The definition of this module, <tt>module MyModuleName</tt> - - def definition - "module #{full_name}" - end - - ## - # This is a module, returns true - - def module? - true - end - - def pretty_print q # :nodoc: - q.group 2, "[module #{full_name}:", "]" do - q.breakable - q.text "includes:" - q.breakable - q.seplist @includes do |inc| q.pp inc end - q.breakable - - q.breakable - q.text "constants:" - q.breakable - q.seplist @constants do |const| q.pp const end - - q.text "attributes:" - q.breakable - q.seplist @attributes do |attr| q.pp attr end - q.breakable - - q.text "methods:" - q.breakable - q.seplist @method_list do |meth| q.pp meth end - q.breakable - - q.text "aliases:" - q.breakable - q.seplist @aliases do |aliaz| q.pp aliaz end - q.breakable - - q.text "comment:" - q.breakable - q.pp comment - end - end - - ## - # Modules don't have one, raises NoMethodError - - def superclass - raise NoMethodError, "#{full_name} is a module" - end - -end diff --git a/lib/rdoc/code_object/require.rb b/lib/rdoc/code_object/require.rb deleted file mode 100644 index 05e26b84b0..0000000000 --- a/lib/rdoc/code_object/require.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -## -# A file loaded by \#require - -class RDoc::Require < RDoc::CodeObject - - ## - # Name of the required file - - attr_accessor :name - - ## - # Creates a new Require that loads +name+ with +comment+ - - def initialize(name, comment) - super() - @name = name.gsub(/'|"/, "") #' - @top_level = nil - self.comment = comment - end - - def inspect # :nodoc: - "#<%s:0x%x require '%s' in %s>" % [ - self.class, - object_id, - @name, - parent_file_name, - ] - end - - def to_s # :nodoc: - "require #{name} in: #{parent}" - end - - ## - # The RDoc::TopLevel corresponding to this require, or +nil+ if not found. - - def top_level - @top_level ||= begin - tl = RDoc::TopLevel.all_files_hash[name + '.rb'] - - if tl.nil? and RDoc::TopLevel.all_files.first.full_name =~ %r(^lib/) then - # second chance - tl = RDoc::TopLevel.all_files_hash['lib/' + name + '.rb'] - end - - tl - end - end - -end diff --git a/lib/rdoc/code_object/single_class.rb b/lib/rdoc/code_object/single_class.rb deleted file mode 100644 index dd16529648..0000000000 --- a/lib/rdoc/code_object/single_class.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true -## -# A singleton class - -class RDoc::SingleClass < RDoc::ClassModule - - ## - # Adds the superclass to the included modules. - - def ancestors - superclass ? super + [superclass] : super - end - - def aref_prefix # :nodoc: - 'sclass' - end - - ## - # The definition of this singleton class, <tt>class << MyClassName</tt> - - def definition - "class << #{full_name}" - end - - def pretty_print q # :nodoc: - q.group 2, "[class << #{full_name}", "]" do - next - end - end -end diff --git a/lib/rdoc/code_object/top_level.rb b/lib/rdoc/code_object/top_level.rb deleted file mode 100644 index b93e3802fc..0000000000 --- a/lib/rdoc/code_object/top_level.rb +++ /dev/null @@ -1,291 +0,0 @@ -# frozen_string_literal: true -## -# A TopLevel context is a representation of the contents of a single file - -class RDoc::TopLevel < RDoc::Context - - MARSHAL_VERSION = 0 # :nodoc: - - ## - # This TopLevel's File::Stat struct - - attr_accessor :file_stat - - ## - # Relative name of this file - - attr_accessor :relative_name - - ## - # Absolute name of this file - - attr_accessor :absolute_name - - ## - # All the classes or modules that were declared in - # this file. These are assigned to either +#classes_hash+ - # or +#modules_hash+ once we know what they really are. - - attr_reader :classes_or_modules - - attr_accessor :diagram # :nodoc: - - ## - # The parser class that processed this file - - attr_reader :parser - - ## - # Creates a new TopLevel for the file at +absolute_name+. If documentation - # is being generated outside the source dir +relative_name+ is relative to - # the source directory. - - def initialize absolute_name, relative_name = absolute_name - super() - @name = nil - @absolute_name = absolute_name - @relative_name = relative_name - @file_stat = File.stat(absolute_name) rescue nil # HACK for testing - @diagram = nil - @parser = nil - - @classes_or_modules = [] - end - - ## - # Sets the parser for this toplevel context, also the store. - - def parser=(val) - @parser = val - @store.update_parser_of_file(absolute_name, val) if @store - @parser - end - - ## - # An RDoc::TopLevel is equal to another with the same relative_name - - def == other - self.class === other and @relative_name == other.relative_name - end - - alias eql? == - - ## - # Adds +an_alias+ to +Object+ instead of +self+. - - def add_alias(an_alias) - object_class.record_location self - return an_alias unless @document_self - object_class.add_alias an_alias - end - - ## - # Adds +constant+ to +Object+ instead of +self+. - - def add_constant constant - object_class.record_location self - return constant unless @document_self - object_class.add_constant constant - end - - ## - # Adds +include+ to +Object+ instead of +self+. - - def add_include(include) - object_class.record_location self - return include unless @document_self - object_class.add_include include - end - - ## - # Adds +method+ to +Object+ instead of +self+. - - def add_method(method) - object_class.record_location self - return method unless @document_self - object_class.add_method method - end - - ## - # Adds class or module +mod+. Used in the building phase - # by the Ruby parser. - - def add_to_classes_or_modules mod - @classes_or_modules << mod - end - - ## - # Base name of this file - - def base_name - File.basename @relative_name - end - - alias name base_name - - ## - # Only a TopLevel that contains text file) will be displayed. See also - # RDoc::CodeObject#display? - - def display? - text? and super - end - - ## - # See RDoc::TopLevel::find_class_or_module - #-- - # TODO Why do we search through all classes/modules found, not just the - # ones of this instance? - - def find_class_or_module name - @store.find_class_or_module name - end - - ## - # Finds a class or module named +symbol+ - - def find_local_symbol(symbol) - find_class_or_module(symbol) || super - end - - ## - # Finds a module or class with +name+ - - def find_module_named(name) - find_class_or_module(name) - end - - ## - # Returns the relative name of this file - - def full_name - @relative_name - end - - ## - # An RDoc::TopLevel has the same hash as another with the same - # relative_name - - def hash - @relative_name.hash - end - - ## - # URL for this with a +prefix+ - - def http_url(prefix) - path = [prefix, @relative_name.tr('.', '_')] - - File.join(*path.compact) + '.html' - end - - def inspect # :nodoc: - "#<%s:0x%x %p modules: %p classes: %p>" % [ - self.class, object_id, - base_name, - @modules.map { |n, m| m }, - @classes.map { |n, c| c } - ] - end - - ## - # Time this file was last modified, if known - - def last_modified - @file_stat ? file_stat.mtime : nil - end - - ## - # Dumps this TopLevel for use by ri. See also #marshal_load - - def marshal_dump - [ - MARSHAL_VERSION, - @relative_name, - @parser, - parse(@comment), - ] - end - - ## - # Loads this TopLevel from +array+. - - def marshal_load array # :nodoc: - initialize array[1] - - @parser = array[2] - @comment = array[3] - - @file_stat = nil - end - - ## - # Returns the NormalClass "Object", creating it if not found. - # - # Records +self+ as a location in "Object". - - def object_class - @object_class ||= begin - oc = @store.find_class_named('Object') || add_class(RDoc::NormalClass, 'Object') - oc.record_location self - oc - end - end - - ## - # Base name of this file without the extension - - def page_name - basename = File.basename @relative_name - basename =~ /\.(rb|rdoc|txt|md)$/i - - $` || basename - end - - ## - # Path to this file for use with HTML generator output. - - def path - http_url @store.rdoc.generator.file_dir - end - - def pretty_print q # :nodoc: - q.group 2, "[#{self.class}: ", "]" do - q.text "base name: #{base_name.inspect}" - q.breakable - - items = @modules.map { |n, m| m } - items.concat @modules.map { |n, c| c } - q.seplist items do |mod| q.pp mod end - end - end - - ## - # Search record used by RDoc::Generator::JsonIndex - - def search_record - return unless @parser < RDoc::Parser::Text - - [ - page_name, - '', - page_name, - '', - path, - '', - snippet(@comment), - ] - end - - ## - # Is this TopLevel from a text file instead of a source code file? - - def text? - @parser and @parser.include? RDoc::Parser::Text - end - - def to_s # :nodoc: - "file #{full_name}" - end - -end diff --git a/lib/rdoc/code_objects.rb b/lib/rdoc/code_objects.rb deleted file mode 100644 index d5f2f920ad..0000000000 --- a/lib/rdoc/code_objects.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true -# This file was used to load all the RDoc::CodeObject subclasses at once. Now -# autoload handles this. - -require_relative '../rdoc' diff --git a/lib/rdoc/comment.rb b/lib/rdoc/comment.rb deleted file mode 100644 index 04ec226436..0000000000 --- a/lib/rdoc/comment.rb +++ /dev/null @@ -1,229 +0,0 @@ -# frozen_string_literal: true -## -# A comment holds the text comment for a RDoc::CodeObject and provides a -# unified way of cleaning it up and parsing it into an RDoc::Markup::Document. -# -# Each comment may have a different markup format set by #format=. By default -# 'rdoc' is used. The :markup: directive tells RDoc which format to use. -# -# See RDoc::MarkupReference@Directive+for+Specifying+RDoc+Source+Format. - - -class RDoc::Comment - - include RDoc::Text - - ## - # The format of this comment. Defaults to RDoc::Markup - - attr_reader :format - - ## - # The RDoc::TopLevel this comment was found in - - attr_accessor :location - - ## - # Line where this Comment was written - - attr_accessor :line - - ## - # For duck-typing when merging classes at load time - - alias file location # :nodoc: - - ## - # The text for this comment - - attr_reader :text - - ## - # Alias for text - - alias to_s text - - ## - # Overrides the content returned by #parse. Use when there is no #text - # source for this comment - - attr_writer :document - - ## - # Creates a new comment with +text+ that is found in the RDoc::TopLevel - # +location+. - - def initialize text = nil, location = nil, language = nil - @location = location - @text = text.nil? ? nil : text.dup - @language = language - - @document = nil - @format = 'rdoc' - @normalized = false - end - - ## - #-- - # TODO deep copy @document - - def initialize_copy copy # :nodoc: - @text = copy.text.dup - end - - def == other # :nodoc: - self.class === other and - other.text == @text and other.location == @location - end - - ## - # Look for a 'call-seq' in the comment to override the normal parameter - # handling. The :call-seq: is indented from the baseline. All lines of the - # same indentation level and prefix are consumed. - # - # For example, all of the following will be used as the :call-seq: - # - # # :call-seq: - # # ARGF.readlines(sep=$/) -> array - # # ARGF.readlines(limit) -> array - # # ARGF.readlines(sep, limit) -> array - # # - # # ARGF.to_a(sep=$/) -> array - # # ARGF.to_a(limit) -> array - # # ARGF.to_a(sep, limit) -> array - - def extract_call_seq method - # we must handle situations like the above followed by an unindented first - # comment. The difficulty is to make sure not to match lines starting - # with ARGF at the same indent, but that are after the first description - # paragraph. - if /^(?<S> ((?!\n)\s)*+ (?# whitespaces except newline)) - :?call-seq: - (?<B> \g<S>(?<N>\n|\z) (?# trailing spaces))? - (?<seq> - (\g<S>(?!\w)\S.*\g<N>)* - (?> - (?<H> \g<S>\w+ (?# ' # ARGF' in the example above)) - .*\g<N>)? - (\g<S>\S.*\g<N> (?# other non-blank line))*+ - (\g<B>+(\k<H>.*\g<N> (?# ARGF.to_a lines))++)*+ - ) - (?m:^\s*$|\z) - /x =~ @text - seq = $~[:seq] - - all_start, all_stop = $~.offset(0) - @text.slice! all_start...all_stop - - seq.gsub!(/^\s*/, '') - method.call_seq = seq - end - - method - end - - ## - # A comment is empty if its text String is empty. - - def empty? - @text.empty? - end - - ## - # HACK dubious - - def encode! encoding - @text = String.new @text, encoding: encoding - self - end - - ## - # Sets the format of this comment and resets any parsed document - - def format= format - @format = format - @document = nil - end - - def inspect # :nodoc: - location = @location ? @location.relative_name : '(unknown)' - - "#<%s:%x %s %p>" % [self.class, object_id, location, @text] - end - - ## - # Normalizes the text. See RDoc::Text#normalize_comment for details - - def normalize - return self unless @text - return self if @normalized # TODO eliminate duplicate normalization - - @text = normalize_comment @text - - @normalized = true - - self - end - - ## - # Was this text normalized? - - def normalized? # :nodoc: - @normalized - end - - ## - # Parses the comment into an RDoc::Markup::Document. The parsed document is - # cached until the text is changed. - - def parse - return @document if @document - - @document = super @text, @format - @document.file = @location - @document - end - - ## - # Removes private sections from this comment. Private sections are flush to - # the comment marker and start with <tt>--</tt> and end with <tt>++</tt>. - # For C-style comments, a private marker may not start at the opening of the - # comment. - # - # /* - # *-- - # * private - # *++ - # * public - # */ - - def remove_private - # Workaround for gsub encoding for Ruby 1.9.2 and earlier - empty = '' - empty = RDoc::Encoding.change_encoding empty, @text.encoding - - @text = @text.gsub(%r%^\s*([#*]?)--.*?^\s*(\1)\+\+\n?%m, empty) - @text = @text.sub(%r%^\s*[#*]?--.*%m, '') - end - - ## - # Replaces this comment's text with +text+ and resets the parsed document. - # - # An error is raised if the comment contains a document but no text. - - def text= text - raise RDoc::Error, 'replacing document-only comment is not allowed' if - @text.nil? and @document - - @document = nil - @text = text.nil? ? nil : text.dup - end - - ## - # Returns true if this comment is in TomDoc format. - - def tomdoc? - @format == 'tomdoc' - end - -end diff --git a/lib/rdoc/cross_reference.rb b/lib/rdoc/cross_reference.rb deleted file mode 100644 index 4e011219e8..0000000000 --- a/lib/rdoc/cross_reference.rb +++ /dev/null @@ -1,228 +0,0 @@ -# frozen_string_literal: true - -require_relative 'markup/attribute_manager' # for PROTECT_ATTR - -## -# RDoc::CrossReference is a reusable way to create cross references for names. - -class RDoc::CrossReference - - ## - # Regular expression to match class references - # - # 1. There can be a '\\' in front of text to suppress the cross-reference - # 2. There can be a '::' in front of class names to reference from the - # top-level namespace. - # 3. The method can be followed by parenthesis (not recommended) - - CLASS_REGEXP_STR = '\\\\?((?:\:{2})?[A-Z]\w*(?:\:\:\w+)*)' - - ## - # Regular expression to match a single method argument. - - METHOD_ARG_REGEXP_STR = '[\w.+*/=<>-]+' - - ## - # Regular expression to match method arguments. - - METHOD_ARGS_REGEXP_STR = /(?:\((?:#{METHOD_ARG_REGEXP_STR}(?:,\s*#{METHOD_ARG_REGEXP_STR})*)?\))?/.source - - ## - # Regular expression to match method references. - # - # See CLASS_REGEXP_STR - - METHOD_REGEXP_STR = /( - (?!\d)[\w#{RDoc::Markup::AttributeManager::PROTECT_ATTR}]+[!?=]?| - %|=(?:==?|~)|![=~]|\[\]=?|<(?:<|=>?)?|>[>=]?|[-+!]@?|\*\*?|[\/%\`|&^~] - )#{METHOD_ARGS_REGEXP_STR}/.source.delete("\n ").freeze - - ## - # Regular expressions matching text that should potentially have - # cross-reference links generated are passed to add_regexp_handling. Note - # that these expressions are meant to pick up text for which cross-references - # have been suppressed, since the suppression characters are removed by the - # code that is triggered. - - CROSSREF_REGEXP = /(?:^|[\s()]) - ( - (?: - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - - # A::B::C - # The stuff after CLASS_REGEXP_STR is a - # nasty hack. CLASS_REGEXP_STR unfortunately matches - # words like dog and cat (these are legal "class" - # names in Fortran 95). When a word is flagged as a - # potential cross-reference, limitations in the markup - # engine suppress other processing, such as typesetting. - # This is particularly noticeable for contractions. - # In order that words like "can't" not - # be flagged as potential cross-references, only - # flag potential class cross-references if the character - # after the cross-reference is a space, sentence - # punctuation, tag start character, or attribute - # marker. - | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) - - # Stand-alone method (preceded by a #) - | \\?\##{METHOD_REGEXP_STR} - - # Stand-alone method (preceded by ::) - | ::#{METHOD_REGEXP_STR} - - # Things that look like filenames - # The key thing is that there must be at least - # one special character (period, slash, or - # underscore). - | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ - - # Things that have markup suppressed - # Don't process things like '\<' in \<tt>, though. - # TODO: including < is a hack, not very satisfying. - | \\[^\s<] - ) - - # labels for headings - (?:@[\w+%-]+(?:\.[\w|%-]+)?)? - )/x - - ## - # Version of CROSSREF_REGEXP used when <tt>--hyperlink-all</tt> is specified. - - ALL_CROSSREF_REGEXP = / - (?:^|[\s()]) - ( - (?: - # A::B::C.meth - #{CLASS_REGEXP_STR}(?:[.#]|::)#{METHOD_REGEXP_STR} - - # A::B::C - | #{CLASS_REGEXP_STR}(?=[@\s).?!,;<\000]|\z) - - # Stand-alone method - | \\?#{METHOD_REGEXP_STR} - - # Things that look like filenames - | (?:\.\.\/)*[-\/\w]+[_\/.][-\w\/.]+ - - # Things that have markup suppressed - | \\[^\s<] - ) - - # labels for headings - (?:@[\w+%-]+)? - )/x - - ## - # Hash of references that have been looked-up to their replacements - - attr_accessor :seen - - ## - # Allows cross-references to be created based on the given +context+ - # (RDoc::Context). - - def initialize context - @context = context - @store = context.store - - @seen = {} - end - - ## - # Returns a method reference to +name+. - - def resolve_method name - ref = nil - - if /#{CLASS_REGEXP_STR}([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then - type = $2 - if '.' == type # will find either #method or ::method - method = $3 - else - method = "#{type}#{$3}" - end - container = @context.find_symbol_module($1) - elsif /^([.#]|::)#{METHOD_REGEXP_STR}/o =~ name then - type = $1 - if '.' == type - method = $2 - else - method = "#{type}#{$2}" - end - container = @context - else - type = nil - container = nil - end - - if container then - unless RDoc::TopLevel === container then - if '.' == type then - if 'new' == method then # AnyClassName.new will be class method - ref = container.find_local_symbol method - ref = container.find_ancestor_local_symbol method unless ref - else - ref = container.find_local_symbol "::#{method}" - ref = container.find_ancestor_local_symbol "::#{method}" unless ref - ref = container.find_local_symbol "##{method}" unless ref - ref = container.find_ancestor_local_symbol "##{method}" unless ref - end - else - ref = container.find_local_symbol method - ref = container.find_ancestor_local_symbol method unless ref - end - end - end - - ref - end - - ## - # Returns a reference to +name+. - # - # If the reference is found and +name+ is not documented +text+ will be - # returned. If +name+ is escaped +name+ is returned. If +name+ is not - # found +text+ is returned. - - def resolve name, text - return @seen[name] if @seen.include? name - - ref = case name - when /^\\(#{CLASS_REGEXP_STR})$/o then - @context.find_symbol $1 - else - @context.find_symbol name - end - - ref = resolve_method name unless ref - - # Try a page name - ref = @store.page name if not ref and name =~ /^[\w.]+$/ - - ref = nil if RDoc::Alias === ref # external alias, can't link to it - - out = if name == '\\' then - name - elsif name =~ /^\\/ then - # we remove the \ only in front of what we know: - # other backslashes are treated later, only outside of <tt> - ref ? $' : name - elsif ref then - if ref.display? then - ref - else - text - end - else - text - end - - @seen[name] = out - - out - end - -end diff --git a/lib/rdoc/encoding.rb b/lib/rdoc/encoding.rb deleted file mode 100644 index 67e190f782..0000000000 --- a/lib/rdoc/encoding.rb +++ /dev/null @@ -1,120 +0,0 @@ -# coding: US-ASCII -# frozen_string_literal: true - -## -# This class is a wrapper around File IO and Encoding that helps RDoc load -# files and convert them to the correct encoding. - -module RDoc::Encoding - - HEADER_REGEXP = /^ - (?: - \A\#!.*\n - | - ^\#\s+frozen[-_]string[-_]literal[=:].+\n - | - ^\#[^\n]+\b(?:en)?coding[=:]\s*(?<name>[^\s;]+).*\n - | - <\?xml[^?]*encoding=(?<quote>["'])(?<name>.*?)\k<quote>.*\n - )+ - /xi # :nodoc: - - ## - # Reads the contents of +filename+ and handles any encoding directives in - # the file. - # - # The content will be converted to the +encoding+. If the file cannot be - # converted a warning will be printed and nil will be returned. - # - # If +force_transcode+ is true the document will be transcoded and any - # unknown character in the target encoding will be replaced with '?' - - def self.read_file filename, encoding, force_transcode = false - content = File.open filename, "rb" do |f| f.read end - content.gsub!("\r\n", "\n") if RUBY_PLATFORM =~ /mswin|mingw/ - - utf8 = content.sub!(/\A\xef\xbb\xbf/, '') - - enc = RDoc::Encoding.detect_encoding content - content = RDoc::Encoding.change_encoding content, enc if enc - - begin - encoding ||= Encoding.default_external - orig_encoding = content.encoding - - if not orig_encoding.ascii_compatible? then - content = content.encode encoding - elsif utf8 then - content = RDoc::Encoding.change_encoding content, Encoding::UTF_8 - content = content.encode encoding - else - # assume the content is in our output encoding - content = RDoc::Encoding.change_encoding content, encoding - end - - unless content.valid_encoding? then - # revert and try to transcode - content = RDoc::Encoding.change_encoding content, orig_encoding - content = content.encode encoding - end - - unless content.valid_encoding? then - warn "unable to convert #{filename} to #{encoding}, skipping" - content = nil - end - rescue Encoding::InvalidByteSequenceError, - Encoding::UndefinedConversionError => e - if force_transcode then - content = RDoc::Encoding.change_encoding content, orig_encoding - content = content.encode(encoding, - :invalid => :replace, - :undef => :replace, - :replace => '?') - return content - else - warn "unable to convert #{e.message} for #{filename}, skipping" - return nil - end - end - - content - rescue ArgumentError => e - raise unless e.message =~ /unknown encoding name - (.*)/ - warn "unknown encoding name \"#{$1}\" for #{filename}, skipping" - nil - rescue Errno::EISDIR, Errno::ENOENT - nil - end - - ## - # Detects the encoding of +string+ based on the magic comment - - def self.detect_encoding string - result = HEADER_REGEXP.match string - name = result && result[:name] - - name ? Encoding.find(name) : nil - end - - ## - # Removes magic comments and shebang - - def self.remove_magic_comment string - string.sub HEADER_REGEXP do |s| - s.gsub(/[^\n]/, '') - end - end - - ## - # Changes encoding based on +encoding+ without converting and returns new - # string - - def self.change_encoding text, encoding - if text.kind_of? RDoc::Comment - text.encode! encoding - else - String.new text, encoding: encoding - end - end - -end diff --git a/lib/rdoc/erb_partial.rb b/lib/rdoc/erb_partial.rb deleted file mode 100644 index 043d763db1..0000000000 --- a/lib/rdoc/erb_partial.rb +++ /dev/null @@ -1,18 +0,0 @@ -# frozen_string_literal: true -## -# Allows an ERB template to be rendered in the context (binding) of an -# existing ERB template evaluation. - -class RDoc::ERBPartial < ERB - - ## - # Overrides +compiler+ startup to set the +eoutvar+ to an empty string only - # if it isn't already set. - - def set_eoutvar compiler, eoutvar = '_erbout' - super - - compiler.pre_cmd = ["#{eoutvar} ||= +''"] - end - -end diff --git a/lib/rdoc/erbio.rb b/lib/rdoc/erbio.rb deleted file mode 100644 index 0f98eaedee..0000000000 --- a/lib/rdoc/erbio.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true -require 'erb' - -## -# A subclass of ERB that writes directly to an IO. Credit to Aaron Patterson -# and Masatoshi SEKI. -# -# To use: -# -# erbio = RDoc::ERBIO.new '<%= "hello world" %>', nil, nil -# -# File.open 'hello.txt', 'w' do |io| -# erbio.result binding -# end -# -# Note that binding must enclose the io you wish to output on. - -class RDoc::ERBIO < ERB - - ## - # Defaults +eoutvar+ to 'io', otherwise is identical to ERB's initialize - - def initialize str, trim_mode: nil, eoutvar: 'io' - super(str, trim_mode: trim_mode, eoutvar: eoutvar) - end - - ## - # Instructs +compiler+ how to write to +io_variable+ - - def set_eoutvar compiler, io_variable - compiler.put_cmd = "#{io_variable}.write" - compiler.insert_cmd = "#{io_variable}.write" - compiler.pre_cmd = [] - compiler.post_cmd = [] - end - -end diff --git a/lib/rdoc/generator.rb b/lib/rdoc/generator.rb deleted file mode 100644 index a769cf8ac0..0000000000 --- a/lib/rdoc/generator.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -## -# RDoc uses generators to turn parsed source code in the form of an -# RDoc::CodeObject tree into some form of output. RDoc comes with the HTML -# generator RDoc::Generator::Darkfish and an ri data generator -# RDoc::Generator::RI. -# -# == Registering a Generator -# -# Generators are registered by calling RDoc::RDoc.add_generator with the class -# of the generator: -# -# class My::Awesome::Generator -# RDoc::RDoc.add_generator self -# end -# -# == Adding Options to +rdoc+ -# -# Before option processing in +rdoc+, RDoc::Options will call ::setup_options -# on the generator class with an RDoc::Options instance. The generator can -# use RDoc::Options#option_parser to add command-line options to the +rdoc+ -# tool. See RDoc::Options@Custom+Options for an example and see OptionParser -# for details on how to add options. -# -# You can extend the RDoc::Options instance with additional accessors for your -# generator. -# -# == Generator Instantiation -# -# After parsing, RDoc::RDoc will instantiate a generator by calling -# #initialize with an RDoc::Store instance and an RDoc::Options instance. -# -# The RDoc::Store instance holds documentation for parsed source code. In -# RDoc 3 and earlier the RDoc::TopLevel class held this data. When upgrading -# a generator from RDoc 3 and earlier you should only need to replace -# RDoc::TopLevel with the store instance. -# -# RDoc will then call #generate on the generator instance. You can use the -# various methods on RDoc::Store and in the RDoc::CodeObject tree to create -# your desired output format. - -module RDoc::Generator - - autoload :Markup, "#{__dir__}/generator/markup" - - autoload :Darkfish, "#{__dir__}/generator/darkfish" - autoload :JsonIndex, "#{__dir__}/generator/json_index" - autoload :RI, "#{__dir__}/generator/ri" - autoload :POT, "#{__dir__}/generator/pot" - -end diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb deleted file mode 100644 index 5709fabf81..0000000000 --- a/lib/rdoc/generator/darkfish.rb +++ /dev/null @@ -1,798 +0,0 @@ -# frozen_string_literal: true -# -*- mode: ruby; ruby-indent-level: 2; tab-width: 2 -*- - -require 'erb' -require 'fileutils' -require 'pathname' -require_relative 'markup' - -## -# Darkfish RDoc HTML Generator -# -# $Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $ -# -# == Author/s -# * Michael Granger (ged@FaerieMUD.org) -# -# == Contributors -# * Mahlon E. Smith (mahlon@martini.nu) -# * Eric Hodel (drbrain@segment7.net) -# -# == License -# -# Copyright (c) 2007, 2008, Michael Granger. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# * Neither the name of the author/s, nor the names of the project's -# contributors may be used to endorse or promote products derived from this -# software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# == Attributions -# -# Darkfish uses the {Silk Icons}[http://www.famfamfam.com/lab/icons/silk/] set -# by Mark James. - -class RDoc::Generator::Darkfish - - RDoc::RDoc.add_generator self - - include ERB::Util - - ## - # Stylesheets, fonts, etc. that are included in RDoc. - - BUILTIN_STYLE_ITEMS = # :nodoc: - %w[ - css/fonts.css - fonts/Lato-Light.ttf - fonts/Lato-LightItalic.ttf - fonts/Lato-Regular.ttf - fonts/Lato-RegularItalic.ttf - fonts/SourceCodePro-Bold.ttf - fonts/SourceCodePro-Regular.ttf - css/rdoc.css - ] - - ## - # Path to this file's parent directory. Used to find templates and other - # resources. - - GENERATOR_DIR = File.join 'rdoc', 'generator' - - ## - # Release Version - - VERSION = '3' - - ## - # Description of this generator - - DESCRIPTION = 'HTML generator, written by Michael Granger' - - ## - # The relative path to style sheets and javascript. By default this is set - # the same as the rel_prefix. - - attr_accessor :asset_rel_path - - ## - # The path to generate files into, combined with <tt>--op</tt> from the - # options for a full path. - - attr_reader :base_dir - - ## - # Classes and modules to be used by this generator, not necessarily - # displayed. See also #modsort - - attr_reader :classes - - ## - # No files will be written when dry_run is true. - - attr_accessor :dry_run - - ## - # When false the generate methods return a String instead of writing to a - # file. The default is true. - - attr_accessor :file_output - - ## - # Files to be displayed by this generator - - attr_reader :files - - ## - # The JSON index generator for this Darkfish generator - - attr_reader :json_index - - ## - # Methods to be displayed by this generator - - attr_reader :methods - - ## - # Sorted list of classes and modules to be displayed by this generator - - attr_reader :modsort - - ## - # The RDoc::Store that is the source of the generated content - - attr_reader :store - - ## - # The directory where the template files live - - attr_reader :template_dir # :nodoc: - - ## - # The output directory - - attr_reader :outputdir - - ## - # Initialize a few instance variables before we start - - def initialize store, options - @store = store - @options = options - - @asset_rel_path = '' - @base_dir = Pathname.pwd.expand_path - @dry_run = @options.dry_run - @file_output = true - @template_dir = Pathname.new options.template_dir - @template_cache = {} - - @classes = nil - @context = nil - @files = nil - @methods = nil - @modsort = nil - - @json_index = RDoc::Generator::JsonIndex.new self, options - end - - ## - # Output progress information if debugging is enabled - - def debug_msg *msg - return unless $DEBUG_RDOC - $stderr.puts(*msg) - end - - ## - # Directory where generated class HTML files live relative to the output - # dir. - - def class_dir - nil - end - - ## - # Directory where generated class HTML files live relative to the output - # dir. - - def file_dir - nil - end - - ## - # Create the directories the generated docs will live in if they don't - # already exist. - - def gen_sub_directories - @outputdir.mkpath - end - - ## - # Copy over the stylesheet into the appropriate place in the output - # directory. - - def write_style_sheet - debug_msg "Copying static files" - options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } - - BUILTIN_STYLE_ITEMS.each do |item| - install_rdoc_static_file @template_dir + item, "./#{item}", options - end - - unless @options.template_stylesheets.empty? - FileUtils.cp @options.template_stylesheets, '.', **options - end - - Dir[(@template_dir + "{js,images}/**/*").to_s].each do |path| - next if File.directory? path - next if File.basename(path) =~ /^\./ - - dst = Pathname.new(path).relative_path_from @template_dir - - install_rdoc_static_file @template_dir + path, dst, options - end - end - - ## - # Build the initial indices and output objects based on an array of TopLevel - # objects containing the extracted information. - - def generate - setup - - write_style_sheet - generate_index - generate_class_files - generate_file_files - generate_table_of_contents - @json_index.generate - @json_index.generate_gzipped - - copy_static - - rescue => e - debug_msg "%s: %s\n %s" % [ - e.class.name, e.message, e.backtrace.join("\n ") - ] - - raise - end - - ## - # Copies static files from the static_path into the output directory - - def copy_static - return if @options.static_path.empty? - - fu_options = { :verbose => $DEBUG_RDOC, :noop => @dry_run } - - @options.static_path.each do |path| - unless File.directory? path then - FileUtils.install path, @outputdir, **fu_options.merge(:mode => 0644) - next - end - - Dir.chdir path do - Dir[File.join('**', '*')].each do |entry| - dest_file = @outputdir + entry - - if File.directory? entry then - FileUtils.mkdir_p entry, **fu_options - else - FileUtils.install entry, dest_file, **fu_options.merge(:mode => 0644) - end - end - end - end - end - - ## - # Return a list of the documented modules sorted by salience first, then - # by name. - - def get_sorted_module_list classes - classes.select do |klass| - klass.display? - end.sort - end - - ## - # Generate an index page which lists all the classes which are documented. - - def generate_index - setup - - template_file = @template_dir + 'index.rhtml' - return unless template_file.exist? - - debug_msg "Rendering the index page..." - - out_file = @base_dir + @options.op_dir + 'index.html' - rel_prefix = @outputdir.relative_path_from out_file.dirname - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - asset_rel_prefix = rel_prefix + @asset_rel_path - - @title = @options.title - - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - rescue => e - error = RDoc::Error.new \ - "error generating index.html: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - ## - # Generates a class file for +klass+ - - def generate_class klass, template_file = nil - setup - - current = klass - - template_file ||= @template_dir + 'class.rhtml' - - debug_msg " working on %s (%s)" % [klass.full_name, klass.path] - out_file = @outputdir + klass.path - rel_prefix = @outputdir.relative_path_from out_file.dirname - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - asset_rel_prefix = rel_prefix + @asset_rel_path - svninfo = get_svninfo(current) - - @title = "#{klass.type} #{klass.full_name} - #{@options.title}" - - debug_msg " rendering #{out_file}" - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here.local_variable_set(:svninfo, svninfo) - here - end - end - - ## - # Generate a documentation file for each class and module - - def generate_class_files - setup - - template_file = @template_dir + 'class.rhtml' - template_file = @template_dir + 'classpage.rhtml' unless - template_file.exist? - return unless template_file.exist? - debug_msg "Generating class documentation in #{@outputdir}" - - current = nil - - @classes.each do |klass| - current = klass - - generate_class klass, template_file - end - rescue => e - error = RDoc::Error.new \ - "error generating #{current.path}: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - ## - # Generate a documentation file for each file - - def generate_file_files - setup - - page_file = @template_dir + 'page.rhtml' - fileinfo_file = @template_dir + 'fileinfo.rhtml' - - # for legacy templates - filepage_file = @template_dir + 'filepage.rhtml' unless - page_file.exist? or fileinfo_file.exist? - - return unless - page_file.exist? or fileinfo_file.exist? or filepage_file.exist? - - debug_msg "Generating file documentation in #{@outputdir}" - - out_file = nil - current = nil - - @files.each do |file| - current = file - - if file.text? and page_file.exist? then - generate_page file - next - end - - template_file = nil - out_file = @outputdir + file.path - debug_msg " working on %s (%s)" % [file.full_name, out_file] - rel_prefix = @outputdir.relative_path_from out_file.dirname - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - asset_rel_prefix = rel_prefix + @asset_rel_path - - unless filepage_file then - if file.text? then - next unless page_file.exist? - template_file = page_file - @title = file.page_name - else - next unless fileinfo_file.exist? - template_file = fileinfo_file - @title = "File: #{file.base_name}" - end - end - - @title += " - #{@options.title}" - template_file ||= filepage_file - - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here.local_variable_set(:current, current) - here - end - end - rescue => e - error = - RDoc::Error.new "error generating #{out_file}: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - ## - # Generate a page file for +file+ - - def generate_page file - setup - - template_file = @template_dir + 'page.rhtml' - - out_file = @outputdir + file.path - debug_msg " working on %s (%s)" % [file.full_name, out_file] - rel_prefix = @outputdir.relative_path_from out_file.dirname - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - current = file - asset_rel_prefix = rel_prefix + @asset_rel_path - - @title = "#{file.page_name} - #{@options.title}" - - debug_msg " rendering #{out_file}" - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:current, current) - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - end - - ## - # Generates the 404 page for the RDoc servlet - - def generate_servlet_not_found message - setup - - template_file = @template_dir + 'servlet_not_found.rhtml' - return unless template_file.exist? - - debug_msg "Rendering the servlet 404 Not Found page..." - - rel_prefix = rel_prefix = '' - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - asset_rel_prefix = '' - - @title = 'Not Found' - - render_template template_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - rescue => e - error = RDoc::Error.new \ - "error generating servlet_not_found: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - ## - # Generates the servlet root page for the RDoc servlet - - def generate_servlet_root installed - setup - - template_file = @template_dir + 'servlet_root.rhtml' - return unless template_file.exist? - - debug_msg 'Rendering the servlet root page...' - - rel_prefix = '.' - asset_rel_prefix = rel_prefix - search_index_rel_prefix = asset_rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - @title = 'Local RDoc Documentation' - - render_template template_file do |io| binding end - rescue => e - error = RDoc::Error.new \ - "error generating servlet_root: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - ## - # Generate an index page which lists all the classes which are documented. - - def generate_table_of_contents - setup - - template_file = @template_dir + 'table_of_contents.rhtml' - return unless template_file.exist? - - debug_msg "Rendering the Table of Contents..." - - out_file = @outputdir + 'table_of_contents.html' - rel_prefix = @outputdir.relative_path_from out_file.dirname - search_index_rel_prefix = rel_prefix - search_index_rel_prefix += @asset_rel_path if @file_output - - asset_rel_prefix = rel_prefix + @asset_rel_path - - @title = "Table of Contents - #{@options.title}" - - render_template template_file, out_file do |io| - here = binding - # suppress 1.9.3 warning - here.local_variable_set(:asset_rel_prefix, asset_rel_prefix) - here - end - rescue => e - error = RDoc::Error.new \ - "error generating table_of_contents.html: #{e.message} (#{e.class})" - error.set_backtrace e.backtrace - - raise error - end - - def install_rdoc_static_file source, destination, options # :nodoc: - return unless source.exist? - - begin - FileUtils.mkdir_p File.dirname(destination), **options - - begin - FileUtils.ln source, destination, **options - rescue Errno::EEXIST - FileUtils.rm destination - retry - end - rescue - FileUtils.cp source, destination, **options - end - end - - ## - # Prepares for generation of output from the current directory - - def setup - return if instance_variable_defined? :@outputdir - - @outputdir = Pathname.new(@options.op_dir).expand_path @base_dir - - return unless @store - - @classes = @store.all_classes_and_modules.sort - @files = @store.all_files.sort - @methods = @classes.flat_map { |m| m.method_list }.sort - @modsort = get_sorted_module_list @classes - end - - ## - # Return a string describing the amount of time in the given number of - # seconds in terms a human can understand easily. - - def time_delta_string seconds - return 'less than a minute' if seconds < 60 - return "#{seconds / 60} minute#{seconds / 60 == 1 ? '' : 's'}" if - seconds < 3000 # 50 minutes - return 'about one hour' if seconds < 5400 # 90 minutes - return "#{seconds / 3600} hours" if seconds < 64800 # 18 hours - return 'one day' if seconds < 86400 # 1 day - return 'about one day' if seconds < 172800 # 2 days - return "#{seconds / 86400} days" if seconds < 604800 # 1 week - return 'about one week' if seconds < 1209600 # 2 week - return "#{seconds / 604800} weeks" if seconds < 7257600 # 3 months - return "#{seconds / 2419200} months" if seconds < 31536000 # 1 year - return "#{seconds / 31536000} years" - end - - # %q$Id: darkfish.rb 52 2009-01-07 02:08:11Z deveiant $" - SVNID_PATTERN = / - \$Id:\s - (\S+)\s # filename - (\d+)\s # rev - (\d{4}-\d{2}-\d{2})\s # Date (YYYY-MM-DD) - (\d{2}:\d{2}:\d{2}Z)\s # Time (HH:MM:SSZ) - (\w+)\s # committer - \$$ - /x - - ## - # Try to extract Subversion information out of the first constant whose - # value looks like a subversion Id tag. If no matching constant is found, - # and empty hash is returned. - - def get_svninfo klass - constants = klass.constants or return {} - - constants.find { |c| c.value =~ SVNID_PATTERN } or return {} - - filename, rev, date, time, committer = $~.captures - commitdate = Time.parse "#{date} #{time}" - - return { - :filename => filename, - :rev => Integer(rev), - :commitdate => commitdate, - :commitdelta => time_delta_string(Time.now - commitdate), - :committer => committer, - } - end - - ## - # Creates a template from its components and the +body_file+. - # - # For backwards compatibility, if +body_file+ contains "<html" the body is - # used directly. - - def assemble_template body_file - body = body_file.read - return body if body =~ /<html/ - - head_file = @template_dir + '_head.rhtml' - - <<-TEMPLATE -<!DOCTYPE html> - -<html> -<head> -#{head_file.read} - -#{body} - TEMPLATE - end - - ## - # Renders the ERb contained in +file_name+ relative to the template - # directory and returns the result based on the current context. - - def render file_name - template_file = @template_dir + file_name - - template = template_for template_file, false, RDoc::ERBPartial - - template.filename = template_file.to_s - - template.result @context - end - - ## - # Load and render the erb template in the given +template_file+ and write - # it out to +out_file+. - # - # Both +template_file+ and +out_file+ should be Pathname-like objects. - # - # An io will be yielded which must be captured by binding in the caller. - - def render_template template_file, out_file = nil # :yield: io - io_output = out_file && !@dry_run && @file_output - erb_klass = io_output ? RDoc::ERBIO : ERB - - template = template_for template_file, true, erb_klass - - if io_output then - debug_msg "Outputting to %s" % [out_file.expand_path] - - out_file.dirname.mkpath - out_file.open 'w', 0644 do |io| - io.set_encoding @options.encoding - - @context = yield io - - template_result template, @context, template_file - end - else - @context = yield nil - - output = template_result template, @context, template_file - - debug_msg " would have written %d characters to %s" % [ - output.length, out_file.expand_path - ] if @dry_run - - output - end - end - - ## - # Creates the result for +template+ with +context+. If an error is raised a - # Pathname +template_file+ will indicate the file where the error occurred. - - def template_result template, context, template_file - template.filename = template_file.to_s - template.result context - rescue NoMethodError => e - raise RDoc::Error, "Error while evaluating %s: %s" % [ - template_file.expand_path, - e.message, - ], e.backtrace - end - - ## - # Retrieves a cache template for +file+, if present, or fills the cache. - - def template_for file, page = true, klass = ERB - template = @template_cache[file] - - return template if template - - if page then - template = assemble_template file - erbout = 'io' - else - template = file.read - template = template.encode @options.encoding - - file_var = File.basename(file).sub(/\..*/, '') - - erbout = "_erbout_#{file_var}" - end - - template = klass.new template, trim_mode: '-', eoutvar: erbout - @template_cache[file] = template - template - end - - # Returns an excerpt of the content for usage in meta description tags - def excerpt(content) - text = content.is_a?(RDoc::Comment) ? content.text : content - - # Match from a capital letter to the first period, discarding any links, so - # that we don't end up matching badges in the README - first_paragraph_match = text.match(/[A-Z][^\.:\/]+\./) - return text[0...150].gsub(/\n/, " ").squeeze(" ") unless first_paragraph_match - - extracted_text = first_paragraph_match[0] - second_paragraph = first_paragraph_match.post_match.match(/[A-Z][^\.:\/]+\./) - extracted_text << " " << second_paragraph[0] if second_paragraph - - extracted_text[0...150].gsub(/\n/, " ").squeeze(" ") - end -end diff --git a/lib/rdoc/generator/json_index.rb b/lib/rdoc/generator/json_index.rb deleted file mode 100644 index c454910d5c..0000000000 --- a/lib/rdoc/generator/json_index.rb +++ /dev/null @@ -1,300 +0,0 @@ -# frozen_string_literal: true -require 'json' -begin - require 'zlib' -rescue LoadError -end - -## -# The JsonIndex generator is designed to complement an HTML generator and -# produces a JSON search index. This generator is derived from sdoc by -# Vladimir Kolesnikov and contains verbatim code written by him. -# -# This generator is designed to be used with a regular HTML generator: -# -# class RDoc::Generator::Darkfish -# def initialize options -# # ... -# @base_dir = Pathname.pwd.expand_path -# -# @json_index = RDoc::Generator::JsonIndex.new self, options -# end -# -# def generate -# # ... -# @json_index.generate -# end -# end -# -# == Index Format -# -# The index is output as a JSON file assigned to the global variable -# +search_data+. The structure is: -# -# var search_data = { -# "index": { -# "searchIndex": -# ["a", "b", ...], -# "longSearchIndex": -# ["a", "a::b", ...], -# "info": [ -# ["A", "A", "A.html", "", ""], -# ["B", "A::B", "A::B.html", "", ""], -# ... -# ] -# } -# } -# -# The same item is described across the +searchIndex+, +longSearchIndex+ and -# +info+ fields. The +searchIndex+ field contains the item's short name, the -# +longSearchIndex+ field contains the full_name (when appropriate) and the -# +info+ field contains the item's name, full_name, path, parameters and a -# snippet of the item's comment. -# -# == LICENSE -# -# Copyright (c) 2009 Vladimir Kolesnikov -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -class RDoc::Generator::JsonIndex - - include RDoc::Text - - ## - # Where the search index lives in the generated output - - SEARCH_INDEX_FILE = File.join 'js', 'search_index.js' - - attr_reader :index # :nodoc: - - ## - # Creates a new generator. +parent_generator+ is used to determine the - # class_dir and file_dir of links in the output index. - # - # +options+ are the same options passed to the parent generator. - - def initialize parent_generator, options - @parent_generator = parent_generator - @store = parent_generator.store - @options = options - - @template_dir = File.expand_path '../template/json_index', __FILE__ - @base_dir = @parent_generator.base_dir - - @classes = nil - @files = nil - @index = nil - end - - ## - # Builds the JSON index as a Hash. - - def build_index - reset @store.all_files.sort, @store.all_classes_and_modules.sort - - index_classes - index_methods - index_pages - - { :index => @index } - end - - ## - # Output progress information if debugging is enabled - - def debug_msg *msg - return unless $DEBUG_RDOC - $stderr.puts(*msg) - end - - ## - # Writes the JSON index to disk - - def generate - debug_msg "Generating JSON index" - - debug_msg " writing search index to %s" % SEARCH_INDEX_FILE - data = build_index - - return if @options.dry_run - - out_dir = @base_dir + @options.op_dir - index_file = out_dir + SEARCH_INDEX_FILE - - FileUtils.mkdir_p index_file.dirname, :verbose => $DEBUG_RDOC - - index_file.open 'w', 0644 do |io| - io.set_encoding Encoding::UTF_8 - io.write 'var search_data = ' - - JSON.dump data, io, 0 - end - unless ENV['SOURCE_DATE_EPOCH'].nil? - index_file.utime index_file.atime, Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime - end - - Dir.chdir @template_dir do - Dir['**/*.js'].each do |source| - dest = File.join out_dir, source - - FileUtils.install source, dest, :mode => 0644, :preserve => true, :verbose => $DEBUG_RDOC - end - end - end - - ## - # Compress the search_index.js file using gzip - - def generate_gzipped - return if @options.dry_run or not defined?(Zlib) - - debug_msg "Compressing generated JSON index" - out_dir = @base_dir + @options.op_dir - - search_index_file = out_dir + SEARCH_INDEX_FILE - outfile = out_dir + "#{search_index_file}.gz" - - debug_msg "Reading the JSON index file from %s" % search_index_file - search_index = search_index_file.read(mode: 'r:utf-8') - - debug_msg "Writing gzipped search index to %s" % outfile - - Zlib::GzipWriter.open(outfile) do |gz| - gz.mtime = File.mtime(search_index_file) - gz.orig_name = search_index_file.basename.to_s - gz.write search_index - gz.close - end - - # GZip the rest of the js files - Dir.chdir @template_dir do - Dir['**/*.js'].each do |source| - dest = out_dir + source - outfile = out_dir + "#{dest}.gz" - - debug_msg "Reading the original js file from %s" % dest - data = dest.read - - debug_msg "Writing gzipped file to %s" % outfile - - Zlib::GzipWriter.open(outfile) do |gz| - gz.mtime = File.mtime(dest) - gz.orig_name = dest.basename.to_s - gz.write data - gz.close - end - end - end - end - - ## - # Adds classes and modules to the index - - def index_classes - debug_msg " generating class search index" - - documented = @classes.uniq.select do |klass| - klass.document_self_or_methods - end - - documented.each do |klass| - debug_msg " #{klass.full_name}" - record = klass.search_record - @index[:searchIndex] << search_string(record.shift) - @index[:longSearchIndex] << search_string(record.shift) - @index[:info] << record - end - end - - ## - # Adds methods to the index - - def index_methods - debug_msg " generating method search index" - - list = @classes.uniq.flat_map do |klass| - klass.method_list - end.sort_by do |method| - [method.name, method.parent.full_name] - end - - list.each do |method| - debug_msg " #{method.full_name}" - record = method.search_record - @index[:searchIndex] << "#{search_string record.shift}()" - @index[:longSearchIndex] << "#{search_string record.shift}()" - @index[:info] << record - end - end - - ## - # Adds pages to the index - - def index_pages - debug_msg " generating pages search index" - - pages = @files.select do |file| - file.text? - end - - pages.each do |page| - debug_msg " #{page.page_name}" - record = page.search_record - @index[:searchIndex] << search_string(record.shift) - @index[:longSearchIndex] << '' - record.shift - @index[:info] << record - end - end - - ## - # The directory classes are written to - - def class_dir - @parent_generator.class_dir - end - - ## - # The directory files are written to - - def file_dir - @parent_generator.file_dir - end - - def reset files, classes # :nodoc: - @files = files - @classes = classes - - @index = { - :searchIndex => [], - :longSearchIndex => [], - :info => [] - } - end - - ## - # Removes whitespace and downcases +string+ - - def search_string string - string.downcase.gsub(/\s/, '') - end - -end diff --git a/lib/rdoc/generator/markup.rb b/lib/rdoc/generator/markup.rb deleted file mode 100644 index 76b7d458aa..0000000000 --- a/lib/rdoc/generator/markup.rb +++ /dev/null @@ -1,159 +0,0 @@ -# frozen_string_literal: true -## -# Handle common RDoc::Markup tasks for various CodeObjects -# -# This module is loaded by generators. It allows RDoc's CodeObject tree to -# avoid loading generator code to improve startup time for +ri+. - -module RDoc::Generator::Markup - - ## - # Generates a relative URL from this object's path to +target_path+ - - def aref_to(target_path) - RDoc::Markup::ToHtml.gen_relative_url path, target_path - end - - ## - # Generates a relative URL from +from_path+ to this object's path - - def as_href(from_path) - RDoc::Markup::ToHtml.gen_relative_url from_path, path - end - - ## - # Handy wrapper for marking up this object's comment - - def description - markup @comment - end - - ## - # Creates an RDoc::Markup::ToHtmlCrossref formatter - - def formatter - return @formatter if defined? @formatter - - options = @store.rdoc.options - this = RDoc::Context === self ? self : @parent - - @formatter = RDoc::Markup::ToHtmlCrossref.new options, this.path, this - @formatter.code_object = self - @formatter - end - - ## - # Build a webcvs URL starting for the given +url+ with +full_path+ appended - # as the destination path. If +url+ contains '%s' +full_path+ will be - # will replace the %s using sprintf on the +url+. - - def cvs_url(url, full_path) - if /%s/ =~ url then - sprintf url, full_path - else - url + full_path - end - end - -end - -class RDoc::CodeObject - - include RDoc::Generator::Markup - -end - -class RDoc::MethodAttr - - ## - # Prepend +src+ with line numbers. Relies on the first line of a source - # code listing having: - # - # # File xxxxx, line dddd - # - # If it has this comment then line numbers are added to +src+ and the <tt>, - # line dddd</tt> portion of the comment is removed. - - def add_line_numbers(src) - return unless src.sub!(/\A(.*)(, line (\d+))/, '\1') - first = $3.to_i - 1 - last = first + src.count("\n") - size = last.to_s.length - - line = first - src.gsub!(/^/) do - res = if line == first then - " " * (size + 1) - else - "<span class=\"line-num\">%2$*1$d</span> " % [size, line] - end - - line += 1 - res - end - end - - ## - # Turns the method's token stream into HTML. - # - # Prepends line numbers if +options.line_numbers+ is true. - - def markup_code - return '' unless @token_stream - - src = RDoc::TokenStream.to_html @token_stream - - # dedent the source - indent = src.length - lines = src.lines.to_a - lines.shift if src =~ /\A.*#\ *File/i # remove '# File' comment - lines.each do |line| - if line =~ /^ *(?=\S)/ - n = $~.end(0) - indent = n if n < indent - break if n == 0 - end - end - src.gsub!(/^#{' ' * indent}/, '') if indent > 0 - - add_line_numbers(src) if options.line_numbers - - src - end - -end - -class RDoc::ClassModule - - ## - # Handy wrapper for marking up this class or module's comment - - def description - markup @comment_location - end - -end - -class RDoc::Context::Section - - include RDoc::Generator::Markup - -end - -class RDoc::TopLevel - - ## - # Returns a URL for this source file on some web repository. Use the -W - # command line option to set. - - def cvs_url - url = @store.rdoc.options.webcvs - - if /%s/ =~ url then - url % @relative_name - else - url + @relative_name - end - end - -end diff --git a/lib/rdoc/generator/pot.rb b/lib/rdoc/generator/pot.rb deleted file mode 100644 index b0b7c07179..0000000000 --- a/lib/rdoc/generator/pot.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true -## -# Generates a POT file. -# -# Here is a translator work flow with the generator. -# -# == Create .pot -# -# You create .pot file by pot formatter: -# -# % rdoc --format pot -# -# It generates doc/rdoc.pot. -# -# == Create .po -# -# You create .po file from doc/rdoc.pot. This operation is needed only -# the first time. This work flow assumes that you are a translator -# for Japanese. -# -# You create locale/ja/rdoc.po from doc/rdoc.pot. You can use msginit -# provided by GNU gettext or rmsginit provided by gettext gem. This -# work flow uses gettext gem because it is more portable than GNU -# gettext for Rubyists. Gettext gem is implemented by pure Ruby. -# -# % gem install gettext -# % mkdir -p locale/ja -# % rmsginit --input doc/rdoc.pot --output locale/ja/rdoc.po --locale ja -# -# Translate messages in .po -# -# You translate messages in .po by a PO file editor. po-mode.el exists -# for Emacs users. There are some GUI tools such as GTranslator. -# There are some Web services such as POEditor and Tansifex. You can -# edit by your favorite text editor because .po is a text file. -# Generate localized documentation -# -# You can generate localized documentation with locale/ja/rdoc.po: -# -# % rdoc --locale ja -# -# You can find documentation in Japanese in doc/. Yay! -# -# == Update translation -# -# You need to update translation when your application is added or -# modified messages. -# -# You can update .po by the following command lines: -# -# % rdoc --format pot -# % rmsgmerge --update locale/ja/rdoc.po doc/rdoc.pot -# -# You edit locale/ja/rdoc.po to translate new messages. - -class RDoc::Generator::POT - - RDoc::RDoc.add_generator self - - ## - # Description of this generator - - DESCRIPTION = 'creates .pot file' - - ## - # Set up a new .pot generator - - def initialize store, options #:not-new: - @options = options - @store = store - end - - ## - # Writes .pot to disk. - - def generate - po = extract_messages - pot_path = 'rdoc.pot' - File.open(pot_path, "w") do |pot| - pot.print(po.to_s) - end - end - - # :nodoc: - def class_dir - nil - end - - private - def extract_messages - extractor = MessageExtractor.new(@store) - extractor.extract - end - - require_relative 'pot/message_extractor' - require_relative 'pot/po' - require_relative 'pot/po_entry' - -end diff --git a/lib/rdoc/generator/pot/message_extractor.rb b/lib/rdoc/generator/pot/message_extractor.rb deleted file mode 100644 index 4938858bdc..0000000000 --- a/lib/rdoc/generator/pot/message_extractor.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true -## -# Extracts message from RDoc::Store - -class RDoc::Generator::POT::MessageExtractor - - ## - # Creates a message extractor for +store+. - - def initialize store - @store = store - @po = RDoc::Generator::POT::PO.new - end - - ## - # Extracts messages from +store+, stores them into - # RDoc::Generator::POT::PO and returns it. - - def extract - @store.all_classes_and_modules.each do |klass| - extract_from_klass(klass) - end - @po - end - - private - - def extract_from_klass klass - extract_text(klass.comment_location, klass.full_name) - - klass.each_section do |section, constants, attributes| - extract_text(section.title, "#{klass.full_name}: section title") - section.comments.each do |comment| - extract_text(comment, "#{klass.full_name}: #{section.title}") - end - end - - klass.each_constant do |constant| - extract_text(constant.comment, constant.full_name) - end - - klass.each_attribute do |attribute| - extract_text(attribute.comment, attribute.full_name) - end - - klass.each_method do |method| - extract_text(method.comment, method.full_name) - end - end - - def extract_text text, comment, location = nil - return if text.nil? - - options = { - :extracted_comment => comment, - :references => [location].compact, - } - i18n_text = RDoc::I18n::Text.new(text) - i18n_text.extract_messages do |part| - @po.add(entry(part[:paragraph], options)) - end - end - - def entry msgid, options - RDoc::Generator::POT::POEntry.new(msgid, options) - end - -end diff --git a/lib/rdoc/generator/pot/po.rb b/lib/rdoc/generator/pot/po.rb deleted file mode 100644 index 37d45e5258..0000000000 --- a/lib/rdoc/generator/pot/po.rb +++ /dev/null @@ -1,84 +0,0 @@ -# frozen_string_literal: true -## -# Generates a PO format text - -class RDoc::Generator::POT::PO - - ## - # Creates an object that represents PO format. - - def initialize - @entries = {} - add_header - end - - ## - # Adds a PO entry to the PO. - - def add entry - existing_entry = @entries[entry.msgid] - if existing_entry - entry = existing_entry.merge(entry) - end - @entries[entry.msgid] = entry - end - - ## - # Returns PO format text for the PO. - - def to_s - po = '' - sort_entries.each do |entry| - po += "\n" unless po.empty? - po += entry.to_s - end - po - end - - private - - def add_header - add(header_entry) - end - - def header_entry - comment = <<-COMMENT -SOME DESCRIPTIVE TITLE. -Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -This file is distributed under the same license as the PACKAGE package. -FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. - COMMENT - - content = <<-CONTENT -Project-Id-Version: PACKAGE VERSEION -Report-Msgid-Bugs-To: -PO-Revision-Date: YEAR-MO_DA HO:MI+ZONE -Last-Translator: FULL NAME <EMAIL@ADDRESS> -Language-Team: LANGUAGE <LL@li.org> -Language: -MIME-Version: 1.0 -Content-Type: text/plain; charset=CHARSET -Content-Transfer-Encoding: 8bit -Plural-Forms: nplurals=INTEGER; plural=EXPRESSION; - CONTENT - - options = { - :msgstr => content, - :translator_comment => comment, - :flags => ['fuzzy'], - } - RDoc::Generator::POT::POEntry.new('', options) - end - - def sort_entries - headers, messages = @entries.values.partition do |entry| - entry.msgid.empty? - end - # TODO: sort by location - sorted_messages = messages.sort_by do |entry| - entry.msgid - end - headers + sorted_messages - end - -end diff --git a/lib/rdoc/generator/pot/po_entry.rb b/lib/rdoc/generator/pot/po_entry.rb deleted file mode 100644 index 7454b29273..0000000000 --- a/lib/rdoc/generator/pot/po_entry.rb +++ /dev/null @@ -1,141 +0,0 @@ -# frozen_string_literal: true -## -# A PO entry in PO - -class RDoc::Generator::POT::POEntry - - # The msgid content - attr_reader :msgid - - # The msgstr content - attr_reader :msgstr - - # The comment content created by translator (PO editor) - attr_reader :translator_comment - - # The comment content extracted from source file - attr_reader :extracted_comment - - # The locations where the PO entry is extracted - attr_reader :references - - # The flags of the PO entry - attr_reader :flags - - ## - # Creates a PO entry for +msgid+. Other values can be specified by - # +options+. - - def initialize msgid, options = {} - @msgid = msgid - @msgstr = options[:msgstr] || "" - @translator_comment = options[:translator_comment] - @extracted_comment = options[:extracted_comment] - @references = options[:references] || [] - @flags = options[:flags] || [] - end - - ## - # Returns the PO entry in PO format. - - def to_s - entry = '' - entry += format_translator_comment - entry += format_extracted_comment - entry += format_references - entry += format_flags - entry += <<-ENTRY -msgid #{format_message(@msgid)} -msgstr #{format_message(@msgstr)} - ENTRY - end - - ## - # Merges the PO entry with +other_entry+. - - def merge other_entry - options = { - :extracted_comment => merge_string(@extracted_comment, - other_entry.extracted_comment), - :translator_comment => merge_string(@translator_comment, - other_entry.translator_comment), - :references => merge_array(@references, - other_entry.references), - :flags => merge_array(@flags, - other_entry.flags), - } - self.class.new(@msgid, options) - end - - private - - def format_comment mark, comment - return '' unless comment - return '' if comment.empty? - - formatted_comment = '' - comment.each_line do |line| - formatted_comment += "#{mark} #{line}" - end - formatted_comment += "\n" unless formatted_comment.end_with?("\n") - formatted_comment - end - - def format_translator_comment - format_comment('#', @translator_comment) - end - - def format_extracted_comment - format_comment('#.', @extracted_comment) - end - - def format_references - return '' if @references.empty? - - formatted_references = '' - @references.sort.each do |file, line| - formatted_references += "\#: #{file}:#{line}\n" - end - formatted_references - end - - def format_flags - return '' if @flags.empty? - - formatted_flags = flags.join(",") - "\#, #{formatted_flags}\n" - end - - def format_message message - return "\"#{escape(message)}\"" unless message.include?("\n") - - formatted_message = '""' - message.each_line do |line| - formatted_message += "\n" - formatted_message += "\"#{escape(line)}\"" - end - formatted_message - end - - def escape string - string.gsub(/["\\\t\n]/) do |special_character| - case special_character - when "\t" - "\\t" - when "\n" - "\\n" - else - "\\#{special_character}" - end - end - end - - def merge_string string1, string2 - [string1, string2].compact.join("\n") - end - - def merge_array array1, array2 - (array1 + array2).uniq - end - -end diff --git a/lib/rdoc/generator/ri.rb b/lib/rdoc/generator/ri.rb deleted file mode 100644 index 1c2f018f97..0000000000 --- a/lib/rdoc/generator/ri.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true -## -# Generates ri data files - -class RDoc::Generator::RI - - RDoc::RDoc.add_generator self - - ## - # Description of this generator - - DESCRIPTION = 'creates ri data files' - - ## - # Set up a new ri generator - - def initialize store, options #:not-new: - @options = options - @store = store - @store.path = '.' - end - - ## - # Writes the parsed data store to disk for use by ri. - - def generate - @store.save - end - -end diff --git a/lib/rdoc/generator/template/darkfish/.document b/lib/rdoc/generator/template/darkfish/.document deleted file mode 100644 index e69de29bb2..0000000000 --- a/lib/rdoc/generator/template/darkfish/.document +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/_footer.rhtml b/lib/rdoc/generator/template/darkfish/_footer.rhtml deleted file mode 100644 index 9791b42901..0000000000 --- a/lib/rdoc/generator/template/darkfish/_footer.rhtml +++ /dev/null @@ -1,5 +0,0 @@ -<footer id="validator-badges" role="contentinfo"> - <p><a href="https://validator.w3.org/check/referer">Validate</a> - <p>Generated by <a href="https://ruby.github.io/rdoc/">RDoc</a> <%= RDoc::VERSION %>. - <p>Based on <a href="http://deveiate.org/projects/Darkfish-RDoc/">Darkfish</a> by <a href="http://deveiate.org">Michael Granger</a>. -</footer> diff --git a/lib/rdoc/generator/template/darkfish/_head.rhtml b/lib/rdoc/generator/template/darkfish/_head.rhtml deleted file mode 100644 index 9e6fb4f94c..0000000000 --- a/lib/rdoc/generator/template/darkfish/_head.rhtml +++ /dev/null @@ -1,43 +0,0 @@ -<meta charset="<%= @options.charset %>"> -<meta name="viewport" content="width=device-width, initial-scale=1" /> - -<title><%= h @title %></title> - -<%- if defined?(klass) -%> - <meta name="keywords" content="ruby,<%= h "#{klass.type},#{klass.full_name}" %>"> - - <%- if klass.comment.empty? -%> - <meta name="description" content="Documentation for the <%= h "#{klass.full_name} #{klass.type}" %>"> - <%- else -%> - <meta name="description" content="<%= h "#{klass.type} #{klass.full_name}: #{excerpt(klass.comment)}" %>"> - <%- end -%> -<%- elsif defined?(file) -%> - <meta name="keywords" content="ruby,documentation,<%= h file.page_name %>"> - <meta name="description" content="<%= h "#{file.page_name}: #{excerpt(file.comment)}" %>"> -<%- elsif @title -%> - <meta name="keywords" content="ruby,documentation,<%= h @title %>"> - - <%- if @options.main_page and - main_page = @files.find { |f| f.full_name == @options.main_page } then %> - <meta name="description" content="<%= h "#{@title}: #{excerpt(main_page.comment)}" %>"> - <%- else -%> - <meta name="description" content="Documentation for <%= h @title %>"> - <%- end -%> -<%- end -%> - -<script type="text/javascript"> - var rdoc_rel_prefix = "<%= h asset_rel_prefix %>/"; - var index_rel_prefix = "<%= h rel_prefix %>/"; -</script> - -<script src="<%= h asset_rel_prefix %>/js/navigation.js" defer></script> -<script src="<%= h asset_rel_prefix %>/js/search.js" defer></script> -<script src="<%= h asset_rel_prefix %>/js/search_index.js" defer></script> -<script src="<%= h asset_rel_prefix %>/js/searcher.js" defer></script> -<script src="<%= h asset_rel_prefix %>/js/darkfish.js" defer></script> - -<link href="<%= h asset_rel_prefix %>/css/fonts.css" rel="stylesheet"> -<link href="<%= h asset_rel_prefix %>/css/rdoc.css" rel="stylesheet"> -<%- @options.template_stylesheets.each do |stylesheet| -%> -<link href="<%= h asset_rel_prefix %>/<%= File.basename stylesheet %>" rel="stylesheet"> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml deleted file mode 100644 index 22a12d9e95..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml +++ /dev/null @@ -1,19 +0,0 @@ -<%- if !svninfo.empty? then %> -<div id="file-svninfo-section" class="nav-section"> - <h3>VCS Info</h3> - - <div class="section-body"> - <dl class="svninfo"> - <dt>Rev - <dd><%= svninfo[:rev] %> - - <dt>Last Checked In - <dd><%= svninfo[:commitdate].strftime('%Y-%m-%d %H:%M:%S') %> - (<%= svninfo[:commitdelta] %> ago) - - <dt>Checked in by - <dd><%= svninfo[:committer] %> - </dl> - </div> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml deleted file mode 100644 index d3d8da4017..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml +++ /dev/null @@ -1,33 +0,0 @@ -<div id="classindex-section" class="nav-section"> - <h3>Class and Module Index</h3> - - <%- - all_classes = @classes.group_by do |klass| - klass.full_name[/\A[^:]++(?:::[^:]++(?=::))*+(?=::[^:]*+\z)/] - end.delete_if do |_, klasses| - !klasses.any?(&:display?) - end - link = proc do |index_klass, display = index_klass.display?| - if display - -%><code><a href="<%= rel_prefix %>/<%= index_klass.path %>"><%= index_klass.name %></a></code><%- - else - -%><code><%= index_klass.name %></code><%- - end - end - if top = all_classes[nil] - solo = top.one? {|klass| klass.display?} - traverse = proc do |klasses| -%> - <ul class="link-list"> - <%- klasses.each do |index_klass| -%> - <%- if children = all_classes[index_klass.full_name] -%> - <li><details<% if solo; solo = false %> open<% end %>><summary><% link.call(index_klass) %></summary> - <%- traverse.call(children) -%> - </ul></details> - <%- elsif index_klass.display? -%> - <li><% link.call(index_klass, true) %> - <%- end -%> - <%- end -%> - <%- end -%> - <%- traverse.call(top) -%> - <%- end -%> -</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml deleted file mode 100644 index 7602076c96..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml +++ /dev/null @@ -1,15 +0,0 @@ -<%- unless klass.extends.empty? then %> -<div id="extends-section" class="nav-section"> - <h3>Extended With Modules</h3> - - <ul class="link-list"> - <%- klass.each_extend do |ext| -%> - <%- unless String === ext.module then -%> - <li><a class="extend" href="<%= klass.aref_to ext.module.path %>"><%= ext.module.full_name %></a> - <%- else -%> - <li><span class="extend"><%= ext.name %></span> - <%- end -%> - <%- end -%> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml deleted file mode 100644 index 74869a4b51..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml +++ /dev/null @@ -1,9 +0,0 @@ -<div id="file-list-section" class="nav-section"> - <h3>Defined In</h3> - - <ul> -<%- klass.in_files.each do |tl| -%> - <li><%= h tl.relative_name %> -<%- end -%> - </ul> -</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml deleted file mode 100644 index 5b600e5975..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml +++ /dev/null @@ -1,15 +0,0 @@ -<%- unless klass.includes.empty? then %> -<div id="includes-section" class="nav-section"> - <h3>Included Modules</h3> - - <ul class="link-list"> - <%- klass.each_include do |inc| -%> - <%- unless String === inc.module then -%> - <li><a class="include" href="<%= klass.aref_to inc.module.path %>"><%= inc.module.full_name %></a> - <%- else -%> - <li><span class="include"><%= inc.name %></span> - <%- end -%> - <%- end -%> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml deleted file mode 100644 index faed7e0a94..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml +++ /dev/null @@ -1,15 +0,0 @@ -<div id="home-section" class="nav-section"> - <h3>Documentation</h3> - - <ul> - <%- installed.each do |name, href, exists, type, _| -%> - <%- next if type == :extra -%> - <li class="folder"> - <%- if exists then -%> - <a href="<%= href %>"><%= h name %></a> - <%- else -%> - <%= h name %> - <%- end -%> - <%- end -%> - </ul> -</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml deleted file mode 100644 index 5b4c295bed..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml +++ /dev/null @@ -1,12 +0,0 @@ -<%- unless klass.method_list.empty? then %> -<!-- Method Quickref --> -<div id="method-list-section" class="nav-section"> - <h3>Methods</h3> - - <ul class="link-list" role="directory"> - <%- klass.each_method do |meth| -%> - <li <%- if meth.calls_super %>class="calls-super" <%- end %>><a href="#<%= meth.aref %>"><%= meth.singleton ? '::' : '#' %><%= h meth.name -%></a> - <%- end -%> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml deleted file mode 100644 index d7f330840a..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml +++ /dev/null @@ -1,11 +0,0 @@ -<div id="home-section" role="region" title="Quick navigation" class="nav-section"> - <h2> - <a href="<%= rel_prefix %>/index.html" rel="home">Home</a> - </h2> - - <div id="table-of-contents-navigation"> - <a href="<%= rel_prefix %>/table_of_contents.html#pages">Pages</a> - <a href="<%= rel_prefix %>/table_of_contents.html#classes">Classes</a> - <a href="<%= rel_prefix %>/table_of_contents.html#methods">Methods</a> - </div> -</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml deleted file mode 100644 index 3f68f0c0dc..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml +++ /dev/null @@ -1,32 +0,0 @@ -<%- simple_files = @files.select { |f| f.text? } %> -<%- if defined?(current) -%> - <%- dir = current.full_name[%r{\A[^/]+(?=/)}] || current.page_name -%> -<%- end -%> -<%- unless simple_files.empty? then -%> -<div id="fileindex-section" class="nav-section"> - <h3>Pages</h3> - - <ul class="link-list"> - <%- simple_files.group_by do |f| -%> - <%- f.full_name[%r{\A[^/]+(?=/)}] || f.page_name -%> - <%- end.each do |n, files| -%> - <%- f = files.shift -%> - <%- if files.empty? -%> - <li><a href="<%= rel_prefix %>/<%= h f.path %>"><%= h f.page_name %></a> - <%- next -%> - <%- end -%> - <li><details<% if dir == n %> open<% end %>><summary><% - if n == f.page_name - %><a href="<%= rel_prefix %>/<%= h f.path %>"><%= h n %></a><% - else - %><%= h n %><% files.unshift(f) - end %></summary> - <ul class="link-list"> - <%- files.each do |f| -%> - <li><a href="<%= rel_prefix %>/<%= h f.path %>"><%= h f.page_name %></a> - <%- end -%> - </ul></details> - <%- end -%> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml deleted file mode 100644 index 1420da3201..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml +++ /dev/null @@ -1,11 +0,0 @@ -<%- if klass.type == 'class' then %> -<div id="parent-class-section" class="nav-section"> - <h3>Parent</h3> - - <%- if klass.superclass and not String === klass.superclass then -%> - <p class="link"><a href="<%= klass.aref_to klass.superclass.path %>"><%= klass.superclass.full_name %></a> - <%- else -%> - <p class="link"><%= klass.superclass %> - <%- end -%> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml deleted file mode 100644 index afc7f7b88d..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml +++ /dev/null @@ -1,14 +0,0 @@ -<div id="search-section" role="search" class="project-section initially-hidden"> - <form action="#" method="get" accept-charset="utf-8"> - <div id="search-field-wrapper"> - <input id="search-field" role="combobox" aria-label="Search" - aria-autocomplete="list" aria-controls="search-results" - type="text" name="search" placeholder="Search (/) for a class, method, ..." spellcheck="false" - title="Type to search, Up and Down to navigate, Enter to load"> - </div> - - <ul id="search-results" aria-label="Search Results" - aria-busy="false" aria-expanded="false" - aria-atomic="false" class="initially-hidden"></ul> - </form> -</div> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml deleted file mode 100644 index 6dcd2ae81f..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml +++ /dev/null @@ -1,11 +0,0 @@ -<%- unless klass.sections.length == 1 then %> -<div id="sections-section" class="nav-section"> - <h3>Sections</h3> - - <ul class="link-list" role="directory"> - <%- klass.sort_sections.each do |section| -%> - <li><a href="#<%= section.aref %>"><%= h section.title %></a></li> - <%- end -%> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml deleted file mode 100644 index b1e047b5f7..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml +++ /dev/null @@ -1,39 +0,0 @@ -<%- comment = if current.respond_to? :comment_location then - current.comment_location - else - current.comment - end - table = current.parse(comment).table_of_contents.dup - - if table.length > 1 then %> -<div class="nav-section"> - <h3>Table of Contents</h3> - - <%- display_link = proc do |heading| -%> - <a href="#<%= heading.label current %>"><%= heading.plain_html %></a> - <%- end -%> - - <%- list_siblings = proc do -%> - <%- level = table.first&.level -%> - <%- while table.first && table.first.level >= level -%> - <%- heading = table.shift -%> - <%- if table.first.nil? || table.first.level <= heading.level -%> - <li><% display_link.call heading -%> - <%- else -%> - <li> - <details open> - <summary><%- display_link.call heading -%></summary> - <ul class="link-list" role="directory"> - <% list_siblings.call %> - </ul> - </details> - </li> - <%- end -%> - <%- end -%> - <%- end -%> - - <ul class="link-list" role="directory"> - <% list_siblings.call %> - </ul> -</div> -<%- end -%> diff --git a/lib/rdoc/generator/template/darkfish/_sidebar_toggle.rhtml b/lib/rdoc/generator/template/darkfish/_sidebar_toggle.rhtml deleted file mode 100644 index ed2cbe31a0..0000000000 --- a/lib/rdoc/generator/template/darkfish/_sidebar_toggle.rhtml +++ /dev/null @@ -1,3 +0,0 @@ -<div id="navigation-toggle" role="button" tabindex="0" aria-label="Toggle sidebar" aria-expanded="true" aria-controls="navigation"> - <span aria-hidden="true">☰</span> -</div> diff --git a/lib/rdoc/generator/template/darkfish/class.rhtml b/lib/rdoc/generator/template/darkfish/class.rhtml deleted file mode 100644 index 0bec9fc9ce..0000000000 --- a/lib/rdoc/generator/template/darkfish/class.rhtml +++ /dev/null @@ -1,206 +0,0 @@ -<body id="top" role="document" class="<%= klass.type %>"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <%= render '_sidebar_navigation.rhtml' %> - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_sidebar_table_of_contents.rhtml' %> - <%= render '_sidebar_sections.rhtml' %> - <%= render '_sidebar_parent.rhtml' %> - <%= render '_sidebar_includes.rhtml' %> - <%= render '_sidebar_extends.rhtml' %> - <%= render '_sidebar_methods.rhtml' %> - - <%= render '_footer.rhtml' %> -</nav> - -<main role="main" aria-labelledby="<%=h klass.aref %>"> - <h1 id="<%=h klass.aref %>" class="anchor-link <%= klass.type %>"> - <%= klass.type %> <%= klass.full_name %> - </h1> - - <section class="description"> - <%= klass.description %> - </section> - - <%- klass.each_section do |section, constants, attributes| -%> - <section id="<%= section.aref %>" class="documentation-section anchor-link"> - <%- if section.title then -%> - <header class="documentation-section-title"> - <h2> - <%= section.title %> - </h2> - <span class="section-click-top"> - <a href="#top">↑ top</a> - </span> - </header> - <%- end -%> - - <%- if section.comment then -%> - <div> - <%= section.description %> - </div> - <%- end -%> - - <%- unless constants.empty? then -%> - <section class="constants-list"> - <header> - <h3>Constants</h3> - </header> - <dl> - <%- constants.each do |const| -%> - <dt id="<%= const.name %>"><%= const.name %> - <%- if const.comment then -%> - <dd> - <%- if const.mixin_from then -%> - <div class="mixin-from"> - Included from <a href="<%= klass.aref_to(const.mixin_from.path)%>"><%= const.mixin_from.full_name %></a> - </div> - <%- end -%> - <%= const.description.strip %> - <%- else -%> - <dd class="missing-docs">(Not documented) - <%- end -%> - <%- end -%> - </dl> - </section> - <%- end -%> - - <%- unless attributes.empty? then -%> - <section class="attribute-method-details" class="method-section"> - <header> - <h3>Attributes</h3> - </header> - - <%- attributes.each do |attrib| -%> - <div id="<%= attrib.aref %>" class="method-detail anchor-link"> - <div class="method-heading attribute-method-heading"> - <a href="#<%= attrib.aref %>" title="Link to this attribute"> - <span class="method-name"><%= h attrib.name %></span> - <span class="attribute-access-type">[<%= attrib.rw %>]</span> - </a> - </div> - - <div class="method-description"> - <%- if attrib.mixin_from then -%> - <div class="mixin-from"> - <%= attrib.singleton ? "Extended" : "Included" %> from <a href="<%= klass.aref_to(attrib.mixin_from.path)%>"><%= attrib.mixin_from.full_name %></a> - </div> - <%- end -%> - <%- if attrib.comment then -%> - <%= attrib.description.strip %> - <%- else -%> - <p class="missing-docs">(Not documented) - <%- end -%> - </div> - </div> - <%- end -%> - </section> - <%- end -%> - - <%- klass.methods_by_type(section).each do |type, visibilities| - next if visibilities.empty? - visibilities.each do |visibility, methods| - next if methods.empty? %> - <section id="<%= visibility %>-<%= type %>-<%= section.aref %>-method-details" class="method-section anchor-link"> - <header> - <h3><%= visibility.to_s.capitalize %> <%= type.capitalize %> Methods</h3> - </header> - - <%- methods.each do |method| -%> - <div id="<%= method.aref %>" class="method-detail anchor-link <%= method.is_alias_for ? "method-alias" : '' %>"> - <div class="method-header"> - <%- if (call_seq = method.call_seq) then -%> - <%- call_seq.strip.split("\n").each_with_index do |call_seq, i| -%> - <div class="method-heading"> - <a href="#<%= method.aref %>" title="Link to this method"> - <span class="method-callseq"> - <%= h(call_seq.strip. - gsub( /^\w+\./m, '')). - gsub(/(.*)[-=]>/, '\1→') %> - </span> - </a> - </div> - <%- end -%> - <%- elsif method.has_call_seq? then -%> - <div class="method-heading"> - <a href="#<%= method.aref %>" title="Link to this method"> - <span class="method-name"><%= h method.name %></span> - </a> - </div> - <%- else -%> - <div class="method-heading"> - <a href="#<%= method.aref %>" title="Link to this method"> - <span class="method-name"><%= h method.name %></span> - <span class="method-args"><%= h method.param_seq %></span> - </a> - </div> - <%- end -%> - </div> - - <%- if method.token_stream -%> - <div class="method-controls"> - <details class="method-source-toggle"> - <summary>Source</summary> - </details> - </div> - <%- end -%> - - <%- unless method.skip_description? then -%> - <div class="method-description"> - <%- if method.token_stream then -%> - <div class="method-source-code" id="<%= method.html_name %>-source"> - <pre><%= method.markup_code %></pre> - </div> - <%- end -%> - <%- if method.mixin_from then -%> - <div class="mixin-from"> - <%= method.singleton ? "Extended" : "Included" %> from <a href="<%= klass.aref_to(method.mixin_from.path)%>"><%= method.mixin_from.full_name %></a> - </div> - <%- end -%> - <%- if method.comment then -%> - <%= method.description.strip %> - <%- else -%> - <p class="missing-docs">(Not documented) - <%- end -%> - <%- if method.calls_super then -%> - <div class="method-calls-super"> - Calls superclass method - <%= - method.superclass_method ? - method.formatter.link(method.superclass_method.full_name, method.superclass_method.full_name) : nil - %> - </div> - <%- end -%> - </div> - <%- end -%> - - <%- unless method.aliases.empty? then -%> - <div class="aliases"> - Also aliased as: <%= method.aliases.map do |aka| - if aka.parent then # HACK lib/rexml/encodings - %{<a href="#{klass.aref_to aka.path}">#{h aka.name}</a>} - else - h aka.name - end - end.join ", " %> - </div> - <%- end -%> - - <%- if method.is_alias_for then -%> - <div class="aliases"> - Alias for: <a href="<%= klass.aref_to method.is_alias_for.path %>"><%= h method.is_alias_for.name %></a> - </div> - <%- end -%> - </div> - - <%- end -%> - </section> - <%- end - end %> - </section> -<%- end -%> -</main> diff --git a/lib/rdoc/generator/template/darkfish/css/fonts.css b/lib/rdoc/generator/template/darkfish/css/fonts.css deleted file mode 100644 index 57302b5183..0000000000 --- a/lib/rdoc/generator/template/darkfish/css/fonts.css +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), - * with Reserved Font Name "Source". All Rights Reserved. Source is a - * trademark of Adobe Systems Incorporated in the United States and/or other - * countries. - * - * This Font Software is licensed under the SIL Open Font License, Version - * 1.1. - * - * This license is copied below, and is also available with a FAQ at: - * http://scripts.sil.org/OFL - */ - -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 400; - src: local("Source Code Pro"), - local("SourceCodePro-Regular"), - url("../fonts/SourceCodePro-Regular.ttf") format("truetype"); -} - -@font-face { - font-family: "Source Code Pro"; - font-style: normal; - font-weight: 700; - src: local("Source Code Pro Bold"), - local("SourceCodePro-Bold"), - url("../fonts/SourceCodePro-Bold.ttf") format("truetype"); -} - -/* - * Copyright (c) 2010, Łukasz Dziedzic (dziedzic@typoland.com), - * with Reserved Font Name Lato. - * - * This Font Software is licensed under the SIL Open Font License, Version - * 1.1. - * - * This license is copied below, and is also available with a FAQ at: - * http://scripts.sil.org/OFL - */ - -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 300; - src: local("Lato Light"), - local("Lato-Light"), - url("../fonts/Lato-Light.ttf") format("truetype"); -} - -@font-face { - font-family: "Lato"; - font-style: italic; - font-weight: 300; - src: local("Lato Light Italic"), - local("Lato-LightItalic"), - url("../fonts/Lato-LightItalic.ttf") format("truetype"); -} - -@font-face { - font-family: "Lato"; - font-style: normal; - font-weight: 700; - src: local("Lato Regular"), - local("Lato-Regular"), - url("../fonts/Lato-Regular.ttf") format("truetype"); -} - -@font-face { - font-family: "Lato"; - font-style: italic; - font-weight: 700; - src: local("Lato Italic"), - local("Lato-Italic"), - url("../fonts/Lato-RegularItalic.ttf") format("truetype"); -} - -/* - * ----------------------------------------------------------- - * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 - * ----------------------------------------------------------- - * - * PREAMBLE - * The goals of the Open Font License (OFL) are to stimulate worldwide - * development of collaborative font projects, to support the font creation - * efforts of academic and linguistic communities, and to provide a free and - * open framework in which fonts may be shared and improved in partnership - * with others. - * - * The OFL allows the licensed fonts to be used, studied, modified and - * redistributed freely as long as they are not sold by themselves. The - * fonts, including any derivative works, can be bundled, embedded, - * redistributed and/or sold with any software provided that any reserved - * names are not used by derivative works. The fonts and derivatives, - * however, cannot be released under any other type of license. The - * requirement for fonts to remain under this license does not apply - * to any document created using the fonts or their derivatives. - * - * DEFINITIONS - * "Font Software" refers to the set of files released by the Copyright - * Holder(s) under this license and clearly marked as such. This may - * include source files, build scripts and documentation. - * - * "Reserved Font Name" refers to any names specified as such after the - * copyright statement(s). - * - * "Original Version" refers to the collection of Font Software components as - * distributed by the Copyright Holder(s). - * - * "Modified Version" refers to any derivative made by adding to, deleting, - * or substituting -- in part or in whole -- any of the components of the - * Original Version, by changing formats or by porting the Font Software to a - * new environment. - * - * "Author" refers to any designer, engineer, programmer, technical - * writer or other person who contributed to the Font Software. - * - * PERMISSION & CONDITIONS - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of the Font Software, to use, study, copy, merge, embed, modify, - * redistribute, and sell modified and unmodified copies of the Font - * Software, subject to the following conditions: - * - * 1) Neither the Font Software nor any of its individual components, - * in Original or Modified Versions, may be sold by itself. - * - * 2) Original or Modified Versions of the Font Software may be bundled, - * redistributed and/or sold with any software, provided that each copy - * contains the above copyright notice and this license. These can be - * included either as stand-alone text files, human-readable headers or - * in the appropriate machine-readable metadata fields within text or - * binary files as long as those fields can be easily viewed by the user. - * - * 3) No Modified Version of the Font Software may use the Reserved Font - * Name(s) unless explicit written permission is granted by the corresponding - * Copyright Holder. This restriction only applies to the primary font name as - * presented to the users. - * - * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font - * Software shall not be used to promote, endorse or advertise any - * Modified Version, except to acknowledge the contribution(s) of the - * Copyright Holder(s) and the Author(s) or with their explicit written - * permission. - * - * 5) The Font Software, modified or unmodified, in part or in whole, - * must be distributed entirely under this license, and must not be - * distributed under any other license. The requirement for fonts to - * remain under this license does not apply to any document created - * using the Font Software. - * - * TERMINATION - * This license becomes null and void if any of the above conditions are - * not met. - * - * DISCLAIMER - * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT - * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE - * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL - * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM - * OTHER DEALINGS IN THE FONT SOFTWARE. - */ - diff --git a/lib/rdoc/generator/template/darkfish/css/rdoc.css b/lib/rdoc/generator/template/darkfish/css/rdoc.css deleted file mode 100644 index 82c1e4baf6..0000000000 --- a/lib/rdoc/generator/template/darkfish/css/rdoc.css +++ /dev/null @@ -1,662 +0,0 @@ -/* - * "Darkfish" RDoc CSS - * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $ - * - * Author: Michael Granger <ged@FaerieMUD.org> - * - */ - -/* vim: ft=css et sw=2 ts=2 sts=2 */ - -/* 1. Variables and Root Styles */ -:root { - --sidebar-width: 300px; - --highlight-color: #cc342d; /* Reddish color for accents and headings */ - --secondary-highlight-color: #c83045; /* Darker reddish color for secondary highlights */ - --text-color: #505050; /* Dark bluish-grey for text */ - --background-color: #fefefe; /* Near white background */ - --code-block-background-color: #f6f6f3; /* Slightly darker grey for code blocks */ - --link-color: #42405F; /* Dark bluish-grey for links */ - --link-hover-color: var(--highlight-color); /* Reddish color on hover */ - --border-color: #e0e0e0;; /* General border color */ - --source-code-toggle-color: var(--secondary-highlight-color); - --scrollbar-thumb-hover-background: #505050; /* Hover color for scrollbar thumb */ - --table-header-background-color: #eceaed; - --table-td-background-color: #f5f4f6; - - /* Font family variables */ - --font-primary: 'Segoe UI', 'Verdana', 'Arial', sans-serif; - --font-heading: 'Helvetica', 'Arial', sans-serif; - --font-code: monospace; -} - -/* 2. Global Styles */ -body { - background: var(--background-color); - font-family: var(--font-primary); - font-weight: 400; - color: var(--text-color); - line-height: 1.6; - - /* Layout */ - display: flex; - flex-direction: column; - min-height: 100vh; - margin: 0; -} - -/* 3. Typography */ -h1 span, -h2 span, -h3 span, -h4 span, -h5 span, -h6 span { - position: relative; - - display: none; - padding-left: 1em; - line-height: 0; - vertical-align: baseline; - font-size: 10px; -} - -h1 span { top: -1.3em; } -h2 span { top: -1.2em; } -h3 span { top: -1.0em; } -h4 span { top: -0.8em; } -h5 span { top: -0.5em; } -h6 span { top: -0.5em; } - -h1:hover span, -h2:hover span, -h3:hover span, -h4:hover span, -h5:hover span, -h6:hover span { - display: inline; -} - -h1:target, -h2:target, -h3:target, -h4:target, -h5:target, -h6:target { - margin-left: -10px; - border-left: 10px solid var(--border-color); - scroll-margin-top: 1rem; -} - -main .anchor-link:target { - scroll-margin-top: 1rem; -} - -/* 4. Links */ -a { - color: var(--link-color); - transition: color 0.3s ease; -} - -a:hover { - color: var(--link-hover-color); -} - -a code:hover { - color: var(--link-hover-color); -} - -/* 5. Code and Pre */ -code, -pre { - font-family: var(--font-code); - background-color: var(--code-block-background-color); - border: 1px solid var(--border-color); - border-radius: 6px; - padding: 16px; - overflow-x: auto; - font-size: 15px; - line-height: 1.5; - margin: 1em 0; -} - -code { - background-color: var(--code-block-background-color); - padding: 0.1em 0.3em; - border-radius: 3px; - font-size: 85%; -} - -/* Tables */ -table { - margin: 0; - border-spacing: 0; - border-collapse: collapse; -} - -table tr th, table tr td { - padding: 0.2em 0.4em; - border: 1px solid var(--border-color); -} - -table tr th { - background-color: var(--table-header-background-color); -} - -table tr:nth-child(even) td { - background-color: var(--table-td-background-color); -} - -/* 7. Navigation and Sidebar */ -nav { - font-family: var(--font-heading); - font-size: 16px; - border-right: 1px solid var(--border-color); - position: fixed; - top: 0; - bottom: 0; - left: 0; - width: var(--sidebar-width); - background: var(--background-color); /* It needs an explicit background for toggling narrow screens */ - overflow-y: auto; - z-index: 10; - display: flex; - flex-direction: column; - color: var(--text-color); -} - -nav[hidden] { - display: none; -} - -nav footer { - padding: 1em; - border-top: 1px solid var(--border-color); -} - -nav footer a { - color: var(--secondary-highlight-color); -} - -nav .nav-section { - margin-top: 1em; - padding: 0 1em; -} - -nav h2, nav h3 { - margin: 0 0 0.5em; - padding: 0.5em 0; - color: var(--highlight-color); - border-bottom: 1px solid var(--border-color); -} - -nav h2 { - font-size: 1.2em; -} - -nav h3, -#table-of-contents-navigation { - font-size: 1em; -} - -nav ul, -nav dl, -nav p { - padding: 0; - list-style: none; - margin: 0.5em 0; -} - -nav ul li { - margin-bottom: 0.3em; -} - -nav ul ul { - padding-left: 1em; -} - -nav ul ul ul { - padding-left: 1em; -} - -nav ul ul ul ul { - padding-left: 1em; -} - -nav a { - color: var(--link-color); - text-decoration: none; -} - -nav a:hover { - color: var(--link-hover-color); - text-decoration: underline; -} - -#navigation-toggle { - z-index: 1000; - font-size: 2em; - display: block; - position: fixed; - top: 10px; - left: 20px; - cursor: pointer; -} - -#navigation-toggle[aria-expanded="true"] { - top: 10px; - left: 250px; -} - -nav ul li details { - position: relative; - padding-right: 1.5em; /* Add space for the marker on the right */ -} - -nav ul li details > summary { - list-style: none; /* Remove the default marker */ - position: relative; /* So that the open/close triangle can position itself absolutely inside */ -} - -nav ul li details > summary::-webkit-details-marker { - display: none; /* Removes the default marker, in Safari 18. */ -} - -nav ul li details > summary::after { - content: '▶'; /* Unicode right-pointing triangle */ - position: absolute; - font-size: 0.8em; - bottom: 0.1em; - margin-left: 0.3em; - transition: transform 0.2s ease; -} - -nav ul li details[open] > summary::after { - transform: rotate(90deg); /* Rotate the triangle when open */ -} - -/* 8. Main Content */ -main { - flex: 1; - display: block; - margin: 3em auto; - padding: 0 2em; - max-width: 800px; - font-size: 16px; - line-height: 1.6; - color: var(--text-color); - box-sizing: border-box; -} - -@media (min-width: 1024px) { - main { - margin-left: var(--sidebar-width); - } -} - -main h1[class] { - margin-top: 0; - margin-bottom: 1em; - font-size: 2.5em; - color: var(--highlight-color); -} - -main h1, -main h2, -main h3, -main h4, -main h5, -main h6 { - font-family: var(--font-heading); - color: var(--highlight-color); -} - -@media (min-width: 1024px) { - .table-of-contents main { - margin-left: 20em; - } -} - -/* Search */ -#search-section { - padding: 1em; - background-color: var(--background-color); - border-bottom: 1px solid var(--border-color); -} - -#search-field-wrapper { - position: relative; - display: flex; - align-items: center; -} - -#search-field { - width: 100%; - padding: 0.5em 1em 0.5em 2.5em; - border: 1px solid var(--border-color); - border-radius: 20px; - font-size: 14px; - outline: none; - transition: border-color 0.3s ease; - color: var(--text-color); -} - -#search-field:focus { - border-color: var(--highlight-color); -} - -#search-field::placeholder { - color: var(--text-color); -} - -#search-field-wrapper::before { - content: "\1F50D"; - position: absolute; - left: 0.75em; - top: 50%; - transform: translateY(-50%); - font-size: 14px; - color: var(--text-color); - opacity: 0.6; -} - -/* Search Results */ -#search-results { - font-family: var(--font-primary); - font-weight: 300; -} - -#search-results .search-match { - font-family: var(--font-heading); - font-weight: normal; -} - -#search-results .search-selected { - background: var(--code-block-background-color); - border-bottom: 1px solid transparent; -} - -#search-results li { - list-style: none; - border-bottom: 1px solid var(--border-color); - margin-bottom: 0.5em; -} - -#search-results li:last-child { - border-bottom: none; - margin-bottom: 0; -} - -#search-results li p { - padding: 0; - margin: 0.5em; -} - -#search-results .search-namespace { - font-weight: bold; -} - -#search-results li em { - background-color: rgba(224, 108, 117, 0.1); - font-style: normal; -} - -#search-results pre { - margin: 0.5em; - font-family: var(--font-code); -} - -/* Syntax Highlighting - Gruvbox Light Scheme */ - -.ruby-constant { color: #AF3A03; } /* Dark Orange */ -.ruby-keyword { color: #9D0006; } /* Dark Red */ -.ruby-ivar { color: #B57614; } /* Brown */ -.ruby-operator { color: #427B58; } /* Dark Teal */ -.ruby-identifier { color: #076678; } /* Deep Teal */ -.ruby-node { color: #8F3F71; } /* Plum */ -.ruby-comment { color: #928374; font-style: italic; } /* Gray */ -.ruby-regexp { color: #8F3F71; } /* Plum */ -.ruby-value { color: #AF3A03; } /* Dark Orange */ -.ruby-string { color: #79740E; } /* Olive */ - -/* Emphasis */ -em { - text-decoration-color: rgba(52, 48, 64, 0.25); - text-decoration-line: underline; - text-decoration-style: dotted; -} - -strong, -em { - color: var(--highlight-color); - background-color: rgba(255, 111, 97, 0.1); /* Light red background for emphasis */ -} - -/* Paragraphs */ -main p { - line-height: 1.5em; - font-weight: 400; -} - -/* Preformatted Text */ -main pre { - margin: 1.2em 0.5em; - padding: 1em; - font-size: 0.8em; -} - -/* Horizontal Rules */ -main hr { - margin: 1.5em 1em; - border: 2px solid var(--border-color); -} - -/* Blockquotes */ -main blockquote { - margin: 0 2em 1.2em 1.2em; - padding-left: 0.5em; - border-left: 2px solid var(--border-color); -} - -/* Lists */ -main li > p { - margin: 0.5em; -} - -/* Definition Lists */ -main dl { - margin: 1em 0.5em; -} - -main dt { - margin-bottom: 0.5em; - margin-right: 1em; - float: left; - font-weight: bold; -} - -main dd { - margin: 0 1em 1em 0.5em; -} - -/* Headers within Main */ -main header h2 { - margin-top: 2em; - border-width: 0; - border-top: 4px solid var(--border-color); - font-size: 130%; -} - -main header h3 { - margin: 2em 0 1.5em; - border-width: 0; - border-top: 3px solid var(--border-color); - font-size: 120%; -} - -/* Utility Classes */ -.hide { display: none !important; } -.initially-hidden { display: none; } - -/* Media Queries */ -@media (min-width: 1024px) { - /* Styles for larger screens */ - .table-of-contents main { - margin-left: 20em; - } -} - -@media print { - /* Print-specific styles */ -} - -/* Table of Contents */ -.table-of-contents ul { - margin: 1em; - list-style: none; -} - -.table-of-contents ul ul { - margin-top: 0.25em; -} - -.table-of-contents ul :link, -.table-of-contents ul :visited { - font-size: 16px; -} - -.table-of-contents li { - margin-bottom: 0.25em; -} - -/* Method Details */ -main .method-source-code { - visibility: hidden; - max-height: 0; - overflow: auto; - transition-duration: 200ms; - transition-delay: 0ms; - transition-property: all; - transition-timing-function: ease-in-out; -} - -main .method-source-code pre { - border-color: var(--source-code-toggle-color); -} - -main .method-source-code.active-menu { - visibility: visible; - max-height: 100vh; -} - -main .method-description .method-calls-super { - color: var(--text-color); - font-weight: bold; -} - -main .method-detail { - margin-bottom: 2.5em; -} - -main .method-detail:target { - margin-left: -10px; - border-left: 10px solid var(--border-color); -} - -main .method-header { - display: inline-block; -} - -main .method-heading { - position: relative; - font-family: var(--font-code); - font-size: 110%; - font-weight: bold; -} - -main .method-heading::after { - content: '¶'; - position: absolute; - visibility: hidden; - color: var(--highlight-color); - font-size: 0.5em; -} - -main .method-heading:hover::after { - visibility: visible; -} - -main .method-controls { - line-height: 20px; - float: right; - color: var(--source-code-toggle-color); - cursor: pointer; -} - -main .method-description, -main .aliases { - margin-top: 0.75em; - color: var(--text-color); -} - -main .aliases { - padding-top: 4px; - font-style: italic; - cursor: default; -} - -main .aliases a { - color: var(--secondary-highlight-color); -} - -main .mixin-from { - font-size: 80%; - font-style: italic; - margin-bottom: 0.75em; -} - -main .method-description ul { - margin-left: 1.5em; -} - -main #attribute-method-details .method-detail:hover { - background-color: transparent; - cursor: default; -} - -main .attribute-access-type { - text-transform: uppercase; -} - -/* Responsive Adjustments */ -@media (max-width: 480px) { - nav { - width: 100%; - } - - main { - margin: 1em auto; - padding: 0 1em; - max-width: 100%; - } - - #navigation-toggle { - right: 10px; - left: auto; - } - - #navigation-toggle[aria-expanded="true"] { - left: auto; - } - - table { - display: block; - overflow-x: auto; - white-space: nowrap; - } - - main .method-controls { - margin-top: 10px; - float: none; - } -} diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf Binary files differdeleted file mode 100644 index b49dd43729..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf Binary files differdeleted file mode 100644 index 7959fef075..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf Binary files differdeleted file mode 100644 index 839cd589dc..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf b/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf Binary files differdeleted file mode 100644 index bababa09e3..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf Binary files differdeleted file mode 100644 index dd00982d49..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf b/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf Binary files differdeleted file mode 100644 index 1decfb95af..0000000000 --- a/lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/add.png b/lib/rdoc/generator/template/darkfish/images/add.png Binary files differdeleted file mode 100644 index 6332fefea4..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/add.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/arrow_up.png b/lib/rdoc/generator/template/darkfish/images/arrow_up.png Binary files differdeleted file mode 100644 index 1ebb193243..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/arrow_up.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/brick.png b/lib/rdoc/generator/template/darkfish/images/brick.png Binary files differdeleted file mode 100644 index 7851cf34c9..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/brick.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/brick_link.png b/lib/rdoc/generator/template/darkfish/images/brick_link.png Binary files differdeleted file mode 100644 index 9ebf013a23..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/brick_link.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/bug.png b/lib/rdoc/generator/template/darkfish/images/bug.png Binary files differdeleted file mode 100644 index 2d5fb90ec6..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/bug.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_black.png b/lib/rdoc/generator/template/darkfish/images/bullet_black.png Binary files differdeleted file mode 100644 index 57619706d1..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/bullet_black.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png Binary files differdeleted file mode 100644 index b47ce55f68..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png b/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png Binary files differdeleted file mode 100644 index 9ab4a89664..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/date.png b/lib/rdoc/generator/template/darkfish/images/date.png Binary files differdeleted file mode 100644 index 783c83357f..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/date.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/delete.png b/lib/rdoc/generator/template/darkfish/images/delete.png Binary files differdeleted file mode 100644 index 08f249365a..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/delete.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/find.png b/lib/rdoc/generator/template/darkfish/images/find.png Binary files differdeleted file mode 100644 index 1547479646..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/find.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif b/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif Binary files differdeleted file mode 100644 index 82290f4833..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png b/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png Binary files differdeleted file mode 100644 index c6473b324e..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/macFFBgHack.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/package.png b/lib/rdoc/generator/template/darkfish/images/package.png Binary files differdeleted file mode 100644 index da3c2a2d74..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/package.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/page_green.png b/lib/rdoc/generator/template/darkfish/images/page_green.png Binary files differdeleted file mode 100644 index de8e003f9f..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/page_green.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_text.png b/lib/rdoc/generator/template/darkfish/images/page_white_text.png Binary files differdeleted file mode 100644 index 813f712f72..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/page_white_text.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/page_white_width.png b/lib/rdoc/generator/template/darkfish/images/page_white_width.png Binary files differdeleted file mode 100644 index 1eb880947d..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/page_white_width.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/plugin.png b/lib/rdoc/generator/template/darkfish/images/plugin.png Binary files differdeleted file mode 100644 index 6187b15aec..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/plugin.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/ruby.png b/lib/rdoc/generator/template/darkfish/images/ruby.png Binary files differdeleted file mode 100644 index f763a16880..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/ruby.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/tag_blue.png b/lib/rdoc/generator/template/darkfish/images/tag_blue.png Binary files differdeleted file mode 100644 index 3f02b5f8f8..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/tag_blue.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/tag_green.png b/lib/rdoc/generator/template/darkfish/images/tag_green.png Binary files differdeleted file mode 100644 index 83ec984bd7..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/tag_green.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/transparent.png b/lib/rdoc/generator/template/darkfish/images/transparent.png Binary files differdeleted file mode 100644 index d665e179ef..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/transparent.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/wrench.png b/lib/rdoc/generator/template/darkfish/images/wrench.png Binary files differdeleted file mode 100644 index 5c8213fef5..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/wrench.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/wrench_orange.png b/lib/rdoc/generator/template/darkfish/images/wrench_orange.png Binary files differdeleted file mode 100644 index 565a9330e0..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/wrench_orange.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/images/zoom.png b/lib/rdoc/generator/template/darkfish/images/zoom.png Binary files differdeleted file mode 100644 index 908612e394..0000000000 --- a/lib/rdoc/generator/template/darkfish/images/zoom.png +++ /dev/null diff --git a/lib/rdoc/generator/template/darkfish/index.rhtml b/lib/rdoc/generator/template/darkfish/index.rhtml deleted file mode 100644 index a5c0dd54da..0000000000 --- a/lib/rdoc/generator/template/darkfish/index.rhtml +++ /dev/null @@ -1,23 +0,0 @@ -<body id="top" role="document" class="file"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <%= render '_sidebar_navigation.rhtml' %> - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_sidebar_pages.rhtml' %> - <%= render '_sidebar_classes.rhtml' %> - - <%= render '_footer.rhtml' %> -</nav> - -<main role="main"> -<%- if @options.main_page and - main_page = @files.find { |f| f.full_name == @options.main_page } then %> -<%= main_page.description %> -<%- else -%> -<p>This is the API documentation for <%= h @title %>. -<%- end -%> -</main> diff --git a/lib/rdoc/generator/template/darkfish/js/darkfish.js b/lib/rdoc/generator/template/darkfish/js/darkfish.js deleted file mode 100644 index aeb8526344..0000000000 --- a/lib/rdoc/generator/template/darkfish/js/darkfish.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * - * Darkfish Page Functions - * $Id: darkfish.js 53 2009-01-07 02:52:03Z deveiant $ - * - * Author: Michael Granger <mgranger@laika.com> - * - */ - -/* Provide console simulation for firebug-less environments */ -/* -if (!("console" in window) || !("firebug" in console)) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", - "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; - - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -}; -*/ - - -function showSource( e ) { - var target = e.target; - while (!target.classList.contains('method-detail')) { - target = target.parentNode; - } - if (typeof target !== "undefined" && target !== null) { - target = target.querySelector('.method-source-code'); - } - if (typeof target !== "undefined" && target !== null) { - target.classList.toggle('active-menu') - } -}; - -function hookSourceViews() { - document.querySelectorAll('.method-source-toggle').forEach(function (codeObject) { - codeObject.addEventListener('click', showSource); - }); -}; - -function hookSearch() { - var input = document.querySelector('#search-field'); - var result = document.querySelector('#search-results'); - result.classList.remove("initially-hidden"); - - var search_section = document.querySelector('#search-section'); - search_section.classList.remove("initially-hidden"); - - var search = new Search(search_data, input, result); - - search.renderItem = function(result) { - var li = document.createElement('li'); - var html = ''; - - // TODO add relative path to <script> per-page - html += '<p class="search-match"><a href="' + index_rel_prefix + this.escapeHTML(result.path) + '">' + this.hlt(result.title); - if (result.params) - html += '<span class="params">' + result.params + '</span>'; - html += '</a>'; - - - if (result.namespace) - html += '<p class="search-namespace">' + this.hlt(result.namespace); - - if (result.snippet) - html += '<div class="search-snippet">' + result.snippet + '</div>'; - - li.innerHTML = html; - - return li; - } - - search.select = function(result) { - window.location.href = result.firstChild.firstChild.href; - } - - search.scrollIntoView = search.scrollInWindow; -}; - -function hookFocus() { - document.addEventListener("keydown", (event) => { - if (document.activeElement.tagName === 'INPUT') { - return; - } - if (event.key === "/") { - event.preventDefault(); - document.querySelector('#search-field').focus(); - } - }); -} - -function hookSidebar() { - var navigation = document.querySelector('#navigation'); - var navigationToggle = document.querySelector('#navigation-toggle'); - - navigationToggle.addEventListener('click', function() { - navigation.hidden = !navigation.hidden; - navigationToggle.ariaExpanded = navigationToggle.ariaExpanded !== 'true'; - }); - - var isSmallViewport = window.matchMedia("(max-width: 1024px)").matches; - if (isSmallViewport) { - navigation.hidden = true; - navigationToggle.ariaExpanded = false; - } -} - -document.addEventListener('DOMContentLoaded', function() { - hookSourceViews(); - hookSearch(); - hookFocus(); - hookSidebar(); -}); diff --git a/lib/rdoc/generator/template/darkfish/js/search.js b/lib/rdoc/generator/template/darkfish/js/search.js deleted file mode 100644 index d3cded1d57..0000000000 --- a/lib/rdoc/generator/template/darkfish/js/search.js +++ /dev/null @@ -1,110 +0,0 @@ -Search = function(data, input, result) { - this.data = data; - this.input = input; - this.result = result; - - this.current = null; - this.view = this.result.parentNode; - this.searcher = new Searcher(data.index); - this.init(); -} - -Search.prototype = Object.assign({}, Navigation, new function() { - var suid = 1; - - this.init = function() { - var _this = this; - var observer = function(e) { - switch(e.key) { - case 'ArrowUp': - case 'ArrowDown': - return; - } - _this.search(_this.input.value); - }; - this.input.addEventListener('keyup', observer); - this.input.addEventListener('click', observer); // mac's clear field - - this.searcher.ready(function(results, isLast) { - _this.addResults(results, isLast); - }) - - this.initNavigation(); - this.setNavigationActive(false); - } - - this.search = function(value, selectFirstMatch) { - value = value.trim().toLowerCase(); - if (value) { - this.setNavigationActive(true); - } else { - this.setNavigationActive(false); - } - - if (value == '') { - this.lastQuery = value; - this.result.innerHTML = ''; - this.result.setAttribute('aria-expanded', 'false'); - this.setNavigationActive(false); - } else if (value != this.lastQuery) { - this.lastQuery = value; - this.result.setAttribute('aria-busy', 'true'); - this.result.setAttribute('aria-expanded', 'true'); - this.firstRun = true; - this.searcher.find(value); - } - } - - this.addResults = function(results, isLast) { - var target = this.result; - if (this.firstRun && (results.length > 0 || isLast)) { - this.current = null; - this.result.innerHTML = ''; - } - - for (var i=0, l = results.length; i < l; i++) { - var item = this.renderItem.call(this, results[i]); - item.setAttribute('id', 'search-result-' + target.childElementCount); - target.appendChild(item); - }; - - if (this.firstRun && results.length > 0) { - this.firstRun = false; - this.current = target.firstChild; - this.current.classList.add('search-selected'); - } - //TODO: ECMAScript - //if (jQuery.browser.msie) this.$element[0].className += ''; - - if (isLast) this.result.setAttribute('aria-busy', 'false'); - } - - this.move = function(isDown) { - if (!this.current) return; - var next = isDown ? this.current.nextElementSibling : this.current.previousElementSibling; - if (next) { - this.current.classList.remove('search-selected'); - next.classList.add('search-selected'); - this.input.setAttribute('aria-activedescendant', next.getAttribute('id')); - this.scrollIntoView(next, this.view); - this.current = next; - this.input.value = next.firstChild.firstChild.text; - this.input.select(); - } - return true; - } - - this.hlt = function(html) { - return this.escapeHTML(html). - replace(/\u0001/g, '<em>'). - replace(/\u0002/g, '</em>'); - } - - this.escapeHTML = function(html) { - return html.replace(/[&<>"`']/g, function(c) { - return '&#' + c.charCodeAt(0) + ';'; - }); - } - -}); - diff --git a/lib/rdoc/generator/template/darkfish/page.rhtml b/lib/rdoc/generator/template/darkfish/page.rhtml deleted file mode 100644 index fb33eba6fd..0000000000 --- a/lib/rdoc/generator/template/darkfish/page.rhtml +++ /dev/null @@ -1,18 +0,0 @@ -<body id="top" role="document" class="file"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <%= render '_sidebar_navigation.rhtml' %> - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_sidebar_table_of_contents.rhtml' %> - <%= render '_sidebar_pages.rhtml' %> - - <%= render '_footer.rhtml' %> -</nav> - -<main role="main" aria-label="Page <%=h file.full_name%>"> -<%= file.description %> -</main> diff --git a/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml b/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml deleted file mode 100644 index 098b589add..0000000000 --- a/lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml +++ /dev/null @@ -1,20 +0,0 @@ -<body role="document"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <%= render '_sidebar_navigation.rhtml' %> - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_sidebar_pages.rhtml' %> - <%= render '_sidebar_classes.rhtml' %> - - <%= render '_footer.rhtml' %> -</nav> - -<main role="main"> - <h1>Not Found</h1> - - <p><%= message %> -</main> diff --git a/lib/rdoc/generator/template/darkfish/servlet_root.rhtml b/lib/rdoc/generator/template/darkfish/servlet_root.rhtml deleted file mode 100644 index 373e0006d9..0000000000 --- a/lib/rdoc/generator/template/darkfish/servlet_root.rhtml +++ /dev/null @@ -1,65 +0,0 @@ -<body role="document"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <div id="home-section" class="nav-section"> - <h2> - <a href="<%= rel_prefix %>/" rel="home">Home</a> - </h2> - </div> - - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_sidebar_installed.rhtml' %> - <%= render '_footer.rhtml' %> -</nav> - -<main role="main"> - <h1>Local RDoc Documentation</h1> - - <p>Here you can browse local documentation from the ruby standard library and - your installed gems. - -<%- extra_dirs = installed.select { |_, _, _, type,| type == :extra } -%> -<%- unless extra_dirs.empty? -%> - <h2>Extra Documentation Directories</h2> - - <p>The following additional documentation directories are available:</p> - - <ol> - <%- extra_dirs.each do |name, href, exists, _, path| -%> - <li> - <%- if exists -%> - <a href="<%= href %>"><%= h name %></a> (<%= h path %>) - <%- else -%> - <%= h name %> (<%= h path %>; <i>not available</i>) - <%- end -%> - </li> - <%- end -%> - </ol> -<%- end -%> - -<%- gems = installed.select { |_, _, _, type,| type == :gem } -%> -<%- missing = gems.reject { |_, _, exists,| exists } -%> -<%- unless missing.empty? then -%> - <h2>Missing Gem Documentation</h2> - - <p>You are missing documentation for some of your installed gems. - You can install missing documentation for gems by running - <kbd>gem rdoc --all</kbd>. After installing the missing documentation you - only need to reload this page. The newly created documentation will - automatically appear. - - <p>You can also install documentation for a specific gem by running one of - the following commands. - - <ul> - <%- names = missing.map { |name,| name.sub(/-([^-]*)$/, '') }.uniq -%> - <%- names.each do |name| -%> - <li><kbd>gem rdoc <%=h name %></kbd> - <%- end -%> - </ul> -<%- end -%> -</main> diff --git a/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml b/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml deleted file mode 100644 index 2cd2207836..0000000000 --- a/lib/rdoc/generator/template/darkfish/table_of_contents.rhtml +++ /dev/null @@ -1,70 +0,0 @@ -<body id="top" class="table-of-contents"> -<%= render '_sidebar_toggle.rhtml' %> - -<nav id="navigation" role="navigation"> - <div id="project-navigation"> - <%= render '_sidebar_navigation.rhtml' %> - - <%= render '_sidebar_search.rhtml' %> - </div> - - <%= render '_footer.rhtml' %> -</nav> -<main role="main"> -<h1 class="class"><%= h @title %></h1> - -<%- simple_files = @files.select { |f| f.text? } -%> -<%- unless simple_files.empty? then -%> -<h2 id="pages">Pages</h2> -<ul> -<%- simple_files.sort.each do |file| -%> - <li class="file"> - <a href="<%= h file.path %>"><%= h file.page_name %></a> -<% - # HACK table_of_contents should not exist on Document - table = file.parse(file.comment).table_of_contents - unless table.empty? then %> - <ul> -<%- table.each do |heading| -%> - <li><a href="<%= h file.path %>#<%= heading.aref %>"><%= heading.plain_html %></a> -<%- end -%> - </ul> -<%- end -%> - </li> - <%- end -%> -</ul> -<%- end -%> - -<h2 id="classes">Classes and Modules</h2> -<ul> -<%- @modsort.each do |klass| -%> - <li class="<%= klass.type %>"> - <a href="<%= klass.path %>"><%= klass.full_name %></a> -<%- table = [] - table.concat klass.parse(klass.comment_location).table_of_contents - table.concat klass.section_contents - - unless table.empty? then %> - <ul> -<%- table.each do |item| -%> -<%- label = item.respond_to?(:label) ? item.label(klass) : item.aref -%> - <li><a href="<%= klass.path %>#<%= label %>"><%= item.plain_html %></a> -<%- end -%> - </ul> -<%- end -%> - </li> -<%- end -%> -</ul> - -<h2 id="methods">Methods</h2> -<ul> -<%- @store.all_classes_and_modules.flat_map do |mod| - mod.method_list - end.sort.each do |method| %> - <li class="method"> - <a href="<%= method.path %>"><%= h method.pretty_name %></a> - — - <span class="container"><%= method.parent.full_name %></span> -<%- end -%> -</ul> -</main> diff --git a/lib/rdoc/generator/template/json_index/.document b/lib/rdoc/generator/template/json_index/.document deleted file mode 100644 index 1713b67654..0000000000 --- a/lib/rdoc/generator/template/json_index/.document +++ /dev/null @@ -1 +0,0 @@ -# ignore all files in this directory diff --git a/lib/rdoc/generator/template/json_index/js/navigation.js b/lib/rdoc/generator/template/json_index/js/navigation.js deleted file mode 100644 index 137e3a0038..0000000000 --- a/lib/rdoc/generator/template/json_index/js/navigation.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Navigation allows movement using the arrow keys through the search results. - * - * When using this library you will need to set scrollIntoView to the - * appropriate function for your layout. Use scrollInWindow if the container - * is not scrollable and scrollInElement if the container is a separate - * scrolling region. - */ -Navigation = new function() { - this.initNavigation = function() { - var _this = this; - - document.addEventListener('keydown', function(e) { - _this.onkeydown(e); - }); - - this.navigationActive = true; - } - - this.setNavigationActive = function(state) { - this.navigationActive = state; - } - - this.onkeydown = function(e) { - if (!this.navigationActive) return; - switch(e.key) { - case 'ArrowLeft': - if (this.moveLeft()) e.preventDefault(); - break; - case 'ArrowUp': - if (e.key == 'ArrowUp' || e.ctrlKey) { - if (this.moveUp()) e.preventDefault(); - } - break; - case 'ArrowRight': - if (this.moveRight()) e.preventDefault(); - break; - case 'ArrowDown': - if (e.key == 'ArrowDown' || e.ctrlKey) { - if (this.moveDown()) e.preventDefault(); - } - break; - case 'Enter': - if (this.current) e.preventDefault(); - this.select(this.current); - break; - } - if (e.ctrlKey && e.shiftKey) this.select(this.current); - } - - this.moveRight = function() { - } - - this.moveLeft = function() { - } - - this.move = function(isDown) { - } - - this.moveUp = function() { - return this.move(false); - } - - this.moveDown = function() { - return this.move(true); - } - - /* - * Scrolls to the given element in the scrollable element view. - */ - this.scrollInElement = function(element, view) { - var offset, viewHeight, viewScroll, height; - offset = element.offsetTop; - height = element.offsetHeight; - viewHeight = view.offsetHeight; - viewScroll = view.scrollTop; - - if (offset - viewScroll + height > viewHeight) { - view.scrollTop = offset - viewHeight + height; - } - if (offset < viewScroll) { - view.scrollTop = offset; - } - } - - /* - * Scrolls to the given element in the window. The second argument is - * ignored - */ - this.scrollInWindow = function(element, ignored) { - var offset, viewHeight, viewScroll, height; - offset = element.offsetTop; - height = element.offsetHeight; - viewHeight = window.innerHeight; - viewScroll = window.scrollY; - - if (offset - viewScroll + height > viewHeight) { - window.scrollTo(window.scrollX, offset - viewHeight + height); - } - if (offset < viewScroll) { - window.scrollTo(window.scrollX, offset); - } - } -} - diff --git a/lib/rdoc/generator/template/json_index/js/searcher.js b/lib/rdoc/generator/template/json_index/js/searcher.js deleted file mode 100644 index e200a168b0..0000000000 --- a/lib/rdoc/generator/template/json_index/js/searcher.js +++ /dev/null @@ -1,229 +0,0 @@ -Searcher = function(data) { - this.data = data; - this.handlers = []; -} - -Searcher.prototype = new function() { - // search is performed in chunks of 1000 for non-blocking user input - var CHUNK_SIZE = 1000; - // do not try to find more than 100 results - var MAX_RESULTS = 100; - var huid = 1; - var suid = 1; - var runs = 0; - - this.find = function(query) { - var queries = splitQuery(query); - var regexps = buildRegexps(queries); - var highlighters = buildHilighters(queries); - var state = { from: 0, pass: 0, limit: MAX_RESULTS, n: suid++}; - var _this = this; - - this.currentSuid = state.n; - - if (!query) return; - - var run = function() { - // stop current search thread if new search started - if (state.n != _this.currentSuid) return; - - var results = - performSearch(_this.data, regexps, queries, highlighters, state); - var hasMore = (state.limit > 0 && state.pass < 4); - - triggerResults.call(_this, results, !hasMore); - if (hasMore) { - setTimeout(run, 2); - } - runs++; - }; - runs = 0; - - // start search thread - run(); - } - - /* ----- Events ------ */ - this.ready = function(fn) { - fn.huid = huid; - this.handlers.push(fn); - } - - /* ----- Utilities ------ */ - function splitQuery(query) { - return query.split(/(\s+|::?|\(\)?)/).filter(function(string) { - return string.match(/\S/); - }); - } - - function buildRegexps(queries) { - return queries.map(function(query) { - return new RegExp(query.replace(/(.)/g, '([$1])([^$1]*?)'), 'i'); - }); - } - - function buildHilighters(queries) { - return queries.map(function(query) { - return query.split('').map(function(l, i) { - return '\u0001$' + (i*2+1) + '\u0002$' + (i*2+2); - }).join(''); - }); - } - - // function longMatchRegexp(index, longIndex, regexps) { - // for (var i = regexps.length - 1; i >= 0; i--){ - // if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) return false; - // }; - // return true; - // } - - - /* ----- Mathchers ------ */ - - /* - * This record matches if the index starts with queries[0] and the record - * matches all of the regexps - */ - function matchPassBeginning(index, longIndex, queries, regexps) { - if (index.indexOf(queries[0]) != 0) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if the longIndex starts with queries[0] and the - * longIndex matches all of the regexps - */ - function matchPassLongIndex(index, longIndex, queries, regexps) { - if (longIndex.indexOf(queries[0]) != 0) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if the index contains queries[0] and the record - * matches all of the regexps - */ - function matchPassContains(index, longIndex, queries, regexps) { - if (index.indexOf(queries[0]) == -1) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - /* - * This record matches if regexps[0] matches the index and the record - * matches all of the regexps - */ - function matchPassRegexp(index, longIndex, queries, regexps) { - if (!index.match(regexps[0])) return false; - for (var i=1, l = regexps.length; i < l; i++) { - if (!index.match(regexps[i]) && !longIndex.match(regexps[i])) - return false; - }; - return true; - } - - - /* ----- Highlighters ------ */ - function highlightRegexp(info, queries, regexps, highlighters) { - var result = createResult(info); - for (var i=0, l = regexps.length; i < l; i++) { - result.title = result.title.replace(regexps[i], highlighters[i]); - result.namespace = result.namespace.replace(regexps[i], highlighters[i]); - }; - return result; - } - - function hltSubstring(string, pos, length) { - return string.substring(0, pos) + '\u0001' + string.substring(pos, pos + length) + '\u0002' + string.substring(pos + length); - } - - function highlightQuery(info, queries, regexps, highlighters) { - var result = createResult(info); - var pos = 0; - var lcTitle = result.title.toLowerCase(); - - pos = lcTitle.indexOf(queries[0]); - if (pos != -1) { - result.title = hltSubstring(result.title, pos, queries[0].length); - } - - result.namespace = result.namespace.replace(regexps[0], highlighters[0]); - for (var i=1, l = regexps.length; i < l; i++) { - result.title = result.title.replace(regexps[i], highlighters[i]); - result.namespace = result.namespace.replace(regexps[i], highlighters[i]); - }; - return result; - } - - function createResult(info) { - var result = {}; - result.title = info[0]; - result.namespace = info[1]; - result.path = info[2]; - result.params = info[3]; - result.snippet = info[4]; - result.badge = info[6]; - return result; - } - - /* ----- Searching ------ */ - function performSearch(data, regexps, queries, highlighters, state) { - var searchIndex = data.searchIndex; - var longSearchIndex = data.longSearchIndex; - var info = data.info; - var result = []; - var i = state.from; - var l = searchIndex.length; - var togo = CHUNK_SIZE; - var matchFunc, hltFunc; - - while (state.pass < 4 && state.limit > 0 && togo > 0) { - if (state.pass == 0) { - matchFunc = matchPassBeginning; - hltFunc = highlightQuery; - } else if (state.pass == 1) { - matchFunc = matchPassLongIndex; - hltFunc = highlightQuery; - } else if (state.pass == 2) { - matchFunc = matchPassContains; - hltFunc = highlightQuery; - } else if (state.pass == 3) { - matchFunc = matchPassRegexp; - hltFunc = highlightRegexp; - } - - for (; togo > 0 && i < l && state.limit > 0; i++, togo--) { - if (info[i].n == state.n) continue; - if (matchFunc(searchIndex[i], longSearchIndex[i], queries, regexps)) { - info[i].n = state.n; - result.push(hltFunc(info[i], queries, regexps, highlighters)); - state.limit--; - } - }; - if (searchIndex.length <= i) { - state.pass++; - i = state.from = 0; - } else { - state.from = i; - } - } - return result; - } - - function triggerResults(results, isLast) { - this.handlers.forEach(function(fn) { - fn.call(this, results, isLast) - }); - } -} - diff --git a/lib/rdoc/i18n.rb b/lib/rdoc/i18n.rb deleted file mode 100644 index f209a9a6f6..0000000000 --- a/lib/rdoc/i18n.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true -## -# This module provides i18n related features. - -module RDoc::I18n - - autoload :Locale, "#{__dir__}/i18n/locale" - require_relative 'i18n/text' - -end diff --git a/lib/rdoc/i18n/locale.rb b/lib/rdoc/i18n/locale.rb deleted file mode 100644 index 6a70d6c986..0000000000 --- a/lib/rdoc/i18n/locale.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true -## -# A message container for a locale. -# -# This object provides the following two features: -# -# * Loads translated messages from .po file. -# * Translates a message into the locale. - -class RDoc::I18n::Locale - - @@locales = {} # :nodoc: - - class << self - - ## - # Returns the locale object for +locale_name+. - - def [](locale_name) - @@locales[locale_name] ||= new(locale_name) - end - - ## - # Sets the locale object for +locale_name+. - # - # Normally, this method is not used. This method is useful for - # testing. - - def []=(locale_name, locale) - @@locales[locale_name] = locale - end - - end - - ## - # The name of the locale. It uses IETF language tag format - # +[language[_territory][.codeset][@modifier]]+. - # - # See also {BCP 47 - Tags for Identifying - # Languages}[http://tools.ietf.org/rfc/bcp/bcp47.txt]. - - attr_reader :name - - ## - # Creates a new locale object for +name+ locale. +name+ must - # follow IETF language tag format. - - def initialize(name) - @name = name - @messages = {} - end - - ## - # Loads translation messages from +locale_directory+/+@name+/rdoc.po - # or +locale_directory+/+@name+.po. The former has high priority. - # - # This method requires gettext gem for parsing .po file. If you - # don't have gettext gem, this method doesn't load .po file. This - # method warns and returns +false+. - # - # Returns +true+ if succeeded, +false+ otherwise. - - def load(locale_directory) - return false if @name.nil? - - po_file_candidates = [ - File.join(locale_directory, @name, 'rdoc.po'), - File.join(locale_directory, "#{@name}.po"), - ] - po_file = po_file_candidates.find do |po_file_candidate| - File.exist?(po_file_candidate) - end - return false unless po_file - - begin - require 'gettext/po_parser' - require 'gettext/mo' - rescue LoadError - warn('Need gettext gem for i18n feature:') - warn(' gem install gettext') - return false - end - - po_parser = GetText::POParser.new - messages = GetText::MO.new - po_parser.report_warning = false - po_parser.parse_file(po_file, messages) - - @messages.merge!(messages) - - true - end - - ## - # Translates the +message+ into locale. If there is no translation - # messages for +message+ in locale, +message+ itself is returned. - - def translate(message) - @messages[message] || message - end - -end diff --git a/lib/rdoc/i18n/text.rb b/lib/rdoc/i18n/text.rb deleted file mode 100644 index 7ea6664442..0000000000 --- a/lib/rdoc/i18n/text.rb +++ /dev/null @@ -1,126 +0,0 @@ -# frozen_string_literal: true -## -# An i18n supported text. -# -# This object provides the following two features: -# -# * Extracts translation messages from wrapped raw text. -# * Translates wrapped raw text in specified locale. -# -# Wrapped raw text is one of String, RDoc::Comment or Array of them. - -class RDoc::I18n::Text - - ## - # Creates a new i18n supported text for +raw+ text. - - def initialize(raw) - @raw = raw - end - - ## - # Extracts translation target messages and yields each message. - # - # Each yielded message is a Hash. It consists of the followings: - # - # :type :: :paragraph - # :paragraph :: String (The translation target message itself.) - # :line_no :: Integer (The line number of the :paragraph is started.) - # - # The above content may be added in the future. - - def extract_messages - parse do |part| - case part[:type] - when :empty_line - # ignore - when :paragraph - yield(part) - end - end - end - - # Translates raw text into +locale+. - def translate(locale) - translated_text = '' - parse do |part| - case part[:type] - when :paragraph - translated_text += locale.translate(part[:paragraph]) - when :empty_line - translated_text += part[:line] - else - raise "should not reach here: unexpected type: #{type}" - end - end - translated_text - end - - private - def parse(&block) - paragraph = '' - paragraph_start_line = 0 - line_no = 0 - - each_line(@raw) do |line| - line_no += 1 - case line - when /\A\s*\z/ - if paragraph.empty? - emit_empty_line_event(line, line_no, &block) - else - paragraph += line - emit_paragraph_event(paragraph, paragraph_start_line, line_no, - &block) - paragraph = '' - end - else - paragraph_start_line = line_no if paragraph.empty? - paragraph += line - end - end - - unless paragraph.empty? - emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) - end - end - - def each_line(raw, &block) - case raw - when RDoc::Comment - raw.text.each_line(&block) - when Array - raw.each do |comment, location| - each_line(comment, &block) - end - else - raw.each_line(&block) - end - end - - def emit_empty_line_event(line, line_no) - part = { - :type => :empty_line, - :line => line, - :line_no => line_no, - } - yield(part) - end - - def emit_paragraph_event(paragraph, paragraph_start_line, line_no, &block) - paragraph_part = { - :type => :paragraph, - :line_no => paragraph_start_line, - } - match_data = /(\s*)\z/.match(paragraph) - if match_data - paragraph_part[:paragraph] = match_data.pre_match - yield(paragraph_part) - emit_empty_line_event(match_data[1], line_no, &block) - else - paragraph_part[:paragraph] = paragraph - yield(paragraph_part) - end - end - -end diff --git a/lib/rdoc/known_classes.rb b/lib/rdoc/known_classes.rb deleted file mode 100644 index 3e8752bbde..0000000000 --- a/lib/rdoc/known_classes.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true -module RDoc - - ## - # Ruby's built-in classes, modules and exceptions - - KNOWN_CLASSES = { - "rb_cArray" => "Array", - "rb_cBasicObject" => "BasicObject", - "rb_cBignum" => "Bignum", - "rb_cClass" => "Class", - "rb_cData" => "Data", - "rb_cDir" => "Dir", - "rb_cEncoding" => "Encoding", - "rb_cFalseClass" => "FalseClass", - "rb_cFile" => "File", - "rb_cFixnum" => "Fixnum", - "rb_cFloat" => "Float", - "rb_cHash" => "Hash", - "rb_cIO" => "IO", - "rb_cInteger" => "Integer", - "rb_cModule" => "Module", - "rb_cNilClass" => "NilClass", - "rb_cNumeric" => "Numeric", - "rb_cObject" => "Object", - "rb_cProc" => "Proc", - "rb_cRange" => "Range", - "rb_cRefinement" => "Refinement", - "rb_cRegexp" => "Regexp", - "rb_cRubyVM" => "RubyVM", - "rb_cSocket" => "Socket", - "rb_cString" => "String", - "rb_cStruct" => "Struct", - "rb_cSymbol" => "Symbol", - "rb_cThread" => "Thread", - "rb_cTime" => "Time", - "rb_cTrueClass" => "TrueClass", - - "rb_eArgError" => "ArgumentError", - "rb_eEOFError" => "EOFError", - "rb_eException" => "Exception", - "rb_eFatal" => "fatal", - "rb_eFloatDomainError" => "FloatDomainError", - "rb_eIOError" => "IOError", - "rb_eIndexError" => "IndexError", - "rb_eInterrupt" => "Interrupt", - "rb_eLoadError" => "LoadError", - "rb_eNameError" => "NameError", - "rb_eNoMemError" => "NoMemoryError", - "rb_eNotImpError" => "NotImplementedError", - "rb_eRangeError" => "RangeError", - "rb_eRuntimeError" => "RuntimeError", - "rb_eScriptError" => "ScriptError", - "rb_eSecurityError" => "SecurityError", - "rb_eSignal" => "SignalException", - "rb_eStandardError" => "StandardError", - "rb_eSyntaxError" => "SyntaxError", - "rb_eSystemCallError" => "SystemCallError", - "rb_eSystemExit" => "SystemExit", - "rb_eTypeError" => "TypeError", - "rb_eZeroDivError" => "ZeroDivisionError", - - "rb_mComparable" => "Comparable", - "rb_mEnumerable" => "Enumerable", - "rb_mErrno" => "Errno", - "rb_mFConst" => "File::Constants", - "rb_mFileTest" => "FileTest", - "rb_mGC" => "GC", - "rb_mKernel" => "Kernel", - "rb_mMath" => "Math", - "rb_mProcess" => "Process" - } - -end diff --git a/lib/rdoc/markdown.rb b/lib/rdoc/markdown.rb deleted file mode 100644 index 881d19ecb4..0000000000 --- a/lib/rdoc/markdown.rb +++ /dev/null @@ -1,16793 +0,0 @@ -# coding: UTF-8 -# frozen_string_literal: true -# :markup: markdown - -## -# RDoc::Markdown as described by the [markdown syntax][syntax]. -# -# To choose Markdown as your only default format see -# RDoc::Options@Saved+Options for instructions on setting up a `.rdoc_options` -# file to store your project default. -# -# ## Usage -# -# Here is a brief example of using this parse to read a markdown file by hand. -# -# data = File.read("README.md") -# formatter = RDoc::Markup::ToHtml.new(RDoc::Options.new, nil) -# html = RDoc::Markdown.parse(data).accept(formatter) -# -# # do something with html -# -# ## Extensions -# -# The following markdown extensions are supported by the parser, but not all -# are used in RDoc output by default. -# -# ### RDoc -# -# The RDoc Markdown parser has the following built-in behaviors that cannot be -# disabled. -# -# Underscores embedded in words are never interpreted as emphasis. (While the -# [markdown dingus][dingus] emphasizes in-word underscores, neither the -# Markdown syntax nor MarkdownTest mention this behavior.) -# -# For HTML output, RDoc always auto-links bare URLs. -# -# ### Break on Newline -# -# The break_on_newline extension converts all newlines into hard line breaks -# as in [Github Flavored Markdown][GFM]. This extension is disabled by -# default. -# -# ### CSS -# -# The #css extension enables CSS blocks to be included in the output, but they -# are not used for any built-in RDoc output format. This extension is disabled -# by default. -# -# Example: -# -# <style type="text/css"> -# h1 { font-size: 3em } -# </style> -# -# ### Definition Lists -# -# The definition_lists extension allows definition lists using the [PHP -# Markdown Extra syntax][PHPE], but only one label and definition are supported -# at this time. This extension is enabled by default. -# -# Example: -# -# ``` -# cat -# : A small furry mammal -# that seems to sleep a lot -# -# ant -# : A little insect that is known -# to enjoy picnics -# -# ``` -# -# Produces: -# -# cat -# : A small furry mammal -# that seems to sleep a lot -# -# ant -# : A little insect that is known -# to enjoy picnics -# -# ### Strike -# -# Example: -# -# ``` -# This is ~~striked~~. -# ``` -# -# Produces: -# -# This is ~~striked~~. -# -# ### Github -# -# The #github extension enables a partial set of [Github Flavored Markdown] -# [GFM]. This extension is enabled by default. -# -# Supported github extensions include: -# -# #### Fenced code blocks -# -# Use ` ``` ` around a block of code instead of indenting it four spaces. -# -# #### Syntax highlighting -# -# Use ` ``` ruby ` as the start of a code fence to add syntax highlighting. -# (Currently only `ruby` syntax is supported). -# -# ### HTML -# -# Enables raw HTML to be included in the output. This extension is enabled by -# default. -# -# Example: -# -# <table> -# ... -# </table> -# -# ### Notes -# -# The #notes extension enables footnote support. This extension is enabled by -# default. -# -# Example: -# -# Here is some text[^1] including an inline footnote ^[for short footnotes] -# -# ... -# -# [^1]: With the footnote text down at the bottom -# -# Produces: -# -# Here is some text[^1] including an inline footnote ^[for short footnotes] -# -# [^1]: With the footnote text down at the bottom -# -# ## Limitations -# -# * Link titles are not used -# * Footnotes are collapsed into a single paragraph -# -# ## Author -# -# This markdown parser is a port to kpeg from [peg-markdown][pegmarkdown] by -# John MacFarlane. -# -# It is used under the MIT license: -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# The port to kpeg was performed by Eric Hodel and Evan Phoenix -# -# [dingus]: http://daringfireball.net/projects/markdown/dingus -# [GFM]: https://github.github.com/gfm/ -# [pegmarkdown]: https://github.com/jgm/peg-markdown -# [PHPE]: https://michelf.ca/projects/php-markdown/extra/#def-list -# [syntax]: http://daringfireball.net/projects/markdown/syntax -#-- -# Last updated to jgm/peg-markdown commit 8f8fc22ef0 -class RDoc::Markdown - # :stopdoc: - - # This is distinct from setup_parser so that a standalone parser - # can redefine #initialize and still have access to the proper - # parser setup code. - def initialize(str, debug=false) - setup_parser(str, debug) - end - - - - # Prepares for parsing +str+. If you define a custom initialize you must - # call this method before #parse - def setup_parser(str, debug=false) - set_string str, 0 - @memoizations = Hash.new { |h,k| h[k] = {} } - @result = nil - @failed_rule = nil - @failing_rule_offset = -1 - @line_offsets = nil - - setup_foreign_grammar - end - - attr_reader :string - attr_reader :failing_rule_offset - attr_accessor :result, :pos - - def current_column(target=pos) - if string[target] == "\n" && (c = string.rindex("\n", target-1) || -1) - return target - c - elsif c = string.rindex("\n", target) - return target - c - end - - target + 1 - end - - def position_line_offsets - unless @position_line_offsets - @position_line_offsets = [] - total = 0 - string.each_line do |line| - total += line.size - @position_line_offsets << total - end - end - @position_line_offsets - end - - if [].respond_to? :bsearch_index - def current_line(target=pos) - if line = position_line_offsets.bsearch_index {|x| x > target } - return line + 1 - end - raise "Target position #{target} is outside of string" - end - else - def current_line(target=pos) - if line = position_line_offsets.index {|x| x > target } - return line + 1 - end - - raise "Target position #{target} is outside of string" - end - end - - def current_character(target=pos) - if target < 0 || target >= string.size - raise "Target position #{target} is outside of string" - end - string[target, 1] - end - - KpegPosInfo = Struct.new(:pos, :lno, :col, :line, :char) - - def current_pos_info(target=pos) - l = current_line target - c = current_column target - ln = get_line(l-1) - chr = string[target,1] - KpegPosInfo.new(target, l, c, ln, chr) - end - - def lines - string.lines - end - - def get_line(no) - loff = position_line_offsets - if no < 0 - raise "Line No is out of range: #{no} < 0" - elsif no >= loff.size - raise "Line No is out of range: #{no} >= #{loff.size}" - end - lend = loff[no]-1 - lstart = no > 0 ? loff[no-1] : 0 - string[lstart..lend] - end - - - - def get_text(start) - @string[start..@pos-1] - end - - # Sets the string and current parsing position for the parser. - def set_string string, pos - @string = string - @string_size = string ? string.size : 0 - @pos = pos - @position_line_offsets = nil - end - - def show_pos - width = 10 - if @pos < width - "#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")" - else - "#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")" - end - end - - def failure_info - l = current_line @failing_rule_offset - c = current_column @failing_rule_offset - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - "line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'" - else - "line #{l}, column #{c}: failed rule '#{@failed_rule}'" - end - end - - def failure_caret - p = current_pos_info @failing_rule_offset - "#{p.line.chomp}\n#{' ' * (p.col - 1)}^" - end - - def failure_character - current_character @failing_rule_offset - end - - def failure_oneline - p = current_pos_info @failing_rule_offset - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - "@#{p.lno}:#{p.col} failed rule '#{info.name}', got '#{p.char}'" - else - "@#{p.lno}:#{p.col} failed rule '#{@failed_rule}', got '#{p.char}'" - end - end - - class ParseError < RuntimeError - end - - def raise_error - raise ParseError, failure_oneline - end - - def show_error(io=STDOUT) - error_pos = @failing_rule_offset - p = current_pos_info(error_pos) - - io.puts "On line #{p.lno}, column #{p.col}:" - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')" - else - io.puts "Failed to match rule '#{@failed_rule}'" - end - - io.puts "Got: #{p.char.inspect}" - io.puts "=> #{p.line}" - io.print(" " * (p.col + 2)) - io.puts "^" - end - - def set_failed_rule(name) - if @pos > @failing_rule_offset - @failed_rule = name - @failing_rule_offset = @pos - end - end - - attr_reader :failed_rule - - def match_string(str) - len = str.size - if @string[pos,len] == str - @pos += len - return str - end - - return nil - end - - def scan(reg) - if m = reg.match(@string, @pos) - @pos = m.end(0) - return true - end - - return nil - end - - if "".respond_to? :ord - def get_byte - if @pos >= @string_size - return nil - end - - s = @string[@pos].ord - @pos += 1 - s - end - else - def get_byte - if @pos >= @string_size - return nil - end - - s = @string[@pos] - @pos += 1 - s - end - end - - def parse(rule=nil) - # We invoke the rules indirectly via apply - # instead of by just calling them as methods because - # if the rules use left recursion, apply needs to - # manage that. - - if !rule - apply(:_root) - else - method = rule.gsub("-","_hyphen_") - apply :"_#{method}" - end - end - - class MemoEntry - def initialize(ans, pos) - @ans = ans - @pos = pos - @result = nil - @set = false - @left_rec = false - end - - attr_reader :ans, :pos, :result, :set - attr_accessor :left_rec - - def move!(ans, pos, result) - @ans = ans - @pos = pos - @result = result - @set = true - @left_rec = false - end - end - - def external_invoke(other, rule, *args) - old_pos = @pos - old_string = @string - - set_string other.string, other.pos - - begin - if val = __send__(rule, *args) - other.pos = @pos - other.result = @result - else - other.set_failed_rule "#{self.class}##{rule}" - end - val - ensure - set_string old_string, old_pos - end - end - - def apply_with_args(rule, *args) - @result = nil - memo_key = [rule, args] - if m = @memoizations[memo_key][@pos] - @pos = m.pos - if !m.set - m.left_rec = true - return nil - end - - @result = m.result - - return m.ans - else - m = MemoEntry.new(nil, @pos) - @memoizations[memo_key][@pos] = m - start_pos = @pos - - ans = __send__ rule, *args - - lr = m.left_rec - - m.move! ans, @pos, @result - - # Don't bother trying to grow the left recursion - # if it's failing straight away (thus there is no seed) - if ans and lr - return grow_lr(rule, args, start_pos, m) - else - return ans - end - end - end - - def apply(rule) - @result = nil - if m = @memoizations[rule][@pos] - @pos = m.pos - if !m.set - m.left_rec = true - return nil - end - - @result = m.result - - return m.ans - else - m = MemoEntry.new(nil, @pos) - @memoizations[rule][@pos] = m - start_pos = @pos - - ans = __send__ rule - - lr = m.left_rec - - m.move! ans, @pos, @result - - # Don't bother trying to grow the left recursion - # if it's failing straight away (thus there is no seed) - if ans and lr - return grow_lr(rule, nil, start_pos, m) - else - return ans - end - end - end - - def grow_lr(rule, args, start_pos, m) - while true - @pos = start_pos - @result = m.result - - if args - ans = __send__ rule, *args - else - ans = __send__ rule - end - return nil unless ans - - break if @pos <= m.pos - - m.move! ans, @pos, @result - end - - @result = m.result - @pos = m.pos - return m.ans - end - - class RuleInfo - def initialize(name, rendered) - @name = name - @rendered = rendered - end - - attr_reader :name, :rendered - end - - def self.rule_info(name, rendered) - RuleInfo.new(name, rendered) - end - - - # :startdoc: - - - - require_relative '../rdoc' - require_relative 'markup/to_joined_paragraph' - require_relative 'markdown/entities' - - require_relative 'markdown/literals' - - ## - # Supported extensions - - EXTENSIONS = [] - - ## - # Extensions enabled by default - - DEFAULT_EXTENSIONS = [ - :definition_lists, - :github, - :html, - :notes, - :strike, - ] - - # :section: Extensions - - ## - # Creates extension methods for the `name` extension to enable and disable - # the extension and to query if they are active. - - def self.extension name - EXTENSIONS << name - - define_method "#{name}?" do - extension? name - end - - define_method "#{name}=" do |enable| - extension name, enable - end - end - - ## - # Converts all newlines into hard breaks - - extension :break_on_newline - - ## - # Allow style blocks - - extension :css - - ## - # Allow PHP Markdown Extras style definition lists - - extension :definition_lists - - ## - # Allow Github Flavored Markdown - - extension :github - - ## - # Allow HTML - - extension :html - - ## - # Enables the notes extension - - extension :notes - - ## - # Enables the strike extension - - extension :strike - - # :section: - - ## - # Parses the `markdown` document into an RDoc::Document using the default - # extensions. - - def self.parse markdown - parser = new - - parser.parse markdown - end - - # TODO remove when kpeg 0.10 is released - alias orig_initialize initialize # :nodoc: - - ## - # Creates a new markdown parser that enables the given +extensions+. - - def initialize extensions = DEFAULT_EXTENSIONS, debug = false - @debug = debug - @formatter = RDoc::Markup::ToJoinedParagraph.new - @extensions = extensions - - @references = nil - @unlinked_references = nil - - @footnotes = nil - @note_order = nil - end - - ## - # Wraps `text` in emphasis for rdoc inline formatting - - def emphasis text - if text =~ /\A[a-z\d.\/]+\z/i then - "_#{text}_" - else - "<em>#{text}</em>" - end - end - - ## - # :category: Extensions - # - # Is the extension `name` enabled? - - def extension? name - @extensions.include? name - end - - ## - # :category: Extensions - # - # Enables or disables the extension with `name` - - def extension name, enable - if enable then - @extensions |= [name] - else - @extensions -= [name] - end - end - - ## - # Parses `text` in a clone of this parser. This is used for handling nested - # lists the same way as markdown_parser. - - def inner_parse text # :nodoc: - parser = clone - - parser.setup_parser text, @debug - - parser.peg_parse - - doc = parser.result - - doc.accept @formatter - - doc.parts - end - - ## - # Finds a link reference for `label` and creates a new link to it with - # `content` as the link text. If `label` was not encountered in the - # reference-gathering parser pass the label and content are reconstructed - # with the linking `text` (usually whitespace). - - def link_to content, label = content, text = nil - raise ParseError, 'enable notes extension' if - content.start_with? '^' and label.equal? content - - if ref = @references[label] then - "{#{content}}[#{ref}]" - elsif label.equal? content then - "[#{content}]#{text}" - else - "[#{content}]#{text}[#{label}]" - end - end - - ## - # Creates an RDoc::Markup::ListItem by parsing the `unparsed` content from - # the first parsing pass. - - def list_item_from unparsed - parsed = inner_parse unparsed.join - RDoc::Markup::ListItem.new nil, *parsed - end - - ## - # Stores `label` as a note and fills in previously unknown note references. - - def note label - #foottext = "rdoc-label:foottext-#{label}:footmark-#{label}" - - #ref.replace foottext if ref = @unlinked_notes.delete(label) - - @notes[label] = foottext - - #"{^1}[rdoc-label:footmark-#{label}:foottext-#{label}] " - end - - ## - # Creates a new link for the footnote `reference` and adds the reference to - # the note order list for proper display at the end of the document. - - def note_for ref - @note_order << ref - - label = @note_order.length - - "{*#{label}}[rdoc-label:foottext-#{label}:footmark-#{label}]" - end - - ## - # The internal kpeg parse method - - alias peg_parse parse # :nodoc: - - ## - # Creates an RDoc::Markup::Paragraph from `parts` and including - # extension-specific behavior - - def paragraph parts - parts = parts.map do |part| - if "\n" == part then - RDoc::Markup::HardBreak.new - else - part - end - end if break_on_newline? - - RDoc::Markup::Paragraph.new(*parts) - end - - ## - # Parses `markdown` into an RDoc::Document - - def parse markdown - @references = {} - @unlinked_references = {} - - markdown += "\n\n" - - setup_parser markdown, @debug - peg_parse 'References' - - if notes? then - @footnotes = {} - - setup_parser markdown, @debug - peg_parse 'Notes' - - # using note_order on the first pass would be a bug - @note_order = [] - end - - setup_parser markdown, @debug - peg_parse - - doc = result - - if notes? and not @footnotes.empty? then - doc << RDoc::Markup::Rule.new(1) - - @note_order.each_with_index do |ref, index| - label = index + 1 - note = @footnotes[ref] or raise ParseError, "footnote [^#{ref}] not found" - - link = "{^#{label}}[rdoc-label:footmark-#{label}:foottext-#{label}] " - note.parts.unshift link - - doc << note - end - end - - doc.accept @formatter - - doc - end - - ## - # Stores `label` as a reference to `link` and fills in previously unknown - # link references. - - def reference label, link - if ref = @unlinked_references.delete(label) then - ref.replace link - end - - @references[label] = link - end - - ## - # Wraps `text` in strong markup for rdoc inline formatting - - def strong text - if text =~ /\A[a-z\d.\/-]+\z/i then - "*#{text}*" - else - "<b>#{text}</b>" - end - end - - ## - # Wraps `text` in strike markup for rdoc inline formatting - - def strike text - if text =~ /\A[a-z\d.\/-]+\z/i then - "~#{text}~" - else - "<s>#{text}</s>" - end - end - - - # :stopdoc: - def setup_foreign_grammar - @_grammar_literals = RDoc::Markdown::Literals.new(nil) - end - - # root = Doc - def _root - _tmp = apply(:_Doc) - set_failed_rule :_root unless _tmp - return _tmp - end - - # Doc = BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) } - def _Doc - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = apply(:_BOM) - unless _tmp - _tmp = true - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _ary = [] - while true - _tmp = apply(:_Block) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Document.new(*a.compact) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Doc unless _tmp - return _tmp - end - - # Block = @BlankLine* (BlockQuote | Verbatim | CodeFence | Table | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain) - def _Block - - _save = self.pos - while true # sequence - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - - _save2 = self.pos - while true # choice - _tmp = apply(:_BlockQuote) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Verbatim) - break if _tmp - self.pos = _save2 - _tmp = apply(:_CodeFence) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Table) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Note) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Reference) - break if _tmp - self.pos = _save2 - _tmp = apply(:_HorizontalRule) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Heading) - break if _tmp - self.pos = _save2 - _tmp = apply(:_OrderedList) - break if _tmp - self.pos = _save2 - _tmp = apply(:_BulletList) - break if _tmp - self.pos = _save2 - _tmp = apply(:_DefinitionList) - break if _tmp - self.pos = _save2 - _tmp = apply(:_HtmlBlock) - break if _tmp - self.pos = _save2 - _tmp = apply(:_StyleBlock) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Para) - break if _tmp - self.pos = _save2 - _tmp = apply(:_Plain) - break if _tmp - self.pos = _save2 - break - end # end choice - - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Block unless _tmp - return _tmp - end - - # Para = @NonindentSpace Inlines:a @BlankLine+ { paragraph a } - def _Para - - _save = self.pos - while true # sequence - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Inlines) - a = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - @result = begin; paragraph a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Para unless _tmp - return _tmp - end - - # Plain = Inlines:a { paragraph a } - def _Plain - - _save = self.pos - while true # sequence - _tmp = apply(:_Inlines) - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; paragraph a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Plain unless _tmp - return _tmp - end - - # AtxInline = !@Newline !(@Sp /#*/ @Sp @Newline) Inline - def _AtxInline - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save3 - break - end - _tmp = scan(/\G(?-mix:#*)/) - unless _tmp - self.pos = _save3 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save3 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save2 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Inline) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_AtxInline unless _tmp - return _tmp - end - - # AtxStart = < /\#{1,6}/ > { text.length } - def _AtxStart - - _save = self.pos - while true # sequence - _text_start = self.pos - _tmp = scan(/\G(?-mix:\#{1,6})/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text.length ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_AtxStart unless _tmp - return _tmp - end - - # AtxHeading = AtxStart:s @Spacechar+ AtxInline+:a (@Sp /#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) } - def _AtxHeading - - _save = self.pos - while true # sequence - _tmp = apply(:_AtxStart) - s = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Spacechar() - if _tmp - while true - _tmp = _Spacechar() - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _ary = [] - _tmp = apply(:_AtxInline) - if _tmp - _ary << @result - while true - _tmp = apply(:_AtxInline) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save2 - end - a = @result - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save4 - break - end - _tmp = scan(/\G(?-mix:#*)/) - unless _tmp - self.pos = _save4 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - unless _tmp - _tmp = true - self.pos = _save3 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Heading.new(s, a.join) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_AtxHeading unless _tmp - return _tmp - end - - # SetextHeading = (SetextHeading1 | SetextHeading2) - def _SetextHeading - - _save = self.pos - while true # choice - _tmp = apply(:_SetextHeading1) - break if _tmp - self.pos = _save - _tmp = apply(:_SetextHeading2) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_SetextHeading unless _tmp - return _tmp - end - - # SetextBottom1 = /={1,}/ @Newline - def _SetextBottom1 - - _save = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:={1,})/) - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_SetextBottom1 unless _tmp - return _tmp - end - - # SetextBottom2 = /-{1,}/ @Newline - def _SetextBottom2 - - _save = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:-{1,})/) - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_SetextBottom2 unless _tmp - return _tmp - end - - # SetextHeading1 = &(@RawLine SetextBottom1) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom1 { RDoc::Markup::Heading.new(1, a.join) } - def _SetextHeading1 - - _save = self.pos - while true # sequence - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = _RawLine() - unless _tmp - self.pos = _save2 - break - end - _tmp = apply(:_SetextBottom1) - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - if _tmp - while true - - _save6 = self.pos - while true # sequence - _save7 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save6 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save6 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save3 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_SetextBottom1) - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Heading.new(1, a.join) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_SetextHeading1 unless _tmp - return _tmp - end - - # SetextHeading2 = &(@RawLine SetextBottom2) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) } - def _SetextHeading2 - - _save = self.pos - while true # sequence - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = _RawLine() - unless _tmp - self.pos = _save2 - break - end - _tmp = apply(:_SetextBottom2) - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - if _tmp - while true - - _save6 = self.pos - while true # sequence - _save7 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save6 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save6 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save3 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_SetextBottom2) - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Heading.new(2, a.join) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_SetextHeading2 unless _tmp - return _tmp - end - - # Heading = (SetextHeading | AtxHeading) - def _Heading - - _save = self.pos - while true # choice - _tmp = apply(:_SetextHeading) - break if _tmp - self.pos = _save - _tmp = apply(:_AtxHeading) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Heading unless _tmp - return _tmp - end - - # BlockQuote = BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) } - def _BlockQuote - - _save = self.pos - while true # sequence - _tmp = apply(:_BlockQuoteRaw) - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::BlockQuote.new(*a) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_BlockQuote unless _tmp - return _tmp - end - - # BlockQuoteRaw = @StartList:a (">" " "? Line:l { a << l } (!">" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join } - def _BlockQuoteRaw - - _save = self.pos - while true # sequence - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = match_string(">") - unless _tmp - self.pos = _save2 - break - end - _save3 = self.pos - _tmp = match_string(" ") - unless _tmp - _tmp = true - self.pos = _save3 - end - unless _tmp - self.pos = _save2 - break - end - _tmp = apply(:_Line) - l = @result - unless _tmp - self.pos = _save2 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save2 - break - end - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _save7 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_Line) - c = @result - unless _tmp - self.pos = _save5 - break - end - @result = begin; a << c ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save2 - break - end - while true - - _save9 = self.pos - while true # sequence - _tmp = _BlankLine() - n = @result - unless _tmp - self.pos = _save9 - break - end - @result = begin; a << n ; end - _tmp = true - unless _tmp - self.pos = _save9 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - while true - - _save10 = self.pos - while true # sequence - _tmp = match_string(">") - unless _tmp - self.pos = _save10 - break - end - _save11 = self.pos - _tmp = match_string(" ") - unless _tmp - _tmp = true - self.pos = _save11 - end - unless _tmp - self.pos = _save10 - break - end - _tmp = apply(:_Line) - l = @result - unless _tmp - self.pos = _save10 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save10 - break - end - while true - - _save13 = self.pos - while true # sequence - _save14 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save14 - unless _tmp - self.pos = _save13 - break - end - _save15 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save15 - unless _tmp - self.pos = _save13 - break - end - _tmp = apply(:_Line) - c = @result - unless _tmp - self.pos = _save13 - break - end - @result = begin; a << c ; end - _tmp = true - unless _tmp - self.pos = _save13 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save10 - break - end - while true - - _save17 = self.pos - while true # sequence - _tmp = _BlankLine() - n = @result - unless _tmp - self.pos = _save17 - break - end - @result = begin; a << n ; end - _tmp = true - unless _tmp - self.pos = _save17 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - @result = begin; inner_parse a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_BlockQuoteRaw unless _tmp - return _tmp - end - - # NonblankIndentedLine = !@BlankLine IndentedLine - def _NonblankIndentedLine - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_IndentedLine) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_NonblankIndentedLine unless _tmp - return _tmp - end - - # VerbatimChunk = @BlankLine*:a NonblankIndentedLine+:b { a.concat b } - def _VerbatimChunk - - _save = self.pos - while true # sequence - _ary = [] - while true - _tmp = _BlankLine() - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _ary = [] - _tmp = apply(:_NonblankIndentedLine) - if _tmp - _ary << @result - while true - _tmp = apply(:_NonblankIndentedLine) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save2 - end - b = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a.concat b ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_VerbatimChunk unless _tmp - return _tmp - end - - # Verbatim = VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) } - def _Verbatim - - _save = self.pos - while true # sequence - _save1 = self.pos - _ary = [] - _tmp = apply(:_VerbatimChunk) - if _tmp - _ary << @result - while true - _tmp = apply(:_VerbatimChunk) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save1 - end - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Verbatim.new(*a.flatten) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Verbatim unless _tmp - return _tmp - end - - # HorizontalRule = @NonindentSpace ("*" @Sp "*" @Sp "*" (@Sp "*")* | "-" @Sp "-" @Sp "-" (@Sp "-")* | "_" @Sp "_" @Sp "_" (@Sp "_")*) @Sp @Newline @BlankLine+ { RDoc::Markup::Rule.new 1 } - def _HorizontalRule - - _save = self.pos - while true # sequence - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _tmp = match_string("*") - unless _tmp - self.pos = _save2 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save2 - break - end - _tmp = match_string("*") - unless _tmp - self.pos = _save2 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save2 - break - end - _tmp = match_string("*") - unless _tmp - self.pos = _save2 - break - end - while true - - _save4 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save4 - break - end - _tmp = match_string("*") - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save5 = self.pos - while true # sequence - _tmp = match_string("-") - unless _tmp - self.pos = _save5 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save5 - break - end - _tmp = match_string("-") - unless _tmp - self.pos = _save5 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save5 - break - end - _tmp = match_string("-") - unless _tmp - self.pos = _save5 - break - end - while true - - _save7 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save7 - break - end - _tmp = match_string("-") - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save8 = self.pos - while true # sequence - _tmp = match_string("_") - unless _tmp - self.pos = _save8 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save8 - break - end - _tmp = match_string("_") - unless _tmp - self.pos = _save8 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save8 - break - end - _tmp = match_string("_") - unless _tmp - self.pos = _save8 - break - end - while true - - _save10 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save10 - break - end - _tmp = match_string("_") - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - _save11 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save11 - end - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::Rule.new 1 ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HorizontalRule unless _tmp - return _tmp - end - - # Bullet = !HorizontalRule @NonindentSpace /[+*-]/ @Spacechar+ - def _Bullet - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = apply(:_HorizontalRule) - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _tmp = scan(/\G(?-mix:[+*-])/) - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = _Spacechar() - if _tmp - while true - _tmp = _Spacechar() - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Bullet unless _tmp - return _tmp - end - - # BulletList = &Bullet (ListTight | ListLoose):a { RDoc::Markup::List.new(:BULLET, *a) } - def _BulletList - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = apply(:_Bullet) - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - - _save2 = self.pos - while true # choice - _tmp = apply(:_ListTight) - break if _tmp - self.pos = _save2 - _tmp = apply(:_ListLoose) - break if _tmp - self.pos = _save2 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::List.new(:BULLET, *a) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_BulletList unless _tmp - return _tmp - end - - # ListTight = ListItemTight+:a @BlankLine* !(Bullet | Enumerator) { a } - def _ListTight - - _save = self.pos - while true # sequence - _save1 = self.pos - _ary = [] - _tmp = apply(:_ListItemTight) - if _tmp - _ary << @result - while true - _tmp = apply(:_ListItemTight) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save1 - end - a = @result - unless _tmp - self.pos = _save - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - - _save4 = self.pos - while true # choice - _tmp = apply(:_Bullet) - break if _tmp - self.pos = _save4 - _tmp = apply(:_Enumerator) - break if _tmp - self.pos = _save4 - break - end # end choice - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListTight unless _tmp - return _tmp - end - - # ListLoose = @StartList:a (ListItem:b @BlankLine* { a << b })+ { a } - def _ListLoose - - _save = self.pos - while true # sequence - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = apply(:_ListItem) - b = @result - unless _tmp - self.pos = _save2 - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save2 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - while true - - _save4 = self.pos - while true # sequence - _tmp = apply(:_ListItem) - b = @result - unless _tmp - self.pos = _save4 - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListLoose unless _tmp - return _tmp - end - - # ListItem = (Bullet | Enumerator) @StartList:a ListBlock:b { a << b } (ListContinuationBlock:c { a.push(*c) })* { list_item_from a } - def _ListItem - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _tmp = apply(:_Bullet) - break if _tmp - self.pos = _save1 - _tmp = apply(:_Enumerator) - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_ListBlock) - b = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save - break - end - while true - - _save3 = self.pos - while true # sequence - _tmp = apply(:_ListContinuationBlock) - c = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a.push(*c) ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; list_item_from a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListItem unless _tmp - return _tmp - end - - # ListItemTight = (Bullet | Enumerator) ListBlock:a (!@BlankLine ListContinuationBlock:b { a.push(*b) })* !ListContinuationBlock { list_item_from a } - def _ListItemTight - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _tmp = apply(:_Bullet) - break if _tmp - self.pos = _save1 - _tmp = apply(:_Enumerator) - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_ListBlock) - a = @result - unless _tmp - self.pos = _save - break - end - while true - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_ListContinuationBlock) - b = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a.push(*b) ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _save5 = self.pos - _tmp = apply(:_ListContinuationBlock) - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save - break - end - @result = begin; list_item_from a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListItemTight unless _tmp - return _tmp - end - - # ListBlock = !@BlankLine Line:a ListBlockLine*:c { [a, *c] } - def _ListBlock - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Line) - a = @result - unless _tmp - self.pos = _save - break - end - _ary = [] - while true - _tmp = apply(:_ListBlockLine) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - c = @result - unless _tmp - self.pos = _save - break - end - @result = begin; [a, *c] ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListBlock unless _tmp - return _tmp - end - - # ListContinuationBlock = @StartList:a @BlankLine* { a << "\n" } (Indent ListBlock:b { a.concat b })+ { a } - def _ListContinuationBlock - - _save = self.pos - while true # sequence - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; a << "\n" ; end - _tmp = true - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _tmp = apply(:_Indent) - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_ListBlock) - b = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a.concat b ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save4 = self.pos - while true # sequence - _tmp = apply(:_Indent) - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_ListBlock) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a.concat b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListContinuationBlock unless _tmp - return _tmp - end - - # Enumerator = @NonindentSpace [0-9]+ "." @Spacechar+ - def _Enumerator - - _save = self.pos - while true # sequence - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _save2 = self.pos - _tmp = get_byte - if _tmp - unless _tmp >= 48 and _tmp <= 57 - self.pos = _save2 - _tmp = nil - end - end - if _tmp - while true - _save3 = self.pos - _tmp = get_byte - if _tmp - unless _tmp >= 48 and _tmp <= 57 - self.pos = _save3 - _tmp = nil - end - end - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(".") - unless _tmp - self.pos = _save - break - end - _save4 = self.pos - _tmp = _Spacechar() - if _tmp - while true - _tmp = _Spacechar() - break unless _tmp - end - _tmp = true - else - self.pos = _save4 - end - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Enumerator unless _tmp - return _tmp - end - - # OrderedList = &Enumerator (ListTight | ListLoose):a { RDoc::Markup::List.new(:NUMBER, *a) } - def _OrderedList - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = apply(:_Enumerator) - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - - _save2 = self.pos - while true # choice - _tmp = apply(:_ListTight) - break if _tmp - self.pos = _save2 - _tmp = apply(:_ListLoose) - break if _tmp - self.pos = _save2 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::List.new(:NUMBER, *a) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_OrderedList unless _tmp - return _tmp - end - - # ListBlockLine = !@BlankLine !(Indent? (Bullet | Enumerator)) !HorizontalRule OptionallyIndentedLine - def _ListBlockLine - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_Indent) - unless _tmp - _tmp = true - self.pos = _save4 - end - unless _tmp - self.pos = _save3 - break - end - - _save5 = self.pos - while true # choice - _tmp = apply(:_Bullet) - break if _tmp - self.pos = _save5 - _tmp = apply(:_Enumerator) - break if _tmp - self.pos = _save5 - break - end # end choice - - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save2 - unless _tmp - self.pos = _save - break - end - _save6 = self.pos - _tmp = apply(:_HorizontalRule) - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_OptionallyIndentedLine) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ListBlockLine unless _tmp - return _tmp - end - - # HtmlOpenAnchor = "<" Spnl ("a" | "A") Spnl HtmlAttribute* ">" - def _HtmlOpenAnchor - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("a") - break if _tmp - self.pos = _save1 - _tmp = match_string("A") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlOpenAnchor unless _tmp - return _tmp - end - - # HtmlCloseAnchor = "<" Spnl "/" ("a" | "A") Spnl ">" - def _HtmlCloseAnchor - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("a") - break if _tmp - self.pos = _save1 - _tmp = match_string("A") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlCloseAnchor unless _tmp - return _tmp - end - - # HtmlAnchor = HtmlOpenAnchor (HtmlAnchor | !HtmlCloseAnchor .)* HtmlCloseAnchor - def _HtmlAnchor - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlOpenAnchor) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlAnchor) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlCloseAnchor) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlCloseAnchor) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlAnchor unless _tmp - return _tmp - end - - # HtmlBlockOpenAddress = "<" Spnl ("address" | "ADDRESS") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenAddress - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("address") - break if _tmp - self.pos = _save1 - _tmp = match_string("ADDRESS") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenAddress unless _tmp - return _tmp - end - - # HtmlBlockCloseAddress = "<" Spnl "/" ("address" | "ADDRESS") Spnl ">" - def _HtmlBlockCloseAddress - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("address") - break if _tmp - self.pos = _save1 - _tmp = match_string("ADDRESS") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseAddress unless _tmp - return _tmp - end - - # HtmlBlockAddress = HtmlBlockOpenAddress (HtmlBlockAddress | !HtmlBlockCloseAddress .)* HtmlBlockCloseAddress - def _HtmlBlockAddress - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenAddress) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockAddress) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseAddress) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseAddress) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockAddress unless _tmp - return _tmp - end - - # HtmlBlockOpenBlockquote = "<" Spnl ("blockquote" | "BLOCKQUOTE") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenBlockquote - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("blockquote") - break if _tmp - self.pos = _save1 - _tmp = match_string("BLOCKQUOTE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenBlockquote unless _tmp - return _tmp - end - - # HtmlBlockCloseBlockquote = "<" Spnl "/" ("blockquote" | "BLOCKQUOTE") Spnl ">" - def _HtmlBlockCloseBlockquote - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("blockquote") - break if _tmp - self.pos = _save1 - _tmp = match_string("BLOCKQUOTE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseBlockquote unless _tmp - return _tmp - end - - # HtmlBlockBlockquote = HtmlBlockOpenBlockquote (HtmlBlockBlockquote | !HtmlBlockCloseBlockquote .)* HtmlBlockCloseBlockquote - def _HtmlBlockBlockquote - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenBlockquote) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockBlockquote) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseBlockquote) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseBlockquote) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockBlockquote unless _tmp - return _tmp - end - - # HtmlBlockOpenCenter = "<" Spnl ("center" | "CENTER") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenCenter - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("center") - break if _tmp - self.pos = _save1 - _tmp = match_string("CENTER") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenCenter unless _tmp - return _tmp - end - - # HtmlBlockCloseCenter = "<" Spnl "/" ("center" | "CENTER") Spnl ">" - def _HtmlBlockCloseCenter - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("center") - break if _tmp - self.pos = _save1 - _tmp = match_string("CENTER") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseCenter unless _tmp - return _tmp - end - - # HtmlBlockCenter = HtmlBlockOpenCenter (HtmlBlockCenter | !HtmlBlockCloseCenter .)* HtmlBlockCloseCenter - def _HtmlBlockCenter - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenCenter) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockCenter) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseCenter) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseCenter) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCenter unless _tmp - return _tmp - end - - # HtmlBlockOpenDir = "<" Spnl ("dir" | "DIR") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenDir - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dir") - break if _tmp - self.pos = _save1 - _tmp = match_string("DIR") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenDir unless _tmp - return _tmp - end - - # HtmlBlockCloseDir = "<" Spnl "/" ("dir" | "DIR") Spnl ">" - def _HtmlBlockCloseDir - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dir") - break if _tmp - self.pos = _save1 - _tmp = match_string("DIR") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseDir unless _tmp - return _tmp - end - - # HtmlBlockDir = HtmlBlockOpenDir (HtmlBlockDir | !HtmlBlockCloseDir .)* HtmlBlockCloseDir - def _HtmlBlockDir - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenDir) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockDir) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseDir) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseDir) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockDir unless _tmp - return _tmp - end - - # HtmlBlockOpenDiv = "<" Spnl ("div" | "DIV") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenDiv - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("div") - break if _tmp - self.pos = _save1 - _tmp = match_string("DIV") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenDiv unless _tmp - return _tmp - end - - # HtmlBlockCloseDiv = "<" Spnl "/" ("div" | "DIV") Spnl ">" - def _HtmlBlockCloseDiv - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("div") - break if _tmp - self.pos = _save1 - _tmp = match_string("DIV") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseDiv unless _tmp - return _tmp - end - - # HtmlBlockDiv = HtmlBlockOpenDiv (HtmlBlockDiv | !HtmlBlockCloseDiv .)* HtmlBlockCloseDiv - def _HtmlBlockDiv - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenDiv) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockDiv) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseDiv) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseDiv) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockDiv unless _tmp - return _tmp - end - - # HtmlBlockOpenDl = "<" Spnl ("dl" | "DL") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenDl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dl") - break if _tmp - self.pos = _save1 - _tmp = match_string("DL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenDl unless _tmp - return _tmp - end - - # HtmlBlockCloseDl = "<" Spnl "/" ("dl" | "DL") Spnl ">" - def _HtmlBlockCloseDl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dl") - break if _tmp - self.pos = _save1 - _tmp = match_string("DL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseDl unless _tmp - return _tmp - end - - # HtmlBlockDl = HtmlBlockOpenDl (HtmlBlockDl | !HtmlBlockCloseDl .)* HtmlBlockCloseDl - def _HtmlBlockDl - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenDl) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockDl) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseDl) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseDl) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockDl unless _tmp - return _tmp - end - - # HtmlBlockOpenFieldset = "<" Spnl ("fieldset" | "FIELDSET") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenFieldset - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("fieldset") - break if _tmp - self.pos = _save1 - _tmp = match_string("FIELDSET") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenFieldset unless _tmp - return _tmp - end - - # HtmlBlockCloseFieldset = "<" Spnl "/" ("fieldset" | "FIELDSET") Spnl ">" - def _HtmlBlockCloseFieldset - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("fieldset") - break if _tmp - self.pos = _save1 - _tmp = match_string("FIELDSET") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseFieldset unless _tmp - return _tmp - end - - # HtmlBlockFieldset = HtmlBlockOpenFieldset (HtmlBlockFieldset | !HtmlBlockCloseFieldset .)* HtmlBlockCloseFieldset - def _HtmlBlockFieldset - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenFieldset) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockFieldset) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseFieldset) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseFieldset) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockFieldset unless _tmp - return _tmp - end - - # HtmlBlockOpenForm = "<" Spnl ("form" | "FORM") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenForm - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("form") - break if _tmp - self.pos = _save1 - _tmp = match_string("FORM") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenForm unless _tmp - return _tmp - end - - # HtmlBlockCloseForm = "<" Spnl "/" ("form" | "FORM") Spnl ">" - def _HtmlBlockCloseForm - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("form") - break if _tmp - self.pos = _save1 - _tmp = match_string("FORM") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseForm unless _tmp - return _tmp - end - - # HtmlBlockForm = HtmlBlockOpenForm (HtmlBlockForm | !HtmlBlockCloseForm .)* HtmlBlockCloseForm - def _HtmlBlockForm - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenForm) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockForm) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseForm) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseForm) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockForm unless _tmp - return _tmp - end - - # HtmlBlockOpenH1 = "<" Spnl ("h1" | "H1") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH1 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h1") - break if _tmp - self.pos = _save1 - _tmp = match_string("H1") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH1 unless _tmp - return _tmp - end - - # HtmlBlockCloseH1 = "<" Spnl "/" ("h1" | "H1") Spnl ">" - def _HtmlBlockCloseH1 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h1") - break if _tmp - self.pos = _save1 - _tmp = match_string("H1") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH1 unless _tmp - return _tmp - end - - # HtmlBlockH1 = HtmlBlockOpenH1 (HtmlBlockH1 | !HtmlBlockCloseH1 .)* HtmlBlockCloseH1 - def _HtmlBlockH1 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH1) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH1) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH1) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH1) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH1 unless _tmp - return _tmp - end - - # HtmlBlockOpenH2 = "<" Spnl ("h2" | "H2") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH2 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h2") - break if _tmp - self.pos = _save1 - _tmp = match_string("H2") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH2 unless _tmp - return _tmp - end - - # HtmlBlockCloseH2 = "<" Spnl "/" ("h2" | "H2") Spnl ">" - def _HtmlBlockCloseH2 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h2") - break if _tmp - self.pos = _save1 - _tmp = match_string("H2") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH2 unless _tmp - return _tmp - end - - # HtmlBlockH2 = HtmlBlockOpenH2 (HtmlBlockH2 | !HtmlBlockCloseH2 .)* HtmlBlockCloseH2 - def _HtmlBlockH2 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH2) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH2) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH2) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH2) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH2 unless _tmp - return _tmp - end - - # HtmlBlockOpenH3 = "<" Spnl ("h3" | "H3") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH3 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h3") - break if _tmp - self.pos = _save1 - _tmp = match_string("H3") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH3 unless _tmp - return _tmp - end - - # HtmlBlockCloseH3 = "<" Spnl "/" ("h3" | "H3") Spnl ">" - def _HtmlBlockCloseH3 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h3") - break if _tmp - self.pos = _save1 - _tmp = match_string("H3") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH3 unless _tmp - return _tmp - end - - # HtmlBlockH3 = HtmlBlockOpenH3 (HtmlBlockH3 | !HtmlBlockCloseH3 .)* HtmlBlockCloseH3 - def _HtmlBlockH3 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH3) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH3) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH3) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH3) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH3 unless _tmp - return _tmp - end - - # HtmlBlockOpenH4 = "<" Spnl ("h4" | "H4") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH4 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h4") - break if _tmp - self.pos = _save1 - _tmp = match_string("H4") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH4 unless _tmp - return _tmp - end - - # HtmlBlockCloseH4 = "<" Spnl "/" ("h4" | "H4") Spnl ">" - def _HtmlBlockCloseH4 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h4") - break if _tmp - self.pos = _save1 - _tmp = match_string("H4") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH4 unless _tmp - return _tmp - end - - # HtmlBlockH4 = HtmlBlockOpenH4 (HtmlBlockH4 | !HtmlBlockCloseH4 .)* HtmlBlockCloseH4 - def _HtmlBlockH4 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH4) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH4) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH4) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH4) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH4 unless _tmp - return _tmp - end - - # HtmlBlockOpenH5 = "<" Spnl ("h5" | "H5") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH5 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h5") - break if _tmp - self.pos = _save1 - _tmp = match_string("H5") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH5 unless _tmp - return _tmp - end - - # HtmlBlockCloseH5 = "<" Spnl "/" ("h5" | "H5") Spnl ">" - def _HtmlBlockCloseH5 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h5") - break if _tmp - self.pos = _save1 - _tmp = match_string("H5") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH5 unless _tmp - return _tmp - end - - # HtmlBlockH5 = HtmlBlockOpenH5 (HtmlBlockH5 | !HtmlBlockCloseH5 .)* HtmlBlockCloseH5 - def _HtmlBlockH5 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH5) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH5) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH5) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH5) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH5 unless _tmp - return _tmp - end - - # HtmlBlockOpenH6 = "<" Spnl ("h6" | "H6") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenH6 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h6") - break if _tmp - self.pos = _save1 - _tmp = match_string("H6") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenH6 unless _tmp - return _tmp - end - - # HtmlBlockCloseH6 = "<" Spnl "/" ("h6" | "H6") Spnl ">" - def _HtmlBlockCloseH6 - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("h6") - break if _tmp - self.pos = _save1 - _tmp = match_string("H6") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseH6 unless _tmp - return _tmp - end - - # HtmlBlockH6 = HtmlBlockOpenH6 (HtmlBlockH6 | !HtmlBlockCloseH6 .)* HtmlBlockCloseH6 - def _HtmlBlockH6 - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenH6) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockH6) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseH6) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseH6) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockH6 unless _tmp - return _tmp - end - - # HtmlBlockOpenMenu = "<" Spnl ("menu" | "MENU") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenMenu - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("menu") - break if _tmp - self.pos = _save1 - _tmp = match_string("MENU") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenMenu unless _tmp - return _tmp - end - - # HtmlBlockCloseMenu = "<" Spnl "/" ("menu" | "MENU") Spnl ">" - def _HtmlBlockCloseMenu - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("menu") - break if _tmp - self.pos = _save1 - _tmp = match_string("MENU") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseMenu unless _tmp - return _tmp - end - - # HtmlBlockMenu = HtmlBlockOpenMenu (HtmlBlockMenu | !HtmlBlockCloseMenu .)* HtmlBlockCloseMenu - def _HtmlBlockMenu - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenMenu) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockMenu) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseMenu) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseMenu) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockMenu unless _tmp - return _tmp - end - - # HtmlBlockOpenNoframes = "<" Spnl ("noframes" | "NOFRAMES") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenNoframes - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("noframes") - break if _tmp - self.pos = _save1 - _tmp = match_string("NOFRAMES") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenNoframes unless _tmp - return _tmp - end - - # HtmlBlockCloseNoframes = "<" Spnl "/" ("noframes" | "NOFRAMES") Spnl ">" - def _HtmlBlockCloseNoframes - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("noframes") - break if _tmp - self.pos = _save1 - _tmp = match_string("NOFRAMES") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseNoframes unless _tmp - return _tmp - end - - # HtmlBlockNoframes = HtmlBlockOpenNoframes (HtmlBlockNoframes | !HtmlBlockCloseNoframes .)* HtmlBlockCloseNoframes - def _HtmlBlockNoframes - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenNoframes) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockNoframes) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseNoframes) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseNoframes) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockNoframes unless _tmp - return _tmp - end - - # HtmlBlockOpenNoscript = "<" Spnl ("noscript" | "NOSCRIPT") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenNoscript - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("noscript") - break if _tmp - self.pos = _save1 - _tmp = match_string("NOSCRIPT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenNoscript unless _tmp - return _tmp - end - - # HtmlBlockCloseNoscript = "<" Spnl "/" ("noscript" | "NOSCRIPT") Spnl ">" - def _HtmlBlockCloseNoscript - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("noscript") - break if _tmp - self.pos = _save1 - _tmp = match_string("NOSCRIPT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseNoscript unless _tmp - return _tmp - end - - # HtmlBlockNoscript = HtmlBlockOpenNoscript (HtmlBlockNoscript | !HtmlBlockCloseNoscript .)* HtmlBlockCloseNoscript - def _HtmlBlockNoscript - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenNoscript) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockNoscript) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseNoscript) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseNoscript) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockNoscript unless _tmp - return _tmp - end - - # HtmlBlockOpenOl = "<" Spnl ("ol" | "OL") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenOl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("ol") - break if _tmp - self.pos = _save1 - _tmp = match_string("OL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenOl unless _tmp - return _tmp - end - - # HtmlBlockCloseOl = "<" Spnl "/" ("ol" | "OL") Spnl ">" - def _HtmlBlockCloseOl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("ol") - break if _tmp - self.pos = _save1 - _tmp = match_string("OL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseOl unless _tmp - return _tmp - end - - # HtmlBlockOl = HtmlBlockOpenOl (HtmlBlockOl | !HtmlBlockCloseOl .)* HtmlBlockCloseOl - def _HtmlBlockOl - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenOl) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockOl) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseOl) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseOl) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOl unless _tmp - return _tmp - end - - # HtmlBlockOpenP = "<" Spnl ("p" | "P") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenP - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("p") - break if _tmp - self.pos = _save1 - _tmp = match_string("P") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenP unless _tmp - return _tmp - end - - # HtmlBlockCloseP = "<" Spnl "/" ("p" | "P") Spnl ">" - def _HtmlBlockCloseP - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("p") - break if _tmp - self.pos = _save1 - _tmp = match_string("P") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseP unless _tmp - return _tmp - end - - # HtmlBlockP = HtmlBlockOpenP (HtmlBlockP | !HtmlBlockCloseP .)* HtmlBlockCloseP - def _HtmlBlockP - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenP) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockP) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseP) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseP) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockP unless _tmp - return _tmp - end - - # HtmlBlockOpenPre = "<" Spnl ("pre" | "PRE") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenPre - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("pre") - break if _tmp - self.pos = _save1 - _tmp = match_string("PRE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenPre unless _tmp - return _tmp - end - - # HtmlBlockClosePre = "<" Spnl "/" ("pre" | "PRE") Spnl ">" - def _HtmlBlockClosePre - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("pre") - break if _tmp - self.pos = _save1 - _tmp = match_string("PRE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockClosePre unless _tmp - return _tmp - end - - # HtmlBlockPre = HtmlBlockOpenPre (HtmlBlockPre | !HtmlBlockClosePre .)* HtmlBlockClosePre - def _HtmlBlockPre - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenPre) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockPre) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockClosePre) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockClosePre) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockPre unless _tmp - return _tmp - end - - # HtmlBlockOpenTable = "<" Spnl ("table" | "TABLE") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTable - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("table") - break if _tmp - self.pos = _save1 - _tmp = match_string("TABLE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTable unless _tmp - return _tmp - end - - # HtmlBlockCloseTable = "<" Spnl "/" ("table" | "TABLE") Spnl ">" - def _HtmlBlockCloseTable - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("table") - break if _tmp - self.pos = _save1 - _tmp = match_string("TABLE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTable unless _tmp - return _tmp - end - - # HtmlBlockTable = HtmlBlockOpenTable (HtmlBlockTable | !HtmlBlockCloseTable .)* HtmlBlockCloseTable - def _HtmlBlockTable - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTable) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTable) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTable) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTable) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTable unless _tmp - return _tmp - end - - # HtmlBlockOpenUl = "<" Spnl ("ul" | "UL") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenUl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("ul") - break if _tmp - self.pos = _save1 - _tmp = match_string("UL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenUl unless _tmp - return _tmp - end - - # HtmlBlockCloseUl = "<" Spnl "/" ("ul" | "UL") Spnl ">" - def _HtmlBlockCloseUl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("ul") - break if _tmp - self.pos = _save1 - _tmp = match_string("UL") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseUl unless _tmp - return _tmp - end - - # HtmlBlockUl = HtmlBlockOpenUl (HtmlBlockUl | !HtmlBlockCloseUl .)* HtmlBlockCloseUl - def _HtmlBlockUl - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenUl) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockUl) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseUl) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseUl) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockUl unless _tmp - return _tmp - end - - # HtmlBlockOpenDd = "<" Spnl ("dd" | "DD") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenDd - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dd") - break if _tmp - self.pos = _save1 - _tmp = match_string("DD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenDd unless _tmp - return _tmp - end - - # HtmlBlockCloseDd = "<" Spnl "/" ("dd" | "DD") Spnl ">" - def _HtmlBlockCloseDd - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dd") - break if _tmp - self.pos = _save1 - _tmp = match_string("DD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseDd unless _tmp - return _tmp - end - - # HtmlBlockDd = HtmlBlockOpenDd (HtmlBlockDd | !HtmlBlockCloseDd .)* HtmlBlockCloseDd - def _HtmlBlockDd - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenDd) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockDd) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseDd) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseDd) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockDd unless _tmp - return _tmp - end - - # HtmlBlockOpenDt = "<" Spnl ("dt" | "DT") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenDt - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dt") - break if _tmp - self.pos = _save1 - _tmp = match_string("DT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenDt unless _tmp - return _tmp - end - - # HtmlBlockCloseDt = "<" Spnl "/" ("dt" | "DT") Spnl ">" - def _HtmlBlockCloseDt - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("dt") - break if _tmp - self.pos = _save1 - _tmp = match_string("DT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseDt unless _tmp - return _tmp - end - - # HtmlBlockDt = HtmlBlockOpenDt (HtmlBlockDt | !HtmlBlockCloseDt .)* HtmlBlockCloseDt - def _HtmlBlockDt - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenDt) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockDt) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseDt) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseDt) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockDt unless _tmp - return _tmp - end - - # HtmlBlockOpenFrameset = "<" Spnl ("frameset" | "FRAMESET") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenFrameset - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("frameset") - break if _tmp - self.pos = _save1 - _tmp = match_string("FRAMESET") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenFrameset unless _tmp - return _tmp - end - - # HtmlBlockCloseFrameset = "<" Spnl "/" ("frameset" | "FRAMESET") Spnl ">" - def _HtmlBlockCloseFrameset - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("frameset") - break if _tmp - self.pos = _save1 - _tmp = match_string("FRAMESET") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseFrameset unless _tmp - return _tmp - end - - # HtmlBlockFrameset = HtmlBlockOpenFrameset (HtmlBlockFrameset | !HtmlBlockCloseFrameset .)* HtmlBlockCloseFrameset - def _HtmlBlockFrameset - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenFrameset) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockFrameset) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseFrameset) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseFrameset) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockFrameset unless _tmp - return _tmp - end - - # HtmlBlockOpenLi = "<" Spnl ("li" | "LI") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenLi - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("li") - break if _tmp - self.pos = _save1 - _tmp = match_string("LI") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenLi unless _tmp - return _tmp - end - - # HtmlBlockCloseLi = "<" Spnl "/" ("li" | "LI") Spnl ">" - def _HtmlBlockCloseLi - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("li") - break if _tmp - self.pos = _save1 - _tmp = match_string("LI") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseLi unless _tmp - return _tmp - end - - # HtmlBlockLi = HtmlBlockOpenLi (HtmlBlockLi | !HtmlBlockCloseLi .)* HtmlBlockCloseLi - def _HtmlBlockLi - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenLi) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockLi) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseLi) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseLi) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockLi unless _tmp - return _tmp - end - - # HtmlBlockOpenTbody = "<" Spnl ("tbody" | "TBODY") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTbody - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tbody") - break if _tmp - self.pos = _save1 - _tmp = match_string("TBODY") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTbody unless _tmp - return _tmp - end - - # HtmlBlockCloseTbody = "<" Spnl "/" ("tbody" | "TBODY") Spnl ">" - def _HtmlBlockCloseTbody - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tbody") - break if _tmp - self.pos = _save1 - _tmp = match_string("TBODY") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTbody unless _tmp - return _tmp - end - - # HtmlBlockTbody = HtmlBlockOpenTbody (HtmlBlockTbody | !HtmlBlockCloseTbody .)* HtmlBlockCloseTbody - def _HtmlBlockTbody - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTbody) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTbody) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTbody) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTbody) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTbody unless _tmp - return _tmp - end - - # HtmlBlockOpenTd = "<" Spnl ("td" | "TD") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTd - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("td") - break if _tmp - self.pos = _save1 - _tmp = match_string("TD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTd unless _tmp - return _tmp - end - - # HtmlBlockCloseTd = "<" Spnl "/" ("td" | "TD") Spnl ">" - def _HtmlBlockCloseTd - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("td") - break if _tmp - self.pos = _save1 - _tmp = match_string("TD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTd unless _tmp - return _tmp - end - - # HtmlBlockTd = HtmlBlockOpenTd (HtmlBlockTd | !HtmlBlockCloseTd .)* HtmlBlockCloseTd - def _HtmlBlockTd - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTd) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTd) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTd) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTd) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTd unless _tmp - return _tmp - end - - # HtmlBlockOpenTfoot = "<" Spnl ("tfoot" | "TFOOT") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTfoot - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tfoot") - break if _tmp - self.pos = _save1 - _tmp = match_string("TFOOT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTfoot unless _tmp - return _tmp - end - - # HtmlBlockCloseTfoot = "<" Spnl "/" ("tfoot" | "TFOOT") Spnl ">" - def _HtmlBlockCloseTfoot - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tfoot") - break if _tmp - self.pos = _save1 - _tmp = match_string("TFOOT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTfoot unless _tmp - return _tmp - end - - # HtmlBlockTfoot = HtmlBlockOpenTfoot (HtmlBlockTfoot | !HtmlBlockCloseTfoot .)* HtmlBlockCloseTfoot - def _HtmlBlockTfoot - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTfoot) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTfoot) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTfoot) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTfoot) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTfoot unless _tmp - return _tmp - end - - # HtmlBlockOpenTh = "<" Spnl ("th" | "TH") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTh - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("th") - break if _tmp - self.pos = _save1 - _tmp = match_string("TH") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTh unless _tmp - return _tmp - end - - # HtmlBlockCloseTh = "<" Spnl "/" ("th" | "TH") Spnl ">" - def _HtmlBlockCloseTh - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("th") - break if _tmp - self.pos = _save1 - _tmp = match_string("TH") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTh unless _tmp - return _tmp - end - - # HtmlBlockTh = HtmlBlockOpenTh (HtmlBlockTh | !HtmlBlockCloseTh .)* HtmlBlockCloseTh - def _HtmlBlockTh - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTh) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTh) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTh) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTh) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTh unless _tmp - return _tmp - end - - # HtmlBlockOpenThead = "<" Spnl ("thead" | "THEAD") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenThead - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("thead") - break if _tmp - self.pos = _save1 - _tmp = match_string("THEAD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenThead unless _tmp - return _tmp - end - - # HtmlBlockCloseThead = "<" Spnl "/" ("thead" | "THEAD") Spnl ">" - def _HtmlBlockCloseThead - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("thead") - break if _tmp - self.pos = _save1 - _tmp = match_string("THEAD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseThead unless _tmp - return _tmp - end - - # HtmlBlockThead = HtmlBlockOpenThead (HtmlBlockThead | !HtmlBlockCloseThead .)* HtmlBlockCloseThead - def _HtmlBlockThead - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenThead) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockThead) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseThead) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseThead) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockThead unless _tmp - return _tmp - end - - # HtmlBlockOpenTr = "<" Spnl ("tr" | "TR") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenTr - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tr") - break if _tmp - self.pos = _save1 - _tmp = match_string("TR") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenTr unless _tmp - return _tmp - end - - # HtmlBlockCloseTr = "<" Spnl "/" ("tr" | "TR") Spnl ">" - def _HtmlBlockCloseTr - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("tr") - break if _tmp - self.pos = _save1 - _tmp = match_string("TR") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseTr unless _tmp - return _tmp - end - - # HtmlBlockTr = HtmlBlockOpenTr (HtmlBlockTr | !HtmlBlockCloseTr .)* HtmlBlockCloseTr - def _HtmlBlockTr - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenTr) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockTr) - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_HtmlBlockCloseTr) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseTr) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockTr unless _tmp - return _tmp - end - - # HtmlBlockOpenScript = "<" Spnl ("script" | "SCRIPT") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenScript - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("script") - break if _tmp - self.pos = _save1 - _tmp = match_string("SCRIPT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenScript unless _tmp - return _tmp - end - - # HtmlBlockCloseScript = "<" Spnl "/" ("script" | "SCRIPT") Spnl ">" - def _HtmlBlockCloseScript - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("script") - break if _tmp - self.pos = _save1 - _tmp = match_string("SCRIPT") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseScript unless _tmp - return _tmp - end - - # HtmlBlockScript = HtmlBlockOpenScript (!HtmlBlockCloseScript .)* HtmlBlockCloseScript - def _HtmlBlockScript - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenScript) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = apply(:_HtmlBlockCloseScript) - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseScript) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockScript unless _tmp - return _tmp - end - - # HtmlBlockOpenHead = "<" Spnl ("head" | "HEAD") Spnl HtmlAttribute* ">" - def _HtmlBlockOpenHead - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("head") - break if _tmp - self.pos = _save1 - _tmp = match_string("HEAD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockOpenHead unless _tmp - return _tmp - end - - # HtmlBlockCloseHead = "<" Spnl "/" ("head" | "HEAD") Spnl ">" - def _HtmlBlockCloseHead - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("head") - break if _tmp - self.pos = _save1 - _tmp = match_string("HEAD") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockCloseHead unless _tmp - return _tmp - end - - # HtmlBlockHead = HtmlBlockOpenHead (!HtmlBlockCloseHead .)* HtmlBlockCloseHead - def _HtmlBlockHead - - _save = self.pos - while true # sequence - _tmp = apply(:_HtmlBlockOpenHead) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = apply(:_HtmlBlockCloseHead) - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockCloseHead) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockHead unless _tmp - return _tmp - end - - # HtmlBlockInTags = (HtmlAnchor | HtmlBlockAddress | HtmlBlockBlockquote | HtmlBlockCenter | HtmlBlockDir | HtmlBlockDiv | HtmlBlockDl | HtmlBlockFieldset | HtmlBlockForm | HtmlBlockH1 | HtmlBlockH2 | HtmlBlockH3 | HtmlBlockH4 | HtmlBlockH5 | HtmlBlockH6 | HtmlBlockMenu | HtmlBlockNoframes | HtmlBlockNoscript | HtmlBlockOl | HtmlBlockP | HtmlBlockPre | HtmlBlockTable | HtmlBlockUl | HtmlBlockDd | HtmlBlockDt | HtmlBlockFrameset | HtmlBlockLi | HtmlBlockTbody | HtmlBlockTd | HtmlBlockTfoot | HtmlBlockTh | HtmlBlockThead | HtmlBlockTr | HtmlBlockScript | HtmlBlockHead) - def _HtmlBlockInTags - - _save = self.pos - while true # choice - _tmp = apply(:_HtmlAnchor) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockAddress) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockBlockquote) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockCenter) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockDir) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockDiv) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockDl) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockFieldset) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockForm) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH1) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH2) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH3) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH4) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH5) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockH6) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockMenu) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockNoframes) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockNoscript) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockOl) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockP) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockPre) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTable) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockUl) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockDd) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockDt) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockFrameset) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockLi) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTbody) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTd) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTfoot) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTh) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockThead) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockTr) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockScript) - break if _tmp - self.pos = _save - _tmp = apply(:_HtmlBlockHead) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_HtmlBlockInTags unless _tmp - return _tmp - end - - # HtmlBlock = < (HtmlBlockInTags | HtmlComment | HtmlBlockSelfClosing | HtmlUnclosed) > @BlankLine+ { if html? then RDoc::Markup::Raw.new text end } - def _HtmlBlock - - _save = self.pos - while true # sequence - _text_start = self.pos - - _save1 = self.pos - while true # choice - _tmp = apply(:_HtmlBlockInTags) - break if _tmp - self.pos = _save1 - _tmp = apply(:_HtmlComment) - break if _tmp - self.pos = _save1 - _tmp = apply(:_HtmlBlockSelfClosing) - break if _tmp - self.pos = _save1 - _tmp = apply(:_HtmlUnclosed) - break if _tmp - self.pos = _save1 - break - end # end choice - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - @result = begin; if html? then - RDoc::Markup::Raw.new text - end ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlock unless _tmp - return _tmp - end - - # HtmlUnclosed = "<" Spnl HtmlUnclosedType Spnl HtmlAttribute* Spnl ">" - def _HtmlUnclosed - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlUnclosedType) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlUnclosed unless _tmp - return _tmp - end - - # HtmlUnclosedType = ("HR" | "hr") - def _HtmlUnclosedType - - _save = self.pos - while true # choice - _tmp = match_string("HR") - break if _tmp - self.pos = _save - _tmp = match_string("hr") - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_HtmlUnclosedType unless _tmp - return _tmp - end - - # HtmlBlockSelfClosing = "<" Spnl HtmlBlockType Spnl HtmlAttribute* "/" Spnl ">" - def _HtmlBlockSelfClosing - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_HtmlBlockType) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlBlockSelfClosing unless _tmp - return _tmp - end - - # HtmlBlockType = ("ADDRESS" | "BLOCKQUOTE" | "CENTER" | "DD" | "DIR" | "DIV" | "DL" | "DT" | "FIELDSET" | "FORM" | "FRAMESET" | "H1" | "H2" | "H3" | "H4" | "H5" | "H6" | "HR" | "ISINDEX" | "LI" | "MENU" | "NOFRAMES" | "NOSCRIPT" | "OL" | "P" | "PRE" | "SCRIPT" | "TABLE" | "TBODY" | "TD" | "TFOOT" | "TH" | "THEAD" | "TR" | "UL" | "address" | "blockquote" | "center" | "dd" | "dir" | "div" | "dl" | "dt" | "fieldset" | "form" | "frameset" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "hr" | "isindex" | "li" | "menu" | "noframes" | "noscript" | "ol" | "p" | "pre" | "script" | "table" | "tbody" | "td" | "tfoot" | "th" | "thead" | "tr" | "ul") - def _HtmlBlockType - - _save = self.pos - while true # choice - _tmp = match_string("ADDRESS") - break if _tmp - self.pos = _save - _tmp = match_string("BLOCKQUOTE") - break if _tmp - self.pos = _save - _tmp = match_string("CENTER") - break if _tmp - self.pos = _save - _tmp = match_string("DD") - break if _tmp - self.pos = _save - _tmp = match_string("DIR") - break if _tmp - self.pos = _save - _tmp = match_string("DIV") - break if _tmp - self.pos = _save - _tmp = match_string("DL") - break if _tmp - self.pos = _save - _tmp = match_string("DT") - break if _tmp - self.pos = _save - _tmp = match_string("FIELDSET") - break if _tmp - self.pos = _save - _tmp = match_string("FORM") - break if _tmp - self.pos = _save - _tmp = match_string("FRAMESET") - break if _tmp - self.pos = _save - _tmp = match_string("H1") - break if _tmp - self.pos = _save - _tmp = match_string("H2") - break if _tmp - self.pos = _save - _tmp = match_string("H3") - break if _tmp - self.pos = _save - _tmp = match_string("H4") - break if _tmp - self.pos = _save - _tmp = match_string("H5") - break if _tmp - self.pos = _save - _tmp = match_string("H6") - break if _tmp - self.pos = _save - _tmp = match_string("HR") - break if _tmp - self.pos = _save - _tmp = match_string("ISINDEX") - break if _tmp - self.pos = _save - _tmp = match_string("LI") - break if _tmp - self.pos = _save - _tmp = match_string("MENU") - break if _tmp - self.pos = _save - _tmp = match_string("NOFRAMES") - break if _tmp - self.pos = _save - _tmp = match_string("NOSCRIPT") - break if _tmp - self.pos = _save - _tmp = match_string("OL") - break if _tmp - self.pos = _save - _tmp = match_string("P") - break if _tmp - self.pos = _save - _tmp = match_string("PRE") - break if _tmp - self.pos = _save - _tmp = match_string("SCRIPT") - break if _tmp - self.pos = _save - _tmp = match_string("TABLE") - break if _tmp - self.pos = _save - _tmp = match_string("TBODY") - break if _tmp - self.pos = _save - _tmp = match_string("TD") - break if _tmp - self.pos = _save - _tmp = match_string("TFOOT") - break if _tmp - self.pos = _save - _tmp = match_string("TH") - break if _tmp - self.pos = _save - _tmp = match_string("THEAD") - break if _tmp - self.pos = _save - _tmp = match_string("TR") - break if _tmp - self.pos = _save - _tmp = match_string("UL") - break if _tmp - self.pos = _save - _tmp = match_string("address") - break if _tmp - self.pos = _save - _tmp = match_string("blockquote") - break if _tmp - self.pos = _save - _tmp = match_string("center") - break if _tmp - self.pos = _save - _tmp = match_string("dd") - break if _tmp - self.pos = _save - _tmp = match_string("dir") - break if _tmp - self.pos = _save - _tmp = match_string("div") - break if _tmp - self.pos = _save - _tmp = match_string("dl") - break if _tmp - self.pos = _save - _tmp = match_string("dt") - break if _tmp - self.pos = _save - _tmp = match_string("fieldset") - break if _tmp - self.pos = _save - _tmp = match_string("form") - break if _tmp - self.pos = _save - _tmp = match_string("frameset") - break if _tmp - self.pos = _save - _tmp = match_string("h1") - break if _tmp - self.pos = _save - _tmp = match_string("h2") - break if _tmp - self.pos = _save - _tmp = match_string("h3") - break if _tmp - self.pos = _save - _tmp = match_string("h4") - break if _tmp - self.pos = _save - _tmp = match_string("h5") - break if _tmp - self.pos = _save - _tmp = match_string("h6") - break if _tmp - self.pos = _save - _tmp = match_string("hr") - break if _tmp - self.pos = _save - _tmp = match_string("isindex") - break if _tmp - self.pos = _save - _tmp = match_string("li") - break if _tmp - self.pos = _save - _tmp = match_string("menu") - break if _tmp - self.pos = _save - _tmp = match_string("noframes") - break if _tmp - self.pos = _save - _tmp = match_string("noscript") - break if _tmp - self.pos = _save - _tmp = match_string("ol") - break if _tmp - self.pos = _save - _tmp = match_string("p") - break if _tmp - self.pos = _save - _tmp = match_string("pre") - break if _tmp - self.pos = _save - _tmp = match_string("script") - break if _tmp - self.pos = _save - _tmp = match_string("table") - break if _tmp - self.pos = _save - _tmp = match_string("tbody") - break if _tmp - self.pos = _save - _tmp = match_string("td") - break if _tmp - self.pos = _save - _tmp = match_string("tfoot") - break if _tmp - self.pos = _save - _tmp = match_string("th") - break if _tmp - self.pos = _save - _tmp = match_string("thead") - break if _tmp - self.pos = _save - _tmp = match_string("tr") - break if _tmp - self.pos = _save - _tmp = match_string("ul") - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_HtmlBlockType unless _tmp - return _tmp - end - - # StyleOpen = "<" Spnl ("style" | "STYLE") Spnl HtmlAttribute* ">" - def _StyleOpen - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("style") - break if _tmp - self.pos = _save1 - _tmp = match_string("STYLE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StyleOpen unless _tmp - return _tmp - end - - # StyleClose = "<" Spnl "/" ("style" | "STYLE") Spnl ">" - def _StyleClose - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("/") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = match_string("style") - break if _tmp - self.pos = _save1 - _tmp = match_string("STYLE") - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StyleClose unless _tmp - return _tmp - end - - # InStyleTags = StyleOpen (!StyleClose .)* StyleClose - def _InStyleTags - - _save = self.pos - while true # sequence - _tmp = apply(:_StyleOpen) - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = apply(:_StyleClose) - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_StyleClose) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_InStyleTags unless _tmp - return _tmp - end - - # StyleBlock = < InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end } - def _StyleBlock - - _save = self.pos - while true # sequence - _text_start = self.pos - _tmp = apply(:_InStyleTags) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; if css? then - RDoc::Markup::Raw.new text - end ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StyleBlock unless _tmp - return _tmp - end - - # Inlines = (!@Endline Inline:i { i } | @Endline:c !(&{ github? } Ticks3 /[^`\n]*$/) &Inline { c })+:chunks @Endline? { chunks } - def _Inlines - - _save = self.pos - while true # sequence - _save1 = self.pos - _ary = [] - - _save2 = self.pos - while true # choice - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_Inline) - i = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; i ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - - _save5 = self.pos - while true # sequence - _tmp = _Endline() - c = @result - unless _tmp - self.pos = _save5 - break - end - _save6 = self.pos - - _save7 = self.pos - while true # sequence - _save8 = self.pos - _tmp = begin; github? ; end - self.pos = _save8 - unless _tmp - self.pos = _save7 - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save7 - break - end - _tmp = scan(/\G(?-mix:[^`\n]*$)/) - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _save9 = self.pos - _tmp = apply(:_Inline) - self.pos = _save9 - unless _tmp - self.pos = _save5 - break - end - @result = begin; c ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - if _tmp - _ary << @result - while true - - _save10 = self.pos - while true # choice - - _save11 = self.pos - while true # sequence - _save12 = self.pos - _tmp = _Endline() - _tmp = _tmp ? nil : true - self.pos = _save12 - unless _tmp - self.pos = _save11 - break - end - _tmp = apply(:_Inline) - i = @result - unless _tmp - self.pos = _save11 - break - end - @result = begin; i ; end - _tmp = true - unless _tmp - self.pos = _save11 - end - break - end # end sequence - - break if _tmp - self.pos = _save10 - - _save13 = self.pos - while true # sequence - _tmp = _Endline() - c = @result - unless _tmp - self.pos = _save13 - break - end - _save14 = self.pos - - _save15 = self.pos - while true # sequence - _save16 = self.pos - _tmp = begin; github? ; end - self.pos = _save16 - unless _tmp - self.pos = _save15 - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save15 - break - end - _tmp = scan(/\G(?-mix:[^`\n]*$)/) - unless _tmp - self.pos = _save15 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save14 - unless _tmp - self.pos = _save13 - break - end - _save17 = self.pos - _tmp = apply(:_Inline) - self.pos = _save17 - unless _tmp - self.pos = _save13 - break - end - @result = begin; c ; end - _tmp = true - unless _tmp - self.pos = _save13 - end - break - end # end sequence - - break if _tmp - self.pos = _save10 - break - end # end choice - - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save1 - end - chunks = @result - unless _tmp - self.pos = _save - break - end - _save18 = self.pos - _tmp = _Endline() - unless _tmp - _tmp = true - self.pos = _save18 - end - unless _tmp - self.pos = _save - break - end - @result = begin; chunks ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Inlines unless _tmp - return _tmp - end - - # Inline = (Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol) - def _Inline - - _save = self.pos - while true # choice - _tmp = apply(:_Str) - break if _tmp - self.pos = _save - _tmp = _Endline() - break if _tmp - self.pos = _save - _tmp = apply(:_UlOrStarLine) - break if _tmp - self.pos = _save - _tmp = _Space() - break if _tmp - self.pos = _save - _tmp = apply(:_Strong) - break if _tmp - self.pos = _save - _tmp = apply(:_Emph) - break if _tmp - self.pos = _save - _tmp = apply(:_Strike) - break if _tmp - self.pos = _save - _tmp = apply(:_Image) - break if _tmp - self.pos = _save - _tmp = apply(:_Link) - break if _tmp - self.pos = _save - _tmp = apply(:_NoteReference) - break if _tmp - self.pos = _save - _tmp = apply(:_InlineNote) - break if _tmp - self.pos = _save - _tmp = apply(:_Code) - break if _tmp - self.pos = _save - _tmp = apply(:_RawHtml) - break if _tmp - self.pos = _save - _tmp = apply(:_Entity) - break if _tmp - self.pos = _save - _tmp = apply(:_EscapedChar) - break if _tmp - self.pos = _save - _tmp = apply(:_Symbol) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Inline unless _tmp - return _tmp - end - - # Space = @Spacechar+ { " " } - def _Space - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _Spacechar() - if _tmp - while true - _tmp = _Spacechar() - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - @result = begin; " " ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Space unless _tmp - return _tmp - end - - # Str = @StartList:a < @NormalChar+ > { a = text } (StrChunk:c { a << c })* { a } - def _Str - - _save = self.pos - while true # sequence - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _save1 = self.pos - _tmp = _NormalChar() - if _tmp - while true - _tmp = _NormalChar() - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; a = text ; end - _tmp = true - unless _tmp - self.pos = _save - break - end - while true - - _save3 = self.pos - while true # sequence - _tmp = apply(:_StrChunk) - c = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a << c ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Str unless _tmp - return _tmp - end - - # StrChunk = < (@NormalChar | /_+/ &Alphanumeric)+ > { text } - def _StrChunk - - _save = self.pos - while true # sequence - _text_start = self.pos - _save1 = self.pos - - _save2 = self.pos - while true # choice - _tmp = _NormalChar() - break if _tmp - self.pos = _save2 - - _save3 = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:_+)/) - unless _tmp - self.pos = _save3 - break - end - _save4 = self.pos - _tmp = apply(:_Alphanumeric) - self.pos = _save4 - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save2 - break - end # end choice - - if _tmp - while true - - _save5 = self.pos - while true # choice - _tmp = _NormalChar() - break if _tmp - self.pos = _save5 - - _save6 = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:_+)/) - unless _tmp - self.pos = _save6 - break - end - _save7 = self.pos - _tmp = apply(:_Alphanumeric) - self.pos = _save7 - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break if _tmp - self.pos = _save5 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StrChunk unless _tmp - return _tmp - end - - # EscapedChar = "\\" !@Newline < /[:\\`|*_{}\[\]()#+.!><-]/ > { text } - def _EscapedChar - - _save = self.pos - while true # sequence - _tmp = match_string("\\") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _tmp = scan(/\G(?-mix:[:\\`|*_{}\[\]()#+.!><-])/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_EscapedChar unless _tmp - return _tmp - end - - # Entity = (HexEntity | DecEntity | CharEntity):a { a } - def _Entity - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _tmp = apply(:_HexEntity) - break if _tmp - self.pos = _save1 - _tmp = apply(:_DecEntity) - break if _tmp - self.pos = _save1 - _tmp = apply(:_CharEntity) - break if _tmp - self.pos = _save1 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Entity unless _tmp - return _tmp - end - - # Endline = (@LineBreak | @TerminalEndline | @NormalEndline) - def _Endline - - _save = self.pos - while true # choice - _tmp = _LineBreak() - break if _tmp - self.pos = _save - _tmp = _TerminalEndline() - break if _tmp - self.pos = _save - _tmp = _NormalEndline() - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Endline unless _tmp - return _tmp - end - - # NormalEndline = @Sp @Newline !@BlankLine !">" !AtxStart !(Line /={1,}|-{1,}/ @Newline) { "\n" } - def _NormalEndline - - _save = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save2 - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - _tmp = apply(:_AtxStart) - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save - break - end - _save4 = self.pos - - _save5 = self.pos - while true # sequence - _tmp = apply(:_Line) - unless _tmp - self.pos = _save5 - break - end - _tmp = scan(/\G(?-mix:={1,}|-{1,})/) - unless _tmp - self.pos = _save5 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save - break - end - @result = begin; "\n" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_NormalEndline unless _tmp - return _tmp - end - - # TerminalEndline = @Sp @Newline @Eof - def _TerminalEndline - - _save = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - _tmp = _Eof() - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TerminalEndline unless _tmp - return _tmp - end - - # LineBreak = " " @NormalEndline { RDoc::Markup::HardBreak.new } - def _LineBreak - - _save = self.pos - while true # sequence - _tmp = match_string(" ") - unless _tmp - self.pos = _save - break - end - _tmp = _NormalEndline() - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::HardBreak.new ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_LineBreak unless _tmp - return _tmp - end - - # Symbol = < @SpecialChar > { text } - def _Symbol - - _save = self.pos - while true # sequence - _text_start = self.pos - _tmp = _SpecialChar() - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Symbol unless _tmp - return _tmp - end - - # UlOrStarLine = (UlLine | StarLine):a { a } - def _UlOrStarLine - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _tmp = apply(:_UlLine) - break if _tmp - self.pos = _save1 - _tmp = apply(:_StarLine) - break if _tmp - self.pos = _save1 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_UlOrStarLine unless _tmp - return _tmp - end - - # StarLine = (< /\*{4,}/ > { text } | < @Spacechar /\*+/ &@Spacechar > { text }) - def _StarLine - - _save = self.pos - while true # choice - - _save1 = self.pos - while true # sequence - _text_start = self.pos - _tmp = scan(/\G(?-mix:\*{4,})/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save1 - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save1 - end - break - end # end sequence - - break if _tmp - self.pos = _save - - _save2 = self.pos - while true # sequence - _text_start = self.pos - - _save3 = self.pos - while true # sequence - _tmp = _Spacechar() - unless _tmp - self.pos = _save3 - break - end - _tmp = scan(/\G(?-mix:\*+)/) - unless _tmp - self.pos = _save3 - break - end - _save4 = self.pos - _tmp = _Spacechar() - self.pos = _save4 - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save2 - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_StarLine unless _tmp - return _tmp - end - - # UlLine = (< /_{4,}/ > { text } | < @Spacechar /_+/ &@Spacechar > { text }) - def _UlLine - - _save = self.pos - while true # choice - - _save1 = self.pos - while true # sequence - _text_start = self.pos - _tmp = scan(/\G(?-mix:_{4,})/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save1 - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save1 - end - break - end # end sequence - - break if _tmp - self.pos = _save - - _save2 = self.pos - while true # sequence - _text_start = self.pos - - _save3 = self.pos - while true # sequence - _tmp = _Spacechar() - unless _tmp - self.pos = _save3 - break - end - _tmp = scan(/\G(?-mix:_+)/) - unless _tmp - self.pos = _save3 - break - end - _save4 = self.pos - _tmp = _Spacechar() - self.pos = _save4 - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save2 - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_UlLine unless _tmp - return _tmp - end - - # Emph = (EmphStar | EmphUl) - def _Emph - - _save = self.pos - while true # choice - _tmp = apply(:_EmphStar) - break if _tmp - self.pos = _save - _tmp = apply(:_EmphUl) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Emph unless _tmp - return _tmp - end - - # Whitespace = (@Spacechar | @Newline) - def _Whitespace - - _save = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save - _tmp = _Newline() - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Whitespace unless _tmp - return _tmp - end - - # EmphStar = "*" !@Whitespace @StartList:a (!"*" Inline:b { a << b } | StrongStar:b { a << b })+ "*" { emphasis a.join } - def _EmphStar - - _save = self.pos - while true # sequence - _tmp = match_string("*") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Whitespace() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # choice - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = match_string("*") - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - break if _tmp - self.pos = _save3 - - _save6 = self.pos - while true # sequence - _tmp = apply(:_StrongStar) - b = @result - unless _tmp - self.pos = _save6 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break if _tmp - self.pos = _save3 - break - end # end choice - - if _tmp - while true - - _save7 = self.pos - while true # choice - - _save8 = self.pos - while true # sequence - _save9 = self.pos - _tmp = match_string("*") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save8 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save8 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - break if _tmp - self.pos = _save7 - - _save10 = self.pos - while true # sequence - _tmp = apply(:_StrongStar) - b = @result - unless _tmp - self.pos = _save10 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break if _tmp - self.pos = _save7 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("*") - unless _tmp - self.pos = _save - break - end - @result = begin; emphasis a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_EmphStar unless _tmp - return _tmp - end - - # EmphUl = "_" !@Whitespace @StartList:a (!"_" Inline:b { a << b } | StrongUl:b { a << b })+ "_" { emphasis a.join } - def _EmphUl - - _save = self.pos - while true # sequence - _tmp = match_string("_") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Whitespace() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # choice - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = match_string("_") - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - break if _tmp - self.pos = _save3 - - _save6 = self.pos - while true # sequence - _tmp = apply(:_StrongUl) - b = @result - unless _tmp - self.pos = _save6 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break if _tmp - self.pos = _save3 - break - end # end choice - - if _tmp - while true - - _save7 = self.pos - while true # choice - - _save8 = self.pos - while true # sequence - _save9 = self.pos - _tmp = match_string("_") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save8 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save8 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - break if _tmp - self.pos = _save7 - - _save10 = self.pos - while true # sequence - _tmp = apply(:_StrongUl) - b = @result - unless _tmp - self.pos = _save10 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break if _tmp - self.pos = _save7 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("_") - unless _tmp - self.pos = _save - break - end - @result = begin; emphasis a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_EmphUl unless _tmp - return _tmp - end - - # Strong = (StrongStar | StrongUl) - def _Strong - - _save = self.pos - while true # choice - _tmp = apply(:_StrongStar) - break if _tmp - self.pos = _save - _tmp = apply(:_StrongUl) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Strong unless _tmp - return _tmp - end - - # StrongStar = "**" !@Whitespace @StartList:a (!"**" Inline:b { a << b })+ "**" { strong a.join } - def _StrongStar - - _save = self.pos - while true # sequence - _tmp = match_string("**") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Whitespace() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("**") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = match_string("**") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save5 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("**") - unless _tmp - self.pos = _save - break - end - @result = begin; strong a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StrongStar unless _tmp - return _tmp - end - - # StrongUl = "__" !@Whitespace @StartList:a (!"__" Inline:b { a << b })+ "__" { strong a.join } - def _StrongUl - - _save = self.pos - while true # sequence - _tmp = match_string("__") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _Whitespace() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("__") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = match_string("__") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save5 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("__") - unless _tmp - self.pos = _save - break - end - @result = begin; strong a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StrongUl unless _tmp - return _tmp - end - - # Strike = &{ strike? } "~~" !@Whitespace @StartList:a (!"~~" Inline:b { a << b })+ "~~" { strike a.join } - def _Strike - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; strike? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = match_string("~~") - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = _Whitespace() - _tmp = _tmp ? nil : true - self.pos = _save2 - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = match_string("~~") - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save4 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - if _tmp - while true - - _save6 = self.pos - while true # sequence - _save7 = self.pos - _tmp = match_string("~~") - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save6 - break - end - _tmp = apply(:_Inline) - b = @result - unless _tmp - self.pos = _save6 - break - end - @result = begin; a << b ; end - _tmp = true - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save3 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("~~") - unless _tmp - self.pos = _save - break - end - @result = begin; strike a.join ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Strike unless _tmp - return _tmp - end - - # Image = "!" (ExplicitLink | ReferenceLink):a { "rdoc-image:#{a[/\[(.*)\]/, 1]}" } - def _Image - - _save = self.pos - while true # sequence - _tmp = match_string("!") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - _tmp = apply(:_ExplicitLink) - break if _tmp - self.pos = _save1 - _tmp = apply(:_ReferenceLink) - break if _tmp - self.pos = _save1 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; "rdoc-image:#{a[/\[(.*)\]/, 1]}" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Image unless _tmp - return _tmp - end - - # Link = (ExplicitLink | ReferenceLink | AutoLink) - def _Link - - _save = self.pos - while true # choice - _tmp = apply(:_ExplicitLink) - break if _tmp - self.pos = _save - _tmp = apply(:_ReferenceLink) - break if _tmp - self.pos = _save - _tmp = apply(:_AutoLink) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Link unless _tmp - return _tmp - end - - # ReferenceLink = (ReferenceLinkDouble | ReferenceLinkSingle) - def _ReferenceLink - - _save = self.pos - while true # choice - _tmp = apply(:_ReferenceLinkDouble) - break if _tmp - self.pos = _save - _tmp = apply(:_ReferenceLinkSingle) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_ReferenceLink unless _tmp - return _tmp - end - - # ReferenceLinkDouble = Label:content < Spnl > !"[]" Label:label { link_to content, label, text } - def _ReferenceLinkDouble - - _save = self.pos - while true # sequence - _tmp = apply(:_Label) - content = @result - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _tmp = apply(:_Spnl) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("[]") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Label) - label = @result - unless _tmp - self.pos = _save - break - end - @result = begin; link_to content, label, text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ReferenceLinkDouble unless _tmp - return _tmp - end - - # ReferenceLinkSingle = Label:content < (Spnl "[]")? > { link_to content, content, text } - def _ReferenceLinkSingle - - _save = self.pos - while true # sequence - _tmp = apply(:_Label) - content = @result - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save2 - break - end - _tmp = match_string("[]") - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - unless _tmp - _tmp = true - self.pos = _save1 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; link_to content, content, text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ReferenceLinkSingle unless _tmp - return _tmp - end - - # ExplicitLink = Label:l "(" @Sp Source:s Spnl Title @Sp ")" { "{#{l}}[#{s}]" } - def _ExplicitLink - - _save = self.pos - while true # sequence - _tmp = apply(:_Label) - l = @result - unless _tmp - self.pos = _save - break - end - _tmp = match_string("(") - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Source) - s = @result - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Title) - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = match_string(")") - unless _tmp - self.pos = _save - break - end - @result = begin; "{#{l}}[#{s}]" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ExplicitLink unless _tmp - return _tmp - end - - # Source = ("<" < SourceContents > ">" | < SourceContents >) { text } - def _Source - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save2 - break - end - _text_start = self.pos - _tmp = apply(:_SourceContents) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save2 - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - _text_start = self.pos - _tmp = apply(:_SourceContents) - if _tmp - text = get_text(_text_start) - end - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Source unless _tmp - return _tmp - end - - # SourceContents = ((!"(" !")" !">" Nonspacechar)+ | "(" SourceContents ")")* - def _SourceContents - while true - - _save1 = self.pos - while true # choice - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("(") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _save5 = self.pos - _tmp = match_string(")") - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save3 - break - end - _save6 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save7 = self.pos - while true # sequence - _save8 = self.pos - _tmp = match_string("(") - _tmp = _tmp ? nil : true - self.pos = _save8 - unless _tmp - self.pos = _save7 - break - end - _save9 = self.pos - _tmp = match_string(")") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save7 - break - end - _save10 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save10 - unless _tmp - self.pos = _save7 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - break if _tmp - self.pos = _save1 - - _save11 = self.pos - while true # sequence - _tmp = match_string("(") - unless _tmp - self.pos = _save11 - break - end - _tmp = apply(:_SourceContents) - unless _tmp - self.pos = _save11 - break - end - _tmp = match_string(")") - unless _tmp - self.pos = _save11 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - break - end # end choice - - break unless _tmp - end - _tmp = true - set_failed_rule :_SourceContents unless _tmp - return _tmp - end - - # Title = (TitleSingle | TitleDouble | ""):a { a } - def _Title - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _tmp = apply(:_TitleSingle) - break if _tmp - self.pos = _save1 - _tmp = apply(:_TitleDouble) - break if _tmp - self.pos = _save1 - _tmp = match_string("") - break if _tmp - self.pos = _save1 - break - end # end choice - - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Title unless _tmp - return _tmp - end - - # TitleSingle = "'" (!("'" @Sp (")" | @Newline)) .)* "'" - def _TitleSingle - - _save = self.pos - while true # sequence - _tmp = match_string("'") - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _tmp = match_string("'") - unless _tmp - self.pos = _save4 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save4 - break - end - - _save5 = self.pos - while true # choice - _tmp = match_string(")") - break if _tmp - self.pos = _save5 - _tmp = _Newline() - break if _tmp - self.pos = _save5 - break - end # end choice - - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string("'") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TitleSingle unless _tmp - return _tmp - end - - # TitleDouble = "\"" (!("\"" @Sp (")" | @Newline)) .)* "\"" - def _TitleDouble - - _save = self.pos - while true # sequence - _tmp = match_string("\"") - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _tmp = match_string("\"") - unless _tmp - self.pos = _save4 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save4 - break - end - - _save5 = self.pos - while true # choice - _tmp = match_string(")") - break if _tmp - self.pos = _save5 - _tmp = _Newline() - break if _tmp - self.pos = _save5 - break - end # end choice - - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string("\"") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TitleDouble unless _tmp - return _tmp - end - - # AutoLink = (AutoLinkUrl | AutoLinkEmail) - def _AutoLink - - _save = self.pos - while true # choice - _tmp = apply(:_AutoLinkUrl) - break if _tmp - self.pos = _save - _tmp = apply(:_AutoLinkEmail) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_AutoLink unless _tmp - return _tmp - end - - # AutoLinkUrl = "<" < /[A-Za-z]+/ "://" (!@Newline !">" .)+ > ">" { text } - def _AutoLinkUrl - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - - _save1 = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:[A-Za-z]+)/) - unless _tmp - self.pos = _save1 - break - end - _tmp = match_string("://") - unless _tmp - self.pos = _save1 - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _save5 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save6 = self.pos - while true # sequence - _save7 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save6 - break - end - _save8 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save8 - unless _tmp - self.pos = _save6 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save1 - end - break - end # end sequence - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_AutoLinkUrl unless _tmp - return _tmp - end - - # AutoLinkEmail = "<" "mailto:"? < /[\w+.\/!%~$-]+/i "@" (!@Newline !">" .)+ > ">" { "mailto:#{text}" } - def _AutoLinkEmail - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("mailto:") - unless _tmp - _tmp = true - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - - _save2 = self.pos - while true # sequence - _tmp = scan(/\G(?i-mx:[\w+.\/!%~$-]+)/) - unless _tmp - self.pos = _save2 - break - end - _tmp = match_string("@") - unless _tmp - self.pos = _save2 - break - end - _save3 = self.pos - - _save4 = self.pos - while true # sequence - _save5 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save4 - break - end - _save6 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save4 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save4 - end - break - end # end sequence - - if _tmp - while true - - _save7 = self.pos - while true # sequence - _save8 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save8 - unless _tmp - self.pos = _save7 - break - end - _save9 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save7 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save3 - end - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - break - end - @result = begin; "mailto:#{text}" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_AutoLinkEmail unless _tmp - return _tmp - end - - # Reference = @NonindentSpace !"[]" Label:label ":" Spnl RefSrc:link RefTitle @BlankLine+ { # TODO use title reference label, link nil } - def _Reference - - _save = self.pos - while true # sequence - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("[]") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Label) - label = @result - unless _tmp - self.pos = _save - break - end - _tmp = match_string(":") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_RefSrc) - link = @result - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_RefTitle) - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - @result = begin; # TODO use title - reference label, link - nil - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Reference unless _tmp - return _tmp - end - - # Label = "[" (!"^" &{ notes? } | &. &{ !notes? }) @StartList:a (!"]" Inline:l { a << l })* "]" { a.join.gsub(/\s+/, ' ') } - def _Label - - _save = self.pos - while true # sequence - _tmp = match_string("[") - unless _tmp - self.pos = _save - break - end - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = match_string("^") - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _save4 = self.pos - _tmp = begin; notes? ; end - self.pos = _save4 - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = get_byte - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _save7 = self.pos - _tmp = begin; !notes? ; end - self.pos = _save7 - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - while true - - _save9 = self.pos - while true # sequence - _save10 = self.pos - _tmp = match_string("]") - _tmp = _tmp ? nil : true - self.pos = _save10 - unless _tmp - self.pos = _save9 - break - end - _tmp = apply(:_Inline) - l = @result - unless _tmp - self.pos = _save9 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save9 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string("]") - unless _tmp - self.pos = _save - break - end - @result = begin; a.join.gsub(/\s+/, ' ') ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Label unless _tmp - return _tmp - end - - # RefSrc = < Nonspacechar+ > { text } - def _RefSrc - - _save = self.pos - while true # sequence - _text_start = self.pos - _save1 = self.pos - _tmp = apply(:_Nonspacechar) - if _tmp - while true - _tmp = apply(:_Nonspacechar) - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RefSrc unless _tmp - return _tmp - end - - # RefTitle = (RefTitleSingle | RefTitleDouble | RefTitleParens | EmptyTitle) - def _RefTitle - - _save = self.pos - while true # choice - _tmp = apply(:_RefTitleSingle) - break if _tmp - self.pos = _save - _tmp = apply(:_RefTitleDouble) - break if _tmp - self.pos = _save - _tmp = apply(:_RefTitleParens) - break if _tmp - self.pos = _save - _tmp = apply(:_EmptyTitle) - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_RefTitle unless _tmp - return _tmp - end - - # EmptyTitle = "" - def _EmptyTitle - _tmp = match_string("") - set_failed_rule :_EmptyTitle unless _tmp - return _tmp - end - - # RefTitleSingle = Spnl "'" < (!("'" @Sp @Newline | @Newline) .)* > "'" { text } - def _RefTitleSingle - - _save = self.pos - while true # sequence - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("'") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - - _save4 = self.pos - while true # choice - - _save5 = self.pos - while true # sequence - _tmp = match_string("'") - unless _tmp - self.pos = _save5 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save5 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save4 - _tmp = _Newline() - break if _tmp - self.pos = _save4 - break - end # end choice - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("'") - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RefTitleSingle unless _tmp - return _tmp - end - - # RefTitleDouble = Spnl "\"" < (!("\"" @Sp @Newline | @Newline) .)* > "\"" { text } - def _RefTitleDouble - - _save = self.pos - while true # sequence - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("\"") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - - _save4 = self.pos - while true # choice - - _save5 = self.pos - while true # sequence - _tmp = match_string("\"") - unless _tmp - self.pos = _save5 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save5 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save4 - _tmp = _Newline() - break if _tmp - self.pos = _save4 - break - end # end choice - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("\"") - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RefTitleDouble unless _tmp - return _tmp - end - - # RefTitleParens = Spnl "(" < (!(")" @Sp @Newline | @Newline) .)* > ")" { text } - def _RefTitleParens - - _save = self.pos - while true # sequence - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string("(") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - - _save4 = self.pos - while true # choice - - _save5 = self.pos - while true # sequence - _tmp = match_string(")") - unless _tmp - self.pos = _save5 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save5 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save4 - _tmp = _Newline() - break if _tmp - self.pos = _save4 - break - end # end choice - - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(")") - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RefTitleParens unless _tmp - return _tmp - end - - # References = (Reference | SkipBlock)* - def _References - while true - - _save1 = self.pos - while true # choice - _tmp = apply(:_Reference) - break if _tmp - self.pos = _save1 - _tmp = apply(:_SkipBlock) - break if _tmp - self.pos = _save1 - break - end # end choice - - break unless _tmp - end - _tmp = true - set_failed_rule :_References unless _tmp - return _tmp - end - - # Ticks1 = "`" !"`" - def _Ticks1 - - _save = self.pos - while true # sequence - _tmp = match_string("`") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Ticks1 unless _tmp - return _tmp - end - - # Ticks2 = "``" !"`" - def _Ticks2 - - _save = self.pos - while true # sequence - _tmp = match_string("``") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Ticks2 unless _tmp - return _tmp - end - - # Ticks3 = "```" !"`" - def _Ticks3 - - _save = self.pos - while true # sequence - _tmp = match_string("```") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Ticks3 unless _tmp - return _tmp - end - - # Ticks4 = "````" !"`" - def _Ticks4 - - _save = self.pos - while true # sequence - _tmp = match_string("````") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Ticks4 unless _tmp - return _tmp - end - - # Ticks5 = "`````" !"`" - def _Ticks5 - - _save = self.pos - while true # sequence - _tmp = match_string("`````") - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Ticks5 unless _tmp - return _tmp - end - - # Code = (Ticks1 @Sp < ((!"`" Nonspacechar)+ | !Ticks1 /`+/ | !(@Sp Ticks1) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks1 | Ticks2 @Sp < ((!"`" Nonspacechar)+ | !Ticks2 /`+/ | !(@Sp Ticks2) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks2 | Ticks3 @Sp < ((!"`" Nonspacechar)+ | !Ticks3 /`+/ | !(@Sp Ticks3) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks3 | Ticks4 @Sp < ((!"`" Nonspacechar)+ | !Ticks4 /`+/ | !(@Sp Ticks4) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks4 | Ticks5 @Sp < ((!"`" Nonspacechar)+ | !Ticks5 /`+/ | !(@Sp Ticks5) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks5) { "<code>#{text}</code>" } - def _Code - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _tmp = apply(:_Ticks1) - unless _tmp - self.pos = _save2 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save2 - break - end - _text_start = self.pos - _save3 = self.pos - - _save4 = self.pos - while true # choice - _save5 = self.pos - - _save6 = self.pos - while true # sequence - _save7 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save6 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save6 - end - break - end # end sequence - - if _tmp - while true - - _save8 = self.pos - while true # sequence - _save9 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save8 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save5 - end - break if _tmp - self.pos = _save4 - - _save10 = self.pos - while true # sequence - _save11 = self.pos - _tmp = apply(:_Ticks1) - _tmp = _tmp ? nil : true - self.pos = _save11 - unless _tmp - self.pos = _save10 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break if _tmp - self.pos = _save4 - - _save12 = self.pos - while true # sequence - _save13 = self.pos - - _save14 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save14 - break - end - _tmp = apply(:_Ticks1) - unless _tmp - self.pos = _save14 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save13 - unless _tmp - self.pos = _save12 - break - end - - _save15 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save15 - - _save16 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save16 - break - end - _save17 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save17 - unless _tmp - self.pos = _save16 - end - break - end # end sequence - - break if _tmp - self.pos = _save15 - break - end # end choice - - unless _tmp - self.pos = _save12 - end - break - end # end sequence - - break if _tmp - self.pos = _save4 - break - end # end choice - - if _tmp - while true - - _save18 = self.pos - while true # choice - _save19 = self.pos - - _save20 = self.pos - while true # sequence - _save21 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save21 - unless _tmp - self.pos = _save20 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save20 - end - break - end # end sequence - - if _tmp - while true - - _save22 = self.pos - while true # sequence - _save23 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save23 - unless _tmp - self.pos = _save22 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save22 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save19 - end - break if _tmp - self.pos = _save18 - - _save24 = self.pos - while true # sequence - _save25 = self.pos - _tmp = apply(:_Ticks1) - _tmp = _tmp ? nil : true - self.pos = _save25 - unless _tmp - self.pos = _save24 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save24 - end - break - end # end sequence - - break if _tmp - self.pos = _save18 - - _save26 = self.pos - while true # sequence - _save27 = self.pos - - _save28 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save28 - break - end - _tmp = apply(:_Ticks1) - unless _tmp - self.pos = _save28 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save27 - unless _tmp - self.pos = _save26 - break - end - - _save29 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save29 - - _save30 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save30 - break - end - _save31 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save31 - unless _tmp - self.pos = _save30 - end - break - end # end sequence - - break if _tmp - self.pos = _save29 - break - end # end choice - - unless _tmp - self.pos = _save26 - end - break - end # end sequence - - break if _tmp - self.pos = _save18 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save3 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save2 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save2 - break - end - _tmp = apply(:_Ticks1) - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save32 = self.pos - while true # sequence - _tmp = apply(:_Ticks2) - unless _tmp - self.pos = _save32 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save32 - break - end - _text_start = self.pos - _save33 = self.pos - - _save34 = self.pos - while true # choice - _save35 = self.pos - - _save36 = self.pos - while true # sequence - _save37 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save37 - unless _tmp - self.pos = _save36 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save36 - end - break - end # end sequence - - if _tmp - while true - - _save38 = self.pos - while true # sequence - _save39 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save39 - unless _tmp - self.pos = _save38 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save38 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save35 - end - break if _tmp - self.pos = _save34 - - _save40 = self.pos - while true # sequence - _save41 = self.pos - _tmp = apply(:_Ticks2) - _tmp = _tmp ? nil : true - self.pos = _save41 - unless _tmp - self.pos = _save40 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save40 - end - break - end # end sequence - - break if _tmp - self.pos = _save34 - - _save42 = self.pos - while true # sequence - _save43 = self.pos - - _save44 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save44 - break - end - _tmp = apply(:_Ticks2) - unless _tmp - self.pos = _save44 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save43 - unless _tmp - self.pos = _save42 - break - end - - _save45 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save45 - - _save46 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save46 - break - end - _save47 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save47 - unless _tmp - self.pos = _save46 - end - break - end # end sequence - - break if _tmp - self.pos = _save45 - break - end # end choice - - unless _tmp - self.pos = _save42 - end - break - end # end sequence - - break if _tmp - self.pos = _save34 - break - end # end choice - - if _tmp - while true - - _save48 = self.pos - while true # choice - _save49 = self.pos - - _save50 = self.pos - while true # sequence - _save51 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save51 - unless _tmp - self.pos = _save50 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save50 - end - break - end # end sequence - - if _tmp - while true - - _save52 = self.pos - while true # sequence - _save53 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save53 - unless _tmp - self.pos = _save52 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save52 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save49 - end - break if _tmp - self.pos = _save48 - - _save54 = self.pos - while true # sequence - _save55 = self.pos - _tmp = apply(:_Ticks2) - _tmp = _tmp ? nil : true - self.pos = _save55 - unless _tmp - self.pos = _save54 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save54 - end - break - end # end sequence - - break if _tmp - self.pos = _save48 - - _save56 = self.pos - while true # sequence - _save57 = self.pos - - _save58 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save58 - break - end - _tmp = apply(:_Ticks2) - unless _tmp - self.pos = _save58 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save57 - unless _tmp - self.pos = _save56 - break - end - - _save59 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save59 - - _save60 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save60 - break - end - _save61 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save61 - unless _tmp - self.pos = _save60 - end - break - end # end sequence - - break if _tmp - self.pos = _save59 - break - end # end choice - - unless _tmp - self.pos = _save56 - end - break - end # end sequence - - break if _tmp - self.pos = _save48 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save33 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save32 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save32 - break - end - _tmp = apply(:_Ticks2) - unless _tmp - self.pos = _save32 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save62 = self.pos - while true # sequence - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save62 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save62 - break - end - _text_start = self.pos - _save63 = self.pos - - _save64 = self.pos - while true # choice - _save65 = self.pos - - _save66 = self.pos - while true # sequence - _save67 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save67 - unless _tmp - self.pos = _save66 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save66 - end - break - end # end sequence - - if _tmp - while true - - _save68 = self.pos - while true # sequence - _save69 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save69 - unless _tmp - self.pos = _save68 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save68 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save65 - end - break if _tmp - self.pos = _save64 - - _save70 = self.pos - while true # sequence - _save71 = self.pos - _tmp = apply(:_Ticks3) - _tmp = _tmp ? nil : true - self.pos = _save71 - unless _tmp - self.pos = _save70 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save70 - end - break - end # end sequence - - break if _tmp - self.pos = _save64 - - _save72 = self.pos - while true # sequence - _save73 = self.pos - - _save74 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save74 - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save74 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save73 - unless _tmp - self.pos = _save72 - break - end - - _save75 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save75 - - _save76 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save76 - break - end - _save77 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save77 - unless _tmp - self.pos = _save76 - end - break - end # end sequence - - break if _tmp - self.pos = _save75 - break - end # end choice - - unless _tmp - self.pos = _save72 - end - break - end # end sequence - - break if _tmp - self.pos = _save64 - break - end # end choice - - if _tmp - while true - - _save78 = self.pos - while true # choice - _save79 = self.pos - - _save80 = self.pos - while true # sequence - _save81 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save81 - unless _tmp - self.pos = _save80 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save80 - end - break - end # end sequence - - if _tmp - while true - - _save82 = self.pos - while true # sequence - _save83 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save83 - unless _tmp - self.pos = _save82 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save82 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save79 - end - break if _tmp - self.pos = _save78 - - _save84 = self.pos - while true # sequence - _save85 = self.pos - _tmp = apply(:_Ticks3) - _tmp = _tmp ? nil : true - self.pos = _save85 - unless _tmp - self.pos = _save84 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save84 - end - break - end # end sequence - - break if _tmp - self.pos = _save78 - - _save86 = self.pos - while true # sequence - _save87 = self.pos - - _save88 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save88 - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save88 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save87 - unless _tmp - self.pos = _save86 - break - end - - _save89 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save89 - - _save90 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save90 - break - end - _save91 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save91 - unless _tmp - self.pos = _save90 - end - break - end # end sequence - - break if _tmp - self.pos = _save89 - break - end # end choice - - unless _tmp - self.pos = _save86 - end - break - end # end sequence - - break if _tmp - self.pos = _save78 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save63 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save62 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save62 - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save62 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save92 = self.pos - while true # sequence - _tmp = apply(:_Ticks4) - unless _tmp - self.pos = _save92 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save92 - break - end - _text_start = self.pos - _save93 = self.pos - - _save94 = self.pos - while true # choice - _save95 = self.pos - - _save96 = self.pos - while true # sequence - _save97 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save97 - unless _tmp - self.pos = _save96 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save96 - end - break - end # end sequence - - if _tmp - while true - - _save98 = self.pos - while true # sequence - _save99 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save99 - unless _tmp - self.pos = _save98 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save98 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save95 - end - break if _tmp - self.pos = _save94 - - _save100 = self.pos - while true # sequence - _save101 = self.pos - _tmp = apply(:_Ticks4) - _tmp = _tmp ? nil : true - self.pos = _save101 - unless _tmp - self.pos = _save100 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save100 - end - break - end # end sequence - - break if _tmp - self.pos = _save94 - - _save102 = self.pos - while true # sequence - _save103 = self.pos - - _save104 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save104 - break - end - _tmp = apply(:_Ticks4) - unless _tmp - self.pos = _save104 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save103 - unless _tmp - self.pos = _save102 - break - end - - _save105 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save105 - - _save106 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save106 - break - end - _save107 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save107 - unless _tmp - self.pos = _save106 - end - break - end # end sequence - - break if _tmp - self.pos = _save105 - break - end # end choice - - unless _tmp - self.pos = _save102 - end - break - end # end sequence - - break if _tmp - self.pos = _save94 - break - end # end choice - - if _tmp - while true - - _save108 = self.pos - while true # choice - _save109 = self.pos - - _save110 = self.pos - while true # sequence - _save111 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save111 - unless _tmp - self.pos = _save110 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save110 - end - break - end # end sequence - - if _tmp - while true - - _save112 = self.pos - while true # sequence - _save113 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save113 - unless _tmp - self.pos = _save112 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save112 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save109 - end - break if _tmp - self.pos = _save108 - - _save114 = self.pos - while true # sequence - _save115 = self.pos - _tmp = apply(:_Ticks4) - _tmp = _tmp ? nil : true - self.pos = _save115 - unless _tmp - self.pos = _save114 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save114 - end - break - end # end sequence - - break if _tmp - self.pos = _save108 - - _save116 = self.pos - while true # sequence - _save117 = self.pos - - _save118 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save118 - break - end - _tmp = apply(:_Ticks4) - unless _tmp - self.pos = _save118 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save117 - unless _tmp - self.pos = _save116 - break - end - - _save119 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save119 - - _save120 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save120 - break - end - _save121 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save121 - unless _tmp - self.pos = _save120 - end - break - end # end sequence - - break if _tmp - self.pos = _save119 - break - end # end choice - - unless _tmp - self.pos = _save116 - end - break - end # end sequence - - break if _tmp - self.pos = _save108 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save93 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save92 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save92 - break - end - _tmp = apply(:_Ticks4) - unless _tmp - self.pos = _save92 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - - _save122 = self.pos - while true # sequence - _tmp = apply(:_Ticks5) - unless _tmp - self.pos = _save122 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save122 - break - end - _text_start = self.pos - _save123 = self.pos - - _save124 = self.pos - while true # choice - _save125 = self.pos - - _save126 = self.pos - while true # sequence - _save127 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save127 - unless _tmp - self.pos = _save126 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save126 - end - break - end # end sequence - - if _tmp - while true - - _save128 = self.pos - while true # sequence - _save129 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save129 - unless _tmp - self.pos = _save128 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save128 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save125 - end - break if _tmp - self.pos = _save124 - - _save130 = self.pos - while true # sequence - _save131 = self.pos - _tmp = apply(:_Ticks5) - _tmp = _tmp ? nil : true - self.pos = _save131 - unless _tmp - self.pos = _save130 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save130 - end - break - end # end sequence - - break if _tmp - self.pos = _save124 - - _save132 = self.pos - while true # sequence - _save133 = self.pos - - _save134 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save134 - break - end - _tmp = apply(:_Ticks5) - unless _tmp - self.pos = _save134 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save133 - unless _tmp - self.pos = _save132 - break - end - - _save135 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save135 - - _save136 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save136 - break - end - _save137 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save137 - unless _tmp - self.pos = _save136 - end - break - end # end sequence - - break if _tmp - self.pos = _save135 - break - end # end choice - - unless _tmp - self.pos = _save132 - end - break - end # end sequence - - break if _tmp - self.pos = _save124 - break - end # end choice - - if _tmp - while true - - _save138 = self.pos - while true # choice - _save139 = self.pos - - _save140 = self.pos - while true # sequence - _save141 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save141 - unless _tmp - self.pos = _save140 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save140 - end - break - end # end sequence - - if _tmp - while true - - _save142 = self.pos - while true # sequence - _save143 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save143 - unless _tmp - self.pos = _save142 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save142 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save139 - end - break if _tmp - self.pos = _save138 - - _save144 = self.pos - while true # sequence - _save145 = self.pos - _tmp = apply(:_Ticks5) - _tmp = _tmp ? nil : true - self.pos = _save145 - unless _tmp - self.pos = _save144 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save144 - end - break - end # end sequence - - break if _tmp - self.pos = _save138 - - _save146 = self.pos - while true # sequence - _save147 = self.pos - - _save148 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save148 - break - end - _tmp = apply(:_Ticks5) - unless _tmp - self.pos = _save148 - end - break - end # end sequence - - _tmp = _tmp ? nil : true - self.pos = _save147 - unless _tmp - self.pos = _save146 - break - end - - _save149 = self.pos - while true # choice - _tmp = _Spacechar() - break if _tmp - self.pos = _save149 - - _save150 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save150 - break - end - _save151 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save151 - unless _tmp - self.pos = _save150 - end - break - end # end sequence - - break if _tmp - self.pos = _save149 - break - end # end choice - - unless _tmp - self.pos = _save146 - end - break - end # end sequence - - break if _tmp - self.pos = _save138 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save123 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save122 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save122 - break - end - _tmp = apply(:_Ticks5) - unless _tmp - self.pos = _save122 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - @result = begin; "<code>#{text}</code>" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Code unless _tmp - return _tmp - end - - # RawHtml = < (HtmlComment | HtmlBlockScript | HtmlTag) > { if html? then text else '' end } - def _RawHtml - - _save = self.pos - while true # sequence - _text_start = self.pos - - _save1 = self.pos - while true # choice - _tmp = apply(:_HtmlComment) - break if _tmp - self.pos = _save1 - _tmp = apply(:_HtmlBlockScript) - break if _tmp - self.pos = _save1 - _tmp = apply(:_HtmlTag) - break if _tmp - self.pos = _save1 - break - end # end choice - - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; if html? then text else '' end ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RawHtml unless _tmp - return _tmp - end - - # BlankLine = @Sp @Newline { "\n" } - def _BlankLine - - _save = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; "\n" ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_BlankLine unless _tmp - return _tmp - end - - # Quoted = ("\"" (!"\"" .)* "\"" | "'" (!"'" .)* "'") - def _Quoted - - _save = self.pos - while true # choice - - _save1 = self.pos - while true # sequence - _tmp = match_string("\"") - unless _tmp - self.pos = _save1 - break - end - while true - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("\"") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save1 - break - end - _tmp = match_string("\"") - unless _tmp - self.pos = _save1 - end - break - end # end sequence - - break if _tmp - self.pos = _save - - _save5 = self.pos - while true # sequence - _tmp = match_string("'") - unless _tmp - self.pos = _save5 - break - end - while true - - _save7 = self.pos - while true # sequence - _save8 = self.pos - _tmp = match_string("'") - _tmp = _tmp ? nil : true - self.pos = _save8 - unless _tmp - self.pos = _save7 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save5 - break - end - _tmp = match_string("'") - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_Quoted unless _tmp - return _tmp - end - - # HtmlAttribute = (AlphanumericAscii | "-")+ Spnl ("=" Spnl (Quoted | (!">" Nonspacechar)+))? Spnl - def _HtmlAttribute - - _save = self.pos - while true # sequence - _save1 = self.pos - - _save2 = self.pos - while true # choice - _tmp = apply(:_AlphanumericAscii) - break if _tmp - self.pos = _save2 - _tmp = match_string("-") - break if _tmp - self.pos = _save2 - break - end # end choice - - if _tmp - while true - - _save3 = self.pos - while true # choice - _tmp = apply(:_AlphanumericAscii) - break if _tmp - self.pos = _save3 - _tmp = match_string("-") - break if _tmp - self.pos = _save3 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _save4 = self.pos - - _save5 = self.pos - while true # sequence - _tmp = match_string("=") - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save5 - break - end - - _save6 = self.pos - while true # choice - _tmp = apply(:_Quoted) - break if _tmp - self.pos = _save6 - _save7 = self.pos - - _save8 = self.pos - while true # sequence - _save9 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save8 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - if _tmp - while true - - _save10 = self.pos - while true # sequence - _save11 = self.pos - _tmp = match_string(">") - _tmp = _tmp ? nil : true - self.pos = _save11 - unless _tmp - self.pos = _save10 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save10 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save7 - end - break if _tmp - self.pos = _save6 - break - end # end choice - - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - unless _tmp - _tmp = true - self.pos = _save4 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlAttribute unless _tmp - return _tmp - end - - # HtmlComment = "<!--" (!"-->" .)* "-->" - def _HtmlComment - - _save = self.pos - while true # sequence - _tmp = match_string("<!--") - unless _tmp - self.pos = _save - break - end - while true - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = match_string("-->") - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _tmp = match_string("-->") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlComment unless _tmp - return _tmp - end - - # HtmlTag = "<" Spnl "/"? AlphanumericAscii+ Spnl HtmlAttribute* "/"? Spnl ">" - def _HtmlTag - - _save = self.pos - while true # sequence - _tmp = match_string("<") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = match_string("/") - unless _tmp - _tmp = true - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = apply(:_AlphanumericAscii) - if _tmp - while true - _tmp = apply(:_AlphanumericAscii) - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - while true - _tmp = apply(:_HtmlAttribute) - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - _save4 = self.pos - _tmp = match_string("/") - unless _tmp - _tmp = true - self.pos = _save4 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _tmp = match_string(">") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HtmlTag unless _tmp - return _tmp - end - - # Eof = !. - def _Eof - _save = self.pos - _tmp = get_byte - _tmp = _tmp ? nil : true - self.pos = _save - set_failed_rule :_Eof unless _tmp - return _tmp - end - - # Nonspacechar = !@Spacechar !@Newline . - def _Nonspacechar - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = _Spacechar() - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save2 - unless _tmp - self.pos = _save - break - end - _tmp = get_byte - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Nonspacechar unless _tmp - return _tmp - end - - # Sp = @Spacechar* - def _Sp - while true - _tmp = _Spacechar() - break unless _tmp - end - _tmp = true - set_failed_rule :_Sp unless _tmp - return _tmp - end - - # Spnl = @Sp (@Newline @Sp)? - def _Spnl - - _save = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _tmp = _Newline() - unless _tmp - self.pos = _save2 - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - unless _tmp - _tmp = true - self.pos = _save1 - end - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Spnl unless _tmp - return _tmp - end - - # SpecialChar = (/[~*_`&\[\]()<!#\\'"]/ | @ExtendedSpecialChar) - def _SpecialChar - - _save = self.pos - while true # choice - _tmp = scan(/\G(?-mix:[~*_`&\[\]()<!#\\'"])/) - break if _tmp - self.pos = _save - _tmp = _ExtendedSpecialChar() - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_SpecialChar unless _tmp - return _tmp - end - - # NormalChar = !(@SpecialChar | @Spacechar | @Newline) . - def _NormalChar - - _save = self.pos - while true # sequence - _save1 = self.pos - - _save2 = self.pos - while true # choice - _tmp = _SpecialChar() - break if _tmp - self.pos = _save2 - _tmp = _Spacechar() - break if _tmp - self.pos = _save2 - _tmp = _Newline() - break if _tmp - self.pos = _save2 - break - end # end choice - - _tmp = _tmp ? nil : true - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = get_byte - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_NormalChar unless _tmp - return _tmp - end - - # Digit = [0-9] - def _Digit - _save = self.pos - _tmp = get_byte - if _tmp - unless _tmp >= 48 and _tmp <= 57 - self.pos = _save - _tmp = nil - end - end - set_failed_rule :_Digit unless _tmp - return _tmp - end - - # Alphanumeric = %literals.Alphanumeric - def _Alphanumeric - _tmp = @_grammar_literals.external_invoke(self, :_Alphanumeric) - set_failed_rule :_Alphanumeric unless _tmp - return _tmp - end - - # AlphanumericAscii = %literals.AlphanumericAscii - def _AlphanumericAscii - _tmp = @_grammar_literals.external_invoke(self, :_AlphanumericAscii) - set_failed_rule :_AlphanumericAscii unless _tmp - return _tmp - end - - # BOM = %literals.BOM - def _BOM - _tmp = @_grammar_literals.external_invoke(self, :_BOM) - set_failed_rule :_BOM unless _tmp - return _tmp - end - - # Newline = %literals.Newline - def _Newline - _tmp = @_grammar_literals.external_invoke(self, :_Newline) - set_failed_rule :_Newline unless _tmp - return _tmp - end - - # Spacechar = %literals.Spacechar - def _Spacechar - _tmp = @_grammar_literals.external_invoke(self, :_Spacechar) - set_failed_rule :_Spacechar unless _tmp - return _tmp - end - - # HexEntity = /&#x/i < /[0-9a-fA-F]+/ > ";" { [text.to_i(16)].pack 'U' } - def _HexEntity - - _save = self.pos - while true # sequence - _tmp = scan(/\G(?i-mx:&#x)/) - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _tmp = scan(/\G(?-mix:[0-9a-fA-F]+)/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(";") - unless _tmp - self.pos = _save - break - end - @result = begin; [text.to_i(16)].pack 'U' ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_HexEntity unless _tmp - return _tmp - end - - # DecEntity = "&#" < /[0-9]+/ > ";" { [text.to_i].pack 'U' } - def _DecEntity - - _save = self.pos - while true # sequence - _tmp = match_string("&#") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _tmp = scan(/\G(?-mix:[0-9]+)/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(";") - unless _tmp - self.pos = _save - break - end - @result = begin; [text.to_i].pack 'U' ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_DecEntity unless _tmp - return _tmp - end - - # CharEntity = "&" < /[A-Za-z0-9]+/ > ";" { if entity = HTML_ENTITIES[text] then entity.pack 'U*' else "&#{text};" end } - def _CharEntity - - _save = self.pos - while true # sequence - _tmp = match_string("&") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _tmp = scan(/\G(?-mix:[A-Za-z0-9]+)/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string(";") - unless _tmp - self.pos = _save - break - end - @result = begin; if entity = HTML_ENTITIES[text] then - entity.pack 'U*' - else - "&#{text};" - end - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_CharEntity unless _tmp - return _tmp - end - - # NonindentSpace = / {0,3}/ - def _NonindentSpace - _tmp = scan(/\G(?-mix: {0,3})/) - set_failed_rule :_NonindentSpace unless _tmp - return _tmp - end - - # Indent = /\t| / - def _Indent - _tmp = scan(/\G(?-mix:\t| )/) - set_failed_rule :_Indent unless _tmp - return _tmp - end - - # IndentedLine = Indent Line - def _IndentedLine - - _save = self.pos - while true # sequence - _tmp = apply(:_Indent) - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Line) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_IndentedLine unless _tmp - return _tmp - end - - # OptionallyIndentedLine = Indent? Line - def _OptionallyIndentedLine - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = apply(:_Indent) - unless _tmp - _tmp = true - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Line) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_OptionallyIndentedLine unless _tmp - return _tmp - end - - # StartList = &. { [] } - def _StartList - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = get_byte - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - @result = begin; [] ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_StartList unless _tmp - return _tmp - end - - # Line = @RawLine:a { a } - def _Line - - _save = self.pos - while true # sequence - _tmp = _RawLine() - a = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Line unless _tmp - return _tmp - end - - # RawLine = (< /[^\r\n]*/ @Newline > | < .+ > @Eof) { text } - def _RawLine - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - _text_start = self.pos - - _save2 = self.pos - while true # sequence - _tmp = scan(/\G(?-mix:[^\r\n]*)/) - unless _tmp - self.pos = _save2 - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - text = get_text(_text_start) - end - break if _tmp - self.pos = _save1 - - _save3 = self.pos - while true # sequence - _text_start = self.pos - _save4 = self.pos - _tmp = get_byte - if _tmp - while true - _tmp = get_byte - break unless _tmp - end - _tmp = true - else - self.pos = _save4 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save3 - break - end - _tmp = _Eof() - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RawLine unless _tmp - return _tmp - end - - # SkipBlock = (HtmlBlock | (!"#" !SetextBottom1 !SetextBottom2 !@BlankLine @RawLine)+ @BlankLine* | @BlankLine+ | @RawLine) - def _SkipBlock - - _save = self.pos - while true # choice - _tmp = apply(:_HtmlBlock) - break if _tmp - self.pos = _save - - _save1 = self.pos - while true # sequence - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("#") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _save5 = self.pos - _tmp = apply(:_SetextBottom1) - _tmp = _tmp ? nil : true - self.pos = _save5 - unless _tmp - self.pos = _save3 - break - end - _save6 = self.pos - _tmp = apply(:_SetextBottom2) - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save3 - break - end - _save7 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save3 - break - end - _tmp = _RawLine() - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save8 = self.pos - while true # sequence - _save9 = self.pos - _tmp = match_string("#") - _tmp = _tmp ? nil : true - self.pos = _save9 - unless _tmp - self.pos = _save8 - break - end - _save10 = self.pos - _tmp = apply(:_SetextBottom1) - _tmp = _tmp ? nil : true - self.pos = _save10 - unless _tmp - self.pos = _save8 - break - end - _save11 = self.pos - _tmp = apply(:_SetextBottom2) - _tmp = _tmp ? nil : true - self.pos = _save11 - unless _tmp - self.pos = _save8 - break - end - _save12 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save12 - unless _tmp - self.pos = _save8 - break - end - _tmp = _RawLine() - unless _tmp - self.pos = _save8 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save1 - break - end - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save1 - end - break - end # end sequence - - break if _tmp - self.pos = _save - _save14 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save14 - end - break if _tmp - self.pos = _save - _tmp = _RawLine() - break if _tmp - self.pos = _save - break - end # end choice - - set_failed_rule :_SkipBlock unless _tmp - return _tmp - end - - # ExtendedSpecialChar = &{ notes? } "^" - def _ExtendedSpecialChar - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; notes? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = match_string("^") - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_ExtendedSpecialChar unless _tmp - return _tmp - end - - # NoteReference = &{ notes? } RawNoteReference:ref { note_for ref } - def _NoteReference - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; notes? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_RawNoteReference) - ref = @result - unless _tmp - self.pos = _save - break - end - @result = begin; note_for ref ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_NoteReference unless _tmp - return _tmp - end - - # RawNoteReference = "[^" < (!@Newline !"]" .)+ > "]" { text } - def _RawNoteReference - - _save = self.pos - while true # sequence - _tmp = match_string("[^") - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _save4 = self.pos - _tmp = match_string("]") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save2 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = _Newline() - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _save7 = self.pos - _tmp = match_string("]") - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save5 - break - end - _tmp = get_byte - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("]") - unless _tmp - self.pos = _save - break - end - @result = begin; text ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RawNoteReference unless _tmp - return _tmp - end - - # Note = &{ notes? } @NonindentSpace RawNoteReference:ref ":" @Sp @StartList:a RawNoteBlock:i { a.concat i } (&Indent RawNoteBlock:i { a.concat i })* { @footnotes[ref] = paragraph a nil } - def _Note - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; notes? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_RawNoteReference) - ref = @result - unless _tmp - self.pos = _save - break - end - _tmp = match_string(":") - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_RawNoteBlock) - i = @result - unless _tmp - self.pos = _save - break - end - @result = begin; a.concat i ; end - _tmp = true - unless _tmp - self.pos = _save - break - end - while true - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = apply(:_Indent) - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_RawNoteBlock) - i = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a.concat i ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; @footnotes[ref] = paragraph a - - nil - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Note unless _tmp - return _tmp - end - - # InlineNote = &{ notes? } "^[" @StartList:a (!"]" Inline:l { a << l })+ "]" { ref = [:inline, @note_order.length] @footnotes[ref] = paragraph a note_for ref } - def _InlineNote - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; notes? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = match_string("^[") - unless _tmp - self.pos = _save - break - end - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _save4 = self.pos - _tmp = match_string("]") - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_Inline) - l = @result - unless _tmp - self.pos = _save3 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - if _tmp - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = match_string("]") - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_Inline) - l = @result - unless _tmp - self.pos = _save5 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = match_string("]") - unless _tmp - self.pos = _save - break - end - @result = begin; ref = [:inline, @note_order.length] - @footnotes[ref] = paragraph a - - note_for ref - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_InlineNote unless _tmp - return _tmp - end - - # Notes = (Note | SkipBlock)* - def _Notes - while true - - _save1 = self.pos - while true # choice - _tmp = apply(:_Note) - break if _tmp - self.pos = _save1 - _tmp = apply(:_SkipBlock) - break if _tmp - self.pos = _save1 - break - end # end choice - - break unless _tmp - end - _tmp = true - set_failed_rule :_Notes unless _tmp - return _tmp - end - - # RawNoteBlock = @StartList:a (!@BlankLine !RawNoteReference OptionallyIndentedLine:l { a << l })+ < @BlankLine* > { a << text } { a } - def _RawNoteBlock - - _save = self.pos - while true # sequence - _tmp = _StartList() - a = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - - _save2 = self.pos - while true # sequence - _save3 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save3 - unless _tmp - self.pos = _save2 - break - end - _save4 = self.pos - _tmp = apply(:_RawNoteReference) - _tmp = _tmp ? nil : true - self.pos = _save4 - unless _tmp - self.pos = _save2 - break - end - _tmp = apply(:_OptionallyIndentedLine) - l = @result - unless _tmp - self.pos = _save2 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - if _tmp - while true - - _save5 = self.pos - while true # sequence - _save6 = self.pos - _tmp = _BlankLine() - _tmp = _tmp ? nil : true - self.pos = _save6 - unless _tmp - self.pos = _save5 - break - end - _save7 = self.pos - _tmp = apply(:_RawNoteReference) - _tmp = _tmp ? nil : true - self.pos = _save7 - unless _tmp - self.pos = _save5 - break - end - _tmp = apply(:_OptionallyIndentedLine) - l = @result - unless _tmp - self.pos = _save5 - break - end - @result = begin; a << l ; end - _tmp = true - unless _tmp - self.pos = _save5 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; a << text ; end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_RawNoteBlock unless _tmp - return _tmp - end - - # CodeFence = &{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ((!"`" Nonspacechar)+ | !Ticks3 /`+/ | Spacechar | @Newline)+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim } - def _CodeFence - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; github? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - - _save3 = self.pos - while true # sequence - _tmp = _Sp() - unless _tmp - self.pos = _save3 - break - end - _tmp = apply(:_StrChunk) - format = @result - unless _tmp - self.pos = _save3 - end - break - end # end sequence - - unless _tmp - _tmp = true - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Spnl) - unless _tmp - self.pos = _save - break - end - _text_start = self.pos - _save4 = self.pos - - _save5 = self.pos - while true # choice - _save6 = self.pos - - _save7 = self.pos - while true # sequence - _save8 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save8 - unless _tmp - self.pos = _save7 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save7 - end - break - end # end sequence - - if _tmp - while true - - _save9 = self.pos - while true # sequence - _save10 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save10 - unless _tmp - self.pos = _save9 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save9 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save6 - end - break if _tmp - self.pos = _save5 - - _save11 = self.pos - while true # sequence - _save12 = self.pos - _tmp = apply(:_Ticks3) - _tmp = _tmp ? nil : true - self.pos = _save12 - unless _tmp - self.pos = _save11 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save11 - end - break - end # end sequence - - break if _tmp - self.pos = _save5 - _tmp = apply(:_Spacechar) - break if _tmp - self.pos = _save5 - _tmp = _Newline() - break if _tmp - self.pos = _save5 - break - end # end choice - - if _tmp - while true - - _save13 = self.pos - while true # choice - _save14 = self.pos - - _save15 = self.pos - while true # sequence - _save16 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save16 - unless _tmp - self.pos = _save15 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save15 - end - break - end # end sequence - - if _tmp - while true - - _save17 = self.pos - while true # sequence - _save18 = self.pos - _tmp = match_string("`") - _tmp = _tmp ? nil : true - self.pos = _save18 - unless _tmp - self.pos = _save17 - break - end - _tmp = apply(:_Nonspacechar) - unless _tmp - self.pos = _save17 - end - break - end # end sequence - - break unless _tmp - end - _tmp = true - else - self.pos = _save14 - end - break if _tmp - self.pos = _save13 - - _save19 = self.pos - while true # sequence - _save20 = self.pos - _tmp = apply(:_Ticks3) - _tmp = _tmp ? nil : true - self.pos = _save20 - unless _tmp - self.pos = _save19 - break - end - _tmp = scan(/\G(?-mix:`+)/) - unless _tmp - self.pos = _save19 - end - break - end # end sequence - - break if _tmp - self.pos = _save13 - _tmp = apply(:_Spacechar) - break if _tmp - self.pos = _save13 - _tmp = _Newline() - break if _tmp - self.pos = _save13 - break - end # end choice - - break unless _tmp - end - _tmp = true - else - self.pos = _save4 - end - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Ticks3) - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - while true - _tmp = _Newline() - break unless _tmp - end - _tmp = true - unless _tmp - self.pos = _save - break - end - @result = begin; verbatim = RDoc::Markup::Verbatim.new text - verbatim.format = format.intern if format.instance_of?(String) - verbatim - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_CodeFence unless _tmp - return _tmp - end - - # Table = &{ github? } TableHead:header TableLine:line TableRow+:body { table = RDoc::Markup::Table.new(header, line, body) } - def _Table - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; github? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_TableHead) - header = @result - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_TableLine) - line = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _ary = [] - _tmp = apply(:_TableRow) - if _tmp - _ary << @result - while true - _tmp = apply(:_TableRow) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save2 - end - body = @result - unless _tmp - self.pos = _save - break - end - @result = begin; table = RDoc::Markup::Table.new(header, line, body) ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_Table unless _tmp - return _tmp - end - - # TableHead = TableItem2+:items "|"? @Newline { items } - def _TableHead - - _save = self.pos - while true # sequence - _save1 = self.pos - _ary = [] - _tmp = apply(:_TableItem2) - if _tmp - _ary << @result - while true - _tmp = apply(:_TableItem2) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save1 - end - items = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _tmp = match_string("|") - unless _tmp - _tmp = true - self.pos = _save2 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; items ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableHead unless _tmp - return _tmp - end - - # TableRow = ((TableItem:item1 TableItem2*:items { [item1, *items] }):row | TableItem2+:row) "|"? @Newline { row } - def _TableRow - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _tmp = apply(:_TableItem) - item1 = @result - unless _tmp - self.pos = _save2 - break - end - _ary = [] - while true - _tmp = apply(:_TableItem2) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - items = @result - unless _tmp - self.pos = _save2 - break - end - @result = begin; [item1, *items] ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - row = @result - break if _tmp - self.pos = _save1 - _save4 = self.pos - _ary = [] - _tmp = apply(:_TableItem2) - if _tmp - _ary << @result - while true - _tmp = apply(:_TableItem2) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save4 - end - row = @result - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _save5 = self.pos - _tmp = match_string("|") - unless _tmp - _tmp = true - self.pos = _save5 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; row ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableRow unless _tmp - return _tmp - end - - # TableItem2 = "|" TableItem - def _TableItem2 - - _save = self.pos - while true # sequence - _tmp = match_string("|") - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_TableItem) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableItem2 unless _tmp - return _tmp - end - - # TableItem = < /(?:\\.|[^|\n])+/ > { text.strip.gsub(/\\(.)/, '\1') } - def _TableItem - - _save = self.pos - while true # sequence - _text_start = self.pos - _tmp = scan(/\G(?-mix:(?:\\.|[^|\n])+)/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - @result = begin; text.strip.gsub(/\\(.)/, '\1') ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableItem unless _tmp - return _tmp - end - - # TableLine = ((TableAlign:align1 TableAlign2*:aligns {[align1, *aligns] }):line | TableAlign2+:line) "|"? @Newline { line } - def _TableLine - - _save = self.pos - while true # sequence - - _save1 = self.pos - while true # choice - - _save2 = self.pos - while true # sequence - _tmp = apply(:_TableAlign) - align1 = @result - unless _tmp - self.pos = _save2 - break - end - _ary = [] - while true - _tmp = apply(:_TableAlign2) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - aligns = @result - unless _tmp - self.pos = _save2 - break - end - @result = begin; [align1, *aligns] ; end - _tmp = true - unless _tmp - self.pos = _save2 - end - break - end # end sequence - - line = @result - break if _tmp - self.pos = _save1 - _save4 = self.pos - _ary = [] - _tmp = apply(:_TableAlign2) - if _tmp - _ary << @result - while true - _tmp = apply(:_TableAlign2) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save4 - end - line = @result - break if _tmp - self.pos = _save1 - break - end # end choice - - unless _tmp - self.pos = _save - break - end - _save5 = self.pos - _tmp = match_string("|") - unless _tmp - _tmp = true - self.pos = _save5 - end - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; line ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableLine unless _tmp - return _tmp - end - - # TableAlign2 = "|" @Sp TableAlign - def _TableAlign2 - - _save = self.pos - while true # sequence - _tmp = match_string("|") - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_TableAlign) - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableAlign2 unless _tmp - return _tmp - end - - # TableAlign = < /:?-+:?/ > @Sp { text.start_with?(":") ? (text.end_with?(":") ? :center : :left) : (text.end_with?(":") ? :right : nil) } - def _TableAlign - - _save = self.pos - while true # sequence - _text_start = self.pos - _tmp = scan(/\G(?-mix::?-+:?)/) - if _tmp - text = get_text(_text_start) - end - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - @result = begin; - text.start_with?(":") ? - (text.end_with?(":") ? :center : :left) : - (text.end_with?(":") ? :right : nil) - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_TableAlign unless _tmp - return _tmp - end - - # DefinitionList = &{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten } - def _DefinitionList - - _save = self.pos - while true # sequence - _save1 = self.pos - _tmp = begin; definition_lists? ; end - self.pos = _save1 - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _ary = [] - _tmp = apply(:_DefinitionListItem) - if _tmp - _ary << @result - while true - _tmp = apply(:_DefinitionListItem) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save2 - end - list = @result - unless _tmp - self.pos = _save - break - end - @result = begin; RDoc::Markup::List.new :NOTE, *list.flatten ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_DefinitionList unless _tmp - return _tmp - end - - # DefinitionListItem = DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items } - def _DefinitionListItem - - _save = self.pos - while true # sequence - _save1 = self.pos - _ary = [] - _tmp = apply(:_DefinitionListLabel) - if _tmp - _ary << @result - while true - _tmp = apply(:_DefinitionListLabel) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save1 - end - label = @result - unless _tmp - self.pos = _save - break - end - _save2 = self.pos - _ary = [] - _tmp = apply(:_DefinitionListDefinition) - if _tmp - _ary << @result - while true - _tmp = apply(:_DefinitionListDefinition) - _ary << @result if _tmp - break unless _tmp - end - _tmp = true - @result = _ary - else - self.pos = _save2 - end - defns = @result - unless _tmp - self.pos = _save - break - end - @result = begin; list_items = [] - list_items << - RDoc::Markup::ListItem.new(label, defns.shift) - - list_items.concat defns.map { |defn| - RDoc::Markup::ListItem.new nil, defn - } unless list_items.empty? - - list_items - ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_DefinitionListItem unless _tmp - return _tmp - end - - # DefinitionListLabel = Inline:label @Sp @Newline { label } - def _DefinitionListLabel - - _save = self.pos - while true # sequence - _tmp = apply(:_Inline) - label = @result - unless _tmp - self.pos = _save - break - end - _tmp = _Sp() - unless _tmp - self.pos = _save - break - end - _tmp = _Newline() - unless _tmp - self.pos = _save - break - end - @result = begin; label ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_DefinitionListLabel unless _tmp - return _tmp - end - - # DefinitionListDefinition = @NonindentSpace ":" @Space Inlines:a @BlankLine+ { paragraph a } - def _DefinitionListDefinition - - _save = self.pos - while true # sequence - _tmp = _NonindentSpace() - unless _tmp - self.pos = _save - break - end - _tmp = match_string(":") - unless _tmp - self.pos = _save - break - end - _tmp = _Space() - unless _tmp - self.pos = _save - break - end - _tmp = apply(:_Inlines) - a = @result - unless _tmp - self.pos = _save - break - end - _save1 = self.pos - _tmp = _BlankLine() - if _tmp - while true - _tmp = _BlankLine() - break unless _tmp - end - _tmp = true - else - self.pos = _save1 - end - unless _tmp - self.pos = _save - break - end - @result = begin; paragraph a ; end - _tmp = true - unless _tmp - self.pos = _save - end - break - end # end sequence - - set_failed_rule :_DefinitionListDefinition unless _tmp - return _tmp - end - - Rules = {} - Rules[:_root] = rule_info("root", "Doc") - Rules[:_Doc] = rule_info("Doc", "BOM? Block*:a { RDoc::Markup::Document.new(*a.compact) }") - Rules[:_Block] = rule_info("Block", "@BlankLine* (BlockQuote | Verbatim | CodeFence | Table | Note | Reference | HorizontalRule | Heading | OrderedList | BulletList | DefinitionList | HtmlBlock | StyleBlock | Para | Plain)") - Rules[:_Para] = rule_info("Para", "@NonindentSpace Inlines:a @BlankLine+ { paragraph a }") - Rules[:_Plain] = rule_info("Plain", "Inlines:a { paragraph a }") - Rules[:_AtxInline] = rule_info("AtxInline", "!@Newline !(@Sp /\#*/ @Sp @Newline) Inline") - Rules[:_AtxStart] = rule_info("AtxStart", "< /\\\#{1,6}/ > { text.length }") - Rules[:_AtxHeading] = rule_info("AtxHeading", "AtxStart:s @Spacechar+ AtxInline+:a (@Sp /\#*/ @Sp)? @Newline { RDoc::Markup::Heading.new(s, a.join) }") - Rules[:_SetextHeading] = rule_info("SetextHeading", "(SetextHeading1 | SetextHeading2)") - Rules[:_SetextBottom1] = rule_info("SetextBottom1", "/={1,}/ @Newline") - Rules[:_SetextBottom2] = rule_info("SetextBottom2", "/-{1,}/ @Newline") - Rules[:_SetextHeading1] = rule_info("SetextHeading1", "&(@RawLine SetextBottom1) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom1 { RDoc::Markup::Heading.new(1, a.join) }") - Rules[:_SetextHeading2] = rule_info("SetextHeading2", "&(@RawLine SetextBottom2) @StartList:a (!@Endline Inline:b { a << b })+ @Sp @Newline SetextBottom2 { RDoc::Markup::Heading.new(2, a.join) }") - Rules[:_Heading] = rule_info("Heading", "(SetextHeading | AtxHeading)") - Rules[:_BlockQuote] = rule_info("BlockQuote", "BlockQuoteRaw:a { RDoc::Markup::BlockQuote.new(*a) }") - Rules[:_BlockQuoteRaw] = rule_info("BlockQuoteRaw", "@StartList:a (\">\" \" \"? Line:l { a << l } (!\">\" !@BlankLine Line:c { a << c })* (@BlankLine:n { a << n })*)+ { inner_parse a.join }") - Rules[:_NonblankIndentedLine] = rule_info("NonblankIndentedLine", "!@BlankLine IndentedLine") - Rules[:_VerbatimChunk] = rule_info("VerbatimChunk", "@BlankLine*:a NonblankIndentedLine+:b { a.concat b }") - Rules[:_Verbatim] = rule_info("Verbatim", "VerbatimChunk+:a { RDoc::Markup::Verbatim.new(*a.flatten) }") - Rules[:_HorizontalRule] = rule_info("HorizontalRule", "@NonindentSpace (\"*\" @Sp \"*\" @Sp \"*\" (@Sp \"*\")* | \"-\" @Sp \"-\" @Sp \"-\" (@Sp \"-\")* | \"_\" @Sp \"_\" @Sp \"_\" (@Sp \"_\")*) @Sp @Newline @BlankLine+ { RDoc::Markup::Rule.new 1 }") - Rules[:_Bullet] = rule_info("Bullet", "!HorizontalRule @NonindentSpace /[+*-]/ @Spacechar+") - Rules[:_BulletList] = rule_info("BulletList", "&Bullet (ListTight | ListLoose):a { RDoc::Markup::List.new(:BULLET, *a) }") - Rules[:_ListTight] = rule_info("ListTight", "ListItemTight+:a @BlankLine* !(Bullet | Enumerator) { a }") - Rules[:_ListLoose] = rule_info("ListLoose", "@StartList:a (ListItem:b @BlankLine* { a << b })+ { a }") - Rules[:_ListItem] = rule_info("ListItem", "(Bullet | Enumerator) @StartList:a ListBlock:b { a << b } (ListContinuationBlock:c { a.push(*c) })* { list_item_from a }") - Rules[:_ListItemTight] = rule_info("ListItemTight", "(Bullet | Enumerator) ListBlock:a (!@BlankLine ListContinuationBlock:b { a.push(*b) })* !ListContinuationBlock { list_item_from a }") - Rules[:_ListBlock] = rule_info("ListBlock", "!@BlankLine Line:a ListBlockLine*:c { [a, *c] }") - Rules[:_ListContinuationBlock] = rule_info("ListContinuationBlock", "@StartList:a @BlankLine* { a << \"\\n\" } (Indent ListBlock:b { a.concat b })+ { a }") - Rules[:_Enumerator] = rule_info("Enumerator", "@NonindentSpace [0-9]+ \".\" @Spacechar+") - Rules[:_OrderedList] = rule_info("OrderedList", "&Enumerator (ListTight | ListLoose):a { RDoc::Markup::List.new(:NUMBER, *a) }") - Rules[:_ListBlockLine] = rule_info("ListBlockLine", "!@BlankLine !(Indent? (Bullet | Enumerator)) !HorizontalRule OptionallyIndentedLine") - Rules[:_HtmlOpenAnchor] = rule_info("HtmlOpenAnchor", "\"<\" Spnl (\"a\" | \"A\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlCloseAnchor] = rule_info("HtmlCloseAnchor", "\"<\" Spnl \"/\" (\"a\" | \"A\") Spnl \">\"") - Rules[:_HtmlAnchor] = rule_info("HtmlAnchor", "HtmlOpenAnchor (HtmlAnchor | !HtmlCloseAnchor .)* HtmlCloseAnchor") - Rules[:_HtmlBlockOpenAddress] = rule_info("HtmlBlockOpenAddress", "\"<\" Spnl (\"address\" | \"ADDRESS\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseAddress] = rule_info("HtmlBlockCloseAddress", "\"<\" Spnl \"/\" (\"address\" | \"ADDRESS\") Spnl \">\"") - Rules[:_HtmlBlockAddress] = rule_info("HtmlBlockAddress", "HtmlBlockOpenAddress (HtmlBlockAddress | !HtmlBlockCloseAddress .)* HtmlBlockCloseAddress") - Rules[:_HtmlBlockOpenBlockquote] = rule_info("HtmlBlockOpenBlockquote", "\"<\" Spnl (\"blockquote\" | \"BLOCKQUOTE\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseBlockquote] = rule_info("HtmlBlockCloseBlockquote", "\"<\" Spnl \"/\" (\"blockquote\" | \"BLOCKQUOTE\") Spnl \">\"") - Rules[:_HtmlBlockBlockquote] = rule_info("HtmlBlockBlockquote", "HtmlBlockOpenBlockquote (HtmlBlockBlockquote | !HtmlBlockCloseBlockquote .)* HtmlBlockCloseBlockquote") - Rules[:_HtmlBlockOpenCenter] = rule_info("HtmlBlockOpenCenter", "\"<\" Spnl (\"center\" | \"CENTER\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseCenter] = rule_info("HtmlBlockCloseCenter", "\"<\" Spnl \"/\" (\"center\" | \"CENTER\") Spnl \">\"") - Rules[:_HtmlBlockCenter] = rule_info("HtmlBlockCenter", "HtmlBlockOpenCenter (HtmlBlockCenter | !HtmlBlockCloseCenter .)* HtmlBlockCloseCenter") - Rules[:_HtmlBlockOpenDir] = rule_info("HtmlBlockOpenDir", "\"<\" Spnl (\"dir\" | \"DIR\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseDir] = rule_info("HtmlBlockCloseDir", "\"<\" Spnl \"/\" (\"dir\" | \"DIR\") Spnl \">\"") - Rules[:_HtmlBlockDir] = rule_info("HtmlBlockDir", "HtmlBlockOpenDir (HtmlBlockDir | !HtmlBlockCloseDir .)* HtmlBlockCloseDir") - Rules[:_HtmlBlockOpenDiv] = rule_info("HtmlBlockOpenDiv", "\"<\" Spnl (\"div\" | \"DIV\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseDiv] = rule_info("HtmlBlockCloseDiv", "\"<\" Spnl \"/\" (\"div\" | \"DIV\") Spnl \">\"") - Rules[:_HtmlBlockDiv] = rule_info("HtmlBlockDiv", "HtmlBlockOpenDiv (HtmlBlockDiv | !HtmlBlockCloseDiv .)* HtmlBlockCloseDiv") - Rules[:_HtmlBlockOpenDl] = rule_info("HtmlBlockOpenDl", "\"<\" Spnl (\"dl\" | \"DL\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseDl] = rule_info("HtmlBlockCloseDl", "\"<\" Spnl \"/\" (\"dl\" | \"DL\") Spnl \">\"") - Rules[:_HtmlBlockDl] = rule_info("HtmlBlockDl", "HtmlBlockOpenDl (HtmlBlockDl | !HtmlBlockCloseDl .)* HtmlBlockCloseDl") - Rules[:_HtmlBlockOpenFieldset] = rule_info("HtmlBlockOpenFieldset", "\"<\" Spnl (\"fieldset\" | \"FIELDSET\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseFieldset] = rule_info("HtmlBlockCloseFieldset", "\"<\" Spnl \"/\" (\"fieldset\" | \"FIELDSET\") Spnl \">\"") - Rules[:_HtmlBlockFieldset] = rule_info("HtmlBlockFieldset", "HtmlBlockOpenFieldset (HtmlBlockFieldset | !HtmlBlockCloseFieldset .)* HtmlBlockCloseFieldset") - Rules[:_HtmlBlockOpenForm] = rule_info("HtmlBlockOpenForm", "\"<\" Spnl (\"form\" | \"FORM\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseForm] = rule_info("HtmlBlockCloseForm", "\"<\" Spnl \"/\" (\"form\" | \"FORM\") Spnl \">\"") - Rules[:_HtmlBlockForm] = rule_info("HtmlBlockForm", "HtmlBlockOpenForm (HtmlBlockForm | !HtmlBlockCloseForm .)* HtmlBlockCloseForm") - Rules[:_HtmlBlockOpenH1] = rule_info("HtmlBlockOpenH1", "\"<\" Spnl (\"h1\" | \"H1\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH1] = rule_info("HtmlBlockCloseH1", "\"<\" Spnl \"/\" (\"h1\" | \"H1\") Spnl \">\"") - Rules[:_HtmlBlockH1] = rule_info("HtmlBlockH1", "HtmlBlockOpenH1 (HtmlBlockH1 | !HtmlBlockCloseH1 .)* HtmlBlockCloseH1") - Rules[:_HtmlBlockOpenH2] = rule_info("HtmlBlockOpenH2", "\"<\" Spnl (\"h2\" | \"H2\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH2] = rule_info("HtmlBlockCloseH2", "\"<\" Spnl \"/\" (\"h2\" | \"H2\") Spnl \">\"") - Rules[:_HtmlBlockH2] = rule_info("HtmlBlockH2", "HtmlBlockOpenH2 (HtmlBlockH2 | !HtmlBlockCloseH2 .)* HtmlBlockCloseH2") - Rules[:_HtmlBlockOpenH3] = rule_info("HtmlBlockOpenH3", "\"<\" Spnl (\"h3\" | \"H3\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH3] = rule_info("HtmlBlockCloseH3", "\"<\" Spnl \"/\" (\"h3\" | \"H3\") Spnl \">\"") - Rules[:_HtmlBlockH3] = rule_info("HtmlBlockH3", "HtmlBlockOpenH3 (HtmlBlockH3 | !HtmlBlockCloseH3 .)* HtmlBlockCloseH3") - Rules[:_HtmlBlockOpenH4] = rule_info("HtmlBlockOpenH4", "\"<\" Spnl (\"h4\" | \"H4\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH4] = rule_info("HtmlBlockCloseH4", "\"<\" Spnl \"/\" (\"h4\" | \"H4\") Spnl \">\"") - Rules[:_HtmlBlockH4] = rule_info("HtmlBlockH4", "HtmlBlockOpenH4 (HtmlBlockH4 | !HtmlBlockCloseH4 .)* HtmlBlockCloseH4") - Rules[:_HtmlBlockOpenH5] = rule_info("HtmlBlockOpenH5", "\"<\" Spnl (\"h5\" | \"H5\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH5] = rule_info("HtmlBlockCloseH5", "\"<\" Spnl \"/\" (\"h5\" | \"H5\") Spnl \">\"") - Rules[:_HtmlBlockH5] = rule_info("HtmlBlockH5", "HtmlBlockOpenH5 (HtmlBlockH5 | !HtmlBlockCloseH5 .)* HtmlBlockCloseH5") - Rules[:_HtmlBlockOpenH6] = rule_info("HtmlBlockOpenH6", "\"<\" Spnl (\"h6\" | \"H6\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseH6] = rule_info("HtmlBlockCloseH6", "\"<\" Spnl \"/\" (\"h6\" | \"H6\") Spnl \">\"") - Rules[:_HtmlBlockH6] = rule_info("HtmlBlockH6", "HtmlBlockOpenH6 (HtmlBlockH6 | !HtmlBlockCloseH6 .)* HtmlBlockCloseH6") - Rules[:_HtmlBlockOpenMenu] = rule_info("HtmlBlockOpenMenu", "\"<\" Spnl (\"menu\" | \"MENU\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseMenu] = rule_info("HtmlBlockCloseMenu", "\"<\" Spnl \"/\" (\"menu\" | \"MENU\") Spnl \">\"") - Rules[:_HtmlBlockMenu] = rule_info("HtmlBlockMenu", "HtmlBlockOpenMenu (HtmlBlockMenu | !HtmlBlockCloseMenu .)* HtmlBlockCloseMenu") - Rules[:_HtmlBlockOpenNoframes] = rule_info("HtmlBlockOpenNoframes", "\"<\" Spnl (\"noframes\" | \"NOFRAMES\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseNoframes] = rule_info("HtmlBlockCloseNoframes", "\"<\" Spnl \"/\" (\"noframes\" | \"NOFRAMES\") Spnl \">\"") - Rules[:_HtmlBlockNoframes] = rule_info("HtmlBlockNoframes", "HtmlBlockOpenNoframes (HtmlBlockNoframes | !HtmlBlockCloseNoframes .)* HtmlBlockCloseNoframes") - Rules[:_HtmlBlockOpenNoscript] = rule_info("HtmlBlockOpenNoscript", "\"<\" Spnl (\"noscript\" | \"NOSCRIPT\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseNoscript] = rule_info("HtmlBlockCloseNoscript", "\"<\" Spnl \"/\" (\"noscript\" | \"NOSCRIPT\") Spnl \">\"") - Rules[:_HtmlBlockNoscript] = rule_info("HtmlBlockNoscript", "HtmlBlockOpenNoscript (HtmlBlockNoscript | !HtmlBlockCloseNoscript .)* HtmlBlockCloseNoscript") - Rules[:_HtmlBlockOpenOl] = rule_info("HtmlBlockOpenOl", "\"<\" Spnl (\"ol\" | \"OL\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseOl] = rule_info("HtmlBlockCloseOl", "\"<\" Spnl \"/\" (\"ol\" | \"OL\") Spnl \">\"") - Rules[:_HtmlBlockOl] = rule_info("HtmlBlockOl", "HtmlBlockOpenOl (HtmlBlockOl | !HtmlBlockCloseOl .)* HtmlBlockCloseOl") - Rules[:_HtmlBlockOpenP] = rule_info("HtmlBlockOpenP", "\"<\" Spnl (\"p\" | \"P\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseP] = rule_info("HtmlBlockCloseP", "\"<\" Spnl \"/\" (\"p\" | \"P\") Spnl \">\"") - Rules[:_HtmlBlockP] = rule_info("HtmlBlockP", "HtmlBlockOpenP (HtmlBlockP | !HtmlBlockCloseP .)* HtmlBlockCloseP") - Rules[:_HtmlBlockOpenPre] = rule_info("HtmlBlockOpenPre", "\"<\" Spnl (\"pre\" | \"PRE\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockClosePre] = rule_info("HtmlBlockClosePre", "\"<\" Spnl \"/\" (\"pre\" | \"PRE\") Spnl \">\"") - Rules[:_HtmlBlockPre] = rule_info("HtmlBlockPre", "HtmlBlockOpenPre (HtmlBlockPre | !HtmlBlockClosePre .)* HtmlBlockClosePre") - Rules[:_HtmlBlockOpenTable] = rule_info("HtmlBlockOpenTable", "\"<\" Spnl (\"table\" | \"TABLE\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTable] = rule_info("HtmlBlockCloseTable", "\"<\" Spnl \"/\" (\"table\" | \"TABLE\") Spnl \">\"") - Rules[:_HtmlBlockTable] = rule_info("HtmlBlockTable", "HtmlBlockOpenTable (HtmlBlockTable | !HtmlBlockCloseTable .)* HtmlBlockCloseTable") - Rules[:_HtmlBlockOpenUl] = rule_info("HtmlBlockOpenUl", "\"<\" Spnl (\"ul\" | \"UL\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseUl] = rule_info("HtmlBlockCloseUl", "\"<\" Spnl \"/\" (\"ul\" | \"UL\") Spnl \">\"") - Rules[:_HtmlBlockUl] = rule_info("HtmlBlockUl", "HtmlBlockOpenUl (HtmlBlockUl | !HtmlBlockCloseUl .)* HtmlBlockCloseUl") - Rules[:_HtmlBlockOpenDd] = rule_info("HtmlBlockOpenDd", "\"<\" Spnl (\"dd\" | \"DD\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseDd] = rule_info("HtmlBlockCloseDd", "\"<\" Spnl \"/\" (\"dd\" | \"DD\") Spnl \">\"") - Rules[:_HtmlBlockDd] = rule_info("HtmlBlockDd", "HtmlBlockOpenDd (HtmlBlockDd | !HtmlBlockCloseDd .)* HtmlBlockCloseDd") - Rules[:_HtmlBlockOpenDt] = rule_info("HtmlBlockOpenDt", "\"<\" Spnl (\"dt\" | \"DT\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseDt] = rule_info("HtmlBlockCloseDt", "\"<\" Spnl \"/\" (\"dt\" | \"DT\") Spnl \">\"") - Rules[:_HtmlBlockDt] = rule_info("HtmlBlockDt", "HtmlBlockOpenDt (HtmlBlockDt | !HtmlBlockCloseDt .)* HtmlBlockCloseDt") - Rules[:_HtmlBlockOpenFrameset] = rule_info("HtmlBlockOpenFrameset", "\"<\" Spnl (\"frameset\" | \"FRAMESET\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseFrameset] = rule_info("HtmlBlockCloseFrameset", "\"<\" Spnl \"/\" (\"frameset\" | \"FRAMESET\") Spnl \">\"") - Rules[:_HtmlBlockFrameset] = rule_info("HtmlBlockFrameset", "HtmlBlockOpenFrameset (HtmlBlockFrameset | !HtmlBlockCloseFrameset .)* HtmlBlockCloseFrameset") - Rules[:_HtmlBlockOpenLi] = rule_info("HtmlBlockOpenLi", "\"<\" Spnl (\"li\" | \"LI\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseLi] = rule_info("HtmlBlockCloseLi", "\"<\" Spnl \"/\" (\"li\" | \"LI\") Spnl \">\"") - Rules[:_HtmlBlockLi] = rule_info("HtmlBlockLi", "HtmlBlockOpenLi (HtmlBlockLi | !HtmlBlockCloseLi .)* HtmlBlockCloseLi") - Rules[:_HtmlBlockOpenTbody] = rule_info("HtmlBlockOpenTbody", "\"<\" Spnl (\"tbody\" | \"TBODY\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTbody] = rule_info("HtmlBlockCloseTbody", "\"<\" Spnl \"/\" (\"tbody\" | \"TBODY\") Spnl \">\"") - Rules[:_HtmlBlockTbody] = rule_info("HtmlBlockTbody", "HtmlBlockOpenTbody (HtmlBlockTbody | !HtmlBlockCloseTbody .)* HtmlBlockCloseTbody") - Rules[:_HtmlBlockOpenTd] = rule_info("HtmlBlockOpenTd", "\"<\" Spnl (\"td\" | \"TD\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTd] = rule_info("HtmlBlockCloseTd", "\"<\" Spnl \"/\" (\"td\" | \"TD\") Spnl \">\"") - Rules[:_HtmlBlockTd] = rule_info("HtmlBlockTd", "HtmlBlockOpenTd (HtmlBlockTd | !HtmlBlockCloseTd .)* HtmlBlockCloseTd") - Rules[:_HtmlBlockOpenTfoot] = rule_info("HtmlBlockOpenTfoot", "\"<\" Spnl (\"tfoot\" | \"TFOOT\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTfoot] = rule_info("HtmlBlockCloseTfoot", "\"<\" Spnl \"/\" (\"tfoot\" | \"TFOOT\") Spnl \">\"") - Rules[:_HtmlBlockTfoot] = rule_info("HtmlBlockTfoot", "HtmlBlockOpenTfoot (HtmlBlockTfoot | !HtmlBlockCloseTfoot .)* HtmlBlockCloseTfoot") - Rules[:_HtmlBlockOpenTh] = rule_info("HtmlBlockOpenTh", "\"<\" Spnl (\"th\" | \"TH\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTh] = rule_info("HtmlBlockCloseTh", "\"<\" Spnl \"/\" (\"th\" | \"TH\") Spnl \">\"") - Rules[:_HtmlBlockTh] = rule_info("HtmlBlockTh", "HtmlBlockOpenTh (HtmlBlockTh | !HtmlBlockCloseTh .)* HtmlBlockCloseTh") - Rules[:_HtmlBlockOpenThead] = rule_info("HtmlBlockOpenThead", "\"<\" Spnl (\"thead\" | \"THEAD\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseThead] = rule_info("HtmlBlockCloseThead", "\"<\" Spnl \"/\" (\"thead\" | \"THEAD\") Spnl \">\"") - Rules[:_HtmlBlockThead] = rule_info("HtmlBlockThead", "HtmlBlockOpenThead (HtmlBlockThead | !HtmlBlockCloseThead .)* HtmlBlockCloseThead") - Rules[:_HtmlBlockOpenTr] = rule_info("HtmlBlockOpenTr", "\"<\" Spnl (\"tr\" | \"TR\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseTr] = rule_info("HtmlBlockCloseTr", "\"<\" Spnl \"/\" (\"tr\" | \"TR\") Spnl \">\"") - Rules[:_HtmlBlockTr] = rule_info("HtmlBlockTr", "HtmlBlockOpenTr (HtmlBlockTr | !HtmlBlockCloseTr .)* HtmlBlockCloseTr") - Rules[:_HtmlBlockOpenScript] = rule_info("HtmlBlockOpenScript", "\"<\" Spnl (\"script\" | \"SCRIPT\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseScript] = rule_info("HtmlBlockCloseScript", "\"<\" Spnl \"/\" (\"script\" | \"SCRIPT\") Spnl \">\"") - Rules[:_HtmlBlockScript] = rule_info("HtmlBlockScript", "HtmlBlockOpenScript (!HtmlBlockCloseScript .)* HtmlBlockCloseScript") - Rules[:_HtmlBlockOpenHead] = rule_info("HtmlBlockOpenHead", "\"<\" Spnl (\"head\" | \"HEAD\") Spnl HtmlAttribute* \">\"") - Rules[:_HtmlBlockCloseHead] = rule_info("HtmlBlockCloseHead", "\"<\" Spnl \"/\" (\"head\" | \"HEAD\") Spnl \">\"") - Rules[:_HtmlBlockHead] = rule_info("HtmlBlockHead", "HtmlBlockOpenHead (!HtmlBlockCloseHead .)* HtmlBlockCloseHead") - Rules[:_HtmlBlockInTags] = rule_info("HtmlBlockInTags", "(HtmlAnchor | HtmlBlockAddress | HtmlBlockBlockquote | HtmlBlockCenter | HtmlBlockDir | HtmlBlockDiv | HtmlBlockDl | HtmlBlockFieldset | HtmlBlockForm | HtmlBlockH1 | HtmlBlockH2 | HtmlBlockH3 | HtmlBlockH4 | HtmlBlockH5 | HtmlBlockH6 | HtmlBlockMenu | HtmlBlockNoframes | HtmlBlockNoscript | HtmlBlockOl | HtmlBlockP | HtmlBlockPre | HtmlBlockTable | HtmlBlockUl | HtmlBlockDd | HtmlBlockDt | HtmlBlockFrameset | HtmlBlockLi | HtmlBlockTbody | HtmlBlockTd | HtmlBlockTfoot | HtmlBlockTh | HtmlBlockThead | HtmlBlockTr | HtmlBlockScript | HtmlBlockHead)") - Rules[:_HtmlBlock] = rule_info("HtmlBlock", "< (HtmlBlockInTags | HtmlComment | HtmlBlockSelfClosing | HtmlUnclosed) > @BlankLine+ { if html? then RDoc::Markup::Raw.new text end }") - Rules[:_HtmlUnclosed] = rule_info("HtmlUnclosed", "\"<\" Spnl HtmlUnclosedType Spnl HtmlAttribute* Spnl \">\"") - Rules[:_HtmlUnclosedType] = rule_info("HtmlUnclosedType", "(\"HR\" | \"hr\")") - Rules[:_HtmlBlockSelfClosing] = rule_info("HtmlBlockSelfClosing", "\"<\" Spnl HtmlBlockType Spnl HtmlAttribute* \"/\" Spnl \">\"") - Rules[:_HtmlBlockType] = rule_info("HtmlBlockType", "(\"ADDRESS\" | \"BLOCKQUOTE\" | \"CENTER\" | \"DD\" | \"DIR\" | \"DIV\" | \"DL\" | \"DT\" | \"FIELDSET\" | \"FORM\" | \"FRAMESET\" | \"H1\" | \"H2\" | \"H3\" | \"H4\" | \"H5\" | \"H6\" | \"HR\" | \"ISINDEX\" | \"LI\" | \"MENU\" | \"NOFRAMES\" | \"NOSCRIPT\" | \"OL\" | \"P\" | \"PRE\" | \"SCRIPT\" | \"TABLE\" | \"TBODY\" | \"TD\" | \"TFOOT\" | \"TH\" | \"THEAD\" | \"TR\" | \"UL\" | \"address\" | \"blockquote\" | \"center\" | \"dd\" | \"dir\" | \"div\" | \"dl\" | \"dt\" | \"fieldset\" | \"form\" | \"frameset\" | \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\" | \"hr\" | \"isindex\" | \"li\" | \"menu\" | \"noframes\" | \"noscript\" | \"ol\" | \"p\" | \"pre\" | \"script\" | \"table\" | \"tbody\" | \"td\" | \"tfoot\" | \"th\" | \"thead\" | \"tr\" | \"ul\")") - Rules[:_StyleOpen] = rule_info("StyleOpen", "\"<\" Spnl (\"style\" | \"STYLE\") Spnl HtmlAttribute* \">\"") - Rules[:_StyleClose] = rule_info("StyleClose", "\"<\" Spnl \"/\" (\"style\" | \"STYLE\") Spnl \">\"") - Rules[:_InStyleTags] = rule_info("InStyleTags", "StyleOpen (!StyleClose .)* StyleClose") - Rules[:_StyleBlock] = rule_info("StyleBlock", "< InStyleTags > @BlankLine* { if css? then RDoc::Markup::Raw.new text end }") - Rules[:_Inlines] = rule_info("Inlines", "(!@Endline Inline:i { i } | @Endline:c !(&{ github? } Ticks3 /[^`\\n]*$/) &Inline { c })+:chunks @Endline? { chunks }") - Rules[:_Inline] = rule_info("Inline", "(Str | @Endline | UlOrStarLine | @Space | Strong | Emph | Strike | Image | Link | NoteReference | InlineNote | Code | RawHtml | Entity | EscapedChar | Symbol)") - Rules[:_Space] = rule_info("Space", "@Spacechar+ { \" \" }") - Rules[:_Str] = rule_info("Str", "@StartList:a < @NormalChar+ > { a = text } (StrChunk:c { a << c })* { a }") - Rules[:_StrChunk] = rule_info("StrChunk", "< (@NormalChar | /_+/ &Alphanumeric)+ > { text }") - Rules[:_EscapedChar] = rule_info("EscapedChar", "\"\\\\\" !@Newline < /[:\\\\`|*_{}\\[\\]()\#+.!><-]/ > { text }") - Rules[:_Entity] = rule_info("Entity", "(HexEntity | DecEntity | CharEntity):a { a }") - Rules[:_Endline] = rule_info("Endline", "(@LineBreak | @TerminalEndline | @NormalEndline)") - Rules[:_NormalEndline] = rule_info("NormalEndline", "@Sp @Newline !@BlankLine !\">\" !AtxStart !(Line /={1,}|-{1,}/ @Newline) { \"\\n\" }") - Rules[:_TerminalEndline] = rule_info("TerminalEndline", "@Sp @Newline @Eof") - Rules[:_LineBreak] = rule_info("LineBreak", "\" \" @NormalEndline { RDoc::Markup::HardBreak.new }") - Rules[:_Symbol] = rule_info("Symbol", "< @SpecialChar > { text }") - Rules[:_UlOrStarLine] = rule_info("UlOrStarLine", "(UlLine | StarLine):a { a }") - Rules[:_StarLine] = rule_info("StarLine", "(< /\\*{4,}/ > { text } | < @Spacechar /\\*+/ &@Spacechar > { text })") - Rules[:_UlLine] = rule_info("UlLine", "(< /_{4,}/ > { text } | < @Spacechar /_+/ &@Spacechar > { text })") - Rules[:_Emph] = rule_info("Emph", "(EmphStar | EmphUl)") - Rules[:_Whitespace] = rule_info("Whitespace", "(@Spacechar | @Newline)") - Rules[:_EmphStar] = rule_info("EmphStar", "\"*\" !@Whitespace @StartList:a (!\"*\" Inline:b { a << b } | StrongStar:b { a << b })+ \"*\" { emphasis a.join }") - Rules[:_EmphUl] = rule_info("EmphUl", "\"_\" !@Whitespace @StartList:a (!\"_\" Inline:b { a << b } | StrongUl:b { a << b })+ \"_\" { emphasis a.join }") - Rules[:_Strong] = rule_info("Strong", "(StrongStar | StrongUl)") - Rules[:_StrongStar] = rule_info("StrongStar", "\"**\" !@Whitespace @StartList:a (!\"**\" Inline:b { a << b })+ \"**\" { strong a.join }") - Rules[:_StrongUl] = rule_info("StrongUl", "\"__\" !@Whitespace @StartList:a (!\"__\" Inline:b { a << b })+ \"__\" { strong a.join }") - Rules[:_Strike] = rule_info("Strike", "&{ strike? } \"~~\" !@Whitespace @StartList:a (!\"~~\" Inline:b { a << b })+ \"~~\" { strike a.join }") - Rules[:_Image] = rule_info("Image", "\"!\" (ExplicitLink | ReferenceLink):a { \"rdoc-image:\#{a[/\\[(.*)\\]/, 1]}\" }") - Rules[:_Link] = rule_info("Link", "(ExplicitLink | ReferenceLink | AutoLink)") - Rules[:_ReferenceLink] = rule_info("ReferenceLink", "(ReferenceLinkDouble | ReferenceLinkSingle)") - Rules[:_ReferenceLinkDouble] = rule_info("ReferenceLinkDouble", "Label:content < Spnl > !\"[]\" Label:label { link_to content, label, text }") - Rules[:_ReferenceLinkSingle] = rule_info("ReferenceLinkSingle", "Label:content < (Spnl \"[]\")? > { link_to content, content, text }") - Rules[:_ExplicitLink] = rule_info("ExplicitLink", "Label:l \"(\" @Sp Source:s Spnl Title @Sp \")\" { \"{\#{l}}[\#{s}]\" }") - Rules[:_Source] = rule_info("Source", "(\"<\" < SourceContents > \">\" | < SourceContents >) { text }") - Rules[:_SourceContents] = rule_info("SourceContents", "((!\"(\" !\")\" !\">\" Nonspacechar)+ | \"(\" SourceContents \")\")*") - Rules[:_Title] = rule_info("Title", "(TitleSingle | TitleDouble | \"\"):a { a }") - Rules[:_TitleSingle] = rule_info("TitleSingle", "\"'\" (!(\"'\" @Sp (\")\" | @Newline)) .)* \"'\"") - Rules[:_TitleDouble] = rule_info("TitleDouble", "\"\\\"\" (!(\"\\\"\" @Sp (\")\" | @Newline)) .)* \"\\\"\"") - Rules[:_AutoLink] = rule_info("AutoLink", "(AutoLinkUrl | AutoLinkEmail)") - Rules[:_AutoLinkUrl] = rule_info("AutoLinkUrl", "\"<\" < /[A-Za-z]+/ \"://\" (!@Newline !\">\" .)+ > \">\" { text }") - Rules[:_AutoLinkEmail] = rule_info("AutoLinkEmail", "\"<\" \"mailto:\"? < /[\\w+.\\/!%~$-]+/i \"@\" (!@Newline !\">\" .)+ > \">\" { \"mailto:\#{text}\" }") - Rules[:_Reference] = rule_info("Reference", "@NonindentSpace !\"[]\" Label:label \":\" Spnl RefSrc:link RefTitle @BlankLine+ { \# TODO use title reference label, link nil }") - Rules[:_Label] = rule_info("Label", "\"[\" (!\"^\" &{ notes? } | &. &{ !notes? }) @StartList:a (!\"]\" Inline:l { a << l })* \"]\" { a.join.gsub(/\\s+/, ' ') }") - Rules[:_RefSrc] = rule_info("RefSrc", "< Nonspacechar+ > { text }") - Rules[:_RefTitle] = rule_info("RefTitle", "(RefTitleSingle | RefTitleDouble | RefTitleParens | EmptyTitle)") - Rules[:_EmptyTitle] = rule_info("EmptyTitle", "\"\"") - Rules[:_RefTitleSingle] = rule_info("RefTitleSingle", "Spnl \"'\" < (!(\"'\" @Sp @Newline | @Newline) .)* > \"'\" { text }") - Rules[:_RefTitleDouble] = rule_info("RefTitleDouble", "Spnl \"\\\"\" < (!(\"\\\"\" @Sp @Newline | @Newline) .)* > \"\\\"\" { text }") - Rules[:_RefTitleParens] = rule_info("RefTitleParens", "Spnl \"(\" < (!(\")\" @Sp @Newline | @Newline) .)* > \")\" { text }") - Rules[:_References] = rule_info("References", "(Reference | SkipBlock)*") - Rules[:_Ticks1] = rule_info("Ticks1", "\"`\" !\"`\"") - Rules[:_Ticks2] = rule_info("Ticks2", "\"``\" !\"`\"") - Rules[:_Ticks3] = rule_info("Ticks3", "\"```\" !\"`\"") - Rules[:_Ticks4] = rule_info("Ticks4", "\"````\" !\"`\"") - Rules[:_Ticks5] = rule_info("Ticks5", "\"`````\" !\"`\"") - Rules[:_Code] = rule_info("Code", "(Ticks1 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks1 /`+/ | !(@Sp Ticks1) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks1 | Ticks2 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks2 /`+/ | !(@Sp Ticks2) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks2 | Ticks3 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks3 /`+/ | !(@Sp Ticks3) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks3 | Ticks4 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks4 /`+/ | !(@Sp Ticks4) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks4 | Ticks5 @Sp < ((!\"`\" Nonspacechar)+ | !Ticks5 /`+/ | !(@Sp Ticks5) (@Spacechar | @Newline !@BlankLine))+ > @Sp Ticks5) { \"<code>\#{text}</code>\" }") - Rules[:_RawHtml] = rule_info("RawHtml", "< (HtmlComment | HtmlBlockScript | HtmlTag) > { if html? then text else '' end }") - Rules[:_BlankLine] = rule_info("BlankLine", "@Sp @Newline { \"\\n\" }") - Rules[:_Quoted] = rule_info("Quoted", "(\"\\\"\" (!\"\\\"\" .)* \"\\\"\" | \"'\" (!\"'\" .)* \"'\")") - Rules[:_HtmlAttribute] = rule_info("HtmlAttribute", "(AlphanumericAscii | \"-\")+ Spnl (\"=\" Spnl (Quoted | (!\">\" Nonspacechar)+))? Spnl") - Rules[:_HtmlComment] = rule_info("HtmlComment", "\"<!--\" (!\"-->\" .)* \"-->\"") - Rules[:_HtmlTag] = rule_info("HtmlTag", "\"<\" Spnl \"/\"? AlphanumericAscii+ Spnl HtmlAttribute* \"/\"? Spnl \">\"") - Rules[:_Eof] = rule_info("Eof", "!.") - Rules[:_Nonspacechar] = rule_info("Nonspacechar", "!@Spacechar !@Newline .") - Rules[:_Sp] = rule_info("Sp", "@Spacechar*") - Rules[:_Spnl] = rule_info("Spnl", "@Sp (@Newline @Sp)?") - Rules[:_SpecialChar] = rule_info("SpecialChar", "(/[~*_`&\\[\\]()<!\#\\\\'\"]/ | @ExtendedSpecialChar)") - Rules[:_NormalChar] = rule_info("NormalChar", "!(@SpecialChar | @Spacechar | @Newline) .") - Rules[:_Digit] = rule_info("Digit", "[0-9]") - Rules[:_Alphanumeric] = rule_info("Alphanumeric", "%literals.Alphanumeric") - Rules[:_AlphanumericAscii] = rule_info("AlphanumericAscii", "%literals.AlphanumericAscii") - Rules[:_BOM] = rule_info("BOM", "%literals.BOM") - Rules[:_Newline] = rule_info("Newline", "%literals.Newline") - Rules[:_Spacechar] = rule_info("Spacechar", "%literals.Spacechar") - Rules[:_HexEntity] = rule_info("HexEntity", "/&\#x/i < /[0-9a-fA-F]+/ > \";\" { [text.to_i(16)].pack 'U' }") - Rules[:_DecEntity] = rule_info("DecEntity", "\"&\#\" < /[0-9]+/ > \";\" { [text.to_i].pack 'U' }") - Rules[:_CharEntity] = rule_info("CharEntity", "\"&\" < /[A-Za-z0-9]+/ > \";\" { if entity = HTML_ENTITIES[text] then entity.pack 'U*' else \"&\#{text};\" end }") - Rules[:_NonindentSpace] = rule_info("NonindentSpace", "/ {0,3}/") - Rules[:_Indent] = rule_info("Indent", "/\\t| /") - Rules[:_IndentedLine] = rule_info("IndentedLine", "Indent Line") - Rules[:_OptionallyIndentedLine] = rule_info("OptionallyIndentedLine", "Indent? Line") - Rules[:_StartList] = rule_info("StartList", "&. { [] }") - Rules[:_Line] = rule_info("Line", "@RawLine:a { a }") - Rules[:_RawLine] = rule_info("RawLine", "(< /[^\\r\\n]*/ @Newline > | < .+ > @Eof) { text }") - Rules[:_SkipBlock] = rule_info("SkipBlock", "(HtmlBlock | (!\"\#\" !SetextBottom1 !SetextBottom2 !@BlankLine @RawLine)+ @BlankLine* | @BlankLine+ | @RawLine)") - Rules[:_ExtendedSpecialChar] = rule_info("ExtendedSpecialChar", "&{ notes? } \"^\"") - Rules[:_NoteReference] = rule_info("NoteReference", "&{ notes? } RawNoteReference:ref { note_for ref }") - Rules[:_RawNoteReference] = rule_info("RawNoteReference", "\"[^\" < (!@Newline !\"]\" .)+ > \"]\" { text }") - Rules[:_Note] = rule_info("Note", "&{ notes? } @NonindentSpace RawNoteReference:ref \":\" @Sp @StartList:a RawNoteBlock:i { a.concat i } (&Indent RawNoteBlock:i { a.concat i })* { @footnotes[ref] = paragraph a nil }") - Rules[:_InlineNote] = rule_info("InlineNote", "&{ notes? } \"^[\" @StartList:a (!\"]\" Inline:l { a << l })+ \"]\" { ref = [:inline, @note_order.length] @footnotes[ref] = paragraph a note_for ref }") - Rules[:_Notes] = rule_info("Notes", "(Note | SkipBlock)*") - Rules[:_RawNoteBlock] = rule_info("RawNoteBlock", "@StartList:a (!@BlankLine !RawNoteReference OptionallyIndentedLine:l { a << l })+ < @BlankLine* > { a << text } { a }") - Rules[:_CodeFence] = rule_info("CodeFence", "&{ github? } Ticks3 (@Sp StrChunk:format)? Spnl < ((!\"`\" Nonspacechar)+ | !Ticks3 /`+/ | Spacechar | @Newline)+ > Ticks3 @Sp @Newline* { verbatim = RDoc::Markup::Verbatim.new text verbatim.format = format.intern if format.instance_of?(String) verbatim }") - Rules[:_Table] = rule_info("Table", "&{ github? } TableHead:header TableLine:line TableRow+:body { table = RDoc::Markup::Table.new(header, line, body) }") - Rules[:_TableHead] = rule_info("TableHead", "TableItem2+:items \"|\"? @Newline { items }") - Rules[:_TableRow] = rule_info("TableRow", "((TableItem:item1 TableItem2*:items { [item1, *items] }):row | TableItem2+:row) \"|\"? @Newline { row }") - Rules[:_TableItem2] = rule_info("TableItem2", "\"|\" TableItem") - Rules[:_TableItem] = rule_info("TableItem", "< /(?:\\\\.|[^|\\n])+/ > { text.strip.gsub(/\\\\(.)/, '\\1') }") - Rules[:_TableLine] = rule_info("TableLine", "((TableAlign:align1 TableAlign2*:aligns {[align1, *aligns] }):line | TableAlign2+:line) \"|\"? @Newline { line }") - Rules[:_TableAlign2] = rule_info("TableAlign2", "\"|\" @Sp TableAlign") - Rules[:_TableAlign] = rule_info("TableAlign", "< /:?-+:?/ > @Sp { text.start_with?(\":\") ? (text.end_with?(\":\") ? :center : :left) : (text.end_with?(\":\") ? :right : nil) }") - Rules[:_DefinitionList] = rule_info("DefinitionList", "&{ definition_lists? } DefinitionListItem+:list { RDoc::Markup::List.new :NOTE, *list.flatten }") - Rules[:_DefinitionListItem] = rule_info("DefinitionListItem", "DefinitionListLabel+:label DefinitionListDefinition+:defns { list_items = [] list_items << RDoc::Markup::ListItem.new(label, defns.shift) list_items.concat defns.map { |defn| RDoc::Markup::ListItem.new nil, defn } unless list_items.empty? list_items }") - Rules[:_DefinitionListLabel] = rule_info("DefinitionListLabel", "Inline:label @Sp @Newline { label }") - Rules[:_DefinitionListDefinition] = rule_info("DefinitionListDefinition", "@NonindentSpace \":\" @Space Inlines:a @BlankLine+ { paragraph a }") - # :startdoc: -end diff --git a/lib/rdoc/markdown/entities.rb b/lib/rdoc/markdown/entities.rb deleted file mode 100644 index 265c2eb3f3..0000000000 --- a/lib/rdoc/markdown/entities.rb +++ /dev/null @@ -1,2131 +0,0 @@ -# frozen_string_literal: true -## -# HTML entity name map for RDoc::Markdown - -RDoc::Markdown::HTML_ENTITIES = { - "AElig" => [0x000C6], - "AMP" => [0x00026], - "Aacute" => [0x000C1], - "Abreve" => [0x00102], - "Acirc" => [0x000C2], - "Acy" => [0x00410], - "Afr" => [0x1D504], - "Agrave" => [0x000C0], - "Alpha" => [0x00391], - "Amacr" => [0x00100], - "And" => [0x02A53], - "Aogon" => [0x00104], - "Aopf" => [0x1D538], - "ApplyFunction" => [0x02061], - "Aring" => [0x000C5], - "Ascr" => [0x1D49C], - "Assign" => [0x02254], - "Atilde" => [0x000C3], - "Auml" => [0x000C4], - "Backslash" => [0x02216], - "Barv" => [0x02AE7], - "Barwed" => [0x02306], - "Bcy" => [0x00411], - "Because" => [0x02235], - "Bernoullis" => [0x0212C], - "Beta" => [0x00392], - "Bfr" => [0x1D505], - "Bopf" => [0x1D539], - "Breve" => [0x002D8], - "Bscr" => [0x0212C], - "Bumpeq" => [0x0224E], - "CHcy" => [0x00427], - "COPY" => [0x000A9], - "Cacute" => [0x00106], - "Cap" => [0x022D2], - "CapitalDifferentialD" => [0x02145], - "Cayleys" => [0x0212D], - "Ccaron" => [0x0010C], - "Ccedil" => [0x000C7], - "Ccirc" => [0x00108], - "Cconint" => [0x02230], - "Cdot" => [0x0010A], - "Cedilla" => [0x000B8], - "CenterDot" => [0x000B7], - "Cfr" => [0x0212D], - "Chi" => [0x003A7], - "CircleDot" => [0x02299], - "CircleMinus" => [0x02296], - "CirclePlus" => [0x02295], - "CircleTimes" => [0x02297], - "ClockwiseContourIntegral" => [0x02232], - "CloseCurlyDoubleQuote" => [0x0201D], - "CloseCurlyQuote" => [0x02019], - "Colon" => [0x02237], - "Colone" => [0x02A74], - "Congruent" => [0x02261], - "Conint" => [0x0222F], - "ContourIntegral" => [0x0222E], - "Copf" => [0x02102], - "Coproduct" => [0x02210], - "CounterClockwiseContourIntegral" => [0x02233], - "Cross" => [0x02A2F], - "Cscr" => [0x1D49E], - "Cup" => [0x022D3], - "CupCap" => [0x0224D], - "DD" => [0x02145], - "DDotrahd" => [0x02911], - "DJcy" => [0x00402], - "DScy" => [0x00405], - "DZcy" => [0x0040F], - "Dagger" => [0x02021], - "Darr" => [0x021A1], - "Dashv" => [0x02AE4], - "Dcaron" => [0x0010E], - "Dcy" => [0x00414], - "Del" => [0x02207], - "Delta" => [0x00394], - "Dfr" => [0x1D507], - "DiacriticalAcute" => [0x000B4], - "DiacriticalDot" => [0x002D9], - "DiacriticalDoubleAcute" => [0x002DD], - "DiacriticalGrave" => [0x00060], - "DiacriticalTilde" => [0x002DC], - "Diamond" => [0x022C4], - "DifferentialD" => [0x02146], - "Dopf" => [0x1D53B], - "Dot" => [0x000A8], - "DotDot" => [0x020DC], - "DotEqual" => [0x02250], - "DoubleContourIntegral" => [0x0222F], - "DoubleDot" => [0x000A8], - "DoubleDownArrow" => [0x021D3], - "DoubleLeftArrow" => [0x021D0], - "DoubleLeftRightArrow" => [0x021D4], - "DoubleLeftTee" => [0x02AE4], - "DoubleLongLeftArrow" => [0x027F8], - "DoubleLongLeftRightArrow" => [0x027FA], - "DoubleLongRightArrow" => [0x027F9], - "DoubleRightArrow" => [0x021D2], - "DoubleRightTee" => [0x022A8], - "DoubleUpArrow" => [0x021D1], - "DoubleUpDownArrow" => [0x021D5], - "DoubleVerticalBar" => [0x02225], - "DownArrow" => [0x02193], - "DownArrowBar" => [0x02913], - "DownArrowUpArrow" => [0x021F5], - "DownBreve" => [0x00311], - "DownLeftRightVector" => [0x02950], - "DownLeftTeeVector" => [0x0295E], - "DownLeftVector" => [0x021BD], - "DownLeftVectorBar" => [0x02956], - "DownRightTeeVector" => [0x0295F], - "DownRightVector" => [0x021C1], - "DownRightVectorBar" => [0x02957], - "DownTee" => [0x022A4], - "DownTeeArrow" => [0x021A7], - "Downarrow" => [0x021D3], - "Dscr" => [0x1D49F], - "Dstrok" => [0x00110], - "ENG" => [0x0014A], - "ETH" => [0x000D0], - "Eacute" => [0x000C9], - "Ecaron" => [0x0011A], - "Ecirc" => [0x000CA], - "Ecy" => [0x0042D], - "Edot" => [0x00116], - "Efr" => [0x1D508], - "Egrave" => [0x000C8], - "Element" => [0x02208], - "Emacr" => [0x00112], - "EmptySmallSquare" => [0x025FB], - "EmptyVerySmallSquare" => [0x025AB], - "Eogon" => [0x00118], - "Eopf" => [0x1D53C], - "Epsilon" => [0x00395], - "Equal" => [0x02A75], - "EqualTilde" => [0x02242], - "Equilibrium" => [0x021CC], - "Escr" => [0x02130], - "Esim" => [0x02A73], - "Eta" => [0x00397], - "Euml" => [0x000CB], - "Exists" => [0x02203], - "ExponentialE" => [0x02147], - "Fcy" => [0x00424], - "Ffr" => [0x1D509], - "FilledSmallSquare" => [0x025FC], - "FilledVerySmallSquare" => [0x025AA], - "Fopf" => [0x1D53D], - "ForAll" => [0x02200], - "Fouriertrf" => [0x02131], - "Fscr" => [0x02131], - "GJcy" => [0x00403], - "GT" => [0x0003E], - "Gamma" => [0x00393], - "Gammad" => [0x003DC], - "Gbreve" => [0x0011E], - "Gcedil" => [0x00122], - "Gcirc" => [0x0011C], - "Gcy" => [0x00413], - "Gdot" => [0x00120], - "Gfr" => [0x1D50A], - "Gg" => [0x022D9], - "Gopf" => [0x1D53E], - "GreaterEqual" => [0x02265], - "GreaterEqualLess" => [0x022DB], - "GreaterFullEqual" => [0x02267], - "GreaterGreater" => [0x02AA2], - "GreaterLess" => [0x02277], - "GreaterSlantEqual" => [0x02A7E], - "GreaterTilde" => [0x02273], - "Gscr" => [0x1D4A2], - "Gt" => [0x0226B], - "HARDcy" => [0x0042A], - "Hacek" => [0x002C7], - "Hat" => [0x0005E], - "Hcirc" => [0x00124], - "Hfr" => [0x0210C], - "HilbertSpace" => [0x0210B], - "Hopf" => [0x0210D], - "HorizontalLine" => [0x02500], - "Hscr" => [0x0210B], - "Hstrok" => [0x00126], - "HumpDownHump" => [0x0224E], - "HumpEqual" => [0x0224F], - "IEcy" => [0x00415], - "IJlig" => [0x00132], - "IOcy" => [0x00401], - "Iacute" => [0x000CD], - "Icirc" => [0x000CE], - "Icy" => [0x00418], - "Idot" => [0x00130], - "Ifr" => [0x02111], - "Igrave" => [0x000CC], - "Im" => [0x02111], - "Imacr" => [0x0012A], - "ImaginaryI" => [0x02148], - "Implies" => [0x021D2], - "Int" => [0x0222C], - "Integral" => [0x0222B], - "Intersection" => [0x022C2], - "InvisibleComma" => [0x02063], - "InvisibleTimes" => [0x02062], - "Iogon" => [0x0012E], - "Iopf" => [0x1D540], - "Iota" => [0x00399], - "Iscr" => [0x02110], - "Itilde" => [0x00128], - "Iukcy" => [0x00406], - "Iuml" => [0x000CF], - "Jcirc" => [0x00134], - "Jcy" => [0x00419], - "Jfr" => [0x1D50D], - "Jopf" => [0x1D541], - "Jscr" => [0x1D4A5], - "Jsercy" => [0x00408], - "Jukcy" => [0x00404], - "KHcy" => [0x00425], - "KJcy" => [0x0040C], - "Kappa" => [0x0039A], - "Kcedil" => [0x00136], - "Kcy" => [0x0041A], - "Kfr" => [0x1D50E], - "Kopf" => [0x1D542], - "Kscr" => [0x1D4A6], - "LJcy" => [0x00409], - "LT" => [0x0003C], - "Lacute" => [0x00139], - "Lambda" => [0x0039B], - "Lang" => [0x027EA], - "Laplacetrf" => [0x02112], - "Larr" => [0x0219E], - "Lcaron" => [0x0013D], - "Lcedil" => [0x0013B], - "Lcy" => [0x0041B], - "LeftAngleBracket" => [0x027E8], - "LeftArrow" => [0x02190], - "LeftArrowBar" => [0x021E4], - "LeftArrowRightArrow" => [0x021C6], - "LeftCeiling" => [0x02308], - "LeftDoubleBracket" => [0x027E6], - "LeftDownTeeVector" => [0x02961], - "LeftDownVector" => [0x021C3], - "LeftDownVectorBar" => [0x02959], - "LeftFloor" => [0x0230A], - "LeftRightArrow" => [0x02194], - "LeftRightVector" => [0x0294E], - "LeftTee" => [0x022A3], - "LeftTeeArrow" => [0x021A4], - "LeftTeeVector" => [0x0295A], - "LeftTriangle" => [0x022B2], - "LeftTriangleBar" => [0x029CF], - "LeftTriangleEqual" => [0x022B4], - "LeftUpDownVector" => [0x02951], - "LeftUpTeeVector" => [0x02960], - "LeftUpVector" => [0x021BF], - "LeftUpVectorBar" => [0x02958], - "LeftVector" => [0x021BC], - "LeftVectorBar" => [0x02952], - "Leftarrow" => [0x021D0], - "Leftrightarrow" => [0x021D4], - "LessEqualGreater" => [0x022DA], - "LessFullEqual" => [0x02266], - "LessGreater" => [0x02276], - "LessLess" => [0x02AA1], - "LessSlantEqual" => [0x02A7D], - "LessTilde" => [0x02272], - "Lfr" => [0x1D50F], - "Ll" => [0x022D8], - "Lleftarrow" => [0x021DA], - "Lmidot" => [0x0013F], - "LongLeftArrow" => [0x027F5], - "LongLeftRightArrow" => [0x027F7], - "LongRightArrow" => [0x027F6], - "Longleftarrow" => [0x027F8], - "Longleftrightarrow" => [0x027FA], - "Longrightarrow" => [0x027F9], - "Lopf" => [0x1D543], - "LowerLeftArrow" => [0x02199], - "LowerRightArrow" => [0x02198], - "Lscr" => [0x02112], - "Lsh" => [0x021B0], - "Lstrok" => [0x00141], - "Lt" => [0x0226A], - "Map" => [0x02905], - "Mcy" => [0x0041C], - "MediumSpace" => [0x0205F], - "Mellintrf" => [0x02133], - "Mfr" => [0x1D510], - "MinusPlus" => [0x02213], - "Mopf" => [0x1D544], - "Mscr" => [0x02133], - "Mu" => [0x0039C], - "NJcy" => [0x0040A], - "Nacute" => [0x00143], - "Ncaron" => [0x00147], - "Ncedil" => [0x00145], - "Ncy" => [0x0041D], - "NegativeMediumSpace" => [0x0200B], - "NegativeThickSpace" => [0x0200B], - "NegativeThinSpace" => [0x0200B], - "NegativeVeryThinSpace" => [0x0200B], - "NestedGreaterGreater" => [0x0226B], - "NestedLessLess" => [0x0226A], - "NewLine" => [0x0000A], - "Nfr" => [0x1D511], - "NoBreak" => [0x02060], - "NonBreakingSpace" => [0x000A0], - "Nopf" => [0x02115], - "Not" => [0x02AEC], - "NotCongruent" => [0x02262], - "NotCupCap" => [0x0226D], - "NotDoubleVerticalBar" => [0x02226], - "NotElement" => [0x02209], - "NotEqual" => [0x02260], - "NotEqualTilde" => [0x02242, 0x00338], - "NotExists" => [0x02204], - "NotGreater" => [0x0226F], - "NotGreaterEqual" => [0x02271], - "NotGreaterFullEqual" => [0x02267, 0x00338], - "NotGreaterGreater" => [0x0226B, 0x00338], - "NotGreaterLess" => [0x02279], - "NotGreaterSlantEqual" => [0x02A7E, 0x00338], - "NotGreaterTilde" => [0x02275], - "NotHumpDownHump" => [0x0224E, 0x00338], - "NotHumpEqual" => [0x0224F, 0x00338], - "NotLeftTriangle" => [0x022EA], - "NotLeftTriangleBar" => [0x029CF, 0x00338], - "NotLeftTriangleEqual" => [0x022EC], - "NotLess" => [0x0226E], - "NotLessEqual" => [0x02270], - "NotLessGreater" => [0x02278], - "NotLessLess" => [0x0226A, 0x00338], - "NotLessSlantEqual" => [0x02A7D, 0x00338], - "NotLessTilde" => [0x02274], - "NotNestedGreaterGreater" => [0x02AA2, 0x00338], - "NotNestedLessLess" => [0x02AA1, 0x00338], - "NotPrecedes" => [0x02280], - "NotPrecedesEqual" => [0x02AAF, 0x00338], - "NotPrecedesSlantEqual" => [0x022E0], - "NotReverseElement" => [0x0220C], - "NotRightTriangle" => [0x022EB], - "NotRightTriangleBar" => [0x029D0, 0x00338], - "NotRightTriangleEqual" => [0x022ED], - "NotSquareSubset" => [0x0228F, 0x00338], - "NotSquareSubsetEqual" => [0x022E2], - "NotSquareSuperset" => [0x02290, 0x00338], - "NotSquareSupersetEqual" => [0x022E3], - "NotSubset" => [0x02282, 0x020D2], - "NotSubsetEqual" => [0x02288], - "NotSucceeds" => [0x02281], - "NotSucceedsEqual" => [0x02AB0, 0x00338], - "NotSucceedsSlantEqual" => [0x022E1], - "NotSucceedsTilde" => [0x0227F, 0x00338], - "NotSuperset" => [0x02283, 0x020D2], - "NotSupersetEqual" => [0x02289], - "NotTilde" => [0x02241], - "NotTildeEqual" => [0x02244], - "NotTildeFullEqual" => [0x02247], - "NotTildeTilde" => [0x02249], - "NotVerticalBar" => [0x02224], - "Nscr" => [0x1D4A9], - "Ntilde" => [0x000D1], - "Nu" => [0x0039D], - "OElig" => [0x00152], - "Oacute" => [0x000D3], - "Ocirc" => [0x000D4], - "Ocy" => [0x0041E], - "Odblac" => [0x00150], - "Ofr" => [0x1D512], - "Ograve" => [0x000D2], - "Omacr" => [0x0014C], - "Omega" => [0x003A9], - "Omicron" => [0x0039F], - "Oopf" => [0x1D546], - "OpenCurlyDoubleQuote" => [0x0201C], - "OpenCurlyQuote" => [0x02018], - "Or" => [0x02A54], - "Oscr" => [0x1D4AA], - "Oslash" => [0x000D8], - "Otilde" => [0x000D5], - "Otimes" => [0x02A37], - "Ouml" => [0x000D6], - "OverBar" => [0x0203E], - "OverBrace" => [0x023DE], - "OverBracket" => [0x023B4], - "OverParenthesis" => [0x023DC], - "PartialD" => [0x02202], - "Pcy" => [0x0041F], - "Pfr" => [0x1D513], - "Phi" => [0x003A6], - "Pi" => [0x003A0], - "PlusMinus" => [0x000B1], - "Poincareplane" => [0x0210C], - "Popf" => [0x02119], - "Pr" => [0x02ABB], - "Precedes" => [0x0227A], - "PrecedesEqual" => [0x02AAF], - "PrecedesSlantEqual" => [0x0227C], - "PrecedesTilde" => [0x0227E], - "Prime" => [0x02033], - "Product" => [0x0220F], - "Proportion" => [0x02237], - "Proportional" => [0x0221D], - "Pscr" => [0x1D4AB], - "Psi" => [0x003A8], - "QUOT" => [0x00022], - "Qfr" => [0x1D514], - "Qopf" => [0x0211A], - "Qscr" => [0x1D4AC], - "RBarr" => [0x02910], - "REG" => [0x000AE], - "Racute" => [0x00154], - "Rang" => [0x027EB], - "Rarr" => [0x021A0], - "Rarrtl" => [0x02916], - "Rcaron" => [0x00158], - "Rcedil" => [0x00156], - "Rcy" => [0x00420], - "Re" => [0x0211C], - "ReverseElement" => [0x0220B], - "ReverseEquilibrium" => [0x021CB], - "ReverseUpEquilibrium" => [0x0296F], - "Rfr" => [0x0211C], - "Rho" => [0x003A1], - "RightAngleBracket" => [0x027E9], - "RightArrow" => [0x02192], - "RightArrowBar" => [0x021E5], - "RightArrowLeftArrow" => [0x021C4], - "RightCeiling" => [0x02309], - "RightDoubleBracket" => [0x027E7], - "RightDownTeeVector" => [0x0295D], - "RightDownVector" => [0x021C2], - "RightDownVectorBar" => [0x02955], - "RightFloor" => [0x0230B], - "RightTee" => [0x022A2], - "RightTeeArrow" => [0x021A6], - "RightTeeVector" => [0x0295B], - "RightTriangle" => [0x022B3], - "RightTriangleBar" => [0x029D0], - "RightTriangleEqual" => [0x022B5], - "RightUpDownVector" => [0x0294F], - "RightUpTeeVector" => [0x0295C], - "RightUpVector" => [0x021BE], - "RightUpVectorBar" => [0x02954], - "RightVector" => [0x021C0], - "RightVectorBar" => [0x02953], - "Rightarrow" => [0x021D2], - "Ropf" => [0x0211D], - "RoundImplies" => [0x02970], - "Rrightarrow" => [0x021DB], - "Rscr" => [0x0211B], - "Rsh" => [0x021B1], - "RuleDelayed" => [0x029F4], - "SHCHcy" => [0x00429], - "SHcy" => [0x00428], - "SOFTcy" => [0x0042C], - "Sacute" => [0x0015A], - "Sc" => [0x02ABC], - "Scaron" => [0x00160], - "Scedil" => [0x0015E], - "Scirc" => [0x0015C], - "Scy" => [0x00421], - "Sfr" => [0x1D516], - "ShortDownArrow" => [0x02193], - "ShortLeftArrow" => [0x02190], - "ShortRightArrow" => [0x02192], - "ShortUpArrow" => [0x02191], - "Sigma" => [0x003A3], - "SmallCircle" => [0x02218], - "Sopf" => [0x1D54A], - "Sqrt" => [0x0221A], - "Square" => [0x025A1], - "SquareIntersection" => [0x02293], - "SquareSubset" => [0x0228F], - "SquareSubsetEqual" => [0x02291], - "SquareSuperset" => [0x02290], - "SquareSupersetEqual" => [0x02292], - "SquareUnion" => [0x02294], - "Sscr" => [0x1D4AE], - "Star" => [0x022C6], - "Sub" => [0x022D0], - "Subset" => [0x022D0], - "SubsetEqual" => [0x02286], - "Succeeds" => [0x0227B], - "SucceedsEqual" => [0x02AB0], - "SucceedsSlantEqual" => [0x0227D], - "SucceedsTilde" => [0x0227F], - "SuchThat" => [0x0220B], - "Sum" => [0x02211], - "Sup" => [0x022D1], - "Superset" => [0x02283], - "SupersetEqual" => [0x02287], - "Supset" => [0x022D1], - "THORN" => [0x000DE], - "TRADE" => [0x02122], - "TSHcy" => [0x0040B], - "TScy" => [0x00426], - "Tab" => [0x00009], - "Tau" => [0x003A4], - "Tcaron" => [0x00164], - "Tcedil" => [0x00162], - "Tcy" => [0x00422], - "Tfr" => [0x1D517], - "Therefore" => [0x02234], - "Theta" => [0x00398], - "ThickSpace" => [0x0205F, 0x0200A], - "ThinSpace" => [0x02009], - "Tilde" => [0x0223C], - "TildeEqual" => [0x02243], - "TildeFullEqual" => [0x02245], - "TildeTilde" => [0x02248], - "Topf" => [0x1D54B], - "TripleDot" => [0x020DB], - "Tscr" => [0x1D4AF], - "Tstrok" => [0x00166], - "Uacute" => [0x000DA], - "Uarr" => [0x0219F], - "Uarrocir" => [0x02949], - "Ubrcy" => [0x0040E], - "Ubreve" => [0x0016C], - "Ucirc" => [0x000DB], - "Ucy" => [0x00423], - "Udblac" => [0x00170], - "Ufr" => [0x1D518], - "Ugrave" => [0x000D9], - "Umacr" => [0x0016A], - "UnderBar" => [0x0005F], - "UnderBrace" => [0x023DF], - "UnderBracket" => [0x023B5], - "UnderParenthesis" => [0x023DD], - "Union" => [0x022C3], - "UnionPlus" => [0x0228E], - "Uogon" => [0x00172], - "Uopf" => [0x1D54C], - "UpArrow" => [0x02191], - "UpArrowBar" => [0x02912], - "UpArrowDownArrow" => [0x021C5], - "UpDownArrow" => [0x02195], - "UpEquilibrium" => [0x0296E], - "UpTee" => [0x022A5], - "UpTeeArrow" => [0x021A5], - "Uparrow" => [0x021D1], - "Updownarrow" => [0x021D5], - "UpperLeftArrow" => [0x02196], - "UpperRightArrow" => [0x02197], - "Upsi" => [0x003D2], - "Upsilon" => [0x003A5], - "Uring" => [0x0016E], - "Uscr" => [0x1D4B0], - "Utilde" => [0x00168], - "Uuml" => [0x000DC], - "VDash" => [0x022AB], - "Vbar" => [0x02AEB], - "Vcy" => [0x00412], - "Vdash" => [0x022A9], - "Vdashl" => [0x02AE6], - "Vee" => [0x022C1], - "Verbar" => [0x02016], - "Vert" => [0x02016], - "VerticalBar" => [0x02223], - "VerticalLine" => [0x0007C], - "VerticalSeparator" => [0x02758], - "VerticalTilde" => [0x02240], - "VeryThinSpace" => [0x0200A], - "Vfr" => [0x1D519], - "Vopf" => [0x1D54D], - "Vscr" => [0x1D4B1], - "Vvdash" => [0x022AA], - "Wcirc" => [0x00174], - "Wedge" => [0x022C0], - "Wfr" => [0x1D51A], - "Wopf" => [0x1D54E], - "Wscr" => [0x1D4B2], - "Xfr" => [0x1D51B], - "Xi" => [0x0039E], - "Xopf" => [0x1D54F], - "Xscr" => [0x1D4B3], - "YAcy" => [0x0042F], - "YIcy" => [0x00407], - "YUcy" => [0x0042E], - "Yacute" => [0x000DD], - "Ycirc" => [0x00176], - "Ycy" => [0x0042B], - "Yfr" => [0x1D51C], - "Yopf" => [0x1D550], - "Yscr" => [0x1D4B4], - "Yuml" => [0x00178], - "ZHcy" => [0x00416], - "Zacute" => [0x00179], - "Zcaron" => [0x0017D], - "Zcy" => [0x00417], - "Zdot" => [0x0017B], - "ZeroWidthSpace" => [0x0200B], - "Zeta" => [0x00396], - "Zfr" => [0x02128], - "Zopf" => [0x02124], - "Zscr" => [0x1D4B5], - "aacute" => [0x000E1], - "abreve" => [0x00103], - "ac" => [0x0223E], - "acE" => [0x0223E, 0x00333], - "acd" => [0x0223F], - "acirc" => [0x000E2], - "acute" => [0x000B4], - "acy" => [0x00430], - "aelig" => [0x000E6], - "af" => [0x02061], - "afr" => [0x1D51E], - "agrave" => [0x000E0], - "alefsym" => [0x02135], - "aleph" => [0x02135], - "alpha" => [0x003B1], - "amacr" => [0x00101], - "amalg" => [0x02A3F], - "amp" => [0x00026], - "and" => [0x02227], - "andand" => [0x02A55], - "andd" => [0x02A5C], - "andslope" => [0x02A58], - "andv" => [0x02A5A], - "ang" => [0x02220], - "ange" => [0x029A4], - "angle" => [0x02220], - "angmsd" => [0x02221], - "angmsdaa" => [0x029A8], - "angmsdab" => [0x029A9], - "angmsdac" => [0x029AA], - "angmsdad" => [0x029AB], - "angmsdae" => [0x029AC], - "angmsdaf" => [0x029AD], - "angmsdag" => [0x029AE], - "angmsdah" => [0x029AF], - "angrt" => [0x0221F], - "angrtvb" => [0x022BE], - "angrtvbd" => [0x0299D], - "angsph" => [0x02222], - "angst" => [0x000C5], - "angzarr" => [0x0237C], - "aogon" => [0x00105], - "aopf" => [0x1D552], - "ap" => [0x02248], - "apE" => [0x02A70], - "apacir" => [0x02A6F], - "ape" => [0x0224A], - "apid" => [0x0224B], - "apos" => [0x00027], - "approx" => [0x02248], - "approxeq" => [0x0224A], - "aring" => [0x000E5], - "ascr" => [0x1D4B6], - "ast" => [0x0002A], - "asymp" => [0x02248], - "asympeq" => [0x0224D], - "atilde" => [0x000E3], - "auml" => [0x000E4], - "awconint" => [0x02233], - "awint" => [0x02A11], - "bNot" => [0x02AED], - "backcong" => [0x0224C], - "backepsilon" => [0x003F6], - "backprime" => [0x02035], - "backsim" => [0x0223D], - "backsimeq" => [0x022CD], - "barvee" => [0x022BD], - "barwed" => [0x02305], - "barwedge" => [0x02305], - "bbrk" => [0x023B5], - "bbrktbrk" => [0x023B6], - "bcong" => [0x0224C], - "bcy" => [0x00431], - "bdquo" => [0x0201E], - "becaus" => [0x02235], - "because" => [0x02235], - "bemptyv" => [0x029B0], - "bepsi" => [0x003F6], - "bernou" => [0x0212C], - "beta" => [0x003B2], - "beth" => [0x02136], - "between" => [0x0226C], - "bfr" => [0x1D51F], - "bigcap" => [0x022C2], - "bigcirc" => [0x025EF], - "bigcup" => [0x022C3], - "bigodot" => [0x02A00], - "bigoplus" => [0x02A01], - "bigotimes" => [0x02A02], - "bigsqcup" => [0x02A06], - "bigstar" => [0x02605], - "bigtriangledown" => [0x025BD], - "bigtriangleup" => [0x025B3], - "biguplus" => [0x02A04], - "bigvee" => [0x022C1], - "bigwedge" => [0x022C0], - "bkarow" => [0x0290D], - "blacklozenge" => [0x029EB], - "blacksquare" => [0x025AA], - "blacktriangle" => [0x025B4], - "blacktriangledown" => [0x025BE], - "blacktriangleleft" => [0x025C2], - "blacktriangleright" => [0x025B8], - "blank" => [0x02423], - "blk12" => [0x02592], - "blk14" => [0x02591], - "blk34" => [0x02593], - "block" => [0x02588], - "bne" => [0x0003D, 0x020E5], - "bnequiv" => [0x02261, 0x020E5], - "bnot" => [0x02310], - "bopf" => [0x1D553], - "bot" => [0x022A5], - "bottom" => [0x022A5], - "bowtie" => [0x022C8], - "boxDL" => [0x02557], - "boxDR" => [0x02554], - "boxDl" => [0x02556], - "boxDr" => [0x02553], - "boxH" => [0x02550], - "boxHD" => [0x02566], - "boxHU" => [0x02569], - "boxHd" => [0x02564], - "boxHu" => [0x02567], - "boxUL" => [0x0255D], - "boxUR" => [0x0255A], - "boxUl" => [0x0255C], - "boxUr" => [0x02559], - "boxV" => [0x02551], - "boxVH" => [0x0256C], - "boxVL" => [0x02563], - "boxVR" => [0x02560], - "boxVh" => [0x0256B], - "boxVl" => [0x02562], - "boxVr" => [0x0255F], - "boxbox" => [0x029C9], - "boxdL" => [0x02555], - "boxdR" => [0x02552], - "boxdl" => [0x02510], - "boxdr" => [0x0250C], - "boxh" => [0x02500], - "boxhD" => [0x02565], - "boxhU" => [0x02568], - "boxhd" => [0x0252C], - "boxhu" => [0x02534], - "boxminus" => [0x0229F], - "boxplus" => [0x0229E], - "boxtimes" => [0x022A0], - "boxuL" => [0x0255B], - "boxuR" => [0x02558], - "boxul" => [0x02518], - "boxur" => [0x02514], - "boxv" => [0x02502], - "boxvH" => [0x0256A], - "boxvL" => [0x02561], - "boxvR" => [0x0255E], - "boxvh" => [0x0253C], - "boxvl" => [0x02524], - "boxvr" => [0x0251C], - "bprime" => [0x02035], - "breve" => [0x002D8], - "brvbar" => [0x000A6], - "bscr" => [0x1D4B7], - "bsemi" => [0x0204F], - "bsim" => [0x0223D], - "bsime" => [0x022CD], - "bsol" => [0x0005C], - "bsolb" => [0x029C5], - "bsolhsub" => [0x027C8], - "bull" => [0x02022], - "bullet" => [0x02022], - "bump" => [0x0224E], - "bumpE" => [0x02AAE], - "bumpe" => [0x0224F], - "bumpeq" => [0x0224F], - "cacute" => [0x00107], - "cap" => [0x02229], - "capand" => [0x02A44], - "capbrcup" => [0x02A49], - "capcap" => [0x02A4B], - "capcup" => [0x02A47], - "capdot" => [0x02A40], - "caps" => [0x02229, 0x0FE00], - "caret" => [0x02041], - "caron" => [0x002C7], - "ccaps" => [0x02A4D], - "ccaron" => [0x0010D], - "ccedil" => [0x000E7], - "ccirc" => [0x00109], - "ccups" => [0x02A4C], - "ccupssm" => [0x02A50], - "cdot" => [0x0010B], - "cedil" => [0x000B8], - "cemptyv" => [0x029B2], - "cent" => [0x000A2], - "centerdot" => [0x000B7], - "cfr" => [0x1D520], - "chcy" => [0x00447], - "check" => [0x02713], - "checkmark" => [0x02713], - "chi" => [0x003C7], - "cir" => [0x025CB], - "cirE" => [0x029C3], - "circ" => [0x002C6], - "circeq" => [0x02257], - "circlearrowleft" => [0x021BA], - "circlearrowright" => [0x021BB], - "circledR" => [0x000AE], - "circledS" => [0x024C8], - "circledast" => [0x0229B], - "circledcirc" => [0x0229A], - "circleddash" => [0x0229D], - "cire" => [0x02257], - "cirfnint" => [0x02A10], - "cirmid" => [0x02AEF], - "cirscir" => [0x029C2], - "clubs" => [0x02663], - "clubsuit" => [0x02663], - "colon" => [0x0003A], - "colone" => [0x02254], - "coloneq" => [0x02254], - "comma" => [0x0002C], - "commat" => [0x00040], - "comp" => [0x02201], - "compfn" => [0x02218], - "complement" => [0x02201], - "complexes" => [0x02102], - "cong" => [0x02245], - "congdot" => [0x02A6D], - "conint" => [0x0222E], - "copf" => [0x1D554], - "coprod" => [0x02210], - "copy" => [0x000A9], - "copysr" => [0x02117], - "crarr" => [0x021B5], - "cross" => [0x02717], - "cscr" => [0x1D4B8], - "csub" => [0x02ACF], - "csube" => [0x02AD1], - "csup" => [0x02AD0], - "csupe" => [0x02AD2], - "ctdot" => [0x022EF], - "cudarrl" => [0x02938], - "cudarrr" => [0x02935], - "cuepr" => [0x022DE], - "cuesc" => [0x022DF], - "cularr" => [0x021B6], - "cularrp" => [0x0293D], - "cup" => [0x0222A], - "cupbrcap" => [0x02A48], - "cupcap" => [0x02A46], - "cupcup" => [0x02A4A], - "cupdot" => [0x0228D], - "cupor" => [0x02A45], - "cups" => [0x0222A, 0x0FE00], - "curarr" => [0x021B7], - "curarrm" => [0x0293C], - "curlyeqprec" => [0x022DE], - "curlyeqsucc" => [0x022DF], - "curlyvee" => [0x022CE], - "curlywedge" => [0x022CF], - "curren" => [0x000A4], - "curvearrowleft" => [0x021B6], - "curvearrowright" => [0x021B7], - "cuvee" => [0x022CE], - "cuwed" => [0x022CF], - "cwconint" => [0x02232], - "cwint" => [0x02231], - "cylcty" => [0x0232D], - "dArr" => [0x021D3], - "dHar" => [0x02965], - "dagger" => [0x02020], - "daleth" => [0x02138], - "darr" => [0x02193], - "dash" => [0x02010], - "dashv" => [0x022A3], - "dbkarow" => [0x0290F], - "dblac" => [0x002DD], - "dcaron" => [0x0010F], - "dcy" => [0x00434], - "dd" => [0x02146], - "ddagger" => [0x02021], - "ddarr" => [0x021CA], - "ddotseq" => [0x02A77], - "deg" => [0x000B0], - "delta" => [0x003B4], - "demptyv" => [0x029B1], - "dfisht" => [0x0297F], - "dfr" => [0x1D521], - "dharl" => [0x021C3], - "dharr" => [0x021C2], - "diam" => [0x022C4], - "diamond" => [0x022C4], - "diamondsuit" => [0x02666], - "diams" => [0x02666], - "die" => [0x000A8], - "digamma" => [0x003DD], - "disin" => [0x022F2], - "div" => [0x000F7], - "divide" => [0x000F7], - "divideontimes" => [0x022C7], - "divonx" => [0x022C7], - "djcy" => [0x00452], - "dlcorn" => [0x0231E], - "dlcrop" => [0x0230D], - "dollar" => [0x00024], - "dopf" => [0x1D555], - "dot" => [0x002D9], - "doteq" => [0x02250], - "doteqdot" => [0x02251], - "dotminus" => [0x02238], - "dotplus" => [0x02214], - "dotsquare" => [0x022A1], - "doublebarwedge" => [0x02306], - "downarrow" => [0x02193], - "downdownarrows" => [0x021CA], - "downharpoonleft" => [0x021C3], - "downharpoonright" => [0x021C2], - "drbkarow" => [0x02910], - "drcorn" => [0x0231F], - "drcrop" => [0x0230C], - "dscr" => [0x1D4B9], - "dscy" => [0x00455], - "dsol" => [0x029F6], - "dstrok" => [0x00111], - "dtdot" => [0x022F1], - "dtri" => [0x025BF], - "dtrif" => [0x025BE], - "duarr" => [0x021F5], - "duhar" => [0x0296F], - "dwangle" => [0x029A6], - "dzcy" => [0x0045F], - "dzigrarr" => [0x027FF], - "eDDot" => [0x02A77], - "eDot" => [0x02251], - "eacute" => [0x000E9], - "easter" => [0x02A6E], - "ecaron" => [0x0011B], - "ecir" => [0x02256], - "ecirc" => [0x000EA], - "ecolon" => [0x02255], - "ecy" => [0x0044D], - "edot" => [0x00117], - "ee" => [0x02147], - "efDot" => [0x02252], - "efr" => [0x1D522], - "eg" => [0x02A9A], - "egrave" => [0x000E8], - "egs" => [0x02A96], - "egsdot" => [0x02A98], - "el" => [0x02A99], - "elinters" => [0x023E7], - "ell" => [0x02113], - "els" => [0x02A95], - "elsdot" => [0x02A97], - "emacr" => [0x00113], - "empty" => [0x02205], - "emptyset" => [0x02205], - "emptyv" => [0x02205], - "emsp" => [0x02003], - "emsp13" => [0x02004], - "emsp14" => [0x02005], - "eng" => [0x0014B], - "ensp" => [0x02002], - "eogon" => [0x00119], - "eopf" => [0x1D556], - "epar" => [0x022D5], - "eparsl" => [0x029E3], - "eplus" => [0x02A71], - "epsi" => [0x003B5], - "epsilon" => [0x003B5], - "epsiv" => [0x003F5], - "eqcirc" => [0x02256], - "eqcolon" => [0x02255], - "eqsim" => [0x02242], - "eqslantgtr" => [0x02A96], - "eqslantless" => [0x02A95], - "equals" => [0x0003D], - "equest" => [0x0225F], - "equiv" => [0x02261], - "equivDD" => [0x02A78], - "eqvparsl" => [0x029E5], - "erDot" => [0x02253], - "erarr" => [0x02971], - "escr" => [0x0212F], - "esdot" => [0x02250], - "esim" => [0x02242], - "eta" => [0x003B7], - "eth" => [0x000F0], - "euml" => [0x000EB], - "euro" => [0x020AC], - "excl" => [0x00021], - "exist" => [0x02203], - "expectation" => [0x02130], - "exponentiale" => [0x02147], - "fallingdotseq" => [0x02252], - "fcy" => [0x00444], - "female" => [0x02640], - "ffilig" => [0x0FB03], - "fflig" => [0x0FB00], - "ffllig" => [0x0FB04], - "ffr" => [0x1D523], - "filig" => [0x0FB01], - "fjlig" => [0x00066, 0x0006A], - "flat" => [0x0266D], - "fllig" => [0x0FB02], - "fltns" => [0x025B1], - "fnof" => [0x00192], - "fopf" => [0x1D557], - "forall" => [0x02200], - "fork" => [0x022D4], - "forkv" => [0x02AD9], - "fpartint" => [0x02A0D], - "frac12" => [0x000BD], - "frac13" => [0x02153], - "frac14" => [0x000BC], - "frac15" => [0x02155], - "frac16" => [0x02159], - "frac18" => [0x0215B], - "frac23" => [0x02154], - "frac25" => [0x02156], - "frac34" => [0x000BE], - "frac35" => [0x02157], - "frac38" => [0x0215C], - "frac45" => [0x02158], - "frac56" => [0x0215A], - "frac58" => [0x0215D], - "frac78" => [0x0215E], - "frasl" => [0x02044], - "frown" => [0x02322], - "fscr" => [0x1D4BB], - "gE" => [0x02267], - "gEl" => [0x02A8C], - "gacute" => [0x001F5], - "gamma" => [0x003B3], - "gammad" => [0x003DD], - "gap" => [0x02A86], - "gbreve" => [0x0011F], - "gcirc" => [0x0011D], - "gcy" => [0x00433], - "gdot" => [0x00121], - "ge" => [0x02265], - "gel" => [0x022DB], - "geq" => [0x02265], - "geqq" => [0x02267], - "geqslant" => [0x02A7E], - "ges" => [0x02A7E], - "gescc" => [0x02AA9], - "gesdot" => [0x02A80], - "gesdoto" => [0x02A82], - "gesdotol" => [0x02A84], - "gesl" => [0x022DB, 0x0FE00], - "gesles" => [0x02A94], - "gfr" => [0x1D524], - "gg" => [0x0226B], - "ggg" => [0x022D9], - "gimel" => [0x02137], - "gjcy" => [0x00453], - "gl" => [0x02277], - "glE" => [0x02A92], - "gla" => [0x02AA5], - "glj" => [0x02AA4], - "gnE" => [0x02269], - "gnap" => [0x02A8A], - "gnapprox" => [0x02A8A], - "gne" => [0x02A88], - "gneq" => [0x02A88], - "gneqq" => [0x02269], - "gnsim" => [0x022E7], - "gopf" => [0x1D558], - "grave" => [0x00060], - "gscr" => [0x0210A], - "gsim" => [0x02273], - "gsime" => [0x02A8E], - "gsiml" => [0x02A90], - "gt" => [0x0003E], - "gtcc" => [0x02AA7], - "gtcir" => [0x02A7A], - "gtdot" => [0x022D7], - "gtlPar" => [0x02995], - "gtquest" => [0x02A7C], - "gtrapprox" => [0x02A86], - "gtrarr" => [0x02978], - "gtrdot" => [0x022D7], - "gtreqless" => [0x022DB], - "gtreqqless" => [0x02A8C], - "gtrless" => [0x02277], - "gtrsim" => [0x02273], - "gvertneqq" => [0x02269, 0x0FE00], - "gvnE" => [0x02269, 0x0FE00], - "hArr" => [0x021D4], - "hairsp" => [0x0200A], - "half" => [0x000BD], - "hamilt" => [0x0210B], - "hardcy" => [0x0044A], - "harr" => [0x02194], - "harrcir" => [0x02948], - "harrw" => [0x021AD], - "hbar" => [0x0210F], - "hcirc" => [0x00125], - "hearts" => [0x02665], - "heartsuit" => [0x02665], - "hellip" => [0x02026], - "hercon" => [0x022B9], - "hfr" => [0x1D525], - "hksearow" => [0x02925], - "hkswarow" => [0x02926], - "hoarr" => [0x021FF], - "homtht" => [0x0223B], - "hookleftarrow" => [0x021A9], - "hookrightarrow" => [0x021AA], - "hopf" => [0x1D559], - "horbar" => [0x02015], - "hscr" => [0x1D4BD], - "hslash" => [0x0210F], - "hstrok" => [0x00127], - "hybull" => [0x02043], - "hyphen" => [0x02010], - "iacute" => [0x000ED], - "ic" => [0x02063], - "icirc" => [0x000EE], - "icy" => [0x00438], - "iecy" => [0x00435], - "iexcl" => [0x000A1], - "iff" => [0x021D4], - "ifr" => [0x1D526], - "igrave" => [0x000EC], - "ii" => [0x02148], - "iiiint" => [0x02A0C], - "iiint" => [0x0222D], - "iinfin" => [0x029DC], - "iiota" => [0x02129], - "ijlig" => [0x00133], - "imacr" => [0x0012B], - "image" => [0x02111], - "imagline" => [0x02110], - "imagpart" => [0x02111], - "imath" => [0x00131], - "imof" => [0x022B7], - "imped" => [0x001B5], - "in" => [0x02208], - "incare" => [0x02105], - "infin" => [0x0221E], - "infintie" => [0x029DD], - "inodot" => [0x00131], - "int" => [0x0222B], - "intcal" => [0x022BA], - "integers" => [0x02124], - "intercal" => [0x022BA], - "intlarhk" => [0x02A17], - "intprod" => [0x02A3C], - "iocy" => [0x00451], - "iogon" => [0x0012F], - "iopf" => [0x1D55A], - "iota" => [0x003B9], - "iprod" => [0x02A3C], - "iquest" => [0x000BF], - "iscr" => [0x1D4BE], - "isin" => [0x02208], - "isinE" => [0x022F9], - "isindot" => [0x022F5], - "isins" => [0x022F4], - "isinsv" => [0x022F3], - "isinv" => [0x02208], - "it" => [0x02062], - "itilde" => [0x00129], - "iukcy" => [0x00456], - "iuml" => [0x000EF], - "jcirc" => [0x00135], - "jcy" => [0x00439], - "jfr" => [0x1D527], - "jmath" => [0x00237], - "jopf" => [0x1D55B], - "jscr" => [0x1D4BF], - "jsercy" => [0x00458], - "jukcy" => [0x00454], - "kappa" => [0x003BA], - "kappav" => [0x003F0], - "kcedil" => [0x00137], - "kcy" => [0x0043A], - "kfr" => [0x1D528], - "kgreen" => [0x00138], - "khcy" => [0x00445], - "kjcy" => [0x0045C], - "kopf" => [0x1D55C], - "kscr" => [0x1D4C0], - "lAarr" => [0x021DA], - "lArr" => [0x021D0], - "lAtail" => [0x0291B], - "lBarr" => [0x0290E], - "lE" => [0x02266], - "lEg" => [0x02A8B], - "lHar" => [0x02962], - "lacute" => [0x0013A], - "laemptyv" => [0x029B4], - "lagran" => [0x02112], - "lambda" => [0x003BB], - "lang" => [0x027E8], - "langd" => [0x02991], - "langle" => [0x027E8], - "lap" => [0x02A85], - "laquo" => [0x000AB], - "larr" => [0x02190], - "larrb" => [0x021E4], - "larrbfs" => [0x0291F], - "larrfs" => [0x0291D], - "larrhk" => [0x021A9], - "larrlp" => [0x021AB], - "larrpl" => [0x02939], - "larrsim" => [0x02973], - "larrtl" => [0x021A2], - "lat" => [0x02AAB], - "latail" => [0x02919], - "late" => [0x02AAD], - "lates" => [0x02AAD, 0x0FE00], - "lbarr" => [0x0290C], - "lbbrk" => [0x02772], - "lbrace" => [0x0007B], - "lbrack" => [0x0005B], - "lbrke" => [0x0298B], - "lbrksld" => [0x0298F], - "lbrkslu" => [0x0298D], - "lcaron" => [0x0013E], - "lcedil" => [0x0013C], - "lceil" => [0x02308], - "lcub" => [0x0007B], - "lcy" => [0x0043B], - "ldca" => [0x02936], - "ldquo" => [0x0201C], - "ldquor" => [0x0201E], - "ldrdhar" => [0x02967], - "ldrushar" => [0x0294B], - "ldsh" => [0x021B2], - "le" => [0x02264], - "leftarrow" => [0x02190], - "leftarrowtail" => [0x021A2], - "leftharpoondown" => [0x021BD], - "leftharpoonup" => [0x021BC], - "leftleftarrows" => [0x021C7], - "leftrightarrow" => [0x02194], - "leftrightarrows" => [0x021C6], - "leftrightharpoons" => [0x021CB], - "leftrightsquigarrow" => [0x021AD], - "leftthreetimes" => [0x022CB], - "leg" => [0x022DA], - "leq" => [0x02264], - "leqq" => [0x02266], - "leqslant" => [0x02A7D], - "les" => [0x02A7D], - "lescc" => [0x02AA8], - "lesdot" => [0x02A7F], - "lesdoto" => [0x02A81], - "lesdotor" => [0x02A83], - "lesg" => [0x022DA, 0x0FE00], - "lesges" => [0x02A93], - "lessapprox" => [0x02A85], - "lessdot" => [0x022D6], - "lesseqgtr" => [0x022DA], - "lesseqqgtr" => [0x02A8B], - "lessgtr" => [0x02276], - "lesssim" => [0x02272], - "lfisht" => [0x0297C], - "lfloor" => [0x0230A], - "lfr" => [0x1D529], - "lg" => [0x02276], - "lgE" => [0x02A91], - "lhard" => [0x021BD], - "lharu" => [0x021BC], - "lharul" => [0x0296A], - "lhblk" => [0x02584], - "ljcy" => [0x00459], - "ll" => [0x0226A], - "llarr" => [0x021C7], - "llcorner" => [0x0231E], - "llhard" => [0x0296B], - "lltri" => [0x025FA], - "lmidot" => [0x00140], - "lmoust" => [0x023B0], - "lmoustache" => [0x023B0], - "lnE" => [0x02268], - "lnap" => [0x02A89], - "lnapprox" => [0x02A89], - "lne" => [0x02A87], - "lneq" => [0x02A87], - "lneqq" => [0x02268], - "lnsim" => [0x022E6], - "loang" => [0x027EC], - "loarr" => [0x021FD], - "lobrk" => [0x027E6], - "longleftarrow" => [0x027F5], - "longleftrightarrow" => [0x027F7], - "longmapsto" => [0x027FC], - "longrightarrow" => [0x027F6], - "looparrowleft" => [0x021AB], - "looparrowright" => [0x021AC], - "lopar" => [0x02985], - "lopf" => [0x1D55D], - "loplus" => [0x02A2D], - "lotimes" => [0x02A34], - "lowast" => [0x02217], - "lowbar" => [0x0005F], - "loz" => [0x025CA], - "lozenge" => [0x025CA], - "lozf" => [0x029EB], - "lpar" => [0x00028], - "lparlt" => [0x02993], - "lrarr" => [0x021C6], - "lrcorner" => [0x0231F], - "lrhar" => [0x021CB], - "lrhard" => [0x0296D], - "lrm" => [0x0200E], - "lrtri" => [0x022BF], - "lsaquo" => [0x02039], - "lscr" => [0x1D4C1], - "lsh" => [0x021B0], - "lsim" => [0x02272], - "lsime" => [0x02A8D], - "lsimg" => [0x02A8F], - "lsqb" => [0x0005B], - "lsquo" => [0x02018], - "lsquor" => [0x0201A], - "lstrok" => [0x00142], - "lt" => [0x0003C], - "ltcc" => [0x02AA6], - "ltcir" => [0x02A79], - "ltdot" => [0x022D6], - "lthree" => [0x022CB], - "ltimes" => [0x022C9], - "ltlarr" => [0x02976], - "ltquest" => [0x02A7B], - "ltrPar" => [0x02996], - "ltri" => [0x025C3], - "ltrie" => [0x022B4], - "ltrif" => [0x025C2], - "lurdshar" => [0x0294A], - "luruhar" => [0x02966], - "lvertneqq" => [0x02268, 0x0FE00], - "lvnE" => [0x02268, 0x0FE00], - "mDDot" => [0x0223A], - "macr" => [0x000AF], - "male" => [0x02642], - "malt" => [0x02720], - "maltese" => [0x02720], - "map" => [0x021A6], - "mapsto" => [0x021A6], - "mapstodown" => [0x021A7], - "mapstoleft" => [0x021A4], - "mapstoup" => [0x021A5], - "marker" => [0x025AE], - "mcomma" => [0x02A29], - "mcy" => [0x0043C], - "mdash" => [0x02014], - "measuredangle" => [0x02221], - "mfr" => [0x1D52A], - "mho" => [0x02127], - "micro" => [0x000B5], - "mid" => [0x02223], - "midast" => [0x0002A], - "midcir" => [0x02AF0], - "middot" => [0x000B7], - "minus" => [0x02212], - "minusb" => [0x0229F], - "minusd" => [0x02238], - "minusdu" => [0x02A2A], - "mlcp" => [0x02ADB], - "mldr" => [0x02026], - "mnplus" => [0x02213], - "models" => [0x022A7], - "mopf" => [0x1D55E], - "mp" => [0x02213], - "mscr" => [0x1D4C2], - "mstpos" => [0x0223E], - "mu" => [0x003BC], - "multimap" => [0x022B8], - "mumap" => [0x022B8], - "nGg" => [0x022D9, 0x00338], - "nGt" => [0x0226B, 0x020D2], - "nGtv" => [0x0226B, 0x00338], - "nLeftarrow" => [0x021CD], - "nLeftrightarrow" => [0x021CE], - "nLl" => [0x022D8, 0x00338], - "nLt" => [0x0226A, 0x020D2], - "nLtv" => [0x0226A, 0x00338], - "nRightarrow" => [0x021CF], - "nVDash" => [0x022AF], - "nVdash" => [0x022AE], - "nabla" => [0x02207], - "nacute" => [0x00144], - "nang" => [0x02220, 0x020D2], - "nap" => [0x02249], - "napE" => [0x02A70, 0x00338], - "napid" => [0x0224B, 0x00338], - "napos" => [0x00149], - "napprox" => [0x02249], - "natur" => [0x0266E], - "natural" => [0x0266E], - "naturals" => [0x02115], - "nbsp" => [0x000A0], - "nbump" => [0x0224E, 0x00338], - "nbumpe" => [0x0224F, 0x00338], - "ncap" => [0x02A43], - "ncaron" => [0x00148], - "ncedil" => [0x00146], - "ncong" => [0x02247], - "ncongdot" => [0x02A6D, 0x00338], - "ncup" => [0x02A42], - "ncy" => [0x0043D], - "ndash" => [0x02013], - "ne" => [0x02260], - "neArr" => [0x021D7], - "nearhk" => [0x02924], - "nearr" => [0x02197], - "nearrow" => [0x02197], - "nedot" => [0x02250, 0x00338], - "nequiv" => [0x02262], - "nesear" => [0x02928], - "nesim" => [0x02242, 0x00338], - "nexist" => [0x02204], - "nexists" => [0x02204], - "nfr" => [0x1D52B], - "ngE" => [0x02267, 0x00338], - "nge" => [0x02271], - "ngeq" => [0x02271], - "ngeqq" => [0x02267, 0x00338], - "ngeqslant" => [0x02A7E, 0x00338], - "nges" => [0x02A7E, 0x00338], - "ngsim" => [0x02275], - "ngt" => [0x0226F], - "ngtr" => [0x0226F], - "nhArr" => [0x021CE], - "nharr" => [0x021AE], - "nhpar" => [0x02AF2], - "ni" => [0x0220B], - "nis" => [0x022FC], - "nisd" => [0x022FA], - "niv" => [0x0220B], - "njcy" => [0x0045A], - "nlArr" => [0x021CD], - "nlE" => [0x02266, 0x00338], - "nlarr" => [0x0219A], - "nldr" => [0x02025], - "nle" => [0x02270], - "nleftarrow" => [0x0219A], - "nleftrightarrow" => [0x021AE], - "nleq" => [0x02270], - "nleqq" => [0x02266, 0x00338], - "nleqslant" => [0x02A7D, 0x00338], - "nles" => [0x02A7D, 0x00338], - "nless" => [0x0226E], - "nlsim" => [0x02274], - "nlt" => [0x0226E], - "nltri" => [0x022EA], - "nltrie" => [0x022EC], - "nmid" => [0x02224], - "nopf" => [0x1D55F], - "not" => [0x000AC], - "notin" => [0x02209], - "notinE" => [0x022F9, 0x00338], - "notindot" => [0x022F5, 0x00338], - "notinva" => [0x02209], - "notinvb" => [0x022F7], - "notinvc" => [0x022F6], - "notni" => [0x0220C], - "notniva" => [0x0220C], - "notnivb" => [0x022FE], - "notnivc" => [0x022FD], - "npar" => [0x02226], - "nparallel" => [0x02226], - "nparsl" => [0x02AFD, 0x020E5], - "npart" => [0x02202, 0x00338], - "npolint" => [0x02A14], - "npr" => [0x02280], - "nprcue" => [0x022E0], - "npre" => [0x02AAF, 0x00338], - "nprec" => [0x02280], - "npreceq" => [0x02AAF, 0x00338], - "nrArr" => [0x021CF], - "nrarr" => [0x0219B], - "nrarrc" => [0x02933, 0x00338], - "nrarrw" => [0x0219D, 0x00338], - "nrightarrow" => [0x0219B], - "nrtri" => [0x022EB], - "nrtrie" => [0x022ED], - "nsc" => [0x02281], - "nsccue" => [0x022E1], - "nsce" => [0x02AB0, 0x00338], - "nscr" => [0x1D4C3], - "nshortmid" => [0x02224], - "nshortparallel" => [0x02226], - "nsim" => [0x02241], - "nsime" => [0x02244], - "nsimeq" => [0x02244], - "nsmid" => [0x02224], - "nspar" => [0x02226], - "nsqsube" => [0x022E2], - "nsqsupe" => [0x022E3], - "nsub" => [0x02284], - "nsubE" => [0x02AC5, 0x00338], - "nsube" => [0x02288], - "nsubset" => [0x02282, 0x020D2], - "nsubseteq" => [0x02288], - "nsubseteqq" => [0x02AC5, 0x00338], - "nsucc" => [0x02281], - "nsucceq" => [0x02AB0, 0x00338], - "nsup" => [0x02285], - "nsupE" => [0x02AC6, 0x00338], - "nsupe" => [0x02289], - "nsupset" => [0x02283, 0x020D2], - "nsupseteq" => [0x02289], - "nsupseteqq" => [0x02AC6, 0x00338], - "ntgl" => [0x02279], - "ntilde" => [0x000F1], - "ntlg" => [0x02278], - "ntriangleleft" => [0x022EA], - "ntrianglelefteq" => [0x022EC], - "ntriangleright" => [0x022EB], - "ntrianglerighteq" => [0x022ED], - "nu" => [0x003BD], - "num" => [0x00023], - "numero" => [0x02116], - "numsp" => [0x02007], - "nvDash" => [0x022AD], - "nvHarr" => [0x02904], - "nvap" => [0x0224D, 0x020D2], - "nvdash" => [0x022AC], - "nvge" => [0x02265, 0x020D2], - "nvgt" => [0x0003E, 0x020D2], - "nvinfin" => [0x029DE], - "nvlArr" => [0x02902], - "nvle" => [0x02264, 0x020D2], - "nvlt" => [0x0003C, 0x020D2], - "nvltrie" => [0x022B4, 0x020D2], - "nvrArr" => [0x02903], - "nvrtrie" => [0x022B5, 0x020D2], - "nvsim" => [0x0223C, 0x020D2], - "nwArr" => [0x021D6], - "nwarhk" => [0x02923], - "nwarr" => [0x02196], - "nwarrow" => [0x02196], - "nwnear" => [0x02927], - "oS" => [0x024C8], - "oacute" => [0x000F3], - "oast" => [0x0229B], - "ocir" => [0x0229A], - "ocirc" => [0x000F4], - "ocy" => [0x0043E], - "odash" => [0x0229D], - "odblac" => [0x00151], - "odiv" => [0x02A38], - "odot" => [0x02299], - "odsold" => [0x029BC], - "oelig" => [0x00153], - "ofcir" => [0x029BF], - "ofr" => [0x1D52C], - "ogon" => [0x002DB], - "ograve" => [0x000F2], - "ogt" => [0x029C1], - "ohbar" => [0x029B5], - "ohm" => [0x003A9], - "oint" => [0x0222E], - "olarr" => [0x021BA], - "olcir" => [0x029BE], - "olcross" => [0x029BB], - "oline" => [0x0203E], - "olt" => [0x029C0], - "omacr" => [0x0014D], - "omega" => [0x003C9], - "omicron" => [0x003BF], - "omid" => [0x029B6], - "ominus" => [0x02296], - "oopf" => [0x1D560], - "opar" => [0x029B7], - "operp" => [0x029B9], - "oplus" => [0x02295], - "or" => [0x02228], - "orarr" => [0x021BB], - "ord" => [0x02A5D], - "order" => [0x02134], - "orderof" => [0x02134], - "ordf" => [0x000AA], - "ordm" => [0x000BA], - "origof" => [0x022B6], - "oror" => [0x02A56], - "orslope" => [0x02A57], - "orv" => [0x02A5B], - "oscr" => [0x02134], - "oslash" => [0x000F8], - "osol" => [0x02298], - "otilde" => [0x000F5], - "otimes" => [0x02297], - "otimesas" => [0x02A36], - "ouml" => [0x000F6], - "ovbar" => [0x0233D], - "par" => [0x02225], - "para" => [0x000B6], - "parallel" => [0x02225], - "parsim" => [0x02AF3], - "parsl" => [0x02AFD], - "part" => [0x02202], - "pcy" => [0x0043F], - "percnt" => [0x00025], - "period" => [0x0002E], - "permil" => [0x02030], - "perp" => [0x022A5], - "pertenk" => [0x02031], - "pfr" => [0x1D52D], - "phi" => [0x003C6], - "phiv" => [0x003D5], - "phmmat" => [0x02133], - "phone" => [0x0260E], - "pi" => [0x003C0], - "pitchfork" => [0x022D4], - "piv" => [0x003D6], - "planck" => [0x0210F], - "planckh" => [0x0210E], - "plankv" => [0x0210F], - "plus" => [0x0002B], - "plusacir" => [0x02A23], - "plusb" => [0x0229E], - "pluscir" => [0x02A22], - "plusdo" => [0x02214], - "plusdu" => [0x02A25], - "pluse" => [0x02A72], - "plusmn" => [0x000B1], - "plussim" => [0x02A26], - "plustwo" => [0x02A27], - "pm" => [0x000B1], - "pointint" => [0x02A15], - "popf" => [0x1D561], - "pound" => [0x000A3], - "pr" => [0x0227A], - "prE" => [0x02AB3], - "prap" => [0x02AB7], - "prcue" => [0x0227C], - "pre" => [0x02AAF], - "prec" => [0x0227A], - "precapprox" => [0x02AB7], - "preccurlyeq" => [0x0227C], - "preceq" => [0x02AAF], - "precnapprox" => [0x02AB9], - "precneqq" => [0x02AB5], - "precnsim" => [0x022E8], - "precsim" => [0x0227E], - "prime" => [0x02032], - "primes" => [0x02119], - "prnE" => [0x02AB5], - "prnap" => [0x02AB9], - "prnsim" => [0x022E8], - "prod" => [0x0220F], - "profalar" => [0x0232E], - "profline" => [0x02312], - "profsurf" => [0x02313], - "prop" => [0x0221D], - "propto" => [0x0221D], - "prsim" => [0x0227E], - "prurel" => [0x022B0], - "pscr" => [0x1D4C5], - "psi" => [0x003C8], - "puncsp" => [0x02008], - "qfr" => [0x1D52E], - "qint" => [0x02A0C], - "qopf" => [0x1D562], - "qprime" => [0x02057], - "qscr" => [0x1D4C6], - "quaternions" => [0x0210D], - "quatint" => [0x02A16], - "quest" => [0x0003F], - "questeq" => [0x0225F], - "quot" => [0x00022], - "rAarr" => [0x021DB], - "rArr" => [0x021D2], - "rAtail" => [0x0291C], - "rBarr" => [0x0290F], - "rHar" => [0x02964], - "race" => [0x0223D, 0x00331], - "racute" => [0x00155], - "radic" => [0x0221A], - "raemptyv" => [0x029B3], - "rang" => [0x027E9], - "rangd" => [0x02992], - "range" => [0x029A5], - "rangle" => [0x027E9], - "raquo" => [0x000BB], - "rarr" => [0x02192], - "rarrap" => [0x02975], - "rarrb" => [0x021E5], - "rarrbfs" => [0x02920], - "rarrc" => [0x02933], - "rarrfs" => [0x0291E], - "rarrhk" => [0x021AA], - "rarrlp" => [0x021AC], - "rarrpl" => [0x02945], - "rarrsim" => [0x02974], - "rarrtl" => [0x021A3], - "rarrw" => [0x0219D], - "ratail" => [0x0291A], - "ratio" => [0x02236], - "rationals" => [0x0211A], - "rbarr" => [0x0290D], - "rbbrk" => [0x02773], - "rbrace" => [0x0007D], - "rbrack" => [0x0005D], - "rbrke" => [0x0298C], - "rbrksld" => [0x0298E], - "rbrkslu" => [0x02990], - "rcaron" => [0x00159], - "rcedil" => [0x00157], - "rceil" => [0x02309], - "rcub" => [0x0007D], - "rcy" => [0x00440], - "rdca" => [0x02937], - "rdldhar" => [0x02969], - "rdquo" => [0x0201D], - "rdquor" => [0x0201D], - "rdsh" => [0x021B3], - "real" => [0x0211C], - "realine" => [0x0211B], - "realpart" => [0x0211C], - "reals" => [0x0211D], - "rect" => [0x025AD], - "reg" => [0x000AE], - "rfisht" => [0x0297D], - "rfloor" => [0x0230B], - "rfr" => [0x1D52F], - "rhard" => [0x021C1], - "rharu" => [0x021C0], - "rharul" => [0x0296C], - "rho" => [0x003C1], - "rhov" => [0x003F1], - "rightarrow" => [0x02192], - "rightarrowtail" => [0x021A3], - "rightharpoondown" => [0x021C1], - "rightharpoonup" => [0x021C0], - "rightleftarrows" => [0x021C4], - "rightleftharpoons" => [0x021CC], - "rightrightarrows" => [0x021C9], - "rightsquigarrow" => [0x0219D], - "rightthreetimes" => [0x022CC], - "ring" => [0x002DA], - "risingdotseq" => [0x02253], - "rlarr" => [0x021C4], - "rlhar" => [0x021CC], - "rlm" => [0x0200F], - "rmoust" => [0x023B1], - "rmoustache" => [0x023B1], - "rnmid" => [0x02AEE], - "roang" => [0x027ED], - "roarr" => [0x021FE], - "robrk" => [0x027E7], - "ropar" => [0x02986], - "ropf" => [0x1D563], - "roplus" => [0x02A2E], - "rotimes" => [0x02A35], - "rpar" => [0x00029], - "rpargt" => [0x02994], - "rppolint" => [0x02A12], - "rrarr" => [0x021C9], - "rsaquo" => [0x0203A], - "rscr" => [0x1D4C7], - "rsh" => [0x021B1], - "rsqb" => [0x0005D], - "rsquo" => [0x02019], - "rsquor" => [0x02019], - "rthree" => [0x022CC], - "rtimes" => [0x022CA], - "rtri" => [0x025B9], - "rtrie" => [0x022B5], - "rtrif" => [0x025B8], - "rtriltri" => [0x029CE], - "ruluhar" => [0x02968], - "rx" => [0x0211E], - "sacute" => [0x0015B], - "sbquo" => [0x0201A], - "sc" => [0x0227B], - "scE" => [0x02AB4], - "scap" => [0x02AB8], - "scaron" => [0x00161], - "sccue" => [0x0227D], - "sce" => [0x02AB0], - "scedil" => [0x0015F], - "scirc" => [0x0015D], - "scnE" => [0x02AB6], - "scnap" => [0x02ABA], - "scnsim" => [0x022E9], - "scpolint" => [0x02A13], - "scsim" => [0x0227F], - "scy" => [0x00441], - "sdot" => [0x022C5], - "sdotb" => [0x022A1], - "sdote" => [0x02A66], - "seArr" => [0x021D8], - "searhk" => [0x02925], - "searr" => [0x02198], - "searrow" => [0x02198], - "sect" => [0x000A7], - "semi" => [0x0003B], - "seswar" => [0x02929], - "setminus" => [0x02216], - "setmn" => [0x02216], - "sext" => [0x02736], - "sfr" => [0x1D530], - "sfrown" => [0x02322], - "sharp" => [0x0266F], - "shchcy" => [0x00449], - "shcy" => [0x00448], - "shortmid" => [0x02223], - "shortparallel" => [0x02225], - "shy" => [0x000AD], - "sigma" => [0x003C3], - "sigmaf" => [0x003C2], - "sigmav" => [0x003C2], - "sim" => [0x0223C], - "simdot" => [0x02A6A], - "sime" => [0x02243], - "simeq" => [0x02243], - "simg" => [0x02A9E], - "simgE" => [0x02AA0], - "siml" => [0x02A9D], - "simlE" => [0x02A9F], - "simne" => [0x02246], - "simplus" => [0x02A24], - "simrarr" => [0x02972], - "slarr" => [0x02190], - "smallsetminus" => [0x02216], - "smashp" => [0x02A33], - "smeparsl" => [0x029E4], - "smid" => [0x02223], - "smile" => [0x02323], - "smt" => [0x02AAA], - "smte" => [0x02AAC], - "smtes" => [0x02AAC, 0x0FE00], - "softcy" => [0x0044C], - "sol" => [0x0002F], - "solb" => [0x029C4], - "solbar" => [0x0233F], - "sopf" => [0x1D564], - "spades" => [0x02660], - "spadesuit" => [0x02660], - "spar" => [0x02225], - "sqcap" => [0x02293], - "sqcaps" => [0x02293, 0x0FE00], - "sqcup" => [0x02294], - "sqcups" => [0x02294, 0x0FE00], - "sqsub" => [0x0228F], - "sqsube" => [0x02291], - "sqsubset" => [0x0228F], - "sqsubseteq" => [0x02291], - "sqsup" => [0x02290], - "sqsupe" => [0x02292], - "sqsupset" => [0x02290], - "sqsupseteq" => [0x02292], - "squ" => [0x025A1], - "square" => [0x025A1], - "squarf" => [0x025AA], - "squf" => [0x025AA], - "srarr" => [0x02192], - "sscr" => [0x1D4C8], - "ssetmn" => [0x02216], - "ssmile" => [0x02323], - "sstarf" => [0x022C6], - "star" => [0x02606], - "starf" => [0x02605], - "straightepsilon" => [0x003F5], - "straightphi" => [0x003D5], - "strns" => [0x000AF], - "sub" => [0x02282], - "subE" => [0x02AC5], - "subdot" => [0x02ABD], - "sube" => [0x02286], - "subedot" => [0x02AC3], - "submult" => [0x02AC1], - "subnE" => [0x02ACB], - "subne" => [0x0228A], - "subplus" => [0x02ABF], - "subrarr" => [0x02979], - "subset" => [0x02282], - "subseteq" => [0x02286], - "subseteqq" => [0x02AC5], - "subsetneq" => [0x0228A], - "subsetneqq" => [0x02ACB], - "subsim" => [0x02AC7], - "subsub" => [0x02AD5], - "subsup" => [0x02AD3], - "succ" => [0x0227B], - "succapprox" => [0x02AB8], - "succcurlyeq" => [0x0227D], - "succeq" => [0x02AB0], - "succnapprox" => [0x02ABA], - "succneqq" => [0x02AB6], - "succnsim" => [0x022E9], - "succsim" => [0x0227F], - "sum" => [0x02211], - "sung" => [0x0266A], - "sup" => [0x02283], - "sup1" => [0x000B9], - "sup2" => [0x000B2], - "sup3" => [0x000B3], - "supE" => [0x02AC6], - "supdot" => [0x02ABE], - "supdsub" => [0x02AD8], - "supe" => [0x02287], - "supedot" => [0x02AC4], - "suphsol" => [0x027C9], - "suphsub" => [0x02AD7], - "suplarr" => [0x0297B], - "supmult" => [0x02AC2], - "supnE" => [0x02ACC], - "supne" => [0x0228B], - "supplus" => [0x02AC0], - "supset" => [0x02283], - "supseteq" => [0x02287], - "supseteqq" => [0x02AC6], - "supsetneq" => [0x0228B], - "supsetneqq" => [0x02ACC], - "supsim" => [0x02AC8], - "supsub" => [0x02AD4], - "supsup" => [0x02AD6], - "swArr" => [0x021D9], - "swarhk" => [0x02926], - "swarr" => [0x02199], - "swarrow" => [0x02199], - "swnwar" => [0x0292A], - "szlig" => [0x000DF], - "target" => [0x02316], - "tau" => [0x003C4], - "tbrk" => [0x023B4], - "tcaron" => [0x00165], - "tcedil" => [0x00163], - "tcy" => [0x00442], - "tdot" => [0x020DB], - "telrec" => [0x02315], - "tfr" => [0x1D531], - "there4" => [0x02234], - "therefore" => [0x02234], - "theta" => [0x003B8], - "thetasym" => [0x003D1], - "thetav" => [0x003D1], - "thickapprox" => [0x02248], - "thicksim" => [0x0223C], - "thinsp" => [0x02009], - "thkap" => [0x02248], - "thksim" => [0x0223C], - "thorn" => [0x000FE], - "tilde" => [0x002DC], - "times" => [0x000D7], - "timesb" => [0x022A0], - "timesbar" => [0x02A31], - "timesd" => [0x02A30], - "tint" => [0x0222D], - "toea" => [0x02928], - "top" => [0x022A4], - "topbot" => [0x02336], - "topcir" => [0x02AF1], - "topf" => [0x1D565], - "topfork" => [0x02ADA], - "tosa" => [0x02929], - "tprime" => [0x02034], - "trade" => [0x02122], - "triangle" => [0x025B5], - "triangledown" => [0x025BF], - "triangleleft" => [0x025C3], - "trianglelefteq" => [0x022B4], - "triangleq" => [0x0225C], - "triangleright" => [0x025B9], - "trianglerighteq" => [0x022B5], - "tridot" => [0x025EC], - "trie" => [0x0225C], - "triminus" => [0x02A3A], - "triplus" => [0x02A39], - "trisb" => [0x029CD], - "tritime" => [0x02A3B], - "trpezium" => [0x023E2], - "tscr" => [0x1D4C9], - "tscy" => [0x00446], - "tshcy" => [0x0045B], - "tstrok" => [0x00167], - "twixt" => [0x0226C], - "twoheadleftarrow" => [0x0219E], - "twoheadrightarrow" => [0x021A0], - "uArr" => [0x021D1], - "uHar" => [0x02963], - "uacute" => [0x000FA], - "uarr" => [0x02191], - "ubrcy" => [0x0045E], - "ubreve" => [0x0016D], - "ucirc" => [0x000FB], - "ucy" => [0x00443], - "udarr" => [0x021C5], - "udblac" => [0x00171], - "udhar" => [0x0296E], - "ufisht" => [0x0297E], - "ufr" => [0x1D532], - "ugrave" => [0x000F9], - "uharl" => [0x021BF], - "uharr" => [0x021BE], - "uhblk" => [0x02580], - "ulcorn" => [0x0231C], - "ulcorner" => [0x0231C], - "ulcrop" => [0x0230F], - "ultri" => [0x025F8], - "umacr" => [0x0016B], - "uml" => [0x000A8], - "uogon" => [0x00173], - "uopf" => [0x1D566], - "uparrow" => [0x02191], - "updownarrow" => [0x02195], - "upharpoonleft" => [0x021BF], - "upharpoonright" => [0x021BE], - "uplus" => [0x0228E], - "upsi" => [0x003C5], - "upsih" => [0x003D2], - "upsilon" => [0x003C5], - "upuparrows" => [0x021C8], - "urcorn" => [0x0231D], - "urcorner" => [0x0231D], - "urcrop" => [0x0230E], - "uring" => [0x0016F], - "urtri" => [0x025F9], - "uscr" => [0x1D4CA], - "utdot" => [0x022F0], - "utilde" => [0x00169], - "utri" => [0x025B5], - "utrif" => [0x025B4], - "uuarr" => [0x021C8], - "uuml" => [0x000FC], - "uwangle" => [0x029A7], - "vArr" => [0x021D5], - "vBar" => [0x02AE8], - "vBarv" => [0x02AE9], - "vDash" => [0x022A8], - "vangrt" => [0x0299C], - "varepsilon" => [0x003F5], - "varkappa" => [0x003F0], - "varnothing" => [0x02205], - "varphi" => [0x003D5], - "varpi" => [0x003D6], - "varpropto" => [0x0221D], - "varr" => [0x02195], - "varrho" => [0x003F1], - "varsigma" => [0x003C2], - "varsubsetneq" => [0x0228A, 0x0FE00], - "varsubsetneqq" => [0x02ACB, 0x0FE00], - "varsupsetneq" => [0x0228B, 0x0FE00], - "varsupsetneqq" => [0x02ACC, 0x0FE00], - "vartheta" => [0x003D1], - "vartriangleleft" => [0x022B2], - "vartriangleright" => [0x022B3], - "vcy" => [0x00432], - "vdash" => [0x022A2], - "vee" => [0x02228], - "veebar" => [0x022BB], - "veeeq" => [0x0225A], - "vellip" => [0x022EE], - "verbar" => [0x0007C], - "vert" => [0x0007C], - "vfr" => [0x1D533], - "vltri" => [0x022B2], - "vnsub" => [0x02282, 0x020D2], - "vnsup" => [0x02283, 0x020D2], - "vopf" => [0x1D567], - "vprop" => [0x0221D], - "vrtri" => [0x022B3], - "vscr" => [0x1D4CB], - "vsubnE" => [0x02ACB, 0x0FE00], - "vsubne" => [0x0228A, 0x0FE00], - "vsupnE" => [0x02ACC, 0x0FE00], - "vsupne" => [0x0228B, 0x0FE00], - "vzigzag" => [0x0299A], - "wcirc" => [0x00175], - "wedbar" => [0x02A5F], - "wedge" => [0x02227], - "wedgeq" => [0x02259], - "weierp" => [0x02118], - "wfr" => [0x1D534], - "wopf" => [0x1D568], - "wp" => [0x02118], - "wr" => [0x02240], - "wreath" => [0x02240], - "wscr" => [0x1D4CC], - "xcap" => [0x022C2], - "xcirc" => [0x025EF], - "xcup" => [0x022C3], - "xdtri" => [0x025BD], - "xfr" => [0x1D535], - "xhArr" => [0x027FA], - "xharr" => [0x027F7], - "xi" => [0x003BE], - "xlArr" => [0x027F8], - "xlarr" => [0x027F5], - "xmap" => [0x027FC], - "xnis" => [0x022FB], - "xodot" => [0x02A00], - "xopf" => [0x1D569], - "xoplus" => [0x02A01], - "xotime" => [0x02A02], - "xrArr" => [0x027F9], - "xrarr" => [0x027F6], - "xscr" => [0x1D4CD], - "xsqcup" => [0x02A06], - "xuplus" => [0x02A04], - "xutri" => [0x025B3], - "xvee" => [0x022C1], - "xwedge" => [0x022C0], - "yacute" => [0x000FD], - "yacy" => [0x0044F], - "ycirc" => [0x00177], - "ycy" => [0x0044B], - "yen" => [0x000A5], - "yfr" => [0x1D536], - "yicy" => [0x00457], - "yopf" => [0x1D56A], - "yscr" => [0x1D4CE], - "yucy" => [0x0044E], - "yuml" => [0x000FF], - "zacute" => [0x0017A], - "zcaron" => [0x0017E], - "zcy" => [0x00437], - "zdot" => [0x0017C], - "zeetrf" => [0x02128], - "zeta" => [0x003B6], - "zfr" => [0x1D537], - "zhcy" => [0x00436], - "zigrarr" => [0x021DD], - "zopf" => [0x1D56B], - "zscr" => [0x1D4CF], - "zwj" => [0x0200D], - "zwnj" => [0x0200C], -} diff --git a/lib/rdoc/markdown/literals.rb b/lib/rdoc/markdown/literals.rb deleted file mode 100644 index c5c15d3100..0000000000 --- a/lib/rdoc/markdown/literals.rb +++ /dev/null @@ -1,454 +0,0 @@ -# coding: UTF-8 -# frozen_string_literal: true -# :markup: markdown - -## -# This set of literals is for Ruby 1.9 regular expressions and gives full -# unicode support. -# -# Unlike peg-markdown, this set of literals recognizes Unicode alphanumeric -# characters, newlines and spaces. -class RDoc::Markdown::Literals - # :stopdoc: - - # This is distinct from setup_parser so that a standalone parser - # can redefine #initialize and still have access to the proper - # parser setup code. - def initialize(str, debug=false) - setup_parser(str, debug) - end - - - - # Prepares for parsing +str+. If you define a custom initialize you must - # call this method before #parse - def setup_parser(str, debug=false) - set_string str, 0 - @memoizations = Hash.new { |h,k| h[k] = {} } - @result = nil - @failed_rule = nil - @failing_rule_offset = -1 - @line_offsets = nil - - setup_foreign_grammar - end - - attr_reader :string - attr_reader :failing_rule_offset - attr_accessor :result, :pos - - def current_column(target=pos) - if string[target] == "\n" && (c = string.rindex("\n", target-1) || -1) - return target - c - elsif c = string.rindex("\n", target) - return target - c - end - - target + 1 - end - - def position_line_offsets - unless @position_line_offsets - @position_line_offsets = [] - total = 0 - string.each_line do |line| - total += line.size - @position_line_offsets << total - end - end - @position_line_offsets - end - - if [].respond_to? :bsearch_index - def current_line(target=pos) - if line = position_line_offsets.bsearch_index {|x| x > target } - return line + 1 - end - raise "Target position #{target} is outside of string" - end - else - def current_line(target=pos) - if line = position_line_offsets.index {|x| x > target } - return line + 1 - end - - raise "Target position #{target} is outside of string" - end - end - - def current_character(target=pos) - if target < 0 || target >= string.size - raise "Target position #{target} is outside of string" - end - string[target, 1] - end - - KpegPosInfo = Struct.new(:pos, :lno, :col, :line, :char) - - def current_pos_info(target=pos) - l = current_line target - c = current_column target - ln = get_line(l-1) - chr = string[target,1] - KpegPosInfo.new(target, l, c, ln, chr) - end - - def lines - string.lines - end - - def get_line(no) - loff = position_line_offsets - if no < 0 - raise "Line No is out of range: #{no} < 0" - elsif no >= loff.size - raise "Line No is out of range: #{no} >= #{loff.size}" - end - lend = loff[no]-1 - lstart = no > 0 ? loff[no-1] : 0 - string[lstart..lend] - end - - - - def get_text(start) - @string[start..@pos-1] - end - - # Sets the string and current parsing position for the parser. - def set_string string, pos - @string = string - @string_size = string ? string.size : 0 - @pos = pos - @position_line_offsets = nil - end - - def show_pos - width = 10 - if @pos < width - "#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")" - else - "#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")" - end - end - - def failure_info - l = current_line @failing_rule_offset - c = current_column @failing_rule_offset - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - "line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'" - else - "line #{l}, column #{c}: failed rule '#{@failed_rule}'" - end - end - - def failure_caret - p = current_pos_info @failing_rule_offset - "#{p.line.chomp}\n#{' ' * (p.col - 1)}^" - end - - def failure_character - current_character @failing_rule_offset - end - - def failure_oneline - p = current_pos_info @failing_rule_offset - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - "@#{p.lno}:#{p.col} failed rule '#{info.name}', got '#{p.char}'" - else - "@#{p.lno}:#{p.col} failed rule '#{@failed_rule}', got '#{p.char}'" - end - end - - class ParseError < RuntimeError - end - - def raise_error - raise ParseError, failure_oneline - end - - def show_error(io=STDOUT) - error_pos = @failing_rule_offset - p = current_pos_info(error_pos) - - io.puts "On line #{p.lno}, column #{p.col}:" - - if @failed_rule.kind_of? Symbol - info = self.class::Rules[@failed_rule] - io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')" - else - io.puts "Failed to match rule '#{@failed_rule}'" - end - - io.puts "Got: #{p.char.inspect}" - io.puts "=> #{p.line}" - io.print(" " * (p.col + 2)) - io.puts "^" - end - - def set_failed_rule(name) - if @pos > @failing_rule_offset - @failed_rule = name - @failing_rule_offset = @pos - end - end - - attr_reader :failed_rule - - def match_string(str) - len = str.size - if @string[pos,len] == str - @pos += len - return str - end - - return nil - end - - def scan(reg) - if m = reg.match(@string, @pos) - @pos = m.end(0) - return true - end - - return nil - end - - if "".respond_to? :ord - def get_byte - if @pos >= @string_size - return nil - end - - s = @string[@pos].ord - @pos += 1 - s - end - else - def get_byte - if @pos >= @string_size - return nil - end - - s = @string[@pos] - @pos += 1 - s - end - end - - def parse(rule=nil) - # We invoke the rules indirectly via apply - # instead of by just calling them as methods because - # if the rules use left recursion, apply needs to - # manage that. - - if !rule - apply(:_root) - else - method = rule.gsub("-","_hyphen_") - apply :"_#{method}" - end - end - - class MemoEntry - def initialize(ans, pos) - @ans = ans - @pos = pos - @result = nil - @set = false - @left_rec = false - end - - attr_reader :ans, :pos, :result, :set - attr_accessor :left_rec - - def move!(ans, pos, result) - @ans = ans - @pos = pos - @result = result - @set = true - @left_rec = false - end - end - - def external_invoke(other, rule, *args) - old_pos = @pos - old_string = @string - - set_string other.string, other.pos - - begin - if val = __send__(rule, *args) - other.pos = @pos - other.result = @result - else - other.set_failed_rule "#{self.class}##{rule}" - end - val - ensure - set_string old_string, old_pos - end - end - - def apply_with_args(rule, *args) - @result = nil - memo_key = [rule, args] - if m = @memoizations[memo_key][@pos] - @pos = m.pos - if !m.set - m.left_rec = true - return nil - end - - @result = m.result - - return m.ans - else - m = MemoEntry.new(nil, @pos) - @memoizations[memo_key][@pos] = m - start_pos = @pos - - ans = __send__ rule, *args - - lr = m.left_rec - - m.move! ans, @pos, @result - - # Don't bother trying to grow the left recursion - # if it's failing straight away (thus there is no seed) - if ans and lr - return grow_lr(rule, args, start_pos, m) - else - return ans - end - end - end - - def apply(rule) - @result = nil - if m = @memoizations[rule][@pos] - @pos = m.pos - if !m.set - m.left_rec = true - return nil - end - - @result = m.result - - return m.ans - else - m = MemoEntry.new(nil, @pos) - @memoizations[rule][@pos] = m - start_pos = @pos - - ans = __send__ rule - - lr = m.left_rec - - m.move! ans, @pos, @result - - # Don't bother trying to grow the left recursion - # if it's failing straight away (thus there is no seed) - if ans and lr - return grow_lr(rule, nil, start_pos, m) - else - return ans - end - end - end - - def grow_lr(rule, args, start_pos, m) - while true - @pos = start_pos - @result = m.result - - if args - ans = __send__ rule, *args - else - ans = __send__ rule - end - return nil unless ans - - break if @pos <= m.pos - - m.move! ans, @pos, @result - end - - @result = m.result - @pos = m.pos - return m.ans - end - - class RuleInfo - def initialize(name, rendered) - @name = name - @rendered = rendered - end - - attr_reader :name, :rendered - end - - def self.rule_info(name, rendered) - RuleInfo.new(name, rendered) - end - - - # :startdoc: - # :stopdoc: - def setup_foreign_grammar; end - - # Alphanumeric = /\p{Word}/ - def _Alphanumeric - _tmp = scan(/\G(?-mix:\p{Word})/) - set_failed_rule :_Alphanumeric unless _tmp - return _tmp - end - - # AlphanumericAscii = /[A-Za-z0-9]/ - def _AlphanumericAscii - _tmp = scan(/\G(?-mix:[A-Za-z0-9])/) - set_failed_rule :_AlphanumericAscii unless _tmp - return _tmp - end - - # BOM = "uFEFF" - def _BOM - _tmp = match_string("uFEFF") - set_failed_rule :_BOM unless _tmp - return _tmp - end - - # Newline = /\n|\r\n?|\p{Zl}|\p{Zp}/ - def _Newline - _tmp = scan(/\G(?-mix:\n|\r\n?|\p{Zl}|\p{Zp})/) - set_failed_rule :_Newline unless _tmp - return _tmp - end - - # NonAlphanumeric = /\p{^Word}/ - def _NonAlphanumeric - _tmp = scan(/\G(?-mix:\p{^Word})/) - set_failed_rule :_NonAlphanumeric unless _tmp - return _tmp - end - - # Spacechar = /\t|\p{Zs}/ - def _Spacechar - _tmp = scan(/\G(?-mix:\t|\p{Zs})/) - set_failed_rule :_Spacechar unless _tmp - return _tmp - end - - Rules = {} - Rules[:_Alphanumeric] = rule_info("Alphanumeric", "/\\p{Word}/") - Rules[:_AlphanumericAscii] = rule_info("AlphanumericAscii", "/[A-Za-z0-9]/") - Rules[:_BOM] = rule_info("BOM", "\"uFEFF\"") - Rules[:_Newline] = rule_info("Newline", "/\\n|\\r\\n?|\\p{Zl}|\\p{Zp}/") - Rules[:_NonAlphanumeric] = rule_info("NonAlphanumeric", "/\\p{^Word}/") - Rules[:_Spacechar] = rule_info("Spacechar", "/\\t|\\p{Zs}/") - # :startdoc: -end diff --git a/lib/rdoc/markup.rb b/lib/rdoc/markup.rb deleted file mode 100644 index 3c29870d8a..0000000000 --- a/lib/rdoc/markup.rb +++ /dev/null @@ -1,240 +0,0 @@ -# frozen_string_literal: true -## -# RDoc::Markup parses plain text documents and attempts to decompose them into -# their constituent parts. Some of these parts are high-level: paragraphs, -# chunks of verbatim text, list entries and the like. Other parts happen at -# the character level: a piece of bold text, a word in code font. This markup -# is similar in spirit to that used on WikiWiki webs, where folks create web -# pages using a simple set of formatting rules. -# -# RDoc::Markup and other markup formats do no output formatting, this is -# handled by the RDoc::Markup::Formatter subclasses. -# -# = Markup Formats -# -# +RDoc+ supports these markup formats: -# -# - +rdoc+: -# the +RDoc+ markup format; -# see RDoc::MarkupReference. -# - +markdown+: -# The +markdown+ markup format as described in -# the {Markdown Guide}[https://www.markdownguide.org]; -# see RDoc::Markdown. -# - +rd+: -# the +rd+ markup format format; -# see RDoc::RD. -# - +tomdoc+: -# the TomDoc format as described in -# {TomDoc for Ruby}[http://tomdoc.org]; -# see RDoc::TomDoc. -# -# You can choose a markup format using the following methods: -# -# per project:: -# If you build your documentation with rake use RDoc::Task#markup. -# -# If you build your documentation by hand run: -# -# rdoc --markup your_favorite_format --write-options -# -# and commit <tt>.rdoc_options</tt> and ship it with your packaged gem. -# per file:: -# At the top of the file use the <tt>:markup:</tt> directive to set the -# default format for the rest of the file. -# per comment:: -# Use the <tt>:markup:</tt> directive at the top of a comment you want -# to write in a different format. -# -# = RDoc::Markup -# -# RDoc::Markup is extensible at runtime: you can add \new markup elements to -# be recognized in the documents that RDoc::Markup parses. -# -# RDoc::Markup is intended to be the basis for a family of tools which share -# the common requirement that simple, plain-text should be rendered in a -# variety of different output formats and media. It is envisaged that -# RDoc::Markup could be the basis for formatting RDoc style comment blocks, -# Wiki entries, and online FAQs. -# -# == Synopsis -# -# This code converts +input_string+ to HTML. The conversion takes place in -# the +convert+ method, so you can use the same RDoc::Markup converter to -# convert multiple input strings. -# -# require 'rdoc' -# -# h = RDoc::Markup::ToHtml.new(RDoc::Options.new) -# -# puts h.convert(input_string) -# -# You can extend the RDoc::Markup parser to recognize new markup -# sequences, and to add regexp handling. Here we make WikiWords significant to -# the parser, and also make the sequences {word} and \<no>text...</no> signify -# strike-through text. We then subclass the HTML output class to deal -# with these: -# -# require 'rdoc' -# -# class WikiHtml < RDoc::Markup::ToHtml -# def handle_regexp_WIKIWORD(target) -# "<font color=red>" + target.text + "</font>" -# end -# end -# -# markup = RDoc::Markup.new -# markup.add_word_pair("{", "}", :STRIKE) -# markup.add_html("no", :STRIKE) -# -# markup.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) -# -# wh = WikiHtml.new RDoc::Options.new, markup -# wh.add_tag(:STRIKE, "<strike>", "</strike>") -# -# puts "<body>#{wh.convert ARGF.read}</body>" -# -# == Encoding -# -# Where Encoding support is available, RDoc will automatically convert all -# documents to the same output encoding. The output encoding can be set via -# RDoc::Options#encoding and defaults to Encoding.default_external. -# -# = \RDoc Markup Reference -# -# See RDoc::MarkupReference. -# -#-- -# Original Author:: Dave Thomas, dave@pragmaticprogrammer.com -# License:: Ruby license - -class RDoc::Markup - - ## - # An AttributeManager which handles inline markup. - - attr_reader :attribute_manager - - ## - # Parses +str+ into an RDoc::Markup::Document. - - def self.parse str - RDoc::Markup::Parser.parse str - rescue RDoc::Markup::Parser::Error => e - $stderr.puts <<-EOF -While parsing markup, RDoc encountered a #{e.class}: - -#{e} -\tfrom #{e.backtrace.join "\n\tfrom "} - ----8<--- -#{text} ----8<--- - -RDoc #{RDoc::VERSION} - -Ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} #{RUBY_RELEASE_DATE} - -Please file a bug report with the above information at: - -https://github.com/ruby/rdoc/issues - - EOF - raise - end - - ## - # Take a block of text and use various heuristics to determine its - # structure (paragraphs, lists, and so on). Invoke an event handler as we - # identify significant chunks. - - def initialize attribute_manager = nil - @attribute_manager = attribute_manager || RDoc::Markup::AttributeManager.new - @output = nil - end - - ## - # Add to the sequences used to add formatting to an individual word (such - # as *bold*). Matching entries will generate attributes that the output - # formatters can recognize by their +name+. - - def add_word_pair(start, stop, name) - @attribute_manager.add_word_pair(start, stop, name) - end - - ## - # Add to the sequences recognized as general markup. - - def add_html(tag, name) - @attribute_manager.add_html(tag, name) - end - - ## - # Add to other inline sequences. For example, we could add WikiWords using - # something like: - # - # parser.add_regexp_handling(/\b([A-Z][a-z]+[A-Z]\w+)/, :WIKIWORD) - # - # Each wiki word will be presented to the output formatter. - - def add_regexp_handling(pattern, name) - @attribute_manager.add_regexp_handling(pattern, name) - end - - ## - # We take +input+, parse it if necessary, then invoke the output +formatter+ - # using a Visitor to render the result. - - def convert input, formatter - document = case input - when RDoc::Markup::Document then - input - else - RDoc::Markup::Parser.parse input - end - - document.accept formatter - end - - autoload :Parser, "#{__dir__}/markup/parser" - autoload :PreProcess, "#{__dir__}/markup/pre_process" - - # Inline markup classes - autoload :AttrChanger, "#{__dir__}/markup/attr_changer" - autoload :AttrSpan, "#{__dir__}/markup/attr_span" - autoload :Attributes, "#{__dir__}/markup/attributes" - autoload :AttributeManager, "#{__dir__}/markup/attribute_manager" - autoload :RegexpHandling, "#{__dir__}/markup/regexp_handling" - - # RDoc::Markup AST - autoload :BlankLine, "#{__dir__}/markup/blank_line" - autoload :BlockQuote, "#{__dir__}/markup/block_quote" - autoload :Document, "#{__dir__}/markup/document" - autoload :HardBreak, "#{__dir__}/markup/hard_break" - autoload :Heading, "#{__dir__}/markup/heading" - autoload :Include, "#{__dir__}/markup/include" - autoload :IndentedParagraph, "#{__dir__}/markup/indented_paragraph" - autoload :List, "#{__dir__}/markup/list" - autoload :ListItem, "#{__dir__}/markup/list_item" - autoload :Paragraph, "#{__dir__}/markup/paragraph" - autoload :Table, "#{__dir__}/markup/table" - autoload :Raw, "#{__dir__}/markup/raw" - autoload :Rule, "#{__dir__}/markup/rule" - autoload :Verbatim, "#{__dir__}/markup/verbatim" - - # Formatters - autoload :Formatter, "#{__dir__}/markup/formatter" - - autoload :ToAnsi, "#{__dir__}/markup/to_ansi" - autoload :ToBs, "#{__dir__}/markup/to_bs" - autoload :ToHtml, "#{__dir__}/markup/to_html" - autoload :ToHtmlCrossref, "#{__dir__}/markup/to_html_crossref" - autoload :ToHtmlSnippet, "#{__dir__}/markup/to_html_snippet" - autoload :ToLabel, "#{__dir__}/markup/to_label" - autoload :ToMarkdown, "#{__dir__}/markup/to_markdown" - autoload :ToRdoc, "#{__dir__}/markup/to_rdoc" - autoload :ToTableOfContents, "#{__dir__}/markup/to_table_of_contents" - autoload :ToTest, "#{__dir__}/markup/to_test" - autoload :ToTtOnly, "#{__dir__}/markup/to_tt_only" - -end diff --git a/lib/rdoc/markup/attr_changer.rb b/lib/rdoc/markup/attr_changer.rb deleted file mode 100644 index e5ba470bb6..0000000000 --- a/lib/rdoc/markup/attr_changer.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -class RDoc::Markup - - AttrChanger = Struct.new :turn_on, :turn_off # :nodoc: - -end - -## -# An AttrChanger records a change in attributes. It contains a bitmap of the -# attributes to turn on, and a bitmap of those to turn off. - -class RDoc::Markup::AttrChanger - - def to_s # :nodoc: - "Attr: +#{turn_on}/-#{turn_off}" - end - - def inspect # :nodoc: - '+%d/-%d' % [turn_on, turn_off] - end - -end diff --git a/lib/rdoc/markup/attr_span.rb b/lib/rdoc/markup/attr_span.rb deleted file mode 100644 index f1fabf1c3b..0000000000 --- a/lib/rdoc/markup/attr_span.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true -## -# An array of attributes which parallels the characters in a string. - -class RDoc::Markup::AttrSpan - - ## - # Creates a new AttrSpan for +length+ characters - - def initialize(length, exclusive) - @attrs = Array.new(length, 0) - @exclusive = exclusive - end - - ## - # Toggles +bits+ from +start+ to +length+ - def set_attrs(start, length, bits) - updated = false - for i in start ... (start+length) - if (@exclusive & @attrs[i]) == 0 || (@exclusive & bits) != 0 - @attrs[i] |= bits - updated = true - end - end - updated - end - - ## - # Accesses flags for character +n+ - - def [](n) - @attrs[n] - end - -end diff --git a/lib/rdoc/markup/attribute_manager.rb b/lib/rdoc/markup/attribute_manager.rb deleted file mode 100644 index ed014f255b..0000000000 --- a/lib/rdoc/markup/attribute_manager.rb +++ /dev/null @@ -1,405 +0,0 @@ -# frozen_string_literal: true - -## -# Manages changes of attributes in a block of text - -class RDoc::Markup::AttributeManager - unless ::MatchData.method_defined?(:match_length) - using ::Module.new { - refine(::MatchData) { - def match_length(nth) # :nodoc: - b, e = offset(nth) - e - b if b - end - } - } - end - - ## - # The NUL character - - NULL = "\000".freeze - - #-- - # We work by substituting non-printing characters in to the text. For now - # I'm assuming that I can substitute a character in the range 0..8 for a 7 - # bit character without damaging the encoded string, but this might be - # optimistic - #++ - - A_PROTECT = 004 # :nodoc: - - ## - # Special mask character to prevent inline markup handling - - PROTECT_ATTR = A_PROTECT.chr # :nodoc: - - ## - # The attributes enabled for this markup object. - - attr_reader :attributes - - ## - # This maps delimiters that occur around words (such as *bold* or +tt+) - # where the start and end delimiters and the same. This lets us optimize - # the regexp - - attr_reader :matching_word_pairs - - ## - # And this is used when the delimiters aren't the same. In this case the - # hash maps a pattern to the attribute character - - attr_reader :word_pair_map - - ## - # This maps HTML tags to the corresponding attribute char - - attr_reader :html_tags - - ## - # A \ in front of a character that would normally be processed turns off - # processing. We do this by turning \< into <#{PROTECT} - - attr_reader :protectable - - ## - # And this maps _regexp handling_ sequences to a name. A regexp handling - # sequence is something like a WikiWord - - attr_reader :regexp_handlings - - ## - # A bits of exclusive maps - attr_reader :exclusive_bitmap - - ## - # Creates a new attribute manager that understands bold, emphasized and - # teletype text. - - def initialize - @html_tags = {} - @matching_word_pairs = {} - @protectable = %w[<] - @regexp_handlings = [] - @word_pair_map = {} - @exclusive_bitmap = 0 - @attributes = RDoc::Markup::Attributes.new - - add_word_pair "*", "*", :BOLD, true - add_word_pair "_", "_", :EM, true - add_word_pair "+", "+", :TT, true - - add_html "em", :EM, true - add_html "i", :EM, true - add_html "b", :BOLD, true - add_html "tt", :TT, true - add_html "code", :TT, true - end - - ## - # Return an attribute object with the given turn_on and turn_off bits set - - def attribute(turn_on, turn_off) - RDoc::Markup::AttrChanger.new turn_on, turn_off - end - - ## - # Changes the current attribute from +current+ to +new+ - - def change_attribute current, new - diff = current ^ new - attribute(new & diff, current & diff) - end - - ## - # Used by the tests to change attributes by name from +current_set+ to - # +new_set+ - - def changed_attribute_by_name current_set, new_set - current = new = 0 - current_set.each do |name| - current |= @attributes.bitmap_for(name) - end - - new_set.each do |name| - new |= @attributes.bitmap_for(name) - end - - change_attribute(current, new) - end - - ## - # Copies +start_pos+ to +end_pos+ from the current string - - def copy_string(start_pos, end_pos) - res = @str[start_pos...end_pos] - res.gsub!(/\000/, '') - res - end - - # :nodoc: - def exclusive?(attr) - (attr & @exclusive_bitmap) != 0 - end - - NON_PRINTING_START = "\1" # :nodoc: - NON_PRINTING_END = "\2" # :nodoc: - - ## - # Map attributes like <b>text</b>to the sequence - # \001\002<char>\001\003<char>, where <char> is a per-attribute specific - # character - - def convert_attrs(str, attrs, exclusive = false) - convert_attrs_matching_word_pairs(str, attrs, exclusive) - convert_attrs_word_pair_map(str, attrs, exclusive) - end - - # :nodoc: - def convert_attrs_matching_word_pairs(str, attrs, exclusive) - # first do matching ones - tags = @matching_word_pairs.select { |start, bitmap| - exclusive == exclusive?(bitmap) - }.keys - return if tags.empty? - tags = "[#{tags.join("")}](?!#{PROTECT_ATTR})" - all_tags = "[#{@matching_word_pairs.keys.join("")}](?!#{PROTECT_ATTR})" - - re = /(?:^|\W|#{all_tags})\K(#{tags})(\1*[#\\]?[\w:#{PROTECT_ATTR}.\/\[\]-]+?\S?)\1(?!\1)(?=#{all_tags}|\W|$)/ - - 1 while str.gsub!(re) { |orig| - a, w = (m = $~).values_at(1, 2) - attr = @matching_word_pairs[a] - if attrs.set_attrs(m.begin(2), w.length, attr) - a = NULL * a.length - else - a = NON_PRINTING_START + a + NON_PRINTING_END - end - a + w + a - } - str.delete!(NON_PRINTING_START + NON_PRINTING_END) - end - - # :nodoc: - def convert_attrs_word_pair_map(str, attrs, exclusive) - # then non-matching - unless @word_pair_map.empty? then - @word_pair_map.each do |regexp, attr| - next unless exclusive == exclusive?(attr) - 1 while str.gsub!(regexp) { |orig| - w = (m = ($~))[2] - updated = attrs.set_attrs(m.begin(2), w.length, attr) - if updated - NULL * m.match_length(1) + w + NULL * m.match_length(3) - else - orig - end - } - end - end - end - - ## - # Converts HTML tags to RDoc attributes - - def convert_html(str, attrs, exclusive = false) - tags = @html_tags.select { |start, bitmap| - exclusive == exclusive?(bitmap) - }.keys.join '|' - - 1 while str.gsub!(/<(#{tags})>(.*?)<\/\1>/i) { |orig| - attr = @html_tags[$1.downcase] - html_length = $~.match_length(1) + 2 # "<>".length - seq = NULL * html_length - attrs.set_attrs($~.begin(2), $~.match_length(2), attr) - seq + $2 + seq + NULL - } - end - - ## - # Converts regexp handling sequences to RDoc attributes - - def convert_regexp_handlings str, attrs, exclusive = false - @regexp_handlings.each do |regexp, attribute| - next unless exclusive == exclusive?(attribute) - str.scan(regexp) do - capture = $~.size == 1 ? 0 : 1 - - s, e = $~.offset capture - - attrs.set_attrs s, e - s, attribute | @attributes.regexp_handling - end - end - end - - ## - # Escapes regexp handling sequences of text to prevent conversion to RDoc - - def mask_protected_sequences - # protect __send__, __FILE__, etc. - @str.gsub!(/__([a-z]+)__/i, - "_#{PROTECT_ATTR}_#{PROTECT_ATTR}\\1_#{PROTECT_ATTR}_#{PROTECT_ATTR}") - @str.gsub!(/(\A|[^\\])\\([#{Regexp.escape @protectable.join}])/m, - "\\1\\2#{PROTECT_ATTR}") - @str.gsub!(/\\(\\[#{Regexp.escape @protectable.join}])/m, "\\1") - end - - ## - # Unescapes regexp handling sequences of text - - def unmask_protected_sequences - @str.gsub!(/(.)#{PROTECT_ATTR}/, "\\1\000") - end - - ## - # Adds a markup class with +name+ for words wrapped in the +start+ and - # +stop+ character. To make words wrapped with "*" bold: - # - # am.add_word_pair '*', '*', :BOLD - - def add_word_pair(start, stop, name, exclusive = false) - raise ArgumentError, "Word flags may not start with '<'" if - start[0, 1] == '<' - - bitmap = @attributes.bitmap_for name - - if start == stop then - @matching_word_pairs[start] = bitmap - else - pattern = /(#{Regexp.escape start})(\S+)(#{Regexp.escape stop})/ - @word_pair_map[pattern] = bitmap - end - - @protectable << start[0, 1] - @protectable.uniq! - - @exclusive_bitmap |= bitmap if exclusive - end - - ## - # Adds a markup class with +name+ for words surrounded by HTML tag +tag+. - # To process emphasis tags: - # - # am.add_html 'em', :EM - - def add_html(tag, name, exclusive = false) - bitmap = @attributes.bitmap_for name - @html_tags[tag.downcase] = bitmap - @exclusive_bitmap |= bitmap if exclusive - end - - ## - # Adds a regexp handling for +pattern+ with +name+. A simple URL handler - # would be: - # - # @am.add_regexp_handling(/((https?:)\S+\w)/, :HYPERLINK) - - def add_regexp_handling pattern, name, exclusive = false - bitmap = @attributes.bitmap_for(name) - @regexp_handlings << [pattern, bitmap] - @exclusive_bitmap |= bitmap if exclusive - end - - ## - # Processes +str+ converting attributes, HTML and regexp handlings - - def flow str - @str = str.dup - - mask_protected_sequences - - @attrs = RDoc::Markup::AttrSpan.new @str.length, @exclusive_bitmap - - convert_attrs @str, @attrs, true - convert_html @str, @attrs, true - convert_regexp_handlings @str, @attrs, true - convert_attrs @str, @attrs - convert_html @str, @attrs - convert_regexp_handlings @str, @attrs - - unmask_protected_sequences - - split_into_flow - end - - ## - # Debug method that prints a string along with its attributes - - def display_attributes - puts - puts @str.tr(NULL, "!") - bit = 1 - 16.times do |bno| - line = "" - @str.length.times do |i| - if (@attrs[i] & bit) == 0 - line << " " - else - if bno.zero? - line << "S" - else - line << ("%d" % (bno+1)) - end - end - end - puts(line) unless line =~ /^ *$/ - bit <<= 1 - end - end - - ## - # Splits the string into chunks by attribute change - - def split_into_flow - res = [] - current_attr = 0 - - str_len = @str.length - - # skip leading invisible text - i = 0 - i += 1 while i < str_len and @str[i].chr == "\0" - start_pos = i - - # then scan the string, chunking it on attribute changes - while i < str_len - new_attr = @attrs[i] - if new_attr != current_attr - if i > start_pos - res << copy_string(start_pos, i) - start_pos = i - end - - res << change_attribute(current_attr, new_attr) - current_attr = new_attr - - if (current_attr & @attributes.regexp_handling) != 0 then - i += 1 while - i < str_len and (@attrs[i] & @attributes.regexp_handling) != 0 - - res << RDoc::Markup::RegexpHandling.new(current_attr, - copy_string(start_pos, i)) - start_pos = i - next - end - end - - # move on, skipping any invisible characters - begin - i += 1 - end while i < str_len and @str[i].chr == "\0" - end - - # tidy up trailing text - if start_pos < str_len - res << copy_string(start_pos, str_len) - end - - # and reset to all attributes off - res << change_attribute(current_attr, 0) if current_attr != 0 - - res - end - -end diff --git a/lib/rdoc/markup/attributes.rb b/lib/rdoc/markup/attributes.rb deleted file mode 100644 index d9d18b3059..0000000000 --- a/lib/rdoc/markup/attributes.rb +++ /dev/null @@ -1,70 +0,0 @@ -# frozen_string_literal: true -## -# We manage a set of attributes. Each attribute has a symbol name and a bit -# value. - -class RDoc::Markup::Attributes - - ## - # The regexp handling attribute type. See RDoc::Markup#add_regexp_handling - - attr_reader :regexp_handling - - ## - # Creates a new attributes set. - - def initialize - @regexp_handling = 1 - - @name_to_bitmap = [ - [:_REGEXP_HANDLING_, @regexp_handling], - ] - - @next_bitmap = @regexp_handling << 1 - end - - ## - # Returns a unique bit for +name+ - - def bitmap_for name - bitmap = @name_to_bitmap.assoc name - - unless bitmap then - bitmap = @next_bitmap - @next_bitmap <<= 1 - @name_to_bitmap << [name, bitmap] - else - bitmap = bitmap.last - end - - bitmap - end - - ## - # Returns a string representation of +bitmap+ - - def as_string bitmap - return 'none' if bitmap.zero? - res = [] - - @name_to_bitmap.each do |name, bit| - res << name if (bitmap & bit) != 0 - end - - res.join ',' - end - - ## - # yields each attribute name in +bitmap+ - - def each_name_of bitmap - return enum_for __method__, bitmap unless block_given? - - @name_to_bitmap.each do |name, bit| - next if bit == @regexp_handling - - yield name.to_s if (bitmap & bit) != 0 - end - end - -end diff --git a/lib/rdoc/markup/blank_line.rb b/lib/rdoc/markup/blank_line.rb deleted file mode 100644 index f63ae9479c..0000000000 --- a/lib/rdoc/markup/blank_line.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true -## -# An empty line. This class is a singleton. - -class RDoc::Markup::BlankLine - - @instance = new - - ## - # RDoc::Markup::BlankLine is a singleton - - def self.new - @instance - end - - ## - # Calls #accept_blank_line on +visitor+ - - def accept visitor - visitor.accept_blank_line self - end - - def pretty_print q # :nodoc: - q.text 'blankline' - end - -end diff --git a/lib/rdoc/markup/block_quote.rb b/lib/rdoc/markup/block_quote.rb deleted file mode 100644 index d9fcbf213c..0000000000 --- a/lib/rdoc/markup/block_quote.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true -## -# A quoted section which contains markup items. - -class RDoc::Markup::BlockQuote < RDoc::Markup::Raw - - ## - # Calls #accept_block_quote on +visitor+ - - def accept visitor - visitor.accept_block_quote self - end - -end diff --git a/lib/rdoc/markup/document.rb b/lib/rdoc/markup/document.rb deleted file mode 100644 index 94cf6a3666..0000000000 --- a/lib/rdoc/markup/document.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true -## -# A Document containing lists, headings, paragraphs, etc. - -class RDoc::Markup::Document - - include Enumerable - - ## - # The file this document was created from. See also - # RDoc::ClassModule#add_comment - - attr_reader :file - - ## - # If a heading is below the given level it will be omitted from the - # table_of_contents - - attr_accessor :omit_headings_below - - ## - # The parts of the Document - - attr_reader :parts - - ## - # Creates a new Document with +parts+ - - def initialize *parts - @parts = [] - @parts.concat parts - - @file = nil - @omit_headings_from_table_of_contents_below = nil - end - - ## - # Appends +part+ to the document - - def << part - case part - when RDoc::Markup::Document then - unless part.empty? then - parts.concat part.parts - parts << RDoc::Markup::BlankLine.new - end - when String then - raise ArgumentError, - "expected RDoc::Markup::Document and friends, got String" unless - part.empty? - else - parts << part - end - end - - def == other # :nodoc: - self.class == other.class and - @file == other.file and - @parts == other.parts - end - - ## - # Runs this document and all its #items through +visitor+ - - def accept visitor - visitor.start_accepting - - visitor.accept_document self - - visitor.end_accepting - end - - ## - # Concatenates the given +parts+ onto the document - - def concat parts - self.parts.concat parts - end - - ## - # Enumerator for the parts of this document - - def each &block - @parts.each(&block) - end - - ## - # Does this document have no parts? - - def empty? - @parts.empty? or (@parts.length == 1 and merged? and @parts.first.empty?) - end - - ## - # The file this Document was created from. - - def file= location - @file = case location - when RDoc::TopLevel then - location.relative_name - else - location - end - end - - ## - # When this is a collection of documents (#file is not set and this document - # contains only other documents as its direct children) #merge replaces - # documents in this class with documents from +other+ when the file matches - # and adds documents from +other+ when the files do not. - # - # The information in +other+ is preferred over the receiver - - def merge other - if empty? then - @parts = other.parts - return self - end - - other.parts.each do |other_part| - self.parts.delete_if do |self_part| - self_part.file and self_part.file == other_part.file - end - - self.parts << other_part - end - - self - end - - ## - # Does this Document contain other Documents? - - def merged? - RDoc::Markup::Document === @parts.first - end - - def pretty_print q # :nodoc: - start = @file ? "[doc (#{@file}): " : '[doc: ' - - q.group 2, start, ']' do - q.seplist @parts do |part| - q.pp part - end - end - end - - ## - # Appends +parts+ to the document - - def push *parts - self.parts.concat parts - end - - ## - # Returns an Array of headings in the document. - # - # Require 'rdoc/markup/formatter' before calling this method. - - def table_of_contents - accept RDoc::Markup::ToTableOfContents.to_toc - end - -end diff --git a/lib/rdoc/markup/formatter.rb b/lib/rdoc/markup/formatter.rb deleted file mode 100644 index 9daffaabb8..0000000000 --- a/lib/rdoc/markup/formatter.rb +++ /dev/null @@ -1,265 +0,0 @@ -# frozen_string_literal: true -## -# Base class for RDoc markup formatters -# -# Formatters are a visitor that converts an RDoc::Markup tree (from a comment) -# into some kind of output. RDoc ships with formatters for converting back to -# rdoc, ANSI text, HTML, a Table of Contents and other formats. -# -# If you'd like to write your own Formatter use -# RDoc::Markup::FormatterTestCase. If you're writing a text-output formatter -# use RDoc::Markup::TextFormatterTestCase which provides extra test cases. - -class RDoc::Markup::Formatter - - ## - # Tag for inline markup containing a +bit+ for the bitmask and the +on+ and - # +off+ triggers. - - InlineTag = Struct.new(:bit, :on, :off) - - ## - # Converts a target url to one that is relative to a given path - - def self.gen_relative_url path, target - from = File.dirname path - to, to_file = File.split target - - from = from.split "/" - to = to.split "/" - - from.delete '.' - to.delete '.' - - while from.size > 0 and to.size > 0 and from[0] == to[0] do - from.shift - to.shift - end - - from.fill ".." - from.concat to - from << to_file - File.join(*from) - end - - ## - # Creates a new Formatter - - def initialize options, markup = nil - @options = options - - @markup = markup || RDoc::Markup.new - @am = @markup.attribute_manager - @am.add_regexp_handling(/<br>/, :HARD_BREAK) - - @attributes = @am.attributes - - @attr_tags = [] - - @in_tt = 0 - @tt_bit = @attributes.bitmap_for :TT - - @hard_break = '' - @from_path = '.' - end - - ## - # Adds +document+ to the output - - def accept_document document - document.parts.each do |item| - case item - when RDoc::Markup::Document then # HACK - accept_document item - else - item.accept self - end - end - end - - ## - # Adds a regexp handling for links of the form rdoc-...: - - def add_regexp_handling_RDOCLINK - @markup.add_regexp_handling(/rdoc-[a-z]+:[^\s\]]+/, :RDOCLINK) - end - - ## - # Adds a regexp handling for links of the form {<text>}[<url>] and - # <word>[<url>] - - def add_regexp_handling_TIDYLINK - @markup.add_regexp_handling(/(?: - \{[^{}]*\} | # multi-word label - \b[^\s{}]+? # single-word label - ) - - \[\S+?\] # link target - /x, :TIDYLINK) - end - - ## - # Add a new set of tags for an attribute. We allow separate start and end - # tags for flexibility - - def add_tag(name, start, stop) - attr = @attributes.bitmap_for name - @attr_tags << InlineTag.new(attr, start, stop) - end - - ## - # Allows +tag+ to be decorated with additional information. - - def annotate(tag) - tag - end - - ## - # Marks up +content+ - - def convert content - @markup.convert content, self - end - - ## - # Converts flow items +flow+ - - def convert_flow(flow) - res = [] - - flow.each do |item| - case item - when String then - res << convert_string(item) - when RDoc::Markup::AttrChanger then - off_tags res, item - on_tags res, item - when RDoc::Markup::RegexpHandling then - res << convert_regexp_handling(item) - else - raise "Unknown flow element: #{item.inspect}" - end - end - - res.join - end - - ## - # Converts added regexp handlings. See RDoc::Markup#add_regexp_handling - - def convert_regexp_handling target - return target.text if in_tt? - - handled = false - - @attributes.each_name_of target.type do |name| - method_name = "handle_regexp_#{name}" - - if respond_to? method_name then - target.text = public_send method_name, target - handled = true - end - end - - unless handled then - target_name = @attributes.as_string target.type - - raise RDoc::Error, "Unhandled regexp handling #{target_name}: #{target}" - end - - target.text - end - - ## - # Converts a string to be fancier if desired - - def convert_string string - string - end - - ## - # Use ignore in your subclass to ignore the content of a node. - # - # ## - # # We don't support raw nodes in ToNoRaw - # - # alias accept_raw ignore - - def ignore *node - end - - ## - # Are we currently inside tt tags? - - def in_tt? - @in_tt > 0 - end - - ## - # Turns on tags for +item+ on +res+ - - def on_tags res, item - attr_mask = item.turn_on - return if attr_mask.zero? - - @attr_tags.each do |tag| - if attr_mask & tag.bit != 0 then - res << annotate(tag.on) - @in_tt += 1 if tt? tag - end - end - end - - ## - # Turns off tags for +item+ on +res+ - - def off_tags res, item - attr_mask = item.turn_off - return if attr_mask.zero? - - @attr_tags.reverse_each do |tag| - if attr_mask & tag.bit != 0 then - @in_tt -= 1 if tt? tag - res << annotate(tag.off) - end - end - end - - ## - # Extracts and a scheme, url and an anchor id from +url+ and returns them. - - def parse_url url - case url - when /^rdoc-label:([^:]*)(?::(.*))?/ then - scheme = 'link' - path = "##{$1}" - id = " id=\"#{$2}\"" if $2 - when /([A-Za-z]+):(.*)/ then - scheme = $1.downcase - path = $2 - when /^#/ then - else - scheme = 'http' - path = url - url = url - end - - if scheme == 'link' then - url = if path[0, 1] == '#' then # is this meaningful? - path - else - self.class.gen_relative_url @from_path, path - end - end - - [scheme, url, id] - end - - ## - # Is +tag+ a tt tag? - - def tt? tag - tag.bit == @tt_bit - end - -end diff --git a/lib/rdoc/markup/hard_break.rb b/lib/rdoc/markup/hard_break.rb deleted file mode 100644 index de1819c903..0000000000 --- a/lib/rdoc/markup/hard_break.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true -## -# A hard-break in the middle of a paragraph. - -class RDoc::Markup::HardBreak - - @instance = new - - ## - # RDoc::Markup::HardBreak is a singleton - - def self.new - @instance - end - - ## - # Calls #accept_hard_break on +visitor+ - - def accept visitor - visitor.accept_hard_break self - end - - def == other # :nodoc: - self.class === other - end - - def pretty_print q # :nodoc: - q.text "[break]" - end - -end diff --git a/lib/rdoc/markup/heading.rb b/lib/rdoc/markup/heading.rb deleted file mode 100644 index 02476e5226..0000000000 --- a/lib/rdoc/markup/heading.rb +++ /dev/null @@ -1,78 +0,0 @@ -# frozen_string_literal: true -## -# A heading with a level (1-6) and text - -RDoc::Markup::Heading = - Struct.new :level, :text do - - @to_html = nil - @to_label = nil - - ## - # A singleton RDoc::Markup::ToLabel formatter for headings. - - def self.to_label - @to_label ||= RDoc::Markup::ToLabel.new - end - - ## - # A singleton plain HTML formatter for headings. Used for creating labels - # for the Table of Contents - - def self.to_html - return @to_html if @to_html - - markup = RDoc::Markup.new - markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF - - @to_html = RDoc::Markup::ToHtml.new nil - - def @to_html.handle_regexp_CROSSREF target - target.text.sub(/^\\/, '') - end - - @to_html - end - - ## - # Calls #accept_heading on +visitor+ - - def accept visitor - visitor.accept_heading self - end - - ## - # An HTML-safe anchor reference for this header. - - def aref - "label-#{self.class.to_label.convert text.dup}" - end - - ## - # Creates a fully-qualified label which will include the label from - # +context+. This helps keep ids unique in HTML. - - def label context = nil - label = aref - - label = [context.aref, label].compact.join '-' if - context and context.respond_to? :aref - - label - end - - ## - # HTML markup of the text of this label without the surrounding header - # element. - - def plain_html - self.class.to_html.to_html(text.dup) - end - - def pretty_print q # :nodoc: - q.group 2, "[head: #{level} ", ']' do - q.pp text - end - end - -end diff --git a/lib/rdoc/markup/include.rb b/lib/rdoc/markup/include.rb deleted file mode 100644 index 2bf63526b2..0000000000 --- a/lib/rdoc/markup/include.rb +++ /dev/null @@ -1,42 +0,0 @@ -# frozen_string_literal: true -## -# A file included at generation time. Objects of this class are created by -# RDoc::RD for an extension-less include. -# -# This implementation in incomplete. - -class RDoc::Markup::Include - - ## - # The filename to be included, without extension - - attr_reader :file - - ## - # Directories to search for #file - - attr_reader :include_path - - ## - # Creates a new include that will import +file+ from +include_path+ - - def initialize file, include_path - @file = file - @include_path = include_path - end - - def == other # :nodoc: - self.class === other and - @file == other.file and @include_path == other.include_path - end - - def pretty_print q # :nodoc: - q.group 2, '[incl ', ']' do - q.text file - q.breakable - q.text 'from ' - q.pp include_path - end - end - -end diff --git a/lib/rdoc/markup/indented_paragraph.rb b/lib/rdoc/markup/indented_paragraph.rb deleted file mode 100644 index 992cd7cf81..0000000000 --- a/lib/rdoc/markup/indented_paragraph.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -## -# An Indented Paragraph of text - -class RDoc::Markup::IndentedParagraph < RDoc::Markup::Raw - - ## - # The indent in number of spaces - - attr_reader :indent - - ## - # Creates a new IndentedParagraph containing +parts+ indented with +indent+ - # spaces - - def initialize indent, *parts - @indent = indent - - super(*parts) - end - - def == other # :nodoc: - super and indent == other.indent - end - - ## - # Calls #accept_indented_paragraph on +visitor+ - - def accept visitor - visitor.accept_indented_paragraph self - end - - ## - # Joins the raw paragraph text and converts inline HardBreaks to the - # +hard_break+ text followed by the indent. - - def text hard_break = nil - @parts.map do |part| - if RDoc::Markup::HardBreak === part then - '%1$s%3$*2$s' % [hard_break, @indent, ' '] if hard_break - else - part - end - end.join - end - -end diff --git a/lib/rdoc/markup/list.rb b/lib/rdoc/markup/list.rb deleted file mode 100644 index 112b7a1a86..0000000000 --- a/lib/rdoc/markup/list.rb +++ /dev/null @@ -1,101 +0,0 @@ -# frozen_string_literal: true -## -# A List is a homogeneous set of ListItems. -# -# The supported list types include: -# -# :BULLET:: -# An unordered list -# :LABEL:: -# An unordered definition list, but using an alternate RDoc::Markup syntax -# :LALPHA:: -# An ordered list using increasing lowercase English letters -# :NOTE:: -# An unordered definition list -# :NUMBER:: -# An ordered list using increasing Arabic numerals -# :UALPHA:: -# An ordered list using increasing uppercase English letters -# -# Definition lists behave like HTML definition lists. Each list item can -# describe multiple terms. See RDoc::Markup::ListItem for how labels and -# definition are stored as list items. - -class RDoc::Markup::List - - ## - # The list's type - - attr_accessor :type - - ## - # Items in the list - - attr_reader :items - - ## - # Creates a new list of +type+ with +items+. Valid list types are: - # +:BULLET+, +:LABEL+, +:LALPHA+, +:NOTE+, +:NUMBER+, +:UALPHA+ - - def initialize type = nil, *items - @type = type - @items = [] - @items.concat items - end - - ## - # Appends +item+ to the list - - def << item - @items << item - end - - def == other # :nodoc: - self.class == other.class and - @type == other.type and - @items == other.items - end - - ## - # Runs this list and all its #items through +visitor+ - - def accept visitor - visitor.accept_list_start self - - @items.each do |item| - item.accept visitor - end - - visitor.accept_list_end self - end - - ## - # Is the list empty? - - def empty? - @items.empty? - end - - ## - # Returns the last item in the list - - def last - @items.last - end - - def pretty_print q # :nodoc: - q.group 2, "[list: #{@type} ", ']' do - q.seplist @items do |item| - q.pp item - end - end - end - - ## - # Appends +items+ to the list - - def push *items - @items.concat items - end - -end diff --git a/lib/rdoc/markup/list_item.rb b/lib/rdoc/markup/list_item.rb deleted file mode 100644 index 0b8326a69f..0000000000 --- a/lib/rdoc/markup/list_item.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true -## -# An item within a List that contains paragraphs, headings, etc. -# -# For BULLET, NUMBER, LALPHA and UALPHA lists, the label will always be nil. -# For NOTE and LABEL lists, the list label may contain: -# -# * a single String for a single label -# * an Array of Strings for a list item with multiple terms -# * nil for an extra description attached to a previously labeled list item - -class RDoc::Markup::ListItem - - ## - # The label for the ListItem - - attr_accessor :label - - ## - # Parts of the ListItem - - attr_reader :parts - - ## - # Creates a new ListItem with an optional +label+ containing +parts+ - - def initialize label = nil, *parts - @label = label - @parts = [] - @parts.concat parts - end - - ## - # Appends +part+ to the ListItem - - def << part - @parts << part - end - - def == other # :nodoc: - self.class == other.class and - @label == other.label and - @parts == other.parts - end - - ## - # Runs this list item and all its #parts through +visitor+ - - def accept visitor - visitor.accept_list_item_start self - - @parts.each do |part| - part.accept visitor - end - - visitor.accept_list_item_end self - end - - ## - # Is the ListItem empty? - - def empty? - @parts.empty? - end - - ## - # Length of parts in the ListItem - - def length - @parts.length - end - - def pretty_print q # :nodoc: - q.group 2, '[item: ', ']' do - case @label - when Array then - q.pp @label - q.text ';' - q.breakable - when String then - q.pp @label - q.text ';' - q.breakable - end - - q.seplist @parts do |part| - q.pp part - end - end - end - - ## - # Adds +parts+ to the ListItem - - def push *parts - @parts.concat parts - end - -end diff --git a/lib/rdoc/markup/paragraph.rb b/lib/rdoc/markup/paragraph.rb deleted file mode 100644 index 21dfda007a..0000000000 --- a/lib/rdoc/markup/paragraph.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true -## -# A Paragraph of text - -class RDoc::Markup::Paragraph < RDoc::Markup::Raw - - ## - # Calls #accept_paragraph on +visitor+ - - def accept visitor - visitor.accept_paragraph self - end - - ## - # Joins the raw paragraph text and converts inline HardBreaks to the - # +hard_break+ text. - - def text hard_break = '' - @parts.map do |part| - if RDoc::Markup::HardBreak === part then - hard_break - else - part - end - end.join - end - -end diff --git a/lib/rdoc/markup/parser.rb b/lib/rdoc/markup/parser.rb deleted file mode 100644 index 9c77048591..0000000000 --- a/lib/rdoc/markup/parser.rb +++ /dev/null @@ -1,585 +0,0 @@ -# frozen_string_literal: true -require 'strscan' - -## -# A recursive-descent parser for RDoc markup. -# -# The parser tokenizes an input string then parses the tokens into a Document. -# Documents can be converted into output formats by writing a visitor like -# RDoc::Markup::ToHTML. -# -# The parser only handles the block-level constructs Paragraph, List, -# ListItem, Heading, Verbatim, BlankLine, Rule and BlockQuote. -# Inline markup such as <tt>\+blah\+</tt> is handled separately by -# RDoc::Markup::AttributeManager. -# -# To see what markup the Parser implements read RDoc. To see how to use -# RDoc markup to format text in your program read RDoc::Markup. - -class RDoc::Markup::Parser - - include RDoc::Text - - ## - # List token types - - LIST_TOKENS = [ - :BULLET, - :LABEL, - :LALPHA, - :NOTE, - :NUMBER, - :UALPHA, - ] - - ## - # Parser error subclass - - class Error < RuntimeError; end - - ## - # Raised when the parser is unable to handle the given markup - - class ParseError < Error; end - - ## - # Enables display of debugging information - - attr_accessor :debug - - ## - # Token accessor - - attr_reader :tokens - - ## - # Parses +str+ into a Document. - # - # Use RDoc::Markup#parse instead of this method. - - def self.parse str - parser = new - parser.tokenize str - doc = RDoc::Markup::Document.new - parser.parse doc - end - - ## - # Returns a token stream for +str+, for testing - - def self.tokenize str - parser = new - parser.tokenize str - parser.tokens - end - - ## - # Creates a new Parser. See also ::parse - - def initialize - @binary_input = nil - @current_token = nil - @debug = false - @s = nil - @tokens = [] - end - - ## - # Builds a Heading of +level+ - - def build_heading level - type, text, = get - - text = case type - when :TEXT then - skip :NEWLINE - text - else - unget - '' - end - - RDoc::Markup::Heading.new level, text - end - - ## - # Builds a List flush to +margin+ - - def build_list margin - p :list_start => margin if @debug - - list = RDoc::Markup::List.new - label = nil - - until @tokens.empty? do - type, data, column, = get - - case type - when *LIST_TOKENS then - if column < margin || (list.type && list.type != type) then - unget - break - end - - list.type = type - peek_type, _, column, = peek_token - - case type - when :NOTE, :LABEL then - label = [] unless label - - if peek_type == :NEWLINE then - # description not on the same line as LABEL/NOTE - # skip the trailing newline & any blank lines below - while peek_type == :NEWLINE - get - peek_type, _, column, = peek_token - end - - # we may be: - # - at end of stream - # - at a column < margin: - # [text] - # blah blah blah - # - at the same column, but with a different type of list item - # [text] - # * blah blah - # - at the same column, with the same type of list item - # [one] - # [two] - # In all cases, we have an empty description. - # In the last case only, we continue. - if peek_type.nil? || column < margin then - empty = true - elsif column == margin then - case peek_type - when type - empty = :continue - when *LIST_TOKENS - empty = true - else - empty = false - end - else - empty = false - end - - if empty then - label << data - next if empty == :continue - break - end - end - else - data = nil - end - - if label then - data = label << data - label = nil - end - - list_item = RDoc::Markup::ListItem.new data - parse list_item, column - list << list_item - - else - unget - break - end - end - - p :list_end => margin if @debug - - if list.empty? then - return nil unless label - return nil unless [:LABEL, :NOTE].include? list.type - - list_item = RDoc::Markup::ListItem.new label, RDoc::Markup::BlankLine.new - list << list_item - end - - list - end - - ## - # Builds a Paragraph that is flush to +margin+ - - def build_paragraph margin - p :paragraph_start => margin if @debug - - paragraph = RDoc::Markup::Paragraph.new - - until @tokens.empty? do - type, data, column, = get - - if type == :TEXT and column == margin then - paragraph << data - - break if peek_token.first == :BREAK - - data << ' ' if skip :NEWLINE and /#{SPACE_SEPARATED_LETTER_CLASS}\z/o.match?(data) - else - unget - break - end - end - - paragraph.parts.last.sub!(/ \z/, '') # cleanup - - p :paragraph_end => margin if @debug - - paragraph - end - - ## - # Builds a Verbatim that is indented from +margin+. - # - # The verbatim block is shifted left (the least indented lines start in - # column 0). Each part of the verbatim is one line of text, always - # terminated by a newline. Blank lines always consist of a single newline - # character, and there is never a single newline at the end of the verbatim. - - def build_verbatim margin - p :verbatim_begin => margin if @debug - verbatim = RDoc::Markup::Verbatim.new - - min_indent = nil - generate_leading_spaces = true - line = ''.dup - - until @tokens.empty? do - type, data, column, = get - - if type == :NEWLINE then - line << data - verbatim << line - line = ''.dup - generate_leading_spaces = true - next - end - - if column <= margin - unget - break - end - - if generate_leading_spaces then - indent = column - margin - line << ' ' * indent - min_indent = indent if min_indent.nil? || indent < min_indent - generate_leading_spaces = false - end - - case type - when :HEADER then - line << '=' * data - _, _, peek_column, = peek_token - peek_column ||= column + data - indent = peek_column - column - data - line << ' ' * indent - when :RULE then - width = 2 + data - line << '-' * width - _, _, peek_column, = peek_token - peek_column ||= column + width - indent = peek_column - column - width - line << ' ' * indent - when :BREAK, :TEXT then - line << data - when :BLOCKQUOTE then - line << '>>>' - peek_type, _, peek_column = peek_token - if peek_type != :NEWLINE and peek_column - line << ' ' * (peek_column - column - 3) - end - else # *LIST_TOKENS - list_marker = case type - when :BULLET then data - when :LABEL then "[#{data}]" - when :NOTE then "#{data}::" - else # :LALPHA, :NUMBER, :UALPHA - "#{data}." - end - line << list_marker - peek_type, _, peek_column = peek_token - unless peek_type == :NEWLINE then - peek_column ||= column + list_marker.length - indent = peek_column - column - list_marker.length - line << ' ' * indent - end - end - - end - - verbatim << line << "\n" unless line.empty? - verbatim.parts.each { |p| p.slice!(0, min_indent) unless p == "\n" } if min_indent > 0 - verbatim.normalize - - p :verbatim_end => margin if @debug - - verbatim - end - - ## - # Pulls the next token from the stream. - - def get - @current_token = @tokens.shift - p :get => @current_token if @debug - @current_token - end - - ## - # Parses the tokens into an array of RDoc::Markup::XXX objects, - # and appends them to the passed +parent+ RDoc::Markup::YYY object. - # - # Exits at the end of the token stream, or when it encounters a token - # in a column less than +indent+ (unless it is a NEWLINE). - # - # Returns +parent+. - - def parse parent, indent = 0 - p :parse_start => indent if @debug - - until @tokens.empty? do - type, data, column, = get - - case type - when :BREAK then - parent << RDoc::Markup::BlankLine.new - skip :NEWLINE, false - next - when :NEWLINE then - # trailing newlines are skipped below, so this is a blank line - parent << RDoc::Markup::BlankLine.new - skip :NEWLINE, false - next - end - - # indentation change: break or verbatim - if column < indent then - unget - break - elsif column > indent then - unget - parent << build_verbatim(indent) - next - end - - # indentation is the same - case type - when :HEADER then - parent << build_heading(data) - when :RULE then - parent << RDoc::Markup::Rule.new(data) - skip :NEWLINE - when :TEXT then - unget - parse_text parent, indent - when :BLOCKQUOTE then - nil while (type, = get; type) and type != :NEWLINE - _, _, column, = peek_token - bq = RDoc::Markup::BlockQuote.new - p :blockquote_start => [data, column] if @debug - parse bq, column - p :blockquote_end => indent if @debug - parent << bq - when *LIST_TOKENS then - unget - parent << build_list(indent) - else - type, data, column, line = @current_token - raise ParseError, "Unhandled token #{type} (#{data.inspect}) at #{line}:#{column}" - end - end - - p :parse_end => indent if @debug - - parent - - end - - ## - # Small hook that is overridden by RDoc::TomDoc - - def parse_text parent, indent # :nodoc: - parent << build_paragraph(indent) - end - - ## - # Returns the next token on the stream without modifying the stream - - def peek_token - token = @tokens.first || [] - p :peek => token if @debug - token - end - - ## - # A simple wrapper of StringScanner that is aware of the current column and lineno - - class MyStringScanner - # :stopdoc: - - def initialize(input) - @line = @column = 0 - @s = StringScanner.new input - end - - def scan(re) - ret = @s.scan(re) - @column += ret.length if ret - ret - end - - def unscan(s) - @s.pos -= s.bytesize - @column -= s.length - end - - def pos - [@column, @line] - end - - def newline! - @column = 0 - @line += 1 - end - - def eos? - @s.eos? - end - - def matched - @s.matched - end - - def [](i) - @s[i] - end - - #:startdoc: - end - - ## - # Creates the StringScanner - - def setup_scanner input - @s = MyStringScanner.new input - end - - ## - # Skips the next token if its type is +token_type+. - # - # Optionally raises an error if the next token is not of the expected type. - - def skip token_type, error = true - type, = get - return unless type # end of stream - return @current_token if token_type == type - unget - raise ParseError, "expected #{token_type} got #{@current_token.inspect}" if error - end - - ## - # Turns text +input+ into a stream of tokens - - def tokenize input - setup_scanner input - - until @s.eos? do - pos = @s.pos - - # leading spaces will be reflected by the column of the next token - # the only thing we loose are trailing spaces at the end of the file - next if @s.scan(/ +/) - - # note: after BULLET, LABEL, etc., - # indent will be the column of the next non-newline token - - @tokens << case - # [CR]LF => :NEWLINE - when @s.scan(/\r?\n/) then - token = [:NEWLINE, @s.matched, *pos] - @s.newline! - token - # === text => :HEADER then :TEXT - when @s.scan(/(=+)(\s*)/) then - level = @s[1].length - header = [:HEADER, level, *pos] - - if @s[2] =~ /^\r?\n/ then - @s.unscan(@s[2]) - header - else - pos = @s.pos - @s.scan(/.*/) - @tokens << header - [:TEXT, @s.matched.sub(/\r$/, ''), *pos] - end - # --- (at least 3) and nothing else on the line => :RULE - when @s.scan(/(-{3,}) *\r?$/) then - [:RULE, @s[1].length - 2, *pos] - # * or - followed by white space and text => :BULLET - when @s.scan(/([*-]) +(\S)/) then - @s.unscan(@s[2]) - [:BULLET, @s[1], *pos] - # A. text, a. text, 12. text => :UALPHA, :LALPHA, :NUMBER - when @s.scan(/([a-z]|\d+)\. +(\S)/i) then - # FIXME if tab(s), the column will be wrong - # either support tabs everywhere by first expanding them to - # spaces, or assume that they will have been replaced - # before (and provide a check for that at least in debug - # mode) - list_label = @s[1] - @s.unscan(@s[2]) - list_type = - case list_label - when /[a-z]/ then :LALPHA - when /[A-Z]/ then :UALPHA - when /\d/ then :NUMBER - else - raise ParseError, "BUG token #{list_label}" - end - [list_type, list_label, *pos] - # [text] followed by spaces or end of line => :LABEL - when @s.scan(/\[(.*?)\]( +|\r?$)/) then - [:LABEL, @s[1], *pos] - # text:: followed by spaces or end of line => :NOTE - when @s.scan(/(.*?)::( +|\r?$)/) then - [:NOTE, @s[1], *pos] - # >>> followed by end of line => :BLOCKQUOTE - when @s.scan(/>>> *(\w+)?$/) then - if word = @s[1] - @s.unscan(word) - end - [:BLOCKQUOTE, word, *pos] - # anything else: :TEXT - else - @s.scan(/(.*?)( )?\r?$/) - token = [:TEXT, @s[1], *pos] - - if @s[2] then - @tokens << token - [:BREAK, @s[2], pos[0] + @s[1].length, pos[1]] - else - token - end - end - end - - self - end - - ## - # Returns the current token to the token stream - - def unget - token = @current_token - p :unget => token if @debug - raise Error, 'too many #ungets' if token == @tokens.first - @tokens.unshift token if token - end - -end diff --git a/lib/rdoc/markup/pre_process.rb b/lib/rdoc/markup/pre_process.rb deleted file mode 100644 index 979f2eadae..0000000000 --- a/lib/rdoc/markup/pre_process.rb +++ /dev/null @@ -1,301 +0,0 @@ -# frozen_string_literal: true -## -# Handle common directives that can occur in a block of text: -# -# \:include: filename -# -# Directives can be escaped by preceding them with a backslash. -# -# RDoc plugin authors can register additional directives to be handled by -# using RDoc::Markup::PreProcess::register. -# -# Any directive that is not built-in to RDoc (including those registered via -# plugins) will be stored in the metadata hash on the CodeObject the comment -# is attached to. See RDoc::Markup@Directives for the list of built-in -# directives. - -class RDoc::Markup::PreProcess - - ## - # An RDoc::Options instance that will be filled in with overrides from - # directives - - attr_accessor :options - - ## - # Adds a post-process handler for directives. The handler will be called - # with the result RDoc::Comment (or text String) and the code object for the - # comment (if any). - - def self.post_process &block - @post_processors << block - end - - ## - # Registered post-processors - - def self.post_processors - @post_processors - end - - ## - # Registers +directive+ as one handled by RDoc. If a block is given the - # directive will be replaced by the result of the block, otherwise the - # directive will be removed from the processed text. - # - # The block will be called with the directive name and the directive - # parameter: - # - # RDoc::Markup::PreProcess.register 'my-directive' do |directive, param| - # # replace text, etc. - # end - - def self.register directive, &block - @registered[directive] = block - end - - ## - # Registered directives - - def self.registered - @registered - end - - ## - # Clears all registered directives and post-processors - - def self.reset - @post_processors = [] - @registered = {} - end - - reset - - ## - # Creates a new pre-processor for +input_file_name+ that will look for - # included files in +include_path+ - - def initialize(input_file_name, include_path) - @input_file_name = input_file_name - @include_path = include_path - @options = nil - end - - ## - # Look for directives in the given +text+. - # - # Options that we don't handle are yielded. If the block returns false the - # directive is restored to the text. If the block returns nil or no block - # was given the directive is handled according to the registered directives. - # If a String was returned the directive is replaced with the string. - # - # If no matching directive was registered the directive is restored to the - # text. - # - # If +code_object+ is given and the directive is unknown then the - # directive's parameter is set as metadata on the +code_object+. See - # RDoc::CodeObject#metadata for details. - - def handle text, code_object = nil, &block - first_line = 1 - if RDoc::Comment === text then - comment = text - text = text.text - first_line = comment.line || 1 - end - - # regexp helper (square brackets for optional) - # $1 $2 $3 $4 $5 - # [prefix][\]:directive:[spaces][param]newline - text = text.lines.map.with_index(first_line) do |line, num| - next line unless line =~ /\A([ \t]*(?:#|\/?\*)?[ \t]*)(\\?):([\w-]+):([ \t]*)(.+)?(\r?\n|$)/ - # skip something like ':toto::' - next $& if $4.empty? and $5 and $5[0, 1] == ':' - - # skip if escaped - next "#$1:#$3:#$4#$5\n" unless $2.empty? - - # This is not in handle_directive because I didn't want to pass another - # argument into it - if comment and $3 == 'markup' then - next "#{$1.strip}\n" unless $5 - comment.format = $5.downcase - next "#{$1.strip}\n" - end - - handle_directive $1, $3, $5, code_object, text.encoding, num, &block - end.join - - if comment then - comment.text = text - else - comment = text - end - - self.class.post_processors.each do |handler| - handler.call comment, code_object - end - - text - end - - ## - # Performs the actions described by +directive+ and its parameter +param+. - # - # +code_object+ is used for directives that operate on a class or module. - # +prefix+ is used to ensure the replacement for handled directives is - # correct. +encoding+ is used for the <tt>include</tt> directive. - # - # For a list of directives in RDoc see RDoc::Markup. - #-- - # When 1.8.7 support is ditched prefix can be defaulted to '' - - def handle_directive prefix, directive, param, code_object = nil, - encoding = nil, line = nil - blankline = "#{prefix.strip}\n" - directive = directive.downcase - - case directive - when 'arg', 'args' then - return "#{prefix}:#{directive}: #{param}\n" unless code_object && code_object.kind_of?(RDoc::AnyMethod) - - code_object.params = param - - blankline - when 'category' then - if RDoc::Context === code_object then - section = code_object.add_section param - code_object.temporary_section = section - elsif RDoc::AnyMethod === code_object then - code_object.section_title = param - end - - blankline # ignore category if we're not on an RDoc::Context - when 'doc' then - return blankline unless code_object - code_object.document_self = true - code_object.force_documentation = true - - blankline - when 'enddoc' then - return blankline unless code_object - code_object.done_documenting = true - - blankline - when 'include' then - filename = param.split(' ', 2).first - include_file filename, prefix, encoding - when 'main' then - @options.main_page = param if @options.respond_to? :main_page - - blankline - when 'nodoc' then - return blankline unless code_object - code_object.document_self = nil # notify nodoc - code_object.document_children = param !~ /all/i - - blankline - when 'notnew', 'not_new', 'not-new' then - return blankline unless RDoc::AnyMethod === code_object - - code_object.dont_rename_initialize = true - - blankline - when 'startdoc' then - return blankline unless code_object - - code_object.start_doc - code_object.force_documentation = true - - blankline - when 'stopdoc' then - return blankline unless code_object - - code_object.stop_doc - - blankline - when 'title' then - @options.default_title = param if @options.respond_to? :default_title= - - blankline - when 'yield', 'yields' then - return blankline unless code_object - # remove parameter &block - code_object.params = code_object.params.sub(/,?\s*&\w+/, '') if code_object.params - - code_object.block_params = param || '' - - blankline - else - result = yield directive, param, line if block_given? - - case result - when nil then - code_object.metadata[directive] = param if code_object - - if RDoc::Markup::PreProcess.registered.include? directive then - handler = RDoc::Markup::PreProcess.registered[directive] - result = handler.call directive, param if handler - else - result = "#{prefix}:#{directive}: #{param}\n" - end - when false then - result = "#{prefix}:#{directive}: #{param}\n" - end - - result - end - end - - ## - # Handles the <tt>:include: _filename_</tt> directive. - # - # If the first line of the included file starts with '#', and contains - # an encoding information in the form 'coding:' or 'coding=', it is - # removed. - # - # If all lines in the included file start with a '#', this leading '#' - # is removed before inclusion. The included content is indented like - # the <tt>:include:</tt> directive. - #-- - # so all content will be verbatim because of the likely space after '#'? - # TODO shift left the whole file content in that case - # TODO comment stop/start #-- and #++ in included file must be processed here - - def include_file name, indent, encoding - full_name = find_include_file name - - unless full_name then - warn "Couldn't find file to include '#{name}' from #{@input_file_name}" - return '' - end - - content = RDoc::Encoding.read_file full_name, encoding, true - content = RDoc::Encoding.remove_magic_comment content - - # strip magic comment - content = content.sub(/\A# .*coding[=:].*$/, '').lstrip - - # strip leading '#'s, but only if all lines start with them - if content =~ /^[^#]/ then - content.gsub(/^/, indent) - else - content.gsub(/^#?/, indent) - end - end - - ## - # Look for the given file in the directory containing the current file, - # and then in each of the directories specified in the RDOC_INCLUDE path - - def find_include_file(name) - to_search = [File.dirname(@input_file_name)].concat @include_path - to_search.each do |dir| - full_name = File.join(dir, name) - stat = File.stat(full_name) rescue next - return full_name if stat.readable? - end - nil - end - -end diff --git a/lib/rdoc/markup/raw.rb b/lib/rdoc/markup/raw.rb deleted file mode 100644 index a7c1c210a6..0000000000 --- a/lib/rdoc/markup/raw.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true -## -# A section of text that is added to the output document as-is - -class RDoc::Markup::Raw - - ## - # The component parts of the list - - attr_reader :parts - - ## - # Creates a new Raw containing +parts+ - - def initialize *parts - @parts = [] - @parts.concat parts - end - - ## - # Appends +text+ - - def << text - @parts << text - end - - def == other # :nodoc: - self.class == other.class and @parts == other.parts - end - - ## - # Calls #accept_raw+ on +visitor+ - - def accept visitor - visitor.accept_raw self - end - - ## - # Appends +other+'s parts - - def merge other - @parts.concat other.parts - end - - def pretty_print q # :nodoc: - self.class.name =~ /.*::(\w{1,4})/i - - q.group 2, "[#{$1.downcase}: ", ']' do - q.seplist @parts do |part| - q.pp part - end - end - end - - ## - # Appends +texts+ onto this Paragraph - - def push *texts - self.parts.concat texts - end - - ## - # The raw text - - def text - @parts.join ' ' - end - -end diff --git a/lib/rdoc/markup/regexp_handling.rb b/lib/rdoc/markup/regexp_handling.rb deleted file mode 100644 index c471fe73c7..0000000000 --- a/lib/rdoc/markup/regexp_handling.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true -## -# Hold details of a regexp handling sequence - -class RDoc::Markup::RegexpHandling - - ## - # Regexp handling type - - attr_reader :type - - ## - # Regexp handling text - - attr_accessor :text - - ## - # Creates a new regexp handling sequence of +type+ with +text+ - - def initialize(type, text) - @type, @text = type, text - end - - ## - # Regexp handlings are equal when the have the same text and type - - def ==(o) - self.text == o.text && self.type == o.type - end - - def inspect # :nodoc: - "#<RDoc::Markup::RegexpHandling:0x%x @type=%p, @text=%p>" % [ - object_id, @type, text.dump] - end - - def to_s # :nodoc: - "RegexpHandling: type=#{type} text=#{text.dump}" - end - -end diff --git a/lib/rdoc/markup/rule.rb b/lib/rdoc/markup/rule.rb deleted file mode 100644 index 448148d6d1..0000000000 --- a/lib/rdoc/markup/rule.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true -## -# A horizontal rule with a weight - -class RDoc::Markup::Rule < Struct.new :weight - - ## - # Calls #accept_rule on +visitor+ - - def accept visitor - visitor.accept_rule self - end - - def pretty_print q # :nodoc: - q.group 2, '[rule:', ']' do - q.pp weight - end - end - -end diff --git a/lib/rdoc/markup/table.rb b/lib/rdoc/markup/table.rb deleted file mode 100644 index 27a20f073a..0000000000 --- a/lib/rdoc/markup/table.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true -## -# A section of table - -class RDoc::Markup::Table - # headers of each column - attr_accessor :header - - # alignments of each column - attr_accessor :align - - # body texts of each column - attr_accessor :body - - # Creates new instance - def initialize header, align, body - @header, @align, @body = header, align, body - end - - # :stopdoc: - def == other - self.class == other.class and - @header == other.header and - @align == other.align and - @body == other.body - end - - def accept visitor - visitor.accept_table @header, @body, @align - end - - def pretty_print q - q.group 2, '[Table: ', ']' do - q.group 2, '[Head: ', ']' do - q.seplist @header.zip(@align) do |text, align| - q.pp text - if align - q.text ":" - q.breakable - q.text align.to_s - end - end - end - q.breakable - q.group 2, '[Body: ', ']' do - q.seplist @body do |body| - q.group 2, '[', ']' do - q.seplist body do |text| - q.pp text - end - end - end - end - end - end -end diff --git a/lib/rdoc/markup/to_ansi.rb b/lib/rdoc/markup/to_ansi.rb deleted file mode 100644 index c3eacab21a..0000000000 --- a/lib/rdoc/markup/to_ansi.rb +++ /dev/null @@ -1,93 +0,0 @@ -# frozen_string_literal: true -## -# Outputs RDoc markup with vibrant ANSI color! - -class RDoc::Markup::ToAnsi < RDoc::Markup::ToRdoc - - ## - # Creates a new ToAnsi visitor that is ready to output vibrant ANSI color! - - def initialize markup = nil - super - - @headings.clear - @headings[1] = ["\e[1;32m", "\e[m"] # bold - @headings[2] = ["\e[4;32m", "\e[m"] # underline - @headings[3] = ["\e[32m", "\e[m"] # just green - end - - ## - # Maps attributes to ANSI sequences - - def init_tags - add_tag :BOLD, "\e[1m", "\e[m" - add_tag :TT, "\e[7m", "\e[m" - add_tag :EM, "\e[4m", "\e[m" - end - - ## - # Overrides indent width to ensure output lines up correctly. - - def accept_list_item_end list_item - width = case @list_type.last - when :BULLET then - 2 - when :NOTE, :LABEL then - if @prefix then - @res << @prefix.strip - @prefix = nil - end - - @res << "\n" unless res.length == 1 - 2 - else - bullet = @list_index.last.to_s - @list_index[-1] = @list_index.last.succ - bullet.length + 2 - end - - @indent -= width - end - - ## - # Adds coloring to note and label list items - - def accept_list_item_start list_item - bullet = case @list_type.last - when :BULLET then - '*' - when :NOTE, :LABEL then - labels = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" - - labels << ":\n" unless labels.empty? - - labels - else - @list_index.last.to_s + '.' - end - - case @list_type.last - when :NOTE, :LABEL then - @indent += 2 - @prefix = bullet + (' ' * @indent) - else - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - - width = bullet.gsub(/\e\[[\d;]*m/, '').length + 1 - - @indent += width - end - end - - ## - # Starts accepting with a reset screen - - def start_accepting - super - - @res = ["\e[0m"] - end - -end diff --git a/lib/rdoc/markup/to_bs.rb b/lib/rdoc/markup/to_bs.rb deleted file mode 100644 index b7b73e73f7..0000000000 --- a/lib/rdoc/markup/to_bs.rb +++ /dev/null @@ -1,102 +0,0 @@ -# frozen_string_literal: true -## -# Outputs RDoc markup with hot backspace action! You will probably need a -# pager to use this output format. -# -# This formatter won't work on 1.8.6 because it lacks String#chars. - -class RDoc::Markup::ToBs < RDoc::Markup::ToRdoc - - ## - # Returns a new ToBs that is ready for hot backspace action! - - def initialize markup = nil - super - - @in_b = false - @in_em = false - end - - ## - # Sets a flag that is picked up by #annotate to do the right thing in - # #convert_string - - def init_tags - add_tag :BOLD, '+b', '-b' - add_tag :EM, '+_', '-_' - add_tag :TT, '', '' # we need in_tt information maintained - end - - ## - # Makes heading text bold. - - def accept_heading heading - use_prefix or @res << ' ' * @indent - @res << @headings[heading.level][0] - @in_b = true - @res << attributes(heading.text) - @in_b = false - @res << @headings[heading.level][1] - @res << "\n" - end - - ## - # Prepares the visitor for consuming +list_item+ - - def accept_list_item_start list_item - type = @list_type.last - - case type - when :NOTE, :LABEL then - bullets = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" - - bullets << ":\n" unless bullets.empty? - - @prefix = ' ' * @indent - @indent += 2 - @prefix << bullets + (' ' * @indent) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - width = bullet.length + 1 - @indent += width - end - end - - ## - # Turns on or off regexp handling for +convert_string+ - - def annotate tag - case tag - when '+b' then @in_b = true - when '-b' then @in_b = false - when '+_' then @in_em = true - when '-_' then @in_em = false - end - '' - end - - ## - # Calls convert_string on the result of convert_regexp_handling - - def convert_regexp_handling target - convert_string super - end - - ## - # Adds bold or underline mixed with backspaces - - def convert_string string - return string unless @in_b or @in_em - chars = if @in_b then - string.chars.map do |char| "#{char}\b#{char}" end - elsif @in_em then - string.chars.map do |char| "_\b#{char}" end - end - - chars.join - end - -end diff --git a/lib/rdoc/markup/to_html.rb b/lib/rdoc/markup/to_html.rb deleted file mode 100644 index 91cadf9d16..0000000000 --- a/lib/rdoc/markup/to_html.rb +++ /dev/null @@ -1,452 +0,0 @@ -# frozen_string_literal: true -require 'cgi/util' - -## -# Outputs RDoc markup as HTML. - -class RDoc::Markup::ToHtml < RDoc::Markup::Formatter - - include RDoc::Text - - # :section: Utilities - - ## - # Maps RDoc::Markup::Parser::LIST_TOKENS types to HTML tags - - LIST_TYPE_TO_HTML = { - :BULLET => ['<ul>', '</ul>'], - :LABEL => ['<dl class="rdoc-list label-list">', '</dl>'], - :LALPHA => ['<ol style="list-style-type: lower-alpha">', '</ol>'], - :NOTE => ['<dl class="rdoc-list note-list">', '</dl>'], - :NUMBER => ['<ol>', '</ol>'], - :UALPHA => ['<ol style="list-style-type: upper-alpha">', '</ol>'], - } - - attr_reader :res # :nodoc: - attr_reader :in_list_entry # :nodoc: - attr_reader :list # :nodoc: - - ## - # The RDoc::CodeObject HTML is being generated for. This is used to - # generate namespaced URI fragments - - attr_accessor :code_object - - ## - # Path to this document for relative links - - attr_accessor :from_path - - # :section: - - ## - # Creates a new formatter that will output HTML - - def initialize options, markup = nil - super - - @code_object = nil - @from_path = '' - @in_list_entry = nil - @list = nil - @th = nil - @hard_break = "<br>\n" - - init_regexp_handlings - - init_tags - end - - # :section: Regexp Handling - # - # These methods are used by regexp handling markup added by RDoc::Markup#add_regexp_handling. - - # :nodoc: - URL_CHARACTERS_REGEXP_STR = /[A-Za-z0-9\-._~:\/\?#\[\]@!$&'\(\)*+,;%=]/.source - - ## - # Adds regexp handlings. - - def init_regexp_handlings - # external links - @markup.add_regexp_handling(/(?:link:|https?:|mailto:|ftp:|irc:|www\.)#{URL_CHARACTERS_REGEXP_STR}+\w/, - :HYPERLINK) - init_link_notation_regexp_handlings - end - - ## - # Adds regexp handlings about link notations. - - def init_link_notation_regexp_handlings - add_regexp_handling_RDOCLINK - add_regexp_handling_TIDYLINK - end - - def handle_RDOCLINK url # :nodoc: - case url - when /^rdoc-ref:/ - CGI.escapeHTML($') - when /^rdoc-label:/ - text = $' - - text = case text - when /\Alabel-/ then $' - when /\Afootmark-/ then $' - when /\Afoottext-/ then $' - else text - end - - gen_url CGI.escapeHTML(url), CGI.escapeHTML(text) - when /^rdoc-image:/ - %[<img src=\"#{CGI.escapeHTML($')}\">] - when /\Ardoc-[a-z]+:/ - CGI.escapeHTML($') - end - end - - ## - # +target+ is a <code><br></code> - - def handle_regexp_HARD_BREAK target - '<br>' - end - - ## - # +target+ is a potential link. The following schemes are handled: - # - # <tt>mailto:</tt>:: - # Inserted as-is. - # <tt>http:</tt>:: - # Links are checked to see if they reference an image. If so, that image - # gets inserted using an <tt><img></tt> tag. Otherwise a conventional - # <tt><a href></tt> is used. - # <tt>link:</tt>:: - # Reference to a local file relative to the output directory. - - def handle_regexp_HYPERLINK(target) - url = CGI.escapeHTML(target.text) - - gen_url url, url - end - - ## - # +target+ is an rdoc-schemed link that will be converted into a hyperlink. - # - # For the +rdoc-ref+ scheme the named reference will be returned without - # creating a link. - # - # For the +rdoc-label+ scheme the footnote and label prefixes are stripped - # when creating a link. All other contents will be linked verbatim. - - def handle_regexp_RDOCLINK target - handle_RDOCLINK target.text - end - - ## - # This +target+ is a link where the label is different from the URL - # <tt>label[url]</tt> or <tt>{long label}[url]</tt> - - def handle_regexp_TIDYLINK(target) - text = target.text - - return text unless - text =~ /^\{(.*)\}\[(.*?)\]$/ or text =~ /^(\S+)\[(.*?)\]$/ - - label = $1 - url = CGI.escapeHTML($2) - - if /^rdoc-image:/ =~ label - label = handle_RDOCLINK(label) - else - label = CGI.escapeHTML(label) - end - - gen_url url, label - end - - # :section: Visitor - # - # These methods implement the HTML visitor. - - ## - # Prepares the visitor for HTML generation - - def start_accepting - @res = [] - @in_list_entry = [] - @list = [] - end - - ## - # Returns the generated output - - def end_accepting - @res.join - end - - ## - # Adds +block_quote+ to the output - - def accept_block_quote block_quote - @res << "\n<blockquote>" - - block_quote.parts.each do |part| - part.accept self - end - - @res << "</blockquote>\n" - end - - ## - # Adds +paragraph+ to the output - - def accept_paragraph paragraph - @res << "\n<p>" - text = paragraph.text @hard_break - text = text.gsub(/(#{SPACE_SEPARATED_LETTER_CLASS})?\K\r?\n(?=(?(1)(#{SPACE_SEPARATED_LETTER_CLASS})?))/o) { - defined?($2) && ' ' - } - @res << to_html(text) - @res << "</p>\n" - end - - ## - # Adds +verbatim+ to the output - - def accept_verbatim verbatim - text = verbatim.text.rstrip - - klass = nil - - content = if verbatim.ruby? or parseable? text then - begin - tokens = RDoc::Parser::RipperStateLex.parse text - klass = ' class="ruby"' - - result = RDoc::TokenStream.to_html tokens - result = result + "\n" unless "\n" == result[-1] - result - rescue - CGI.escapeHTML text - end - else - CGI.escapeHTML text - end - - if @options.pipe then - @res << "\n<pre><code>#{CGI.escapeHTML text}\n</code></pre>\n" - else - @res << "\n<pre#{klass}>#{content}</pre>\n" - end - end - - ## - # Adds +rule+ to the output - - def accept_rule rule - @res << "<hr>\n" - end - - ## - # Prepares the visitor for consuming +list+ - - def accept_list_start(list) - @list << list.type - @res << html_list_name(list.type, true) - @in_list_entry.push false - end - - ## - # Finishes consumption of +list+ - - def accept_list_end(list) - @list.pop - if tag = @in_list_entry.pop - @res << tag - end - @res << html_list_name(list.type, false) << "\n" - end - - ## - # Prepares the visitor for consuming +list_item+ - - def accept_list_item_start(list_item) - if tag = @in_list_entry.last - @res << tag - end - - @res << list_item_start(list_item, @list.last) - end - - ## - # Finishes consumption of +list_item+ - - def accept_list_item_end(list_item) - @in_list_entry[-1] = list_end_for(@list.last) - end - - ## - # Adds +blank_line+ to the output - - def accept_blank_line(blank_line) - # @res << annotate("<p />") << "\n" - end - - ## - # Adds +heading+ to the output. The headings greater than 6 are trimmed to - # level 6. - - def accept_heading heading - level = [6, heading.level].min - - label = heading.label @code_object - - @res << if @options.output_decoration - "\n<h#{level} id=\"#{label}\">" - else - "\n<h#{level}>" - end - @res << to_html(heading.text) - unless @options.pipe then - @res << "<span><a href=\"##{label}\">¶</a>" - @res << " <a href=\"#top\">↑</a></span>" - end - @res << "</h#{level}>\n" - end - - ## - # Adds +raw+ to the output - - def accept_raw raw - @res << raw.parts.join("\n") - end - - ## - # Adds +table+ to the output - - def accept_table header, body, aligns - @res << "\n<table role=\"table\">\n<thead>\n<tr>\n" - header.zip(aligns) do |text, align| - @res << '<th' - @res << ' align="' << align << '"' if align - @res << '>' << to_html(text) << "</th>\n" - end - @res << "</tr>\n</thead>\n<tbody>\n" - body.each do |row| - @res << "<tr>\n" - row.zip(aligns) do |text, align| - @res << '<td' - @res << ' align="' << align << '"' if align - @res << '>' << to_html(text) << "</td>\n" - end - @res << "</tr>\n" - end - @res << "</tbody>\n</table>\n" - end - - # :section: Utilities - - ## - # CGI-escapes +text+ - - def convert_string(text) - CGI.escapeHTML text - end - - ## - # Generate a link to +url+ with content +text+. Handles the special cases - # for img: and link: described under handle_regexp_HYPERLINK - - def gen_url url, text - scheme, url, id = parse_url url - - if %w[http https link].include?(scheme) and - url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then - "<img src=\"#{url}\" />" - else - if scheme != 'link' and %r%\A((?!https?:)(?:[^/#]*/)*+)([^/#]+)\.(rb|rdoc|md)(?=\z|#)%i =~ url - url = "#$1#{$2.tr('.', '_')}_#$3.html#$'" - end - - text = text.sub %r%^#{scheme}:/*%i, '' - text = text.sub %r%^[*\^](\d+)$%, '\1' - - link = "<a#{id} href=\"#{url}\">#{text}</a>" - - link = "<sup>#{link}</sup>" if /"foot/ =~ id - - link - end - end - - ## - # Determines the HTML list element for +list_type+ and +open_tag+ - - def html_list_name(list_type, open_tag) - tags = LIST_TYPE_TO_HTML[list_type] - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" unless tags - tags[open_tag ? 0 : 1] - end - - ## - # Maps attributes to HTML tags - - def init_tags - add_tag :BOLD, "<strong>", "</strong>" - add_tag :TT, "<code>", "</code>" - add_tag :EM, "<em>", "</em>" - end - - ## - # Returns the HTML tag for +list_type+, possible using a label from - # +list_item+ - - def list_item_start(list_item, list_type) - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA then - "<li>" - when :LABEL, :NOTE then - Array(list_item.label).map do |label| - "<dt>#{to_html label}\n" - end.join << "<dd>" - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end - - ## - # Returns the HTML end-tag for +list_type+ - - def list_end_for(list_type) - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA then - "</li>" - when :LABEL, :NOTE then - "</dd>" - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end - - ## - # Returns true if text is valid ruby syntax - - def parseable? text - verbose, $VERBOSE = $VERBOSE, nil - catch(:valid) do - eval("BEGIN { throw :valid, true }\n#{text}") - end - rescue SyntaxError - false - ensure - $VERBOSE = verbose - end - - ## - # Converts +item+ to HTML using RDoc::Text#to_html - - def to_html item - super convert_flow @am.flow item - end - -end diff --git a/lib/rdoc/markup/to_html_crossref.rb b/lib/rdoc/markup/to_html_crossref.rb deleted file mode 100644 index 9b5de62fd6..0000000000 --- a/lib/rdoc/markup/to_html_crossref.rb +++ /dev/null @@ -1,175 +0,0 @@ -# frozen_string_literal: true -## -# Subclass of the RDoc::Markup::ToHtml class that supports looking up method -# names, classes, etc to create links. RDoc::CrossReference is used to -# generate those links based on the current context. - -class RDoc::Markup::ToHtmlCrossref < RDoc::Markup::ToHtml - - # :stopdoc: - ALL_CROSSREF_REGEXP = RDoc::CrossReference::ALL_CROSSREF_REGEXP - CLASS_REGEXP_STR = RDoc::CrossReference::CLASS_REGEXP_STR - CROSSREF_REGEXP = RDoc::CrossReference::CROSSREF_REGEXP - METHOD_REGEXP_STR = RDoc::CrossReference::METHOD_REGEXP_STR - # :startdoc: - - ## - # RDoc::CodeObject for generating references - - attr_accessor :context - - ## - # Should we show '#' characters on method references? - - attr_accessor :show_hash - - ## - # Creates a new crossref resolver that generates links relative to +context+ - # which lives at +from_path+ in the generated files. '#' characters on - # references are removed unless +show_hash+ is true. Only method names - # preceded by '#' or '::' are linked, unless +hyperlink_all+ is true. - - def initialize(options, from_path, context, markup = nil) - raise ArgumentError, 'from_path cannot be nil' if from_path.nil? - - super options, markup - - @context = context - @from_path = from_path - @hyperlink_all = @options.hyperlink_all - @show_hash = @options.show_hash - - @cross_reference = RDoc::CrossReference.new @context - end - - # :nodoc: - def init_link_notation_regexp_handlings - add_regexp_handling_RDOCLINK - - # The crossref must be linked before tidylink because Klass.method[:sym] - # will be processed as a tidylink first and will be broken. - crossref_re = @options.hyperlink_all ? ALL_CROSSREF_REGEXP : CROSSREF_REGEXP - @markup.add_regexp_handling crossref_re, :CROSSREF - - add_regexp_handling_TIDYLINK - end - - ## - # Creates a link to the reference +name+ if the name exists. If +text+ is - # given it is used as the link text, otherwise +name+ is used. - - def cross_reference name, text = nil, code = true - lookup = name - - name = name[1..-1] unless @show_hash if name[0, 1] == '#' - - if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/ - text ||= [CGI.unescape($'), (" at <code>#{$1}</code>" if $~.begin(1))].join("") - code = false - else - text ||= name - end - - link lookup, text, code - end - - ## - # We're invoked when any text matches the CROSSREF pattern. If we find the - # corresponding reference, generate a link. If the name we're looking for - # contains no punctuation, we look for it up the module/class chain. For - # example, ToHtml is found, even without the <tt>RDoc::Markup::</tt> prefix, - # because we look for it in module Markup first. - - def handle_regexp_CROSSREF(target) - name = target.text - - return name if name =~ /@[\w-]+\.[\w-]/ # labels that look like emails - - unless @hyperlink_all then - # This ensures that words entirely consisting of lowercase letters will - # not have cross-references generated (to suppress lots of erroneous - # cross-references to "new" in text, for instance) - return name if name =~ /\A[a-z]*\z/ - end - - cross_reference name - end - - ## - # Handles <tt>rdoc-ref:</tt> scheme links and allows RDoc::Markup::ToHtml to - # handle other schemes. - - def handle_regexp_HYPERLINK target - return cross_reference $' if target.text =~ /\Ardoc-ref:/ - - super - end - - ## - # +target+ is an rdoc-schemed link that will be converted into a hyperlink. - # For the rdoc-ref scheme the cross-reference will be looked up and the - # given name will be used. - # - # All other contents are handled by - # {the superclass}[rdoc-ref:RDoc::Markup::ToHtml#handle_regexp_RDOCLINK] - - def handle_regexp_RDOCLINK target - url = target.text - - case url - when /\Ardoc-ref:/ then - cross_reference $' - else - super - end - end - - ## - # Generates links for <tt>rdoc-ref:</tt> scheme URLs and allows - # RDoc::Markup::ToHtml to handle other schemes. - - def gen_url url, text - return super unless url =~ /\Ardoc-ref:/ - - name = $' - cross_reference name, text, name == text - end - - ## - # Creates an HTML link to +name+ with the given +text+. - - def link name, text, code = true - if !(name.end_with?('+@', '-@')) and name =~ /(.*[^#:])?@/ - name = $1 - label = $' - end - - ref = @cross_reference.resolve name, text if name - - case ref - when String then - ref - else - path = ref ? ref.as_href(@from_path) : +"" - - if code and RDoc::CodeObject === ref and !(RDoc::TopLevel === ref) - text = "<code>#{CGI.escapeHTML text}</code>" - end - - if label - if path =~ /#/ - path << "-label-#{label}" - elsif ref&.sections&.any? { |section| label == section.title } - path << "##{label}" - elsif ref.respond_to?(:aref) - path << "##{ref.aref}-label-#{label}" - else - path << "#label-#{label}" - end - end - - "<a href=\"#{path}\">#{text}</a>" - end - end - -end diff --git a/lib/rdoc/markup/to_html_snippet.rb b/lib/rdoc/markup/to_html_snippet.rb deleted file mode 100644 index f471395a3a..0000000000 --- a/lib/rdoc/markup/to_html_snippet.rb +++ /dev/null @@ -1,287 +0,0 @@ -# frozen_string_literal: true -## -# Outputs RDoc markup as paragraphs with inline markup only. - -class RDoc::Markup::ToHtmlSnippet < RDoc::Markup::ToHtml - - ## - # After this many characters the input will be cut off. - - attr_reader :character_limit - - ## - # The number of characters seen so far. - - attr_reader :characters # :nodoc: - - ## - # The attribute bitmask - - attr_reader :mask - - ## - # After this many paragraphs the input will be cut off. - - attr_reader :paragraph_limit - - ## - # Count of paragraphs found - - attr_reader :paragraphs - - ## - # Creates a new ToHtmlSnippet formatter that will cut off the input on the - # next word boundary after the given number of +characters+ or +paragraphs+ - # of text have been encountered. - - def initialize options, characters = 100, paragraphs = 3, markup = nil - super options, markup - - @character_limit = characters - @paragraph_limit = paragraphs - - @characters = 0 - @mask = 0 - @paragraphs = 0 - - @markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF - end - - ## - # Adds +heading+ to the output as a paragraph - - def accept_heading heading - @res << "<p>#{to_html heading.text}\n" - - add_paragraph - end - - ## - # Raw sections are untrusted and ignored - - alias accept_raw ignore - - ## - # Rules are ignored - - alias accept_rule ignore - - ## - # Adds +paragraph+ to the output - - def accept_paragraph paragraph - para = @in_list_entry.last || "<p>" - - text = paragraph.text @hard_break - - @res << "#{para}#{to_html text}\n" - - add_paragraph - end - - ## - # Finishes consumption of +list_item+ - - def accept_list_item_end list_item - end - - ## - # Prepares the visitor for consuming +list_item+ - - def accept_list_item_start list_item - @res << list_item_start(list_item, @list.last) - end - - ## - # Prepares the visitor for consuming +list+ - - def accept_list_start list - @list << list.type - @res << html_list_name(list.type, true) - @in_list_entry.push '' - end - - ## - # Adds +verbatim+ to the output - - def accept_verbatim verbatim - throw :done if @characters >= @character_limit - input = verbatim.text.rstrip - - text = truncate input - text << ' ...' unless text == input - - super RDoc::Markup::Verbatim.new text - - add_paragraph - end - - ## - # Prepares the visitor for HTML snippet generation - - def start_accepting - super - - @characters = 0 - end - - ## - # Removes escaping from the cross-references in +target+ - - def handle_regexp_CROSSREF target - target.text.sub(/\A\\/, '') - end - - ## - # +target+ is a <code><br></code> - - def handle_regexp_HARD_BREAK target - @characters -= 4 - '<br>' - end - - ## - # Lists are paragraphs, but notes and labels have a separator - - def list_item_start list_item, list_type - throw :done if @characters >= @character_limit - - case list_type - when :BULLET, :LALPHA, :NUMBER, :UALPHA then - "<p>" - when :LABEL, :NOTE then - labels = Array(list_item.label).map do |label| - to_html label - end.join ', ' - - labels << " — " unless labels.empty? - - start = "<p>#{labels}" - @characters += 1 # try to include the label - start - else - raise RDoc::Error, "Invalid list type: #{list_type.inspect}" - end - end - - ## - # Returns just the text of +link+, +url+ is only used to determine the link - # type. - - def gen_url url, text - if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ then - type = "link" - elsif url =~ /([A-Za-z]+):(.*)/ then - type = $1 - else - type = "http" - end - - if (type == "http" or type == "https" or type == "link") and - url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then - '' - else - text.sub(%r%^#{type}:/*%, '') - end - end - - ## - # In snippets, there are no lists - - def html_list_name list_type, open_tag - '' - end - - ## - # Throws +:done+ when paragraph_limit paragraphs have been encountered - - def add_paragraph - @paragraphs += 1 - - throw :done if @paragraphs >= @paragraph_limit - end - - ## - # Marks up +content+ - - def convert content - catch :done do - return super - end - - end_accepting - end - - ## - # Converts flow items +flow+ - - def convert_flow flow - throw :done if @characters >= @character_limit - - res = [] - @mask = 0 - - flow.each do |item| - case item - when RDoc::Markup::AttrChanger then - off_tags res, item - on_tags res, item - when String then - text = convert_string item - res << truncate(text) - when RDoc::Markup::RegexpHandling then - text = convert_regexp_handling item - res << truncate(text) - else - raise "Unknown flow element: #{item.inspect}" - end - - if @characters >= @character_limit then - off_tags res, RDoc::Markup::AttrChanger.new(0, @mask) - break - end - end - - res << ' ...' if @characters >= @character_limit - - res.join - end - - ## - # Maintains a bitmask to allow HTML elements to be closed properly. See - # RDoc::Markup::Formatter. - - def on_tags res, item - @mask ^= item.turn_on - - super - end - - ## - # Maintains a bitmask to allow HTML elements to be closed properly. See - # RDoc::Markup::Formatter. - - def off_tags res, item - @mask ^= item.turn_off - - super - end - - ## - # Truncates +text+ at the end of the first word after the character_limit. - - def truncate text - length = text.length - characters = @characters - @characters += length - - return text if @characters < @character_limit - - remaining = @character_limit - characters - - text =~ /\A(.{#{remaining},}?)(\s|$)/m # TODO word-break instead of \s? - - $1 - end - -end diff --git a/lib/rdoc/markup/to_joined_paragraph.rb b/lib/rdoc/markup/to_joined_paragraph.rb deleted file mode 100644 index 31cbe0853c..0000000000 --- a/lib/rdoc/markup/to_joined_paragraph.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true -## -# Joins the parts of an RDoc::Markup::Paragraph into a single String. -# -# This allows for easier maintenance and testing of Markdown support. -# -# This formatter only works on Paragraph instances. Attempting to process -# other markup syntax items will not work. - -class RDoc::Markup::ToJoinedParagraph < RDoc::Markup::Formatter - - def initialize # :nodoc: - super nil - end - - def start_accepting # :nodoc: - end - - def end_accepting # :nodoc: - end - - ## - # Converts the parts of +paragraph+ to a single entry. - - def accept_paragraph paragraph - parts = paragraph.parts.chunk do |part| - String === part - end.flat_map do |string, chunk| - string ? chunk.join.rstrip : chunk - end - - paragraph.parts.replace parts - end - - alias accept_block_quote ignore - alias accept_heading ignore - alias accept_list_end ignore - alias accept_list_item_end ignore - alias accept_list_item_start ignore - alias accept_list_start ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_verbatim ignore - alias accept_table ignore - -end diff --git a/lib/rdoc/markup/to_label.rb b/lib/rdoc/markup/to_label.rb deleted file mode 100644 index cf808364e9..0000000000 --- a/lib/rdoc/markup/to_label.rb +++ /dev/null @@ -1,74 +0,0 @@ -# frozen_string_literal: true -require 'cgi/util' - -## -# Creates HTML-safe labels suitable for use in id attributes. Tidylinks are -# converted to their link part and cross-reference links have the suppression -# marks removed (\\SomeClass is converted to SomeClass). - -class RDoc::Markup::ToLabel < RDoc::Markup::Formatter - - attr_reader :res # :nodoc: - - ## - # Creates a new formatter that will output HTML-safe labels - - def initialize markup = nil - super nil, markup - - @markup.add_regexp_handling RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF - @markup.add_regexp_handling(/(((\{.*?\})|\b\S+?)\[\S+?\])/, :TIDYLINK) - - add_tag :BOLD, '', '' - add_tag :TT, '', '' - add_tag :EM, '', '' - - @res = [] - end - - ## - # Converts +text+ to an HTML-safe label - - def convert text - label = convert_flow @am.flow text - - CGI.escape(label).gsub('%', '-').sub(/^-/, '') - end - - ## - # Converts the CROSSREF +target+ to plain text, removing the suppression - # marker, if any - - def handle_regexp_CROSSREF target - text = target.text - - text.sub(/^\\/, '') - end - - ## - # Converts the TIDYLINK +target+ to just the text part - - def handle_regexp_TIDYLINK target - text = target.text - - return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ - - $1 - end - - alias accept_blank_line ignore - alias accept_block_quote ignore - alias accept_heading ignore - alias accept_list_end ignore - alias accept_list_item_end ignore - alias accept_list_item_start ignore - alias accept_list_start ignore - alias accept_paragraph ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_verbatim ignore - alias end_accepting ignore - alias handle_regexp_HARD_BREAK ignore - alias start_accepting ignore - -end diff --git a/lib/rdoc/markup/to_markdown.rb b/lib/rdoc/markup/to_markdown.rb deleted file mode 100644 index b915fab60b..0000000000 --- a/lib/rdoc/markup/to_markdown.rb +++ /dev/null @@ -1,191 +0,0 @@ -# frozen_string_literal: true -# :markup: markdown - -## -# Outputs parsed markup as Markdown - -class RDoc::Markup::ToMarkdown < RDoc::Markup::ToRdoc - - ## - # Creates a new formatter that will output Markdown format text - - def initialize markup = nil - super - - @headings[1] = ['# ', ''] - @headings[2] = ['## ', ''] - @headings[3] = ['### ', ''] - @headings[4] = ['#### ', ''] - @headings[5] = ['##### ', ''] - @headings[6] = ['###### ', ''] - - add_regexp_handling_RDOCLINK - add_regexp_handling_TIDYLINK - - @hard_break = " \n" - end - - ## - # Maps attributes to HTML sequences - - def init_tags - add_tag :BOLD, '**', '**' - add_tag :EM, '*', '*' - add_tag :TT, '`', '`' - end - - ## - # Adds a newline to the output - - def handle_regexp_HARD_BREAK target - " \n" - end - - ## - # Finishes consumption of `list` - - def accept_list_end list - super - end - - ## - # Finishes consumption of `list_item` - - def accept_list_item_end list_item - width = case @list_type.last - when :BULLET then - 4 - when :NOTE, :LABEL then - use_prefix - - @res << "\n" - - 4 - else - @list_index[-1] = @list_index.last.succ - 4 - end - - @indent -= width - end - - ## - # Prepares the visitor for consuming `list_item` - - def accept_list_item_start list_item - type = @list_type.last - - case type - when :NOTE, :LABEL then - bullets = Array(list_item.label).map do |label| - attributes(label).strip - end.join "\n" - - bullets << "\n" unless bullets.empty? - - @prefix = ' ' * @indent - @indent += 4 - @prefix << bullets << ":" << (' ' * (@indent - 1)) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(4) - - @indent += 4 - end - end - - ## - # Prepares the visitor for consuming `list` - - def accept_list_start list - case list.type - when :BULLET, :LABEL, :NOTE then - @list_index << nil - when :LALPHA, :NUMBER, :UALPHA then - @list_index << 1 - else - raise RDoc::Error, "invalid list type #{list.type}" - end - - @list_width << 4 - @list_type << list.type - end - - ## - # Adds `rule` to the output - - def accept_rule rule - use_prefix or @res << ' ' * @indent - @res << '-' * 3 - @res << "\n" - end - - ## - # Outputs `verbatim` indented 4 columns - - def accept_verbatim verbatim - indent = ' ' * (@indent + 4) - - verbatim.parts.each do |part| - @res << indent unless part == "\n" - @res << part - end - - @res << "\n" - end - - ## - # Creates a Markdown-style URL from +url+ with +text+. - - def gen_url url, text - scheme, url, = parse_url url - - "[#{text.sub(%r{^#{scheme}:/*}i, '')}](#{url})" - end - - ## - # Handles <tt>rdoc-</tt> type links for footnotes. - - def handle_rdoc_link url - case url - when /^rdoc-ref:/ then - $' - when /^rdoc-label:footmark-(\d+)/ then - "[^#{$1}]:" - when /^rdoc-label:foottext-(\d+)/ then - "[^#{$1}]" - when /^rdoc-label:label-/ then - gen_url url, $' - when /^rdoc-image:/ then - "" - when /^rdoc-[a-z]+:/ then - $' - end - end - - ## - # Converts the RDoc markup tidylink into a Markdown.style link. - - def handle_regexp_TIDYLINK target - text = target.text - - return text unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/ - - label = $1 - url = $2 - - if url =~ /^rdoc-label:foot/ then - handle_rdoc_link url - else - gen_url url, label - end - end - - ## - # Converts the rdoc-...: links into a Markdown.style links. - - def handle_regexp_RDOCLINK target - handle_rdoc_link target.text - end - -end diff --git a/lib/rdoc/markup/to_rdoc.rb b/lib/rdoc/markup/to_rdoc.rb deleted file mode 100644 index 90763ccfdb..0000000000 --- a/lib/rdoc/markup/to_rdoc.rb +++ /dev/null @@ -1,352 +0,0 @@ -# frozen_string_literal: true -## -# Outputs RDoc markup as RDoc markup! (mostly) - -class RDoc::Markup::ToRdoc < RDoc::Markup::Formatter - - ## - # Current indent amount for output in characters - - attr_accessor :indent - - ## - # Output width in characters - - attr_accessor :width - - ## - # Stack of current list indexes for alphabetic and numeric lists - - attr_reader :list_index - - ## - # Stack of list types - - attr_reader :list_type - - ## - # Stack of list widths for indentation - - attr_reader :list_width - - ## - # Prefix for the next list item. See #use_prefix - - attr_reader :prefix - - ## - # Output accumulator - - attr_reader :res - - ## - # Creates a new formatter that will output (mostly) \RDoc markup - - def initialize markup = nil - super nil, markup - - @markup.add_regexp_handling(/\\\S/, :SUPPRESSED_CROSSREF) - @width = 78 - init_tags - - @headings = {} - @headings.default = [] - - @headings[1] = ['= ', ''] - @headings[2] = ['== ', ''] - @headings[3] = ['=== ', ''] - @headings[4] = ['==== ', ''] - @headings[5] = ['===== ', ''] - @headings[6] = ['====== ', ''] - - @hard_break = "\n" - end - - ## - # Maps attributes to HTML sequences - - def init_tags - add_tag :BOLD, "<b>", "</b>" - add_tag :TT, "<tt>", "</tt>" - add_tag :EM, "<em>", "</em>" - end - - ## - # Adds +blank_line+ to the output - - def accept_blank_line blank_line - @res << "\n" - end - - ## - # Adds +paragraph+ to the output - - def accept_block_quote block_quote - @indent += 2 - - block_quote.parts.each do |part| - @prefix = '> ' - - part.accept self - end - - @indent -= 2 - end - - ## - # Adds +heading+ to the output - - def accept_heading heading - use_prefix or @res << ' ' * @indent - @res << @headings[heading.level][0] - @res << attributes(heading.text) - @res << @headings[heading.level][1] - @res << "\n" - end - - ## - # Finishes consumption of +list+ - - def accept_list_end list - @list_index.pop - @list_type.pop - @list_width.pop - end - - ## - # Finishes consumption of +list_item+ - - def accept_list_item_end list_item - width = case @list_type.last - when :BULLET then - 2 - when :NOTE, :LABEL then - if @prefix then - @res << @prefix.strip - @prefix = nil - end - - @res << "\n" - 2 - else - bullet = @list_index.last.to_s - @list_index[-1] = @list_index.last.succ - bullet.length + 2 - end - - @indent -= width - end - - ## - # Prepares the visitor for consuming +list_item+ - - def accept_list_item_start list_item - type = @list_type.last - - case type - when :NOTE, :LABEL then - stripped_labels = Array(list_item.label).map do |label| - attributes(label).strip - end - - bullets = case type - when :NOTE - stripped_labels.map { |b| "#{b}::" } - when :LABEL - stripped_labels.map { |b| "[#{b}]" } - end - - bullets = bullets.join("\n") - bullets << "\n" unless stripped_labels.empty? - - @prefix = ' ' * @indent - @indent += 2 - @prefix << bullets + (' ' * @indent) - else - bullet = type == :BULLET ? '*' : @list_index.last.to_s + '.' - @prefix = (' ' * @indent) + bullet.ljust(bullet.length + 1) - width = bullet.length + 1 - @indent += width - end - end - - ## - # Prepares the visitor for consuming +list+ - - def accept_list_start list - case list.type - when :BULLET then - @list_index << nil - @list_width << 1 - when :LABEL, :NOTE then - @list_index << nil - @list_width << 2 - when :LALPHA then - @list_index << 'a' - @list_width << list.items.length.to_s.length - when :NUMBER then - @list_index << 1 - @list_width << list.items.length.to_s.length - when :UALPHA then - @list_index << 'A' - @list_width << list.items.length.to_s.length - else - raise RDoc::Error, "invalid list type #{list.type}" - end - - @list_type << list.type - end - - ## - # Adds +paragraph+ to the output - - def accept_paragraph paragraph - text = paragraph.text @hard_break - wrap attributes text - end - - ## - # Adds +paragraph+ to the output - - def accept_indented_paragraph paragraph - @indent += paragraph.indent - text = paragraph.text @hard_break - wrap attributes text - @indent -= paragraph.indent - end - - ## - # Adds +raw+ to the output - - def accept_raw raw - @res << raw.parts.join("\n") - end - - ## - # Adds +rule+ to the output - - def accept_rule rule - use_prefix or @res << ' ' * @indent - @res << '-' * (@width - @indent) - @res << "\n" - end - - ## - # Outputs +verbatim+ indented 2 columns - - def accept_verbatim verbatim - indent = ' ' * (@indent + 2) - - verbatim.parts.each do |part| - @res << indent unless part == "\n" - @res << part - end - - @res << "\n" - end - - ## - # Adds +table+ to the output - - def accept_table header, body, aligns - widths = header.zip(*body).map do |cols| - cols.map(&:size).max - end - aligns = aligns.map do |a| - case a - when nil - :center - when :left - :ljust - when :right - :rjust - end - end - @res << header.zip(widths, aligns).map do |h, w, a| - h.__send__(a, w) - end.join("|").rstrip << "\n" - @res << widths.map {|w| "-" * w }.join("|") << "\n" - body.each do |row| - @res << row.zip(widths, aligns).map do |t, w, a| - t.__send__(a, w) - end.join("|").rstrip << "\n" - end - end - - ## - # Applies attribute-specific markup to +text+ using RDoc::AttributeManager - - def attributes text - flow = @am.flow text.dup - convert_flow flow - end - - ## - # Returns the generated output - - def end_accepting - @res.join - end - - ## - # Removes preceding \\ from the suppressed crossref +target+ - - def handle_regexp_SUPPRESSED_CROSSREF target - text = target.text - text = text.sub('\\', '') unless in_tt? - text - end - - ## - # Adds a newline to the output - - def handle_regexp_HARD_BREAK target - "\n" - end - - ## - # Prepares the visitor for text generation - - def start_accepting - @res = [""] - @indent = 0 - @prefix = nil - - @list_index = [] - @list_type = [] - @list_width = [] - end - - ## - # Adds the stored #prefix to the output and clears it. Lists generate a - # prefix for later consumption. - - def use_prefix - prefix, @prefix = @prefix, nil - @res << prefix if prefix - - prefix - end - - ## - # Wraps +text+ to #width - - def wrap text - return unless text && !text.empty? - - text_len = @width - @indent - - text_len = 20 if text_len < 20 - - next_prefix = ' ' * @indent - - prefix = @prefix || next_prefix - @prefix = nil - - text.scan(/\G(?:([^ \n]{#{text_len}})(?=[^ \n])|(.{1,#{text_len}})(?:[ \n]|\z))/) do - @res << prefix << ($1 || $2) << "\n" - prefix = next_prefix - end - end - -end diff --git a/lib/rdoc/markup/to_table_of_contents.rb b/lib/rdoc/markup/to_table_of_contents.rb deleted file mode 100644 index e5b8225ba3..0000000000 --- a/lib/rdoc/markup/to_table_of_contents.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true -## -# Extracts just the RDoc::Markup::Heading elements from a -# RDoc::Markup::Document to help build a table of contents - -class RDoc::Markup::ToTableOfContents < RDoc::Markup::Formatter - - @to_toc = nil - - ## - # Singleton for table-of-contents generation - - def self.to_toc - @to_toc ||= new - end - - ## - # Output accumulator - - attr_reader :res - - ## - # Omits headings with a level less than the given level. - - attr_accessor :omit_headings_below - - def initialize # :nodoc: - super nil - - @omit_headings_below = nil - end - - ## - # Adds +document+ to the output, using its heading cutoff if present - - def accept_document document - @omit_headings_below = document.omit_headings_below - - super - end - - ## - # Adds +heading+ to the table of contents - - def accept_heading heading - @res << heading unless suppressed? heading - end - - ## - # Returns the table of contents - - def end_accepting - @res - end - - ## - # Prepares the visitor for text generation - - def start_accepting - @omit_headings_below = nil - @res = [] - end - - ## - # Returns true if +heading+ is below the display threshold - - def suppressed? heading - return false unless @omit_headings_below - - heading.level > @omit_headings_below - end - - # :stopdoc: - alias accept_block_quote ignore - alias accept_raw ignore - alias accept_rule ignore - alias accept_blank_line ignore - alias accept_paragraph ignore - alias accept_verbatim ignore - alias accept_list_end ignore - alias accept_list_item_start ignore - alias accept_list_item_end ignore - alias accept_list_end_bullet ignore - alias accept_list_start ignore - alias accept_table ignore - # :startdoc: - -end diff --git a/lib/rdoc/markup/to_test.rb b/lib/rdoc/markup/to_test.rb deleted file mode 100644 index 30113da561..0000000000 --- a/lib/rdoc/markup/to_test.rb +++ /dev/null @@ -1,69 +0,0 @@ -# frozen_string_literal: true -## -# This Markup outputter is used for testing purposes. - -class RDoc::Markup::ToTest < RDoc::Markup::Formatter - - # :stopdoc: - - ## - # :section: Visitor - - def start_accepting - @res = [] - @list = [] - end - - def end_accepting - @res - end - - def accept_paragraph(paragraph) - @res << convert_flow(@am.flow(paragraph.text)) - end - - def accept_raw raw - @res << raw.parts.join - end - - def accept_verbatim(verbatim) - @res << verbatim.text.gsub(/^(\S)/, ' \1') - end - - def accept_list_start(list) - @list << case list.type - when :BULLET then - '*' - when :NUMBER then - '1' - else - list.type - end - end - - def accept_list_end(list) - @list.pop - end - - def accept_list_item_start(list_item) - @res << "#{' ' * (@list.size - 1)}#{@list.last}: " - end - - def accept_list_item_end(list_item) - end - - def accept_blank_line(blank_line) - @res << "\n" - end - - def accept_heading(heading) - @res << "#{'=' * heading.level} #{heading.text}" - end - - def accept_rule(rule) - @res << '-' * rule.weight - end - - # :startdoc: - -end diff --git a/lib/rdoc/markup/to_tt_only.rb b/lib/rdoc/markup/to_tt_only.rb deleted file mode 100644 index 9ac14ed235..0000000000 --- a/lib/rdoc/markup/to_tt_only.rb +++ /dev/null @@ -1,120 +0,0 @@ -# frozen_string_literal: true -## -# Extracts sections of text enclosed in plus, tt or code. Used to discover -# undocumented parameters. - -class RDoc::Markup::ToTtOnly < RDoc::Markup::Formatter - - ## - # Stack of list types - - attr_reader :list_type - - ## - # Output accumulator - - attr_reader :res - - ## - # Creates a new tt-only formatter. - - def initialize markup = nil - super nil, markup - - add_tag :TT, nil, nil - end - - ## - # Adds tts from +block_quote+ to the output - - def accept_block_quote block_quote - tt_sections block_quote.text - end - - ## - # Pops the list type for +list+ from #list_type - - def accept_list_end list - @list_type.pop - end - - ## - # Pushes the list type for +list+ onto #list_type - - def accept_list_start list - @list_type << list.type - end - - ## - # Prepares the visitor for consuming +list_item+ - - def accept_list_item_start list_item - case @list_type.last - when :NOTE, :LABEL then - Array(list_item.label).map do |label| - tt_sections label - end.flatten - end - end - - ## - # Adds +paragraph+ to the output - - def accept_paragraph paragraph - tt_sections(paragraph.text) - end - - ## - # Does nothing to +markup_item+ because it doesn't have any user-built - # content - - def do_nothing markup_item - end - - alias accept_blank_line do_nothing # :nodoc: - alias accept_heading do_nothing # :nodoc: - alias accept_list_item_end do_nothing # :nodoc: - alias accept_raw do_nothing # :nodoc: - alias accept_rule do_nothing # :nodoc: - alias accept_verbatim do_nothing # :nodoc: - - ## - # Extracts tt sections from +text+ - - def tt_sections text - flow = @am.flow text.dup - - flow.each do |item| - case item - when String then - @res << item if in_tt? - when RDoc::Markup::AttrChanger then - off_tags res, item - on_tags res, item - when RDoc::Markup::RegexpHandling then - @res << convert_regexp_handling(item) if in_tt? # TODO can this happen? - else - raise "Unknown flow element: #{item.inspect}" - end - end - - res - end - - ## - # Returns an Array of items that were wrapped in plus, tt or code. - - def end_accepting - @res.compact - end - - ## - # Prepares the visitor for gathering tt sections - - def start_accepting - @res = [] - - @list_type = [] - end - -end diff --git a/lib/rdoc/markup/verbatim.rb b/lib/rdoc/markup/verbatim.rb deleted file mode 100644 index f51c2cfa14..0000000000 --- a/lib/rdoc/markup/verbatim.rb +++ /dev/null @@ -1,83 +0,0 @@ -# frozen_string_literal: true -## -# A section of verbatim text - -class RDoc::Markup::Verbatim < RDoc::Markup::Raw - - ## - # Format of this verbatim section - - attr_accessor :format - - def initialize *parts # :nodoc: - super - - @format = nil - end - - def == other # :nodoc: - super and @format == other.format - end - - ## - # Calls #accept_verbatim on +visitor+ - - def accept visitor - visitor.accept_verbatim self - end - - ## - # Collapses 3+ newlines into two newlines - - def normalize - parts = [] - - newlines = 0 - - @parts.each do |part| - case part - when /^\s*\n/ then - newlines += 1 - parts << part if newlines == 1 - else - newlines = 0 - parts << part - end - end - - parts.pop if parts.last =~ /\A\r?\n\z/ - - @parts = parts - end - - def pretty_print q # :nodoc: - self.class.name =~ /.*::(\w{1,4})/i - - q.group 2, "[#{$1.downcase}: ", ']' do - if @format then - q.text "format: #{@format}" - q.breakable - end - - q.seplist @parts do |part| - q.pp part - end - end - end - - ## - # Is this verbatim section Ruby code? - - def ruby? - @format ||= nil # TODO for older ri data, switch the tree to marshal_dump - @format == :ruby - end - - ## - # The text of the section - - def text - @parts.join - end - -end diff --git a/lib/rdoc/options.rb b/lib/rdoc/options.rb deleted file mode 100644 index a607a76d56..0000000000 --- a/lib/rdoc/options.rb +++ /dev/null @@ -1,1348 +0,0 @@ -# frozen_string_literal: true -require 'optparse' -require 'pathname' - -## -# RDoc::Options handles the parsing and storage of options -# -# == Saved Options -# -# You can save some options like the markup format in the -# <tt>.rdoc_options</tt> file in your gem. The easiest way to do this is: -# -# rdoc --markup tomdoc --write-options -# -# Which will automatically create the file and fill it with the options you -# specified. -# -# The following options will not be saved since they interfere with the user's -# preferences or with the normal operation of RDoc: -# -# * +--coverage-report+ -# * +--dry-run+ -# * +--encoding+ -# * +--force-update+ -# * +--format+ -# * +--pipe+ -# * +--quiet+ -# * +--template+ -# * +--verbose+ -# -# == Custom Options -# -# Generators can hook into RDoc::Options to add generator-specific command -# line options. -# -# When <tt>--format</tt> is encountered in ARGV, RDoc calls ::setup_options on -# the generator class to add extra options to the option parser. Options for -# custom generators must occur after <tt>--format</tt>. <tt>rdoc --help</tt> -# will list options for all installed generators. -# -# Example: -# -# class RDoc::Generator::Spellcheck -# RDoc::RDoc.add_generator self -# -# def self.setup_options rdoc_options -# op = rdoc_options.option_parser -# -# op.on('--spell-dictionary DICTIONARY', -# RDoc::Options::Path) do |dictionary| -# rdoc_options.spell_dictionary = dictionary -# end -# end -# end -# -# Of course, RDoc::Options does not respond to +spell_dictionary+ by default -# so you will need to add it: -# -# class RDoc::Options -# -# ## -# # The spell dictionary used by the spell-checking plugin. -# -# attr_accessor :spell_dictionary -# -# end -# -# == Option Validators -# -# OptionParser validators will validate and cast user input values. In -# addition to the validators that ship with OptionParser (String, Integer, -# Float, TrueClass, FalseClass, Array, Regexp, Date, Time, URI, etc.), -# RDoc::Options adds Path, PathArray and Template. - -class RDoc::Options - - ## - # The deprecated options. - - DEPRECATED = { - '--accessor' => 'support discontinued', - '--diagram' => 'support discontinued', - '--help-output' => 'support discontinued', - '--image-format' => 'was an option for --diagram', - '--inline-source' => 'source code is now always inlined', - '--merge' => 'ri now always merges class information', - '--one-file' => 'support discontinued', - '--op-name' => 'support discontinued', - '--opname' => 'support discontinued', - '--promiscuous' => 'files always only document their content', - '--ri-system' => 'Ruby installers use other techniques', - } - - ## - # RDoc options ignored (or handled specially) by --write-options - - SPECIAL = %w[ - coverage_report - dry_run - encoding - files - force_output - force_update - generator - generator_name - generator_options - generators - locale - op_dir - page_dir - option_parser - pipe - rdoc_include - root - static_path - stylesheet_url - template - template_dir - update_output_dir - verbosity - write_options - ] - - ## - # Option validator for OptionParser that matches a directory that exists on - # the filesystem. - - Directory = Object.new - - ## - # Option validator for OptionParser that matches a file or directory that - # exists on the filesystem. - - Path = Object.new - - ## - # Option validator for OptionParser that matches a comma-separated list of - # files or directories that exist on the filesystem. - - PathArray = Object.new - - ## - # Option validator for OptionParser that matches a template directory for an - # installed generator that lives in - # <tt>"rdoc/generator/template/#{template_name}"</tt> - - Template = Object.new - - ## - # Character-set for HTML output. #encoding is preferred over #charset - - attr_accessor :charset - - ## - # If true, RDoc will not write any files. - - attr_accessor :dry_run - - ## - # The output encoding. All input files will be transcoded to this encoding. - # - # The default encoding is UTF-8. This is set via --encoding. - - attr_accessor :encoding - - ## - # Files matching this pattern will be excluded - - attr_writer :exclude - - ## - # The list of files to be processed - - attr_accessor :files - - ## - # Create the output even if the output directory does not look - # like an rdoc output directory - - attr_accessor :force_output - - ## - # Scan newer sources than the flag file if true. - - attr_accessor :force_update - - ## - # Formatter to mark up text with - - attr_accessor :formatter - - ## - # Description of the output generator (set with the <tt>--format</tt> option) - - attr_accessor :generator - - ## - # For #== - - attr_reader :generator_name # :nodoc: - - ## - # Loaded generator options. Used to prevent --help from loading the same - # options multiple times. - - attr_accessor :generator_options - - ## - # Old rdoc behavior: hyperlink all words that match a method name, - # even if not preceded by '#' or '::' - - attr_accessor :hyperlink_all - - ## - # Include line numbers in the source code - - attr_accessor :line_numbers - - ## - # The output locale. - - attr_accessor :locale - - ## - # The directory where locale data live. - - attr_accessor :locale_dir - - ## - # Name of the file, class or module to display in the initial index page (if - # not specified the first file we encounter is used) - - attr_accessor :main_page - - ## - # The markup format. - # One of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. - # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. - attr_accessor :markup - - ## - # If true, only report on undocumented files - - attr_accessor :coverage_report - - ## - # The name of the output directory - - attr_accessor :op_dir - - ## - # The OptionParser for this instance - - attr_accessor :option_parser - - ## - # Output heading decorations? - attr_accessor :output_decoration - - ## - # Directory where guides, FAQ, and other pages not associated with a class - # live. You may leave this unset if these are at the root of your project. - - attr_accessor :page_dir - - ## - # Is RDoc in pipe mode? - - attr_accessor :pipe - - ## - # Array of directories to search for files to satisfy an :include: - - attr_accessor :rdoc_include - - ## - # Root of the source documentation will be generated for. Set this when - # building documentation outside the source directory. Defaults to the - # current directory. - - attr_accessor :root - - ## - # Include the '#' at the front of hyperlinked instance method names - - attr_accessor :show_hash - - ## - # Directory to copy static files from - - attr_accessor :static_path - - ## - # The number of columns in a tab - - attr_accessor :tab_width - - ## - # Template to be used when generating output - - attr_accessor :template - - ## - # Directory the template lives in - - attr_accessor :template_dir - - ## - # Additional template stylesheets - - attr_accessor :template_stylesheets - - ## - # Documentation title - - attr_accessor :title - - ## - # Should RDoc update the timestamps in the output dir? - - attr_accessor :update_output_dir - - ## - # Verbosity, zero means quiet - - attr_accessor :verbosity - - ## - # URL of web cvs frontend - - attr_accessor :webcvs - - ## - # Minimum visibility of a documented method. One of +:public+, +:protected+, - # +:private+ or +:nodoc+. - # - # The +:nodoc+ visibility ignores all directives related to visibility. The - # other visibilities may be overridden on a per-method basis with the :doc: - # directive. - - attr_reader :visibility - - ## - # Indicates if files of test suites should be skipped - attr_accessor :skip_tests - - ## - # Embed mixin methods, attributes, and constants into class documentation. Set via - # +--[no-]embed-mixins+ (Default is +false+.) - attr_accessor :embed_mixins - - def initialize loaded_options = nil # :nodoc: - init_ivars - override loaded_options if loaded_options - end - - def init_ivars # :nodoc: - @dry_run = false - @embed_mixins = false - @exclude = %w[ - ~\z \.orig\z \.rej\z \.bak\z - \.gemspec\z - ] - @files = nil - @force_output = false - @force_update = true - @generator = nil - @generator_name = nil - @generator_options = [] - @generators = RDoc::RDoc::GENERATORS - @hyperlink_all = false - @line_numbers = false - @locale = nil - @locale_name = nil - @locale_dir = 'locale' - @main_page = nil - @markup = 'rdoc' - @coverage_report = false - @op_dir = nil - @page_dir = nil - @pipe = false - @output_decoration = true - @rdoc_include = [] - @root = Pathname(Dir.pwd) - @show_hash = false - @static_path = [] - @stylesheet_url = nil # TODO remove in RDoc 4 - @tab_width = 8 - @template = nil - @template_dir = nil - @template_stylesheets = [] - @title = nil - @update_output_dir = true - @verbosity = 1 - @visibility = :protected - @webcvs = nil - @write_options = false - @encoding = Encoding::UTF_8 - @charset = @encoding.name - @skip_tests = true - end - - def init_with map # :nodoc: - init_ivars - - encoding = map['encoding'] - @encoding = encoding ? Encoding.find(encoding) : encoding - - @charset = map['charset'] - @embed_mixins = map['embed_mixins'] - @exclude = map['exclude'] - @generator_name = map['generator_name'] - @hyperlink_all = map['hyperlink_all'] - @line_numbers = map['line_numbers'] - @locale_name = map['locale_name'] - @locale_dir = map['locale_dir'] - @main_page = map['main_page'] - @markup = map['markup'] - @op_dir = map['op_dir'] - @show_hash = map['show_hash'] - @tab_width = map['tab_width'] - @template_dir = map['template_dir'] - @title = map['title'] - @visibility = map['visibility'] - @webcvs = map['webcvs'] - - @rdoc_include = sanitize_path map['rdoc_include'] - @static_path = sanitize_path map['static_path'] - end - - def yaml_initialize tag, map # :nodoc: - init_with map - end - - def override map # :nodoc: - if map.has_key?('encoding') - encoding = map['encoding'] - @encoding = encoding ? Encoding.find(encoding) : encoding - end - - @charset = map['charset'] if map.has_key?('charset') - @embed_mixins = map['embed_mixins'] if map.has_key?('embed_mixins') - @exclude = map['exclude'] if map.has_key?('exclude') - @generator_name = map['generator_name'] if map.has_key?('generator_name') - @hyperlink_all = map['hyperlink_all'] if map.has_key?('hyperlink_all') - @line_numbers = map['line_numbers'] if map.has_key?('line_numbers') - @locale_name = map['locale_name'] if map.has_key?('locale_name') - @locale_dir = map['locale_dir'] if map.has_key?('locale_dir') - @main_page = map['main_page'] if map.has_key?('main_page') - @markup = map['markup'] if map.has_key?('markup') - @op_dir = map['op_dir'] if map.has_key?('op_dir') - @page_dir = map['page_dir'] if map.has_key?('page_dir') - @show_hash = map['show_hash'] if map.has_key?('show_hash') - @tab_width = map['tab_width'] if map.has_key?('tab_width') - @template_dir = map['template_dir'] if map.has_key?('template_dir') - @title = map['title'] if map.has_key?('title') - @visibility = map['visibility'] if map.has_key?('visibility') - @webcvs = map['webcvs'] if map.has_key?('webcvs') - - if map.has_key?('rdoc_include') - @rdoc_include = sanitize_path map['rdoc_include'] - end - if map.has_key?('static_path') - @static_path = sanitize_path map['static_path'] - end - end - - def == other # :nodoc: - self.class === other and - @encoding == other.encoding and - @embed_mixins == other.embed_mixins and - @generator_name == other.generator_name and - @hyperlink_all == other.hyperlink_all and - @line_numbers == other.line_numbers and - @locale == other.locale and - @locale_dir == other.locale_dir and - @main_page == other.main_page and - @markup == other.markup and - @op_dir == other.op_dir and - @rdoc_include == other.rdoc_include and - @show_hash == other.show_hash and - @static_path == other.static_path and - @tab_width == other.tab_width and - @template == other.template and - @title == other.title and - @visibility == other.visibility and - @webcvs == other.webcvs - end - - ## - # Check that the files on the command line exist - - def check_files - @files.delete_if do |file| - if File.exist? file then - if File.readable? file then - false - else - warn "file '#{file}' not readable" - - true - end - else - warn "file '#{file}' not found" - - true - end - end - end - - ## - # Ensure only one generator is loaded - - def check_generator - if @generator then - raise OptionParser::InvalidOption, - "generator already set to #{@generator_name}" - end - end - - ## - # Set the title, but only if not already set. Used to set the title - # from a source file, so that a title set from the command line - # will have the priority. - - def default_title=(string) - @title ||= string - end - - ## - # For dumping YAML - - def to_yaml(*options) # :nodoc: - encoding = @encoding ? @encoding.name : nil - - yaml = {} - yaml['encoding'] = encoding - yaml['static_path'] = sanitize_path(@static_path) - yaml['rdoc_include'] = sanitize_path(@rdoc_include) - yaml['page_dir'] = (sanitize_path([@page_dir]).first if @page_dir) - - ivars = instance_variables.map { |ivar| ivar.to_s[1..-1] } - ivars -= SPECIAL - - ivars.sort.each do |ivar| - yaml[ivar] = instance_variable_get("@#{ivar}") - end - yaml.to_yaml - end - - ## - # Create a regexp for #exclude - - def exclude - if @exclude.nil? or Regexp === @exclude then - # done, #finish is being re-run - @exclude - elsif @exclude.empty? then - nil - else - Regexp.new(@exclude.join("|")) - end - end - - ## - # Completes any unfinished option setup business such as filtering for - # existent files, creating a regexp for #exclude and setting a default - # #template. - - def finish - if @write_options then - write_options - exit - end - - @op_dir ||= 'doc' - - root = @root.to_s - if @rdoc_include.empty? || !@rdoc_include.include?(root) - @rdoc_include << root - end - - @exclude = self.exclude - - finish_page_dir - - check_files - - # If no template was specified, use the default template for the output - # formatter - - unless @template then - @template = @generator_name - @template_dir = template_dir_for @template - end - - if @locale_name - @locale = RDoc::I18n::Locale[@locale_name] - @locale.load(@locale_dir) - else - @locale = nil - end - - self - end - - ## - # Fixes the page_dir to be relative to the root_dir and adds the page_dir to - # the files list. - - def finish_page_dir - return unless @page_dir - - @files << @page_dir - - page_dir = Pathname(@page_dir) - begin - page_dir = page_dir.expand_path.relative_path_from @root - rescue ArgumentError - # On Windows, sometimes crosses different drive letters. - page_dir = page_dir.expand_path - end - - @page_dir = page_dir - end - - ## - # Returns a properly-space list of generators and their descriptions. - - def generator_descriptions - lengths = [] - - generators = RDoc::RDoc::GENERATORS.map do |name, generator| - lengths << name.length - - description = generator::DESCRIPTION if - generator.const_defined? :DESCRIPTION - - [name, description] - end - - longest = lengths.max - - generators.sort.map do |name, description| - if description then - " %-*s - %s" % [longest, name, description] - else - " #{name}" - end - end.join "\n" - end - - ## - # Parses command line options. - - def parse argv - ignore_invalid = true - - argv.insert(0, *ENV['RDOCOPT'].split) if ENV['RDOCOPT'] - - opts = OptionParser.new do |opt| - @option_parser = opt - opt.program_name = File.basename $0 - opt.version = RDoc::VERSION - opt.release = nil - opt.summary_indent = ' ' * 4 - opt.banner = <<-EOF -Usage: #{opt.program_name} [options] [names...] - - Files are parsed, and the information they contain collected, before any - output is produced. This allows cross references between all files to be - resolved. If a name is a directory, it is traversed. If no names are - specified, all Ruby files in the current directory (and subdirectories) are - processed. - - How RDoc generates output depends on the output formatter being used, and on - the options you give. - - Options can be specified via the RDOCOPT environment variable, which - functions similar to the RUBYOPT environment variable for ruby. - - $ export RDOCOPT="--show-hash" - - will make rdoc show hashes in method links by default. Command-line options - always will override those in RDOCOPT. - - Available formatters: - -#{generator_descriptions} - - RDoc understands the following file formats: - - EOF - - parsers = Hash.new { |h, parser| h[parser] = [] } - - RDoc::Parser.parsers.each do |regexp, parser| - parsers[parser.name.sub('RDoc::Parser::', '')] << regexp.source - end - - parsers.sort.each do |parser, regexp| - opt.banner += " - #{parser}: #{regexp.join ', '}\n" - end - opt.banner += " - TomDoc: Only in ruby files\n" - - opt.banner += "\n The following options are deprecated:\n\n" - - name_length = DEPRECATED.keys.sort_by { |k| k.length }.last.length - - DEPRECATED.sort_by { |k,| k }.each do |name, reason| - opt.banner += " %*1$2$s %3$s\n" % [-name_length, name, reason] - end - - opt.accept Template do |template| - template_dir = template_dir_for template - - unless template_dir then - $stderr.puts "could not find template #{template}" - nil - else - [template, template_dir] - end - end - - opt.accept Directory do |directory| - directory = File.expand_path directory - - raise OptionParser::InvalidArgument unless File.directory? directory - - directory - end - - opt.accept Path do |path| - path = File.expand_path path - - raise OptionParser::InvalidArgument unless File.exist? path - - path - end - - opt.accept PathArray do |paths,| - paths = if paths then - paths.split(',').map { |d| d unless d.empty? } - end - - paths.map do |path| - path = File.expand_path path - - raise OptionParser::InvalidArgument unless File.exist? path - - path - end - end - - opt.separator nil - opt.separator "Parsing options:" - opt.separator nil - - opt.on("--encoding=ENCODING", "-e", Encoding.list.map { |e| e.name }, - "Specifies the output encoding. All files", - "read will be converted to this encoding.", - "The default encoding is UTF-8.", - "--encoding is preferred over --charset") do |value| - @encoding = Encoding.find value - @charset = @encoding.name # may not be valid value - end - - opt.separator nil - - opt.on("--locale=NAME", - "Specifies the output locale.") do |value| - @locale_name = value - end - - opt.on("--locale-data-dir=DIR", - "Specifies the directory where locale data live.") do |value| - @locale_dir = value - end - - opt.separator nil - - opt.on("--all", "-a", - "Synonym for --visibility=private.") do |value| - @visibility = :private - end - - opt.separator nil - - opt.on("--exclude=PATTERN", "-x", Regexp, - "Do not process files or directories", - "matching PATTERN.") do |value| - @exclude << value - end - - opt.separator nil - - opt.on("--no-skipping-tests", nil, - "Don't skip generating documentation for test and spec files") do |value| - @skip_tests = false - end - - opt.separator nil - - opt.on("--extension=NEW=OLD", "-E", - "Treat files ending with .new as if they", - "ended with .old. Using '-E cgi=rb' will", - "cause xxx.cgi to be parsed as a Ruby file.") do |value| - new, old = value.split(/=/, 2) - - unless new and old then - raise OptionParser::InvalidArgument, "Invalid parameter to '-E'" - end - - unless RDoc::Parser.alias_extension old, new then - raise OptionParser::InvalidArgument, "Unknown extension .#{old} to -E" - end - end - - opt.separator nil - - opt.on("--[no-]force-update", "-U", - "Forces rdoc to scan all sources even if", - "no files are newer than the flag file.") do |value| - @force_update = value - end - - opt.separator nil - - opt.on("--pipe", "-p", - "Convert RDoc on stdin to HTML") do - @pipe = true - end - - opt.separator nil - - opt.on("--tab-width=WIDTH", "-w", Integer, - "Set the width of tab characters.") do |value| - raise OptionParser::InvalidArgument, - "#{value} is an invalid tab width" if value <= 0 - @tab_width = value - end - - opt.separator nil - - opt.on("--visibility=VISIBILITY", "-V", RDoc::VISIBILITIES + [:nodoc], - "Minimum visibility to document a method.", - "One of 'public', 'protected' (the default),", - "'private' or 'nodoc' (show everything)") do |value| - @visibility = value - end - - opt.separator nil - - opt.on("--[no-]embed-mixins", - "Embed mixin methods, attributes, and constants", - "into class documentation. (default false)") do |value| - @embed_mixins = value - end - - opt.separator nil - - markup_formats = RDoc::Text::MARKUP_FORMAT.keys.sort - - opt.on("--markup=MARKUP", markup_formats, - "The markup format for the named files.", - "The default is rdoc. Valid values are:", - markup_formats.join(', ')) do |value| - @markup = value - end - - opt.separator nil - - opt.on("--root=ROOT", Directory, - "Root of the source tree documentation", - "will be generated for. Set this when", - "building documentation outside the", - "source directory. Default is the", - "current directory.") do |root| - @root = Pathname(root) - end - - opt.separator nil - - opt.on("--page-dir=DIR", Directory, - "Directory where guides, your FAQ or", - "other pages not associated with a class", - "live. Set this when you don't store", - "such files at your project root.", - "NOTE: Do not use the same file name in", - "the page dir and the root of your project") do |page_dir| - @page_dir = page_dir - end - - opt.separator nil - opt.separator "Common generator options:" - opt.separator nil - - opt.on("--force-output", "-O", - "Forces rdoc to write the output files,", - "even if the output directory exists", - "and does not seem to have been created", - "by rdoc.") do |value| - @force_output = value - end - - opt.separator nil - - generator_text = @generators.keys.map { |name| " #{name}" }.sort - - opt.on("-f", "--fmt=FORMAT", "--format=FORMAT", @generators.keys, - "Set the output formatter. One of:", *generator_text) do |value| - check_generator - - @generator_name = value.downcase - setup_generator - end - - opt.separator nil - - opt.on("--include=DIRECTORIES", "-i", PathArray, - "Set (or add to) the list of directories to", - "be searched when satisfying :include:", - "requests. Can be used more than once.") do |value| - @rdoc_include.concat value.map { |dir| dir.strip } - end - - opt.separator nil - - opt.on("--[no-]coverage-report=[LEVEL]", "--[no-]dcov", "-C", Integer, - "Prints a report on undocumented items.", - "Does not generate files.") do |value| - value = 0 if value.nil? # Integer converts -C to nil - - @coverage_report = value - @force_update = true if value - end - - opt.separator nil - - opt.on("--output=DIR", "--op", "-o", - "Set the output directory.") do |value| - @op_dir = value - end - - opt.separator nil - - opt.on("-d", - "Deprecated --diagram option.", - "Prevents firing debug mode", - "with legacy invocation.") do |value| - end - - opt.separator nil - opt.separator 'HTML generator options:' - opt.separator nil - - opt.on("--charset=CHARSET", "-c", - "Specifies the output HTML character-set.", - "Use --encoding instead of --charset if", - "available.") do |value| - @charset = value - end - - opt.separator nil - - opt.on("--hyperlink-all", "-A", - "Generate hyperlinks for all words that", - "correspond to known methods, even if they", - "do not start with '#' or '::' (legacy", - "behavior).") do |value| - @hyperlink_all = value - end - - opt.separator nil - - opt.on("--main=NAME", "-m", - "NAME will be the initial page displayed.") do |value| - @main_page = value - end - - opt.separator nil - - opt.on("--[no-]line-numbers", "-N", - "Include line numbers in the source code.", - "By default, only the number of the first", - "line is displayed, in a leading comment.") do |value| - @line_numbers = value - end - - opt.separator nil - - opt.on("--show-hash", "-H", - "A name of the form #name in a comment is a", - "possible hyperlink to an instance method", - "name. When displayed, the '#' is removed", - "unless this option is specified.") do |value| - @show_hash = value - end - - opt.separator nil - - opt.on("--template=NAME", "-T", Template, - "Set the template used when generating", - "output. The default depends on the", - "formatter used.") do |(template, template_dir)| - @template = template - @template_dir = template_dir - end - - opt.separator nil - - opt.on("--template-stylesheets=FILES", PathArray, - "Set (or add to) the list of files to", - "include with the html template.") do |value| - @template_stylesheets.concat value - end - - opt.separator nil - - opt.on("--title=TITLE", "-t", - "Set TITLE as the title for HTML output.") do |value| - @title = value - end - - opt.separator nil - - opt.on("--copy-files=PATH", Path, - "Specify a file or directory to copy static", - "files from.", - "If a file is given it will be copied into", - "the output dir. If a directory is given the", - "entire directory will be copied.", - "You can use this multiple times") do |value| - @static_path << value - end - - opt.separator nil - - opt.on("--webcvs=URL", "-W", - "Specify a URL for linking to a web frontend", - "to CVS. If the URL contains a '\%s', the", - "name of the current file will be", - "substituted; if the URL doesn't contain a", - "'\%s', the filename will be appended to it.") do |value| - @webcvs = value - end - - opt.separator nil - opt.separator "ri generator options:" - opt.separator nil - - opt.on("--ri", "-r", - "Generate output for use by `ri`. The files", - "are stored in the '.rdoc' directory under", - "your home directory unless overridden by a", - "subsequent --op parameter, so no special", - "privileges are needed.") do |value| - check_generator - - @generator_name = "ri" - @op_dir ||= RDoc::RI::Paths::HOMEDIR - setup_generator - end - - opt.separator nil - - opt.on("--ri-site", "-R", - "Generate output for use by `ri`. The files", - "are stored in a site-wide directory,", - "making them accessible to others, so", - "special privileges are needed.") do |value| - check_generator - - @generator_name = "ri" - @op_dir = RDoc::RI::Paths.site_dir - setup_generator - end - - opt.separator nil - opt.separator "Generic options:" - opt.separator nil - - opt.on("--write-options", - "Write .rdoc_options to the current", - "directory with the given options. Not all", - "options will be used. See RDoc::Options", - "for details.") do |value| - @write_options = true - end - - opt.separator nil - - opt.on("--[no-]dry-run", - "Don't write any files") do |value| - @dry_run = value - end - - opt.separator nil - - opt.on("-D", "--[no-]debug", - "Displays lots on internal stuff.") do |value| - $DEBUG_RDOC = value - end - - opt.separator nil - - opt.on("--[no-]ignore-invalid", - "Ignore invalid options and continue", - "(default true).") do |value| - ignore_invalid = value - end - - opt.separator nil - - opt.on("--quiet", "-q", - "Don't show progress as we parse.") do |value| - @verbosity = 0 - end - - opt.separator nil - - opt.on("--verbose", "-V", - "Display extra progress as RDoc parses") do |value| - @verbosity = 2 - end - - opt.separator nil - - opt.on("--version", "-v", "print the version") do - puts opt.version - exit - end - - opt.separator nil - - opt.on("--help", "-h", "Display this help") do - RDoc::RDoc::GENERATORS.each_key do |generator| - setup_generator generator - end - - puts opt.help - exit - end - - opt.separator nil - end - - setup_generator 'darkfish' if - argv.grep(/\A(-f|--fmt|--format|-r|-R|--ri|--ri-site)\b/).empty? - - deprecated = [] - invalid = [] - - begin - opts.parse! argv - rescue OptionParser::ParseError => e - if DEPRECATED[e.args.first] then - deprecated << e.args.first - elsif %w[--format --ri -r --ri-site -R].include? e.args.first then - raise - else - invalid << e.args.join(' ') - end - - retry - end - - unless @generator then - @generator = RDoc::Generator::Darkfish - @generator_name = 'darkfish' - end - - if @pipe and not argv.empty? then - @pipe = false - invalid << '-p (with files)' - end - - unless quiet then - deprecated.each do |opt| - $stderr.puts 'option ' + opt + ' is deprecated: ' + DEPRECATED[opt] - end - end - - unless invalid.empty? then - invalid = "invalid options: #{invalid.join ', '}" - - if ignore_invalid then - unless quiet then - $stderr.puts invalid - $stderr.puts '(invalid options are ignored)' - end - else - unless quiet then - $stderr.puts opts - end - $stderr.puts invalid - exit 1 - end - end - - @files = argv.dup - - self - end - - ## - # Don't display progress as we process the files - - def quiet - @verbosity.zero? - end - - ## - # Set quietness to +bool+ - - def quiet= bool - @verbosity = bool ? 0 : 1 - end - - ## - # Removes directories from +path+ that are outside the current directory - - def sanitize_path path - require 'pathname' - dot = Pathname.new('.').expand_path - - path.reject do |item| - path = Pathname.new(item).expand_path - is_reject = nil - relative = nil - begin - relative = path.relative_path_from(dot).to_s - rescue ArgumentError - # On Windows, sometimes crosses different drive letters. - is_reject = true - else - is_reject = relative.start_with? '..' - end - is_reject - end - end - - ## - # Set up an output generator for the named +generator_name+. - # - # If the found generator responds to :setup_options it will be called with - # the options instance. This allows generators to add custom options or set - # default options. - - def setup_generator generator_name = @generator_name - @generator = @generators[generator_name] - - unless @generator then - raise OptionParser::InvalidArgument, - "Invalid output formatter #{generator_name}" - end - - return if @generator_options.include? @generator - - @generator_name = generator_name - @generator_options << @generator - - if @generator.respond_to? :setup_options then - @option_parser ||= OptionParser.new - @generator.setup_options self - end - end - - ## - # Finds the template dir for +template+ - - def template_dir_for template - template_path = File.join 'rdoc', 'generator', 'template', template - - $LOAD_PATH.map do |path| - File.join File.expand_path(path), template_path - end.find do |dir| - File.directory? dir - end - end - - # Sets the minimum visibility of a documented method. - # - # Accepts +:public+, +:protected+, +:private+, +:nodoc+, or +:all+. - # - # When +:all+ is passed, visibility is set to +:private+, similarly to - # RDOCOPT="--all", see #visibility for more information. - - def visibility= visibility - case visibility - when :all - @visibility = :private - else - @visibility = visibility - end - end - - ## - # Displays a warning using Kernel#warn if we're being verbose - - def warn message - super message if @verbosity > 1 - end - - ## - # Writes the YAML file .rdoc_options to the current directory containing the - # parsed options. - - def write_options - RDoc.load_yaml - - File.open '.rdoc_options', 'w' do |io| - io.set_encoding Encoding::UTF_8 - - io.print to_yaml - end - end - - ## - # Loads options from .rdoc_options if the file exists, otherwise creates a - # new RDoc::Options instance. - - def self.load_options - options_file = File.expand_path '.rdoc_options' - return RDoc::Options.new unless File.exist? options_file - - RDoc.load_yaml - - begin - options = YAML.safe_load File.read('.rdoc_options'), permitted_classes: [RDoc::Options, Symbol] - rescue Psych::SyntaxError - raise RDoc::Error, "#{options_file} is not a valid rdoc options file" - end - - return RDoc::Options.new unless options # Allow empty file. - - raise RDoc::Error, "#{options_file} is not a valid rdoc options file" unless - RDoc::Options === options or Hash === options - - if Hash === options - # Override the default values with the contents of YAML file. - options = RDoc::Options.new options - end - - options - end - -end diff --git a/lib/rdoc/parser.rb b/lib/rdoc/parser.rb deleted file mode 100644 index 76801ba377..0000000000 --- a/lib/rdoc/parser.rb +++ /dev/null @@ -1,297 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true - -## -# A parser is simple a class that subclasses RDoc::Parser and implements #scan -# to fill in an RDoc::TopLevel with parsed data. -# -# The initialize method takes an RDoc::TopLevel to fill with parsed content, -# the name of the file to be parsed, the content of the file, an RDoc::Options -# object and an RDoc::Stats object to inform the user of parsed items. The -# scan method is then called to parse the file and must return the -# RDoc::TopLevel object. By calling super these items will be set for you. -# -# In order to be used by RDoc the parser needs to register the file extensions -# it can parse. Use ::parse_files_matching to register extensions. -# -# require 'rdoc' -# -# class RDoc::Parser::Xyz < RDoc::Parser -# parse_files_matching /\.xyz$/ -# -# def initialize top_level, file_name, content, options, stats -# super -# -# # extra initialization if needed -# end -# -# def scan -# # parse file and fill in @top_level -# end -# end - -class RDoc::Parser - - @parsers = [] - - class << self - - ## - # An Array of arrays that maps file extension (or name) regular - # expressions to parser classes that will parse matching filenames. - # - # Use parse_files_matching to register a parser's file extensions. - - attr_reader :parsers - - end - - ## - # The name of the file being parsed - - attr_reader :file_name - - ## - # Alias an extension to another extension. After this call, files ending - # "new_ext" will be parsed using the same parser as "old_ext" - - def self.alias_extension(old_ext, new_ext) - old_ext = old_ext.sub(/^\.(.*)/, '\1') - new_ext = new_ext.sub(/^\.(.*)/, '\1') - - parser = can_parse_by_name "xxx.#{old_ext}" - return false unless parser - - RDoc::Parser.parsers.unshift [/\.#{new_ext}$/, parser] - - true - end - - ## - # Determines if the file is a "binary" file which basically means it has - # content that an RDoc parser shouldn't try to consume. - - def self.binary?(file) - return false if file =~ /\.(rdoc|txt)$/ - - s = File.read(file, 1024) or return false - - return true if s[0, 2] == Marshal.dump('')[0, 2] or s.index("\x00") - - mode = 'r:utf-8' # default source encoding has been changed to utf-8 - s.sub!(/\A#!.*\n/, '') # assume shebang line isn't longer than 1024. - encoding = s[/^\s*\#\s*(?:-\*-\s*)?(?:en)?coding:\s*([^\s;]+?)(?:-\*-|[\s;])/, 1] - mode = "rb:#{encoding}" if encoding - s = File.open(file, mode) {|f| f.gets(nil, 1024)} - - not s.valid_encoding? - end - - ## - # Checks if +file+ is a zip file in disguise. Signatures from - # http://www.garykessler.net/library/file_sigs.html - - def self.zip? file - zip_signature = File.read file, 4 - - zip_signature == "PK\x03\x04" or - zip_signature == "PK\x05\x06" or - zip_signature == "PK\x07\x08" - rescue - false - end - - ## - # Return a parser that can handle a particular extension - - def self.can_parse file_name - parser = can_parse_by_name file_name - - # HACK Selenium hides a jar file using a .txt extension - return if parser == RDoc::Parser::Simple and zip? file_name - - parser - end - - ## - # Returns a parser that can handle the extension for +file_name+. This does - # not depend upon the file being readable. - - def self.can_parse_by_name file_name - _, parser = RDoc::Parser.parsers.find { |regexp,| regexp =~ file_name } - - # The default parser must not parse binary files - ext_name = File.extname file_name - return parser if ext_name.empty? - - if parser == RDoc::Parser::Simple and ext_name !~ /txt|rdoc/ then - case mode = check_modeline(file_name) - when nil, 'rdoc' then # continue - else - RDoc::Parser.parsers.find { |_, p| return p if mode.casecmp?(p.name[/\w+\z/]) } - return nil - end - end - - parser - rescue Errno::EACCES - end - - ## - # Returns the file type from the modeline in +file_name+ - - def self.check_modeline file_name - line = File.open file_name do |io| - io.gets - end - - /-\*-\s*(.*?\S)\s*-\*-/ =~ line - - return nil unless type = $1 - - if /;/ =~ type then - return nil unless /(?:\s|\A)mode:\s*([^\s;]+)/i =~ type - type = $1 - end - - return nil if /coding:/i =~ type - - type.downcase - rescue ArgumentError - rescue Encoding::InvalidByteSequenceError # invalid byte sequence - - end - - ## - # Finds and instantiates the correct parser for the given +file_name+ and - # +content+. - - def self.for top_level, content, options, stats - file_name = top_level.absolute_name - return if binary? file_name - - parser = use_markup content - - unless parser then - parse_name = file_name - - # If no extension, look for shebang - if file_name !~ /\.\w+$/ && content =~ %r{\A#!(.+)} then - shebang = $1 - case shebang - when %r{env\s+ruby}, %r{/ruby} - parse_name = 'dummy.rb' - end - end - - parser = can_parse parse_name - end - - return unless parser - - content = remove_modeline content - - parser.new top_level, file_name, content, options, stats - rescue SystemCallError - nil - end - - ## - # Record which file types this parser can understand. - # - # It is ok to call this multiple times. - - def self.parse_files_matching(regexp) - RDoc::Parser.parsers.unshift [regexp, self] - end - - ## - # Removes an emacs-style modeline from the first line of the document - - def self.remove_modeline content - content.sub(/\A.*-\*-\s*(.*?\S)\s*-\*-.*\r?\n/, '') - end - - ## - # If there is a <tt>markup: parser_name</tt> comment at the front of the - # file, use it to determine the parser. For example: - # - # # markup: rdoc - # # Class comment can go here - # - # class C - # end - # - # The comment should appear as the first line of the +content+. - # - # If the content contains a shebang or editor modeline the comment may - # appear on the second or third line. - # - # Any comment style may be used to hide the markup comment. - - def self.use_markup content - markup = content.lines.first(3).grep(/markup:\s+(\w+)/) { $1 }.first - - return unless markup - - # TODO Ruby should be returned only when the filename is correct - return RDoc::Parser::Ruby if %w[tomdoc markdown].include? markup - - markup = Regexp.escape markup - - _, selected = RDoc::Parser.parsers.find do |_, parser| - /^#{markup}$/i =~ parser.name.sub(/.*:/, '') - end - - selected - end - - ## - # Creates a new Parser storing +top_level+, +file_name+, +content+, - # +options+ and +stats+ in instance variables. In +@preprocess+ an - # RDoc::Markup::PreProcess object is created which allows processing of - # directives. - - def initialize top_level, file_name, content, options, stats - @top_level = top_level - @top_level.parser = self.class - @store = @top_level.store - - @file_name = file_name - @content = content - @options = options - @stats = stats - - @preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - @preprocess.options = @options - end - - autoload :RubyTools, "#{__dir__}/parser/ruby_tools" - autoload :Text, "#{__dir__}/parser/text" - - ## - # Normalizes tabs in +body+ - - def handle_tab_width(body) - if /\t/ =~ body - tab_width = @options.tab_width - body.split(/\n/).map do |line| - 1 while line.gsub!(/\t+/) do - b, e = $~.offset(0) - ' ' * (tab_width * (e-b) - b % tab_width) - end - line - end.join "\n" - else - body - end - end -end - -# simple must come first in order to show up last in the parsers list -require_relative 'parser/simple' -require_relative 'parser/c' -require_relative 'parser/changelog' -require_relative 'parser/markdown' -require_relative 'parser/rd' -require_relative 'parser/ruby' diff --git a/lib/rdoc/parser/c.rb b/lib/rdoc/parser/c.rb deleted file mode 100644 index 4050d7aa49..0000000000 --- a/lib/rdoc/parser/c.rb +++ /dev/null @@ -1,1236 +0,0 @@ -# frozen_string_literal: true -require 'tsort' - -## -# RDoc::Parser::C attempts to parse C extension files. It looks for -# the standard patterns that you find in extensions: +rb_define_class+, -# +rb_define_method+ and so on. It tries to find the corresponding -# C source for the methods and extract comments, but if we fail -# we don't worry too much. -# -# The comments associated with a Ruby method are extracted from the C -# comment block associated with the routine that _implements_ that -# method, that is to say the method whose name is given in the -# +rb_define_method+ call. For example, you might write: -# -# /* -# * Returns a new array that is a one-dimensional flattening of this -# * array (recursively). That is, for every element that is an array, -# * extract its elements into the new array. -# * -# * s = [ 1, 2, 3 ] #=> [1, 2, 3] -# * t = [ 4, 5, 6, [7, 8] ] #=> [4, 5, 6, [7, 8]] -# * a = [ s, t, 9, 10 ] #=> [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] -# * a.flatten #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -# */ -# static VALUE -# rb_ary_flatten(VALUE ary) -# { -# ary = rb_obj_dup(ary); -# rb_ary_flatten_bang(ary); -# return ary; -# } -# -# ... -# -# void -# Init_Array(void) -# { -# ... -# rb_define_method(rb_cArray, "flatten", rb_ary_flatten, 0); -# -# Here RDoc will determine from the +rb_define_method+ line that there's a -# method called "flatten" in class Array, and will look for the implementation -# in the method +rb_ary_flatten+. It will then use the comment from that -# method in the HTML output. This method must be in the same source file -# as the +rb_define_method+. -# -# The comment blocks may include special directives: -# -# [Document-class: +name+] -# Documentation for the named class. -# -# [Document-module: +name+] -# Documentation for the named module. -# -# [Document-const: +name+] -# Documentation for the named +rb_define_const+. -# -# Constant values can be supplied on the first line of the comment like so: -# -# /* 300: The highest possible score in bowling */ -# rb_define_const(cFoo, "PERFECT", INT2FIX(300)); -# -# The value can contain internal colons so long as they are escaped with a \ -# -# [Document-global: +name+] -# Documentation for the named +rb_define_global_const+ -# -# [Document-variable: +name+] -# Documentation for the named +rb_define_variable+ -# -# [Document-method\: +method_name+] -# Documentation for the named method. Use this when the method name is -# unambiguous. -# -# [Document-method\: <tt>ClassName::method_name</tt>] -# Documentation for a singleton method in the given class. Use this when -# the method name alone is ambiguous. -# -# [Document-method\: <tt>ClassName#method_name</tt>] -# Documentation for a instance method in the given class. Use this when the -# method name alone is ambiguous. -# -# [Document-attr: +name+] -# Documentation for the named attribute. -# -# [call-seq: <i>text up to an empty line</i>] -# Because C source doesn't give descriptive names to Ruby-level parameters, -# you need to document the calling sequence explicitly -# -# In addition, RDoc assumes by default that the C method implementing a -# Ruby function is in the same source file as the rb_define_method call. -# If this isn't the case, add the comment: -# -# rb_define_method(....); // in filename -# -# As an example, we might have an extension that defines multiple classes -# in its Init_xxx method. We could document them using -# -# /* -# * Document-class: MyClass -# * -# * Encapsulate the writing and reading of the configuration -# * file. ... -# */ -# -# /* -# * Document-method: read_value -# * -# * call-seq: -# * cfg.read_value(key) -> value -# * cfg.read_value(key} { |key| } -> value -# * -# * Return the value corresponding to +key+ from the configuration. -# * In the second form, if the key isn't found, invoke the -# * block and return its value. -# */ - -class RDoc::Parser::C < RDoc::Parser - - parse_files_matching(/\.(?:([CcHh])\1?|c([+xp])\2|y)\z/) - - include RDoc::Text - - # :stopdoc: - BOOL_ARG_PATTERN = /\s*+\b([01]|Q?(?:true|false)|TRUE|FALSE)\b\s*/ - TRUE_VALUES = ['1', 'TRUE', 'true', 'Qtrue'].freeze - # :startdoc: - - ## - # Maps C variable names to names of Ruby classes or modules - - attr_reader :classes - - ## - # C file the parser is parsing - - attr_accessor :content - - ## - # Dependencies from a missing enclosing class to the classes in - # missing_dependencies that depend upon it. - - attr_reader :enclosure_dependencies - - ## - # Maps C variable names to names of Ruby classes (and singleton classes) - - attr_reader :known_classes - - ## - # Classes found while parsing the C file that were not yet registered due to - # a missing enclosing class. These are processed by do_missing - - attr_reader :missing_dependencies - - ## - # Maps C variable names to names of Ruby singleton classes - - attr_reader :singleton_classes - - ## - # The TopLevel items in the parsed file belong to - - attr_reader :top_level - - ## - # Prepares for parsing a C file. See RDoc::Parser#initialize for details on - # the arguments. - - def initialize top_level, file_name, content, options, stats - super - - @known_classes = RDoc::KNOWN_CLASSES.dup - @content = handle_tab_width handle_ifdefs_in @content - @file_dir = File.dirname @file_name - - @classes = load_variable_map :c_class_variables - @singleton_classes = load_variable_map :c_singleton_class_variables - - @markup = @options.markup - - # class_variable => { function => [method, ...] } - @methods = Hash.new { |h, f| h[f] = Hash.new { |i, m| i[m] = [] } } - - # missing variable => [handle_class_module arguments] - @missing_dependencies = {} - - # missing enclosure variable => [dependent handle_class_module arguments] - @enclosure_dependencies = Hash.new { |h, k| h[k] = [] } - @enclosure_dependencies.instance_variable_set :@missing_dependencies, - @missing_dependencies - - @enclosure_dependencies.extend TSort - - def @enclosure_dependencies.tsort_each_node &block - each_key(&block) - rescue TSort::Cyclic => e - cycle_vars = e.message.scan(/"(.*?)"/).flatten - - cycle = cycle_vars.sort.map do |var_name| - delete var_name - - var_name, type, mod_name, = @missing_dependencies[var_name] - - "#{type} #{mod_name} (#{var_name})" - end.join ', ' - - warn "Unable to create #{cycle} due to a cyclic class or module creation" - - retry - end - - def @enclosure_dependencies.tsort_each_child node, &block - fetch(node, []).each(&block) - end - end - - ## - # Scans #content for rb_define_alias - - def do_aliases - @content.scan(/rb_define_alias\s*\( - \s*(\w+), - \s*"(.+?)", - \s*"(.+?)" - \s*\)/xm) do |var_name, new_name, old_name| - class_name = @known_classes[var_name] - - unless class_name then - @options.warn "Enclosing class or module %p for alias %s %s is not known" % [ - var_name, new_name, old_name] - next - end - - class_obj = find_class var_name, class_name - comment = find_alias_comment var_name, new_name, old_name - comment.normalize - if comment.to_s.empty? and existing_method = class_obj.method_list.find { |m| m.name == old_name} - comment = existing_method.comment - end - add_alias(var_name, class_obj, old_name, new_name, comment) - end - end - - ## - # Add alias, either from a direct alias definition, or from two - # method that reference the same function. - - def add_alias(var_name, class_obj, old_name, new_name, comment) - al = RDoc::Alias.new '', old_name, new_name, '' - al.singleton = @singleton_classes.key? var_name - al.comment = comment - al.record_location @top_level - class_obj.add_alias al - @stats.add_alias al - al - end - - ## - # Scans #content for rb_attr and rb_define_attr - - def do_attrs - @content.scan(/rb_attr\s*\( - \s*(\w+), - \s*([\w"()]+), - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}, - \s*\w+\);/xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end - - @content.scan(%r%rb_define_attr\( - \s*([\w\.]+), - \s*"([^"]+)", - #{BOOL_ARG_PATTERN}, - #{BOOL_ARG_PATTERN}\); - %xmo) do |var_name, attr_name, read, write| - handle_attr var_name, attr_name, read, write - end - end - - ## - # Scans #content for boot_defclass - - def do_boot_defclass - @content.scan(/(\w+)\s*=\s*boot_defclass\s*\(\s*"(\w+?)",\s*(\w+?)\s*\)/) do - |var_name, class_name, parent| - parent = nil if parent == "0" - handle_class_module(var_name, :class, class_name, parent, nil) - end - end - - ## - # Scans #content for rb_define_class, boot_defclass, rb_define_class_under - # and rb_singleton_class - - def do_classes_and_modules - do_boot_defclass if @file_name == "class.c" - - @content.scan( - %r( - (?<open>\s*\(\s*) {0} - (?<close>\s*\)\s*) {0} - (?<name>\s*"(?<class_name>\w+)") {0} - (?<parent>\s*(?: - (?<parent_name>[\w\*\s\(\)\.\->]+) | - rb_path2class\s*\(\s*"(?<path>[\w:]+)"\s*\) - )) {0} - (?<under>\w+) {0} - - (?<var_name>[\w\.]+)\s* = - \s*rb_(?: - define_(?: - class(?: # rb_define_class(name, parent_name) - \(\s* - \g<name>, - \g<parent> - \s*\) - | - _under\g<open> # rb_define_class_under(under, name, parent_name...) - \g<under>, - \g<name>, - \g<parent> - \g<close> - ) - | - (?<module>) - module(?: # rb_define_module(name) - \g<open> - \g<name> - \g<close> - | - _under\g<open> # rb_define_module_under(under, name) - \g<under>, - \g<name> - \g<close> - ) - ) - | - (?<attributes>(?:\s*"\w+",)*\s*NULL\s*) {0} - struct_define(?: - \g<open> # rb_struct_define(name, ...) - \g<name>, - | - _under\g<open> # rb_struct_define_under(under, name, ...) - \g<under>, - \g<name>, - | - _without_accessor(?: - \g<open> # rb_struct_define_without_accessor(name, parent_name, ...) - | - _under\g<open> # rb_struct_define_without_accessor_under(under, name, parent_name, ...) - \g<under>, - ) - \g<name>, - \g<parent>, - \s*\w+, # Allocation function - ) - \g<attributes> - \g<close> - | - singleton_class\g<open> # rb_singleton_class(target_class_name) - (?<target_class_name>\w+) - \g<close> - ) - )mx - ) do - if target_class_name = $~[:target_class_name] - # rb_singleton_class(target_class_name) - handle_singleton $~[:var_name], target_class_name - next - end - - var_name = $~[:var_name] - type = $~[:module] ? :module : :class - class_name = $~[:class_name] - parent_name = $~[:parent_name] || $~[:path] - under = $~[:under] - attributes = $~[:attributes] - - handle_class_module(var_name, type, class_name, parent_name, under) - if attributes and !parent_name # rb_struct_define *not* without_accessor - true_flag = 'Qtrue' - attributes.scan(/"\K\w+(?=")/) do |attr_name| - handle_attr var_name, attr_name, true_flag, true_flag - end - end - end - end - - ## - # Scans #content for rb_define_variable, rb_define_readonly_variable, - # rb_define_const and rb_define_global_const - - def do_constants - @content.scan(%r%\Wrb_define_ - ( variable | - readonly_variable | - const | - global_const ) - \s*\( - (?:\s*(\w+),)? - \s*"(\w+)", - \s*(.*?)\s*\)\s*; - %xm) do |type, var_name, const_name, definition| - var_name = "rb_cObject" if !var_name or var_name == "rb_mKernel" - handle_constants type, var_name, const_name, definition - end - - @content.scan(%r% - \Wrb_curses_define_const - \s*\( - \s* - (\w+) - \s* - \) - \s*;%xm) do |consts| - const = consts.first - - handle_constants 'const', 'mCurses', const, "UINT2NUM(#{const})" - end - - @content.scan(%r% - \Wrb_file_const - \s*\( - \s* - "([^"]+)", - \s* - (.*?) - \s* - \) - \s*;%xm) do |name, value| - handle_constants 'const', 'rb_mFConst', name, value - end - end - - - ## - # Scans #content for rb_include_module - - def do_includes - @content.scan(/rb_include_module\s*\(\s*(\w+?),\s*(\w+?)\s*\)/) do |c, m| - next unless cls = @classes[c] - m = @known_classes[m] || m - - comment = new_comment '', @top_level, :c - incl = cls.add_include RDoc::Include.new(m, comment) - incl.record_location @top_level - end - end - - ## - # Scans #content for rb_define_method, rb_define_singleton_method, - # rb_define_module_function, rb_define_private_method, - # rb_define_global_function and define_filetest_function - - def do_methods - @content.scan(%r%rb_define_ - ( - singleton_method | - method | - module_function | - private_method - ) - \s*\(\s*([\w\.]+), - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\(|\(METHOD\))?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.(?:cpp|c|y)))? - %xm) do |type, var_name, meth_name, function, param_count, source_file| - - # Ignore top-object and weird struct.c dynamic stuff - next if var_name == "ruby_top_self" - next if var_name == "nstr" - - var_name = "rb_cObject" if var_name == "rb_mKernel" - handle_method(type, var_name, meth_name, function, param_count, - source_file) - end - - @content.scan(%r%rb_define_global_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\) - (?:;\s*/[*/]\s+in\s+(\w+?\.[cy]))? - %xm) do |meth_name, function, param_count, source_file| - handle_method("method", "rb_mKernel", meth_name, function, param_count, - source_file) - end - - @content.scan(/define_filetest_function\s*\( - \s*"([^"]+)", - \s*(?:RUBY_METHOD_FUNC\(|VALUEFUNC\()?(\w+)\)?, - \s*(-?\w+)\s*\)/xm) do |meth_name, function, param_count| - - handle_method("method", "rb_mFileTest", meth_name, function, param_count) - handle_method("singleton_method", "rb_cFile", meth_name, function, - param_count) - end - end - - ## - # Creates classes and module that were missing were defined due to the file - # order being different than the declaration order. - - def do_missing - return if @missing_dependencies.empty? - - @enclosure_dependencies.tsort.each do |in_module| - arguments = @missing_dependencies.delete in_module - - next unless arguments # dependency on existing class - - handle_class_module(*arguments) - end - end - - ## - # Finds the comment for an alias on +class_name+ from +new_name+ to - # +old_name+ - - def find_alias_comment class_name, new_name, old_name - content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_alias\(\s*#{Regexp.escape class_name}\s*, - \s*"#{Regexp.escape new_name}"\s*, - \s*"#{Regexp.escape old_name}"\s*\);%xm - - new_comment($1 || '', @top_level, :c) - end - - ## - # Finds a comment for rb_define_attr, rb_attr or Document-attr. - # - # +var_name+ is the C class variable the attribute is defined on. - # +attr_name+ is the attribute's name. - # - # +read+ and +write+ are the read/write flags ('1' or '0'). Either both or - # neither must be provided. - - def find_attr_comment var_name, attr_name, read = nil, write = nil - attr_name = Regexp.escape attr_name - - rw = if read and write then - /\s*#{read}\s*,\s*#{write}\s*/xm - else - /.*?/m - end - - comment = if @content =~ %r%((?>/\*.*?\*/\s+)) - rb_define_attr\((?:\s*#{var_name},)?\s* - "#{attr_name}"\s*, - #{rw}\)\s*;%xm then - $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - rb_attr\(\s*#{var_name}\s*, - \s*#{attr_name}\s*, - #{rw},.*?\)\s*;%xm then - $1 - elsif @content =~ %r%(/\*.*?(?:\s*\*\s*)?) - Document-attr:\s#{attr_name}\s*?\n - ((?>(.|\n)*?\*/))%x then - "#{$1}\n#{$2}" - else - '' - end - - new_comment comment, @top_level, :c - end - - ## - # Generate a Ruby-method table - - def gen_body_table file_content - table = {} - file_content.scan(%r{ - ((?>/\*.*?\*/\s*)?) - ((?:\w+\s+){0,2} VALUE\s+(\w+) - \s*(?:\([^\)]*\))(?:[^\);]|$)) - | ((?>/\*.*?\*/\s*))^\s*(\#\s*define\s+(\w+)\s+(\w+)) - | ^\s*\#\s*define\s+(\w+)\s+(\w+) - }xm) do - case - when name = $3 - table[name] = [:func_def, $1, $2, $~.offset(2)] if !(t = table[name]) || t[0] != :func_def - when name = $6 - table[name] = [:macro_def, $4, $5, $~.offset(5), $7] if !(t = table[name]) || t[0] == :macro_alias - when name = $8 - table[name] ||= [:macro_alias, $9] - end - end - table - end - - ## - # Find the C code corresponding to a Ruby method - - def find_body class_name, meth_name, meth_obj, file_content, quiet = false - if file_content - @body_table ||= {} - @body_table[file_content] ||= gen_body_table file_content - type, *args = @body_table[file_content][meth_name] - end - - case type - when :func_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] - - comment.remove_private if comment - - # try to find the whole body - body = $& if /#{Regexp.escape body}[^(]*?\{.*?^\}/m =~ file_content - - # The comment block may have been overridden with a 'Document-method' - # block. This happens in the interpreter when multiple methods are - # vectored through to the same C method but those methods are logically - # distinct (for example Kernel.hash and Kernel.object_id share the same - # implementation - - override_comment = find_override_comment class_name, meth_obj - comment = override_comment if override_comment - - comment.normalize - find_modifiers comment, meth_obj if comment - - #meth_obj.params = params - meth_obj.start_collecting_tokens - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 - - body - when :macro_def - comment = new_comment args[0], @top_level, :c - body = args[1] - offset, = args[2] - - find_body class_name, args[3], meth_obj, file_content, true - - comment.normalize - find_modifiers comment, meth_obj - - meth_obj.start_collecting_tokens - tk = { :line_no => 1, :char_no => 1, :text => body } - meth_obj.add_token tk - meth_obj.comment = comment - meth_obj.line = file_content[0, offset].count("\n") + 1 - - body - when :macro_alias - # with no comment we hope the aliased definition has it and use it's - # definition - - body = find_body(class_name, args[0], meth_obj, file_content, true) - - return body if body - - @options.warn "No definition for #{meth_name}" - false - else # No body, but might still have an override comment - comment = find_override_comment class_name, meth_obj - - if comment then - comment.normalize - find_modifiers comment, meth_obj - meth_obj.comment = comment - - '' - else - @options.warn "No definition for #{meth_name}" - false - end - end - end - - ## - # Finds a RDoc::NormalClass or RDoc::NormalModule for +raw_name+ - - def find_class(raw_name, name, base_name = nil) - unless @classes[raw_name] - if raw_name =~ /^rb_m/ - container = @top_level.add_module RDoc::NormalModule, name - else - container = @top_level.add_class RDoc::NormalClass, name - end - container.name = base_name if base_name - - container.record_location @top_level - @classes[raw_name] = container - end - @classes[raw_name] - end - - ## - # Look for class or module documentation above Init_+class_name+(void), - # in a Document-class +class_name+ (or module) comment or above an - # rb_define_class (or module). If a comment is supplied above a matching - # Init_ and a rb_define_class the Init_ comment is used. - # - # /* - # * This is a comment for Foo - # */ - # Init_Foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * Document-class: Foo - # * This is a comment for Foo - # */ - # Init_foo(void) { - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - # } - # - # /* - # * This is a comment for Foo - # */ - # VALUE cFoo = rb_define_class("Foo", rb_cObject); - - def find_class_comment class_name, class_mod - comment = nil - - if @content =~ %r% - ((?>/\*.*?\*/\s+)) - (static\s+)? - void\s+ - Init(?:VM)?_(?i:#{class_name})\s*(?:_\(\s*)?\(\s*(?:void\s*)?\)%xm then - comment = $1.sub(%r%Document-(?:class|module):\s+#{class_name}%, '') - elsif @content =~ %r%Document-(?:class|module):\s+#{class_name}\s*? - (?:<\s+[:,\w]+)?\n((?>.*?\*/))%xm then - comment = "/*\n#{$1}" - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\.\s]+\s* = \s+)?rb_define_(class|module)[\t (]*?"(#{class_name})"%xm then - comment = $1 - elsif @content =~ %r%((?>/\*.*?\*/\s+)) - ([\w\. \t]+ = \s+)?rb_define_(class|module)_under[\t\w, (]*?"(#{class_name.split('::').last})"%xm then - comment = $1 - else - comment = '' - end - - comment = new_comment comment, @top_level, :c - comment.normalize - - look_for_directives_in class_mod, comment - - class_mod.add_comment comment, @top_level - end - - ## - # Generate a const table - - def gen_const_table file_content - table = {} - @content.scan(%r{ - (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_define_(?<type>\w+)\(\s*(?:\w+),\s* - "(?<name>\w+)"\s*, - .*?\)\s*; - | (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_file_(?<type>const)\(\s* - "(?<name>\w+)"\s*, - .*?\)\s*; - | (?<doc>(?>^\s*/\*.*?\*/\s+)) - rb_curses_define_(?<type>const)\(\s* - (?<name>\w+) - \s*\)\s*; - | Document-(?:const|global|variable):\s - (?<name>(?:\w+::)*\w+) - \s*?\n(?<doc>(?>.*?\*/)) - }mxi) do - name, doc, type = $~.values_at(:name, :doc, :type) - if type - table[[type, name]] = doc - else - table[name] = "/*\n" + doc - end - end - table - end - - ## - # Finds a comment matching +type+ and +const_name+ either above the - # comment or in the matching Document- section. - - def find_const_comment(type, const_name, class_name = nil) - @const_table ||= {} - @const_table[@content] ||= gen_const_table @content - table = @const_table[@content] - - comment = - table[[type, const_name]] || - (class_name && table[class_name + "::" + const_name]) || - table[const_name] || - '' - - new_comment comment, @top_level, :c - end - - ## - # Handles modifiers in +comment+ and updates +meth_obj+ as appropriate. - - def find_modifiers comment, meth_obj - comment.normalize - comment.extract_call_seq meth_obj - - look_for_directives_in meth_obj, comment - end - - ## - # Finds a <tt>Document-method</tt> override for +meth_obj+ on +class_name+ - - def find_override_comment class_name, meth_obj - name = Regexp.escape meth_obj.name - prefix = Regexp.escape meth_obj.name_prefix - - comment = if @content =~ %r%Document-method: - \s+#{class_name}#{prefix}#{name} - \s*?\n((?>.*?\*/))%xm then - "/*#{$1}" - elsif @content =~ %r%Document-method: - \s#{name}\s*?\n((?>.*?\*/))%xm then - "/*#{$1}" - end - - return unless comment - - new_comment comment, @top_level, :c - end - - ## - # Creates a new RDoc::Attr +attr_name+ on class +var_name+ that is either - # +read+, +write+ or both - - def handle_attr(var_name, attr_name, read, write) - rw = '' - rw += 'R' if TRUE_VALUES.include?(read) - rw += 'W' if TRUE_VALUES.include?(write) - - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class var_name, class_name - - return unless class_obj - - comment = find_attr_comment var_name, attr_name - comment.normalize - - name = attr_name.gsub(/rb_intern(?:_const)?\("([^"]+)"\)/, '\1') - - attr = RDoc::Attr.new '', name, rw, comment - - attr.record_location @top_level - class_obj.add_attribute attr - @stats.add_attribute attr - end - - ## - # Creates a new RDoc::NormalClass or RDoc::NormalModule based on +type+ - # named +class_name+ in +parent+ which was assigned to the C +var_name+. - - def handle_class_module(var_name, type, class_name, parent, in_module) - parent_name = @known_classes[parent] || parent - - if in_module then - enclosure = @classes[in_module] || @store.find_c_enclosure(in_module) - - if enclosure.nil? and enclosure = @known_classes[in_module] then - enc_type = /^rb_m/ =~ in_module ? :module : :class - handle_class_module in_module, enc_type, enclosure, nil, nil - enclosure = @classes[in_module] - end - - unless enclosure then - @enclosure_dependencies[in_module] << var_name - @missing_dependencies[var_name] = - [var_name, type, class_name, parent, in_module] - - return - end - else - enclosure = @top_level - end - - if type == :class then - full_name = if RDoc::ClassModule === enclosure then - enclosure.full_name + "::#{class_name}" - else - class_name - end - - if @content =~ %r%Document-class:\s+#{full_name}\s*<\s+([:,\w]+)% then - parent_name = $1 - end - - cm = enclosure.add_class RDoc::NormalClass, class_name, parent_name - else - cm = enclosure.add_module RDoc::NormalModule, class_name - end - - cm.record_location enclosure.top_level - - find_class_comment cm.full_name, cm - - case cm - when RDoc::NormalClass - @stats.add_class cm - when RDoc::NormalModule - @stats.add_module cm - end - - @classes[var_name] = cm - @known_classes[var_name] = cm.full_name - @store.add_c_enclosure var_name, cm - end - - ## - # Adds constants. By providing some_value: at the start of the comment you - # can override the C value of the comment to give a friendly definition. - # - # /* 300: The perfect score in bowling */ - # rb_define_const(cFoo, "PERFECT", INT2FIX(300)); - # - # Will override <tt>INT2FIX(300)</tt> with the value +300+ in the output - # RDoc. Values may include quotes and escaped colons (\:). - - def handle_constants(type, var_name, const_name, definition) - class_name = @known_classes[var_name] - - return unless class_name - - class_obj = find_class var_name, class_name, class_name[/::\K[^:]+\z/] - - unless class_obj then - @options.warn 'Enclosing class or module %p is not known' % [const_name] - return - end - - comment = find_const_comment type, const_name, class_name - comment.normalize - - # In the case of rb_define_const, the definition and comment are in - # "/* definition: comment */" form. The literal ':' and '\' characters - # can be escaped with a backslash. - if type.downcase == 'const' then - if /\A(.+?)?:(?!\S)/ =~ comment.text - new_definition, new_comment = $1, $' - - if !new_definition # Default to literal C definition - new_definition = definition - else - new_definition = new_definition.gsub(/\\([\\:])/, '\1') - end - - new_definition.sub!(/\A(\s+)/, '') - - new_comment = "#{$1}#{new_comment.lstrip}" - - new_comment = self.new_comment(new_comment, @top_level, :c) - - con = RDoc::Constant.new const_name, new_definition, new_comment - else - con = RDoc::Constant.new const_name, definition, comment - end - else - con = RDoc::Constant.new const_name, definition, comment - end - - con.record_location @top_level - @stats.add_constant con - class_obj.add_constant con - end - - ## - # Removes #ifdefs that would otherwise confuse us - - def handle_ifdefs_in(body) - body.gsub(/^#ifdef HAVE_PROTOTYPES.*?#else.*?\n(.*?)#endif.*?\n/m, '\1') - end - - ## - # Adds an RDoc::AnyMethod +meth_name+ defined on a class or module assigned - # to +var_name+. +type+ is the type of method definition function used. - # +singleton_method+ and +module_function+ create a singleton method. - - def handle_method(type, var_name, meth_name, function, param_count, - source_file = nil) - class_name = @known_classes[var_name] - singleton = @singleton_classes.key? var_name - - @methods[var_name][function] << meth_name - - return unless class_name - - class_obj = find_class var_name, class_name - - if existing_method = class_obj.method_list.find { |m| m.c_function == function } - add_alias(var_name, class_obj, existing_method.name, meth_name, existing_method.comment) - end - - if class_obj then - if meth_name == 'initialize' then - meth_name = 'new' - singleton = true - type = 'method' # force public - end - - meth_obj = RDoc::AnyMethod.new '', meth_name - meth_obj.c_function = function - meth_obj.singleton = - singleton || %w[singleton_method module_function].include?(type) - - p_count = Integer(param_count) rescue -1 - - if source_file then - file_name = File.join @file_dir, source_file - - if File.exist? file_name then - file_content = File.read file_name - else - @options.warn "unknown source #{source_file} for #{meth_name} in #{@file_name}" - end - else - file_content = @content - end - - body = find_body class_name, function, meth_obj, file_content - - if body and meth_obj.document_self then - meth_obj.params = if p_count < -1 then # -2 is Array - '(*args)' - elsif p_count == -1 then # argc, argv - rb_scan_args body - else - args = (1..p_count).map { |i| "p#{i}" } - "(#{args.join ', '})" - end - - - meth_obj.record_location @top_level - - if meth_obj.section_title - class_obj.temporary_section = class_obj.add_section(meth_obj.section_title) - end - class_obj.add_method meth_obj - - @stats.add_method meth_obj - meth_obj.visibility = :private if 'private_method' == type - end - end - end - - ## - # Registers a singleton class +sclass_var+ as a singleton of +class_var+ - - def handle_singleton sclass_var, class_var - class_name = @known_classes[class_var] - - @known_classes[sclass_var] = class_name - @singleton_classes[sclass_var] = class_name - end - - ## - # Loads the variable map with the given +name+ from the RDoc::Store, if - # present. - - def load_variable_map map_name - return {} unless files = @store.cache[map_name] - return {} unless name_map = files[@file_name] - - class_map = {} - - name_map.each do |variable, name| - next unless mod = @store.find_class_or_module(name) - - class_map[variable] = if map_name == :c_class_variables then - mod - else - name - end - @known_classes[variable] = name - end - - class_map - end - - ## - # Look for directives in a normal comment block: - # - # /* - # * :title: My Awesome Project - # */ - # - # This method modifies the +comment+ - - def look_for_directives_in context, comment - @preprocess.handle comment, context do |directive, param| - case directive - when 'main' then - @options.main_page = param - '' - when 'title' then - @options.default_title = param if @options.respond_to? :default_title= - '' - end - end - - comment - end - - ## - # Extracts parameters from the +method_body+ and returns a method - # parameter string. Follows 1.9.3dev's scan-arg-spec, see README.EXT - - def rb_scan_args method_body - method_body =~ /rb_scan_args\((.*?)\)/m - return '(*args)' unless $1 - - $1.split(/,/)[2] =~ /"(.*?)"/ # format argument - format = $1.split(//) - - lead = opt = trail = 0 - - if format.first =~ /\d/ then - lead = $&.to_i - format.shift - if format.first =~ /\d/ then - opt = $&.to_i - format.shift - if format.first =~ /\d/ then - trail = $&.to_i - format.shift - block_arg = true - end - end - end - - if format.first == '*' and not block_arg then - var = true - format.shift - if format.first =~ /\d/ then - trail = $&.to_i - format.shift - end - end - - if format.first == ':' then - hash = true - format.shift - end - - if format.first == '&' then - block = true - format.shift - end - - # if the format string is not empty there's a bug in the C code, ignore it - - args = [] - position = 1 - - (1...(position + lead)).each do |index| - args << "p#{index}" - end - - position += lead - - (position...(position + opt)).each do |index| - args << "p#{index} = v#{index}" - end - - position += opt - - if var then - args << '*args' - position += 1 - end - - (position...(position + trail)).each do |index| - args << "p#{index}" - end - - position += trail - - if hash then - args << "p#{position} = {}" - end - - args << '&block' if block - - "(#{args.join ', '})" - end - - ## - # Removes lines that are commented out that might otherwise get picked up - # when scanning for classes and methods - - def remove_commented_out_lines - @content = @content.gsub(%r%//.*rb_define_%, '//') - end - - ## - # Extracts the classes, modules, methods, attributes, constants and aliases - # from a C file and returns an RDoc::TopLevel for this file - - def scan - remove_commented_out_lines - - do_classes_and_modules - do_missing - - do_constants - do_methods - do_includes - do_aliases - do_attrs - - @store.add_c_variables self - - @top_level - end - - ## - # Creates a RDoc::Comment instance. - - def new_comment text = nil, location = nil, language = nil - RDoc::Comment.new(text, location, language).tap do |comment| - comment.format = @markup - end - end -end diff --git a/lib/rdoc/parser/changelog.rb b/lib/rdoc/parser/changelog.rb deleted file mode 100644 index 12a50f8d0e..0000000000 --- a/lib/rdoc/parser/changelog.rb +++ /dev/null @@ -1,349 +0,0 @@ -# frozen_string_literal: true - -## -# A ChangeLog file parser. -# -# This parser converts a ChangeLog into an RDoc::Markup::Document. When -# viewed as HTML a ChangeLog page will have an entry for each day's entries in -# the sidebar table of contents. -# -# This parser is meant to parse the MRI ChangeLog, but can be used to parse any -# {GNU style Change -# Log}[http://www.gnu.org/prep/standards/html_node/Style-of-Change-Logs.html]. - -class RDoc::Parser::ChangeLog < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/(\/|\\|\A)ChangeLog[^\/\\]*\z/) - - ## - # Attaches the +continuation+ of the previous line to the +entry_body+. - # - # Continued function listings are joined together as a single entry. - # Continued descriptions are joined to make a single paragraph. - - def continue_entry_body entry_body, continuation - return unless last = entry_body.last - - if last =~ /\)\s*\z/ and continuation =~ /\A\(/ then - last.sub!(/\)\s*\z/, ',') - continuation = continuation.sub(/\A\(/, '') - end - - if last =~ /\s\z/ then - last << continuation - else - last << ' ' + continuation - end - end - - ## - # Creates an RDoc::Markup::Document given the +groups+ of ChangeLog entries. - - def create_document groups - doc = RDoc::Markup::Document.new - doc.omit_headings_below = 2 - doc.file = @top_level - - doc << RDoc::Markup::Heading.new(1, File.basename(@file_name)) - doc << RDoc::Markup::BlankLine.new - - groups.sort_by do |day,| day end.reverse_each do |day, entries| - doc << RDoc::Markup::Heading.new(2, day.dup) - doc << RDoc::Markup::BlankLine.new - - doc.concat create_entries entries - end - - doc - end - - ## - # Returns a list of ChangeLog entries an RDoc::Markup nodes for the given - # +entries+. - - def create_entries entries - out = [] - - entries.each do |entry, items| - out << RDoc::Markup::Heading.new(3, entry) - out << RDoc::Markup::BlankLine.new - - out << create_items(items) - end - - out - end - - ## - # Returns an RDoc::Markup::List containing the given +items+ in the - # ChangeLog - - def create_items items - list = RDoc::Markup::List.new :NOTE - - items.each do |item| - item =~ /\A(.*?(?:\([^)]+\))?):\s*/ - - title = $1 - body = $' - - paragraph = RDoc::Markup::Paragraph.new body - list_item = RDoc::Markup::ListItem.new title, paragraph - list << list_item - end - - list - end - - ## - # Groups +entries+ by date. - - def group_entries entries - @time_cache ||= {} - entries.group_by do |title, _| - begin - time = @time_cache[title] - (time || parse_date(title)).strftime '%Y-%m-%d' - rescue NoMethodError, ArgumentError - time, = title.split ' ', 2 - parse_date(time).strftime '%Y-%m-%d' - end - end - end - - ## - # Parse date in ISO-8601, RFC-2822, or default of Git - - def parse_date(date) - case date - when /\A\s*(\d+)-(\d+)-(\d+)(?:[ T](\d+):(\d+):(\d+) *([-+]\d\d):?(\d\d))?\b/ - Time.new($1, $2, $3, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3}, +(\d+) (\w{3}) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $2, $1, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+) (\d+):(\d+):(\d+) *(?:([-+]\d\d):?(\d\d))\b/ - Time.new($3, $1, $2, $4, $5, $6, ("#{$7}:#{$8}" if $7)) - when /\A\s*\w{3} (\w{3}) +(\d+) (\d+):(\d+):(\d+) (\d+)\b/ - Time.new($6, $1, $2, $3, $4, $5) - else - raise ArgumentError, "bad date: #{date}" - end - end - - ## - # Parses the entries in the ChangeLog. - # - # Returns an Array of each ChangeLog entry in order of parsing. - # - # A ChangeLog entry is an Array containing the ChangeLog title (date and - # committer) and an Array of ChangeLog items (file and function changed with - # description). - # - # An example result would be: - # - # [ 'Tue Dec 4 08:33:46 2012 Eric Hodel <drbrain@segment7.net>', - # [ 'README.EXT: Converted to RDoc format', - # 'README.EXT.ja: ditto']] - - def parse_entries - @time_cache ||= {} - - if /\A((?:.*\n){,3})commit\s/ =~ @content - class << self; prepend Git; end - parse_info($1) - return parse_entries - end - - entries = [] - entry_name = nil - entry_body = [] - - @content.each_line do |line| - case line - when /^\s*$/ then - next - when /^\w.*/ then - entries << [entry_name, entry_body] if entry_name - - entry_name = $& - - begin - time = parse_date entry_name - @time_cache[entry_name] = time - rescue ArgumentError - entry_name = nil - end - - entry_body = [] - when /^(\t| {8})?\*\s*(.*)/ then # "\t* file.c (func): ..." - entry_body << $2.dup - when /^(\t| {8})?\s*(\(.*)/ then # "\t(func): ..." - entry = $2 - - if entry_body.last =~ /:/ then - entry_body << entry.dup - else - continue_entry_body entry_body, entry - end - when /^(\t| {8})?\s*(.*)/ then - continue_entry_body entry_body, $2 - end - end - - entries << [entry_name, entry_body] if entry_name - - entries.reject! do |(entry, _)| - entry == nil - end - - entries - end - - ## - # Converts the ChangeLog into an RDoc::Markup::Document - - def scan - @time_cache = {} - - entries = parse_entries - grouped_entries = group_entries entries - - doc = create_document grouped_entries - - @top_level.comment = doc - - @top_level - end - - ## - # The extension for Git commit log - - module Git - ## - # Parses auxiliary info. Currently `base-url` to expand - # references is effective. - - def parse_info(info) - /^\s*base-url\s*=\s*(.*\S)/ =~ info - @base_url = $1 - end - - ## - # Parses the entries in the Git commit logs - - def parse_entries - entries = [] - - @content.scan(/^commit\s+(\h{20})\h*\n((?:.+\n)*)\n((?: {4}.*\n+)*)/) do - entry_name, header, entry_body = $1, $2, $3.gsub(/^ {4}/, '') - # header = header.scan(/^ *(\S+?): +(.*)/).to_h - # date = header["CommitDate"] || header["Date"] - date = header[/^ *(?:Author)?Date: +(.*)/, 1] - author = header[/^ *Author: +(.*)/, 1] - begin - time = parse_date(header[/^ *CommitDate: +(.*)/, 1] || date) - @time_cache[entry_name] = time - author.sub!(/\s*<(.*)>/, '') - email = $1 - entries << [entry_name, [author, email, date, entry_body]] - rescue ArgumentError - end - end - - entries - end - - ## - # Returns a list of ChangeLog entries as - # RDoc::Parser::ChangeLog::Git::LogEntry list for the given - # +entries+. - - def create_entries entries - # git log entries have no strictly itemized style like the old - # style, just assume Markdown. - entries.map do |commit, entry| - LogEntry.new(@base_url, commit, *entry) - end - end - - LogEntry = Struct.new(:base, :commit, :author, :email, :date, :contents) do - HEADING_LEVEL = 3 - - def initialize(base, commit, author, email, date, contents) - case contents - when String - contents = RDoc::Markdown.parse(contents).parts.each do |body| - case body - when RDoc::Markup::Heading - body.level += HEADING_LEVEL + 1 - end - end - case first = contents[0] - when RDoc::Markup::Paragraph - contents[0] = RDoc::Markup::Heading.new(HEADING_LEVEL + 1, first.text) - end - end - super - end - - def level - HEADING_LEVEL - end - - def aref - "label-#{commit}" - end - - def label context = nil - aref - end - - def text - case base - when nil - "#{date}" - when /%s/ - "{#{date}}[#{base % commit}]" - else - "{#{date}}[#{base}#{commit}]" - end + " {#{author}}[mailto:#{email}]" - end - - def accept visitor - visitor.accept_heading self - begin - if visitor.respond_to?(:code_object=) - code_object = visitor.code_object - visitor.code_object = self - end - contents.each do |body| - body.accept visitor - end - ensure - if visitor.respond_to?(:code_object) - visitor.code_object = code_object - end - end - end - - def pretty_print q # :nodoc: - q.group(2, '[log_entry: ', ']') do - q.text commit - q.text ',' - q.breakable - q.group(2, '[date: ', ']') { q.text date } - q.text ',' - q.breakable - q.group(2, '[author: ', ']') { q.text author } - q.text ',' - q.breakable - q.group(2, '[email: ', ']') { q.text email } - q.text ',' - q.breakable - q.pp contents - end - end - end - end -end diff --git a/lib/rdoc/parser/markdown.rb b/lib/rdoc/parser/markdown.rb deleted file mode 100644 index 3c316227b9..0000000000 --- a/lib/rdoc/parser/markdown.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -## -# Parse a Markdown format file. The parsed RDoc::Markup::Document is attached -# as a file comment. - -class RDoc::Parser::Markdown < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/\.(md|markdown)(?:\.[^.]+)?$/) - - ## - # Creates an Markdown-format TopLevel for the given file. - - def scan - comment = RDoc::Comment.new @content, @top_level - comment.format = 'markdown' - - @top_level.comment = comment - end - -end diff --git a/lib/rdoc/parser/prism_ruby.rb b/lib/rdoc/parser/prism_ruby.rb deleted file mode 100644 index 05e98ad6c4..0000000000 --- a/lib/rdoc/parser/prism_ruby.rb +++ /dev/null @@ -1,1026 +0,0 @@ -# frozen_string_literal: true - -require 'prism' -require_relative 'ripper_state_lex' - -# Unlike lib/rdoc/parser/ruby.rb, this file is not based on rtags and does not contain code from -# rtags.rb - -# ruby-lex.rb - ruby lexcal analyzer -# ruby-token.rb - ruby tokens - -# Parse and collect document from Ruby source code. -# RDoc::Parser::PrismRuby is compatible with RDoc::Parser::Ruby and aims to replace it. - -class RDoc::Parser::PrismRuby < RDoc::Parser - - parse_files_matching(/\.rbw?$/) if ENV['RDOC_USE_PRISM_PARSER'] - - attr_accessor :visibility - attr_reader :container, :singleton - - def initialize(top_level, file_name, content, options, stats) - super - - content = handle_tab_width(content) - - @size = 0 - @token_listeners = nil - content = RDoc::Encoding.remove_magic_comment content - @content = content - @markup = @options.markup - @track_visibility = :nodoc != @options.visibility - @encoding = @options.encoding - - @module_nesting = [top_level] - @container = top_level - @visibility = :public - @singleton = false - end - - # Dive into another container - - def with_container(container, singleton: false) - old_container = @container - old_visibility = @visibility - old_singleton = @singleton - @visibility = :public - @container = container - @singleton = singleton - unless singleton - @module_nesting.push container - - # Need to update module parent chain to emulate Module.nesting. - # This mechanism is inaccurate and needs to be fixed. - container.parent = old_container - end - yield container - ensure - @container = old_container - @visibility = old_visibility - @singleton = old_singleton - @module_nesting.pop unless singleton - end - - # Records the location of this +container+ in the file for this parser and - # adds it to the list of classes and modules in the file. - - def record_location container # :nodoc: - case container - when RDoc::ClassModule then - @top_level.add_to_classes_or_modules container - end - - container.record_location @top_level - end - - # Scans this Ruby file for Ruby constructs - - def scan - @tokens = RDoc::Parser::RipperStateLex.parse(@content) - @lines = @content.lines - result = Prism.parse(@content) - @program_node = result.value - @line_nodes = {} - prepare_line_nodes(@program_node) - prepare_comments(result.comments) - return if @top_level.done_documenting - - @first_non_meta_comment = nil - if (_line_no, start_line, rdoc_comment = @unprocessed_comments.first) - @first_non_meta_comment = rdoc_comment if start_line < @program_node.location.start_line - end - - @program_node.accept(RDocVisitor.new(self, @top_level, @store)) - process_comments_until(@lines.size + 1) - end - - def should_document?(code_object) # :nodoc: - return true unless @track_visibility - return false if code_object.parent&.document_children == false - code_object.document_self - end - - # Assign AST node to a line. - # This is used to show meta-method source code in the documentation. - - def prepare_line_nodes(node) # :nodoc: - case node - when Prism::CallNode, Prism::DefNode - @line_nodes[node.location.start_line] ||= node - end - node.compact_child_nodes.each do |child| - prepare_line_nodes(child) - end - end - - # Prepares comments for processing. Comments are grouped into consecutive. - # Consecutive comment is linked to the next non-blank line. - # - # Example: - # 01| class A # modifier comment 1 - # 02| def foo; end # modifier comment 2 - # 03| - # 04| # consecutive comment 1 start_line: 4 - # 05| # consecutive comment 1 linked to line: 7 - # 06| - # 07| # consecutive comment 2 start_line: 7 - # 08| # consecutive comment 2 linked to line: 10 - # 09| - # 10| def bar; end # consecutive comment 2 linked to this line - # 11| end - - def prepare_comments(comments) - current = [] - consecutive_comments = [current] - @modifier_comments = {} - comments.each do |comment| - if comment.is_a? Prism::EmbDocComment - consecutive_comments << [comment] << (current = []) - elsif comment.location.start_line_slice.match?(/\S/) - @modifier_comments[comment.location.start_line] = RDoc::Comment.new(comment.slice, @top_level, :ruby) - elsif current.empty? || current.last.location.end_line + 1 == comment.location.start_line - current << comment - else - consecutive_comments << (current = [comment]) - end - end - consecutive_comments.reject!(&:empty?) - - # Example: line_no = 5, start_line = 2, comment_text = "# comment_start_line\n# comment\n" - # 1| class A - # 2| # comment_start_line - # 3| # comment - # 4| - # 5| def f; end # comment linked to this line - # 6| end - @unprocessed_comments = consecutive_comments.map! do |comments| - start_line = comments.first.location.start_line - line_no = comments.last.location.end_line + (comments.last.location.end_column == 0 ? 0 : 1) - texts = comments.map do |c| - c.is_a?(Prism::EmbDocComment) ? c.slice.lines[1...-1].join : c.slice - end - text = RDoc::Encoding.change_encoding(texts.join("\n"), @encoding) if @encoding - line_no += 1 while @lines[line_no - 1]&.match?(/\A\s*$/) - comment = RDoc::Comment.new(text, @top_level, :ruby) - comment.line = start_line - [line_no, start_line, comment] - end - - # The first comment is special. It defines markup for the rest of the comments. - _, first_comment_start_line, first_comment_text = @unprocessed_comments.first - if first_comment_text && @lines[0...first_comment_start_line - 1].all? { |l| l.match?(/\A\s*$/) } - comment = RDoc::Comment.new(first_comment_text.text, @top_level, :ruby) - handle_consecutive_comment_directive(@container, comment) - @markup = comment.format - end - @unprocessed_comments.each do |_, _, comment| - comment.format = @markup - end - end - - # Creates an RDoc::Method on +container+ from +comment+ if there is a - # Signature section in the comment - - def parse_comment_tomdoc(container, comment, line_no, start_line) - return unless signature = RDoc::TomDoc.signature(comment) - - name, = signature.split %r%[ \(]%, 2 - - meth = RDoc::GhostMethod.new comment.text, name - record_location(meth) - meth.line = start_line - meth.call_seq = signature - return unless meth.name - - meth.start_collecting_tokens - node = @line_nodes[line_no] - tokens = node ? visible_tokens_from_location(node.location) : [file_line_comment_token(start_line)] - tokens.each { |token| meth.token_stream << token } - - container.add_method meth - comment.remove_private - comment.normalize - meth.comment = comment - @stats.add_method meth - end - - def handle_modifier_directive(code_object, line_no) # :nodoc: - comment = @modifier_comments[line_no] - @preprocess.handle(comment.text, code_object) if comment - end - - def handle_consecutive_comment_directive(code_object, comment) # :nodoc: - return unless comment - @preprocess.handle(comment, code_object) do |directive, param| - case directive - when 'method', 'singleton-method', - 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then - # handled elsewhere - '' - when 'section' then - @container.set_current_section(param, comment.dup) - comment.text = '' - break - end - end - comment.remove_private - end - - def call_node_name_arguments(call_node) # :nodoc: - return [] unless call_node.arguments - call_node.arguments.arguments.map do |arg| - case arg - when Prism::SymbolNode - arg.value - when Prism::StringNode - arg.unescaped - end - end || [] - end - - # Handles meta method comments - - def handle_meta_method_comment(comment, node) - is_call_node = node.is_a?(Prism::CallNode) - singleton_method = false - visibility = @visibility - attributes = rw = line_no = method_name = nil - - processed_comment = comment.dup - @preprocess.handle(processed_comment, @container) do |directive, param, line| - case directive - when 'attr', 'attr_reader', 'attr_writer', 'attr_accessor' - attributes = [param] if param - attributes ||= call_node_name_arguments(node) if is_call_node - rw = directive == 'attr_writer' ? 'W' : directive == 'attr_accessor' ? 'RW' : 'R' - '' - when 'method' - method_name = param - line_no = line - '' - when 'singleton-method' - method_name = param - line_no = line - singleton_method = true - visibility = :public - '' - when 'section' then - @container.set_current_section(param, comment.dup) - return # If the comment contains :section:, it is not a meta method comment - end - end - - if attributes - attributes.each do |attr| - a = RDoc::Attr.new(@container, attr, rw, processed_comment) - a.store = @store - a.line = line_no - a.singleton = @singleton - record_location(a) - @container.add_attribute(a) - a.visibility = visibility - end - elsif line_no || node - method_name ||= call_node_name_arguments(node).first if is_call_node - meth = RDoc::AnyMethod.new(@container, method_name) - meth.singleton = @singleton || singleton_method - handle_consecutive_comment_directive(meth, comment) - comment.normalize - comment.extract_call_seq(meth) - meth.comment = comment - if node - tokens = visible_tokens_from_location(node.location) - line_no = node.location.start_line - else - tokens = [file_line_comment_token(line_no)] - end - internal_add_method( - @container, - meth, - line_no: line_no, - visibility: visibility, - singleton: @singleton || singleton_method, - params: '()', - calls_super: false, - block_params: nil, - tokens: tokens - ) - end - end - - def normal_comment_treat_as_ghost_method_for_now?(comment_text, line_no) # :nodoc: - # Meta method comment should start with `##` but some comments does not follow this rule. - # For now, RDoc accepts them as a meta method comment if there is no node linked to it. - !@line_nodes[line_no] && comment_text.match?(/^#\s+:(method|singleton-method|attr|attr_reader|attr_writer|attr_accessor):/) - end - - def handle_standalone_consecutive_comment_directive(comment, line_no, start_line) # :nodoc: - if @markup == 'tomdoc' - parse_comment_tomdoc(@container, comment, line_no, start_line) - return - end - - if comment.text =~ /\A#\#$/ && comment != @first_non_meta_comment - node = @line_nodes[line_no] - handle_meta_method_comment(comment, node) - elsif normal_comment_treat_as_ghost_method_for_now?(comment.text, line_no) && comment != @first_non_meta_comment - handle_meta_method_comment(comment, nil) - else - handle_consecutive_comment_directive(@container, comment) - end - end - - # Processes consecutive comments that were not linked to any documentable code until the given line number - - def process_comments_until(line_no_until) - while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until - line_no, start_line, rdoc_comment = @unprocessed_comments.shift - handle_standalone_consecutive_comment_directive(rdoc_comment, line_no, start_line) - end - end - - # Skips all undocumentable consecutive comments until the given line number. - # Undocumentable comments are comments written inside `def` or inside undocumentable class/module - - def skip_comments_until(line_no_until) - while !@unprocessed_comments.empty? && @unprocessed_comments.first[0] <= line_no_until - @unprocessed_comments.shift - end - end - - # Returns consecutive comment linked to the given line number - - def consecutive_comment(line_no) - if @unprocessed_comments.first&.first == line_no - @unprocessed_comments.shift.last - end - end - - def slice_tokens(start_pos, end_pos) # :nodoc: - start_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> start_pos) >= 0 } - end_index = @tokens.bsearch_index { |t| ([t.line_no, t.char_no] <=> end_pos) >= 0 } - tokens = @tokens[start_index...end_index] - tokens.pop if tokens.last&.kind == :on_nl - tokens - end - - def file_line_comment_token(line_no) # :nodoc: - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no - 1, 0, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - position_comment - end - - # Returns tokens from the given location - - def visible_tokens_from_location(location) - position_comment = file_line_comment_token(location.start_line) - newline_token = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - indent_token = RDoc::Parser::RipperStateLex::Token.new(location.start_line, 0, :on_sp, ' ' * location.start_character_column) - tokens = slice_tokens( - [location.start_line, location.start_character_column], - [location.end_line, location.end_character_column] - ) - [position_comment, newline_token, indent_token, *tokens] - end - - # Handles `public :foo, :bar` `private :foo, :bar` and `protected :foo, :bar` - - def change_method_visibility(names, visibility, singleton: @singleton) - new_methods = [] - @container.methods_matching(names, singleton) do |m| - if m.parent != @container - m = m.dup - record_location(m) - new_methods << m - else - m.visibility = visibility - end - end - new_methods.each do |method| - case method - when RDoc::AnyMethod then - @container.add_method(method) - when RDoc::Attr then - @container.add_attribute(method) - end - method.visibility = visibility - end - end - - # Handles `module_function :foo, :bar` - - def change_method_to_module_function(names) - @container.set_visibility_for(names, :private, false) - new_methods = [] - @container.methods_matching(names) do |m| - s_m = m.dup - record_location(s_m) - s_m.singleton = true - new_methods << s_m - end - new_methods.each do |method| - case method - when RDoc::AnyMethod then - @container.add_method(method) - when RDoc::Attr then - @container.add_attribute(method) - end - method.visibility = :public - end - end - - # Handles `alias foo bar` and `alias_method :foo, :bar` - - def add_alias_method(old_name, new_name, line_no) - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - visibility = @container.find_method(old_name, @singleton)&.visibility || :public - a = RDoc::Alias.new(nil, old_name, new_name, comment, @singleton) - a.comment = comment - handle_modifier_directive(a, line_no) - a.store = @store - a.line = line_no - record_location(a) - if should_document?(a) - @container.add_alias(a) - @container.find_method(new_name, @singleton)&.visibility = visibility - end - end - - # Handles `attr :a, :b`, `attr_reader :a, :b`, `attr_writer :a, :b` and `attr_accessor :a, :b` - - def add_attributes(names, rw, line_no) - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - return unless @container.document_children - - names.each do |symbol| - a = RDoc::Attr.new(nil, symbol.to_s, rw, comment) - a.store = @store - a.line = line_no - a.singleton = @singleton - record_location(a) - handle_modifier_directive(a, line_no) - @container.add_attribute(a) if should_document?(a) - a.visibility = visibility # should set after adding to container - end - end - - def add_includes_extends(names, rdoc_class, line_no) # :nodoc: - comment = consecutive_comment(line_no) - handle_consecutive_comment_directive(@container, comment) - names.each do |name| - ie = @container.add(rdoc_class, name, '') - ie.store = @store - ie.line = line_no - ie.comment = comment - record_location(ie) - end - end - - # Handle `include Foo, Bar` - - def add_includes(names, line_no) # :nodoc: - add_includes_extends(names, RDoc::Include, line_no) - end - - # Handle `extend Foo, Bar` - - def add_extends(names, line_no) # :nodoc: - add_includes_extends(names, RDoc::Extend, line_no) - end - - # Adds a method defined by `def` syntax - - def add_method(name, receiver_name:, receiver_fallback_type:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:, start_line:, end_line:) - receiver = receiver_name ? find_or_create_module_path(receiver_name, receiver_fallback_type) : @container - meth = RDoc::AnyMethod.new(nil, name) - if (comment = consecutive_comment(start_line)) - handle_consecutive_comment_directive(@container, comment) - handle_consecutive_comment_directive(meth, comment) - - comment.normalize - comment.extract_call_seq(meth) - meth.comment = comment - end - handle_modifier_directive(meth, start_line) - handle_modifier_directive(meth, end_line) - return unless should_document?(meth) - - - if meth.name == 'initialize' && !singleton - if meth.dont_rename_initialize - visibility = :protected - else - meth.name = 'new' - singleton = true - visibility = :public - end - end - - internal_add_method( - receiver, - meth, - line_no: start_line, - visibility: visibility, - singleton: singleton, - params: params, - calls_super: calls_super, - block_params: block_params, - tokens: tokens - ) - end - - private def internal_add_method(container, meth, line_no:, visibility:, singleton:, params:, calls_super:, block_params:, tokens:) # :nodoc: - meth.name ||= meth.call_seq[/\A[^()\s]+/] if meth.call_seq - meth.name ||= 'unknown' - meth.store = @store - meth.line = line_no - meth.singleton = singleton - container.add_method(meth) # should add after setting singleton and before setting visibility - meth.visibility = visibility - meth.params ||= params - meth.calls_super = calls_super - meth.block_params ||= block_params if block_params - record_location(meth) - meth.start_collecting_tokens - tokens.each do |token| - meth.token_stream << token - end - end - - # Find or create module or class from a given module name. - # If module or class does not exist, creates a module or a class according to `create_mode` argument. - - def find_or_create_module_path(module_name, create_mode) - root_name, *path, name = module_name.split('::') - add_module = ->(mod, name, mode) { - case mode - when :class - mod.add_class(RDoc::NormalClass, name, 'Object').tap { |m| m.store = @store } - when :module - mod.add_module(RDoc::NormalModule, name).tap { |m| m.store = @store } - end - } - if root_name.empty? - mod = @top_level - else - @module_nesting.reverse_each do |nesting| - mod = nesting.find_module_named(root_name) - break if mod - end - return mod || add_module.call(@top_level, root_name, create_mode) unless name - mod ||= add_module.call(@top_level, root_name, :module) - end - path.each do |name| - mod = mod.find_module_named(name) || add_module.call(mod, name, :module) - end - mod.find_module_named(name) || add_module.call(mod, name, create_mode) - end - - # Resolves constant path to a full path by searching module nesting - - def resolve_constant_path(constant_path) - owner_name, path = constant_path.split('::', 2) - return constant_path if owner_name.empty? # ::Foo, ::Foo::Bar - mod = nil - @module_nesting.reverse_each do |nesting| - mod = nesting.find_module_named(owner_name) - break if mod - end - mod ||= @top_level.find_module_named(owner_name) - [mod.full_name, path].compact.join('::') if mod - end - - # Returns a pair of owner module and constant name from a given constant path. - # Creates owner module if it does not exist. - - def find_or_create_constant_owner_name(constant_path) - const_path, colon, name = constant_path.rpartition('::') - if colon.empty? # class Foo - [@container, name] - elsif const_path.empty? # class ::Foo - [@top_level, name] - else # `class Foo::Bar` or `class ::Foo::Bar` - [find_or_create_module_path(const_path, :module), name] - end - end - - # Adds a constant - - def add_constant(constant_name, rhs_name, start_line, end_line) - comment = consecutive_comment(start_line) - handle_consecutive_comment_directive(@container, comment) - owner, name = find_or_create_constant_owner_name(constant_name) - constant = RDoc::Constant.new(name, rhs_name, comment) - constant.store = @store - constant.line = start_line - record_location(constant) - handle_modifier_directive(constant, start_line) - handle_modifier_directive(constant, end_line) - owner.add_constant(constant) - mod = - if rhs_name =~ /^::/ - @store.find_class_or_module(rhs_name) - else - @container.find_module_named(rhs_name) - end - if mod && constant.document_self - a = @container.add_module_alias(mod, rhs_name, constant, @top_level) - a.store = @store - a.line = start_line - record_location(a) - end - end - - # Adds module or class - - def add_module_or_class(module_name, start_line, end_line, is_class: false, superclass_name: nil) - comment = consecutive_comment(start_line) - handle_consecutive_comment_directive(@container, comment) - return unless @container.document_children - - owner, name = find_or_create_constant_owner_name(module_name) - if is_class - mod = owner.classes_hash[name] || owner.add_class(RDoc::NormalClass, name, superclass_name || '::Object') - - # RDoc::NormalClass resolves superclass name despite of the lack of module nesting information. - # We need to fix it when RDoc::NormalClass resolved to a wrong constant name - if superclass_name - superclass_full_path = resolve_constant_path(superclass_name) - superclass = @store.find_class_or_module(superclass_full_path) if superclass_full_path - superclass_full_path ||= superclass_name - if superclass - mod.superclass = superclass - elsif mod.superclass.is_a?(String) && mod.superclass != superclass_full_path - mod.superclass = superclass_full_path - end - end - else - mod = owner.modules_hash[name] || owner.add_module(RDoc::NormalModule, name) - end - - mod.store = @store - mod.line = start_line - record_location(mod) - handle_modifier_directive(mod, start_line) - handle_modifier_directive(mod, end_line) - mod.add_comment(comment, @top_level) if comment - mod - end - - class RDocVisitor < Prism::Visitor # :nodoc: - def initialize(scanner, top_level, store) - @scanner = scanner - @top_level = top_level - @store = store - end - - def visit_call_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - if node.receiver.nil? - case node.name - when :attr - _visit_call_attr_reader_writer_accessor(node, 'R') - when :attr_reader - _visit_call_attr_reader_writer_accessor(node, 'R') - when :attr_writer - _visit_call_attr_reader_writer_accessor(node, 'W') - when :attr_accessor - _visit_call_attr_reader_writer_accessor(node, 'RW') - when :include - _visit_call_include(node) - when :extend - _visit_call_extend(node) - when :public - _visit_call_public_private_protected(node, :public) { super } - when :private - _visit_call_public_private_protected(node, :private) { super } - when :protected - _visit_call_public_private_protected(node, :protected) { super } - when :private_constant - _visit_call_private_constant(node) - when :public_constant - _visit_call_public_constant(node) - when :require - _visit_call_require(node) - when :alias_method - _visit_call_alias_method(node) - when :module_function - _visit_call_module_function(node) { super } - when :public_class_method - _visit_call_public_private_class_method(node, :public) { super } - when :private_class_method - _visit_call_public_private_class_method(node, :private) { super } - else - super - end - else - super - end - end - - def visit_alias_method_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - return unless node.old_name.is_a?(Prism::SymbolNode) && node.new_name.is_a?(Prism::SymbolNode) - @scanner.add_alias_method(node.old_name.value.to_s, node.new_name.value.to_s, node.location.start_line) - end - - def visit_module_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - module_name = constant_path_string(node.constant_path) - mod = @scanner.add_module_or_class(module_name, node.location.start_line, node.location.end_line) if module_name - if mod - @scanner.with_container(mod) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_class_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - superclass_name = constant_path_string(node.superclass) if node.superclass - class_name = constant_path_string(node.constant_path) - klass = @scanner.add_module_or_class(class_name, node.location.start_line, node.location.end_line, is_class: true, superclass_name: superclass_name) if class_name - if klass - @scanner.with_container(klass) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_singleton_class_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - - expression = node.expression - expression = expression.body.body.first if expression.is_a?(Prism::ParenthesesNode) && expression.body&.body&.size == 1 - - case expression - when Prism::ConstantWriteNode - # Accept `class << (NameErrorCheckers = Object.new)` as a module which is not actually a module - mod = @scanner.container.add_module(RDoc::NormalModule, expression.name.to_s) - when Prism::ConstantPathNode, Prism::ConstantReadNode - expression_name = constant_path_string(expression) - # If a constant_path does not exist, RDoc creates a module - mod = @scanner.find_or_create_module_path(expression_name, :module) if expression_name - when Prism::SelfNode - mod = @scanner.container if @scanner.container != @top_level - end - if mod - @scanner.with_container(mod, singleton: true) do - super - @scanner.process_comments_until(node.location.end_line) - end - else - @scanner.skip_comments_until(node.location.end_line) - end - end - - def visit_def_node(node) - start_line = node.location.start_line - end_line = node.location.end_line - @scanner.process_comments_until(start_line - 1) - - case node.receiver - when Prism::NilNode, Prism::TrueNode, Prism::FalseNode - visibility = :public - singleton = false - receiver_name = - case node.receiver - when Prism::NilNode - 'NilClass' - when Prism::TrueNode - 'TrueClass' - when Prism::FalseNode - 'FalseClass' - end - receiver_fallback_type = :class - when Prism::SelfNode - # singleton method of a singleton class is not documentable - return if @scanner.singleton - visibility = :public - singleton = true - when Prism::ConstantReadNode, Prism::ConstantPathNode - visibility = :public - singleton = true - receiver_name = constant_path_string(node.receiver) - receiver_fallback_type = :module - return unless receiver_name - when nil - visibility = @scanner.visibility - singleton = @scanner.singleton - else - # `def (unknown expression).method_name` is not documentable - return - end - name = node.name.to_s - params, block_params, calls_super = MethodSignatureVisitor.scan_signature(node) - tokens = @scanner.visible_tokens_from_location(node.location) - - @scanner.add_method( - name, - receiver_name: receiver_name, - receiver_fallback_type: receiver_fallback_type, - visibility: visibility, - singleton: singleton, - params: params, - block_params: block_params, - calls_super: calls_super, - tokens: tokens, - start_line: start_line, - end_line: end_line - ) - ensure - @scanner.skip_comments_until(end_line) - end - - def visit_constant_path_write_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - path = constant_path_string(node.target) - return unless path - - @scanner.add_constant( - path, - constant_path_string(node.value) || node.value.slice, - node.location.start_line, - node.location.end_line - ) - @scanner.skip_comments_until(node.location.end_line) - # Do not traverse rhs not to document `A::B = Struct.new{def undocumentable_method; end}` - end - - def visit_constant_write_node(node) - @scanner.process_comments_until(node.location.start_line - 1) - @scanner.add_constant( - node.name.to_s, - constant_path_string(node.value) || node.value.slice, - node.location.start_line, - node.location.end_line - ) - @scanner.skip_comments_until(node.location.end_line) - # Do not traverse rhs not to document `A = Struct.new{def undocumentable_method; end}` - end - - private - - def constant_arguments_names(call_node) - return unless call_node.arguments - names = call_node.arguments.arguments.map { |arg| constant_path_string(arg) } - names.all? ? names : nil - end - - def symbol_arguments(call_node) - arguments_node = call_node.arguments - return unless arguments_node && arguments_node.arguments.all? { |arg| arg.is_a?(Prism::SymbolNode)} - arguments_node.arguments.map { |arg| arg.value.to_sym } - end - - def visibility_method_arguments(call_node, singleton:) - arguments_node = call_node.arguments - return unless arguments_node - symbols = symbol_arguments(call_node) - if symbols - # module_function :foo, :bar - return symbols.map(&:to_s) - else - return unless arguments_node.arguments.size == 1 - arg = arguments_node.arguments.first - return unless arg.is_a?(Prism::DefNode) - - if singleton - # `private_class_method def foo; end` `private_class_method def not_self.foo; end` should be ignored - return unless arg.receiver.is_a?(Prism::SelfNode) - else - # `module_function def something.foo` should be ignored - return if arg.receiver - end - # `module_function def foo; end` or `private_class_method def self.foo; end` - [arg.name.to_s] - end - end - - def constant_path_string(node) - case node - when Prism::ConstantReadNode - node.name.to_s - when Prism::ConstantPathNode - parent_name = node.parent ? constant_path_string(node.parent) : '' - "#{parent_name}::#{node.name}" if parent_name - end - end - - def _visit_call_require(call_node) - return unless call_node.arguments&.arguments&.size == 1 - arg = call_node.arguments.arguments.first - return unless arg.is_a?(Prism::StringNode) - @scanner.container.add_require(RDoc::Require.new(arg.unescaped, nil)) - end - - def _visit_call_module_function(call_node) - yield - return if @scanner.singleton - names = visibility_method_arguments(call_node, singleton: false)&.map(&:to_s) - @scanner.change_method_to_module_function(names) if names - end - - def _visit_call_public_private_class_method(call_node, visibility) - yield - return if @scanner.singleton - names = visibility_method_arguments(call_node, singleton: true) - @scanner.change_method_visibility(names, visibility, singleton: true) if names - end - - def _visit_call_public_private_protected(call_node, visibility) - arguments_node = call_node.arguments - if arguments_node.nil? # `public` `private` - @scanner.visibility = visibility - else # `public :foo, :bar`, `private def foo; end` - yield - names = visibility_method_arguments(call_node, singleton: @scanner.singleton) - @scanner.change_method_visibility(names, visibility) if names - end - end - - def _visit_call_alias_method(call_node) - new_name, old_name, *rest = symbol_arguments(call_node) - return unless old_name && new_name && rest.empty? - @scanner.add_alias_method(old_name.to_s, new_name.to_s, call_node.location.start_line) - end - - def _visit_call_include(call_node) - names = constant_arguments_names(call_node) - line_no = call_node.location.start_line - return unless names - - if @scanner.singleton - @scanner.add_extends(names, line_no) - else - @scanner.add_includes(names, line_no) - end - end - - def _visit_call_extend(call_node) - names = constant_arguments_names(call_node) - @scanner.add_extends(names, call_node.location.start_line) if names && !@scanner.singleton - end - - def _visit_call_public_constant(call_node) - return if @scanner.singleton - names = symbol_arguments(call_node) - @scanner.container.set_constant_visibility_for(names.map(&:to_s), :public) if names - end - - def _visit_call_private_constant(call_node) - return if @scanner.singleton - names = symbol_arguments(call_node) - @scanner.container.set_constant_visibility_for(names.map(&:to_s), :private) if names - end - - def _visit_call_attr_reader_writer_accessor(call_node, rw) - names = symbol_arguments(call_node) - @scanner.add_attributes(names.map(&:to_s), rw, call_node.location.start_line) if names - end - class MethodSignatureVisitor < Prism::Visitor # :nodoc: - class << self - def scan_signature(def_node) - visitor = new - def_node.body&.accept(visitor) - params = "(#{def_node.parameters&.slice})" - block_params = visitor.yields.first - [params, block_params, visitor.calls_super] - end - end - - attr_reader :params, :yields, :calls_super - - def initialize - @params = nil - @calls_super = false - @yields = [] - end - - def visit_def_node(node) - # stop traverse inside nested def - end - - def visit_yield_node(node) - @yields << (node.arguments&.slice || '') - end - - def visit_super_node(node) - @calls_super = true - super - end - - def visit_forwarding_super_node(node) - @calls_super = true - end - end - end -end diff --git a/lib/rdoc/parser/rd.rb b/lib/rdoc/parser/rd.rb deleted file mode 100644 index 19e47e549d..0000000000 --- a/lib/rdoc/parser/rd.rb +++ /dev/null @@ -1,22 +0,0 @@ -# frozen_string_literal: true -## -# Parse a RD format file. The parsed RDoc::Markup::Document is attached as a -# file comment. - -class RDoc::Parser::RD < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(/\.rd(?:\.[^.]+)?$/) - - ## - # Creates an rd-format TopLevel for the given file. - - def scan - comment = RDoc::Comment.new @content, @top_level - comment.format = 'rd' - - @top_level.comment = comment - end - -end diff --git a/lib/rdoc/parser/ripper_state_lex.rb b/lib/rdoc/parser/ripper_state_lex.rb deleted file mode 100644 index 2212906bbd..0000000000 --- a/lib/rdoc/parser/ripper_state_lex.rb +++ /dev/null @@ -1,302 +0,0 @@ -# frozen_string_literal: true -require 'ripper' - -## -# Wrapper for Ripper lex states - -class RDoc::Parser::RipperStateLex - # :stopdoc: - - Token = Struct.new(:line_no, :char_no, :kind, :text, :state) - - EXPR_END = Ripper::EXPR_END - EXPR_ENDFN = Ripper::EXPR_ENDFN - EXPR_ARG = Ripper::EXPR_ARG - EXPR_FNAME = Ripper::EXPR_FNAME - - class InnerStateLex < Ripper::Filter - def initialize(code) - super(code) - end - - def on_default(event, tok, data) - data << Token.new(lineno, column, event, tok, state) - end - end - - def get_squashed_tk - if @buf.empty? - tk = @tokens.shift - else - tk = @buf.shift - end - return nil if tk.nil? - case tk[:kind] - when :on_symbeg then - tk = get_symbol_tk(tk) - when :on_tstring_beg then - tk = get_string_tk(tk) - when :on_backtick then - if (tk[:state] & (EXPR_FNAME | EXPR_ENDFN)) != 0 - tk[:kind] = :on_ident - tk[:state] = Ripper::Lexer::State.new(EXPR_ARG) - else - tk = get_string_tk(tk) - end - when :on_regexp_beg then - tk = get_regexp_tk(tk) - when :on_embdoc_beg then - tk = get_embdoc_tk(tk) - when :on_heredoc_beg then - @heredoc_queue << retrieve_heredoc_info(tk) - when :on_nl, :on_ignored_nl, :on_comment, :on_heredoc_end then - if !@heredoc_queue.empty? - get_heredoc_tk(*@heredoc_queue.shift) - elsif tk[:text].nil? # :on_ignored_nl sometimes gives nil - tk[:text] = '' - end - when :on_words_beg then - tk = get_words_tk(tk) - when :on_qwords_beg then - tk = get_words_tk(tk) - when :on_symbols_beg then - tk = get_words_tk(tk) - when :on_qsymbols_beg then - tk = get_words_tk(tk) - when :on_op then - if '&.' == tk[:text] - tk[:kind] = :on_period - else - tk = get_op_tk(tk) - end - end - tk - end - - private def get_symbol_tk(tk) - is_symbol = true - symbol_tk = Token.new(tk.line_no, tk.char_no, :on_symbol) - if ":'" == tk[:text] or ':"' == tk[:text] or tk[:text].start_with?('%s') - tk1 = get_string_tk(tk) - symbol_tk[:text] = tk1[:text] - symbol_tk[:state] = tk1[:state] - else - case (tk1 = get_squashed_tk)[:kind] - when :on_ident - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_tstring_content - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = get_squashed_tk[:state] # skip :on_tstring_end - when :on_tstring_end - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_op - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_ivar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_cvar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_gvar - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_const - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - when :on_kw - symbol_tk[:text] = ":#{tk1[:text]}" - symbol_tk[:state] = tk1[:state] - else - is_symbol = false - tk = tk1 - end - end - if is_symbol - tk = symbol_tk - end - tk - end - - private def get_string_tk(tk) - string = tk[:text] - state = nil - kind = :on_tstring - loop do - inner_str_tk = get_squashed_tk - if inner_str_tk.nil? - break - elsif :on_tstring_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - break - elsif :on_label_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - kind = :on_symbol - break - else - string = string + inner_str_tk[:text] - if :on_embexpr_beg == inner_str_tk[:kind] then - kind = :on_dstring if :on_tstring == kind - end - end - end - Token.new(tk.line_no, tk.char_no, kind, string, state) - end - - private def get_regexp_tk(tk) - string = tk[:text] - state = nil - loop do - inner_str_tk = get_squashed_tk - if inner_str_tk.nil? - break - elsif :on_regexp_end == inner_str_tk[:kind] - string = string + inner_str_tk[:text] - state = inner_str_tk[:state] - break - else - string = string + inner_str_tk[:text] - end - end - Token.new(tk.line_no, tk.char_no, :on_regexp, string, state) - end - - private def get_embdoc_tk(tk) - string = tk[:text] - until :on_embdoc_end == (embdoc_tk = get_squashed_tk)[:kind] do - string = string + embdoc_tk[:text] - end - string = string + embdoc_tk[:text] - Token.new(tk.line_no, tk.char_no, :on_embdoc, string, embdoc_tk.state) - end - - private def get_heredoc_tk(heredoc_name, indent) - string = '' - start_tk = nil - prev_tk = nil - until heredoc_end?(heredoc_name, indent, tk = @tokens.shift) do - start_tk = tk unless start_tk - if (prev_tk.nil? or "\n" == prev_tk[:text][-1]) and 0 != tk[:char_no] - string = string + (' ' * tk[:char_no]) - end - string = string + tk[:text] - prev_tk = tk - end - start_tk = tk unless start_tk - prev_tk = tk unless prev_tk - @buf.unshift tk # closing heredoc - heredoc_tk = Token.new(start_tk.line_no, start_tk.char_no, :on_heredoc, string, prev_tk.state) - @buf.unshift heredoc_tk - end - - private def retrieve_heredoc_info(tk) - name = tk[:text].gsub(/\A<<[-~]?(['"`]?)(.+)\1\z/, '\2') - indent = tk[:text] =~ /\A<<[-~]/ - [name, indent] - end - - private def heredoc_end?(name, indent, tk) - result = false - if :on_heredoc_end == tk[:kind] then - tk_name = tk[:text].chomp - tk_name.lstrip! if indent - if name == tk_name - result = true - end - end - result - end - - private def get_words_tk(tk) - string = '' - start_token = tk[:text] - start_quote = tk[:text].rstrip[-1] - line_no = tk[:line_no] - char_no = tk[:char_no] - state = tk[:state] - end_quote = - case start_quote - when ?( then ?) - when ?[ then ?] - when ?{ then ?} - when ?< then ?> - else start_quote - end - end_token = nil - loop do - tk = get_squashed_tk - if tk.nil? - end_token = end_quote - break - elsif :on_tstring_content == tk[:kind] then - string += tk[:text] - elsif :on_words_sep == tk[:kind] or :on_tstring_end == tk[:kind] then - if end_quote == tk[:text].strip then - end_token = tk[:text] - break - else - string += tk[:text] - end - else - string += tk[:text] - end - end - text = "#{start_token}#{string}#{end_token}" - Token.new(line_no, char_no, :on_dstring, text, state) - end - - private def get_op_tk(tk) - redefinable_operators = %w[! != !~ % & * ** + +@ - -@ / < << <= <=> == === =~ > >= >> [] []= ^ ` | ~] - if redefinable_operators.include?(tk[:text]) and tk[:state] == EXPR_ARG then - tk[:state] = Ripper::Lexer::State.new(EXPR_ARG) - tk[:kind] = :on_ident - elsif tk[:text] =~ /^[-+]$/ then - tk_ahead = get_squashed_tk - case tk_ahead[:kind] - when :on_int, :on_float, :on_rational, :on_imaginary then - tk[:text] += tk_ahead[:text] - tk[:kind] = tk_ahead[:kind] - tk[:state] = tk_ahead[:state] - when :on_heredoc_beg, :on_tstring, :on_dstring # frozen/non-frozen string literal - tk[:text] += tk_ahead[:text] - tk[:kind] = tk_ahead[:kind] - tk[:state] = tk_ahead[:state] - else - @buf.unshift tk_ahead - end - end - tk - end - - # :startdoc: - - # New lexer for +code+. - def initialize(code) - @buf = [] - @heredoc_queue = [] - @inner_lex = InnerStateLex.new(code) - @tokens = @inner_lex.parse([]) - end - - # Returns tokens parsed from +code+. - def self.parse(code) - lex = self.new(code) - tokens = [] - begin - while tk = lex.get_squashed_tk - tokens.push tk - end - rescue StopIteration - end - tokens - end - - # Returns +true+ if lex state will be +END+ after +token+. - def self.end?(token) - (token[:state] & EXPR_END) - end -end diff --git a/lib/rdoc/parser/ruby.rb b/lib/rdoc/parser/ruby.rb deleted file mode 100644 index 909b02d2af..0000000000 --- a/lib/rdoc/parser/ruby.rb +++ /dev/null @@ -1,2381 +0,0 @@ -# frozen_string_literal: true -## -# This file contains stuff stolen outright from: -# -# rtags.rb - -# ruby-lex.rb - ruby lexcal analyzer -# ruby-token.rb - ruby tokens -# by Keiju ISHITSUKA (Nippon Rational Inc.) -# - -if ENV['RDOC_USE_PRISM_PARSER'] - require 'rdoc/parser/prism_ruby' - RDoc::Parser.const_set(:Ruby, RDoc::Parser::PrismRuby) - puts "=========================================================================" - puts "RDoc is using the experimental Prism parser to generate the documentation" - puts "=========================================================================" - return -end - -require 'ripper' -require_relative 'ripper_state_lex' - -## -# Extracts code elements from a source file returning a TopLevel object -# containing the constituent file elements. -# -# This file is based on rtags -# -# RubyParser understands how to document: -# * classes -# * modules -# * methods -# * constants -# * aliases -# * private, public, protected -# * private_class_function, public_class_function -# * private_constant, public_constant -# * module_function -# * attr, attr_reader, attr_writer, attr_accessor -# * extra accessors given on the command line -# * metaprogrammed methods -# * require -# * include -# -# == Method Arguments -# -#-- -# NOTE: I don't think this works, needs tests, remove the paragraph following -# this block when known to work -# -# The parser extracts the arguments from the method definition. You can -# override this with a custom argument definition using the :args: directive: -# -# ## -# # This method tries over and over until it is tired -# -# def go_go_go(thing_to_try, tries = 10) # :args: thing_to_try -# puts thing_to_try -# go_go_go thing_to_try, tries - 1 -# end -# -# If you have a more-complex set of overrides you can use the :call-seq: -# directive: -#++ -# -# The parser extracts the arguments from the method definition. You can -# override this with a custom argument definition using the :call-seq: -# directive: -# -# ## -# # This method can be called with a range or an offset and length -# # -# # :call-seq: -# # my_method(Range) -# # my_method(offset, length) -# -# def my_method(*args) -# end -# -# The parser extracts +yield+ expressions from method bodies to gather the -# yielded argument names. If your method manually calls a block instead of -# yielding or you want to override the discovered argument names use -# the :yields: directive: -# -# ## -# # My method is awesome -# -# def my_method(&block) # :yields: happy, times -# block.call 1, 2 -# end -# -# == Metaprogrammed Methods -# -# To pick up a metaprogrammed method, the parser looks for a comment starting -# with '##' before an identifier: -# -# ## -# # This is a meta-programmed method! -# -# add_my_method :meta_method, :arg1, :arg2 -# -# The parser looks at the token after the identifier to determine the name, in -# this example, :meta_method. If a name cannot be found, a warning is printed -# and 'unknown is used. -# -# You can force the name of a method using the :method: directive: -# -# ## -# # :method: some_method! -# -# By default, meta-methods are instance methods. To indicate that a method is -# a singleton method instead use the :singleton-method: directive: -# -# ## -# # :singleton-method: -# -# You can also use the :singleton-method: directive with a name: -# -# ## -# # :singleton-method: some_method! -# -# You can define arguments for metaprogrammed methods via either the -# :call-seq:, :arg: or :args: directives. -# -# Additionally you can mark a method as an attribute by -# using :attr:, :attr_reader:, :attr_writer: or :attr_accessor:. Just like -# for :method:, the name is optional. -# -# ## -# # :attr_reader: my_attr_name -# -# == Hidden methods and attributes -# -# You can provide documentation for methods that don't appear using -# the :method:, :singleton-method: and :attr: directives: -# -# ## -# # :attr_writer: ghost_writer -# # There is an attribute here, but you can't see it! -# -# ## -# # :method: ghost_method -# # There is a method here, but you can't see it! -# -# ## -# # this is a comment for a regular method -# -# def regular_method() end -# -# Note that by default, the :method: directive will be ignored if there is a -# standard rdocable item following it. - -class RDoc::Parser::Ruby < RDoc::Parser - - parse_files_matching(/\.rbw?$/) - - include RDoc::TokenStream - include RDoc::Parser::RubyTools - - ## - # RDoc::NormalClass type - - NORMAL = "::" - - ## - # RDoc::SingleClass type - - SINGLE = "<<" - - ## - # Creates a new Ruby parser. - - def initialize(top_level, file_name, content, options, stats) - super - - content = handle_tab_width(content) - - @size = 0 - @token_listeners = nil - content = RDoc::Encoding.remove_magic_comment content - @scanner = RDoc::Parser::RipperStateLex.parse(content) - @content = content - @scanner_point = 0 - @prev_seek = nil - @markup = @options.markup - @track_visibility = :nodoc != @options.visibility - @encoding = @options.encoding - - reset - end - - ## - # Return +true+ if +tk+ is a newline. - - def tk_nl?(tk) - :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] - end - - ## - # Retrieves the read token stream and replaces +pattern+ with +replacement+ - # using gsub. If the result is only a ";" returns an empty string. - - def get_tkread_clean pattern, replacement # :nodoc: - read = get_tkread.gsub(pattern, replacement).strip - return '' if read == ';' - read - end - - ## - # Extracts the visibility information for the visibility token +tk+ - # and +single+ class type identifier. - # - # Returns the visibility type (a string), the visibility (a symbol) and - # +singleton+ if the methods following should be converted to singleton - # methods. - - def get_visibility_information tk, single # :nodoc: - vis_type = tk[:text] - singleton = single == SINGLE - - vis = - case vis_type - when 'private' then :private - when 'protected' then :protected - when 'public' then :public - when 'private_class_method' then - singleton = true - :private - when 'public_class_method' then - singleton = true - :public - when 'module_function' then - singleton = true - :public - else - raise RDoc::Error, "Invalid visibility: #{tk.name}" - end - - return vis_type, vis, singleton - end - - ## - # Look for the first comment in a file that isn't a shebang line. - - def collect_first_comment - skip_tkspace - comment = ''.dup - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - first_line = true - first_comment_tk_kind = nil - line_no = nil - - tk = get_tk - - while tk && (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) - comment_body = retrieve_comment_body(tk) - if first_line and comment_body =~ /\A#!/ then - skip_tkspace - tk = get_tk - elsif first_line and comment_body =~ /\A#\s*-\*-/ then - first_line = false - skip_tkspace - tk = get_tk - else - break if first_comment_tk_kind and not first_comment_tk_kind === tk[:kind] - first_comment_tk_kind = tk[:kind] - - line_no = tk[:line_no] if first_line - first_line = false - comment << comment_body - tk = get_tk - - if :on_nl === tk then - skip_tkspace_without_nl - tk = get_tk - end - end - end - - unget_tk tk - - new_comment comment, line_no - end - - ## - # Consumes trailing whitespace from the token stream - - def consume_trailing_spaces # :nodoc: - skip_tkspace_without_nl - end - - ## - # Creates a new attribute in +container+ with +name+. - - def create_attr container, single, name, rw, comment # :nodoc: - att = RDoc::Attr.new get_tkread, name, rw, comment, single == SINGLE - record_location att - - container.add_attribute att - @stats.add_attribute att - - att - end - - ## - # Creates a module alias in +container+ at +rhs_name+ (or at the top-level - # for "::") with the name from +constant+. - - def create_module_alias container, constant, rhs_name # :nodoc: - mod = if rhs_name =~ /^::/ then - @store.find_class_or_module rhs_name - else - container.find_module_named rhs_name - end - - container.add_module_alias mod, rhs_name, constant, @top_level - end - - ## - # Aborts with +msg+ - - def error(msg) - msg = make_message msg - - abort msg - end - - ## - # Looks for a true or false token. - - def get_bool - skip_tkspace - tk = get_tk - if :on_kw == tk[:kind] && 'true' == tk[:text] - true - elsif :on_kw == tk[:kind] && ('false' == tk[:text] || 'nil' == tk[:text]) - false - else - unget_tk tk - true - end - end - - ## - # Look for the name of a class of module (optionally with a leading :: or - # with :: separated named) and return the ultimate name, the associated - # container, and the given name (with the ::). - - def get_class_or_module container, ignore_constants = false - skip_tkspace - name_t = get_tk - given_name = ''.dup - - # class ::A -> A is in the top level - if :on_op == name_t[:kind] and '::' == name_t[:text] then # bug - name_t = get_tk - container = @top_level - given_name << '::' - end - - skip_tkspace_without_nl - given_name << name_t[:text] - - is_self = name_t[:kind] == :on_op && name_t[:text] == '<<' - new_modules = [] - while !is_self && (tk = peek_tk) and :on_op == tk[:kind] and '::' == tk[:text] do - prev_container = container - container = container.find_module_named name_t[:text] - container ||= - if ignore_constants then - c = RDoc::NormalModule.new name_t[:text] - c.store = @store - new_modules << [prev_container, c] - c - else - c = prev_container.add_module RDoc::NormalModule, name_t[:text] - c.ignore unless prev_container.document_children - @top_level.add_to_classes_or_modules c - c - end - - record_location container - - get_tk - skip_tkspace - if :on_lparen == peek_tk[:kind] # ProcObjectInConstant::() - parse_method_or_yield_parameters - break - end - name_t = get_tk - unless :on_const == name_t[:kind] || :on_ident == name_t[:kind] - raise RDoc::Error, "Invalid class or module definition: #{given_name}" - end - if prev_container == container and !ignore_constants - given_name = name_t[:text] - else - given_name << '::' + name_t[:text] - end - end - - skip_tkspace_without_nl - - return [container, name_t, given_name, new_modules] - end - - ## - # Skip opening parentheses and yield the block. - # Skip closing parentheses too when exists. - - def skip_parentheses(&block) - left_tk = peek_tk - - if :on_lparen == left_tk[:kind] - get_tk - - ret = skip_parentheses(&block) - - right_tk = peek_tk - if :on_rparen == right_tk[:kind] - get_tk - end - - ret - else - yield - end - end - - ## - # Return a superclass, which can be either a constant of an expression - - def get_class_specification - tk = peek_tk - if tk.nil? - return '' - elsif :on_kw == tk[:kind] && 'self' == tk[:text] - return 'self' - elsif :on_gvar == tk[:kind] - return '' - end - - res = get_constant - - skip_tkspace_without_nl - - get_tkread # empty out read buffer - - tk = get_tk - return res unless tk - - case tk[:kind] - when :on_nl, :on_comment, :on_embdoc, :on_semicolon then - unget_tk(tk) - return res - end - - res += parse_call_parameters(tk) - res - end - - ## - # Parse a constant, which might be qualified by one or more class or module - # names - - def get_constant - res = "" - skip_tkspace_without_nl - tk = get_tk - - while tk && ((:on_op == tk[:kind] && '::' == tk[:text]) || :on_const == tk[:kind]) do - res += tk[:text] - tk = get_tk - end - - unget_tk(tk) - res - end - - ## - # Get an included module that may be surrounded by parens - - def get_included_module_with_optional_parens - skip_tkspace_without_nl - get_tkread - tk = get_tk - end_token = get_end_token tk - return '' unless end_token - - nest = 0 - continue = false - only_constant = true - - while tk != nil do - is_element_of_constant = false - case tk[:kind] - when :on_semicolon then - break if nest == 0 - when :on_lbracket then - nest += 1 - when :on_rbracket then - nest -= 1 - when :on_lbrace then - nest += 1 - when :on_rbrace then - nest -= 1 - if nest <= 0 - # we might have a.each { |i| yield i } - unget_tk(tk) if nest < 0 - break - end - when :on_lparen then - nest += 1 - when end_token[:kind] then - if end_token[:kind] == :on_rparen - nest -= 1 - break if nest <= 0 - else - break if nest <= 0 - end - when :on_rparen then - nest -= 1 - when :on_comment, :on_embdoc then - @read.pop - if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and - (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then - break if !continue and nest <= 0 - end - when :on_comma then - continue = true - when :on_ident then - continue = false if continue - when :on_kw then - case tk[:text] - when 'def', 'do', 'case', 'for', 'begin', 'class', 'module' - nest += 1 - when 'if', 'unless', 'while', 'until', 'rescue' - # postfix if/unless/while/until/rescue must be EXPR_LABEL - nest += 1 unless (tk[:state] & Ripper::EXPR_LABEL) != 0 - when 'end' - nest -= 1 - break if nest == 0 - end - when :on_const then - is_element_of_constant = true - when :on_op then - is_element_of_constant = true if '::' == tk[:text] - end - only_constant = false unless is_element_of_constant - tk = get_tk - end - - if only_constant - get_tkread_clean(/\s+/, ' ') - else - '' - end - end - - ## - # Little hack going on here. In the statement: - # - # f = 2*(1+yield) - # - # We see the RPAREN as the next token, so we need to exit early. This still - # won't catch all cases (such as "a = yield + 1" - - def get_end_token tk # :nodoc: - case tk[:kind] - when :on_lparen - token = RDoc::Parser::RipperStateLex::Token.new - token[:kind] = :on_rparen - token[:text] = ')' - token - when :on_rparen - nil - else - token = RDoc::Parser::RipperStateLex::Token.new - token[:kind] = :on_nl - token[:text] = "\n" - token - end - end - - ## - # Retrieves the method container for a singleton method. - - def get_method_container container, name_t # :nodoc: - prev_container = container - container = container.find_module_named(name_t[:text]) - - unless container then - constant = prev_container.constants.find do |const| - const.name == name_t[:text] - end - - if constant then - parse_method_dummy prev_container - return - end - end - - unless container then - # TODO seems broken, should starting at Object in @store - obj = name_t[:text].split("::").inject(Object) do |state, item| - state.const_get(item) - end rescue nil - - type = obj.class == Class ? RDoc::NormalClass : RDoc::NormalModule - - unless [Class, Module].include?(obj.class) then - warn("Couldn't find #{name_t[:text]}. Assuming it's a module") - end - - if type == RDoc::NormalClass then - sclass = obj.superclass ? obj.superclass.name : nil - container = prev_container.add_class type, name_t[:text], sclass - else - container = prev_container.add_module type, name_t[:text] - end - - record_location container - end - - container - end - - ## - # Extracts a name or symbol from the token stream. - - def get_symbol_or_name - tk = get_tk - case tk[:kind] - when :on_symbol then - text = tk[:text].sub(/^:/, '') - - next_tk = peek_tk - if next_tk && :on_op == next_tk[:kind] && '=' == next_tk[:text] then - get_tk - text << '=' - end - - text - when :on_ident, :on_const, :on_gvar, :on_cvar, :on_ivar, :on_op, :on_kw then - tk[:text] - when :on_tstring, :on_dstring then - tk[:text][1..-2] - else - raise RDoc::Error, "Name or symbol expected (got #{tk})" - end - end - - ## - # Marks containers between +container+ and +ancestor+ as ignored - - def suppress_parents container, ancestor # :nodoc: - while container and container != ancestor do - container.suppress unless container.documented? - container = container.parent - end - end - - ## - # Look for directives in a normal comment block: - # - # # :stopdoc: - # # Don't display comment from this point forward - # - # This routine modifies its +comment+ parameter. - - def look_for_directives_in container, comment - @preprocess.handle comment, container do |directive, param| - case directive - when 'method', 'singleton-method', - 'attr', 'attr_accessor', 'attr_reader', 'attr_writer' then - false # handled elsewhere - when 'section' then - break unless container.kind_of?(RDoc::Context) - container.set_current_section param, comment.dup - comment.text = '' - break - end - end - - comment.remove_private - end - - ## - # Adds useful info about the parser to +message+ - - def make_message message - prefix = "#{@file_name}:".dup - - tk = peek_tk - prefix << "#{tk[:line_no]}:#{tk[:char_no]}:" if tk - - "#{prefix} #{message}" - end - - ## - # Creates a comment with the correct format - - def new_comment comment, line_no = nil - c = RDoc::Comment.new comment, @top_level, :ruby - c.line = line_no - c.format = @markup - c - end - - ## - # Creates an RDoc::Attr for the name following +tk+, setting the comment to - # +comment+. - - def parse_attr(context, single, tk, comment) - line_no = tk[:line_no] - - args = parse_symbol_arg 1 - if args.size > 0 then - name = args[0] - rw = "R" - skip_tkspace_without_nl - tk = get_tk - - if :on_comma == tk[:kind] then - rw = "RW" if get_bool - else - unget_tk tk - end - - att = create_attr context, single, name, rw, comment - att.line = line_no - - read_documentation_modifiers att, RDoc::ATTR_MODIFIERS - else - warn "'attr' ignored - looks like a variable" - end - end - - ## - # Creates an RDoc::Attr for each attribute listed after +tk+, setting the - # comment for each to +comment+. - - def parse_attr_accessor(context, single, tk, comment) - line_no = tk[:line_no] - - args = parse_symbol_arg - rw = "?" - - tmp = RDoc::CodeObject.new - read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS - # TODO In most other places we let the context keep track of document_self - # and add found items appropriately but here we do not. I'm not sure why. - return if @track_visibility and not tmp.document_self - - case tk[:text] - when "attr_reader" then rw = "R" - when "attr_writer" then rw = "W" - when "attr_accessor" then rw = "RW" - else - rw = '?' - end - - for name in args - att = create_attr context, single, name, rw, comment - att.line = line_no - end - end - - ## - # Parses an +alias+ in +context+ with +comment+ - - def parse_alias(context, single, tk, comment) - line_no = tk[:line_no] - - skip_tkspace - - if :on_lparen === peek_tk[:kind] then - get_tk - skip_tkspace - end - - new_name = get_symbol_or_name - - skip_tkspace - if :on_comma === peek_tk[:kind] then - get_tk - skip_tkspace - end - - begin - old_name = get_symbol_or_name - rescue RDoc::Error - return - end - - al = RDoc::Alias.new(get_tkread, old_name, new_name, comment, - single == SINGLE) - record_location al - al.line = line_no - - read_documentation_modifiers al, RDoc::ATTR_MODIFIERS - if al.document_self or not @track_visibility - context.add_alias al - @stats.add_alias al - end - - al - end - - ## - # Extracts call parameters from the token stream. - - def parse_call_parameters(tk) - end_token = case tk[:kind] - when :on_lparen - :on_rparen - when :on_rparen - return "" - else - :on_nl - end - nest = 0 - - loop do - break if tk.nil? - case tk[:kind] - when :on_semicolon - break - when :on_lparen - nest += 1 - when end_token - if end_token == :on_rparen - nest -= 1 - break if RDoc::Parser::RipperStateLex.end?(tk) and nest <= 0 - else - break if RDoc::Parser::RipperStateLex.end?(tk) - end - when :on_comment, :on_embdoc - unget_tk(tk) - break - when :on_op - if tk[:text] =~ /^(.{1,2})?=$/ - unget_tk(tk) - break - end - end - tk = get_tk - end - - get_tkread_clean "\n", " " - end - - ## - # Parses a class in +context+ with +comment+ - - def parse_class container, single, tk, comment - line_no = tk[:line_no] - - declaration_context = container - container, name_t, given_name, = get_class_or_module container - - if name_t[:kind] == :on_const - cls = parse_class_regular container, declaration_context, single, - name_t, given_name, comment - elsif name_t[:kind] == :on_op && name_t[:text] == '<<' - case name = skip_parentheses { get_class_specification } - when 'self', container.name - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - parse_statements container, SINGLE - return # don't update line - else - cls = parse_class_singleton container, name, comment - end - else - warn "Expected class name or '<<'. Got #{name_t[:kind]}: #{name_t[:text].inspect}" - return - end - - cls.line = line_no - - # after end modifiers - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - - cls - end - - ## - # Parses and creates a regular class - - def parse_class_regular container, declaration_context, single, # :nodoc: - name_t, given_name, comment - superclass = '::Object' - - if given_name =~ /^::/ then - declaration_context = @top_level - given_name = $' - end - - tk = peek_tk - if tk[:kind] == :on_op && tk[:text] == '<' then - get_tk - skip_tkspace - superclass = get_class_specification - superclass = '(unknown)' if superclass.empty? - end - - cls_type = single == SINGLE ? RDoc::SingleClass : RDoc::NormalClass - cls = declaration_context.add_class cls_type, given_name, superclass - cls.ignore unless container.document_children - - read_documentation_modifiers cls, RDoc::CLASS_MODIFIERS - record_location cls - - cls.add_comment comment, @top_level - - @top_level.add_to_classes_or_modules cls - @stats.add_class cls - - suppress_parents container, declaration_context unless cls.document_self - - parse_statements cls - - cls - end - - ## - # Parses a singleton class in +container+ with the given +name+ and - # +comment+. - - def parse_class_singleton container, name, comment # :nodoc: - other = @store.find_class_named name - - unless other then - if name =~ /^::/ then - name = $' - container = @top_level - end - - other = container.add_module RDoc::NormalModule, name - record_location other - - # class << $gvar - other.ignore if name.empty? - - other.add_comment comment, @top_level - end - - # notify :nodoc: all if not a constant-named class/module - # (and remove any comment) - unless name =~ /\A(::)?[A-Z]/ then - other.document_self = nil - other.document_children = false - other.clear_comment - end - - @top_level.add_to_classes_or_modules other - @stats.add_class other - - read_documentation_modifiers other, RDoc::CLASS_MODIFIERS - parse_statements(other, SINGLE) - - other - end - - ## - # Parses a constant in +context+ with +comment+. If +ignore_constants+ is - # true, no found constants will be added to RDoc. - - def parse_constant container, tk, comment, ignore_constants = false - line_no = tk[:line_no] - - name = tk[:text] - skip_tkspace_without_nl - - return unless name =~ /^\w+$/ - - new_modules = [] - if :on_op == peek_tk[:kind] && '::' == peek_tk[:text] then - unget_tk tk - - container, name_t, _, new_modules = get_class_or_module container, true - - name = name_t[:text] - end - - is_array_or_hash = false - if peek_tk && :on_lbracket == peek_tk[:kind] - get_tk - nest = 1 - while bracket_tk = get_tk - case bracket_tk[:kind] - when :on_lbracket - nest += 1 - when :on_rbracket - nest -= 1 - break if nest == 0 - end - end - skip_tkspace_without_nl - is_array_or_hash = true - end - - unless peek_tk && :on_op == peek_tk[:kind] && '=' == peek_tk[:text] then - return false - end - get_tk - - unless ignore_constants - new_modules.each do |prev_c, new_module| - prev_c.add_module_by_normal_module new_module - new_module.ignore unless prev_c.document_children - @top_level.add_to_classes_or_modules new_module - end - end - - value = '' - con = RDoc::Constant.new name, value, comment - - body = parse_constant_body container, con, is_array_or_hash - - return unless body - - con.value = body - record_location con - con.line = line_no - read_documentation_modifiers con, RDoc::CONSTANT_MODIFIERS - - return if is_array_or_hash - - @stats.add_constant con - container.add_constant con - - true - end - - def parse_constant_body container, constant, is_array_or_hash # :nodoc: - nest = 0 - rhs_name = ''.dup - - get_tkread - - tk = get_tk - - body = nil - loop do - break if tk.nil? - if :on_semicolon == tk[:kind] then - break if nest <= 0 - elsif [:on_tlambeg, :on_lparen, :on_lbrace, :on_lbracket].include?(tk[:kind]) then - nest += 1 - elsif (:on_kw == tk[:kind] && 'def' == tk[:text]) then - nest += 1 - elsif (:on_kw == tk[:kind] && %w{do if unless case begin}.include?(tk[:text])) then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - end - elsif [:on_rparen, :on_rbrace, :on_rbracket].include?(tk[:kind]) || - (:on_kw == tk[:kind] && 'end' == tk[:text]) then - nest -= 1 - elsif (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then - unget_tk tk - if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then - body = get_tkread_clean(/^[ \t]+/, '') - read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS - break - else - read_documentation_modifiers constant, RDoc::CONSTANT_MODIFIERS - end - elsif :on_const == tk[:kind] then - rhs_name << tk[:text] - - next_tk = peek_tk - if nest <= 0 and (next_tk.nil? || :on_nl == next_tk[:kind]) then - create_module_alias container, constant, rhs_name unless is_array_or_hash - break - end - elsif :on_nl == tk[:kind] then - if nest <= 0 and RDoc::Parser::RipperStateLex.end?(tk) then - unget_tk tk - break - end - elsif :on_op == tk[:kind] && '::' == tk[:text] - rhs_name << '::' - end - tk = get_tk - end - - body ? body : get_tkread_clean(/^[ \t]+/, '') - end - - ## - # Generates an RDoc::Method or RDoc::Attr from +comment+ by looking for - # :method: or :attr: directives in +comment+. - - def parse_comment container, tk, comment - return parse_comment_tomdoc container, tk, comment if @markup == 'tomdoc' - column = tk[:char_no] - line_no = comment.line.nil? ? tk[:line_no] : comment.line - - comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') - singleton = !!$~ - - co = - if (comment.text = comment.text.sub(/^# +:?method: *(\S*).*?\n/i, '')) && !!$~ then - line_no += $`.count("\n") - parse_comment_ghost container, comment.text, $1, column, line_no, comment - elsif (comment.text = comment.text.sub(/# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i, '')) && !!$~ then - parse_comment_attr container, $1, $3, comment - end - - if co then - co.singleton = singleton - co.line = line_no - end - - true - end - - ## - # Parse a comment that is describing an attribute in +container+ with the - # given +name+ and +comment+. - - def parse_comment_attr container, type, name, comment # :nodoc: - return if name.empty? - - rw = case type - when 'attr_reader' then 'R' - when 'attr_writer' then 'W' - else 'RW' - end - - create_attr container, NORMAL, name, rw, comment - end - - def parse_comment_ghost container, text, name, column, line_no, # :nodoc: - comment - name = nil if name.empty? - - meth = RDoc::GhostMethod.new get_tkread, name - record_location meth - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - - meth.params = - if text.sub!(/^#\s+:?args?:\s*(.*?)\s*$/i, '') then - $1 - else - '' - end - - comment.normalize - comment.extract_call_seq meth - - return unless meth.name - - container.add_method meth - - meth.comment = comment - - @stats.add_method meth - - meth - end - - ## - # Creates an RDoc::Method on +container+ from +comment+ if there is a - # Signature section in the comment - - def parse_comment_tomdoc container, tk, comment - return unless signature = RDoc::TomDoc.signature(comment) - column = tk[:char_no] - line_no = tk[:line_no] - - name, = signature.split %r%[ \(]%, 2 - - meth = RDoc::GhostMethod.new get_tkread, name - record_location meth - meth.line = line_no - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - - meth.call_seq = signature - - comment.normalize - - return unless meth.name - - container.add_method meth - - meth.comment = comment - - @stats.add_method meth - end - - ## - # Parses an +include+ or +extend+, indicated by the +klass+ and adds it to - # +container+ # with +comment+ - - def parse_extend_or_include klass, container, comment # :nodoc: - loop do - skip_tkspace_comment - - name = get_included_module_with_optional_parens - - unless name.empty? then - obj = container.add klass, name, comment - record_location obj - end - - return if peek_tk.nil? || :on_comma != peek_tk[:kind] - - get_tk - end - end - - ## - # Parses an +included+ with a block feature of ActiveSupport::Concern. - - def parse_included_with_activesupport_concern container, comment # :nodoc: - skip_tkspace_without_nl - tk = get_tk - unless tk[:kind] == :on_lbracket || (tk[:kind] == :on_kw && tk[:text] == 'do') - unget_tk tk - return nil # should be a block - end - - parse_statements container - - container - end - - ## - # Parses identifiers that can create new methods or change visibility. - # - # Returns true if the comment was not consumed. - - def parse_identifier container, single, tk, comment # :nodoc: - case tk[:text] - when 'private', 'protected', 'public', 'private_class_method', - 'public_class_method', 'module_function' then - parse_visibility container, single, tk - return true - when 'private_constant', 'public_constant' - parse_constant_visibility container, single, tk - return true - when 'attr' then - parse_attr container, single, tk, comment - when /^attr_(reader|writer|accessor)$/ then - parse_attr_accessor container, single, tk, comment - when 'alias_method' then - parse_alias container, single, tk, comment - when 'require', 'include' then - # ignore - else - if comment.text =~ /\A#\#$/ then - case comment.text - when /^# +:?attr(_reader|_writer|_accessor)?:/ then - parse_meta_attr container, single, tk, comment - else - method = parse_meta_method container, single, tk, comment - method.params = container.params if - container.params - method.block_params = container.block_params if - container.block_params - end - end - end - - false - end - - ## - # Parses a meta-programmed attribute and creates an RDoc::Attr. - # - # To create foo and bar attributes on class C with comment "My attributes": - # - # class C - # - # ## - # # :attr: - # # - # # My attributes - # - # my_attr :foo, :bar - # - # end - # - # To create a foo attribute on class C with comment "My attribute": - # - # class C - # - # ## - # # :attr: foo - # # - # # My attribute - # - # my_attr :foo, :bar - # - # end - - def parse_meta_attr(context, single, tk, comment) - args = parse_symbol_arg - rw = "?" - - # If nodoc is given, don't document any of them - - tmp = RDoc::CodeObject.new - read_documentation_modifiers tmp, RDoc::ATTR_MODIFIERS - - regexp = /^# +:?(attr(_reader|_writer|_accessor)?): *(\S*).*?\n/i - if regexp =~ comment.text then - comment.text = comment.text.sub(regexp, '') - rw = case $1 - when 'attr_reader' then 'R' - when 'attr_writer' then 'W' - else 'RW' - end - name = $3 unless $3.empty? - end - - if name then - att = create_attr context, single, name, rw, comment - else - args.each do |attr_name| - att = create_attr context, single, attr_name, rw, comment - end - end - - att - end - - ## - # Parses a meta-programmed method - - def parse_meta_method(container, single, tk, comment) - column = tk[:char_no] - line_no = tk[:line_no] - - start_collecting_tokens - add_token tk - add_token_listener self - - skip_tkspace_without_nl - - comment.text = comment.text.sub(/(^# +:?)(singleton-)(method:)/, '\1\3') - singleton = !!$~ - - name = parse_meta_method_name comment, tk - - return unless name - - meth = RDoc::MetaMethod.new get_tkread, name - record_location meth - meth.line = line_no - meth.singleton = singleton - - remove_token_listener self - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - position_comment = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - position_comment[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [position_comment, newline, indent] - meth.add_tokens @token_stream - - parse_meta_method_params container, single, meth, tk, comment - - meth.comment = comment - - @stats.add_method meth - - meth - end - - ## - # Parses the name of a metaprogrammed method. +comment+ is used to - # determine the name while +tk+ is used in an error message if the name - # cannot be determined. - - def parse_meta_method_name comment, tk # :nodoc: - if comment.text.sub!(/^# +:?method: *(\S*).*?\n/i, '') then - return $1 unless $1.empty? - end - - name_t = get_tk - - if :on_symbol == name_t[:kind] then - name_t[:text][1..-1] - elsif :on_tstring == name_t[:kind] then - name_t[:text][1..-2] - elsif :on_op == name_t[:kind] && '=' == name_t[:text] then # ignore - remove_token_listener self - - nil - else - warn "unknown name token #{name_t.inspect} for meta-method '#{tk[:text]}'" - 'unknown' - end - end - - ## - # Parses the parameters and block for a meta-programmed method. - - def parse_meta_method_params container, single, meth, tk, comment # :nodoc: - token_listener meth do - meth.params = '' - - look_for_directives_in meth, comment - comment.normalize - comment.extract_call_seq meth - - container.add_method meth - - last_tk = tk - - while tk = get_tk do - if :on_semicolon == tk[:kind] then - break - elsif :on_nl == tk[:kind] then - break unless last_tk and :on_comma == last_tk[:kind] - elsif :on_sp == tk[:kind] then - # expression continues - elsif :on_kw == tk[:kind] && 'do' == tk[:text] then - parse_statements container, single, meth - break - else - last_tk = tk - end - end - end - end - - ## - # Parses a normal method defined by +def+ - - def parse_method(container, single, tk, comment) - singleton = nil - added_container = false - name = nil - column = tk[:char_no] - line_no = tk[:line_no] - - start_collecting_tokens - add_token tk - - token_listener self do - prev_container = container - name, container, singleton = parse_method_name container - added_container = container != prev_container - end - - return unless name - - meth = RDoc::AnyMethod.new get_tkread, name - look_for_directives_in meth, comment - meth.singleton = single == SINGLE ? true : singleton - if singleton - # `current_line_visibility' is useless because it works against - # the normal method named as same as the singleton method, after - # the latter was defined. Of course these are different things. - container.current_line_visibility = :public - end - - record_location meth - meth.line = line_no - - meth.start_collecting_tokens - indent = RDoc::Parser::RipperStateLex::Token.new(1, 1, :on_sp, ' ' * column) - token = RDoc::Parser::RipperStateLex::Token.new(line_no, 1, :on_comment) - token[:text] = "# File #{@top_level.relative_name}, line #{line_no}" - newline = RDoc::Parser::RipperStateLex::Token.new(0, 0, :on_nl, "\n") - meth.add_tokens [token, newline, indent] - meth.add_tokens @token_stream - - parse_method_params_and_body container, single, meth, added_container - - comment.normalize - comment.extract_call_seq meth - - meth.comment = comment - - # after end modifiers - read_documentation_modifiers meth, RDoc::METHOD_MODIFIERS - - @stats.add_method meth - end - - ## - # Parses the parameters and body of +meth+ - - def parse_method_params_and_body container, single, meth, added_container - token_listener meth do - parse_method_parameters meth - - if meth.document_self or not @track_visibility then - container.add_method meth - elsif added_container then - container.document_self = false - end - - # Having now read the method parameters and documentation modifiers, we - # now know whether we have to rename #initialize to ::new - - if meth.name == "initialize" && !meth.singleton then - if meth.dont_rename_initialize then - meth.visibility = :protected - else - meth.singleton = true - meth.name = "new" - meth.visibility = :public - end - end - - parse_statements container, single, meth - end - end - - ## - # Parses a method that needs to be ignored. - - def parse_method_dummy container - dummy = RDoc::Context.new - dummy.parent = container - dummy.store = container.store - skip_method dummy - end - - ## - # Parses the name of a method in +container+. - # - # Returns the method name, the container it is in (for def Foo.name) and if - # it is a singleton or regular method. - - def parse_method_name container # :nodoc: - skip_tkspace - name_t = get_tk - back_tk = skip_tkspace_without_nl - singleton = false - - dot = get_tk - if dot[:kind] == :on_period || (dot[:kind] == :on_op && dot[:text] == '::') then - singleton = true - - name, container = parse_method_name_singleton container, name_t - else - unget_tk dot - back_tk.reverse_each do |token| - unget_tk token - end - - name = parse_method_name_regular container, name_t - end - - return name, container, singleton - end - - ## - # For the given +container+ and initial name token +name_t+ the method name - # is parsed from the token stream for a regular method. - - def parse_method_name_regular container, name_t # :nodoc: - if :on_op == name_t[:kind] && (%w{* & [] []= <<}.include?(name_t[:text])) then - name_t[:text] - else - unless [:on_kw, :on_const, :on_ident].include?(name_t[:kind]) then - warn "expected method name token, . or ::, got #{name_t.inspect}" - skip_method container - return - end - name_t[:text] - end - end - - ## - # For the given +container+ and initial name token +name_t+ the method name - # and the new +container+ (if necessary) are parsed from the token stream - # for a singleton method. - - def parse_method_name_singleton container, name_t # :nodoc: - skip_tkspace - name_t2 = get_tk - - if (:on_kw == name_t[:kind] && 'self' == name_t[:text]) || (:on_op == name_t[:kind] && '%' == name_t[:text]) then - # NOTE: work around '[' being consumed early - if :on_lbracket == name_t2[:kind] - get_tk - name = '[]' - else - name = name_t2[:text] - end - elsif :on_const == name_t[:kind] then - name = name_t2[:text] - - container = get_method_container container, name_t - - return unless container - - name - elsif :on_ident == name_t[:kind] || :on_ivar == name_t[:kind] || :on_gvar == name_t[:kind] then - parse_method_dummy container - - name = nil - elsif (:on_kw == name_t[:kind]) && ('true' == name_t[:text] || 'false' == name_t[:text] || 'nil' == name_t[:text]) then - klass_name = "#{name_t[:text].capitalize}Class" - container = @store.find_class_named klass_name - container ||= @top_level.add_class RDoc::NormalClass, klass_name - - name = name_t2[:text] - else - warn "unexpected method name token #{name_t.inspect}" - # break - skip_method container - - name = nil - end - - return name, container - end - - ## - # Extracts +yield+ parameters from +method+ - - def parse_method_or_yield_parameters(method = nil, - modifiers = RDoc::METHOD_MODIFIERS) - skip_tkspace_without_nl - tk = get_tk - end_token = get_end_token tk - return '' unless end_token - - nest = 0 - continue = false - - while tk != nil do - case tk[:kind] - when :on_semicolon then - break if nest == 0 - when :on_lbracket then - nest += 1 - when :on_rbracket then - nest -= 1 - when :on_lbrace then - nest += 1 - when :on_rbrace then - nest -= 1 - if nest <= 0 - # we might have a.each { |i| yield i } - unget_tk(tk) if nest < 0 - break - end - when :on_lparen then - nest += 1 - when end_token[:kind] then - if end_token[:kind] == :on_rparen - nest -= 1 - break if nest <= 0 - else - break - end - when :on_rparen then - nest -= 1 - when :on_comment, :on_embdoc then - @read.pop - if :on_nl == end_token[:kind] and "\n" == tk[:text][-1] and - (!continue or (tk[:state] & Ripper::EXPR_LABEL) != 0) then - if method && method.block_params.nil? then - unget_tk tk - read_documentation_modifiers method, modifiers - end - break if !continue and nest <= 0 - end - when :on_comma then - continue = true - when :on_ident then - continue = false if continue - end - tk = get_tk - end - - get_tkread_clean(/\s+/, ' ') - end - - ## - # Capture the method's parameters. Along the way, look for a comment - # containing: - # - # # yields: .... - # - # and add this as the block_params for the method - - def parse_method_parameters method - res = parse_method_or_yield_parameters method - - res = "(#{res})" unless res =~ /\A\(/ - method.params = res unless method.params - - return if method.block_params - - skip_tkspace_without_nl - read_documentation_modifiers method, RDoc::METHOD_MODIFIERS - end - - ## - # Parses an RDoc::NormalModule in +container+ with +comment+ - - def parse_module container, single, tk, comment - container, name_t, = get_class_or_module container - - name = name_t[:text] - - mod = container.add_module RDoc::NormalModule, name - mod.ignore unless container.document_children - record_location mod - - read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS - mod.add_comment comment, @top_level - parse_statements mod - - # after end modifiers - read_documentation_modifiers mod, RDoc::CLASS_MODIFIERS - - @stats.add_module mod - end - - ## - # Parses an RDoc::Require in +context+ containing +comment+ - - def parse_require(context, comment) - skip_tkspace_comment - tk = get_tk - - if :on_lparen == tk[:kind] then - skip_tkspace_comment - tk = get_tk - end - - name = tk[:text][1..-2] if :on_tstring == tk[:kind] - - if name then - @top_level.add_require RDoc::Require.new(name, comment) - else - unget_tk tk - end - end - - ## - # Parses a rescue - - def parse_rescue - skip_tkspace_without_nl - - while tk = get_tk - case tk[:kind] - when :on_nl, :on_semicolon, :on_comment then - break - when :on_comma then - skip_tkspace_without_nl - - get_tk if :on_nl == peek_tk[:kind] - end - - skip_tkspace_without_nl - end - end - - ## - # Retrieve comment body without =begin/=end - - def retrieve_comment_body(tk) - if :on_embdoc == tk[:kind] - tk[:text].gsub(/\A=begin.*\n/, '').gsub(/=end\n?\z/, '') - else - tk[:text] - end - end - - ## - # The core of the Ruby parser. - - def parse_statements(container, single = NORMAL, current_method = nil, - comment = new_comment('')) - raise 'no' unless RDoc::Comment === comment - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - - nest = 1 - save_visibility = container.visibility - container.visibility = :public unless current_method - - non_comment_seen = true - - while tk = get_tk do - keep_comment = false - try_parse_comment = false - - non_comment_seen = true unless (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) - - case tk[:kind] - when :on_nl, :on_ignored_nl, :on_comment, :on_embdoc then - if :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind] - skip_tkspace - tk = get_tk - else - past_tokens = @read.size > 1 ? @read[0..-2] : [] - nl_position = 0 - past_tokens.reverse.each_with_index do |read_tk, i| - if read_tk =~ /^\n$/ then - nl_position = (past_tokens.size - 1) - i - break - elsif read_tk =~ /^#.*\n$/ then - nl_position = ((past_tokens.size - 1) - i) + 1 - break - end - end - comment_only_line = past_tokens[nl_position..-1].all?{ |c| c =~ /^\s+$/ } - unless comment_only_line then - tk = get_tk - end - end - - if tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) then - if non_comment_seen then - # Look for RDoc in a comment about to be thrown away - non_comment_seen = parse_comment container, tk, comment unless - comment.empty? - - comment = '' - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - end - - line_no = nil - while tk and (:on_comment == tk[:kind] or :on_embdoc == tk[:kind]) do - comment_body = retrieve_comment_body(tk) - line_no = tk[:line_no] if comment.empty? - comment += comment_body - comment << "\n" unless comment_body =~ /\n\z/ - - if comment_body.size > 1 && comment_body =~ /\n\z/ then - skip_tkspace_without_nl # leading spaces - end - tk = get_tk - end - - comment = new_comment comment, line_no - - unless comment.empty? then - look_for_directives_in container, comment - - if container.done_documenting then - throw :eof if RDoc::TopLevel === container - container.ongoing_visibility = save_visibility - end - end - - keep_comment = true - else - non_comment_seen = true - end - - unget_tk tk - keep_comment = true - container.current_line_visibility = nil - - when :on_kw then - case tk[:text] - when 'class' then - parse_class container, single, tk, comment - - when 'module' then - parse_module container, single, tk, comment - - when 'def' then - parse_method container, single, tk, comment - - when 'alias' then - parse_alias container, single, tk, comment unless current_method - - when 'yield' then - if current_method.nil? then - warn "Warning: yield outside of method" if container.document_self - else - parse_yield container, single, tk, current_method - end - - when 'until', 'while' then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - skip_optional_do_after_expression - end - - # Until and While can have a 'do', which shouldn't increase the nesting. - # We can't solve the general case, but we can handle most occurrences by - # ignoring a do at the end of a line. - - # 'for' is trickier - when 'for' then - nest += 1 - skip_for_variable - skip_optional_do_after_expression - - when 'case', 'do', 'if', 'unless', 'begin' then - if (tk[:state] & Ripper::EXPR_LABEL) == 0 - nest += 1 - end - - when 'super' then - current_method.calls_super = true if current_method - - when 'rescue' then - parse_rescue - - when 'end' then - nest -= 1 - if nest == 0 then - container.ongoing_visibility = save_visibility - - parse_comment container, tk, comment unless comment.empty? - - return - end - end - - when :on_const then - unless parse_constant container, tk, comment, current_method then - try_parse_comment = true - end - - when :on_ident then - if nest == 1 and current_method.nil? then - keep_comment = parse_identifier container, single, tk, comment - end - - case tk[:text] - when "require" then - parse_require container, comment - when "include" then - parse_extend_or_include RDoc::Include, container, comment - when "extend" then - parse_extend_or_include RDoc::Extend, container, comment - when "included" then - parse_included_with_activesupport_concern container, comment - end - - else - try_parse_comment = nest == 1 - end - - if try_parse_comment then - non_comment_seen = parse_comment container, tk, comment unless - comment.empty? - - keep_comment = false - end - - unless keep_comment then - comment = new_comment '' - comment = RDoc::Encoding.change_encoding comment, @encoding if @encoding - container.params = nil - container.block_params = nil - end - - consume_trailing_spaces - end - - container.params = nil - container.block_params = nil - end - - ## - # Parse up to +no+ symbol arguments - - def parse_symbol_arg(no = nil) - skip_tkspace_comment - - tk = get_tk - if tk[:kind] == :on_lparen - parse_symbol_arg_paren no - else - parse_symbol_arg_space no, tk - end - end - - ## - # Parses up to +no+ symbol arguments surrounded by () and places them in - # +args+. - - def parse_symbol_arg_paren no # :nodoc: - args = [] - - loop do - skip_tkspace_comment - if tk1 = parse_symbol_in_arg - args.push tk1 - break if no and args.size >= no - end - - skip_tkspace_comment - case (tk2 = get_tk)[:kind] - when :on_rparen - break - when :on_comma - else - warn("unexpected token: '#{tk2.inspect}'") if $DEBUG_RDOC - break - end - end - - args - end - - ## - # Parses up to +no+ symbol arguments separated by spaces and places them in - # +args+. - - def parse_symbol_arg_space no, tk # :nodoc: - args = [] - - unget_tk tk - if tk = parse_symbol_in_arg - args.push tk - return args if no and args.size >= no - end - - loop do - skip_tkspace_without_nl - - tk1 = get_tk - if tk1.nil? || :on_comma != tk1[:kind] then - unget_tk tk1 - break - end - - skip_tkspace_comment - if tk = parse_symbol_in_arg - args.push tk - break if no and args.size >= no - end - end - - args - end - - ## - # Returns symbol text from the next token - - def parse_symbol_in_arg - tk = get_tk - if :on_symbol == tk[:kind] then - tk[:text].sub(/^:/, '') - elsif :on_tstring == tk[:kind] then - tk[:text][1..-2] - elsif :on_dstring == tk[:kind] or :on_ident == tk[:kind] then - nil # ignore - else - warn("Expected symbol or string, got #{tk.inspect}") if $DEBUG_RDOC - nil - end - end - - ## - # Parses statements in the top-level +container+ - - def parse_top_level_statements container - comment = collect_first_comment - - look_for_directives_in container, comment - - throw :eof if container.done_documenting - - @markup = comment.format - - # HACK move if to RDoc::Context#comment= - container.comment = comment if container.document_self unless comment.empty? - - parse_statements container, NORMAL, nil, comment - end - - ## - # Determines the visibility in +container+ from +tk+ - - def parse_visibility(container, single, tk) - vis_type, vis, singleton = get_visibility_information tk, single - - skip_tkspace_comment false - - ptk = peek_tk - # Ryan Davis suggested the extension to ignore modifiers, because he - # often writes - # - # protected unless $TESTING - # - if [:on_nl, :on_semicolon].include?(ptk[:kind]) || (:on_kw == ptk[:kind] && (['if', 'unless'].include?(ptk[:text]))) then - container.ongoing_visibility = vis - elsif :on_kw == ptk[:kind] && 'def' == ptk[:text] - container.current_line_visibility = vis - else - update_visibility container, vis_type, vis, singleton - end - end - - ## - # Parses a Module#private_constant or Module#public_constant call from +tk+. - - def parse_constant_visibility(container, single, tk) - args = parse_symbol_arg - case tk[:text] - when 'private_constant' - vis = :private - when 'public_constant' - vis = :public - else - raise RDoc::Error, 'Unreachable' - end - container.set_constant_visibility_for args, vis - end - - ## - # Determines the block parameter for +context+ - - def parse_yield(context, single, tk, method) - return if method.block_params - - get_tkread - method.block_params = parse_method_or_yield_parameters - end - - ## - # Directives are modifier comments that can appear after class, module, or - # method names. For example: - # - # def fred # :yields: a, b - # - # or: - # - # class MyClass # :nodoc: - # - # We return the directive name and any parameters as a two element array if - # the name is in +allowed+. A directive can be found anywhere up to the end - # of the current line. - - def read_directive allowed - tokens = [] - - while tk = get_tk do - tokens << tk - - if :on_nl == tk[:kind] or (:on_kw == tk[:kind] && 'def' == tk[:text]) then - return - elsif :on_comment == tk[:kind] or :on_embdoc == tk[:kind] then - return unless tk[:text] =~ /:?\b([\w-]+):\s*(.*)/ - - directive = $1.downcase - - return [directive, $2] if allowed.include? directive - - return - end - end - ensure - unless tokens.length == 1 and (:on_comment == tokens.first[:kind] or :on_embdoc == tokens.first[:kind]) then - tokens.reverse_each do |token| - unget_tk token - end - end - end - - ## - # Handles directives following the definition for +context+ (any - # RDoc::CodeObject) if the directives are +allowed+ at this point. - # - # See also RDoc::Markup::PreProcess#handle_directive - - def read_documentation_modifiers context, allowed - skip_tkspace_without_nl - directive, value = read_directive allowed - - return unless directive - - @preprocess.handle_directive '', directive, value, context do |dir, param| - if %w[notnew not_new not-new].include? dir then - context.dont_rename_initialize = true - - true - end - end - end - - ## - # Records the location of this +container+ in the file for this parser and - # adds it to the list of classes and modules in the file. - - def record_location container # :nodoc: - case container - when RDoc::ClassModule then - @top_level.add_to_classes_or_modules container - end - - container.record_location @top_level - end - - ## - # Scans this Ruby file for Ruby constructs - - def scan - reset - - catch :eof do - begin - parse_top_level_statements @top_level - - rescue StandardError => e - if @content.include?('<%') and @content.include?('%>') then - # Maybe, this is ERB. - $stderr.puts "\033[2KRDoc detects ERB file. Skips it for compatibility:" - $stderr.puts @file_name - return - end - - if @scanner_point >= @scanner.size - now_line_no = @scanner[@scanner.size - 1][:line_no] - else - now_line_no = peek_tk[:line_no] - end - first_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no } - last_tk_index = @scanner.find_index { |tk| tk[:line_no] == now_line_no + 1 } - last_tk_index = last_tk_index ? last_tk_index - 1 : @scanner.size - 1 - code = @scanner[first_tk_index..last_tk_index].map{ |t| t[:text] }.join - - $stderr.puts <<-EOF - -#{self.class} failure around line #{now_line_no} of -#{@file_name} - - EOF - - unless code.empty? then - $stderr.puts code - $stderr.puts - end - - raise e - end - end - - @top_level - end - - ## - # while, until, and for have an optional do - - def skip_optional_do_after_expression - skip_tkspace_without_nl - tk = get_tk - - b_nest = 0 - nest = 0 - - loop do - break unless tk - case tk[:kind] - when :on_semicolon, :on_nl, :on_ignored_nl then - break if b_nest.zero? - when :on_lparen then - nest += 1 - when :on_rparen then - nest -= 1 - when :on_kw then - case tk[:text] - when 'begin' - b_nest += 1 - when 'end' - b_nest -= 1 - when 'do' - break if nest.zero? - end - when :on_comment, :on_embdoc then - if b_nest.zero? and "\n" == tk[:text][-1] then - break - end - end - tk = get_tk - end - - skip_tkspace_without_nl - - get_tk if peek_tk && :on_kw == peek_tk[:kind] && 'do' == peek_tk[:text] - end - - ## - # skip the var [in] part of a 'for' statement - - def skip_for_variable - skip_tkspace_without_nl - get_tk - skip_tkspace_without_nl - tk = get_tk - unget_tk(tk) unless :on_kw == tk[:kind] and 'in' == tk[:text] - end - - ## - # Skips the next method in +container+ - - def skip_method container - meth = RDoc::AnyMethod.new "", "anon" - parse_method_parameters meth - parse_statements container, false, meth - end - - ## - # Skip spaces until a comment is found - - def skip_tkspace_comment(skip_nl = true) - loop do - skip_nl ? skip_tkspace : skip_tkspace_without_nl - next_tk = peek_tk - return if next_tk.nil? || (:on_comment != next_tk[:kind] and :on_embdoc != next_tk[:kind]) - get_tk - end - end - - ## - # Updates visibility in +container+ from +vis_type+ and +vis+. - - def update_visibility container, vis_type, vis, singleton # :nodoc: - new_methods = [] - - case vis_type - when 'module_function' then - args = parse_symbol_arg - container.set_visibility_for args, :private, false - - container.methods_matching args do |m| - s_m = m.dup - record_location s_m - s_m.singleton = true - new_methods << s_m - end - when 'public_class_method', 'private_class_method' then - args = parse_symbol_arg - - container.methods_matching args, true do |m| - if m.parent != container then - m = m.dup - record_location m - new_methods << m - end - - m.visibility = vis - end - else - args = parse_symbol_arg - container.set_visibility_for args, vis, singleton - end - - new_methods.each do |method| - case method - when RDoc::AnyMethod then - container.add_method method - when RDoc::Attr then - container.add_attribute method - end - method.visibility = vis - end - end - - ## - # Prints +message+ to +$stderr+ unless we're being quiet - - def warn message - @options.warn make_message message - end - -end diff --git a/lib/rdoc/parser/ruby_tools.rb b/lib/rdoc/parser/ruby_tools.rb deleted file mode 100644 index 40ea517c4d..0000000000 --- a/lib/rdoc/parser/ruby_tools.rb +++ /dev/null @@ -1,165 +0,0 @@ -# frozen_string_literal: true -## -# Collection of methods for writing parsers - -module RDoc::Parser::RubyTools - - ## - # Adds a token listener +obj+, but you should probably use token_listener - - def add_token_listener(obj) - @token_listeners ||= [] - @token_listeners << obj - end - - ## - # Fetches the next token from the scanner - - def get_tk - tk = nil - - if @tokens.empty? then - if @scanner_point >= @scanner.size - return nil - else - tk = @scanner[@scanner_point] - @scanner_point += 1 - @read.push tk[:text] - end - else - @read.push @unget_read.shift - tk = @tokens.shift - end - - if tk == nil || :on___end__ == tk[:kind] - tk = nil - end - - return nil unless tk - - # inform any listeners of our shiny new token - @token_listeners.each do |obj| - obj.add_token(tk) - end if @token_listeners - - tk - end - - ## - # Reads and returns all tokens up to one of +tokens+. Leaves the matched - # token in the token list. - - def get_tk_until(*tokens) - read = [] - - loop do - tk = get_tk - - case tk - when *tokens then - unget_tk tk - break - end - - read << tk - end - - read - end - - ## - # Retrieves a String representation of the read tokens - - def get_tkread - read = @read.join("") - @read = [] - read - end - - ## - # Peek equivalent for get_tkread - - def peek_read - @read.join('') - end - - ## - # Peek at the next token, but don't remove it from the stream - - def peek_tk - unget_tk(tk = get_tk) - tk - end - - ## - # Removes the token listener +obj+ - - def remove_token_listener(obj) - @token_listeners.delete(obj) - end - - ## - # Resets the tools - - def reset - @read = [] - @tokens = [] - @unget_read = [] - @nest = 0 - @scanner_point = 0 - end - - ## - # Skips whitespace tokens including newlines - - def skip_tkspace - tokens = [] - - while (tk = get_tk) and (:on_sp == tk[:kind] or :on_nl == tk[:kind] or :on_ignored_nl == tk[:kind]) do - tokens.push(tk) - end - - unget_tk(tk) - tokens - end - - ## - # Skips whitespace tokens excluding newlines - - def skip_tkspace_without_nl - tokens = [] - - while (tk = get_tk) and :on_sp == tk[:kind] do - tokens.push(tk) - end - - unget_tk(tk) - tokens - end - - ## - # Has +obj+ listen to tokens - - def token_listener(obj) - add_token_listener obj - yield - ensure - remove_token_listener obj - end - - ## - # Returns +tk+ to the scanner - - def unget_tk(tk) - @tokens.unshift tk - @unget_read.unshift @read.pop - - # Remove this token from any listeners - @token_listeners.each do |obj| - obj.pop_token - end if @token_listeners - - nil - end - -end diff --git a/lib/rdoc/parser/simple.rb b/lib/rdoc/parser/simple.rb deleted file mode 100644 index b1dabad0f8..0000000000 --- a/lib/rdoc/parser/simple.rb +++ /dev/null @@ -1,61 +0,0 @@ -# frozen_string_literal: true -## -# Parse a non-source file. We basically take the whole thing as one big -# comment. - -class RDoc::Parser::Simple < RDoc::Parser - - include RDoc::Parser::Text - - parse_files_matching(//) - - attr_reader :content # :nodoc: - - ## - # Prepare to parse a plain file - - def initialize(top_level, file_name, content, options, stats) - super - - preprocess = RDoc::Markup::PreProcess.new @file_name, @options.rdoc_include - - @content = preprocess.handle @content, @top_level - end - - ## - # Extract the file contents and attach them to the TopLevel as a comment - - def scan - comment = remove_coding_comment @content - comment = remove_private_comment comment - - comment = RDoc::Comment.new comment, @top_level - - @top_level.comment = comment - @top_level - end - - ## - # Removes the encoding magic comment from +text+ - - def remove_coding_comment text - text.sub(/\A# .*coding[=:].*$/, '') - end - - ## - # Removes private comments. - # - # Unlike RDoc::Comment#remove_private this implementation only looks for two - # dashes at the beginning of the line. Three or more dashes are considered - # to be a rule and ignored. - - def remove_private_comment comment - # Workaround for gsub encoding for Ruby 1.9.2 and earlier - empty = '' - empty = RDoc::Encoding.change_encoding empty, comment.encoding - - comment = comment.gsub(%r%^--\n.*?^\+\+\n?%m, empty) - comment.sub(%r%^--\n.*%m, empty) - end - -end diff --git a/lib/rdoc/parser/text.rb b/lib/rdoc/parser/text.rb deleted file mode 100644 index 5095d8cc64..0000000000 --- a/lib/rdoc/parser/text.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true -## -# Indicates this parser is text and doesn't contain code constructs. -# -# Include this module in a RDoc::Parser subclass to make it show up as a file, -# not as part of a class or module. -#-- -# This is not named File to avoid overriding ::File - -module RDoc::Parser::Text -end diff --git a/lib/rdoc/rd.rb b/lib/rdoc/rd.rb deleted file mode 100644 index 8c2366a3ca..0000000000 --- a/lib/rdoc/rd.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true -## -# RDoc::RD implements the RD format from the rdtool gem. -# -# To choose RD as your only default format see -# RDoc::Options@Saved+Options for instructions on setting up a -# <code>.doc_options</code> file to store your project default. -# -# == LICENSE -# -# The grammar that produces RDoc::RD::BlockParser and RDoc::RD::InlineParser -# is included in RDoc under the Ruby License. -# -# You can find the original source for rdtool at -# https://github.com/uwabami/rdtool/ -# -# You can use, re-distribute or change these files under Ruby's License or GPL. -# -# 1. You may make and give away verbatim copies of the source form of the -# software without restriction, provided that you duplicate all of the -# original copyright notices and associated disclaimers. -# -# 2. You may modify your copy of the software in any way, provided that -# you do at least ONE of the following: -# -# a. place your modifications in the Public Domain or otherwise -# make them Freely Available, such as by posting said -# modifications to Usenet or an equivalent medium, or by allowing -# the author to include your modifications in the software. -# -# b. use the modified software only within your corporation or -# organization. -# -# c. give non-standard binaries non-standard names, with -# instructions on where to get the original software distribution. -# -# d. make other distribution arrangements with the author. -# -# 3. You may distribute the software in object code or binary form, -# provided that you do at least ONE of the following: -# -# a. distribute the binaries and library files of the software, -# together with instructions (in the manual page or equivalent) -# on where to get the original distribution. -# -# b. accompany the distribution with the machine-readable source of -# the software. -# -# c. give non-standard binaries non-standard names, with -# instructions on where to get the original software distribution. -# -# d. make other distribution arrangements with the author. -# -# 4. You may modify and include the part of the software into any other -# software (possibly commercial). But some files in the distribution -# are not written by the author, so that they are not under these terms. -# -# For the list of those files and their copying conditions, see the -# file LEGAL. -# -# 5. The scripts and library files supplied as input to or produced as -# output from the software do not automatically fall under the -# copyright of the software, but belong to whomever generated them, -# and may be sold commercially, and may be aggregated with this -# software. -# -# 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR -# IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -# PURPOSE. - -class RDoc::RD - - ## - # Parses +rd+ source and returns an RDoc::Markup::Document. If the - # <tt>=begin</tt> or <tt>=end</tt> lines are missing they will be added. - - def self.parse rd - rd = rd.lines.to_a - - if rd.find { |i| /\S/ === i } and !rd.find{|i| /^=begin\b/ === i } then - rd.unshift("=begin\n").push("=end\n") - end - - parser = RDoc::RD::BlockParser.new - document = parser.parse rd - - # isn't this always true? - document.parts.shift if RDoc::Markup::BlankLine === document.parts.first - document.parts.pop if RDoc::Markup::BlankLine === document.parts.last - - document - end - - autoload :BlockParser, "#{__dir__}/rd/block_parser" - autoload :InlineParser, "#{__dir__}/rd/inline_parser" - autoload :Inline, "#{__dir__}/rd/inline" - -end diff --git a/lib/rdoc/rd/block_parser.rb b/lib/rdoc/rd/block_parser.rb deleted file mode 100644 index 256ba553e5..0000000000 --- a/lib/rdoc/rd/block_parser.rb +++ /dev/null @@ -1,1706 +0,0 @@ -# frozen_string_literal: true -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.8.1 -# from Racc grammar file "block_parser.ry". -# - -###### racc/parser.rb begin -unless $".find {|p| p.end_with?('/racc/parser.rb')} -$".push "#{__dir__}/racc/parser.rb" -#-- -# Copyright (c) 1999-2006 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. -# -# As a special exception, when this code is copied by Racc -# into a Racc output file, you may use that output file -# without restriction. -#++ - -unless $".find {|p| p.end_with?('/racc/info.rb')} -$".push "#{__dir__}/racc/info.rb" - -module Racc - VERSION = '1.8.1' - Version = VERSION - Copyright = 'Copyright (c) 1999-2006 Minero Aoki' -end - -end - - -module Racc - class ParseError < StandardError; end -end -unless defined?(::ParseError) - ParseError = Racc::ParseError # :nodoc: -end - -# Racc is an LALR(1) parser generator. -# It is written in Ruby itself, and generates Ruby programs. -# -# == Command-line Reference -# -# racc [-o<var>filename</var>] [--output-file=<var>filename</var>] -# [-e<var>rubypath</var>] [--executable=<var>rubypath</var>] -# [-v] [--verbose] -# [-O<var>filename</var>] [--log-file=<var>filename</var>] -# [-g] [--debug] -# [-E] [--embedded] -# [-l] [--no-line-convert] -# [-c] [--line-convert-all] -# [-a] [--no-omit-actions] -# [-C] [--check-only] -# [-S] [--output-status] -# [--version] [--copyright] [--help] <var>grammarfile</var> -# -# [+grammarfile+] -# Racc grammar file. Any extension is permitted. -# [-o+outfile+, --output-file=+outfile+] -# A filename for output. default is <+filename+>.tab.rb -# [-O+filename+, --log-file=+filename+] -# Place logging output in file +filename+. -# Default log file name is <+filename+>.output. -# [-e+rubypath+, --executable=+rubypath+] -# output executable file(mode 755). where +path+ is the Ruby interpreter. -# [-v, --verbose] -# verbose mode. create +filename+.output file, like yacc's y.output file. -# [-g, --debug] -# add debug code to parser class. To display debugging information, -# use this '-g' option and set @yydebug true in parser class. -# [-E, --embedded] -# Output parser which doesn't need runtime files (racc/parser.rb). -# [-F, --frozen] -# Output parser which declares frozen_string_literals: true -# [-C, --check-only] -# Check syntax of racc grammar file and quit. -# [-S, --output-status] -# Print messages time to time while compiling. -# [-l, --no-line-convert] -# turns off line number converting. -# [-c, --line-convert-all] -# Convert line number of actions, inner, header and footer. -# [-a, --no-omit-actions] -# Call all actions, even if an action is empty. -# [--version] -# print Racc version and quit. -# [--copyright] -# Print copyright and quit. -# [--help] -# Print usage and quit. -# -# == Generating Parser Using Racc -# -# To compile Racc grammar file, simply type: -# -# $ racc parse.y -# -# This creates Ruby script file "parse.tab.y". The -o option can change the output filename. -# -# == Writing A Racc Grammar File -# -# If you want your own parser, you have to write a grammar file. -# A grammar file contains the name of your parser class, grammar for the parser, -# user code, and anything else. -# When writing a grammar file, yacc's knowledge is helpful. -# If you have not used yacc before, Racc is not too difficult. -# -# Here's an example Racc grammar file. -# -# class Calcparser -# rule -# target: exp { print val[0] } -# -# exp: exp '+' exp -# | exp '*' exp -# | '(' exp ')' -# | NUMBER -# end -# -# Racc grammar files resemble yacc files. -# But (of course), this is Ruby code. -# yacc's $$ is the 'result', $0, $1... is -# an array called 'val', and $-1, $-2... is an array called '_values'. -# -# See the {Grammar File Reference}[rdoc-ref:lib/racc/rdoc/grammar.en.rdoc] for -# more information on grammar files. -# -# == Parser -# -# Then you must prepare the parse entry method. There are two types of -# parse methods in Racc, Racc::Parser#do_parse and Racc::Parser#yyparse -# -# Racc::Parser#do_parse is simple. -# -# It's yyparse() of yacc, and Racc::Parser#next_token is yylex(). -# This method must returns an array like [TOKENSYMBOL, ITS_VALUE]. -# EOF is [false, false]. -# (TOKENSYMBOL is a Ruby symbol (taken from String#intern) by default. -# If you want to change this, see the grammar reference. -# -# Racc::Parser#yyparse is little complicated, but useful. -# It does not use Racc::Parser#next_token, instead it gets tokens from any iterator. -# -# For example, <code>yyparse(obj, :scan)</code> causes -# calling +obj#scan+, and you can return tokens by yielding them from +obj#scan+. -# -# == Debugging -# -# When debugging, "-v" or/and the "-g" option is helpful. -# -# "-v" creates verbose log file (.output). -# "-g" creates a "Verbose Parser". -# Verbose Parser prints the internal status when parsing. -# But it's _not_ automatic. -# You must use -g option and set +@yydebug+ to +true+ in order to get output. -# -g option only creates the verbose parser. -# -# === Racc reported syntax error. -# -# Isn't there too many "end"? -# grammar of racc file is changed in v0.10. -# -# Racc does not use '%' mark, while yacc uses huge number of '%' marks.. -# -# === Racc reported "XXXX conflicts". -# -# Try "racc -v xxxx.y". -# It causes producing racc's internal log file, xxxx.output. -# -# === Generated parsers does not work correctly -# -# Try "racc -g xxxx.y". -# This command let racc generate "debugging parser". -# Then set @yydebug=true in your parser. -# It produces a working log of your parser. -# -# == Re-distributing Racc runtime -# -# A parser, which is created by Racc, requires the Racc runtime module; -# racc/parser.rb. -# -# Ruby 1.8.x comes with Racc runtime module, -# you need NOT distribute Racc runtime files. -# -# If you want to include the Racc runtime module with your parser. -# This can be done by using '-E' option: -# -# $ racc -E -omyparser.rb myparser.y -# -# This command creates myparser.rb which `includes' Racc runtime. -# Only you must do is to distribute your parser file (myparser.rb). -# -# Note: parser.rb is ruby license, but your parser is not. -# Your own parser is completely yours. -module Racc - - unless defined?(Racc_No_Extensions) - Racc_No_Extensions = false # :nodoc: - end - - class Parser - - Racc_Runtime_Version = ::Racc::VERSION - Racc_Runtime_Core_Version_R = ::Racc::VERSION - - begin - if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby' - require 'jruby' - require 'racc/cparse-jruby.jar' - com.headius.racc.Cparse.new.load(JRuby.runtime, false) - else - require 'racc/cparse' - end - - unless new.respond_to?(:_racc_do_parse_c, true) - raise LoadError, 'old cparse.so' - end - if Racc_No_Extensions - raise LoadError, 'selecting ruby version of racc runtime core' - end - - Racc_Main_Parsing_Routine = :_racc_do_parse_c # :nodoc: - Racc_YY_Parse_Method = :_racc_yyparse_c # :nodoc: - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C # :nodoc: - Racc_Runtime_Type = 'c' # :nodoc: - rescue LoadError - Racc_Main_Parsing_Routine = :_racc_do_parse_rb - Racc_YY_Parse_Method = :_racc_yyparse_rb - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R - Racc_Runtime_Type = 'ruby' - end - - def Parser.racc_runtime_type # :nodoc: - Racc_Runtime_Type - end - - def _racc_setup - @yydebug = false unless self.class::Racc_debug_parser - @yydebug = false unless defined?(@yydebug) - if @yydebug - @racc_debug_out = $stderr unless defined?(@racc_debug_out) - @racc_debug_out ||= $stderr - end - arg = self.class::Racc_arg - arg[13] = true if arg.size < 14 - arg - end - - def _racc_init_sysvars - @racc_state = [0] - @racc_tstack = [] - @racc_vstack = [] - - @racc_t = nil - @racc_val = nil - - @racc_read_next = true - - @racc_user_yyerror = false - @racc_error_status = 0 - end - - # The entry point of the parser. This method is used with #next_token. - # If Racc wants to get token (and its value), calls next_token. - # - # Example: - # def parse - # @q = [[1,1], - # [2,2], - # [3,3], - # [false, '$']] - # do_parse - # end - # - # def next_token - # @q.shift - # end - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def do_parse - #{Racc_Main_Parsing_Routine}(_racc_setup(), false) - end - RUBY - - # The method to fetch next token. - # If you use #do_parse method, you must implement #next_token. - # - # The format of return value is [TOKEN_SYMBOL, VALUE]. - # +token-symbol+ is represented by Ruby's symbol by default, e.g. :IDENT - # for 'IDENT'. ";" (String) for ';'. - # - # The final symbol (End of file) must be false. - def next_token - raise NotImplementedError, "#{self.class}\#next_token is not defined" - end - - def _racc_do_parse_rb(arg, in_debug) - action_table, action_check, action_default, action_pointer, - _, _, _, _, - _, _, token_table, * = arg - - _racc_init_sysvars - tok = act = i = nil - - catch(:racc_end_parse) { - while true - if i = action_pointer[@racc_state[-1]] - if @racc_read_next - if @racc_t != 0 # not EOF - tok, @racc_val = next_token() - unless tok # EOF - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - racc_read_token(@racc_t, tok, @racc_val) if @yydebug - @racc_read_next = false - end - end - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - else - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - } - end - - # Another entry point for the parser. - # If you use this method, you must implement RECEIVER#METHOD_ID method. - # - # RECEIVER#METHOD_ID is a method to get next token. - # It must 'yield' the token, which format is [TOKEN-SYMBOL, VALUE]. - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def yyparse(recv, mid) - #{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false) - end - RUBY - - def _racc_yyparse_rb(recv, mid, arg, c_debug) - action_table, action_check, action_default, action_pointer, - _, _, _, _, - _, _, token_table, * = arg - - _racc_init_sysvars - - catch(:racc_end_parse) { - until i = action_pointer[@racc_state[-1]] - while act = _racc_evalact(action_default[@racc_state[-1]], arg) - ; - end - end - recv.__send__(mid) do |tok, val| - unless tok - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - @racc_val = val - @racc_read_next = false - - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - - while !(i = action_pointer[@racc_state[-1]]) || - ! @racc_read_next || - @racc_t == 0 # $ - unless i and i += @racc_t and - i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - end - } - end - - ### - ### common - ### - - def _racc_evalact(act, arg) - action_table, action_check, _, action_pointer, - _, _, _, _, - _, _, _, shift_n, - reduce_n, * = arg - nerr = 0 # tmp - - if act > 0 and act < shift_n - # - # shift - # - if @racc_error_status > 0 - @racc_error_status -= 1 unless @racc_t <= 1 # error token or EOF - end - @racc_vstack.push @racc_val - @racc_state.push act - @racc_read_next = true - if @yydebug - @racc_tstack.push @racc_t - racc_shift @racc_t, @racc_tstack, @racc_vstack - end - - elsif act < 0 and act > -reduce_n - # - # reduce - # - code = catch(:racc_jump) { - @racc_state.push _racc_do_reduce(arg, act) - false - } - if code - case code - when 1 # yyerror - @racc_user_yyerror = true # user_yyerror - return -reduce_n - when 2 # yyaccept - return shift_n - else - raise '[Racc Bug] unknown jump code' - end - end - - elsif act == shift_n - # - # accept - # - racc_accept if @yydebug - throw :racc_end_parse, @racc_vstack[0] - - elsif act == -reduce_n - # - # error - # - case @racc_error_status - when 0 - unless arg[21] # user_yyerror - nerr += 1 - on_error @racc_t, @racc_val, @racc_vstack - end - when 3 - if @racc_t == 0 # is $ - # We're at EOF, and another error occurred immediately after - # attempting auto-recovery - throw :racc_end_parse, nil - end - @racc_read_next = true - end - @racc_user_yyerror = false - @racc_error_status = 3 - while true - if i = action_pointer[@racc_state[-1]] - i += 1 # error token - if i >= 0 and - (act = action_table[i]) and - action_check[i] == @racc_state[-1] - break - end - end - throw :racc_end_parse, nil if @racc_state.size <= 1 - @racc_state.pop - @racc_vstack.pop - if @yydebug - @racc_tstack.pop - racc_e_pop @racc_state, @racc_tstack, @racc_vstack - end - end - return act - - else - raise "[Racc Bug] unknown action #{act.inspect}" - end - - racc_next_state(@racc_state[-1], @racc_state) if @yydebug - - nil - end - - def _racc_do_reduce(arg, act) - _, _, _, _, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, _, _, - _, use_result, * = arg - - state = @racc_state - vstack = @racc_vstack - tstack = @racc_tstack - - i = act * -3 - len = reduce_table[i] - reduce_to = reduce_table[i+1] - method_id = reduce_table[i+2] - void_array = [] - - tmp_t = tstack[-len, len] if @yydebug - tmp_v = vstack[-len, len] - tstack[-len, len] = void_array if @yydebug - vstack[-len, len] = void_array - state[-len, len] = void_array - - # tstack must be updated AFTER method call - if use_result - vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) - else - vstack.push __send__(method_id, tmp_v, vstack) - end - tstack.push reduce_to - - racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug - - k1 = reduce_to - nt_base - if i = goto_pointer[k1] - i += state[-1] - if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 - return curstate - end - end - goto_default[k1] - end - - # This method is called when a parse error is found. - # - # ERROR_TOKEN_ID is an internal ID of token which caused error. - # You can get string representation of this ID by calling - # #token_to_str. - # - # ERROR_VALUE is a value of error token. - # - # value_stack is a stack of symbol values. - # DO NOT MODIFY this object. - # - # This method raises ParseError by default. - # - # If this method returns, parsers enter "error recovering mode". - def on_error(t, val, vstack) - raise ParseError, sprintf("parse error on value %s (%s)", - val.inspect, token_to_str(t) || '?') - end - - # Enter error recovering mode. - # This method does not call #on_error. - def yyerror - throw :racc_jump, 1 - end - - # Exit parser. - # Return value is +Symbol_Value_Stack[0]+. - def yyaccept - throw :racc_jump, 2 - end - - # Leave error recovering mode. - def yyerrok - @racc_error_status = 0 - end - - # For debugging output - def racc_read_token(t, tok, val) - @racc_debug_out.print 'read ' - @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' - @racc_debug_out.puts val.inspect - @racc_debug_out.puts - end - - def racc_shift(tok, tstack, vstack) - @racc_debug_out.puts "shift #{racc_token2str tok}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_reduce(toks, sim, tstack, vstack) - out = @racc_debug_out - out.print 'reduce ' - if toks.empty? - out.print ' <none>' - else - toks.each {|t| out.print ' ', racc_token2str(t) } - end - out.puts " --> #{racc_token2str(sim)}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_accept - @racc_debug_out.puts 'accept' - @racc_debug_out.puts - end - - def racc_e_pop(state, tstack, vstack) - @racc_debug_out.puts 'error recovering mode: pop token' - racc_print_states state - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_next_state(curstate, state) - @racc_debug_out.puts "goto #{curstate}" - racc_print_states state - @racc_debug_out.puts - end - - def racc_print_stacks(t, v) - out = @racc_debug_out - out.print ' [' - t.each_index do |i| - out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' - end - out.puts ' ]' - end - - def racc_print_states(s) - out = @racc_debug_out - out.print ' [' - s.each {|st| out.print ' ', st } - out.puts ' ]' - end - - def racc_token2str(tok) - self.class::Racc_token_to_s_table[tok] or - raise "[Racc Bug] can't convert token #{tok} to string" - end - - # Convert internal ID of token symbol to the string. - def token_to_str(t) - self.class::Racc_token_to_s_table[t] - end - - end - -end - -end -###### racc/parser.rb end - -class RDoc::RD - -## -# RD format parser for headings, paragraphs, lists, verbatim sections that -# exist as blocks. - -class BlockParser < Racc::Parser - - -# :stopdoc: - -MARK_TO_LEVEL = { - '=' => 1, - '==' => 2, - '===' => 3, - '====' => 4, - '+' => 5, - '++' => 6, -} - -# :startdoc: - -## -# Footnotes for this document - -attr_reader :footnotes - -## -# Labels for items in this document - -attr_reader :labels - -## -# Path to find included files in - -attr_accessor :include_path - -## -# Creates a new RDoc::RD::BlockParser. Use #parse to parse an rd-format -# document. - -def initialize - @inline_parser = RDoc::RD::InlineParser.new self - @include_path = [] - - # for testing - @footnotes = [] - @labels = {} -end - -## -# Parses +src+ and returns an RDoc::Markup::Document. - -def parse src - @src = src - @src.push false - - @footnotes = [] - @labels = {} - - # @i: index(line no.) of src - @i = 0 - - # stack for current indentation - @indent_stack = [] - - # how indented. - @current_indent = @indent_stack.join("") - - # RDoc::RD::BlockParser for tmp src - @subparser = nil - - # which part is in now - @in_part = nil - @part_content = [] - - @in_verbatim = false - - @yydebug = true - - document = do_parse - - unless @footnotes.empty? then - blankline = document.parts.pop - - document.parts << RDoc::Markup::Rule.new(1) - document.parts.concat @footnotes - - document.parts.push blankline - end - - document -end - -## -# Returns the next token from the document - -def next_token # :nodoc: - # preprocessing - # if it is not in RD part - # => method - while @in_part != "rd" - line = @src[@i] - @i += 1 # next line - - case line - # src end - when false - return [false, false] - # RD part begin - when /^=begin\s*(?:\bRD\b.*)?\s*$/ - if @in_part # if in non-RD part - @part_content.push(line) - else - @in_part = "rd" - return [:WHITELINE, "=begin\n"] # <= for textblockand - end - # non-RD part begin - when /^=begin\s+(\w+)/ - part = $1 -=begin # not imported to RDoc - if @in_part # if in non-RD part - @part_content.push(line) - else - @in_part = part if @tree.filter[part] # if filter exists -# p "BEGIN_PART: #{@in_part}" # DEBUG - end -=end - @in_part = part - # non-RD part end - when /^=end(?:$|[\s\0\C-d\C-z])/ - if @in_part # if in non-RD part -=begin # not imported to RDoc -# p "END_PART: #{@in_part}" # DEBUG - # make Part-in object - part = RDoc::RD::Part.new(@part_content.join(""), @tree, "r") - @part_content.clear - # call filter, part_out is output(Part object) - part_out = @tree.filter[@in_part].call(part) - - if @tree.filter[@in_part].mode == :rd # if output is RD formatted - subtree = parse_subtree(part_out.to_a) - else # if output is target formatted - basename = Tempfile.create(["rdtmp", ".#{@in_part}"], @tree.tmp_dir) do |tmpfile| - tmpfile.print(part_out) - File.basename(tmpfile.path) - end - subtree = parse_subtree(["=begin\n", "<<< #{basename}\n", "=end\n"]) - end - @in_part = nil - return [:SUBTREE, subtree] -=end - end - else -=begin # not imported to RDoc - if @in_part # if in non-RD part - @part_content.push(line) - end -=end - end - end - - @current_indent = @indent_stack.join("") - line = @src[@i] - case line - when false - if_current_indent_equal("") do - [false, false] - end - when /^=end/ - if_current_indent_equal("") do - @in_part = nil - [:WHITELINE, "=end"] # MUST CHANGE?? - end - when /^\s*$/ - @i += 1 # next line - return [:WHITELINE, ':WHITELINE'] - when /^\#/ # comment line - @i += 1 # next line - self.next_token() - when /^(={1,4})(?!=)\s*(?=\S)/, /^(\+{1,2})(?!\+)\s*(?=\S)/ - rest = $' # ' - rest.strip! - mark = $1 - if_current_indent_equal("") do - return [:HEADLINE, [MARK_TO_LEVEL[mark], rest]] - end - when /^<<<\s*(\S+)/ - file = $1 - if_current_indent_equal("") do - suffix = file[-3 .. -1] - if suffix == ".rd" or suffix == ".rb" - subtree = parse_subtree(get_included(file)) - [:SUBTREE, subtree] - else - [:INCLUDE, file] - end - end - when /^(\s*)\*(\s*)/ - rest = $' # ' - newIndent = $2 - if_current_indent_equal($1) do - if @in_verbatim - [:STRINGLINE, line] - else - @indent_stack.push("\s" + newIndent) - [:ITEMLISTLINE, rest] - end - end - when /^(\s*)(\(\d+\))(\s*)/ - rest = $' # ' - mark = $2 - newIndent = $3 - if_current_indent_equal($1) do - if @in_verbatim - [:STRINGLINE, line] - else - @indent_stack.push("\s" * mark.size + newIndent) - [:ENUMLISTLINE, rest] - end - end - when /^(\s*):(\s*)/ - rest = $' # ' - newIndent = $2 - if_current_indent_equal($1) do - if @in_verbatim - [:STRINGLINE, line] - else - @indent_stack.push("\s#{$2}") - [:DESCLISTLINE, rest] - end - end - when /^(\s*)---(?!-|\s*$)/ - indent = $1 - rest = $' - /\s*/ === rest - term = $' - new_indent = $& - if_current_indent_equal(indent) do - if @in_verbatim - [:STRINGLINE, line] - else - @indent_stack.push("\s\s\s" + new_indent) - [:METHODLISTLINE, term] - end - end - when /^(\s*)/ - if_current_indent_equal($1) do - [:STRINGLINE, line] - end - else - raise "[BUG] parsing error may occurred." - end -end - -## -# Yields to the given block if +indent+ matches the current indent, otherwise -# an indentation token is processed. - -def if_current_indent_equal(indent) - indent = indent.sub(/\t/, "\s" * 8) - if @current_indent == indent - @i += 1 # next line - yield - elsif indent.index(@current_indent) == 0 - @indent_stack.push(indent[@current_indent.size .. -1]) - [:INDENT, ":INDENT"] - else - @indent_stack.pop - [:DEDENT, ":DEDENT"] - end -end -private :if_current_indent_equal - -## -# Cuts off excess whitespace in +src+ - -def cut_off(src) - ret = [] - whiteline_buf = [] - - line = src.shift - /^\s*/ =~ line - - indent = Regexp.quote($&) - ret.push($') - - while line = src.shift - if /^(\s*)$/ =~ line - whiteline_buf.push(line) - elsif /^#{indent}/ =~ line - unless whiteline_buf.empty? - ret.concat(whiteline_buf) - whiteline_buf.clear - end - ret.push($') - else - raise "[BUG]: probably Parser Error while cutting off.\n" - end - end - ret -end -private :cut_off - -def set_term_to_element(parent, term) -# parent.set_term_under_document_struct(term, @tree.document_struct) - parent.set_term_without_document_struct(term) -end -private :set_term_to_element - -## -# Raises a ParseError when invalid formatting is found - -def on_error(et, ev, _values) - prv, cur, nxt = format_line_num(@i, @i+1, @i+2) - - raise ParseError, <<Msg - -RD syntax error: line #{@i+1}: - #{prv} |#{@src[@i-1].chomp} - #{cur}=>|#{@src[@i].chomp} - #{nxt} |#{@src[@i+1].chomp} - -Msg -end - -## -# Current line number - -def line_index - @i -end - -## -# Parses subtree +src+ - -def parse_subtree src - @subparser ||= RDoc::RD::BlockParser.new - - @subparser.parse src -end -private :parse_subtree - -## -# Retrieves the content for +file+ from the include_path - -def get_included(file) - included = [] - - @include_path.each do |dir| - file_name = File.join dir, file - - if File.exist? file_name then - included = File.readlines file_name - break - end - end - - included -end -private :get_included - -## -# Formats line numbers +line_numbers+ prettily - -def format_line_num(*line_numbers) - width = line_numbers.collect{|i| i.to_s.length }.max - line_numbers.collect{|i| sprintf("%#{width}d", i) } -end -private :format_line_num - -## -# Retrieves the content of +values+ as a single String - -def content values - values.map { |value| value.content }.join -end - -## -# Creates a paragraph for +value+ - -def paragraph value - content = cut_off(value).join(' ').rstrip - contents = @inline_parser.parse content - - RDoc::Markup::Paragraph.new(*contents) -end - -## -# Adds footnote +content+ to the document - -def add_footnote content - index = @footnotes.length / 2 + 1 - - footmark_link = "{^#{index}}[rdoc-label:footmark-#{index}:foottext-#{index}]" - - @footnotes << RDoc::Markup::Paragraph.new(footmark_link, ' ', *content) - @footnotes << RDoc::Markup::BlankLine.new - - index -end - -## -# Adds label +label+ to the document - -def add_label label - @labels[label] = true - - label -end - -# :stopdoc: - -##### State transition tables begin ### - -racc_action_table = [ - 34, 35, 30, 33, 40, 34, 35, 30, 33, 40, - 65, 34, 35, 30, 33, 14, 73, 36, 38, 34, - 15, 88, 34, 35, 30, 33, 14, 9, 10, 11, - 12, 15, 34, 35, 30, 33, 14, 9, 10, 11, - 12, 15, 34, 35, 30, 33, 35, 47, 30, 54, - 33, 15, 34, 35, 30, 33, 54, 47, 14, 14, - 59, 15, 34, 35, 30, 33, 14, 73, 67, 76, - 77, 15, 34, 35, 30, 33, 14, 73, 54, 81, - 38, 15, 34, 35, 30, 33, 14, 73, 38, 40, - 83, 15, 34, 35, 30, 33, 14, 73, nil, nil, - nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, - nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, - nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, - nil, 15, 34, 35, 30, 33, 14, 73, nil, nil, - nil, 15, 34, 35, 30, 33, 14, 73, 61, 63, - nil, 15, 14, 62, 60, 61, 63, 79, 61, 63, - 62, 87, nil, 62, 34, 35, 30, 33 ] - -racc_action_check = [ - 41, 41, 41, 41, 41, 15, 15, 15, 15, 15, - 41, 86, 86, 86, 86, 86, 86, 1, 13, 22, - 86, 86, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 24, 24, 24, 24, 25, 24, 28, 30, - 31, 24, 27, 27, 27, 27, 33, 27, 34, 35, - 36, 27, 45, 45, 45, 45, 45, 45, 44, 49, - 51, 45, 46, 46, 46, 46, 46, 46, 54, 56, - 57, 46, 47, 47, 47, 47, 47, 47, 58, 62, - 66, 47, 68, 68, 68, 68, 68, 68, nil, nil, - nil, 68, 74, 74, 74, 74, 74, 74, nil, nil, - nil, 74, 75, 75, 75, 75, 75, 75, nil, nil, - nil, 75, 78, 78, 78, 78, 78, 78, nil, nil, - nil, 78, 79, 79, 79, 79, 79, 79, nil, nil, - nil, 79, 85, 85, 85, 85, 85, 85, 39, 39, - nil, 85, 52, 39, 39, 82, 82, 52, 64, 64, - 82, 82, nil, 64, 20, 20, 20, 20 ] - -racc_action_pointer = [ - 19, 17, 29, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 11, nil, 2, nil, nil, nil, nil, - 161, nil, 16, nil, 39, 42, nil, 49, 43, nil, - 41, 44, nil, 48, 51, 52, 60, nil, nil, 141, - nil, -3, nil, nil, 55, 59, 69, 79, nil, 56, - nil, 57, 145, nil, 70, nil, 66, 73, 81, nil, - nil, nil, 82, nil, 151, nil, 77, nil, 89, nil, - nil, nil, nil, nil, 99, 109, nil, nil, 119, 129, - nil, nil, 148, nil, nil, 139, 8, nil, nil ] - -racc_action_default = [ - -2, -73, -1, -4, -5, -6, -7, -8, -9, -10, - -11, -12, -13, -14, -16, -73, -23, -24, -25, -26, - -27, -31, -32, -34, -72, -36, -38, -72, -40, -42, - -59, -44, -46, -59, -63, -65, -73, -3, -15, -73, - -22, -73, -30, -33, -73, -69, -70, -71, -37, -73, - -41, -73, -51, -58, -61, -45, -73, -62, -64, 89, - -17, -19, -73, -21, -18, -28, -73, -35, -66, -53, - -54, -55, -56, -57, -67, -68, -39, -43, -49, -73, - -60, -47, -73, -29, -52, -48, -73, -20, -50 ] - -racc_goto_table = [ - 4, 39, 4, 68, 74, 75, 5, 6, 5, 6, - 44, 42, 51, 49, 3, 56, 37, 57, 58, 1, - 2, 66, 84, 41, 43, 48, 50, 64, 84, 84, - 45, 46, 42, 45, 46, 55, 85, 86, 80, 84, - 84, nil, nil, nil, nil, nil, nil, nil, 82, nil, - nil, nil, 78 ] - -racc_goto_check = [ - 4, 10, 4, 31, 31, 31, 5, 6, 5, 6, - 21, 12, 27, 21, 3, 27, 3, 9, 9, 1, - 2, 11, 32, 17, 19, 23, 26, 10, 32, 32, - 5, 6, 12, 5, 6, 29, 31, 31, 33, 32, - 32, nil, nil, nil, nil, nil, nil, nil, 10, nil, - nil, nil, 4 ] - -racc_goto_pointer = [ - nil, 19, 20, 14, 0, 6, 7, nil, nil, -17, - -14, -20, -9, nil, nil, nil, nil, 8, nil, 2, - nil, -14, nil, 0, nil, nil, -2, -18, nil, 4, - nil, -42, -46, -16 ] - -racc_goto_default = [ - nil, nil, nil, nil, 70, 71, 72, 7, 8, 13, - nil, nil, 21, 16, 17, 18, 19, 20, 22, 23, - 24, nil, 25, 26, 27, 28, 29, nil, 31, 32, - 52, nil, 69, 53 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 1, 15, :_reduce_1, - 0, 15, :_reduce_2, - 2, 16, :_reduce_3, - 1, 16, :_reduce_4, - 1, 17, :_reduce_5, - 1, 17, :_reduce_6, - 1, 17, :_reduce_none, - 1, 17, :_reduce_8, - 1, 17, :_reduce_9, - 1, 17, :_reduce_10, - 1, 17, :_reduce_11, - 1, 21, :_reduce_12, - 1, 22, :_reduce_13, - 1, 18, :_reduce_14, - 2, 23, :_reduce_15, - 1, 23, :_reduce_16, - 3, 19, :_reduce_17, - 1, 25, :_reduce_18, - 2, 24, :_reduce_19, - 4, 24, :_reduce_20, - 2, 24, :_reduce_21, - 1, 24, :_reduce_22, - 1, 26, :_reduce_none, - 1, 26, :_reduce_none, - 1, 26, :_reduce_none, - 1, 26, :_reduce_none, - 1, 20, :_reduce_27, - 3, 20, :_reduce_28, - 4, 20, :_reduce_29, - 2, 31, :_reduce_30, - 1, 31, :_reduce_31, - 1, 27, :_reduce_32, - 2, 32, :_reduce_33, - 1, 32, :_reduce_34, - 3, 33, :_reduce_35, - 1, 28, :_reduce_36, - 2, 36, :_reduce_37, - 1, 36, :_reduce_38, - 3, 37, :_reduce_39, - 1, 29, :_reduce_40, - 2, 39, :_reduce_41, - 1, 39, :_reduce_42, - 3, 40, :_reduce_43, - 1, 30, :_reduce_44, - 2, 42, :_reduce_45, - 1, 42, :_reduce_46, - 3, 43, :_reduce_47, - 3, 41, :_reduce_48, - 2, 41, :_reduce_49, - 4, 41, :_reduce_50, - 1, 41, :_reduce_51, - 2, 45, :_reduce_52, - 1, 45, :_reduce_none, - 1, 46, :_reduce_54, - 1, 46, :_reduce_55, - 1, 46, :_reduce_none, - 1, 46, :_reduce_57, - 1, 44, :_reduce_none, - 0, 44, :_reduce_none, - 2, 47, :_reduce_none, - 1, 47, :_reduce_none, - 2, 34, :_reduce_62, - 1, 34, :_reduce_63, - 2, 38, :_reduce_64, - 1, 38, :_reduce_65, - 2, 35, :_reduce_66, - 2, 35, :_reduce_67, - 2, 35, :_reduce_68, - 1, 35, :_reduce_69, - 1, 35, :_reduce_none, - 1, 35, :_reduce_71, - 0, 35, :_reduce_72 ] - -racc_reduce_n = 73 - -racc_shift_n = 89 - -racc_token_table = { - false => 0, - :error => 1, - :DUMMY => 2, - :ITEMLISTLINE => 3, - :ENUMLISTLINE => 4, - :DESCLISTLINE => 5, - :METHODLISTLINE => 6, - :STRINGLINE => 7, - :WHITELINE => 8, - :SUBTREE => 9, - :HEADLINE => 10, - :INCLUDE => 11, - :INDENT => 12, - :DEDENT => 13 } - -racc_nt_base = 14 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] -Ractor.make_shareable(Racc_arg) if defined?(Ractor) - -Racc_token_to_s_table = [ - "$end", - "error", - "DUMMY", - "ITEMLISTLINE", - "ENUMLISTLINE", - "DESCLISTLINE", - "METHODLISTLINE", - "STRINGLINE", - "WHITELINE", - "SUBTREE", - "HEADLINE", - "INCLUDE", - "INDENT", - "DEDENT", - "$start", - "document", - "blocks", - "block", - "textblock", - "verbatim", - "lists", - "headline", - "include", - "textblockcontent", - "verbatimcontent", - "verbatim_after_lists", - "list", - "itemlist", - "enumlist", - "desclist", - "methodlist", - "lists2", - "itemlistitems", - "itemlistitem", - "first_textblock_in_itemlist", - "other_blocks_in_list", - "enumlistitems", - "enumlistitem", - "first_textblock_in_enumlist", - "desclistitems", - "desclistitem", - "description_part", - "methodlistitems", - "methodlistitem", - "whitelines", - "blocks_in_list", - "block_in_list", - "whitelines2" ] -Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor) - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -def _reduce_1(val, _values, result) - result = RDoc::Markup::Document.new(*val[0]) - result -end - -def _reduce_2(val, _values, result) - raise ParseError, "file empty" - result -end - -def _reduce_3(val, _values, result) - result = val[0].concat val[1] - result -end - -def _reduce_4(val, _values, result) - result = val[0] - result -end - -def _reduce_5(val, _values, result) - result = val - result -end - -def _reduce_6(val, _values, result) - result = val - result -end - -# reduce 7 omitted - -def _reduce_8(val, _values, result) - result = val - result -end - -def _reduce_9(val, _values, result) - result = val - result -end - -def _reduce_10(val, _values, result) - result = [RDoc::Markup::BlankLine.new] - result -end - -def _reduce_11(val, _values, result) - result = val[0].parts - result -end - -def _reduce_12(val, _values, result) - # val[0] is like [level, title] - title = @inline_parser.parse(val[0][1]) - result = RDoc::Markup::Heading.new(val[0][0], title) - - result -end - -def _reduce_13(val, _values, result) - result = RDoc::Markup::Include.new val[0], @include_path - - result -end - -def _reduce_14(val, _values, result) - # val[0] is Array of String - result = paragraph val[0] - - result -end - -def _reduce_15(val, _values, result) - result << val[1].rstrip - result -end - -def _reduce_16(val, _values, result) - result = [val[0].rstrip] - result -end - -def _reduce_17(val, _values, result) - # val[1] is Array of String - content = cut_off val[1] - result = RDoc::Markup::Verbatim.new(*content) - - # imform to lexer. - @in_verbatim = false - - result -end - -def _reduce_18(val, _values, result) - # val[0] is Array of String - content = cut_off val[0] - result = RDoc::Markup::Verbatim.new(*content) - - # imform to lexer. - @in_verbatim = false - - result -end - -def _reduce_19(val, _values, result) - result << val[1] - - result -end - -def _reduce_20(val, _values, result) - result.concat val[2] - - result -end - -def _reduce_21(val, _values, result) - result << "\n" - - result -end - -def _reduce_22(val, _values, result) - result = val - # inform to lexer. - @in_verbatim = true - - result -end - -# reduce 23 omitted - -# reduce 24 omitted - -# reduce 25 omitted - -# reduce 26 omitted - -def _reduce_27(val, _values, result) - result = val[0] - - result -end - -def _reduce_28(val, _values, result) - result = val[1] - - result -end - -def _reduce_29(val, _values, result) - result = val[1].push(val[2]) - - result -end - -def _reduce_30(val, _values, result) - result = val[0] << val[1] - result -end - -def _reduce_31(val, _values, result) - result = [val[0]] - result -end - -def _reduce_32(val, _values, result) - result = RDoc::Markup::List.new :BULLET, *val[0] - - result -end - -def _reduce_33(val, _values, result) - result.push(val[1]) - result -end - -def _reduce_34(val, _values, result) - result = val - result -end - -def _reduce_35(val, _values, result) - result = RDoc::Markup::ListItem.new nil, val[0], *val[1] - - result -end - -def _reduce_36(val, _values, result) - result = RDoc::Markup::List.new :NUMBER, *val[0] - - result -end - -def _reduce_37(val, _values, result) - result.push(val[1]) - result -end - -def _reduce_38(val, _values, result) - result = val - result -end - -def _reduce_39(val, _values, result) - result = RDoc::Markup::ListItem.new nil, val[0], *val[1] - - result -end - -def _reduce_40(val, _values, result) - result = RDoc::Markup::List.new :NOTE, *val[0] - - result -end - -def _reduce_41(val, _values, result) - result.push(val[1]) - result -end - -def _reduce_42(val, _values, result) - result = val - result -end - -def _reduce_43(val, _values, result) - term = @inline_parser.parse val[0].strip - - result = RDoc::Markup::ListItem.new term, *val[1] - - result -end - -def _reduce_44(val, _values, result) - result = RDoc::Markup::List.new :LABEL, *val[0] - - result -end - -def _reduce_45(val, _values, result) - result.push(val[1]) - result -end - -def _reduce_46(val, _values, result) - result = val - result -end - -def _reduce_47(val, _values, result) - result = RDoc::Markup::ListItem.new "<tt>#{val[0].strip}</tt>", *val[1] - - result -end - -def _reduce_48(val, _values, result) - result = [val[1]].concat(val[2]) - - result -end - -def _reduce_49(val, _values, result) - result = [val[1]] - - result -end - -def _reduce_50(val, _values, result) - result = val[2] - - result -end - -def _reduce_51(val, _values, result) - result = [] - - result -end - -def _reduce_52(val, _values, result) - result.concat val[1] - result -end - -# reduce 53 omitted - -def _reduce_54(val, _values, result) - result = val - result -end - -def _reduce_55(val, _values, result) - result = val - result -end - -# reduce 56 omitted - -def _reduce_57(val, _values, result) - result = [] - result -end - -# reduce 58 omitted - -# reduce 59 omitted - -# reduce 60 omitted - -# reduce 61 omitted - -def _reduce_62(val, _values, result) - result = paragraph [val[0]].concat(val[1]) - - result -end - -def _reduce_63(val, _values, result) - result = paragraph [val[0]] - - result -end - -def _reduce_64(val, _values, result) - result = paragraph [val[0]].concat(val[1]) - - result -end - -def _reduce_65(val, _values, result) - result = paragraph [val[0]] - - result -end - -def _reduce_66(val, _values, result) - result = [val[0]].concat(val[1]) - - result -end - -def _reduce_67(val, _values, result) - result.concat val[1] - result -end - -def _reduce_68(val, _values, result) - result = val[1] - result -end - -def _reduce_69(val, _values, result) - result = val - result -end - -# reduce 70 omitted - -def _reduce_71(val, _values, result) - result = [] - result -end - -def _reduce_72(val, _values, result) - result = [] - result -end - -def _reduce_none(val, _values, result) - val[0] -end - -end # class BlockParser - -end diff --git a/lib/rdoc/rd/inline.rb b/lib/rdoc/rd/inline.rb deleted file mode 100644 index 77d88b2860..0000000000 --- a/lib/rdoc/rd/inline.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true -## -# Inline keeps track of markup and labels to create proper links. - -class RDoc::RD::Inline - - ## - # The text of the reference - - attr_reader :reference - - ## - # The markup of this reference in RDoc format - - attr_reader :rdoc - - ## - # Creates a new Inline for +rdoc+ and +reference+. - # - # +rdoc+ may be another Inline or a String. If +reference+ is not given it - # will use the text from +rdoc+. - - def self.new rdoc, reference = rdoc - if self === rdoc and reference.equal? rdoc then - rdoc - else - super - end - end - - ## - # Initializes the Inline with +rdoc+ and +inline+ - - def initialize rdoc, reference # :not-new: - @reference = reference.equal?(rdoc) ? reference.dup : reference - - # unpack - @reference = @reference.reference if self.class === @reference - @rdoc = rdoc - end - - def == other # :nodoc: - self.class === other and - @reference == other.reference and @rdoc == other.rdoc - end - - ## - # Appends +more+ to this inline. +more+ may be a String or another Inline. - - def append more - case more - when String then - @reference += more - @rdoc += more - when RDoc::RD::Inline then - @reference += more.reference - @rdoc += more.rdoc - else - raise "unknown thingy #{more}" - end - - self - end - - def inspect # :nodoc: - "(inline: #{self})" - end - - alias to_s rdoc # :nodoc: - -end diff --git a/lib/rdoc/rd/inline_parser.rb b/lib/rdoc/rd/inline_parser.rb deleted file mode 100644 index b6d521c6bd..0000000000 --- a/lib/rdoc/rd/inline_parser.rb +++ /dev/null @@ -1,1854 +0,0 @@ -# frozen_string_literal: true -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.8.1 -# from Racc grammar file "inline_parser.ry". -# - -###### racc/parser.rb begin -unless $".find {|p| p.end_with?('/racc/parser.rb')} -$".push "#{__dir__}/racc/parser.rb" -#-- -# Copyright (c) 1999-2006 Minero Aoki -# -# This program is free software. -# You can distribute/modify this program under the same terms of ruby. -# -# As a special exception, when this code is copied by Racc -# into a Racc output file, you may use that output file -# without restriction. -#++ - -unless $".find {|p| p.end_with?('/racc/info.rb')} -$".push "#{__dir__}/racc/info.rb" - -module Racc - VERSION = '1.8.1' - Version = VERSION - Copyright = 'Copyright (c) 1999-2006 Minero Aoki' -end - -end - - -module Racc - class ParseError < StandardError; end -end -unless defined?(::ParseError) - ParseError = Racc::ParseError # :nodoc: -end - -# Racc is an LALR(1) parser generator. -# It is written in Ruby itself, and generates Ruby programs. -# -# == Command-line Reference -# -# racc [-o<var>filename</var>] [--output-file=<var>filename</var>] -# [-e<var>rubypath</var>] [--executable=<var>rubypath</var>] -# [-v] [--verbose] -# [-O<var>filename</var>] [--log-file=<var>filename</var>] -# [-g] [--debug] -# [-E] [--embedded] -# [-l] [--no-line-convert] -# [-c] [--line-convert-all] -# [-a] [--no-omit-actions] -# [-C] [--check-only] -# [-S] [--output-status] -# [--version] [--copyright] [--help] <var>grammarfile</var> -# -# [+grammarfile+] -# Racc grammar file. Any extension is permitted. -# [-o+outfile+, --output-file=+outfile+] -# A filename for output. default is <+filename+>.tab.rb -# [-O+filename+, --log-file=+filename+] -# Place logging output in file +filename+. -# Default log file name is <+filename+>.output. -# [-e+rubypath+, --executable=+rubypath+] -# output executable file(mode 755). where +path+ is the Ruby interpreter. -# [-v, --verbose] -# verbose mode. create +filename+.output file, like yacc's y.output file. -# [-g, --debug] -# add debug code to parser class. To display debugging information, -# use this '-g' option and set @yydebug true in parser class. -# [-E, --embedded] -# Output parser which doesn't need runtime files (racc/parser.rb). -# [-F, --frozen] -# Output parser which declares frozen_string_literals: true -# [-C, --check-only] -# Check syntax of racc grammar file and quit. -# [-S, --output-status] -# Print messages time to time while compiling. -# [-l, --no-line-convert] -# turns off line number converting. -# [-c, --line-convert-all] -# Convert line number of actions, inner, header and footer. -# [-a, --no-omit-actions] -# Call all actions, even if an action is empty. -# [--version] -# print Racc version and quit. -# [--copyright] -# Print copyright and quit. -# [--help] -# Print usage and quit. -# -# == Generating Parser Using Racc -# -# To compile Racc grammar file, simply type: -# -# $ racc parse.y -# -# This creates Ruby script file "parse.tab.y". The -o option can change the output filename. -# -# == Writing A Racc Grammar File -# -# If you want your own parser, you have to write a grammar file. -# A grammar file contains the name of your parser class, grammar for the parser, -# user code, and anything else. -# When writing a grammar file, yacc's knowledge is helpful. -# If you have not used yacc before, Racc is not too difficult. -# -# Here's an example Racc grammar file. -# -# class Calcparser -# rule -# target: exp { print val[0] } -# -# exp: exp '+' exp -# | exp '*' exp -# | '(' exp ')' -# | NUMBER -# end -# -# Racc grammar files resemble yacc files. -# But (of course), this is Ruby code. -# yacc's $$ is the 'result', $0, $1... is -# an array called 'val', and $-1, $-2... is an array called '_values'. -# -# See the {Grammar File Reference}[rdoc-ref:lib/racc/rdoc/grammar.en.rdoc] for -# more information on grammar files. -# -# == Parser -# -# Then you must prepare the parse entry method. There are two types of -# parse methods in Racc, Racc::Parser#do_parse and Racc::Parser#yyparse -# -# Racc::Parser#do_parse is simple. -# -# It's yyparse() of yacc, and Racc::Parser#next_token is yylex(). -# This method must returns an array like [TOKENSYMBOL, ITS_VALUE]. -# EOF is [false, false]. -# (TOKENSYMBOL is a Ruby symbol (taken from String#intern) by default. -# If you want to change this, see the grammar reference. -# -# Racc::Parser#yyparse is little complicated, but useful. -# It does not use Racc::Parser#next_token, instead it gets tokens from any iterator. -# -# For example, <code>yyparse(obj, :scan)</code> causes -# calling +obj#scan+, and you can return tokens by yielding them from +obj#scan+. -# -# == Debugging -# -# When debugging, "-v" or/and the "-g" option is helpful. -# -# "-v" creates verbose log file (.output). -# "-g" creates a "Verbose Parser". -# Verbose Parser prints the internal status when parsing. -# But it's _not_ automatic. -# You must use -g option and set +@yydebug+ to +true+ in order to get output. -# -g option only creates the verbose parser. -# -# === Racc reported syntax error. -# -# Isn't there too many "end"? -# grammar of racc file is changed in v0.10. -# -# Racc does not use '%' mark, while yacc uses huge number of '%' marks.. -# -# === Racc reported "XXXX conflicts". -# -# Try "racc -v xxxx.y". -# It causes producing racc's internal log file, xxxx.output. -# -# === Generated parsers does not work correctly -# -# Try "racc -g xxxx.y". -# This command let racc generate "debugging parser". -# Then set @yydebug=true in your parser. -# It produces a working log of your parser. -# -# == Re-distributing Racc runtime -# -# A parser, which is created by Racc, requires the Racc runtime module; -# racc/parser.rb. -# -# Ruby 1.8.x comes with Racc runtime module, -# you need NOT distribute Racc runtime files. -# -# If you want to include the Racc runtime module with your parser. -# This can be done by using '-E' option: -# -# $ racc -E -omyparser.rb myparser.y -# -# This command creates myparser.rb which `includes' Racc runtime. -# Only you must do is to distribute your parser file (myparser.rb). -# -# Note: parser.rb is ruby license, but your parser is not. -# Your own parser is completely yours. -module Racc - - unless defined?(Racc_No_Extensions) - Racc_No_Extensions = false # :nodoc: - end - - class Parser - - Racc_Runtime_Version = ::Racc::VERSION - Racc_Runtime_Core_Version_R = ::Racc::VERSION - - begin - if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby' - require 'jruby' - require 'racc/cparse-jruby.jar' - com.headius.racc.Cparse.new.load(JRuby.runtime, false) - else - require 'racc/cparse' - end - - unless new.respond_to?(:_racc_do_parse_c, true) - raise LoadError, 'old cparse.so' - end - if Racc_No_Extensions - raise LoadError, 'selecting ruby version of racc runtime core' - end - - Racc_Main_Parsing_Routine = :_racc_do_parse_c # :nodoc: - Racc_YY_Parse_Method = :_racc_yyparse_c # :nodoc: - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C # :nodoc: - Racc_Runtime_Type = 'c' # :nodoc: - rescue LoadError - Racc_Main_Parsing_Routine = :_racc_do_parse_rb - Racc_YY_Parse_Method = :_racc_yyparse_rb - Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R - Racc_Runtime_Type = 'ruby' - end - - def Parser.racc_runtime_type # :nodoc: - Racc_Runtime_Type - end - - def _racc_setup - @yydebug = false unless self.class::Racc_debug_parser - @yydebug = false unless defined?(@yydebug) - if @yydebug - @racc_debug_out = $stderr unless defined?(@racc_debug_out) - @racc_debug_out ||= $stderr - end - arg = self.class::Racc_arg - arg[13] = true if arg.size < 14 - arg - end - - def _racc_init_sysvars - @racc_state = [0] - @racc_tstack = [] - @racc_vstack = [] - - @racc_t = nil - @racc_val = nil - - @racc_read_next = true - - @racc_user_yyerror = false - @racc_error_status = 0 - end - - # The entry point of the parser. This method is used with #next_token. - # If Racc wants to get token (and its value), calls next_token. - # - # Example: - # def parse - # @q = [[1,1], - # [2,2], - # [3,3], - # [false, '$']] - # do_parse - # end - # - # def next_token - # @q.shift - # end - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def do_parse - #{Racc_Main_Parsing_Routine}(_racc_setup(), false) - end - RUBY - - # The method to fetch next token. - # If you use #do_parse method, you must implement #next_token. - # - # The format of return value is [TOKEN_SYMBOL, VALUE]. - # +token-symbol+ is represented by Ruby's symbol by default, e.g. :IDENT - # for 'IDENT'. ";" (String) for ';'. - # - # The final symbol (End of file) must be false. - def next_token - raise NotImplementedError, "#{self.class}\#next_token is not defined" - end - - def _racc_do_parse_rb(arg, in_debug) - action_table, action_check, action_default, action_pointer, - _, _, _, _, - _, _, token_table, * = arg - - _racc_init_sysvars - tok = act = i = nil - - catch(:racc_end_parse) { - while true - if i = action_pointer[@racc_state[-1]] - if @racc_read_next - if @racc_t != 0 # not EOF - tok, @racc_val = next_token() - unless tok # EOF - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - racc_read_token(@racc_t, tok, @racc_val) if @yydebug - @racc_read_next = false - end - end - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - else - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - } - end - - # Another entry point for the parser. - # If you use this method, you must implement RECEIVER#METHOD_ID method. - # - # RECEIVER#METHOD_ID is a method to get next token. - # It must 'yield' the token, which format is [TOKEN-SYMBOL, VALUE]. - class_eval <<~RUBY, __FILE__, __LINE__ + 1 - def yyparse(recv, mid) - #{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false) - end - RUBY - - def _racc_yyparse_rb(recv, mid, arg, c_debug) - action_table, action_check, action_default, action_pointer, - _, _, _, _, - _, _, token_table, * = arg - - _racc_init_sysvars - - catch(:racc_end_parse) { - until i = action_pointer[@racc_state[-1]] - while act = _racc_evalact(action_default[@racc_state[-1]], arg) - ; - end - end - recv.__send__(mid) do |tok, val| - unless tok - @racc_t = 0 - else - @racc_t = (token_table[tok] or 1) # error token - end - @racc_val = val - @racc_read_next = false - - i += @racc_t - unless i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - - while !(i = action_pointer[@racc_state[-1]]) || - ! @racc_read_next || - @racc_t == 0 # $ - unless i and i += @racc_t and - i >= 0 and - act = action_table[i] and - action_check[i] == @racc_state[-1] - act = action_default[@racc_state[-1]] - end - while act = _racc_evalact(act, arg) - ; - end - end - end - } - end - - ### - ### common - ### - - def _racc_evalact(act, arg) - action_table, action_check, _, action_pointer, - _, _, _, _, - _, _, _, shift_n, - reduce_n, * = arg - nerr = 0 # tmp - - if act > 0 and act < shift_n - # - # shift - # - if @racc_error_status > 0 - @racc_error_status -= 1 unless @racc_t <= 1 # error token or EOF - end - @racc_vstack.push @racc_val - @racc_state.push act - @racc_read_next = true - if @yydebug - @racc_tstack.push @racc_t - racc_shift @racc_t, @racc_tstack, @racc_vstack - end - - elsif act < 0 and act > -reduce_n - # - # reduce - # - code = catch(:racc_jump) { - @racc_state.push _racc_do_reduce(arg, act) - false - } - if code - case code - when 1 # yyerror - @racc_user_yyerror = true # user_yyerror - return -reduce_n - when 2 # yyaccept - return shift_n - else - raise '[Racc Bug] unknown jump code' - end - end - - elsif act == shift_n - # - # accept - # - racc_accept if @yydebug - throw :racc_end_parse, @racc_vstack[0] - - elsif act == -reduce_n - # - # error - # - case @racc_error_status - when 0 - unless arg[21] # user_yyerror - nerr += 1 - on_error @racc_t, @racc_val, @racc_vstack - end - when 3 - if @racc_t == 0 # is $ - # We're at EOF, and another error occurred immediately after - # attempting auto-recovery - throw :racc_end_parse, nil - end - @racc_read_next = true - end - @racc_user_yyerror = false - @racc_error_status = 3 - while true - if i = action_pointer[@racc_state[-1]] - i += 1 # error token - if i >= 0 and - (act = action_table[i]) and - action_check[i] == @racc_state[-1] - break - end - end - throw :racc_end_parse, nil if @racc_state.size <= 1 - @racc_state.pop - @racc_vstack.pop - if @yydebug - @racc_tstack.pop - racc_e_pop @racc_state, @racc_tstack, @racc_vstack - end - end - return act - - else - raise "[Racc Bug] unknown action #{act.inspect}" - end - - racc_next_state(@racc_state[-1], @racc_state) if @yydebug - - nil - end - - def _racc_do_reduce(arg, act) - _, _, _, _, - goto_table, goto_check, goto_default, goto_pointer, - nt_base, reduce_table, _, _, - _, use_result, * = arg - - state = @racc_state - vstack = @racc_vstack - tstack = @racc_tstack - - i = act * -3 - len = reduce_table[i] - reduce_to = reduce_table[i+1] - method_id = reduce_table[i+2] - void_array = [] - - tmp_t = tstack[-len, len] if @yydebug - tmp_v = vstack[-len, len] - tstack[-len, len] = void_array if @yydebug - vstack[-len, len] = void_array - state[-len, len] = void_array - - # tstack must be updated AFTER method call - if use_result - vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0]) - else - vstack.push __send__(method_id, tmp_v, vstack) - end - tstack.push reduce_to - - racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug - - k1 = reduce_to - nt_base - if i = goto_pointer[k1] - i += state[-1] - if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1 - return curstate - end - end - goto_default[k1] - end - - # This method is called when a parse error is found. - # - # ERROR_TOKEN_ID is an internal ID of token which caused error. - # You can get string representation of this ID by calling - # #token_to_str. - # - # ERROR_VALUE is a value of error token. - # - # value_stack is a stack of symbol values. - # DO NOT MODIFY this object. - # - # This method raises ParseError by default. - # - # If this method returns, parsers enter "error recovering mode". - def on_error(t, val, vstack) - raise ParseError, sprintf("parse error on value %s (%s)", - val.inspect, token_to_str(t) || '?') - end - - # Enter error recovering mode. - # This method does not call #on_error. - def yyerror - throw :racc_jump, 1 - end - - # Exit parser. - # Return value is +Symbol_Value_Stack[0]+. - def yyaccept - throw :racc_jump, 2 - end - - # Leave error recovering mode. - def yyerrok - @racc_error_status = 0 - end - - # For debugging output - def racc_read_token(t, tok, val) - @racc_debug_out.print 'read ' - @racc_debug_out.print tok.inspect, '(', racc_token2str(t), ') ' - @racc_debug_out.puts val.inspect - @racc_debug_out.puts - end - - def racc_shift(tok, tstack, vstack) - @racc_debug_out.puts "shift #{racc_token2str tok}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_reduce(toks, sim, tstack, vstack) - out = @racc_debug_out - out.print 'reduce ' - if toks.empty? - out.print ' <none>' - else - toks.each {|t| out.print ' ', racc_token2str(t) } - end - out.puts " --> #{racc_token2str(sim)}" - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_accept - @racc_debug_out.puts 'accept' - @racc_debug_out.puts - end - - def racc_e_pop(state, tstack, vstack) - @racc_debug_out.puts 'error recovering mode: pop token' - racc_print_states state - racc_print_stacks tstack, vstack - @racc_debug_out.puts - end - - def racc_next_state(curstate, state) - @racc_debug_out.puts "goto #{curstate}" - racc_print_states state - @racc_debug_out.puts - end - - def racc_print_stacks(t, v) - out = @racc_debug_out - out.print ' [' - t.each_index do |i| - out.print ' (', racc_token2str(t[i]), ' ', v[i].inspect, ')' - end - out.puts ' ]' - end - - def racc_print_states(s) - out = @racc_debug_out - out.print ' [' - s.each {|st| out.print ' ', st } - out.puts ' ]' - end - - def racc_token2str(tok) - self.class::Racc_token_to_s_table[tok] or - raise "[Racc Bug] can't convert token #{tok} to string" - end - - # Convert internal ID of token symbol to the string. - def token_to_str(t) - self.class::Racc_token_to_s_table[t] - end - - end - -end - -end -###### racc/parser.rb end - -require 'strscan' - -class RDoc::RD - -## -# RD format parser for inline markup such as emphasis, links, footnotes, etc. - -class InlineParser < Racc::Parser - - -# :stopdoc: - -EM_OPEN = '((*' -EM_OPEN_RE = /\A#{Regexp.quote(EM_OPEN)}/ -EM_CLOSE = '*))' -EM_CLOSE_RE = /\A#{Regexp.quote(EM_CLOSE)}/ -CODE_OPEN = '(({' -CODE_OPEN_RE = /\A#{Regexp.quote(CODE_OPEN)}/ -CODE_CLOSE = '}))' -CODE_CLOSE_RE = /\A#{Regexp.quote(CODE_CLOSE)}/ -VAR_OPEN = '((|' -VAR_OPEN_RE = /\A#{Regexp.quote(VAR_OPEN)}/ -VAR_CLOSE = '|))' -VAR_CLOSE_RE = /\A#{Regexp.quote(VAR_CLOSE)}/ -KBD_OPEN = '((%' -KBD_OPEN_RE = /\A#{Regexp.quote(KBD_OPEN)}/ -KBD_CLOSE = '%))' -KBD_CLOSE_RE = /\A#{Regexp.quote(KBD_CLOSE)}/ -INDEX_OPEN = '((:' -INDEX_OPEN_RE = /\A#{Regexp.quote(INDEX_OPEN)}/ -INDEX_CLOSE = ':))' -INDEX_CLOSE_RE = /\A#{Regexp.quote(INDEX_CLOSE)}/ -REF_OPEN = '((<' -REF_OPEN_RE = /\A#{Regexp.quote(REF_OPEN)}/ -REF_CLOSE = '>))' -REF_CLOSE_RE = /\A#{Regexp.quote(REF_CLOSE)}/ -FOOTNOTE_OPEN = '((-' -FOOTNOTE_OPEN_RE = /\A#{Regexp.quote(FOOTNOTE_OPEN)}/ -FOOTNOTE_CLOSE = '-))' -FOOTNOTE_CLOSE_RE = /\A#{Regexp.quote(FOOTNOTE_CLOSE)}/ -VERB_OPEN = "(('" -VERB_OPEN_RE = /\A#{Regexp.quote(VERB_OPEN)}/ -VERB_CLOSE = "'))" -VERB_CLOSE_RE = /\A#{Regexp.quote(VERB_CLOSE)}/ - -BAR = "|" -BAR_RE = /\A#{Regexp.quote(BAR)}/ -QUOTE = '"' -QUOTE_RE = /\A#{Regexp.quote(QUOTE)}/ -SLASH = "/" -SLASH_RE = /\A#{Regexp.quote(SLASH)}/ -BACK_SLASH = "\\" -BACK_SLASH_RE = /\A#{Regexp.quote(BACK_SLASH)}/ -URL = "URL:" -URL_RE = /\A#{Regexp.quote(URL)}/ - -other_re_mode = Regexp::EXTENDED -other_re_mode |= Regexp::MULTILINE - -OTHER_RE = Regexp.new( - "\\A.+?(?=#{Regexp.quote(EM_OPEN)}|#{Regexp.quote(EM_CLOSE)}| - #{Regexp.quote(CODE_OPEN)}|#{Regexp.quote(CODE_CLOSE)}| - #{Regexp.quote(VAR_OPEN)}|#{Regexp.quote(VAR_CLOSE)}| - #{Regexp.quote(KBD_OPEN)}|#{Regexp.quote(KBD_CLOSE)}| - #{Regexp.quote(INDEX_OPEN)}|#{Regexp.quote(INDEX_CLOSE)}| - #{Regexp.quote(REF_OPEN)}|#{Regexp.quote(REF_CLOSE)}| - #{Regexp.quote(FOOTNOTE_OPEN)}|#{Regexp.quote(FOOTNOTE_CLOSE)}| - #{Regexp.quote(VERB_OPEN)}|#{Regexp.quote(VERB_CLOSE)}| - #{Regexp.quote(BAR)}| - #{Regexp.quote(QUOTE)}| - #{Regexp.quote(SLASH)}| - #{Regexp.quote(BACK_SLASH)}| - #{Regexp.quote(URL)})", other_re_mode) - -# :startdoc: - -## -# Creates a new parser for inline markup in the rd format. The +block_parser+ -# is used to for footnotes and labels in the inline text. - -def initialize block_parser - @block_parser = block_parser -end - -## -# Parses the +inline+ text from RD format into RDoc format. - -def parse inline - @inline = inline - @src = StringScanner.new inline - @pre = "".dup - @yydebug = true - do_parse.to_s -end - -## -# Returns the next token from the inline text - -def next_token - return [false, false] if @src.eos? -# p @src.rest if @yydebug - if ret = @src.scan(EM_OPEN_RE) - @pre << ret - [:EM_OPEN, ret] - elsif ret = @src.scan(EM_CLOSE_RE) - @pre << ret - [:EM_CLOSE, ret] - elsif ret = @src.scan(CODE_OPEN_RE) - @pre << ret - [:CODE_OPEN, ret] - elsif ret = @src.scan(CODE_CLOSE_RE) - @pre << ret - [:CODE_CLOSE, ret] - elsif ret = @src.scan(VAR_OPEN_RE) - @pre << ret - [:VAR_OPEN, ret] - elsif ret = @src.scan(VAR_CLOSE_RE) - @pre << ret - [:VAR_CLOSE, ret] - elsif ret = @src.scan(KBD_OPEN_RE) - @pre << ret - [:KBD_OPEN, ret] - elsif ret = @src.scan(KBD_CLOSE_RE) - @pre << ret - [:KBD_CLOSE, ret] - elsif ret = @src.scan(INDEX_OPEN_RE) - @pre << ret - [:INDEX_OPEN, ret] - elsif ret = @src.scan(INDEX_CLOSE_RE) - @pre << ret - [:INDEX_CLOSE, ret] - elsif ret = @src.scan(REF_OPEN_RE) - @pre << ret - [:REF_OPEN, ret] - elsif ret = @src.scan(REF_CLOSE_RE) - @pre << ret - [:REF_CLOSE, ret] - elsif ret = @src.scan(FOOTNOTE_OPEN_RE) - @pre << ret - [:FOOTNOTE_OPEN, ret] - elsif ret = @src.scan(FOOTNOTE_CLOSE_RE) - @pre << ret - [:FOOTNOTE_CLOSE, ret] - elsif ret = @src.scan(VERB_OPEN_RE) - @pre << ret - [:VERB_OPEN, ret] - elsif ret = @src.scan(VERB_CLOSE_RE) - @pre << ret - [:VERB_CLOSE, ret] - elsif ret = @src.scan(BAR_RE) - @pre << ret - [:BAR, ret] - elsif ret = @src.scan(QUOTE_RE) - @pre << ret - [:QUOTE, ret] - elsif ret = @src.scan(SLASH_RE) - @pre << ret - [:SLASH, ret] - elsif ret = @src.scan(BACK_SLASH_RE) - @pre << ret - [:BACK_SLASH, ret] - elsif ret = @src.scan(URL_RE) - @pre << ret - [:URL, ret] - elsif ret = @src.scan(OTHER_RE) - @pre << ret - [:OTHER, ret] - else - ret = @src.rest - @pre << ret - @src.terminate - [:OTHER, ret] - end -end - -## -# Raises a ParseError when invalid formatting is found - -def on_error(et, ev, values) - lines_of_rest = @src.rest.lines.to_a.length - prev_words = prev_words_on_error(ev) - at = 4 + prev_words.length - - message = <<-MSG -RD syntax error: line #{@block_parser.line_index - lines_of_rest}: -...#{prev_words} #{(ev||'')} #{next_words_on_error()} ... - MSG - - message << " " * at + "^" * (ev ? ev.length : 0) + "\n" - raise ParseError, message -end - -## -# Returns words before the error - -def prev_words_on_error(ev) - pre = @pre - if ev and /#{Regexp.quote(ev)}$/ =~ pre - pre = $` - end - last_line(pre) -end - -## -# Returns the last line of +src+ - -def last_line(src) - if n = src.rindex("\n") - src[(n+1) .. -1] - else - src - end -end -private :last_line - -## -# Returns words following an error - -def next_words_on_error - if n = @src.rest.index("\n") - @src.rest[0 .. (n-1)] - else - @src.rest - end -end - -## -# Creates a new RDoc::RD::Inline for the +rdoc+ markup and the raw +reference+ - -def inline rdoc, reference = rdoc - RDoc::RD::Inline.new rdoc, reference -end - -# :stopdoc: -##### State transition tables begin ### - -racc_action_table = [ - 104, 103, 102, 100, 101, 99, 115, 116, 117, 29, - 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, - 84, 118, 119, 63, 64, 65, 61, 81, 62, 76, - 78, 79, 85, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 77, 80, 149, 63, 64, 65, 153, - 81, 62, 76, 78, 79, 86, 66, 67, 68, 69, - 70, 71, 72, 73, 74, 75, 77, 80, 152, 104, - 103, 102, 100, 101, 99, 115, 116, 117, 87, 105, - 106, 107, 108, 109, 110, 111, 112, 113, 114, 88, - 118, 119, 104, 103, 102, 100, 101, 99, 115, 116, - 117, 89, 105, 106, 107, 108, 109, 110, 111, 112, - 113, 114, 96, 118, 119, 104, 103, 102, 100, 101, - 99, 115, 116, 117, 124, 105, 106, 107, 108, 109, - 110, 111, 112, 113, 114, 137, 118, 119, 22, 23, - 24, 25, 26, 21, 18, 19, 176, 177, 13, 148, - 14, 154, 15, 137, 16, 161, 17, 164, 173, 20, - 22, 23, 24, 25, 26, 21, 18, 19, 175, 177, - 13, nil, 14, nil, 15, nil, 16, nil, 17, nil, - nil, 20, 22, 23, 24, 25, 26, 21, 18, 19, - nil, nil, 13, nil, 14, nil, 15, nil, 16, nil, - 17, nil, nil, 20, 22, 23, 24, 25, 26, 21, - 18, 19, nil, nil, 13, nil, 14, nil, 15, nil, - 16, nil, 17, nil, nil, 20, 22, 23, 24, 25, - 26, 21, 18, 19, nil, nil, 13, nil, 14, nil, - 15, nil, 16, nil, 17, nil, nil, 20, 22, 23, - 24, 25, 26, 21, 18, 19, nil, nil, 13, nil, - 14, nil, 15, nil, 16, nil, 17, nil, nil, 20, - 22, 23, 24, 25, 26, 21, 18, 19, nil, nil, - 13, nil, 14, nil, 15, nil, 16, nil, 17, 42, - nil, 20, 54, 38, 53, 55, 56, 57, nil, 13, - nil, 14, nil, 15, nil, 16, nil, 17, nil, nil, - 20, 22, 23, 24, 25, 26, 21, 18, 19, nil, - nil, 13, nil, 14, nil, 15, nil, 16, nil, 17, - nil, nil, 20, 63, 64, 65, 61, 81, 62, 76, - 78, 79, nil, 66, 67, 68, 69, 70, 71, 72, - 73, 74, 75, 77, 80, 122, nil, nil, 54, nil, - 53, 55, 56, 57, nil, 13, nil, 14, nil, 15, - nil, 16, nil, 17, 145, nil, 20, 54, 133, 53, - 55, 56, 57, nil, 13, nil, 14, nil, 15, nil, - 16, nil, 17, 145, nil, 20, 54, 133, 53, 55, - 56, 57, nil, 13, nil, 14, nil, 15, nil, 16, - nil, 17, 145, nil, 20, 54, 133, 53, 55, 56, - 57, nil, 13, nil, 14, nil, 15, nil, 16, nil, - 17, 145, nil, 20, 54, 133, 53, 55, 56, 57, - nil, 13, nil, 14, nil, 15, nil, 16, nil, 17, - nil, nil, 20, 135, 136, 54, 133, 53, 55, 56, - 57, nil, 13, nil, 14, nil, 15, nil, 16, nil, - 17, nil, nil, 20, 135, 136, 54, 133, 53, 55, - 56, 57, nil, 13, nil, 14, nil, 15, nil, 16, - nil, 17, nil, nil, 20, 135, 136, 54, 133, 53, - 55, 56, 57, nil, 13, nil, 14, nil, 15, nil, - 16, nil, 17, 95, nil, 20, 54, 91, 53, 55, - 56, 57, 145, nil, nil, 54, 133, 53, 55, 56, - 57, 158, nil, nil, 54, nil, 53, 55, 56, 57, - 165, 135, 136, 54, 133, 53, 55, 56, 57, 145, - nil, nil, 54, 133, 53, 55, 56, 57, 172, 135, - 136, 54, 133, 53, 55, 56, 57, 174, 135, 136, - 54, 133, 53, 55, 56, 57, 178, 135, 136, 54, - 133, 53, 55, 56, 57, 135, 136, 54, 133, 53, - 55, 56, 57, 135, 136, 54, 133, 53, 55, 56, - 57, 135, 136, 54, 133, 53, 55, 56, 57, 22, - 23, 24, 25, 26, 21 ] - -racc_action_check = [ - 38, 38, 38, 38, 38, 38, 38, 38, 38, 1, - 38, 38, 38, 38, 38, 38, 38, 38, 38, 38, - 29, 38, 38, 59, 59, 59, 59, 59, 59, 59, - 59, 59, 31, 59, 59, 59, 59, 59, 59, 59, - 59, 59, 59, 59, 59, 59, 61, 61, 61, 61, - 61, 61, 61, 61, 61, 32, 61, 61, 61, 61, - 61, 61, 61, 61, 61, 61, 61, 61, 61, 91, - 91, 91, 91, 91, 91, 91, 91, 91, 33, 91, - 91, 91, 91, 91, 91, 91, 91, 91, 91, 34, - 91, 91, 97, 97, 97, 97, 97, 97, 97, 97, - 97, 35, 97, 97, 97, 97, 97, 97, 97, 97, - 97, 97, 37, 97, 97, 155, 155, 155, 155, 155, - 155, 155, 155, 155, 41, 155, 155, 155, 155, 155, - 155, 155, 155, 155, 155, 43, 155, 155, 0, 0, - 0, 0, 0, 0, 0, 0, 165, 165, 0, 58, - 0, 90, 0, 94, 0, 100, 0, 125, 162, 0, - 2, 2, 2, 2, 2, 2, 2, 2, 164, 172, - 2, nil, 2, nil, 2, nil, 2, nil, 2, nil, - nil, 2, 13, 13, 13, 13, 13, 13, 13, 13, - nil, nil, 13, nil, 13, nil, 13, nil, 13, nil, - 13, nil, nil, 13, 14, 14, 14, 14, 14, 14, - 14, 14, nil, nil, 14, nil, 14, nil, 14, nil, - 14, nil, 14, nil, nil, 14, 15, 15, 15, 15, - 15, 15, 15, 15, nil, nil, 15, nil, 15, nil, - 15, nil, 15, nil, 15, nil, nil, 15, 16, 16, - 16, 16, 16, 16, 16, 16, nil, nil, 16, nil, - 16, nil, 16, nil, 16, nil, 16, nil, nil, 16, - 17, 17, 17, 17, 17, 17, 17, 17, nil, nil, - 17, nil, 17, nil, 17, nil, 17, nil, 17, 18, - nil, 17, 18, 18, 18, 18, 18, 18, nil, 18, - nil, 18, nil, 18, nil, 18, nil, 18, nil, nil, - 18, 19, 19, 19, 19, 19, 19, 19, 19, nil, - nil, 19, nil, 19, nil, 19, nil, 19, nil, 19, - nil, nil, 19, 20, 20, 20, 20, 20, 20, 20, - 20, 20, nil, 20, 20, 20, 20, 20, 20, 20, - 20, 20, 20, 20, 20, 39, nil, nil, 39, nil, - 39, 39, 39, 39, nil, 39, nil, 39, nil, 39, - nil, 39, nil, 39, 44, nil, 39, 44, 44, 44, - 44, 44, 44, nil, 44, nil, 44, nil, 44, nil, - 44, nil, 44, 45, nil, 44, 45, 45, 45, 45, - 45, 45, nil, 45, nil, 45, nil, 45, nil, 45, - nil, 45, 138, nil, 45, 138, 138, 138, 138, 138, - 138, nil, 138, nil, 138, nil, 138, nil, 138, nil, - 138, 146, nil, 138, 146, 146, 146, 146, 146, 146, - nil, 146, nil, 146, nil, 146, nil, 146, nil, 146, - nil, nil, 146, 42, 42, 42, 42, 42, 42, 42, - 42, nil, 42, nil, 42, nil, 42, nil, 42, nil, - 42, nil, nil, 42, 122, 122, 122, 122, 122, 122, - 122, 122, nil, 122, nil, 122, nil, 122, nil, 122, - nil, 122, nil, nil, 122, 127, 127, 127, 127, 127, - 127, 127, 127, nil, 127, nil, 127, nil, 127, nil, - 127, nil, 127, 36, nil, 127, 36, 36, 36, 36, - 36, 36, 52, nil, nil, 52, 52, 52, 52, 52, - 52, 92, nil, nil, 92, nil, 92, 92, 92, 92, - 126, 126, 126, 126, 126, 126, 126, 126, 126, 142, - nil, nil, 142, 142, 142, 142, 142, 142, 159, 159, - 159, 159, 159, 159, 159, 159, 159, 163, 163, 163, - 163, 163, 163, 163, 163, 163, 171, 171, 171, 171, - 171, 171, 171, 171, 171, 95, 95, 95, 95, 95, - 95, 95, 95, 158, 158, 158, 158, 158, 158, 158, - 158, 168, 168, 168, 168, 168, 168, 168, 168, 27, - 27, 27, 27, 27, 27 ] - -racc_action_pointer = [ - 135, 9, 157, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 179, 201, 223, 245, 267, 286, 308, - 330, nil, nil, nil, nil, nil, nil, 606, nil, 20, - nil, 18, 39, 60, 69, 79, 510, 89, -3, 352, - nil, 120, 449, 130, 371, 390, nil, nil, nil, nil, - nil, nil, 519, nil, nil, nil, nil, nil, 138, 20, - nil, 43, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 128, 66, 528, nil, 148, 581, nil, 89, nil, nil, - 149, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 470, nil, nil, 154, 537, 491, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 409, nil, - nil, nil, 546, nil, nil, nil, 428, nil, nil, nil, - nil, nil, nil, nil, nil, 112, nil, nil, 589, 555, - nil, nil, 155, 564, 164, 142, nil, nil, 597, nil, - nil, 573, 164, nil, nil, nil, nil, nil, nil ] - -racc_action_default = [ - -138, -138, -1, -3, -4, -5, -6, -7, -8, -9, - -10, -11, -12, -138, -138, -138, -138, -138, -138, -138, - -138, -103, -104, -105, -106, -107, -108, -111, -110, -138, - -2, -138, -138, -138, -138, -138, -138, -138, -138, -27, - -26, -35, -138, -58, -41, -40, -47, -48, -49, -50, - -51, -52, -63, -66, -67, -68, -69, -70, -138, -138, - -112, -138, -116, -117, -118, -119, -120, -121, -122, -123, - -124, -125, -126, -127, -128, -129, -130, -131, -132, -133, - -134, -135, -137, -109, 179, -13, -14, -15, -16, -17, - -138, -138, -23, -22, -33, -138, -19, -24, -79, -80, - -138, -82, -83, -84, -85, -86, -87, -88, -89, -90, - -91, -92, -93, -94, -95, -96, -97, -98, -99, -100, - -25, -35, -138, -58, -28, -138, -59, -42, -46, -55, - -56, -65, -71, -72, -75, -76, -77, -31, -38, -44, - -53, -54, -57, -61, -73, -74, -39, -62, -101, -102, - -136, -113, -114, -115, -18, -20, -21, -33, -138, -138, - -78, -81, -138, -59, -36, -37, -64, -45, -59, -43, - -60, -138, -34, -36, -37, -29, -30, -32, -34 ] - -racc_goto_table = [ - 126, 44, 125, 43, 144, 144, 160, 93, 97, 52, - 166, 82, 144, 40, 41, 39, 138, 146, 169, 30, - 36, 94, 44, 1, 123, 129, 169, 52, 90, 37, - 52, 167, 147, 92, 120, 121, 31, 32, 33, 34, - 35, 170, 58, 166, 59, 83, 170, 166, 151, nil, - 150, nil, 166, 159, 4, 166, 4, nil, nil, nil, - nil, 155, nil, 156, 160, nil, nil, 4, 4, 4, - 4, 4, nil, 4, 5, nil, 5, 157, nil, nil, - 163, nil, 162, 52, nil, 168, nil, 5, 5, 5, - 5, 5, nil, 5, nil, nil, nil, nil, 144, nil, - nil, nil, 144, nil, nil, 129, 144, 144, nil, 6, - 129, 6, nil, nil, nil, nil, 171, 7, nil, 7, - nil, nil, 6, 6, 6, 6, 6, 8, 6, 8, - 7, 7, 7, 7, 7, 11, 7, 11, nil, nil, - 8, 8, 8, 8, 8, nil, 8, nil, 11, 11, - 11, 11, 11, nil, 11 ] - -racc_goto_check = [ - 22, 24, 21, 23, 36, 36, 37, 18, 16, 34, - 35, 41, 36, 19, 20, 17, 25, 25, 28, 3, - 13, 23, 24, 1, 23, 24, 28, 34, 14, 15, - 34, 29, 32, 17, 19, 20, 1, 1, 1, 1, - 1, 33, 1, 35, 38, 39, 33, 35, 42, nil, - 41, nil, 35, 22, 4, 35, 4, nil, nil, nil, - nil, 16, nil, 18, 37, nil, nil, 4, 4, 4, - 4, 4, nil, 4, 5, nil, 5, 23, nil, nil, - 22, nil, 21, 34, nil, 22, nil, 5, 5, 5, - 5, 5, nil, 5, nil, nil, nil, nil, 36, nil, - nil, nil, 36, nil, nil, 24, 36, 36, nil, 6, - 24, 6, nil, nil, nil, nil, 22, 7, nil, 7, - nil, nil, 6, 6, 6, 6, 6, 8, 6, 8, - 7, 7, 7, 7, 7, 11, 7, 11, nil, nil, - 8, 8, 8, 8, 8, nil, 8, nil, 11, 11, - 11, 11, 11, nil, 11 ] - -racc_goto_pointer = [ - nil, 23, nil, 17, 54, 74, 109, 117, 127, nil, - nil, 135, nil, 2, -8, 11, -30, -3, -29, -5, - -4, -40, -42, -15, -17, -28, nil, nil, -120, -96, - nil, nil, -20, -101, -9, -116, -40, -91, 24, 18, - nil, -9, -13 ] - -racc_goto_default = [ - nil, nil, 2, 3, 46, 47, 48, 49, 50, 9, - 10, 51, 12, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 140, nil, 45, 127, 139, 128, - 141, 130, 142, 143, 132, 131, 134, 98, nil, 28, - 27, nil, 60 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 1, 27, :_reduce_none, - 2, 28, :_reduce_2, - 1, 28, :_reduce_3, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 1, 29, :_reduce_none, - 3, 30, :_reduce_13, - 3, 31, :_reduce_14, - 3, 32, :_reduce_15, - 3, 33, :_reduce_16, - 3, 34, :_reduce_17, - 4, 35, :_reduce_18, - 3, 35, :_reduce_19, - 2, 40, :_reduce_20, - 2, 40, :_reduce_21, - 1, 40, :_reduce_22, - 1, 40, :_reduce_23, - 2, 41, :_reduce_24, - 2, 41, :_reduce_25, - 1, 41, :_reduce_26, - 1, 41, :_reduce_27, - 2, 39, :_reduce_none, - 4, 39, :_reduce_29, - 4, 39, :_reduce_30, - 2, 43, :_reduce_31, - 4, 43, :_reduce_32, - 1, 44, :_reduce_33, - 3, 44, :_reduce_34, - 1, 45, :_reduce_none, - 3, 45, :_reduce_36, - 3, 45, :_reduce_37, - 2, 46, :_reduce_38, - 2, 46, :_reduce_39, - 1, 46, :_reduce_40, - 1, 46, :_reduce_41, - 1, 47, :_reduce_none, - 2, 51, :_reduce_43, - 1, 51, :_reduce_44, - 2, 53, :_reduce_45, - 1, 53, :_reduce_46, - 1, 50, :_reduce_none, - 1, 50, :_reduce_none, - 1, 50, :_reduce_none, - 1, 50, :_reduce_none, - 1, 50, :_reduce_none, - 1, 50, :_reduce_none, - 1, 54, :_reduce_none, - 1, 54, :_reduce_none, - 1, 55, :_reduce_none, - 1, 55, :_reduce_none, - 1, 56, :_reduce_57, - 1, 52, :_reduce_58, - 1, 57, :_reduce_59, - 2, 58, :_reduce_60, - 1, 58, :_reduce_none, - 2, 49, :_reduce_62, - 1, 49, :_reduce_none, - 2, 48, :_reduce_64, - 1, 48, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 60, :_reduce_none, - 1, 62, :_reduce_none, - 1, 62, :_reduce_none, - 1, 59, :_reduce_none, - 1, 59, :_reduce_none, - 1, 61, :_reduce_none, - 1, 61, :_reduce_none, - 1, 61, :_reduce_none, - 2, 42, :_reduce_78, - 1, 42, :_reduce_none, - 1, 63, :_reduce_none, - 2, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 1, 63, :_reduce_none, - 3, 36, :_reduce_101, - 3, 37, :_reduce_102, - 1, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 65, :_reduce_none, - 1, 65, :_reduce_none, - 2, 66, :_reduce_109, - 1, 66, :_reduce_none, - 1, 38, :_reduce_111, - 1, 67, :_reduce_none, - 2, 67, :_reduce_113, - 2, 67, :_reduce_114, - 2, 67, :_reduce_115, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 1, 68, :_reduce_none, - 2, 64, :_reduce_136, - 1, 64, :_reduce_none ] - -racc_reduce_n = 138 - -racc_shift_n = 179 - -racc_token_table = { - false => 0, - :error => 1, - :EX_LOW => 2, - :QUOTE => 3, - :BAR => 4, - :SLASH => 5, - :BACK_SLASH => 6, - :URL => 7, - :OTHER => 8, - :REF_OPEN => 9, - :FOOTNOTE_OPEN => 10, - :FOOTNOTE_CLOSE => 11, - :EX_HIGH => 12, - :EM_OPEN => 13, - :EM_CLOSE => 14, - :CODE_OPEN => 15, - :CODE_CLOSE => 16, - :VAR_OPEN => 17, - :VAR_CLOSE => 18, - :KBD_OPEN => 19, - :KBD_CLOSE => 20, - :INDEX_OPEN => 21, - :INDEX_CLOSE => 22, - :REF_CLOSE => 23, - :VERB_OPEN => 24, - :VERB_CLOSE => 25 } - -racc_nt_base = 26 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] -Ractor.make_shareable(Racc_arg) if defined?(Ractor) - -Racc_token_to_s_table = [ - "$end", - "error", - "EX_LOW", - "QUOTE", - "BAR", - "SLASH", - "BACK_SLASH", - "URL", - "OTHER", - "REF_OPEN", - "FOOTNOTE_OPEN", - "FOOTNOTE_CLOSE", - "EX_HIGH", - "EM_OPEN", - "EM_CLOSE", - "CODE_OPEN", - "CODE_CLOSE", - "VAR_OPEN", - "VAR_CLOSE", - "KBD_OPEN", - "KBD_CLOSE", - "INDEX_OPEN", - "INDEX_CLOSE", - "REF_CLOSE", - "VERB_OPEN", - "VERB_CLOSE", - "$start", - "content", - "elements", - "element", - "emphasis", - "code", - "var", - "keyboard", - "index", - "reference", - "footnote", - "verb", - "normal_str_ele", - "substitute", - "ref_label", - "ref_label2", - "ref_url_strings", - "filename", - "element_label", - "element_label2", - "ref_subst_content", - "ref_subst_content_q", - "ref_subst_strings_q", - "ref_subst_strings_first", - "ref_subst_ele2", - "ref_subst_eles", - "ref_subst_str_ele_first", - "ref_subst_eles_q", - "ref_subst_ele", - "ref_subst_ele_q", - "ref_subst_str_ele", - "ref_subst_str_ele_q", - "ref_subst_strings", - "ref_subst_string3", - "ref_subst_string", - "ref_subst_string_q", - "ref_subst_string2", - "ref_url_string", - "verb_strings", - "normal_string", - "normal_strings", - "verb_string", - "verb_normal_string" ] -Ractor.make_shareable(Racc_token_to_s_table) if defined?(Ractor) - -Racc_debug_parser = false - -##### State transition tables end ##### - -# reduce 0 omitted - -# reduce 1 omitted - -def _reduce_2(val, _values, result) - result.append val[1] - result -end - -def _reduce_3(val, _values, result) - result = val[0] - result -end - -# reduce 4 omitted - -# reduce 5 omitted - -# reduce 6 omitted - -# reduce 7 omitted - -# reduce 8 omitted - -# reduce 9 omitted - -# reduce 10 omitted - -# reduce 11 omitted - -# reduce 12 omitted - -def _reduce_13(val, _values, result) - content = val[1] - result = inline "<em>#{content}</em>", content - - result -end - -def _reduce_14(val, _values, result) - content = val[1] - result = inline "<code>#{content}</code>", content - - result -end - -def _reduce_15(val, _values, result) - content = val[1] - result = inline "+#{content}+", content - - result -end - -def _reduce_16(val, _values, result) - content = val[1] - result = inline "<tt>#{content}</tt>", content - - result -end - -def _reduce_17(val, _values, result) - label = val[1] - @block_parser.add_label label.reference - result = "<span id=\"label-#{label}\">#{label}</span>" - - result -end - -def _reduce_18(val, _values, result) - result = "{#{val[1]}}[#{val[2].join}]" - - result -end - -def _reduce_19(val, _values, result) - scheme, inline = val[1] - - result = "{#{inline}}[#{scheme}#{inline.reference}]" - - result -end - -def _reduce_20(val, _values, result) - result = [nil, inline(val[1])] - - result -end - -def _reduce_21(val, _values, result) - result = [ - 'rdoc-label:', - inline("#{val[0].reference}/#{val[1].reference}") - ] - - result -end - -def _reduce_22(val, _values, result) - result = ['rdoc-label:', val[0].reference] - - result -end - -def _reduce_23(val, _values, result) - result = ['rdoc-label:', "#{val[0].reference}/"] - - result -end - -def _reduce_24(val, _values, result) - result = [nil, inline(val[1])] - - result -end - -def _reduce_25(val, _values, result) - result = [ - 'rdoc-label:', - inline("#{val[0].reference}/#{val[1].reference}") - ] - - result -end - -def _reduce_26(val, _values, result) - result = ['rdoc-label:', val[0]] - - result -end - -def _reduce_27(val, _values, result) - ref = val[0].reference - result = ['rdoc-label:', inline(ref, "#{ref}/")] - - result -end - -# reduce 28 omitted - -def _reduce_29(val, _values, result) - result = val[1] - result -end - -def _reduce_30(val, _values, result) - result = val[1] - result -end - -def _reduce_31(val, _values, result) - result = inline val[0] - - result -end - -def _reduce_32(val, _values, result) - result = inline "\"#{val[1]}\"" - - result -end - -def _reduce_33(val, _values, result) - result = inline val[0] - - result -end - -def _reduce_34(val, _values, result) - result = inline "\"#{val[1]}\"" - - result -end - -# reduce 35 omitted - -def _reduce_36(val, _values, result) - result = val[1] - result -end - -def _reduce_37(val, _values, result) - result = inline val[1] - result -end - -def _reduce_38(val, _values, result) - result = val[0].append val[1] - - result -end - -def _reduce_39(val, _values, result) - result = val[0].append val[1] - - result -end - -def _reduce_40(val, _values, result) - result = val[0] - - result -end - -def _reduce_41(val, _values, result) - result = inline val[0] - - result -end - -# reduce 42 omitted - -def _reduce_43(val, _values, result) - result = val[0].append val[1] - - result -end - -def _reduce_44(val, _values, result) - result = inline val[0] - - result -end - -def _reduce_45(val, _values, result) - result = val[0].append val[1] - - result -end - -def _reduce_46(val, _values, result) - result = val[0] - - result -end - -# reduce 47 omitted - -# reduce 48 omitted - -# reduce 49 omitted - -# reduce 50 omitted - -# reduce 51 omitted - -# reduce 52 omitted - -# reduce 53 omitted - -# reduce 54 omitted - -# reduce 55 omitted - -# reduce 56 omitted - -def _reduce_57(val, _values, result) - result = val[0] - - result -end - -def _reduce_58(val, _values, result) - result = inline val[0] - - result -end - -def _reduce_59(val, _values, result) - result = inline val[0] - - result -end - -def _reduce_60(val, _values, result) - result << val[1] - result -end - -# reduce 61 omitted - -def _reduce_62(val, _values, result) - result << val[1] - - result -end - -# reduce 63 omitted - -def _reduce_64(val, _values, result) - result << val[1] - - result -end - -# reduce 65 omitted - -# reduce 66 omitted - -# reduce 67 omitted - -# reduce 68 omitted - -# reduce 69 omitted - -# reduce 70 omitted - -# reduce 71 omitted - -# reduce 72 omitted - -# reduce 73 omitted - -# reduce 74 omitted - -# reduce 75 omitted - -# reduce 76 omitted - -# reduce 77 omitted - -def _reduce_78(val, _values, result) - result << val[1] - result -end - -# reduce 79 omitted - -# reduce 80 omitted - -# reduce 81 omitted - -# reduce 82 omitted - -# reduce 83 omitted - -# reduce 84 omitted - -# reduce 85 omitted - -# reduce 86 omitted - -# reduce 87 omitted - -# reduce 88 omitted - -# reduce 89 omitted - -# reduce 90 omitted - -# reduce 91 omitted - -# reduce 92 omitted - -# reduce 93 omitted - -# reduce 94 omitted - -# reduce 95 omitted - -# reduce 96 omitted - -# reduce 97 omitted - -# reduce 98 omitted - -# reduce 99 omitted - -# reduce 100 omitted - -def _reduce_101(val, _values, result) - index = @block_parser.add_footnote val[1].rdoc - result = "{*#{index}}[rdoc-label:foottext-#{index}:footmark-#{index}]" - - result -end - -def _reduce_102(val, _values, result) - result = inline "<tt>#{val[1]}</tt>", val[1] - - result -end - -# reduce 103 omitted - -# reduce 104 omitted - -# reduce 105 omitted - -# reduce 106 omitted - -# reduce 107 omitted - -# reduce 108 omitted - -def _reduce_109(val, _values, result) - result << val[1] - result -end - -# reduce 110 omitted - -def _reduce_111(val, _values, result) - result = inline val[0] - - result -end - -# reduce 112 omitted - -def _reduce_113(val, _values, result) - result = val[1] - result -end - -def _reduce_114(val, _values, result) - result = val[1] - result -end - -def _reduce_115(val, _values, result) - result = val[1] - result -end - -# reduce 116 omitted - -# reduce 117 omitted - -# reduce 118 omitted - -# reduce 119 omitted - -# reduce 120 omitted - -# reduce 121 omitted - -# reduce 122 omitted - -# reduce 123 omitted - -# reduce 124 omitted - -# reduce 125 omitted - -# reduce 126 omitted - -# reduce 127 omitted - -# reduce 128 omitted - -# reduce 129 omitted - -# reduce 130 omitted - -# reduce 131 omitted - -# reduce 132 omitted - -# reduce 133 omitted - -# reduce 134 omitted - -# reduce 135 omitted - -def _reduce_136(val, _values, result) - result << val[1] - result -end - -# reduce 137 omitted - -def _reduce_none(val, _values, result) - val[0] -end - -end # class InlineParser - -end diff --git a/lib/rdoc/rdoc.gemspec b/lib/rdoc/rdoc.gemspec deleted file mode 100644 index 26f9ba1a87..0000000000 --- a/lib/rdoc/rdoc.gemspec +++ /dev/null @@ -1,237 +0,0 @@ -begin - require_relative "lib/rdoc/version" -rescue LoadError - # for Ruby repository - require_relative "version" -end - -Gem::Specification.new do |s| - s.name = "rdoc" - s.version = RDoc::VERSION - - s.authors = [ - "Eric Hodel", - "Dave Thomas", - "Phil Hagelberg", - "Tony Strauss", - "Zachary Scott", - "Hiroshi SHIBATA", - "ITOYANAGI Sakura" - ] - s.email = ["drbrain@segment7.net", "", "", "", "mail@zzak.io", "hsbt@ruby-lang.org", "aycabta@gmail.com"] - - s.summary = "RDoc produces HTML and command-line documentation for Ruby projects" - s.description = <<-DESCRIPTION -RDoc produces HTML and command-line documentation for Ruby projects. -RDoc includes the +rdoc+ and +ri+ tools for generating and displaying documentation from the command-line. - DESCRIPTION - s.homepage = "https://ruby.github.io/rdoc" - s.licenses = ["Ruby"] - - s.metadata["homepage_uri"] = s.homepage - s.metadata["source_code_uri"] = "https://github.com/ruby/rdoc" - s.metadata["changelog_uri"] = "#{s.metadata["source_code_uri"]}/releases" - - s.bindir = "exe" - s.executables = ["rdoc", "ri"] - s.require_paths = ["lib"] - # for ruby core repository. It was generated by - # `git ls-files -z`.split("\x0").each {|f| puts " #{f.dump}," unless f.start_with?(*%W[test/ spec/ features/ .]) } - s.files = [ - "CONTRIBUTING.rdoc", - "CVE-2013-0256.rdoc", - "ExampleMarkdown.md", - "ExampleRDoc.rdoc", - "History.rdoc", - "LEGAL.rdoc", - "LICENSE.rdoc", - "README.rdoc", - "RI.md", - "TODO.rdoc", - "exe/rdoc", - "exe/ri", - "lib/rdoc.rb", - "lib/rdoc/code_object/alias.rb", - "lib/rdoc/code_object/anon_class.rb", - "lib/rdoc/code_object/any_method.rb", - "lib/rdoc/code_object/attr.rb", - "lib/rdoc/code_object/class_module.rb", - "lib/rdoc/code_object.rb", - "lib/rdoc/code_objects.rb", - "lib/rdoc/comment.rb", - "lib/rdoc/code_object/constant.rb", - "lib/rdoc/code_object/context.rb", - "lib/rdoc/code_object/context/section.rb", - "lib/rdoc/cross_reference.rb", - "lib/rdoc/encoding.rb", - "lib/rdoc/erb_partial.rb", - "lib/rdoc/erbio.rb", - "lib/rdoc/code_object/extend.rb", - "lib/rdoc/generator.rb", - "lib/rdoc/generator/darkfish.rb", - "lib/rdoc/generator/json_index.rb", - "lib/rdoc/generator/markup.rb", - "lib/rdoc/generator/pot.rb", - "lib/rdoc/generator/pot/message_extractor.rb", - "lib/rdoc/generator/pot/po.rb", - "lib/rdoc/generator/pot/po_entry.rb", - "lib/rdoc/generator/ri.rb", - "lib/rdoc/generator/template/darkfish/.document", - "lib/rdoc/generator/template/darkfish/_footer.rhtml", - "lib/rdoc/generator/template/darkfish/_head.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_VCS_info.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_classes.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_extends.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_in_files.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_includes.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_installed.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_methods.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_navigation.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_pages.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_parent.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_search.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_sections.rhtml", - "lib/rdoc/generator/template/darkfish/_sidebar_table_of_contents.rhtml", - "lib/rdoc/generator/template/darkfish/class.rhtml", - "lib/rdoc/generator/template/darkfish/css/fonts.css", - "lib/rdoc/generator/template/darkfish/css/rdoc.css", - "lib/rdoc/generator/template/darkfish/fonts/Lato-Light.ttf", - "lib/rdoc/generator/template/darkfish/fonts/Lato-LightItalic.ttf", - "lib/rdoc/generator/template/darkfish/fonts/Lato-Regular.ttf", - "lib/rdoc/generator/template/darkfish/fonts/Lato-RegularItalic.ttf", - "lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Bold.ttf", - "lib/rdoc/generator/template/darkfish/fonts/SourceCodePro-Regular.ttf", - "lib/rdoc/generator/template/darkfish/images/add.png", - "lib/rdoc/generator/template/darkfish/images/arrow_up.png", - "lib/rdoc/generator/template/darkfish/images/brick.png", - "lib/rdoc/generator/template/darkfish/images/brick_link.png", - "lib/rdoc/generator/template/darkfish/images/bug.png", - "lib/rdoc/generator/template/darkfish/images/bullet_black.png", - "lib/rdoc/generator/template/darkfish/images/bullet_toggle_minus.png", - "lib/rdoc/generator/template/darkfish/images/bullet_toggle_plus.png", - "lib/rdoc/generator/template/darkfish/images/date.png", - "lib/rdoc/generator/template/darkfish/images/delete.png", - "lib/rdoc/generator/template/darkfish/images/find.png", - "lib/rdoc/generator/template/darkfish/images/loadingAnimation.gif", - "lib/rdoc/generator/template/darkfish/images/macFFBgHack.png", - "lib/rdoc/generator/template/darkfish/images/package.png", - "lib/rdoc/generator/template/darkfish/images/page_green.png", - "lib/rdoc/generator/template/darkfish/images/page_white_text.png", - "lib/rdoc/generator/template/darkfish/images/page_white_width.png", - "lib/rdoc/generator/template/darkfish/images/plugin.png", - "lib/rdoc/generator/template/darkfish/images/ruby.png", - "lib/rdoc/generator/template/darkfish/images/tag_blue.png", - "lib/rdoc/generator/template/darkfish/images/tag_green.png", - "lib/rdoc/generator/template/darkfish/images/transparent.png", - "lib/rdoc/generator/template/darkfish/images/wrench.png", - "lib/rdoc/generator/template/darkfish/images/wrench_orange.png", - "lib/rdoc/generator/template/darkfish/images/zoom.png", - "lib/rdoc/generator/template/darkfish/index.rhtml", - "lib/rdoc/generator/template/darkfish/js/darkfish.js", - "lib/rdoc/generator/template/darkfish/js/search.js", - "lib/rdoc/generator/template/darkfish/page.rhtml", - "lib/rdoc/generator/template/darkfish/servlet_not_found.rhtml", - "lib/rdoc/generator/template/darkfish/servlet_root.rhtml", - "lib/rdoc/generator/template/darkfish/table_of_contents.rhtml", - "lib/rdoc/generator/template/json_index/.document", - "lib/rdoc/generator/template/json_index/js/navigation.js", - "lib/rdoc/generator/template/json_index/js/searcher.js", - "lib/rdoc/code_object/ghost_method.rb", - "lib/rdoc/i18n.rb", - "lib/rdoc/i18n/locale.rb", - "lib/rdoc/i18n/text.rb", - "lib/rdoc/code_object/include.rb", - "lib/rdoc/known_classes.rb", - "lib/rdoc/markdown.kpeg", - "lib/rdoc/markdown/entities.rb", - "lib/rdoc/markdown/literals.kpeg", - "lib/rdoc/markup.rb", - "lib/rdoc/markup/attr_changer.rb", - "lib/rdoc/markup/attr_span.rb", - "lib/rdoc/markup/attribute_manager.rb", - "lib/rdoc/markup/attributes.rb", - "lib/rdoc/markup/blank_line.rb", - "lib/rdoc/markup/block_quote.rb", - "lib/rdoc/markup/document.rb", - "lib/rdoc/markup/formatter.rb", - "lib/rdoc/markup/hard_break.rb", - "lib/rdoc/markup/heading.rb", - "lib/rdoc/markup/include.rb", - "lib/rdoc/markup/indented_paragraph.rb", - "lib/rdoc/markup/list.rb", - "lib/rdoc/markup/list_item.rb", - "lib/rdoc/markup/paragraph.rb", - "lib/rdoc/markup/parser.rb", - "lib/rdoc/markup/pre_process.rb", - "lib/rdoc/markup/raw.rb", - "lib/rdoc/markup/regexp_handling.rb", - "lib/rdoc/markup/rule.rb", - "lib/rdoc/markup/table.rb", - "lib/rdoc/markup/to_ansi.rb", - "lib/rdoc/markup/to_bs.rb", - "lib/rdoc/markup/to_html.rb", - "lib/rdoc/markup/to_html_crossref.rb", - "lib/rdoc/markup/to_html_snippet.rb", - "lib/rdoc/markup/to_joined_paragraph.rb", - "lib/rdoc/markup/to_label.rb", - "lib/rdoc/markup/to_markdown.rb", - "lib/rdoc/markup/to_rdoc.rb", - "lib/rdoc/markup/to_table_of_contents.rb", - "lib/rdoc/markup/to_test.rb", - "lib/rdoc/markup/to_tt_only.rb", - "lib/rdoc/markup/verbatim.rb", - "lib/rdoc/code_object/meta_method.rb", - "lib/rdoc/code_object/method_attr.rb", - "lib/rdoc/code_object/mixin.rb", - "lib/rdoc/code_object/normal_class.rb", - "lib/rdoc/code_object/normal_module.rb", - "lib/rdoc/options.rb", - "lib/rdoc/parser.rb", - "lib/rdoc/parser/c.rb", - "lib/rdoc/parser/changelog.rb", - "lib/rdoc/parser/markdown.rb", - "lib/rdoc/parser/rd.rb", - "lib/rdoc/parser/ripper_state_lex.rb", - "lib/rdoc/parser/ruby.rb", - "lib/rdoc/parser/ruby_tools.rb", - "lib/rdoc/parser/simple.rb", - "lib/rdoc/parser/text.rb", - "lib/rdoc/rd.rb", - "lib/rdoc/rd/block_parser.ry", - "lib/rdoc/rd/inline.rb", - "lib/rdoc/rd/inline_parser.ry", - "lib/rdoc/rdoc.rb", - "lib/rdoc/code_object/require.rb", - "lib/rdoc/ri.rb", - "lib/rdoc/ri/driver.rb", - "lib/rdoc/ri/formatter.rb", - "lib/rdoc/ri/paths.rb", - "lib/rdoc/ri/store.rb", - "lib/rdoc/ri/task.rb", - "lib/rdoc/rubygems_hook.rb", - "lib/rdoc/servlet.rb", - "lib/rdoc/code_object/single_class.rb", - "lib/rdoc/stats.rb", - "lib/rdoc/stats/normal.rb", - "lib/rdoc/stats/quiet.rb", - "lib/rdoc/stats/verbose.rb", - "lib/rdoc/store.rb", - "lib/rdoc/task.rb", - "lib/rdoc/text.rb", - "lib/rdoc/token_stream.rb", - "lib/rdoc/tom_doc.rb", - "lib/rdoc/code_object/top_level.rb", - "lib/rdoc/version.rb", - "man/ri.1", - ] - # files from .gitignore - s.files << "lib/rdoc/rd/block_parser.rb" << "lib/rdoc/rd/inline_parser.rb" << "lib/rdoc/markdown.rb" << "lib/rdoc/markdown/literals.rb" - - s.rdoc_options = ["--main", "README.rdoc"] - s.extra_rdoc_files += s.files.grep(%r[\A[^\/]+\.(?:rdoc|md)\z]) - - s.required_ruby_version = Gem::Requirement.new(">= 2.6.0") - s.required_rubygems_version = Gem::Requirement.new(">= 2.2") - - s.add_dependency 'psych', '>= 4.0.0' -end diff --git a/lib/rdoc/rdoc.rb b/lib/rdoc/rdoc.rb deleted file mode 100644 index 88ae55b409..0000000000 --- a/lib/rdoc/rdoc.rb +++ /dev/null @@ -1,564 +0,0 @@ -# frozen_string_literal: true -require_relative '../rdoc' - -require 'find' -require 'fileutils' -require 'pathname' -require 'time' - -## -# This is the driver for generating RDoc output. It handles file parsing and -# generation of output. -# -# To use this class to generate RDoc output via the API, the recommended way -# is: -# -# rdoc = RDoc::RDoc.new -# options = RDoc::Options.load_options # returns an RDoc::Options instance -# # set extra options -# rdoc.document options -# -# You can also generate output like the +rdoc+ executable: -# -# rdoc = RDoc::RDoc.new -# rdoc.document argv -# -# Where +argv+ is an array of strings, each corresponding to an argument you'd -# give rdoc on the command line. See <tt>rdoc --help</tt> for details. - -class RDoc::RDoc - - @current = nil - - ## - # This is the list of supported output generators - - GENERATORS = {} - - ## - # List of directory names always skipped - - UNCONDITIONALLY_SKIPPED_DIRECTORIES = %w[CVS .svn .git].freeze - - ## - # List of directory names skipped if test suites should be skipped - - TEST_SUITE_DIRECTORY_NAMES = %w[spec test].freeze - - - ## - # Generator instance used for creating output - - attr_accessor :generator - - ## - # Hash of files and their last modified times. - - attr_reader :last_modified - - ## - # RDoc options - - attr_accessor :options - - ## - # Accessor for statistics. Available after each call to parse_files - - attr_reader :stats - - ## - # The current documentation store - - attr_reader :store - - ## - # Add +klass+ that can generate output after parsing - - def self.add_generator(klass) - name = klass.name.sub(/^RDoc::Generator::/, '').downcase - GENERATORS[name] = klass - end - - ## - # Active RDoc::RDoc instance - - def self.current - @current - end - - ## - # Sets the active RDoc::RDoc instance - - def self.current= rdoc - @current = rdoc - end - - ## - # Creates a new RDoc::RDoc instance. Call #document to parse files and - # generate documentation. - - def initialize - @current = nil - @generator = nil - @last_modified = {} - @old_siginfo = nil - @options = nil - @stats = nil - @store = nil - end - - ## - # Report an error message and exit - - def error(msg) - raise RDoc::Error, msg - end - - ## - # Gathers a set of parseable files from the files and directories listed in - # +files+. - - def gather_files files - files = [@options.root.to_s] if files.empty? - - file_list = normalized_file_list files, true, @options.exclude - - file_list = remove_unparseable(file_list) - - if file_list.count {|name, mtime| - file_list[name] = @last_modified[name] unless mtime - mtime - } > 0 - @last_modified.replace file_list - file_list.keys.sort - else - [] - end - end - - ## - # Turns RDoc from stdin into HTML - - def handle_pipe - @html = RDoc::Markup::ToHtml.new @options - - parser = RDoc::Text::MARKUP_FORMAT[@options.markup] - - document = parser.parse $stdin.read - - out = @html.convert document - - $stdout.write out - end - - ## - # Installs a siginfo handler that prints the current filename. - - def install_siginfo_handler - return unless Signal.list.include? 'INFO' - - @old_siginfo = trap 'INFO' do - puts @current if @current - end - end - - ## - # Create an output dir if it doesn't exist. If it does exist, but doesn't - # contain the flag file <tt>created.rid</tt> then we refuse to use it, as - # we may clobber some manually generated documentation - - def setup_output_dir(dir, force) - flag_file = output_flag_file dir - - last = {} - - if @options.dry_run then - # do nothing - elsif File.exist? dir then - error "#{dir} exists and is not a directory" unless File.directory? dir - - begin - File.open flag_file do |io| - unless force then - Time.parse io.gets - - io.each do |line| - file, time = line.split "\t", 2 - time = Time.parse(time) rescue next - last[file] = time - end - end - end - rescue SystemCallError, TypeError - error <<-ERROR - -Directory #{dir} already exists, but it looks like it isn't an RDoc directory. - -Because RDoc doesn't want to risk destroying any of your existing files, -you'll need to specify a different output directory name (using the --op <dir> -option) - - ERROR - end unless @options.force_output - else - FileUtils.mkdir_p dir - FileUtils.touch flag_file - end - - last - end - - ## - # Sets the current documentation tree to +store+ and sets the store's rdoc - # driver to this instance. - - def store= store - @store = store - @store.rdoc = self - end - - ## - # Update the flag file in an output directory. - - def update_output_dir(op_dir, time, last = {}) - return if @options.dry_run or not @options.update_output_dir - unless ENV['SOURCE_DATE_EPOCH'].nil? - time = Time.at(ENV['SOURCE_DATE_EPOCH'].to_i).gmtime - end - - File.open output_flag_file(op_dir), "w" do |f| - f.puts time.rfc2822 - last.each do |n, t| - f.puts "#{n}\t#{t.rfc2822}" - end - end - end - - ## - # Return the path name of the flag file in an output directory. - - def output_flag_file(op_dir) - File.join op_dir, "created.rid" - end - - ## - # The .document file contains a list of file and directory name patterns, - # representing candidates for documentation. It may also contain comments - # (starting with '#') - - def parse_dot_doc_file in_dir, filename - # read and strip comments - patterns = File.read(filename).gsub(/#.*/, '') - - result = {} - - patterns.split(' ').each do |patt| - candidates = Dir.glob(File.join(in_dir, patt)) - result.update normalized_file_list(candidates, false, @options.exclude) - end - - result - end - - ## - # Given a list of files and directories, create a list of all the Ruby - # files they contain. - # - # If +force_doc+ is true we always add the given files, if false, only - # add files that we guarantee we can parse. It is true when looking at - # files given on the command line, false when recursing through - # subdirectories. - # - # The effect of this is that if you want a file with a non-standard - # extension parsed, you must name it explicitly. - - def normalized_file_list(relative_files, force_doc = false, - exclude_pattern = nil) - file_list = {} - - relative_files.each do |rel_file_name| - rel_file_name = rel_file_name.sub(/^\.\//, '') - next if rel_file_name.end_with? 'created.rid' - next if exclude_pattern && exclude_pattern =~ rel_file_name - stat = File.stat rel_file_name rescue next - - case type = stat.ftype - when "file" then - mtime = (stat.mtime unless (last_modified = @last_modified[rel_file_name] and - stat.mtime.to_i <= last_modified.to_i)) - - if force_doc or RDoc::Parser.can_parse(rel_file_name) then - file_list[rel_file_name] = mtime - end - when "directory" then - next if UNCONDITIONALLY_SKIPPED_DIRECTORIES.include?(rel_file_name) - - basename = File.basename(rel_file_name) - next if options.skip_tests && TEST_SUITE_DIRECTORY_NAMES.include?(basename) - - created_rid = File.join rel_file_name, "created.rid" - next if File.file? created_rid - - dot_doc = File.join rel_file_name, RDoc::DOT_DOC_FILENAME - - if File.file? dot_doc then - file_list.update(parse_dot_doc_file(rel_file_name, dot_doc)) - else - file_list.update(list_files_in_directory(rel_file_name)) - end - else - warn "rdoc can't parse the #{type} #{rel_file_name}" - end - end - - file_list - end - - ## - # Return a list of the files to be processed in a directory. We know that - # this directory doesn't have a .document file, so we're looking for real - # files. However we may well contain subdirectories which must be tested - # for .document files. - - def list_files_in_directory dir - files = Dir.glob File.join(dir, "*") - - normalized_file_list files, false, @options.exclude - end - - ## - # Parses +filename+ and returns an RDoc::TopLevel - - def parse_file filename - encoding = @options.encoding - filename = filename.encode encoding - - @stats.add_file filename - - return if RDoc::Parser.binary? filename - - content = RDoc::Encoding.read_file filename, encoding - - return unless content - - filename_path = Pathname(filename).expand_path - begin - relative_path = filename_path.relative_path_from @options.root - rescue ArgumentError - relative_path = filename_path - end - - if @options.page_dir and - relative_path.to_s.start_with? @options.page_dir.to_s then - relative_path = - relative_path.relative_path_from @options.page_dir - end - - top_level = @store.add_file filename, relative_name: relative_path.to_s - - parser = RDoc::Parser.for top_level, content, @options, @stats - - return unless parser - - parser.scan - - # restart documentation for the classes & modules found - top_level.classes_or_modules.each do |cm| - cm.done_documenting = false - end - - top_level - - rescue Errno::EACCES => e - $stderr.puts <<-EOF -Unable to read #{filename}, #{e.message} - -Please check the permissions for this file. Perhaps you do not have access to -it or perhaps the original author's permissions are to restrictive. If the -this is not your library please report a bug to the author. - EOF - rescue => e - $stderr.puts <<-EOF -Before reporting this, could you check that the file you're documenting -has proper syntax: - - #{Gem.ruby} -c #{filename} - -RDoc is not a full Ruby parser and will fail when fed invalid ruby programs. - -The internal error was: - -\t(#{e.class}) #{e.message} - - EOF - - $stderr.puts e.backtrace.join("\n\t") if $DEBUG_RDOC - - raise e - nil - end - - ## - # Parse each file on the command line, recursively entering directories. - - def parse_files files - file_list = gather_files files - @stats = RDoc::Stats.new @store, file_list.length, @options.verbosity - - return [] if file_list.empty? - - original_options = @options.dup - @stats.begin_adding - - file_info = file_list.map do |filename| - @current = filename - parse_file filename - end.compact - - @stats.done_adding - @options = original_options - - file_info - end - - ## - # Removes file extensions known to be unparseable from +files+ and TAGS - # files for emacs and vim. - - def remove_unparseable files - files.reject do |file, *| - file =~ /\.(?:class|eps|erb|scpt\.txt|svg|ttf|yml)$/i or - (file =~ /tags$/i and - /\A(\f\n[^,]+,\d+$|!_TAG_)/.match?(File.binread(file, 100))) - end - end - - ## - # Generates documentation or a coverage report depending upon the settings - # in +options+. - # - # +options+ can be either an RDoc::Options instance or an array of strings - # equivalent to the strings that would be passed on the command line like - # <tt>%w[-q -o doc -t My\ Doc\ Title]</tt>. #document will automatically - # call RDoc::Options#finish if an options instance was given. - # - # For a list of options, see either RDoc::Options or <tt>rdoc --help</tt>. - # - # By default, output will be stored in a directory called "doc" below the - # current directory, so make sure you're somewhere writable before invoking. - - def document options - self.store = RDoc::Store.new - - if RDoc::Options === options then - @options = options - else - @options = RDoc::Options.load_options - @options.parse options - end - @options.finish - - if @options.pipe then - handle_pipe - exit - end - - unless @options.coverage_report then - @last_modified = setup_output_dir @options.op_dir, @options.force_update - end - - @store.encoding = @options.encoding - @store.dry_run = @options.dry_run - @store.main = @options.main_page - @store.title = @options.title - @store.path = @options.op_dir - - @start_time = Time.now - - @store.load_cache - - file_info = parse_files @options.files - - @options.default_title = "RDoc Documentation" - - @store.complete @options.visibility - - @stats.coverage_level = @options.coverage_report - - if @options.coverage_report then - puts - - puts @stats.report.accept RDoc::Markup::ToRdoc.new - elsif file_info.empty? then - $stderr.puts "\nNo newer files." unless @options.quiet - else - gen_klass = @options.generator - - @generator = gen_klass.new @store, @options - - generate - end - - if @stats and (@options.coverage_report or not @options.quiet) then - puts - puts @stats.summary.accept RDoc::Markup::ToRdoc.new - end - - exit @stats.fully_documented? if @options.coverage_report - end - - ## - # Generates documentation for +file_info+ (from #parse_files) into the - # output dir using the generator selected - # by the RDoc options - - def generate - if @options.dry_run then - # do nothing - @generator.generate - else - Dir.chdir @options.op_dir do - unless @options.quiet then - $stderr.puts "\nGenerating #{@generator.class.name.sub(/^.*::/, '')} format into #{Dir.pwd}..." - $stderr.puts "\nYou can visit the home page at: \e]8;;file://#{Dir.pwd}/index.html\e\\file://#{Dir.pwd}/index.html\e]8;;\e\\" - end - - @generator.generate - update_output_dir '.', @start_time, @last_modified - end - end - end - - ## - # Removes a siginfo handler and replaces the previous - - def remove_siginfo_handler - return unless Signal.list.key? 'INFO' - - handler = @old_siginfo || 'DEFAULT' - - trap 'INFO', handler - end - -end - -begin - require 'rubygems' - - rdoc_extensions = Gem.find_latest_files 'rdoc/discover' - - rdoc_extensions.each do |extension| - begin - load extension - rescue => e - warn "error loading #{extension.inspect}: #{e.message} (#{e.class})" - warn "\t#{e.backtrace.join "\n\t"}" if $DEBUG - end - end -rescue LoadError -end - -# require built-in generators after discovery in case they've been replaced -require_relative 'generator/darkfish' -require_relative 'generator/ri' -require_relative 'generator/pot' diff --git a/lib/rdoc/ri.rb b/lib/rdoc/ri.rb deleted file mode 100644 index 0af05f729f..0000000000 --- a/lib/rdoc/ri.rb +++ /dev/null @@ -1,20 +0,0 @@ -# frozen_string_literal: true -require_relative '../rdoc' - -## -# Namespace for the ri command line tool's implementation. -# -# See <tt>ri --help</tt> for details. - -module RDoc::RI - - ## - # Base RI error class - - class Error < RDoc::Error; end - - autoload :Driver, "#{__dir__}/ri/driver" - autoload :Paths, "#{__dir__}/ri/paths" - autoload :Store, "#{__dir__}/ri/store" - -end diff --git a/lib/rdoc/ri/driver.rb b/lib/rdoc/ri/driver.rb deleted file mode 100644 index c6fddbac67..0000000000 --- a/lib/rdoc/ri/driver.rb +++ /dev/null @@ -1,1517 +0,0 @@ -# frozen_string_literal: true -require 'optparse' - -require_relative '../../rdoc' - -require_relative 'formatter' # For RubyGems backwards compatibility -# TODO: Fix weird documentation with `require_relative` - -## -# The RI driver implements the command-line ri tool. -# -# The driver supports: -# * loading RI data from: -# * Ruby's standard library -# * RubyGems -# * ~/.rdoc -# * A user-supplied directory -# * Paging output (uses RI_PAGER environment variable, PAGER environment -# variable or the less, more and pager programs) -# * Interactive mode with tab-completion -# * Abbreviated names (ri Zl shows Zlib documentation) -# * Colorized output -# * Merging output from multiple RI data sources - -class RDoc::RI::Driver - - ## - # Base Driver error class - - class Error < RDoc::RI::Error; end - - ## - # Raised when a name isn't found in the ri data stores - - class NotFoundError < Error - - def initialize(klass, suggestion_proc = nil) # :nodoc: - @klass = klass - @suggestion_proc = suggestion_proc - end - - ## - # Name that wasn't found - - def name - @klass - end - - def message # :nodoc: - str = "Nothing known about #{@klass}" - suggestions = @suggestion_proc&.call - if suggestions and !suggestions.empty? - str += "\nDid you mean? #{suggestions.join("\n ")}" - end - str - end - end - - ## - # Show all method documentation following a class or module - - attr_accessor :show_all - - ## - # An RDoc::RI::Store for each entry in the RI path - - attr_accessor :stores - - ## - # Controls the user of the pager vs $stdout - - attr_accessor :use_stdout - - ## - # Default options for ri - - def self.default_options - options = {} - options[:interactive] = false - options[:profile] = false - options[:show_all] = false - options[:use_stdout] = !$stdout.tty? - options[:width] = 72 - - # By default all standard paths are used. - options[:use_system] = true - options[:use_site] = true - options[:use_home] = true - options[:use_gems] = true - options[:extra_doc_dirs] = [] - - return options - end - - ## - # Dump +data_path+ using pp - - def self.dump data_path - require 'pp' - - File.open data_path, 'rb' do |io| - pp Marshal.load(io.read) - end - end - - ## - # Parses +argv+ and returns a Hash of options - - def self.process_args argv - options = default_options - - opts = OptionParser.new do |opt| - opt.program_name = File.basename $0 - opt.version = RDoc::VERSION - opt.release = nil - opt.summary_indent = ' ' * 4 - - opt.banner = <<-EOT -Usage: #{opt.program_name} [options] [name ...] - -Where name can be: - - Class | Module | Module::Class - - Class::method | Class#method | Class.method | method - - gem_name: | gem_name:README | gem_name:History - - ruby: | ruby:NEWS | ruby:globals - -All class names may be abbreviated to their minimum unambiguous form. -If a name is ambiguous, all valid options will be listed. - -A '.' matches either class or instance methods, while #method -matches only instance and ::method matches only class methods. - -README and other files may be displayed by prefixing them with the gem name -they're contained in. If the gem name is followed by a ':' all files in the -gem will be shown. The file name extension may be omitted where it is -unambiguous. - -'ruby' can be used as a pseudo gem name to display files from the Ruby -core documentation. Use 'ruby:' by itself to get a list of all available -core documentation files. - -For example: - - #{opt.program_name} Fil - #{opt.program_name} File - #{opt.program_name} File.new - #{opt.program_name} zip - #{opt.program_name} rdoc:README - #{opt.program_name} ruby:comments - -Note that shell quoting or escaping may be required for method names -containing punctuation: - - #{opt.program_name} 'Array.[]' - #{opt.program_name} compact\\! - -To see the default directories #{opt.program_name} will search, run: - - #{opt.program_name} --list-doc-dirs - -Specifying the --system, --site, --home, --gems, or --doc-dir options -will limit ri to searching only the specified directories. - -ri options may be set in the RI environment variable. - -The ri pager can be set with the RI_PAGER environment variable -or the PAGER environment variable. - EOT - - opt.separator nil - opt.separator "Options:" - - opt.separator nil - - opt.on("--[no-]interactive", "-i", - "In interactive mode you can repeatedly", - "look up methods with autocomplete.") do |interactive| - options[:interactive] = interactive - end - - opt.separator nil - - opt.on("--[no-]all", "-a", - "Show all documentation for a class or", - "module.") do |show_all| - options[:show_all] = show_all - end - - opt.separator nil - - opt.on("--[no-]list", "-l", - "List classes ri knows about.") do |list| - options[:list] = list - end - - opt.separator nil - - opt.on("--[no-]pager", - "Send output to a pager,", - "rather than directly to stdout.") do |use_pager| - options[:use_stdout] = !use_pager - end - - opt.separator nil - - opt.on("-T", - "Synonym for --no-pager.") do - options[:use_stdout] = true - end - - opt.separator nil - - opt.on("--width=WIDTH", "-w", OptionParser::DecimalInteger, - "Set the width of the output.") do |width| - options[:width] = width - end - - opt.separator nil - - opt.on("--server[=PORT]", Integer, - "Run RDoc server on the given port.", - "The default port is 8214.") do |port| - options[:server] = port || 8214 - end - - opt.separator nil - - formatters = RDoc::Markup.constants.grep(/^To[A-Z][a-z]+$/).sort - formatters = formatters.sort.map do |formatter| - formatter.to_s.sub('To', '').downcase - end - formatters -= %w[html label test] # remove useless output formats - - opt.on("--format=NAME", "-f", - "Use the selected formatter. The default", - "formatter is bs for paged output and ansi", - "otherwise. Valid formatters are:", - "#{formatters.join(', ')}.", formatters) do |value| - options[:formatter] = RDoc::Markup.const_get "To#{value.capitalize}" - end - - opt.separator nil - - opt.on("--help", "-h", - "Show help and exit.") do - puts opts - exit - end - - opt.separator nil - - opt.on("--version", "-v", - "Output version information and exit.") do - puts "#{opts.program_name} #{opts.version}" - exit - end - - opt.separator nil - opt.separator "Data source options:" - opt.separator nil - - opt.on("--[no-]list-doc-dirs", - "List the directories from which ri will", - "source documentation on stdout and exit.") do |list_doc_dirs| - options[:list_doc_dirs] = list_doc_dirs - end - - opt.separator nil - - opt.on("--doc-dir=DIRNAME", "-d", Array, - "List of directories from which to source", - "documentation in addition to the standard", - "directories. May be repeated.") do |value| - value.each do |dir| - unless File.directory? dir then - raise OptionParser::InvalidArgument, "#{dir} is not a directory" - end - - options[:extra_doc_dirs] << File.expand_path(dir) - end - end - - opt.separator nil - - opt.on("--no-standard-docs", - "Do not include documentation from", - "the Ruby standard library, site_lib,", - "installed gems, or ~/.rdoc.", - "Use with --doc-dir.") do - options[:use_system] = false - options[:use_site] = false - options[:use_gems] = false - options[:use_home] = false - end - - opt.separator nil - - opt.on("--[no-]system", - "Include documentation from Ruby's", - "standard library. Defaults to true.") do |value| - options[:use_system] = value - end - - opt.separator nil - - opt.on("--[no-]site", - "Include documentation from libraries", - "installed in site_lib.", - "Defaults to true.") do |value| - options[:use_site] = value - end - - opt.separator nil - - opt.on("--[no-]gems", - "Include documentation from RubyGems.", - "Defaults to true.") do |value| - options[:use_gems] = value - end - - opt.separator nil - - opt.on("--[no-]home", - "Include documentation stored in ~/.rdoc.", - "Defaults to true.") do |value| - options[:use_home] = value - end - - opt.separator nil - opt.separator "Debug options:" - opt.separator nil - - opt.on("--[no-]profile", - "Run with the ruby profiler.") do |value| - options[:profile] = value - end - - opt.separator nil - - opt.on("--dump=CACHE", - "Dump data from an ri cache or data file.") do |value| - unless File.readable?(value) - abort "#{value.inspect} is not readable" - end - - if File.directory?(value) - abort "#{value.inspect} is a directory" - end - - options[:dump_path] = File.new(value) - end - end - - argv = ENV['RI'].to_s.split(' ').concat argv - - opts.parse! argv - - options[:names] = argv - - options[:use_stdout] ||= !$stdout.tty? - options[:use_stdout] ||= options[:interactive] - options[:width] ||= 72 - - options - - rescue OptionParser::InvalidArgument, OptionParser::InvalidOption => e - puts opts - puts - puts e - exit 1 - end - - ## - # Runs the ri command line executable using +argv+ - - def self.run argv = ARGV - options = process_args argv - - if options[:dump_path] then - dump options[:dump_path] - return - end - - ri = new options - ri.run - end - - ## - # Creates a new driver using +initial_options+ from ::process_args - - def initialize initial_options = {} - @paging = false - @classes = nil - - options = self.class.default_options.update(initial_options) - - @formatter_klass = options[:formatter] - - require 'profile' if options[:profile] - - @names = options[:names] - @list = options[:list] - - @doc_dirs = [] - @stores = [] - - RDoc::RI::Paths.each(options[:use_system], options[:use_site], - options[:use_home], options[:use_gems], - *options[:extra_doc_dirs]) do |path, type| - @doc_dirs << path - - store = RDoc::RI::Store.new path, type - store.load_cache - @stores << store - end - - @list_doc_dirs = options[:list_doc_dirs] - - @interactive = options[:interactive] - @server = options[:server] - @use_stdout = options[:use_stdout] - @show_all = options[:show_all] - @width = options[:width] - end - - ## - # Adds paths for undocumented classes +also_in+ to +out+ - - def add_also_in out, also_in - return if also_in.empty? - - out << RDoc::Markup::Rule.new(1) - out << RDoc::Markup::Paragraph.new("Also found in:") - - paths = RDoc::Markup::Verbatim.new - also_in.each do |store| - paths.parts.push store.friendly_path, "\n" - end - out << paths - end - - ## - # Adds a class header to +out+ for class +name+ which is described in - # +classes+. - - def add_class out, name, classes - heading = if classes.all? { |klass| klass.module? } then - name - else - superclass = classes.map do |klass| - klass.superclass unless klass.module? - end.compact.shift || 'Object' - - superclass = superclass.full_name unless String === superclass - - "#{name} < #{superclass}" - end - - out << RDoc::Markup::Heading.new(1, heading) - out << RDoc::Markup::BlankLine.new - end - - ## - # Adds "(from ...)" to +out+ for +store+ - - def add_from out, store - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - end - - ## - # Adds +extends+ to +out+ - - def add_extends out, extends - add_extension_modules out, 'Extended by', extends - end - - ## - # Adds a list of +extensions+ to this module of the given +type+ to +out+. - # add_includes and add_extends call this, so you should use those directly. - - def add_extension_modules out, type, extensions - return if extensions.empty? - - out << RDoc::Markup::Rule.new(1) - out << RDoc::Markup::Heading.new(1, "#{type}:") - - extensions.each do |modules, store| - if modules.length == 1 then - add_extension_modules_single out, store, modules.first - else - add_extension_modules_multiple out, store, modules - end - end - end - - ## - # Renders multiple included +modules+ from +store+ to +out+. - - def add_extension_modules_multiple out, store, modules # :nodoc: - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - - wout, with = modules.partition { |incl| incl.comment.empty? } - - out << RDoc::Markup::BlankLine.new unless with.empty? - - with.each do |incl| - out << RDoc::Markup::Paragraph.new(incl.name) - out << RDoc::Markup::BlankLine.new - out << incl.comment - end - - unless wout.empty? then - verb = RDoc::Markup::Verbatim.new - - wout.each do |incl| - verb.push incl.name, "\n" - end - - out << verb - end - end - - ## - # Adds a single extension module +include+ from +store+ to +out+ - - def add_extension_modules_single out, store, include # :nodoc: - name = include.name - path = store.friendly_path - out << RDoc::Markup::Paragraph.new("#{name} (from #{path})") - - if include.comment then - out << RDoc::Markup::BlankLine.new - out << include.comment - end - end - - ## - # Adds +includes+ to +out+ - - def add_includes out, includes - add_extension_modules out, 'Includes', includes - end - - ## - # Looks up the method +name+ and adds it to +out+ - - def add_method out, name - filtered = lookup_method name - - method_out = method_document name, filtered - - out.concat method_out.parts - end - - ## - # Adds documentation for all methods in +klass+ to +out+ - - def add_method_documentation out, klass - klass.method_list.each do |method| - begin - add_method out, method.full_name - rescue NotFoundError - next - end - end - end - - ## - # Adds a list of +methods+ to +out+ with a heading of +name+ - - def add_method_list out, methods, name - return if methods.empty? - - out << RDoc::Markup::Heading.new(1, "#{name}:") - out << RDoc::Markup::BlankLine.new - - if @use_stdout and !@interactive then - out.concat methods.map { |method| - RDoc::Markup::Verbatim.new method - } - else - out << RDoc::Markup::IndentedParagraph.new(2, methods.join(', ')) - end - - out << RDoc::Markup::BlankLine.new - end - - ## - # Returns ancestor classes of +klass+ - - def ancestors_of klass - ancestors = [] - - unexamined = [klass] - seen = [] - - loop do - break if unexamined.empty? - current = unexamined.shift - seen << current - - stores = classes[current] - - next unless stores and not stores.empty? - - klasses = stores.flat_map do |store| - store.ancestors[current] || [] - end.uniq - - klasses = klasses - seen - - ancestors.concat klasses - unexamined.concat klasses - end - - ancestors.reverse - end - - ## - # For RubyGems backwards compatibility - - def class_cache # :nodoc: - end - - ## - # Builds a RDoc::Markup::Document from +found+, +klasess+ and +includes+ - - def class_document name, found, klasses, includes, extends - also_in = [] - - out = RDoc::Markup::Document.new - - add_class out, name, klasses - - add_includes out, includes - add_extends out, extends - - found.each do |store, klass| - render_class out, store, klass, also_in - end - - add_also_in out, also_in - - out - end - - ## - # Adds the class +comment+ to +out+. - - def class_document_comment out, comment # :nodoc: - unless comment.empty? then - out << RDoc::Markup::Rule.new(1) - - if comment.merged? then - parts = comment.parts - parts = parts.zip [RDoc::Markup::BlankLine.new] * parts.length - parts.flatten! - parts.pop - - out.concat parts - else - out << comment - end - end - end - - ## - # Adds the constants from +klass+ to the Document +out+. - - def class_document_constants out, klass # :nodoc: - return if klass.constants.empty? - - out << RDoc::Markup::Heading.new(1, "Constants:") - out << RDoc::Markup::BlankLine.new - list = RDoc::Markup::List.new :NOTE - - constants = klass.constants.sort_by { |constant| constant.name } - - list.items.concat constants.map { |constant| - parts = constant.comment.parts if constant.comment - parts << RDoc::Markup::Paragraph.new('[not documented]') if - parts.empty? - - RDoc::Markup::ListItem.new(constant.name, *parts) - } - - out << list - out << RDoc::Markup::BlankLine.new - end - - ## - # Hash mapping a known class or module to the stores it can be loaded from - - def classes - return @classes if @classes - - @classes = {} - - @stores.each do |store| - store.cache[:modules].each do |mod| - # using default block causes searched-for modules to be added - @classes[mod] ||= [] - @classes[mod] << store - end - end - - @classes - end - - ## - # Returns the stores wherein +name+ is found along with the classes, - # extends and includes that match it - - def classes_and_includes_and_extends_for name - klasses = [] - extends = [] - includes = [] - - found = @stores.map do |store| - begin - klass = store.load_class name - klasses << klass - extends << [klass.extends, store] if klass.extends - includes << [klass.includes, store] if klass.includes - [store, klass] - rescue RDoc::Store::MissingFileError - end - end.compact - - extends.reject! do |modules,| modules.empty? end - includes.reject! do |modules,| modules.empty? end - - [found, klasses, includes, extends] - end - - ## - # Completes +name+ based on the caches. For Readline - - def complete name - completions = [] - - klass, selector, method = parse_name name - - complete_klass name, klass, selector, method, completions - complete_method name, klass, selector, completions - - completions.sort.uniq - end - - def complete_klass name, klass, selector, method, completions # :nodoc: - klasses = classes.keys - - # may need to include Foo when given Foo:: - klass_name = method ? name : klass - - if name !~ /#|\./ then - completions.replace klasses.grep(/^#{Regexp.escape klass_name}[^:]*$/) - completions.concat klasses.grep(/^#{Regexp.escape name}[^:]*$/) if - name =~ /::$/ - - completions << klass if classes.key? klass # to complete a method name - elsif selector then - completions << klass if classes.key? klass - elsif classes.key? klass_name then - completions << klass_name - end - end - - def complete_method name, klass, selector, completions # :nodoc: - if completions.include? klass and name =~ /#|\.|::/ then - methods = list_methods_matching name - - if not methods.empty? then - # remove Foo if given Foo:: and a method was found - completions.delete klass - elsif selector then - # replace Foo with Foo:: as given - completions.delete klass - completions << "#{klass}#{selector}" - end - - completions.concat methods - end - end - - ## - # Converts +document+ to text and writes it to the pager - - def display document - page do |io| - f = formatter(io) - f.width = @width if @width and f.respond_to?(:width) - text = document.accept f - - io.write text - end - end - - ## - # Outputs formatted RI data for class +name+. Groups undocumented classes - - def display_class name - return if name =~ /#|\./ - - found, klasses, includes, extends = - classes_and_includes_and_extends_for name - - return if found.empty? - - out = class_document name, found, klasses, includes, extends - - display out - end - - ## - # Outputs formatted RI data for method +name+ - - def display_method name - out = RDoc::Markup::Document.new - - add_method out, name - - display out - end - - ## - # Outputs formatted RI data for the class or method +name+. - # - # Returns true if +name+ was found, false if it was not an alternative could - # be guessed, raises an error if +name+ couldn't be guessed. - - def display_name name - if name =~ /\w:(\w|$)/ then - display_page name - return true - end - - return true if display_class name - - display_method name if name =~ /::|#|\./ - - true - rescue NotFoundError - matches = list_methods_matching name if name =~ /::|#|\./ - matches = classes.keys.grep(/^#{Regexp.escape name}/) if matches.empty? - - raise if matches.empty? - - page do |io| - io.puts "#{name} not found, maybe you meant:" - io.puts - io.puts matches.sort.join("\n") - end - - false - end - - ## - # Displays each name in +name+ - - def display_names names - names.each do |name| - name = expand_name name - - display_name name - end - end - - ## - # Outputs formatted RI data for page +name+. - - def display_page name - store_name, page_name = name.split ':', 2 - - store = @stores.find { |s| s.source == store_name } - - return display_page_list store if page_name.empty? - - pages = store.cache[:pages] - - unless pages.include? page_name then - found_names = pages.select do |n| - n =~ /#{Regexp.escape page_name}\.[^.]+$/ - end - - if found_names.length.zero? then - return display_page_list store, pages - elsif found_names.length > 1 then - return display_page_list store, found_names, page_name - end - - page_name = found_names.first - end - - page = store.load_page page_name - - display page.comment - end - - ## - # Outputs a formatted RI page list for the pages in +store+. - - def display_page_list store, pages = store.cache[:pages], search = nil - out = RDoc::Markup::Document.new - - title = if search then - "#{search} pages" - else - 'Pages' - end - - out << RDoc::Markup::Heading.new(1, "#{title} in #{store.friendly_path}") - out << RDoc::Markup::BlankLine.new - - list = RDoc::Markup::List.new(:BULLET) - - pages.each do |page| - list << RDoc::Markup::Paragraph.new(page) - end - - out << list - - display out - end - - def check_did_you_mean # :nodoc: - if defined? DidYouMean::SpellChecker - true - else - begin - require 'did_you_mean' - if defined? DidYouMean::SpellChecker - true - else - false - end - rescue LoadError - false - end - end - end - - ## - # Expands abbreviated klass +klass+ into a fully-qualified class. "Zl::Da" - # will be expanded to Zlib::DataError. - - def expand_class klass - class_names = classes.keys - ary = class_names.grep(Regexp.new("\\A#{klass.gsub(/(?=::|\z)/, '[^:]*')}\\z")) - if ary.length != 1 && ary.first != klass - if check_did_you_mean - suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: class_names).correct(klass) } - raise NotFoundError.new(klass, suggestion_proc) - else - raise NotFoundError, klass - end - end - ary.first - end - - ## - # Expands the class portion of +name+ into a fully-qualified class. See - # #expand_class. - - def expand_name name - klass, selector, method = parse_name name - - return [selector, method].join if klass.empty? - - case selector - when ':' then - [find_store(klass), selector, method] - else - [expand_class(klass), selector, method] - end.join - end - - ## - # Filters the methods in +found+ trying to find a match for +name+. - - def filter_methods found, name - regexp = name_regexp name - - filtered = found.find_all do |store, methods| - methods.any? { |method| method.full_name =~ regexp } - end - - return filtered unless filtered.empty? - - found - end - - ## - # Yields items matching +name+ including the store they were found in, the - # class being searched for, the class they were found in (an ancestor) the - # types of methods to look up (from #method_type), and the method name being - # searched for - - def find_methods name - klass, selector, method = parse_name name - - types = method_type selector - - klasses = nil - ambiguous = klass.empty? - - if ambiguous then - klasses = classes.keys - else - klasses = ancestors_of klass - klasses.unshift klass - end - - methods = [] - - klasses.each do |ancestor| - ancestors = classes[ancestor] - - next unless ancestors - - klass = ancestor if ambiguous - - ancestors.each do |store| - methods << [store, klass, ancestor, types, method] - end - end - - methods = methods.sort_by do |_, k, a, _, m| - [k, a, m].compact - end - - methods.each do |item| - yield(*item) # :yields: store, klass, ancestor, types, method - end - - self - end - - ## - # Finds a store that matches +name+ which can be the name of a gem, "ruby", - # "home" or "site". - # - # See also RDoc::Store#source - - def find_store name - @stores.each do |store| - source = store.source - - return source if source == name - - return source if - store.type == :gem and source =~ /^#{Regexp.escape name}-\d/ - end - - raise RDoc::RI::Driver::NotFoundError, name - end - - ## - # Creates a new RDoc::Markup::Formatter. If a formatter is given with -f, - # use it. If we're outputting to a pager, use bs, otherwise ansi. - - def formatter(io) - if @formatter_klass then - @formatter_klass.new - elsif paging? or !io.tty? then - RDoc::Markup::ToBs.new - else - RDoc::Markup::ToAnsi.new - end - end - - ## - # Runs ri interactively using Readline if it is available. - - def interactive - puts "\nEnter the method name you want to look up." - - begin - require 'readline' - rescue LoadError - end - if defined? Readline then - Readline.completion_proc = method :complete - puts "You can use tab to autocomplete." - end - - puts "Enter a blank line to exit.\n\n" - - loop do - name = if defined? Readline then - Readline.readline ">> ", true - else - print ">> " - $stdin.gets - end - - return if name.nil? or name.empty? - - begin - display_name expand_name(name.strip) - rescue NotFoundError => e - puts e.message - end - end - - rescue Interrupt - exit - end - - ## - # Lists classes known to ri starting with +names+. If +names+ is empty all - # known classes are shown. - - def list_known_classes names = [] - classes = [] - - stores.each do |store| - classes << store.module_names - end - - classes = classes.flatten.uniq.sort - - unless names.empty? then - filter = Regexp.union names.map { |name| /^#{name}/ } - - classes = classes.grep filter - end - - page do |io| - if paging? or io.tty? then - if names.empty? then - io.puts "Classes and Modules known to ri:" - else - io.puts "Classes and Modules starting with #{names.join ', '}:" - end - io.puts - end - - io.puts classes.join("\n") - end - end - - ## - # Returns an Array of methods matching +name+ - - def list_methods_matching name - found = [] - - find_methods name do |store, klass, ancestor, types, method| - if types == :instance or types == :both then - methods = store.instance_methods[ancestor] - - if methods then - matches = methods.grep(/^#{Regexp.escape method.to_s}/) - - matches = matches.map do |match| - "#{klass}##{match}" - end - - found.concat matches - end - end - - if types == :class or types == :both then - methods = store.class_methods[ancestor] - - next unless methods - matches = methods.grep(/^#{Regexp.escape method.to_s}/) - - matches = matches.map do |match| - "#{klass}::#{match}" - end - - found.concat matches - end - end - - found.uniq - end - - ## - # Loads RI data for method +name+ on +klass+ from +store+. +type+ and - # +cache+ indicate if it is a class or instance method. - - def load_method store, cache, klass, type, name - methods = store.public_send(cache)[klass] - - return unless methods - - method = methods.find do |method_name| - method_name == name - end - - return unless method - - store.load_method klass, "#{type}#{method}" - rescue RDoc::Store::MissingFileError => e - comment = RDoc::Comment.new("missing documentation at #{e.file}").parse - - method = RDoc::AnyMethod.new nil, name - method.comment = comment - method - end - - ## - # Returns an Array of RI data for methods matching +name+ - - def load_methods_matching name - found = [] - - find_methods name do |store, klass, ancestor, types, method| - methods = [] - - methods << load_method(store, :class_methods, ancestor, '::', method) if - [:class, :both].include? types - - methods << load_method(store, :instance_methods, ancestor, '#', method) if - [:instance, :both].include? types - - found << [store, methods.compact] - end - - found.reject do |path, methods| methods.empty? end - end - - ## - # Returns a filtered list of methods matching +name+ - - def lookup_method name - found = load_methods_matching name - - if found.empty? - if check_did_you_mean - methods = [] - _, _, method_name = parse_name name - find_methods name do |store, klass, ancestor, types, method| - methods.push(*store.class_methods[klass]) if [:class, :both].include? types - methods.push(*store.instance_methods[klass]) if [:instance, :both].include? types - end - methods = methods.uniq - suggestion_proc = -> { DidYouMean::SpellChecker.new(dictionary: methods).correct(method_name) } - raise NotFoundError.new(name, suggestion_proc) - else - raise NotFoundError, name - end - end - - filter_methods found, name - end - - ## - # Builds a RDoc::Markup::Document from +found+, +klasses+ and +includes+ - - def method_document name, filtered - out = RDoc::Markup::Document.new - - out << RDoc::Markup::Heading.new(1, name) - out << RDoc::Markup::BlankLine.new - - filtered.each do |store, methods| - methods.each do |method| - render_method out, store, method, name - end - end - - out - end - - ## - # Returns the type of method (:both, :instance, :class) for +selector+ - - def method_type selector - case selector - when '.', nil then :both - when '#' then :instance - else :class - end - end - - ## - # Returns a regular expression for +name+ that will match an - # RDoc::AnyMethod's name. - - def name_regexp name - klass, type, name = parse_name name - - case type - when '#', '::' then - /^#{klass}#{type}#{Regexp.escape name}$/ - else - /^#{klass}(#|::)#{Regexp.escape name}$/ - end - end - - ## - # Paginates output through a pager program. - - def page - if pager = setup_pager then - begin - yield pager - ensure - pager.close - end - else - yield $stdout - end - rescue Errno::EPIPE - ensure - @paging = false - end - - ## - # Are we using a pager? - - def paging? - @paging - end - - ## - # Extracts the class, selector and method name parts from +name+ like - # Foo::Bar#baz. - # - # NOTE: Given Foo::Bar, Bar is considered a class even though it may be a - # method - - def parse_name name - parts = name.split(/(::?|#|\.)/) - - if parts.length == 1 then - if parts.first =~ /^[a-z]|^([%&*+\/<>^`|~-]|\+@|-@|<<|<=>?|===?|=>|=~|>>|\[\]=?|~@)$/ then - type = '.' - meth = parts.pop - else - type = nil - meth = nil - end - elsif parts.length == 2 or parts.last =~ /::|#|\./ then - type = parts.pop - meth = nil - elsif parts[1] == ':' then - klass = parts.shift - type = parts.shift - meth = parts.join - elsif parts[-2] != '::' or parts.last !~ /^[A-Z]/ then - meth = parts.pop - type = parts.pop - end - - klass ||= parts.join - - [klass, type, meth] - end - - ## - # Renders the +klass+ from +store+ to +out+. If the klass has no - # documentable items the class is added to +also_in+ instead. - - def render_class out, store, klass, also_in # :nodoc: - comment = klass.comment - # TODO the store's cache should always return an empty Array - class_methods = store.class_methods[klass.full_name] || [] - instance_methods = store.instance_methods[klass.full_name] || [] - attributes = store.attributes[klass.full_name] || [] - - if comment.empty? and - instance_methods.empty? and class_methods.empty? then - also_in << store - return - end - - add_from out, store - - class_document_comment out, comment - - if class_methods or instance_methods or not klass.constants.empty? then - out << RDoc::Markup::Rule.new(1) - end - - class_document_constants out, klass - - add_method_list out, class_methods, 'Class methods' - add_method_list out, instance_methods, 'Instance methods' - add_method_list out, attributes, 'Attributes' - - add_method_documentation out, klass if @show_all - end - - def render_method out, store, method, name # :nodoc: - out << RDoc::Markup::Paragraph.new("(from #{store.friendly_path})") - - unless name =~ /^#{Regexp.escape method.parent_name}/ then - out << RDoc::Markup::Heading.new(3, "Implementation from #{method.parent_name}") - end - - out << RDoc::Markup::Rule.new(1) - - render_method_arguments out, method.arglists - render_method_superclass out, method - if method.is_alias_for - al = method.is_alias_for - alias_for = store.load_method al.parent_name, "#{al.name_prefix}#{al.name}" - render_method_comment out, method, alias_for - else - render_method_comment out, method - end - end - - def render_method_arguments out, arglists # :nodoc: - return unless arglists - - arglists = arglists.chomp.split "\n" - arglists = arglists.map { |line| line + "\n" } - out << RDoc::Markup::Verbatim.new(*arglists) - out << RDoc::Markup::Rule.new(1) - end - - def render_method_comment out, method, alias_for = nil# :nodoc: - if alias_for - unless method.comment.nil? or method.comment.empty? - out << RDoc::Markup::BlankLine.new - out << method.comment - end - out << RDoc::Markup::BlankLine.new - out << RDoc::Markup::Paragraph.new("(This method is an alias for #{alias_for.full_name}.)") - out << RDoc::Markup::BlankLine.new - out << alias_for.comment - out << RDoc::Markup::BlankLine.new - else - out << RDoc::Markup::BlankLine.new - out << method.comment - out << RDoc::Markup::BlankLine.new - end - end - - def render_method_superclass out, method # :nodoc: - return unless - method.respond_to?(:superclass_method) and method.superclass_method - - out << RDoc::Markup::BlankLine.new - out << RDoc::Markup::Heading.new(4, "(Uses superclass method #{method.superclass_method})") - out << RDoc::Markup::Rule.new(1) - end - - ## - # Looks up and displays ri data according to the options given. - - def run - if @list_doc_dirs then - puts @doc_dirs - elsif @list then - list_known_classes @names - elsif @server then - start_server - elsif @interactive or @names.empty? then - interactive - else - display_names @names - end - rescue NotFoundError => e - abort e.message - end - - ## - # Sets up a pager program to pass output through. Tries the RI_PAGER and - # PAGER environment variables followed by pager, less then more. - - def setup_pager - return if @use_stdout - - pagers = [ENV['RI_PAGER'], ENV['PAGER'], 'pager', 'less', 'more'] - - require 'shellwords' - pagers.compact.uniq.each do |pager| - pager = Shellwords.split(pager) - next if pager.empty? - - io = IO.popen(pager, 'w') rescue next - next if $? and $?.pid == io.pid and $?.exited? # pager didn't work - - @paging = true - - return io - end - - @use_stdout = true - - nil - end - - ## - # Starts a WEBrick server for ri. - - def start_server - begin - require 'webrick' - rescue LoadError - abort "webrick is not found. You may need to `gem install webrick` to install webrick." - end - - server = WEBrick::HTTPServer.new :Port => @server - - extra_doc_dirs = @stores.map {|s| s.type == :extra ? s.path : nil}.compact - - server.mount '/', RDoc::Servlet, nil, extra_doc_dirs - - trap 'INT' do server.shutdown end - trap 'TERM' do server.shutdown end - - server.start - end - -end diff --git a/lib/rdoc/ri/formatter.rb b/lib/rdoc/ri/formatter.rb deleted file mode 100644 index 832a101e6c..0000000000 --- a/lib/rdoc/ri/formatter.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -## -# For RubyGems backwards compatibility - -module RDoc::RI::Formatter # :nodoc: -end diff --git a/lib/rdoc/ri/paths.rb b/lib/rdoc/ri/paths.rb deleted file mode 100644 index 8e89b04e54..0000000000 --- a/lib/rdoc/ri/paths.rb +++ /dev/null @@ -1,171 +0,0 @@ -# frozen_string_literal: true -require_relative '../rdoc' - -## -# The directories where ri data lives. Paths can be enumerated via ::each, or -# queried individually via ::system_dir, ::site_dir, ::home_dir and ::gem_dir. - -module RDoc::RI::Paths - - #:stopdoc: - require 'rbconfig' - - version = RbConfig::CONFIG['ruby_version'] - - BASE = File.join RbConfig::CONFIG['ridir'], version - - HOMEDIR = RDoc.home - #:startdoc: - - ## - # Iterates over each selected path yielding the directory and type. - # - # Yielded types: - # :system:: Where Ruby's ri data is stored. Yielded when +system+ is - # true - # :site:: Where ri for installed libraries are stored. Yielded when - # +site+ is true. Normally no ri data is stored here. - # :home:: ~/.rdoc. Yielded when +home+ is true. - # :gem:: ri data for an installed gem. Yielded when +gems+ is true. - # :extra:: ri data directory from the command line. Yielded for each - # entry in +extra_dirs+ - - def self.each system = true, site = true, home = true, gems = :latest, *extra_dirs # :yields: directory, type - return enum_for __method__, system, site, home, gems, *extra_dirs unless - block_given? - - extra_dirs.each do |dir| - yield dir, :extra - end - - yield system_dir, :system if system - yield site_dir, :site if site - yield home_dir, :home if home and HOMEDIR - - gemdirs(gems).each do |dir| - yield dir, :gem - end if gems - - nil - end - - ## - # The ri directory for the gem with +gem_name+. - - def self.gem_dir name, version - req = Gem::Requirement.new "= #{version}" - - spec = Gem::Specification.find_by_name name, req - - File.join spec.doc_dir, 'ri' - end - - ## - # The latest installed gems' ri directories. +filter+ can be :all or - # :latest. - # - # A +filter+ :all includes all versions of gems and includes gems without - # ri documentation. - - def self.gemdirs filter = :latest - ri_paths = {} - - all = Gem::Specification.map do |spec| - [File.join(spec.doc_dir, 'ri'), spec.name, spec.version] - end - - if filter == :all then - gemdirs = [] - - all.group_by do |_, name, _| - name - end.sort_by do |group, _| - group - end.map do |group, items| - items.sort_by do |_, _, version| - version - end.reverse_each do |dir,| - gemdirs << dir - end - end - - return gemdirs - end - - all.each do |dir, name, ver| - next unless File.exist? dir - - if ri_paths[name].nil? or ver > ri_paths[name].first then - ri_paths[name] = [ver, name, dir] - end - end - - ri_paths.sort_by { |_, (_, name, _)| name }.map { |k, v| v.last } - rescue LoadError - [] - end - - ## - # The location of the rdoc data in the user's home directory. - # - # Like ::system, ri data in the user's home directory is rare and predates - # libraries distributed via RubyGems. ri data is rarely generated into this - # directory. - - def self.home_dir - HOMEDIR - end - - ## - # Returns existing directories from the selected documentation directories - # as an Array. - # - # See also ::each - - def self.path(system = true, site = true, home = true, gems = :latest, *extra_dirs) - path = raw_path system, site, home, gems, *extra_dirs - - path.select { |directory| File.directory? directory } - end - - ## - # Returns selected documentation directories including nonexistent - # directories. - # - # See also ::each - - def self.raw_path(system, site, home, gems, *extra_dirs) - path = [] - - each(system, site, home, gems, *extra_dirs) do |dir, type| - path << dir - end - - path.compact - end - - ## - # The location of ri data installed into the site dir. - # - # Historically this was available for documentation installed by Ruby - # libraries predating RubyGems. It is unlikely to contain any content for - # modern Ruby installations. - - def self.site_dir - File.join BASE, 'site' - end - - ## - # The location of the built-in ri data. - # - # This data is built automatically when `make` is run when Ruby is - # installed. If you did not install Ruby by hand you may need to install - # the documentation yourself. Please consult the documentation for your - # package manager or Ruby installer for details. You can also use the - # rdoc-data gem to install system ri data for common versions of Ruby. - - def self.system_dir - File.join BASE, 'system' - end - -end diff --git a/lib/rdoc/ri/store.rb b/lib/rdoc/ri/store.rb deleted file mode 100644 index 96742e7ae3..0000000000 --- a/lib/rdoc/ri/store.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true -module RDoc::RI - - Store = RDoc::Store # :nodoc: - -end diff --git a/lib/rdoc/ri/task.rb b/lib/rdoc/ri/task.rb deleted file mode 100644 index 1122ea3775..0000000000 --- a/lib/rdoc/ri/task.rb +++ /dev/null @@ -1,71 +0,0 @@ -# frozen_string_literal: true -begin - gem 'rdoc' -rescue Gem::LoadError -end unless defined?(RDoc) - -require_relative '../task' - -## -# RDoc::RI::Task creates ri data in <code>./.rdoc</code> for your project. -# -# It contains the following tasks: -# -# [ri] -# Build ri data -# -# [clobber_ri] -# Delete ri data files. This target is automatically added to the main -# clobber target. -# -# [reri] -# Rebuild the ri data from scratch even if they are not out of date. -# -# Simple example: -# -# require 'rdoc/ri/task' -# -# RDoc::RI::Task.new do |ri| -# ri.main = 'README.rdoc' -# ri.rdoc_files.include 'README.rdoc', 'lib/**/*.rb' -# end -# -# For further configuration details see RDoc::Task. - -class RDoc::RI::Task < RDoc::Task - - DEFAULT_NAMES = { # :nodoc: - :clobber_rdoc => :clobber_ri, - :rdoc => :ri, - :rerdoc => :reri, - } - - ## - # Create an ri task with the given name. See RDoc::Task for documentation on - # setting names. - - def initialize name = DEFAULT_NAMES # :yield: self - super - end - - def clobber_task_description # :nodoc: - "Remove RI data files" - end - - ## - # Sets default task values - - def defaults - super - - @rdoc_dir = '.rdoc' - end - - def rdoc_task_description # :nodoc: - 'Build RI data files' - end - - def rerdoc_task_description # :nodoc: - 'Rebuild RI data files' - end -end diff --git a/lib/rdoc/rubygems_hook.rb b/lib/rdoc/rubygems_hook.rb deleted file mode 100644 index 3160072e53..0000000000 --- a/lib/rdoc/rubygems_hook.rb +++ /dev/null @@ -1,248 +0,0 @@ -# frozen_string_literal: true -require 'rubygems/user_interaction' -require 'fileutils' -require_relative '../rdoc' - -## -# Gem::RDoc provides methods to generate RDoc and ri data for installed gems -# upon gem installation. -# -# This file is automatically required by RubyGems 1.9 and newer. - -class RDoc::RubygemsHook - - include Gem::UserInteraction - extend Gem::UserInteraction - - @rdoc_version = nil - @specs = [] - - ## - # Force installation of documentation? - - attr_accessor :force - - ## - # Generate rdoc? - - attr_accessor :generate_rdoc - - ## - # Generate ri data? - - attr_accessor :generate_ri - - class << self - - ## - # Loaded version of RDoc. Set by ::load_rdoc - - attr_reader :rdoc_version - - end - - ## - # Post installs hook that generates documentation for each specification in - # +specs+ - - def self.generation_hook installer, specs - start = Time.now - types = installer.document - - generate_rdoc = types.include? 'rdoc' - generate_ri = types.include? 'ri' - - specs.each do |spec| - new(spec, generate_rdoc, generate_ri).generate - end - - return unless generate_rdoc or generate_ri - - duration = (Time.now - start).to_i - names = specs.map(&:name).join ', ' - - say "Done installing documentation for #{names} after #{duration} seconds" - end - - ## - # Loads the RDoc generator - - def self.load_rdoc - return if @rdoc_version - - require_relative 'rdoc' - - @rdoc_version = Gem::Version.new ::RDoc::VERSION - end - - ## - # Creates a new documentation generator for +spec+. RDoc and ri data - # generation can be enabled or disabled through +generate_rdoc+ and - # +generate_ri+ respectively. - # - # Only +generate_ri+ is enabled by default. - - def initialize spec, generate_rdoc = false, generate_ri = true - @doc_dir = spec.doc_dir - @force = false - @rdoc = nil - @spec = spec - - @generate_rdoc = generate_rdoc - @generate_ri = generate_ri - - @rdoc_dir = spec.doc_dir 'rdoc' - @ri_dir = spec.doc_dir 'ri' - end - - ## - # Removes legacy rdoc arguments from +args+ - #-- - # TODO move to RDoc::Options - - def delete_legacy_args args - args.delete '--inline-source' - args.delete '--promiscuous' - args.delete '-p' - args.delete '--one-file' - end - - ## - # Generates documentation using the named +generator+ ("darkfish" or "ri") - # and following the given +options+. - # - # Documentation will be generated into +destination+ - - def document generator, options, destination - generator_name = generator - - options = options.dup - options.exclude ||= [] # TODO maybe move to RDoc::Options#finish - options.setup_generator generator - options.op_dir = destination - Dir.chdir @spec.full_gem_path do - options.finish - end - - generator = options.generator.new @rdoc.store, options - - @rdoc.options = options - @rdoc.generator = generator - - say "Installing #{generator_name} documentation for #{@spec.full_name}" - - FileUtils.mkdir_p options.op_dir - - Dir.chdir options.op_dir do - begin - @rdoc.class.current = @rdoc - @rdoc.generator.generate - ensure - @rdoc.class.current = nil - end - end - end - - ## - # Generates RDoc and ri data - - def generate - return if @spec.default_gem? - return unless @generate_ri or @generate_rdoc - - setup - - options = nil - - args = @spec.rdoc_options - args.concat @spec.source_paths - args.concat @spec.extra_rdoc_files - - case config_args = Gem.configuration[:rdoc] - when String then - args = args.concat config_args.split(' ') - when Array then - args = args.concat config_args - end - - delete_legacy_args args - - Dir.chdir @spec.full_gem_path do - options = ::RDoc::Options.new - options.default_title = "#{@spec.full_name} Documentation" - options.parse args - end - - options.quiet = !Gem.configuration.really_verbose - - @rdoc = new_rdoc - @rdoc.options = options - - store = RDoc::Store.new - store.encoding = options.encoding - store.dry_run = options.dry_run - store.main = options.main_page - store.title = options.title - - @rdoc.store = store - - say "Parsing documentation for #{@spec.full_name}" - - Dir.chdir @spec.full_gem_path do - @rdoc.parse_files options.files - end - - document 'ri', options, @ri_dir if - @generate_ri and (@force or not File.exist? @ri_dir) - - document 'darkfish', options, @rdoc_dir if - @generate_rdoc and (@force or not File.exist? @rdoc_dir) - end - - ## - # #new_rdoc creates a new RDoc instance. This method is provided only to - # make testing easier. - - def new_rdoc # :nodoc: - ::RDoc::RDoc.new - end - - ## - # Is rdoc documentation installed? - - def rdoc_installed? - File.exist? @rdoc_dir - end - - ## - # Removes generated RDoc and ri data - - def remove - base_dir = @spec.base_dir - - raise Gem::FilePermissionError, base_dir unless File.writable? base_dir - - FileUtils.rm_rf @rdoc_dir - FileUtils.rm_rf @ri_dir - end - - ## - # Is ri data installed? - - def ri_installed? - File.exist? @ri_dir - end - - ## - # Prepares the spec for documentation generation - - def setup - self.class.load_rdoc - - raise Gem::FilePermissionError, @doc_dir if - File.exist?(@doc_dir) and not File.writable?(@doc_dir) - - FileUtils.mkdir_p @doc_dir unless File.exist? @doc_dir - end - -end diff --git a/lib/rdoc/servlet.rb b/lib/rdoc/servlet.rb deleted file mode 100644 index d05368766a..0000000000 --- a/lib/rdoc/servlet.rb +++ /dev/null @@ -1,451 +0,0 @@ -# frozen_string_literal: true -require_relative '../rdoc' -require 'erb' -require 'time' -require 'json' - -begin - require 'webrick' -rescue LoadError - abort "webrick is not found. You may need to `gem install webrick` to install webrick." -end - -## -# This is a WEBrick servlet that allows you to browse ri documentation. -# -# You can show documentation through either `ri --server` or, with RubyGems -# 2.0 or newer, `gem server`. For ri, the server runs on port 8214 by -# default. For RubyGems the server runs on port 8808 by default. -# -# You can use this servlet in your own project by mounting it on a WEBrick -# server: -# -# require 'webrick' -# -# server = WEBrick::HTTPServer.new Port: 8000 -# -# server.mount '/', RDoc::Servlet -# -# If you want to mount the servlet some other place than the root, provide the -# base path when mounting: -# -# server.mount '/rdoc', RDoc::Servlet, '/rdoc' - -class RDoc::Servlet < WEBrick::HTTPServlet::AbstractServlet - - @server_stores = Hash.new { |hash, server| hash[server] = {} } - @cache = Hash.new { |hash, store| hash[store] = {} } - - ## - # Maps an asset type to its path on the filesystem - - attr_reader :asset_dirs - - ## - # An RDoc::Options instance used for rendering options - - attr_reader :options - - ## - # Creates an instance of this servlet that shares cached data between - # requests. - - def self.get_instance server, *options # :nodoc: - stores = @server_stores[server] - - new server, stores, @cache, *options - end - - ## - # Creates a new WEBrick servlet. - # - # Use +mount_path+ when mounting the servlet somewhere other than /. - # - # Use +extra_doc_dirs+ for additional documentation directories. - # - # +server+ is provided automatically by WEBrick when mounting. +stores+ and - # +cache+ are provided automatically by the servlet. - - def initialize server, stores, cache, mount_path = nil, extra_doc_dirs = [] - super server - - @cache = cache - @mount_path = mount_path - @extra_doc_dirs = extra_doc_dirs - @stores = stores - - @options = RDoc::Options.new - @options.op_dir = '.' - - darkfish_dir = nil - - # HACK dup - $LOAD_PATH.each do |path| - darkfish_dir = File.join path, 'rdoc/generator/template/darkfish/' - next unless File.directory? darkfish_dir - @options.template_dir = darkfish_dir - break - end - - @asset_dirs = { - :darkfish => darkfish_dir, - :json_index => - File.expand_path('../generator/template/json_index/', __FILE__), - } - end - - ## - # Serves the asset at the path in +req+ for +generator_name+ via +res+. - - def asset generator_name, req, res - asset_dir = @asset_dirs[generator_name] - - asset_path = File.join asset_dir, req.path - - if_modified_since req, res, asset_path - - res.body = File.read asset_path - - res.content_type = case req.path - when /\.css\z/ then 'text/css' - when /\.js\z/ then 'application/javascript' - else 'application/octet-stream' - end - end - - ## - # GET request entry point. Fills in +res+ for the path, etc. in +req+. - - def do_GET req, res - req.path.sub!(/\A#{Regexp.escape @mount_path}/, '') if @mount_path - - case req.path - when '/' then - root req, res - when '/js/darkfish.js', '/js/jquery.js', '/js/search.js', - %r%^/css/%, %r%^/images/%, %r%^/fonts/% then - asset :darkfish, req, res - when '/js/navigation.js', '/js/searcher.js' then - asset :json_index, req, res - when '/js/search_index.js' then - root_search req, res - else - show_documentation req, res - end - rescue WEBrick::HTTPStatus::NotFound => e - generator = generator_for RDoc::Store.new - - not_found generator, req, res, e.message - rescue WEBrick::HTTPStatus::Status - raise - rescue => e - error e, req, res - end - - ## - # Fills in +res+ with the class, module or page for +req+ from +store+. - # - # +path+ is relative to the mount_path and is used to determine the class, - # module or page name (/RDoc/Servlet.html becomes RDoc::Servlet). - # +generator+ is used to create the page. - - def documentation_page store, generator, path, req, res - text_name = path.chomp '.html' - name = text_name.gsub '/', '::' - - if klass = store.find_class_or_module(name) then - res.body = generator.generate_class klass - elsif page = store.find_text_page(name.sub(/_([^_]*)\z/, '.\1')) then - res.body = generator.generate_page page - elsif page = store.find_text_page(text_name.sub(/_([^_]*)\z/, '.\1')) then - res.body = generator.generate_page page - else - not_found generator, req, res - end - end - - ## - # Creates the JSON search index on +res+ for the given +store+. +generator+ - # must respond to \#json_index to build. +req+ is ignored. - - def documentation_search store, generator, req, res - json_index = @cache[store].fetch :json_index do - @cache[store][:json_index] = - JSON.dump generator.json_index.build_index - end - - res.content_type = 'application/javascript' - res.body = "var search_data = #{json_index}" - end - - ## - # Returns the RDoc::Store and path relative to +mount_path+ for - # documentation at +path+. - - def documentation_source path - _, source_name, path = path.split '/', 3 - - store = @stores[source_name] - return store, path if store - - store = store_for source_name - - store.load_all - - @stores[source_name] = store - - return store, path - end - - ## - # Generates an error page for the +exception+ while handling +req+ on +res+. - - def error exception, req, res - backtrace = exception.backtrace.join "\n" - - res.content_type = 'text/html' - res.status = 500 - res.body = <<-BODY -<!DOCTYPE html> -<html> -<head> -<meta content="text/html; charset=UTF-8" http-equiv="Content-Type"> - -<title>Error - #{ERB::Util.html_escape exception.class}</title> - -<link type="text/css" media="screen" href="#{@mount_path}/css/rdoc.css" rel="stylesheet"> -</head> -<body> -<h1>Error</h1> - -<p>While processing <code>#{ERB::Util.html_escape req.request_uri}</code> the -RDoc (#{ERB::Util.html_escape RDoc::VERSION}) server has encountered a -<code>#{ERB::Util.html_escape exception.class}</code> -exception: - -<pre>#{ERB::Util.html_escape exception.message}</pre> - -<p>Please report this to the -<a href="https://github.com/ruby/rdoc/issues">RDoc issues tracker</a>. Please -include the RDoc version, the URI above and exception class, message and -backtrace. If you're viewing a gem's documentation, include the gem name and -version. If you're viewing Ruby's documentation, include the version of ruby. - -<p>Backtrace: - -<pre>#{ERB::Util.html_escape backtrace}</pre> - -</body> -</html> - BODY - end - - ## - # Instantiates a Darkfish generator for +store+ - - def generator_for store - generator = RDoc::Generator::Darkfish.new store, @options - generator.file_output = false - generator.asset_rel_path = '..' - - rdoc = RDoc::RDoc.new - rdoc.store = store - rdoc.generator = generator - rdoc.options = @options - - @options.main_page = store.main - @options.title = store.title - - generator - end - - ## - # Handles the If-Modified-Since HTTP header on +req+ for +path+. If the - # file has not been modified a Not Modified response is returned. If the - # file has been modified a Last-Modified header is added to +res+. - - def if_modified_since req, res, path = nil - last_modified = File.stat(path).mtime if path - - res['last-modified'] = last_modified.httpdate - - return unless ims = req['if-modified-since'] - - ims = Time.parse ims - - unless ims < last_modified then - res.body = '' - raise WEBrick::HTTPStatus::NotModified - end - end - - ## - # Returns an Array of installed documentation. - # - # Each entry contains the documentation name (gem name, 'Ruby - # Documentation', etc.), the path relative to the mount point, whether the - # documentation exists, the type of documentation (See RDoc::RI::Paths#each) - # and the filesystem to the RDoc::Store for the documentation. - - def installed_docs - extra_counter = 0 - ri_paths.map do |path, type| - store = RDoc::Store.new path, type - exists = File.exist? store.cache_path - - case type - when :gem then - gem_path = path[%r%/([^/]*)/ri$%, 1] - [gem_path, "#{gem_path}/", exists, type, path] - when :system then - ['Ruby Documentation', 'ruby/', exists, type, path] - when :site then - ['Site Documentation', 'site/', exists, type, path] - when :home then - ['Home Documentation', 'home/', exists, type, path] - when :extra then - extra_counter += 1 - store.load_cache if exists - title = store.title || "Extra Documentation" - [title, "extra-#{extra_counter}/", exists, type, path] - end - end - end - - ## - # Returns a 404 page built by +generator+ for +req+ on +res+. - - def not_found generator, req, res, message = nil - message ||= "The page <kbd>#{ERB::Util.h req.path}</kbd> was not found" - res.body = generator.generate_servlet_not_found message - res.status = 404 - end - - ## - # Enumerates the ri paths. See RDoc::RI::Paths#each - - def ri_paths &block - RDoc::RI::Paths.each true, true, true, :all, *@extra_doc_dirs, &block #TODO: pass extra_dirs - end - - ## - # Generates the root page on +res+. +req+ is ignored. - - def root req, res - generator = RDoc::Generator::Darkfish.new nil, @options - - res.body = generator.generate_servlet_root installed_docs - - res.content_type = 'text/html' - end - - ## - # Generates a search index for the root page on +res+. +req+ is ignored. - - def root_search req, res - search_index = [] - info = [] - - installed_docs.map do |name, href, exists, type, path| - next unless exists - - search_index << name - - case type - when :gem - gemspec = path.gsub(%r%/doc/([^/]*?)/ri$%, - '/specifications/\1.gemspec') - - spec = Gem::Specification.load gemspec - - path = spec.full_name - comment = spec.summary - when :system then - path = 'ruby' - comment = 'Documentation for the Ruby standard library' - when :site then - path = 'site' - comment = 'Documentation for non-gem libraries' - when :home then - path = 'home' - comment = 'Documentation from your home directory' - when :extra - comment = name - end - - info << [name, '', path, '', comment] - end - - index = { - :index => { - :searchIndex => search_index, - :longSearchIndex => search_index, - :info => info, - } - } - - res.body = "var search_data = #{JSON.dump index};" - res.content_type = 'application/javascript' - end - - ## - # Displays documentation for +req+ on +res+, whether that be HTML or some - # asset. - - def show_documentation req, res - store, path = documentation_source req.path - - if_modified_since req, res, store.cache_path - - generator = generator_for store - - case path - when nil, '', 'index.html' then - res.body = generator.generate_index - when 'table_of_contents.html' then - res.body = generator.generate_table_of_contents - when 'js/search_index.js' then - documentation_search store, generator, req, res - else - documentation_page store, generator, path, req, res - end - ensure - res.content_type ||= 'text/html' - end - - ## - # Returns an RDoc::Store for the given +source_name+ ('ruby' or a gem name). - - def store_for source_name - case source_name - when 'home' then - RDoc::Store.new RDoc::RI::Paths.home_dir, :home - when 'ruby' then - RDoc::Store.new RDoc::RI::Paths.system_dir, :system - when 'site' then - RDoc::Store.new RDoc::RI::Paths.site_dir, :site - when /\Aextra-(\d+)\z/ then - index = $1.to_i - 1 - ri_dir = installed_docs[index][4] - RDoc::Store.new ri_dir, :extra - else - ri_dir, type = ri_paths.find do |dir, dir_type| - next unless dir_type == :gem - - source_name == dir[%r%/([^/]*)/ri$%, 1] - end - - raise WEBrick::HTTPStatus::NotFound, - "Could not find gem \"#{ERB::Util.html_escape(source_name)}\". Are you sure you installed it?" unless ri_dir - - store = RDoc::Store.new ri_dir, type - - return store if File.exist? store.cache_path - - raise WEBrick::HTTPStatus::NotFound, - "Could not find documentation for \"#{ERB::Util.html_escape(source_name)}\". Please run `gem rdoc --ri gem_name`" - - end - end - -end diff --git a/lib/rdoc/stats.rb b/lib/rdoc/stats.rb deleted file mode 100644 index 4817c9c729..0000000000 --- a/lib/rdoc/stats.rb +++ /dev/null @@ -1,461 +0,0 @@ -# frozen_string_literal: true -## -# RDoc statistics collector which prints a summary and report of a project's -# documentation totals. - -class RDoc::Stats - - include RDoc::Text - - ## - # Output level for the coverage report - - attr_reader :coverage_level - - ## - # Count of files parsed during parsing - - attr_reader :files_so_far - - ## - # Total number of files found - - attr_reader :num_files - - ## - # Creates a new Stats that will have +num_files+. +verbosity+ defaults to 1 - # which will create an RDoc::Stats::Normal outputter. - - def initialize store, num_files, verbosity = 1 - @num_files = num_files - @store = store - - @coverage_level = 0 - @doc_items = nil - @files_so_far = 0 - @fully_documented = false - @num_params = 0 - @percent_doc = nil - @start = Time.now - @undoc_params = 0 - - @display = case verbosity - when 0 then Quiet.new num_files - when 1 then Normal.new num_files - else Verbose.new num_files - end - end - - ## - # Records the parsing of an alias +as+. - - def add_alias as - @display.print_alias as - end - - ## - # Records the parsing of an attribute +attribute+ - - def add_attribute attribute - @display.print_attribute attribute - end - - ## - # Records the parsing of a class +klass+ - - def add_class klass - @display.print_class klass - end - - ## - # Records the parsing of +constant+ - - def add_constant constant - @display.print_constant constant - end - - ## - # Records the parsing of +file+ - - def add_file(file) - @files_so_far += 1 - @display.print_file @files_so_far, file - end - - ## - # Records the parsing of +method+ - - def add_method(method) - @display.print_method method - end - - ## - # Records the parsing of a module +mod+ - - def add_module(mod) - @display.print_module mod - end - - ## - # Call this to mark the beginning of parsing for display purposes - - def begin_adding - @display.begin_adding - end - - ## - # Calculates documentation totals and percentages for classes, modules, - # constants, attributes and methods. - - def calculate - return if @doc_items - - ucm = @store.unique_classes_and_modules - - classes = @store.unique_classes.reject { |cm| cm.full_name == 'Object' } - - constants = [] - ucm.each { |cm| constants.concat cm.constants } - - methods = [] - ucm.each { |cm| methods.concat cm.method_list } - - attributes = [] - ucm.each { |cm| attributes.concat cm.attributes } - - @num_attributes, @undoc_attributes = doc_stats attributes - @num_classes, @undoc_classes = doc_stats classes - @num_constants, @undoc_constants = doc_stats constants - @num_methods, @undoc_methods = doc_stats methods - @num_modules, @undoc_modules = doc_stats @store.unique_modules - - @num_items = - @num_attributes + - @num_classes + - @num_constants + - @num_methods + - @num_modules + - @num_params - - @undoc_items = - @undoc_attributes + - @undoc_classes + - @undoc_constants + - @undoc_methods + - @undoc_modules + - @undoc_params - - @doc_items = @num_items - @undoc_items - end - - ## - # Sets coverage report level. Accepted values are: - # - # false or nil:: No report - # 0:: Classes, modules, constants, attributes, methods - # 1:: Level 0 + method parameters - - def coverage_level= level - level = -1 unless level - - @coverage_level = level - end - - ## - # Returns the length and number of undocumented items in +collection+. - - def doc_stats collection - visible = collection.select { |item| item.display? } - [visible.length, visible.count { |item| not item.documented? }] - end - - ## - # Call this to mark the end of parsing for display purposes - - def done_adding - @display.done_adding - end - - ## - # The documentation status of this project. +true+ when 100%, +false+ when - # less than 100% and +nil+ when unknown. - # - # Set by calling #calculate - - def fully_documented? - @fully_documented - end - - ## - # A report that says you did a great job! - - def great_job - report = RDoc::Markup::Document.new - - report << RDoc::Markup::Paragraph.new('100% documentation!') - report << RDoc::Markup::Paragraph.new('Great Job!') - - report - end - - ## - # Calculates the percentage of items documented. - - def percent_doc - return @percent_doc if @percent_doc - - @fully_documented = (@num_items - @doc_items) == 0 - - @percent_doc = @doc_items.to_f / @num_items * 100 if @num_items.nonzero? - @percent_doc ||= 0 - - @percent_doc - end - - ## - # Returns a report on which items are not documented - - def report - if @coverage_level > 0 then - extend RDoc::Text - end - - if @coverage_level.zero? then - calculate - - return great_job if @num_items == @doc_items - end - - ucm = @store.unique_classes_and_modules - - report = RDoc::Markup::Document.new - report << RDoc::Markup::Paragraph.new('The following items are not documented:') - report << RDoc::Markup::BlankLine.new - - ucm.sort.each do |cm| - body = report_class_module(cm) { - [ - report_constants(cm), - report_attributes(cm), - report_methods(cm), - ].compact - } - - report << body if body - end - - if @coverage_level > 0 then - calculate - - return great_job if @num_items == @doc_items - end - - report - end - - ## - # Returns a report on undocumented attributes in ClassModule +cm+ - - def report_attributes cm - return if cm.attributes.empty? - - report = [] - - cm.each_attribute do |attr| - next if attr.documented? - line = attr.line ? ":#{attr.line}" : nil - report << " #{attr.definition} :#{attr.name} # in file #{attr.file.full_name}#{line}\n" - report << "\n" - end - - report - end - - ## - # Returns a report on undocumented items in ClassModule +cm+ - - def report_class_module cm - return if cm.fully_documented? and @coverage_level.zero? - return unless cm.display? - - report = RDoc::Markup::Document.new - - if cm.in_files.empty? then - report << RDoc::Markup::Paragraph.new("#{cm.definition} is referenced but empty.") - report << RDoc::Markup::Paragraph.new("It probably came from another project. I'm sorry I'm holding it against you.") - - return report - elsif cm.documented? then - documented = true - klass = RDoc::Markup::Verbatim.new("#{cm.definition} # is documented\n") - else - report << RDoc::Markup::Paragraph.new('In files:') - - list = RDoc::Markup::List.new :BULLET - - cm.in_files.each do |file| - para = RDoc::Markup::Paragraph.new file.full_name - list << RDoc::Markup::ListItem.new(nil, para) - end - - report << list - report << RDoc::Markup::BlankLine.new - - klass = RDoc::Markup::Verbatim.new("#{cm.definition}\n") - end - - klass << "\n" - - body = yield.flatten # HACK remove #flatten - - if body.empty? then - return if documented - - klass.parts.pop - else - klass.parts.concat body - end - - klass << "end\n" - - report << klass - - report - end - - ## - # Returns a report on undocumented constants in ClassModule +cm+ - - def report_constants cm - return if cm.constants.empty? - - report = [] - - cm.each_constant do |constant| - # TODO constant aliases are listed in the summary but not reported - # figure out what to do here - next if constant.documented? || constant.is_alias_for - - line = constant.line ? ":#{constant.line}" : line - report << " # in file #{constant.file.full_name}#{line}\n" - report << " #{constant.name} = nil\n" - report << "\n" - end - - report - end - - ## - # Returns a report on undocumented methods in ClassModule +cm+ - - def report_methods cm - return if cm.method_list.empty? - - report = [] - - cm.each_method do |method| - next if method.documented? and @coverage_level.zero? - - if @coverage_level > 0 then - params, undoc = undoc_params method - - @num_params += params - - unless undoc.empty? then - @undoc_params += undoc.length - - undoc = undoc.map do |param| "+#{param}+" end - param_report = " # #{undoc.join ', '} is not documented\n" - end - end - - next if method.documented? and not param_report - - line = method.line ? ":#{method.line}" : nil - scope = method.singleton ? 'self.' : nil - - report << " # in file #{method.file.full_name}#{line}\n" - report << param_report if param_report - report << " def #{scope}#{method.name}#{method.params}; end\n" - report << "\n" - end - - report - end - - ## - # Returns a summary of the collected statistics. - - def summary - calculate - - num_width = [@num_files, @num_items].max.to_s.length - undoc_width = [ - @undoc_attributes, - @undoc_classes, - @undoc_constants, - @undoc_items, - @undoc_methods, - @undoc_modules, - @undoc_params, - ].max.to_s.length - - report = RDoc::Markup::Verbatim.new - - report << "Files: %*d\n" % [num_width, @num_files] - - report << "\n" - - report << "Classes: %*d (%*d undocumented)\n" % [ - num_width, @num_classes, undoc_width, @undoc_classes] - report << "Modules: %*d (%*d undocumented)\n" % [ - num_width, @num_modules, undoc_width, @undoc_modules] - report << "Constants: %*d (%*d undocumented)\n" % [ - num_width, @num_constants, undoc_width, @undoc_constants] - report << "Attributes: %*d (%*d undocumented)\n" % [ - num_width, @num_attributes, undoc_width, @undoc_attributes] - report << "Methods: %*d (%*d undocumented)\n" % [ - num_width, @num_methods, undoc_width, @undoc_methods] - report << "Parameters: %*d (%*d undocumented)\n" % [ - num_width, @num_params, undoc_width, @undoc_params] if - @coverage_level > 0 - - report << "\n" - - report << "Total: %*d (%*d undocumented)\n" % [ - num_width, @num_items, undoc_width, @undoc_items] - - report << "%6.2f%% documented\n" % percent_doc - report << "\n" - report << "Elapsed: %0.1fs\n" % (Time.now - @start) - - RDoc::Markup::Document.new report - end - - ## - # Determines which parameters in +method+ were not documented. Returns a - # total parameter count and an Array of undocumented methods. - - def undoc_params method - @formatter ||= RDoc::Markup::ToTtOnly.new - - params = method.param_list - - params = params.map { |param| param.gsub(/^\*\*?/, '') } - - return 0, [] if params.empty? - - document = parse method.comment - - tts = document.accept @formatter - - undoc = params - tts - - [params.length, undoc] - end - - autoload :Quiet, "#{__dir__}/stats/quiet" - autoload :Normal, "#{__dir__}/stats/normal" - autoload :Verbose, "#{__dir__}/stats/verbose" - -end diff --git a/lib/rdoc/stats/normal.rb b/lib/rdoc/stats/normal.rb deleted file mode 100644 index 0a22f0582b..0000000000 --- a/lib/rdoc/stats/normal.rb +++ /dev/null @@ -1,58 +0,0 @@ -# frozen_string_literal: true -begin - require 'io/console/size' -rescue LoadError - # for JRuby - require 'io/console' -end - -## -# Stats printer that prints just the files being documented with a progress -# bar - -class RDoc::Stats::Normal < RDoc::Stats::Quiet - - def begin_adding # :nodoc: - puts "Parsing sources..." - @last_width = 0 - end - - ## - # Prints a file with a progress bar - - def print_file files_so_far, filename - progress_bar = sprintf("%3d%% [%2d/%2d] ", - 100 * files_so_far / @num_files, - files_so_far, - @num_files) - - if $stdout.tty? - # Print a progress bar, but make sure it fits on a single line. Filename - # will be truncated if necessary. - size = IO.respond_to?(:console_size) ? IO.console_size : IO.console.winsize - terminal_width = size[1].to_i.nonzero? || 80 - max_filename_size = (terminal_width - progress_bar.size) - 1 - - if filename.size > max_filename_size then - # Turn "some_long_filename.rb" to "...ong_filename.rb" - filename = filename[(filename.size - max_filename_size) .. -1] - filename[0..2] = "..." - end - - # Clean the line with whitespaces so that leftover output from the - # previous line doesn't show up. - $stdout.print("\r\e[K") if @last_width && @last_width > 0 - @last_width = progress_bar.size + filename.size - term = "\r" - else - term = "\n" - end - $stdout.print(progress_bar, filename, term) - $stdout.flush - end - - def done_adding # :nodoc: - puts - end - -end diff --git a/lib/rdoc/stats/quiet.rb b/lib/rdoc/stats/quiet.rb deleted file mode 100644 index 9c98ea5f86..0000000000 --- a/lib/rdoc/stats/quiet.rb +++ /dev/null @@ -1,59 +0,0 @@ -# frozen_string_literal: true -## -# Stats printer that prints nothing - -class RDoc::Stats::Quiet - - ## - # Creates a new Quiet that will print nothing - - def initialize num_files - @num_files = num_files - end - - ## - # Prints a message at the beginning of parsing - - def begin_adding(*) end - - ## - # Prints when an alias is added - - def print_alias(*) end - - ## - # Prints when an attribute is added - - def print_attribute(*) end - - ## - # Prints when a class is added - - def print_class(*) end - - ## - # Prints when a constant is added - - def print_constant(*) end - - ## - # Prints when a file is added - - def print_file(*) end - - ## - # Prints when a method is added - - def print_method(*) end - - ## - # Prints when a module is added - - def print_module(*) end - - ## - # Prints when RDoc is done - - def done_adding(*) end - -end diff --git a/lib/rdoc/stats/verbose.rb b/lib/rdoc/stats/verbose.rb deleted file mode 100644 index 7090d561f8..0000000000 --- a/lib/rdoc/stats/verbose.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true -## -# Stats printer that prints everything documented, including the documented -# status - -class RDoc::Stats::Verbose < RDoc::Stats::Normal - - ## - # Returns a marker for RDoc::CodeObject +co+ being undocumented - - def nodoc co - " (undocumented)" unless co.documented? - end - - def print_alias as # :nodoc: - puts " alias #{as.new_name} #{as.old_name}#{nodoc as}" - end - - def print_attribute attribute # :nodoc: - puts " #{attribute.definition} #{attribute.name}#{nodoc attribute}" - end - - def print_class(klass) # :nodoc: - puts " class #{klass.full_name}#{nodoc klass}" - end - - def print_constant(constant) # :nodoc: - puts " #{constant.name}#{nodoc constant}" - end - - def print_file(files_so_far, file) # :nodoc: - super - puts - end - - def print_method(method) # :nodoc: - puts " #{method.singleton ? '::' : '#'}#{method.name}#{nodoc method}" - end - - def print_module(mod) # :nodoc: - puts " module #{mod.full_name}#{nodoc mod}" - end - -end diff --git a/lib/rdoc/store.rb b/lib/rdoc/store.rb deleted file mode 100644 index cd27d47dd1..0000000000 --- a/lib/rdoc/store.rb +++ /dev/null @@ -1,989 +0,0 @@ -# frozen_string_literal: true -require 'fileutils' - -## -# A set of rdoc data for a single project (gem, path, etc.). -# -# The store manages reading and writing ri data for a project and maintains a -# cache of methods, classes and ancestors in the store. -# -# The store maintains a #cache of its contents for faster lookup. After -# adding items to the store it must be flushed using #save_cache. The cache -# contains the following structures: -# -# @cache = { -# :ancestors => {}, # class name => ancestor names -# :attributes => {}, # class name => attributes -# :class_methods => {}, # class name => class methods -# :instance_methods => {}, # class name => instance methods -# :modules => [], # classes and modules in this store -# :pages => [], # page names -# } -#-- -# TODO need to prune classes - -class RDoc::Store - - ## - # Errors raised from loading or saving the store - - class Error < RDoc::Error - end - - ## - # Raised when a stored file for a class, module, page or method is missing. - - class MissingFileError < Error - - ## - # The store the file should exist in - - attr_reader :store - - ## - # The file the #name should be saved as - - attr_reader :file - - ## - # The name of the object the #file would be loaded from - - attr_reader :name - - ## - # Creates a new MissingFileError for the missing +file+ for the given - # +name+ that should have been in the +store+. - - def initialize store, file, name - @store = store - @file = file - @name = name - end - - def message # :nodoc: - "store at #{@store.path} missing file #{@file} for #{@name}" - end - - end - - ## - # Stores the name of the C variable a class belongs to. This helps wire up - # classes defined from C across files. - - attr_reader :c_enclosure_classes # :nodoc: - - attr_reader :c_enclosure_names # :nodoc: - - ## - # Maps C variables to class or module names for each parsed C file. - - attr_reader :c_class_variables - - ## - # Maps C variables to singleton class names for each parsed C file. - - attr_reader :c_singleton_class_variables - - ## - # If true this Store will not write any files - - attr_accessor :dry_run - - ## - # Path this store reads or writes - - attr_accessor :path - - ## - # The RDoc::RDoc driver for this parse tree. This allows classes consulting - # the documentation tree to access user-set options, for example. - - attr_accessor :rdoc - - ## - # Type of ri datastore this was loaded from. See RDoc::RI::Driver, - # RDoc::RI::Paths. - - attr_accessor :type - - ## - # The contents of the Store - - attr_reader :cache - - ## - # The encoding of the contents in the Store - - attr_accessor :encoding - - ## - # The lazy constants alias will be discovered in passing - - attr_reader :unmatched_constant_alias - - ## - # Creates a new Store of +type+ that will load or save to +path+ - - def initialize path = nil, type = nil - @dry_run = false - @encoding = nil - @path = path - @rdoc = nil - @type = type - - @cache = { - :ancestors => {}, - :attributes => {}, - :class_methods => {}, - :c_class_variables => {}, - :c_singleton_class_variables => {}, - :encoding => @encoding, - :instance_methods => {}, - :main => nil, - :modules => [], - :pages => [], - :title => nil, - } - - @classes_hash = {} - @modules_hash = {} - @files_hash = {} - @text_files_hash = {} - - @c_enclosure_classes = {} - @c_enclosure_names = {} - - @c_class_variables = {} - @c_singleton_class_variables = {} - - @unique_classes = nil - @unique_modules = nil - - @unmatched_constant_alias = {} - end - - ## - # Adds +module+ as an enclosure (namespace) for the given +variable+ for C - # files. - - def add_c_enclosure variable, namespace - @c_enclosure_classes[variable] = namespace - end - - ## - # Adds C variables from an RDoc::Parser::C - - def add_c_variables c_parser - filename = c_parser.top_level.relative_name - - @c_class_variables[filename] = make_variable_map c_parser.classes - - @c_singleton_class_variables[filename] = c_parser.singleton_classes - end - - ## - # Adds the file with +name+ as an RDoc::TopLevel to the store. Returns the - # created RDoc::TopLevel. - - def add_file absolute_name, relative_name: absolute_name, parser: nil - unless top_level = @files_hash[relative_name] then - top_level = RDoc::TopLevel.new absolute_name, relative_name - top_level.parser = parser if parser - top_level.store = self - @files_hash[relative_name] = top_level - @text_files_hash[relative_name] = top_level if top_level.text? - end - - top_level - end - - ## - # Sets the parser of +absolute_name+, unless it from a source code file. - - def update_parser_of_file(absolute_name, parser) - if top_level = @files_hash[absolute_name] then - @text_files_hash[absolute_name] = top_level if top_level.text? - end - end - - ## - # Returns all classes discovered by RDoc - - def all_classes - @classes_hash.values - end - - ## - # Returns all classes and modules discovered by RDoc - - def all_classes_and_modules - @classes_hash.values + @modules_hash.values - end - - ## - # All TopLevels known to RDoc - - def all_files - @files_hash.values - end - - ## - # Returns all modules discovered by RDoc - - def all_modules - modules_hash.values - end - - ## - # Ancestors cache accessor. Maps a klass name to an Array of its ancestors - # in this store. If Foo in this store inherits from Object, Kernel won't be - # listed (it will be included from ruby's ri store). - - def ancestors - @cache[:ancestors] - end - - ## - # Attributes cache accessor. Maps a class to an Array of its attributes. - - def attributes - @cache[:attributes] - end - - ## - # Path to the cache file - - def cache_path - File.join @path, 'cache.ri' - end - - ## - # Path to the ri data for +klass_name+ - - def class_file klass_name - name = klass_name.split('::').last - File.join class_path(klass_name), "cdesc-#{name}.ri" - end - - ## - # Class methods cache accessor. Maps a class to an Array of its class - # methods (not full name). - - def class_methods - @cache[:class_methods] - end - - ## - # Path where data for +klass_name+ will be stored (methods or class data) - - def class_path klass_name - File.join @path, *klass_name.split('::') - end - - ## - # Hash of all classes known to RDoc - - def classes_hash - @classes_hash - end - - ## - # Removes empty items and ensures item in each collection are unique and - # sorted - - def clean_cache_collection collection # :nodoc: - collection.each do |name, item| - if item.empty? then - collection.delete name - else - # HACK mongrel-1.1.5 documents its files twice - item.uniq! - item.sort! - end - end - end - - ## - # Prepares the RDoc code object tree for use by a generator. - # - # It finds unique classes/modules defined, and replaces classes/modules that - # are aliases for another one by a copy with RDoc::ClassModule#is_alias_for - # set. - # - # It updates the RDoc::ClassModule#constant_aliases attribute of "real" - # classes or modules. - # - # It also completely removes the classes and modules that should be removed - # from the documentation and the methods that have a visibility below - # +min_visibility+, which is the <tt>--visibility</tt> option. - # - # See also RDoc::Context#remove_from_documentation? - - def complete min_visibility - fix_basic_object_inheritance - - # cache included modules before they are removed from the documentation - all_classes_and_modules.each { |cm| cm.ancestors } - - unless min_visibility == :nodoc then - remove_nodoc @classes_hash - remove_nodoc @modules_hash - end - - @unique_classes = find_unique @classes_hash - @unique_modules = find_unique @modules_hash - - unique_classes_and_modules.each do |cm| - cm.complete min_visibility - end - - @files_hash.each_key do |file_name| - tl = @files_hash[file_name] - - unless tl.text? then - tl.modules_hash.clear - tl.classes_hash.clear - - tl.classes_or_modules.each do |cm| - name = cm.full_name - if cm.type == 'class' then - tl.classes_hash[name] = cm if @classes_hash[name] - else - tl.modules_hash[name] = cm if @modules_hash[name] - end - end - end - end - end - - ## - # Hash of all files known to RDoc - - def files_hash - @files_hash - end - - ## - # Finds the enclosure (namespace) for the given C +variable+. - - def find_c_enclosure variable - @c_enclosure_classes.fetch variable do - break unless name = @c_enclosure_names[variable] - - mod = find_class_or_module name - - unless mod then - loaded_mod = load_class_data name - - file = loaded_mod.in_files.first - - return unless file # legacy data source - - file.store = self - - mod = file.add_module RDoc::NormalModule, name - end - - @c_enclosure_classes[variable] = mod - end - end - - ## - # Finds the class with +name+ in all discovered classes - - def find_class_named name - @classes_hash[name] - end - - ## - # Finds the class with +name+ starting in namespace +from+ - - def find_class_named_from name, from - from = find_class_named from unless RDoc::Context === from - - until RDoc::TopLevel === from do - return nil unless from - - klass = from.find_class_named name - return klass if klass - - from = from.parent - end - - find_class_named name - end - - ## - # Finds the class or module with +name+ - - def find_class_or_module name - name = $' if name =~ /^::/ - @classes_hash[name] || @modules_hash[name] - end - - ## - # Finds the file with +name+ in all discovered files - - def find_file_named name - @files_hash[name] - end - - ## - # Finds the module with +name+ in all discovered modules - - def find_module_named name - @modules_hash[name] - end - - ## - # Returns the RDoc::TopLevel that is a text file and has the given - # +file_name+ - - def find_text_page file_name - @text_files_hash.each_value.find do |file| - file.full_name == file_name - end - end - - ## - # Finds unique classes/modules defined in +all_hash+, - # and returns them as an array. Performs the alias - # updates in +all_hash+: see ::complete. - #-- - # TODO aliases should be registered by Context#add_module_alias - - def find_unique all_hash - unique = [] - - all_hash.each_pair do |full_name, cm| - unique << cm if full_name == cm.full_name - end - - unique - end - - ## - # Fixes the erroneous <tt>BasicObject < Object</tt> in 1.9. - # - # Because we assumed all classes without a stated superclass - # inherit from Object, we have the above wrong inheritance. - # - # We fix BasicObject right away if we are running in a Ruby - # version >= 1.9. - - def fix_basic_object_inheritance - basic = classes_hash['BasicObject'] - return unless basic - basic.superclass = nil - end - - ## - # Friendly rendition of #path - - def friendly_path - case type - when :gem then - parent = File.expand_path '..', @path - "gem #{File.basename parent}" - when :home then RDoc.home - when :site then 'ruby site' - when :system then 'ruby core' - else @path - end - end - - def inspect # :nodoc: - "#<%s:0x%x %s %p>" % [self.class, object_id, @path, module_names.sort] - end - - ## - # Instance methods cache accessor. Maps a class to an Array of its - # instance methods (not full name). - - def instance_methods - @cache[:instance_methods] - end - - ## - # Loads all items from this store into memory. This recreates a - # documentation tree for use by a generator - - def load_all - load_cache - - module_names.each do |module_name| - mod = find_class_or_module(module_name) || load_class(module_name) - - # load method documentation since the loaded class/module does not have - # it - loaded_methods = mod.method_list.map do |method| - load_method module_name, method.full_name - end - - mod.method_list.replace loaded_methods - - loaded_attributes = mod.attributes.map do |attribute| - load_method module_name, attribute.full_name - end - - mod.attributes.replace loaded_attributes - end - - all_classes_and_modules.each do |mod| - descendent_re = /^#{mod.full_name}::[^:]+$/ - - module_names.each do |name| - next unless name =~ descendent_re - - descendent = find_class_or_module name - - case descendent - when RDoc::NormalClass then - mod.classes_hash[name] = descendent - when RDoc::NormalModule then - mod.modules_hash[name] = descendent - end - end - end - - @cache[:pages].each do |page_name| - page = load_page page_name - @files_hash[page_name] = page - @text_files_hash[page_name] = page if page.text? - end - end - - ## - # Loads cache file for this store - - def load_cache - #orig_enc = @encoding - - @cache = marshal_load(cache_path) - - load_enc = @cache[:encoding] - - # TODO this feature will be time-consuming to add: - # a) Encodings may be incompatible but transcodeable - # b) Need to warn in the appropriate spots, wherever they may be - # c) Need to handle cross-cache differences in encodings - # d) Need to warn when generating into a cache with different encodings - # - #if orig_enc and load_enc != orig_enc then - # warn "Cached encoding #{load_enc} is incompatible with #{orig_enc}\n" \ - # "from #{path}/cache.ri" unless - # Encoding.compatible? orig_enc, load_enc - #end - - @encoding = load_enc unless @encoding - - @cache[:pages] ||= [] - @cache[:main] ||= nil - @cache[:c_class_variables] ||= {} - @cache[:c_singleton_class_variables] ||= {} - - @cache[:c_class_variables].each do |_, map| - map.each do |variable, name| - @c_enclosure_names[variable] = name - end - end - - @cache - rescue Errno::ENOENT - end - - ## - # Loads ri data for +klass_name+ and hooks it up to this store. - - def load_class klass_name - obj = load_class_data klass_name - - obj.store = self - - case obj - when RDoc::NormalClass then - @classes_hash[klass_name] = obj - when RDoc::SingleClass then - @classes_hash[klass_name] = obj - when RDoc::NormalModule then - @modules_hash[klass_name] = obj - end - end - - ## - # Loads ri data for +klass_name+ - - def load_class_data klass_name - file = class_file klass_name - - marshal_load(file) - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, klass_name) - error.set_backtrace e.backtrace - raise error - end - - ## - # Loads ri data for +method_name+ in +klass_name+ - - def load_method klass_name, method_name - file = method_file klass_name, method_name - - obj = marshal_load(file) - obj.store = self - obj.parent ||= find_class_or_module(klass_name) || load_class(klass_name) - obj - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, klass_name + method_name) - error.set_backtrace e.backtrace - raise error - end - - ## - # Loads ri data for +page_name+ - - def load_page page_name - file = page_file page_name - - obj = marshal_load(file) - obj.store = self - obj - rescue Errno::ENOENT => e - error = MissingFileError.new(self, file, page_name) - error.set_backtrace e.backtrace - raise error - end - - ## - # Gets the main page for this RDoc store. This page is used as the root of - # the RDoc server. - - def main - @cache[:main] - end - - ## - # Sets the main page for this RDoc store. - - def main= page - @cache[:main] = page - end - - ## - # Converts the variable => ClassModule map +variables+ from a C parser into - # a variable => class name map. - - def make_variable_map variables - map = {} - - variables.each { |variable, class_module| - map[variable] = class_module.full_name - } - - map - end - - ## - # Path to the ri data for +method_name+ in +klass_name+ - - def method_file klass_name, method_name - method_name = method_name.split('::').last - method_name =~ /#(.*)/ - method_type = $1 ? 'i' : 'c' - method_name = $1 if $1 - method_name = method_name.gsub(/\W/) { "%%%02x" % $&[0].ord } - - File.join class_path(klass_name), "#{method_name}-#{method_type}.ri" - end - - ## - # Modules cache accessor. An Array of all the module (and class) names in - # the store. - - def module_names - @cache[:modules] - end - - ## - # Hash of all modules known to RDoc - - def modules_hash - @modules_hash - end - - ## - # Returns the RDoc::TopLevel that is a text file and has the given +name+ - - def page name - @text_files_hash.each_value.find do |file| - file.page_name == name or file.base_name == name - end - end - - ## - # Path to the ri data for +page_name+ - - def page_file page_name - file_name = File.basename(page_name).gsub('.', '_') - - File.join @path, File.dirname(page_name), "page-#{file_name}.ri" - end - - ## - # Removes from +all_hash+ the contexts that are nodoc or have no content. - # - # See RDoc::Context#remove_from_documentation? - - def remove_nodoc all_hash - all_hash.keys.each do |name| - context = all_hash[name] - all_hash.delete(name) if context.remove_from_documentation? - end - end - - ## - # Saves all entries in the store - - def save - load_cache - - all_classes_and_modules.each do |klass| - save_class klass - - klass.each_method do |method| - save_method klass, method - end - - klass.each_attribute do |attribute| - save_method klass, attribute - end - end - - all_files.each do |file| - save_page file - end - - save_cache - end - - ## - # Writes the cache file for this store - - def save_cache - clean_cache_collection @cache[:ancestors] - clean_cache_collection @cache[:attributes] - clean_cache_collection @cache[:class_methods] - clean_cache_collection @cache[:instance_methods] - - @cache[:modules].uniq! - @cache[:modules].sort! - - @cache[:pages].uniq! - @cache[:pages].sort! - - @cache[:encoding] = @encoding # this gets set twice due to assert_cache - - @cache[:c_class_variables].merge! @c_class_variables - @cache[:c_singleton_class_variables].merge! @c_singleton_class_variables - - return if @dry_run - - File.open cache_path, 'wb' do |io| - Marshal.dump @cache, io - end - end - - ## - # Writes the ri data for +klass+ (or module) - - def save_class klass - full_name = klass.full_name - - FileUtils.mkdir_p class_path(full_name) unless @dry_run - - @cache[:modules] << full_name - - path = class_file full_name - - begin - disk_klass = load_class full_name - - klass = disk_klass.merge klass - rescue MissingFileError - end - - # BasicObject has no ancestors - ancestors = klass.direct_ancestors.compact.map do |ancestor| - # HACK for classes we don't know about (class X < RuntimeError) - String === ancestor ? ancestor : ancestor.full_name - end - - @cache[:ancestors][full_name] ||= [] - @cache[:ancestors][full_name].concat ancestors - - attribute_definitions = klass.attributes.map do |attribute| - "#{attribute.definition} #{attribute.name}" - end - - unless attribute_definitions.empty? then - @cache[:attributes][full_name] ||= [] - @cache[:attributes][full_name].concat attribute_definitions - end - - to_delete = [] - - unless klass.method_list.empty? then - @cache[:class_methods][full_name] ||= [] - @cache[:instance_methods][full_name] ||= [] - - class_methods, instance_methods = - klass.method_list.partition { |meth| meth.singleton } - - class_methods = class_methods. map { |method| method.name } - instance_methods = instance_methods.map { |method| method.name } - attribute_names = klass.attributes.map { |attr| attr.name } - - old = @cache[:class_methods][full_name] - class_methods - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}::#{method}" - } - - old = @cache[:instance_methods][full_name] - - instance_methods - attribute_names - to_delete.concat old.map { |method| - method_file full_name, "#{full_name}##{method}" - } - - @cache[:class_methods][full_name] = class_methods - @cache[:instance_methods][full_name] = instance_methods - end - - return if @dry_run - - FileUtils.rm_f to_delete - - File.open path, 'wb' do |io| - Marshal.dump klass, io - end - end - - ## - # Writes the ri data for +method+ on +klass+ - - def save_method klass, method - full_name = klass.full_name - - FileUtils.mkdir_p class_path(full_name) unless @dry_run - - cache = if method.singleton then - @cache[:class_methods] - else - @cache[:instance_methods] - end - cache[full_name] ||= [] - cache[full_name] << method.name - - return if @dry_run - - File.open method_file(full_name, method.full_name), 'wb' do |io| - Marshal.dump method, io - end - end - - ## - # Writes the ri data for +page+ - - def save_page page - return unless page.text? - - path = page_file page.full_name - - FileUtils.mkdir_p File.dirname(path) unless @dry_run - - cache[:pages] ||= [] - cache[:pages] << page.full_name - - return if @dry_run - - File.open path, 'wb' do |io| - Marshal.dump page, io - end - end - - ## - # Source of the contents of this store. - # - # For a store from a gem the source is the gem name. For a store from the - # home directory the source is "home". For system ri store (the standard - # library documentation) the source is"ruby". For a store from the site - # ri directory the store is "site". For other stores the source is the - # #path. - - def source - case type - when :gem then File.basename File.expand_path '..', @path - when :home then 'home' - when :site then 'site' - when :system then 'ruby' - else @path - end - end - - ## - # Gets the title for this RDoc store. This is used as the title in each - # page on the RDoc server - - def title - @cache[:title] - end - - ## - # Sets the title page for this RDoc store. - - def title= title - @cache[:title] = title - end - - ## - # Returns the unique classes discovered by RDoc. - # - # ::complete must have been called prior to using this method. - - def unique_classes - @unique_classes - end - - ## - # Returns the unique classes and modules discovered by RDoc. - # ::complete must have been called prior to using this method. - - def unique_classes_and_modules - @unique_classes + @unique_modules - end - - ## - # Returns the unique modules discovered by RDoc. - # ::complete must have been called prior to using this method. - - def unique_modules - @unique_modules - end - - private - def marshal_load(file) - File.open(file, 'rb') {|io| Marshal.load(io, MarshalFilter)} - end - - MarshalFilter = proc do |obj| - case obj - when true, false, nil, Array, Class, Encoding, Hash, Integer, String, Symbol, RDoc::Text - else - unless obj.class.name.start_with?("RDoc::") - raise TypeError, "not permitted class: #{obj.class.name}" - end - end - obj - end - private_constant :MarshalFilter - -end diff --git a/lib/rdoc/task.rb b/lib/rdoc/task.rb deleted file mode 100644 index ba697d0a93..0000000000 --- a/lib/rdoc/task.rb +++ /dev/null @@ -1,354 +0,0 @@ -# frozen_string_literal: true -#-- -# Copyright (c) 2003, 2004 Jim Weirich, 2009 Eric Hodel -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -#++ - -begin - gem 'rdoc' -rescue Gem::LoadError -end unless defined?(RDoc) - -begin - gem 'rake' -rescue Gem::LoadError -end unless defined?(Rake) - -require_relative '../rdoc' -require 'rake' -require 'rake/tasklib' - -## -# RDoc::Task creates the following rake tasks to generate and clean up RDoc -# output: -# -# [rdoc] -# Main task for this RDoc task. -# -# [clobber_rdoc] -# Delete all the rdoc files. This target is automatically added to the main -# clobber target. -# -# [rerdoc] -# Rebuild the rdoc files from scratch, even if they are not out of date. -# -# [rdoc:coverage] -# Print RDoc coverage report for all rdoc files. -# -# Simple Example: -# -# require 'rdoc/task' -# -# RDoc::Task.new do |rdoc| -# rdoc.main = "README.rdoc" -# rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb") -# end -# -# The +rdoc+ object passed to the block is an RDoc::Task object. See the -# attributes list for the RDoc::Task class for available customization options. -# -# == Specifying different task names -# -# You may wish to give the task a different name, such as if you are -# generating two sets of documentation. For instance, if you want to have a -# development set of documentation including private methods: -# -# require 'rdoc/task' -# -# RDoc::Task.new :rdoc_dev do |rdoc| -# rdoc.main = "README.rdoc" -# rdoc.rdoc_files.include("README.rdoc", "lib/**/*.rb") -# rdoc.options << "--all" -# end -# -# The tasks would then be named :<em>rdoc_dev</em>, -# :clobber_<em>rdoc_dev</em>, and :re<em>rdoc_dev</em>. -# -# If you wish to have completely different task names, then pass a Hash as -# first argument. With the <tt>:rdoc</tt>, <tt>:clobber_rdoc</tt> and -# <tt>:rerdoc</tt> options, you can customize the task names to your liking. -# -# For example: -# -# require 'rdoc/task' -# -# RDoc::Task.new(:rdoc => "rdoc", :clobber_rdoc => "rdoc:clean", -# :rerdoc => "rdoc:force") -# -# This will create the tasks <tt>:rdoc</tt>, <tt>:rdoc:clean</tt>, -# <tt>:rdoc:force</tt>, and <tt>:rdoc:coverage</tt>. - -class RDoc::Task < Rake::TaskLib - - ## - # Name of the main, top level task. (default is :rdoc) - - attr_accessor :name - - ## - # The markup format; one of: +rdoc+ (the default), +markdown+, +rd+, +tomdoc+. - # See {Markup Formats}[rdoc-ref:RDoc::Markup@Markup+Formats]. - attr_accessor :markup - - ## - # Name of directory to receive the html output files. (default is "html") - - attr_accessor :rdoc_dir - - ## - # Title of RDoc documentation. (defaults to rdoc's default) - - attr_accessor :title - - ## - # Name of file to be used as the main, top level file of the RDoc. (default - # is none) - - attr_accessor :main - - ## - # Name of template to be used by rdoc. (defaults to rdoc's default) - - attr_accessor :template - - ## - # Name of format generator (<tt>--format</tt>) used by rdoc. (defaults to - # rdoc's default) - - attr_accessor :generator - - ## - # List of files to be included in the rdoc generation. (default is []) - - attr_accessor :rdoc_files - - ## - # Additional list of options to be passed rdoc. (default is []) - - attr_accessor :options - - ## - # Whether to run the rdoc process as an external shell (default is false) - - attr_accessor :external - - ## - # Create an RDoc task with the given name. See the RDoc::Task class overview - # for documentation. - - def initialize name = :rdoc # :yield: self - defaults - - check_names name - - @name = name - - yield self if block_given? - - define - end - - ## - # Ensures that +names+ only includes names for the :rdoc, :clobber_rdoc and - # :rerdoc. If other names are given an ArgumentError is raised. - - def check_names names - return unless Hash === names - - invalid_options = - names.keys.map { |k| k.to_sym } - [:rdoc, :clobber_rdoc, :rerdoc] - - unless invalid_options.empty? then - raise ArgumentError, "invalid options: #{invalid_options.join ', '}" - end - end - - ## - # Task description for the clobber rdoc task or its renamed equivalent - - def clobber_task_description - "Remove RDoc HTML files" - end - - ## - # Sets default task values - - def defaults - @name = :rdoc - @rdoc_files = Rake::FileList.new - @rdoc_dir = 'html' - @main = nil - @title = nil - @template = nil - @generator = nil - @options = [] - end - - ## - # All source is inline now. This method is deprecated - - def inline_source # :nodoc: - warn "RDoc::Task#inline_source is deprecated" - true - end - - ## - # All source is inline now. This method is deprecated - - def inline_source=(value) # :nodoc: - warn "RDoc::Task#inline_source is deprecated" - end - - ## - # Create the tasks defined by this task lib. - - def define - desc rdoc_task_description - task rdoc_task_name - - desc rerdoc_task_description - task rerdoc_task_name => [clobber_task_name, rdoc_task_name] - - desc clobber_task_description - task clobber_task_name do - rm_r @rdoc_dir rescue nil - end - - task :clobber => [clobber_task_name] - - directory @rdoc_dir - - rdoc_target_deps = [ - @rdoc_files, - Rake.application.rakefile - ].flatten.compact - - task rdoc_task_name => [rdoc_target] - file rdoc_target => rdoc_target_deps do - @before_running_rdoc.call if @before_running_rdoc - args = option_list + @rdoc_files - - $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - RDoc::RDoc.new.document args - end - - namespace rdoc_task_name do - desc coverage_task_description - task coverage_task_name do - @before_running_rdoc.call if @before_running_rdoc - opts = option_list << "-C" - args = opts + @rdoc_files - - $stderr.puts "rdoc #{args.join ' '}" if Rake.application.options.trace - RDoc::RDoc.new.document args - end - end - - self - end - - ## - # List of options that will be supplied to RDoc - - def option_list - result = @options.dup - result << "-o" << @rdoc_dir - result << "--main" << main if main - result << "--markup" << markup if markup - result << "--title" << title if title - result << "-T" << template if template - result << '-f' << generator if generator - result - end - - ## - # The block passed to this method will be called just before running the - # RDoc generator. It is allowed to modify RDoc::Task attributes inside the - # block. - - def before_running_rdoc(&block) - @before_running_rdoc = block - end - - ## - # Task description for the rdoc task or its renamed equivalent - - def rdoc_task_description - 'Build RDoc HTML files' - end - - ## - # Task description for the rerdoc task or its renamed description - - def rerdoc_task_description - "Rebuild RDoc HTML files" - end - - ## - # Task description for the coverage task or its renamed description - - def coverage_task_description - "Print RDoc coverage report" - end - - private - - def rdoc_target - "#{rdoc_dir}/created.rid" - end - - def rdoc_task_name - case name - when Hash then (name[:rdoc] || "rdoc").to_s - else name.to_s - end - end - - def clobber_task_name - case name - when Hash then (name[:clobber_rdoc] || "clobber_rdoc").to_s - else "clobber_#{name}" - end - end - - def rerdoc_task_name - case name - when Hash then (name[:rerdoc] || "rerdoc").to_s - else "re#{name}" - end - end - - def coverage_task_name - "coverage" - end - -end - -# :stopdoc: -module Rake - - ## - # For backwards compatibility - - RDocTask = RDoc::Task # :nodoc: - -end -# :startdoc: diff --git a/lib/rdoc/text.rb b/lib/rdoc/text.rb deleted file mode 100644 index 9804f81abe..0000000000 --- a/lib/rdoc/text.rb +++ /dev/null @@ -1,322 +0,0 @@ -# frozen_string_literal: true - -## -# For RDoc::Text#to_html - -require 'strscan' - -## -# Methods for manipulating comment text - -module RDoc::Text - - ## - # The language for this text. This affects stripping comments - # markers. - - attr_accessor :language - - ## - # Maps markup formats to classes that can parse them. If the format is - # unknown, "rdoc" format is used. - - MARKUP_FORMAT = { - 'markdown' => RDoc::Markdown, - 'rdoc' => RDoc::Markup, - 'rd' => RDoc::RD, - 'tomdoc' => RDoc::TomDoc, - } - - MARKUP_FORMAT.default = RDoc::Markup - - ## - # Maps an encoding to a Hash of characters properly transcoded for that - # encoding. - # - # See also encode_fallback. - - TO_HTML_CHARACTERS = Hash.new do |h, encoding| - h[encoding] = { - :close_dquote => encode_fallback('”', encoding, '"'), - :close_squote => encode_fallback('’', encoding, '\''), - :copyright => encode_fallback('©', encoding, '(c)'), - :ellipsis => encode_fallback('…', encoding, '...'), - :em_dash => encode_fallback('—', encoding, '---'), - :en_dash => encode_fallback('–', encoding, '--'), - :open_dquote => encode_fallback('“', encoding, '"'), - :open_squote => encode_fallback('‘', encoding, '\''), - :trademark => encode_fallback('®', encoding, '(r)'), - } - end - - ## - # Transcodes +character+ to +encoding+ with a +fallback+ character. - - def self.encode_fallback character, encoding, fallback - character.encode(encoding, :fallback => { character => fallback }, - :undef => :replace, :replace => fallback) - end - - ## - # Expands tab characters in +text+ to eight spaces - - def expand_tabs text - expanded = [] - - text.each_line do |line| - nil while line.gsub!(/(?:\G|\r)((?:.{8})*?)([^\t\r\n]{0,7})\t/) do - r = "#{$1}#{$2}#{' ' * (8 - $2.size)}" - r = RDoc::Encoding.change_encoding r, text.encoding - r - end - - expanded << line - end - - expanded.join - end - - ## - # Flush +text+ left based on the shortest line - - def flush_left text - indent = 9999 - - text.each_line do |line| - line_indent = line =~ /\S/ || 9999 - indent = line_indent if indent > line_indent - end - - empty = '' - empty = RDoc::Encoding.change_encoding empty, text.encoding - - text.gsub(/^ {0,#{indent}}/, empty) - end - - ## - # Convert a string in markup format into HTML. - # - # Requires the including class to implement #formatter - - def markup text - if @store.rdoc.options - locale = @store.rdoc.options.locale - else - locale = nil - end - if locale - i18n_text = RDoc::I18n::Text.new(text) - text = i18n_text.translate(locale) - end - parse(text).accept formatter - end - - ## - # Strips hashes, expands tabs then flushes +text+ to the left - - def normalize_comment text - return text if text.empty? - - case language - when :ruby - text = strip_hashes text - when :c - text = strip_stars text - end - text = expand_tabs text - text = flush_left text - text = strip_newlines text - text - end - - ## - # Normalizes +text+ then builds a RDoc::Markup::Document from it - - def parse text, format = 'rdoc' - return text if RDoc::Markup::Document === text - return text.parse if RDoc::Comment === text - - text = normalize_comment text # TODO remove, should not be necessary - - return RDoc::Markup::Document.new if text =~ /\A\n*\z/ - - MARKUP_FORMAT[format].parse text - end - - ## - # The first +limit+ characters of +text+ as HTML - - def snippet text, limit = 100 - document = parse text - - RDoc::Markup::ToHtmlSnippet.new(options, limit).convert document - end - - ## - # Strips leading # characters from +text+ - - def strip_hashes text - return text if text =~ /^(?>\s*)[^\#]/ - - empty = '' - empty = RDoc::Encoding.change_encoding empty, text.encoding - - text.gsub(/^\s*(#+)/) { $1.tr '#', ' ' }.gsub(/^\s+$/, empty) - end - - ## - # Strips leading and trailing \n characters from +text+ - - def strip_newlines text - text.gsub(/\A\n*(.*?)\n*\z/m) do $1 end # block preserves String encoding - end - - ## - # Strips /* */ style comments - - def strip_stars text - return text unless text =~ %r%/\*.*\*/%m - - encoding = text.encoding - - text = text.gsub %r%Document-method:\s+[\w:.#=!?|^&<>~+\-/*\%@`\[\]]+%, '' - - space = ' ' - space = RDoc::Encoding.change_encoding space, encoding if encoding - - text.sub! %r%/\*+% do space * $&.length end - text.sub! %r%\*+/% do space * $&.length end - text.gsub! %r%^[ \t]*\*%m do space * $&.length end - - empty = '' - empty = RDoc::Encoding.change_encoding empty, encoding if encoding - text.gsub(/^\s+$/, empty) - end - - ## - # Converts ampersand, dashes, ellipsis, quotes, copyright and registered - # trademark symbols in +text+ to properly encoded characters. - - def to_html text - html = (''.encode text.encoding).dup - - encoded = RDoc::Text::TO_HTML_CHARACTERS[text.encoding] - - s = StringScanner.new text - insquotes = false - indquotes = false - after_word = nil - - until s.eos? do - case - when s.scan(/<(tt|code)>.*?<\/\1>/) then # skip contents of tt - html << s.matched.gsub('\\\\', '\\') - when s.scan(/<(tt|code)>.*?/) then - warn "mismatched <#{s[1]}> tag" # TODO signal file/line - html << s.matched - when s.scan(/<[^>]+\/?s*>/) then # skip HTML tags - html << s.matched - when s.scan(/\\(\S)/) then # unhandled suppressed crossref - html << s[1] - after_word = nil - when s.scan(/\.\.\.(\.?)/) then - html << s[1] << encoded[:ellipsis] - after_word = nil - when s.scan(/\(c\)/i) then - html << encoded[:copyright] - after_word = nil - when s.scan(/\(r\)/i) then - html << encoded[:trademark] - after_word = nil - when s.scan(/---/) then - html << encoded[:em_dash] - after_word = nil - when s.scan(/--/) then - html << encoded[:en_dash] - after_word = nil - when s.scan(/"|"/) then - html << encoded[indquotes ? :close_dquote : :open_dquote] - indquotes = !indquotes - after_word = nil - when s.scan(/``/) then # backtick double quote - html << encoded[:open_dquote] - after_word = nil - when s.scan(/(?:'|'){2}/) then # tick double quote - html << encoded[:close_dquote] - after_word = nil - when s.scan(/`/) then # backtick - if insquotes or after_word - html << '`' - after_word = false - else - html << encoded[:open_squote] - insquotes = true - end - when s.scan(/'|'/) then # single quote - if insquotes - html << encoded[:close_squote] - insquotes = false - elsif after_word - # Mary's dog, my parents' house: do not start paired quotes - html << encoded[:close_squote] - else - html << encoded[:open_squote] - insquotes = true - end - - after_word = nil - else # advance to the next potentially significant character - match = s.scan(/.+?(?=[<\\.("'`&-])/) #" - - if match then - html << match - after_word = match =~ /\w$/ - else - html << s.rest - break - end - end - end - - html - end - - ## - # Wraps +txt+ to +line_len+ - - def wrap(txt, line_len = 76) - res = [] - sp = 0 - ep = txt.length - - while sp < ep - # scan back for a space - p = sp + line_len - 1 - if p >= ep - p = ep - else - while p > sp and txt[p] != ?\s - p -= 1 - end - if p <= sp - p = sp + line_len - while p < ep and txt[p] != ?\s - p += 1 - end - end - end - res << txt[sp...p] << "\n" - sp = p - sp += 1 while sp < ep and txt[sp] == ?\s - end - - res.join.strip - end - - ## - # Character class to be separated by a space when concatenating - # lines. - - SPACE_SEPARATED_LETTER_CLASS = /[\p{Nd}\p{Lc}\p{Pc}]|[!-~&&\W]/ - -end diff --git a/lib/rdoc/token_stream.rb b/lib/rdoc/token_stream.rb deleted file mode 100644 index 19ca7ed248..0000000000 --- a/lib/rdoc/token_stream.rb +++ /dev/null @@ -1,118 +0,0 @@ -# frozen_string_literal: true -## -# A TokenStream is a list of tokens, gathered during the parse of some entity -# (say a method). Entities populate these streams by being registered with the -# lexer. Any class can collect tokens by including TokenStream. From the -# outside, you use such an object by calling the start_collecting_tokens -# method, followed by calls to add_token and pop_token. - -module RDoc::TokenStream - - ## - # Converts +token_stream+ to HTML wrapping various tokens with - # <tt><span></tt> elements. Some tokens types are wrapped in spans - # with the given class names. Other token types are not wrapped in spans. - - def self.to_html token_stream - starting_title = false - - token_stream.map do |t| - next unless t - - style = case t[:kind] - when :on_const then 'ruby-constant' - when :on_kw then 'ruby-keyword' - when :on_ivar then 'ruby-ivar' - when :on_cvar then 'ruby-identifier' - when :on_gvar then 'ruby-identifier' - when '=' != t[:text] && :on_op - then 'ruby-operator' - when :on_tlambda then 'ruby-operator' - when :on_ident then 'ruby-identifier' - when :on_label then 'ruby-value' - when :on_backref, :on_dstring - then 'ruby-node' - when :on_comment then 'ruby-comment' - when :on_embdoc then 'ruby-comment' - when :on_regexp then 'ruby-regexp' - when :on_tstring then 'ruby-string' - when :on_int, :on_float, - :on_rational, :on_imaginary, - :on_heredoc, - :on_symbol, :on_CHAR then 'ruby-value' - when :on_heredoc_beg, :on_heredoc_end - then 'ruby-identifier' - end - - comment_with_nl = false - if :on_comment == t[:kind] or :on_embdoc == t[:kind] or :on_heredoc_end == t[:kind] - comment_with_nl = true if "\n" == t[:text][-1] - text = t[:text].rstrip - else - text = t[:text] - end - - if :on_ident == t[:kind] && starting_title - starting_title = false - style = 'ruby-identifier ruby-title' - end - - if :on_kw == t[:kind] and 'def' == t[:text] - starting_title = true - end - - text = CGI.escapeHTML text - - if style then - "<span class=\"#{style}\">#{text}</span>#{"\n" if comment_with_nl}" - else - text - end - end.join - end - - ## - # Adds +tokens+ to the collected tokens - - def add_tokens(tokens) - @token_stream.concat(tokens) - end - - ## - # Adds one +token+ to the collected tokens - - def add_token(token) - @token_stream.push(token) - end - - ## - # Starts collecting tokens - - def collect_tokens - @token_stream = [] - end - - alias start_collecting_tokens collect_tokens - - ## - # Remove the last token from the collected tokens - - def pop_token - @token_stream.pop - end - - ## - # Current token stream - - def token_stream - @token_stream - end - - ## - # Returns a string representation of the token stream - - def tokens_to_s - (token_stream or return '').compact.map { |token| token[:text] }.join '' - end - -end diff --git a/lib/rdoc/tom_doc.rb b/lib/rdoc/tom_doc.rb deleted file mode 100644 index d10f024f70..0000000000 --- a/lib/rdoc/tom_doc.rb +++ /dev/null @@ -1,257 +0,0 @@ -# frozen_string_literal: true -# :markup: tomdoc - -# A parser for TomDoc based on TomDoc 1.0.0-rc1 (02adef9b5a) -# -# The TomDoc specification can be found at http://tomdoc.org. -# -# To choose TomDoc as your only default format see RDoc::Options@Saved+Options -# for instructions on setting up a <code>.rdoc_options</code> file to store -# your project default. -# -# There are a few differences between this parser and the specification. A -# best-effort was made to follow the specification as closely as possible but -# some choices to deviate were made. -# -# A future version of RDoc will warn when a MUST or MUST NOT is violated and -# may warn when a SHOULD or SHOULD NOT is violated. RDoc will always try -# to emit documentation even if given invalid TomDoc. -# -# Here are some implementation choices this parser currently makes: -# -# This parser allows rdoc-style inline markup but you should not depended on -# it. -# -# This parser allows a space between the comment and the method body. -# -# This parser does not require the default value to be described for an -# optional argument. -# -# This parser does not examine the order of sections. An Examples section may -# precede the Arguments section. -# -# This class is documented in TomDoc format. Since this is a subclass of the -# RDoc markup parser there isn't much to see here, unfortunately. - -class RDoc::TomDoc < RDoc::Markup::Parser - - # Internal: Token accessor - - attr_reader :tokens - - # Internal: Adds a post-processor which sets the RDoc section based on the - # comment's status. - # - # Returns nothing. - - def self.add_post_processor # :nodoc: - RDoc::Markup::PreProcess.post_process do |comment, code_object| - next unless code_object and - RDoc::Comment === comment and comment.format == 'tomdoc' - - comment.text.gsub!(/(\A\s*# )(Public|Internal|Deprecated):\s+/) do - section = code_object.add_section $2 - code_object.temporary_section = section - - $1 - end - end - end - - add_post_processor - - # Public: Parses TomDoc from text - # - # text - A String containing TomDoc-format text. - # - # Examples - # - # RDoc::TomDoc.parse <<-TOMDOC - # This method does some things - # - # Returns nothing. - # TOMDOC - # # => #<RDoc::Markup::Document:0xXXX @parts=[...], @file=nil> - # - # Returns an RDoc::Markup::Document representing the TomDoc format. - - def self.parse text - parser = new - - parser.tokenize text - doc = RDoc::Markup::Document.new - parser.parse doc - doc - end - - # Internal: Extracts the Signature section's method signature - # - # comment - An RDoc::Comment that will be parsed and have the signature - # extracted - # - # Returns a String containing the signature and nil if not - - def self.signature comment - return unless comment.tomdoc? - - document = comment.parse - - signature = nil - found_heading = false - found_signature = false - - document.parts.delete_if do |part| - next false if found_signature - - found_heading ||= - RDoc::Markup::Heading === part && part.text == 'Signature' - - next false unless found_heading - - next true if RDoc::Markup::BlankLine === part - - if RDoc::Markup::Verbatim === part then - signature = part - found_signature = true - end - end - - signature and signature.text - end - - # Public: Creates a new TomDoc parser. See also RDoc::Markup::parse - - def initialize - super - - @section = nil - @seen_returns = false - end - - # Internal: Builds a heading from the token stream - # - # level - The level of heading to create - # - # Returns an RDoc::Markup::Heading - - def build_heading level - heading = super - - @section = heading.text - - heading - end - - # Internal: Builds a verbatim from the token stream. A verbatim in the - # Examples section will be marked as in Ruby format. - # - # margin - The indentation from the margin for lines that belong to this - # verbatim section. - # - # Returns an RDoc::Markup::Verbatim - - def build_verbatim margin - verbatim = super - - verbatim.format = :ruby if @section == 'Examples' - - verbatim - end - - # Internal: Builds a paragraph from the token stream - # - # margin - Unused - # - # Returns an RDoc::Markup::Paragraph. - - def build_paragraph margin - p :paragraph_start => margin if @debug - - paragraph = RDoc::Markup::Paragraph.new - - until @tokens.empty? do - type, data, = get - - case type - when :TEXT then - @section = 'Returns' if data =~ /\A(Returns|Raises)/ - - paragraph << data - when :NEWLINE then - if :TEXT == peek_token[0] then - # Lines beginning with 'Raises' in the Returns section should not be - # treated as multiline text - if 'Returns' == @section and - peek_token[1].start_with?('Raises') then - break - else - paragraph << ' ' - end - else - break - end - else - unget - break - end - end - - p :paragraph_end => margin if @debug - - paragraph - end - - ## - # Detects a section change to "Returns" and adds a heading - - def parse_text parent, indent # :nodoc: - paragraph = build_paragraph indent - - if false == @seen_returns and 'Returns' == @section then - @seen_returns = true - parent << RDoc::Markup::Heading.new(3, 'Returns') - parent << RDoc::Markup::BlankLine.new - end - - parent << paragraph - end - - # Internal: Turns text into an Array of tokens - # - # text - A String containing TomDoc-format text. - # - # Returns self. - - def tokenize text - text = text.sub(/\A(Public|Internal|Deprecated):\s+/, '') - - setup_scanner text - - until @s.eos? do - pos = @s.pos - - # leading spaces will be reflected by the column of the next token - # the only thing we loose are trailing spaces at the end of the file - next if @s.scan(/ +/) - - @tokens << case - when @s.scan(/\r?\n/) then - token = [:NEWLINE, @s.matched, *pos] - @s.newline! - token - when @s.scan(/(Examples|Signature)$/) then - @tokens << [:HEADER, 3, *pos] - - [:TEXT, @s[1], *pos] - when @s.scan(/([:\w][\w\[\]]*)[ ]+- /) then - [:NOTE, @s[1], *pos] - else - @s.scan(/.*/) - [:TEXT, @s.matched.sub(/\r$/, ''), *pos] - end - end - - self - end - -end diff --git a/lib/rdoc/version.rb b/lib/rdoc/version.rb deleted file mode 100644 index 427d4ae232..0000000000 --- a/lib/rdoc/version.rb +++ /dev/null @@ -1,10 +0,0 @@ -# frozen_string_literal: true - -module RDoc - - ## - # RDoc version you are using - - VERSION = '6.7.0' - -end diff --git a/lib/readline.gemspec b/lib/readline.gemspec deleted file mode 100644 index 9221c29263..0000000000 --- a/lib/readline.gemspec +++ /dev/null @@ -1,33 +0,0 @@ -Gem::Specification.new do |spec| - spec.name = 'readline' - spec.version = '0.0.4' - spec.authors = ['aycabta'] - spec.email = ['aycabta@gmail.com'] - - spec.summary = %q{Loader for "readline".} - spec.description = <<~EOD - This is just a loader for "readline". If Ruby has the "readline-ext" gem - that is a native extension, this gem will load it. If Ruby does not have - the "readline-ext" gem this gem will load "reline", a library that is - compatible with the "readline-ext" gem and implemented in pure Ruby. - EOD - spec.homepage = 'https://github.com/ruby/readline' - spec.license = 'Ruby' - - spec.files = Dir['BSDL', 'COPYING', 'README.md', 'lib/readline.rb'] - spec.require_paths = ['lib'] - - spec.post_install_message = <<~EOM - +---------------------------------------------------------------------------+ - | This is just a loader for "readline". If Ruby has the "readline-ext" gem | - | that is a native extension, this gem will load it. If Ruby does not have | - | the "readline-ext" gem this gem will load "reline", a library that is | - | compatible with the "readline-ext" gem and implemented in pure Ruby. | - | | - | If you intend to use GNU Readline by `require 'readline'`, please install | - | the "readline-ext" gem. | - +---------------------------------------------------------------------------+ - EOM - - spec.add_runtime_dependency 'reline' -end diff --git a/lib/readline.rb b/lib/readline.rb deleted file mode 100644 index d1c9d3a955..0000000000 --- a/lib/readline.rb +++ /dev/null @@ -1,7 +0,0 @@ -begin - require "readline.#{RbConfig::CONFIG["DLEXT"]}" -rescue LoadError - require 'reline' unless defined? Reline - Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline) - Readline = Reline -end diff --git a/lib/reline.rb b/lib/reline.rb deleted file mode 100644 index ae522d4b38..0000000000 --- a/lib/reline.rb +++ /dev/null @@ -1,519 +0,0 @@ -require 'io/console' -require 'forwardable' -require 'reline/version' -require 'reline/config' -require 'reline/key_actor' -require 'reline/key_stroke' -require 'reline/line_editor' -require 'reline/history' -require 'reline/terminfo' -require 'reline/io' -require 'reline/face' -require 'rbconfig' - -module Reline - # NOTE: For making compatible with the rb-readline gem - FILENAME_COMPLETION_PROC = nil - USERNAME_COMPLETION_PROC = nil - - class ConfigEncodingConversionError < StandardError; end - - Key = Struct.new(:char, :combined_char, :with_meta) do - # For dialog_proc `key.match?(dialog.name)` - def match?(sym) - combined_char.is_a?(Symbol) && combined_char == sym - end - end - CursorPos = Struct.new(:x, :y) - DialogRenderInfo = Struct.new( - :pos, - :contents, - :face, - :bg_color, # For the time being, this line should stay here for the compatibility with IRB. - :width, - :height, - :scrollbar, - keyword_init: true - ) - - class Core - ATTR_READER_NAMES = %i( - completion_append_character - basic_word_break_characters - completer_word_break_characters - basic_quote_characters - completer_quote_characters - filename_quote_characters - special_prefixes - completion_proc - output_modifier_proc - prompt_proc - auto_indent_proc - pre_input_hook - dig_perfect_match_proc - ).each(&method(:attr_reader)) - - attr_accessor :config - attr_accessor :key_stroke - attr_accessor :line_editor - attr_accessor :last_incremental_search - attr_reader :output - - extend Forwardable - def_delegators :config, - :autocompletion, - :autocompletion= - - def initialize - self.output = STDOUT - @mutex = Mutex.new - @dialog_proc_list = {} - yield self - @completion_quote_character = nil - end - - def io_gate - Reline::IOGate - end - - def encoding - io_gate.encoding - end - - def completion_append_character=(val) - if val.nil? - @completion_append_character = nil - elsif val.size == 1 - @completion_append_character = val.encode(encoding) - elsif val.size > 1 - @completion_append_character = val[0].encode(encoding) - else - @completion_append_character = nil - end - end - - def basic_word_break_characters=(v) - @basic_word_break_characters = v.encode(encoding) - end - - def completer_word_break_characters=(v) - @completer_word_break_characters = v.encode(encoding) - end - - def basic_quote_characters=(v) - @basic_quote_characters = v.encode(encoding) - end - - def completer_quote_characters=(v) - @completer_quote_characters = v.encode(encoding) - end - - def filename_quote_characters=(v) - @filename_quote_characters = v.encode(encoding) - end - - def special_prefixes=(v) - @special_prefixes = v.encode(encoding) - end - - def completion_case_fold=(v) - @config.completion_ignore_case = v - end - - def completion_case_fold - @config.completion_ignore_case - end - - def completion_quote_character - @completion_quote_character - end - - def completion_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @completion_proc = p - end - - def output_modifier_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @output_modifier_proc = p - end - - def prompt_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @prompt_proc = p - end - - def auto_indent_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @auto_indent_proc = p - end - - def pre_input_hook=(p) - @pre_input_hook = p - end - - def dig_perfect_match_proc=(p) - raise ArgumentError unless p.respond_to?(:call) or p.nil? - @dig_perfect_match_proc = p - end - - DialogProc = Struct.new(:dialog_proc, :context) - def add_dialog_proc(name_sym, p, context = nil) - raise ArgumentError unless name_sym.instance_of?(Symbol) - if p.nil? - @dialog_proc_list.delete(name_sym) - else - raise ArgumentError unless p.respond_to?(:call) - @dialog_proc_list[name_sym] = DialogProc.new(p, context) - end - end - - def dialog_proc(name_sym) - @dialog_proc_list[name_sym] - end - - def input=(val) - raise TypeError unless val.respond_to?(:getc) or val.nil? - if val.respond_to?(:getc) && io_gate.respond_to?(:input=) - io_gate.input = val - end - end - - def output=(val) - raise TypeError unless val.respond_to?(:write) or val.nil? - @output = val - if io_gate.respond_to?(:output=) - io_gate.output = val - end - end - - def vi_editing_mode - config.editing_mode = :vi_insert - nil - end - - def emacs_editing_mode - config.editing_mode = :emacs - nil - end - - def vi_editing_mode? - config.editing_mode_is?(:vi_insert, :vi_command) - end - - def emacs_editing_mode? - config.editing_mode_is?(:emacs) - end - - def get_screen_size - io_gate.get_screen_size - end - - Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() { - # autocomplete - return unless config.autocompletion - - journey_data = completion_journey_data - return unless journey_data - - target = journey_data.list.first - completed = journey_data.list[journey_data.pointer] - result = journey_data.list.drop(1) - pointer = journey_data.pointer - 1 - return if completed.empty? || (result == [completed] && pointer < 0) - - target_width = Reline::Unicode.calculate_width(target) - completed_width = Reline::Unicode.calculate_width(completed) - if cursor_pos.x <= completed_width - target_width - # When target is rendered on the line above cursor position - x = screen_width - completed_width - y = -1 - else - x = [cursor_pos.x - completed_width, 0].max - y = 0 - end - cursor_pos_to_render = Reline::CursorPos.new(x, y) - if context and context.is_a?(Array) - context.clear - context.push(cursor_pos_to_render, result, pointer, dialog) - end - dialog.pointer = pointer - DialogRenderInfo.new( - pos: cursor_pos_to_render, - contents: result, - scrollbar: true, - height: [15, preferred_dialog_height].min, - face: :completion_dialog - ) - } - Reline::DEFAULT_DIALOG_CONTEXT = Array.new - - def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination) - @mutex.synchronize do - unless confirm_multiline_termination - raise ArgumentError.new('#readmultiline needs block to confirm multiline termination') - end - - io_gate.with_raw_input do - inner_readline(prompt, add_hist, true, &confirm_multiline_termination) - end - - whole_buffer = line_editor.whole_buffer.dup - whole_buffer.taint if RUBY_VERSION < '2.7' - if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0 - Reline::HISTORY << whole_buffer - end - - if line_editor.eof? - line_editor.reset_line - # Return nil if the input is aborted by C-d. - nil - else - whole_buffer - end - end - end - - def readline(prompt = '', add_hist = false) - @mutex.synchronize do - io_gate.with_raw_input do - inner_readline(prompt, add_hist, false) - end - - line = line_editor.line.dup - line.taint if RUBY_VERSION < '2.7' - if add_hist and line and line.chomp("\n").size > 0 - Reline::HISTORY << line.chomp("\n") - end - - line_editor.reset_line if line_editor.line.nil? - line - end - end - - private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination) - if ENV['RELINE_STDERR_TTY'] - if io_gate.win? - $stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a') - else - $stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w') - end - $stderr.sync = true - $stderr.puts "Reline is used by #{Process.pid}" - end - unless config.test_mode or config.loaded? - config.read - io_gate.set_default_key_bindings(config) - end - otio = io_gate.prep - - may_req_ambiguous_char_width - line_editor.reset(prompt) - if multiline - line_editor.multiline_on - if block_given? - line_editor.confirm_multiline_termination_proc = confirm_multiline_termination - end - else - line_editor.multiline_off - end - line_editor.output = output - line_editor.completion_proc = completion_proc - line_editor.completion_append_character = completion_append_character - line_editor.output_modifier_proc = output_modifier_proc - line_editor.prompt_proc = prompt_proc - line_editor.auto_indent_proc = auto_indent_proc - line_editor.dig_perfect_match_proc = dig_perfect_match_proc - - # Readline calls pre_input_hook just after printing the first prompt. - line_editor.print_nomultiline_prompt - pre_input_hook&.call - - unless Reline::IOGate.dumb? - @dialog_proc_list.each_pair do |name_sym, d| - line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context) - end - end - - line_editor.update_dialogs - line_editor.rerender - - begin - line_editor.set_signal_handlers - loop do - read_io(config.keyseq_timeout) { |inputs| - line_editor.set_pasting_state(io_gate.in_pasting?) - inputs.each do |key| - if key.char == :bracketed_paste_start - text = io_gate.read_bracketed_paste - line_editor.insert_multiline_text(text) - line_editor.scroll_into_view - else - line_editor.update(key) - end - end - } - if line_editor.finished? - line_editor.render_finished - break - else - line_editor.set_pasting_state(io_gate.in_pasting?) - line_editor.rerender - end - end - io_gate.move_cursor_column(0) - rescue Errno::EIO - # Maybe the I/O has been closed. - ensure - line_editor.finalize - io_gate.deprep(otio) - end - end - - # GNU Readline watis for "keyseq-timeout" milliseconds when the input is - # ambiguous whether it is matching or matched. - # If the next character does not arrive within the specified timeout, input - # is considered as matched. - # `ESC` is ambiguous because it can be a standalone ESC (matched) or part of - # `ESC char` or part of CSI sequence (matching). - private def read_io(keyseq_timeout, &block) - buffer = [] - status = KeyStroke::MATCHING - loop do - timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY - c = io_gate.getc(timeout) - if c.nil? || c == -1 - if status == KeyStroke::MATCHING_MATCHED - status = KeyStroke::MATCHED - elsif buffer.empty? - # io_gate is closed and reached EOF - block.call([Key.new(nil, nil, false)]) - return - else - status = KeyStroke::UNMATCHED - end - else - buffer << c - status = key_stroke.match_status(buffer) - end - - if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED - expanded, rest_bytes = key_stroke.expand(buffer) - rest_bytes.reverse_each { |c| io_gate.ungetc(c) } - block.call(expanded) - return - end - end - end - - def ambiguous_width - may_req_ambiguous_char_width unless defined? @ambiguous_width - @ambiguous_width - end - - private def may_req_ambiguous_char_width - @ambiguous_width = 1 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty? - return if defined? @ambiguous_width - io_gate.move_cursor_column(0) - begin - output.write "\u{25bd}" - rescue Encoding::UndefinedConversionError - # LANG=C - @ambiguous_width = 1 - else - @ambiguous_width = io_gate.cursor_pos.x == 2 ? 2 : 1 - end - io_gate.move_cursor_column(0) - io_gate.erase_after_cursor - end - end - - extend Forwardable - extend SingleForwardable - - #-------------------------------------------------------- - # Documented API - #-------------------------------------------------------- - - (Core::ATTR_READER_NAMES).each { |name| - def_single_delegators :core, :"#{name}", :"#{name}=" - } - def_single_delegators :core, :input=, :output= - def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode - def_single_delegators :core, :readline - def_single_delegators :core, :completion_case_fold, :completion_case_fold= - def_single_delegators :core, :completion_quote_character - def_instance_delegators self, :readline - private :readline - - - #-------------------------------------------------------- - # Undocumented API - #-------------------------------------------------------- - - # Testable in original - def_single_delegators :core, :get_screen_size - def_single_delegators :line_editor, :eof? - def_instance_delegators self, :eof? - def_single_delegators :line_editor, :delete_text - def_single_delegator :line_editor, :line, :line_buffer - def_single_delegator :line_editor, :byte_pointer, :point - def_single_delegator :line_editor, :byte_pointer=, :point= - - def self.insert_text(text) - line_editor.insert_multiline_text(text) - self - end - - # Untestable in original - def_single_delegator :line_editor, :rerender, :redisplay - def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode? - def_single_delegators :core, :ambiguous_width - def_single_delegators :core, :last_incremental_search - def_single_delegators :core, :last_incremental_search= - def_single_delegators :core, :add_dialog_proc - def_single_delegators :core, :dialog_proc - def_single_delegators :core, :autocompletion, :autocompletion= - - def_single_delegators :core, :readmultiline - def_instance_delegators self, :readmultiline - private :readmultiline - - def self.encoding_system_needs - self.core.encoding - end - - def self.core - @core ||= Core.new { |core| - core.config = Reline::Config.new - core.key_stroke = Reline::KeyStroke.new(core.config) - core.line_editor = Reline::LineEditor.new(core.config) - - core.basic_word_break_characters = " \t\n`><=;|&{(" - core.completer_word_break_characters = " \t\n`><=;|&{(" - core.basic_quote_characters = '"\'' - core.completer_quote_characters = '"\'' - core.filename_quote_characters = "" - core.special_prefixes = "" - core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT) - } - end - - def self.ungetc(c) - core.io_gate.ungetc(c) - end - - def self.line_editor - core.line_editor - end -end - - -Reline::IOGate = Reline::IO.decide_io_gate - -# Deprecated -Reline::GeneralIO = Reline::Dumb.new - -Reline::Face.load_initial_configs - -Reline::HISTORY = Reline::History.new(Reline.core.config) diff --git a/lib/reline/config.rb b/lib/reline/config.rb deleted file mode 100644 index e0fc37fc68..0000000000 --- a/lib/reline/config.rb +++ /dev/null @@ -1,373 +0,0 @@ -class Reline::Config - attr_reader :test_mode - - KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-\\(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-\\(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./ - - class InvalidInputrc < RuntimeError - attr_accessor :file, :lineno - end - - VARIABLE_NAMES = %w{ - completion-ignore-case - convert-meta - disable-completion - history-size - keyseq-timeout - show-all-if-ambiguous - show-mode-in-prompt - vi-cmd-mode-string - vi-ins-mode-string - emacs-mode-string - enable-bracketed-paste - isearch-terminators - } - VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" } - VARIABLE_NAME_SYMBOLS.each do |v| - attr_accessor v - end - - attr_accessor :autocompletion - - def initialize - reset_variables - end - - def reset - if editing_mode_is?(:vi_command) - @editing_mode_label = :vi_insert - end - @oneshot_key_bindings.clear - end - - def reset_variables - @additional_key_bindings = { # from inputrc - emacs: Reline::KeyActor::Base.new, - vi_insert: Reline::KeyActor::Base.new, - vi_command: Reline::KeyActor::Base.new - } - @oneshot_key_bindings = Reline::KeyActor::Base.new - @editing_mode_label = :emacs - @keymap_label = :emacs - @keymap_prefix = [] - @default_key_bindings = { - emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING), - vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING), - vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING) - } - @vi_cmd_mode_string = '(cmd)' - @vi_ins_mode_string = '(ins)' - @emacs_mode_string = '@' - # https://tiswww.case.edu/php/chet/readline/readline.html#IDX25 - @history_size = -1 # unlimited - @keyseq_timeout = 500 - @test_mode = false - @autocompletion = false - @convert_meta = seven_bit_encoding?(Reline::IOGate.encoding) - @loaded = false - @enable_bracketed_paste = true - @show_mode_in_prompt = false - @default_inputrc_path = nil - end - - def editing_mode - @default_key_bindings[@editing_mode_label] - end - - def editing_mode=(val) - @editing_mode_label = val - end - - def editing_mode_is?(*val) - val.any?(@editing_mode_label) - end - - def keymap - @default_key_bindings[@keymap_label] - end - - def loaded? - @loaded - end - - def inputrc_path - case ENV['INPUTRC'] - when nil, '' - else - return File.expand_path(ENV['INPUTRC']) - end - - # In the XDG Specification, if ~/.config/readline/inputrc exists, then - # ~/.inputrc should not be read, but for compatibility with GNU Readline, - # if ~/.inputrc exists, then it is given priority. - home_rc_path = File.expand_path('~/.inputrc') - return home_rc_path if File.exist?(home_rc_path) - - case path = ENV['XDG_CONFIG_HOME'] - when nil, '' - else - path = File.join(path, 'readline/inputrc') - return path if File.exist?(path) and path == File.expand_path(path) - end - - path = File.expand_path('~/.config/readline/inputrc') - return path if File.exist?(path) - - return home_rc_path - end - - private def default_inputrc_path - @default_inputrc_path ||= inputrc_path - end - - def read(file = nil) - @loaded = true - file ||= default_inputrc_path - begin - if file.respond_to?(:readlines) - lines = file.readlines - else - lines = File.readlines(file) - end - rescue Errno::ENOENT - return nil - end - - read_lines(lines, file) - self - rescue InvalidInputrc => e - warn e.message - nil - end - - def key_bindings - # The key bindings for each editing mode will be overwritten by the user-defined ones. - Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]]) - end - - def add_oneshot_key_binding(keystroke, target) - # IRB sets invalid keystroke [Reline::Key]. We should ignore it. - return unless keystroke.all? { |c| c.is_a?(Integer) } - - @oneshot_key_bindings.add(keystroke, target) - end - - def reset_oneshot_key_bindings - @oneshot_key_bindings.clear - end - - def add_default_key_binding_by_keymap(keymap, keystroke, target) - @default_key_bindings[keymap].add(keystroke, target) - end - - def add_default_key_binding(keystroke, target) - add_default_key_binding_by_keymap(@keymap_label, keystroke, target) - end - - def read_lines(lines, file = nil) - if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs - begin - lines = lines.map do |l| - l.encode(Reline.encoding_system_needs) - rescue Encoding::UndefinedConversionError - mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}." - raise Reline::ConfigEncodingConversionError.new(mes) - end - end - end - if_stack = [] - - lines.each_with_index do |line, no| - next if line.match(/\A\s*#/) - - no += 1 - - line = line.chomp.lstrip - if line.start_with?('$') - handle_directive(line[1..-1], file, no, if_stack) - next - end - - next if if_stack.any? { |_no, skip| skip } - - case line - when /^set +([^ ]+) +(.+)/i - # value ignores everything after a space, raw_value does not. - var, value, raw_value = $1.downcase, $2.partition(' ').first, $2 - bind_variable(var, value, raw_value) - when /^\s*(?:M|Meta)-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\M-#$1\"", $2) - when /^\s*(?:C|Control)-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\C-#$1\"", $2) - when /^\s*(?:(?:C|Control)-(?:M|Meta)|(?:M|Meta)-(?:C|Control))-([a-zA-Z_])\s*:\s*(.*)\s*$/o - bind_key("\"\\M-\\C-#$1\"", $2) - when /^\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o - bind_key($1, $2) - end - end - unless if_stack.empty? - raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if" - end - end - - def handle_directive(directive, file, no, if_stack) - directive, args = directive.split(' ') - case directive - when 'if' - condition = false - case args - when /^mode=(vi|emacs)$/i - mode = $1.downcase - # NOTE: mode=vi means vi-insert mode - mode = 'vi_insert' if mode == 'vi' - if @editing_mode_label == mode.to_sym - condition = true - end - when 'term' - when 'version' - else # application name - condition = true if args == 'Ruby' - condition = true if args == 'Reline' - end - if_stack << [no, !condition] - when 'else' - if if_stack.empty? - raise InvalidInputrc, "#{file}:#{no}: unmatched else" - end - if_stack.last[1] = !if_stack.last[1] - when 'endif' - if if_stack.empty? - raise InvalidInputrc, "#{file}:#{no}: unmatched endif" - end - if_stack.pop - when 'include' - read(File.expand_path(args)) - end - end - - def bind_variable(name, value, raw_value) - case name - when 'history-size' - begin - @history_size = Integer(value) - rescue ArgumentError - @history_size = 500 - end - when 'isearch-terminators' - @isearch_terminators = retrieve_string(raw_value) - when 'editing-mode' - case value - when 'emacs' - @editing_mode_label = :emacs - @keymap_label = :emacs - @keymap_prefix = [] - when 'vi' - @editing_mode_label = :vi_insert - @keymap_label = :vi_insert - @keymap_prefix = [] - end - when 'keymap' - case value - when 'emacs', 'emacs-standard' - @keymap_label = :emacs - @keymap_prefix = [] - when 'emacs-ctlx' - @keymap_label = :emacs - @keymap_prefix = [?\C-x.ord] - when 'emacs-meta' - @keymap_label = :emacs - @keymap_prefix = [?\e.ord] - when 'vi', 'vi-move', 'vi-command' - @keymap_label = :vi_command - @keymap_prefix = [] - when 'vi-insert' - @keymap_label = :vi_insert - @keymap_prefix = [] - end - when 'keyseq-timeout' - @keyseq_timeout = value.to_i - when 'show-mode-in-prompt' - case value - when 'off' - @show_mode_in_prompt = false - when 'on' - @show_mode_in_prompt = true - else - @show_mode_in_prompt = false - end - when 'vi-cmd-mode-string' - @vi_cmd_mode_string = retrieve_string(raw_value) - when 'vi-ins-mode-string' - @vi_ins_mode_string = retrieve_string(raw_value) - when 'emacs-mode-string' - @emacs_mode_string = retrieve_string(raw_value) - when *VARIABLE_NAMES then - variable_name = :"@#{name.tr(?-, ?_)}" - instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on') - end - end - - def retrieve_string(str) - str = $1 if str =~ /\A"(.*)"\z/ - parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join - end - - def bind_key(key, value) - keystroke, func = parse_key_binding(key, value) - @additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func) if keystroke - end - - def parse_key_binding(key, func_name) - if key =~ /\A"(.*)"\z/ - keyseq = parse_keyseq($1) - else - keyseq = nil - end - if func_name =~ /"(.*)"/ - func = parse_keyseq($1) - else - func = func_name.split.first.tr(?-, ?_).to_sym # It must be macro. - end - [keyseq, func] - end - - def key_notation_to_code(notation) - case notation - when /(?:\\(?:C|Control)-\\(?:M|Meta)|\\(?:M|Meta)-\\(?:C|Control))-([A-Za-z_])/ - [?\e.ord, $1.ord % 32] - when /\\(?:C|Control)-([A-Za-z_])/ - ($1.upcase.ord % 32) - when /\\(?:M|Meta)-([0-9A-Za-z_])/ - [?\e.ord, $1.ord] - when /\\(\d{1,3})/ then $1.to_i(8) # octal - when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal - when "\\e" then ?\e.ord - when "\\\\" then ?\\.ord - when "\\\"" then ?".ord - when "\\'" then ?'.ord - when "\\a" then ?\a.ord - when "\\b" then ?\b.ord - when "\\d" then ?\d.ord - when "\\f" then ?\f.ord - when "\\n" then ?\n.ord - when "\\r" then ?\r.ord - when "\\t" then ?\t.ord - when "\\v" then ?\v.ord - else notation.ord - end - end - - def parse_keyseq(str) - str.scan(KEYSEQ_PATTERN).flat_map do |notation| - key_notation_to_code(notation) - end - end - - def reload - reset_variables - read - end - - private def seven_bit_encoding?(encoding) - encoding == Encoding::US_ASCII - end -end diff --git a/lib/reline/face.rb b/lib/reline/face.rb deleted file mode 100644 index 5b4464a623..0000000000 --- a/lib/reline/face.rb +++ /dev/null @@ -1,199 +0,0 @@ -# frozen_string_literal: true - -class Reline::Face - SGR_PARAMETERS = { - foreground: { - black: 30, - red: 31, - green: 32, - yellow: 33, - blue: 34, - magenta: 35, - cyan: 36, - white: 37, - bright_black: 90, - gray: 90, - bright_red: 91, - bright_green: 92, - bright_yellow: 93, - bright_blue: 94, - bright_magenta: 95, - bright_cyan: 96, - bright_white: 97 - }, - background: { - black: 40, - red: 41, - green: 42, - yellow: 43, - blue: 44, - magenta: 45, - cyan: 46, - white: 47, - bright_black: 100, - gray: 100, - bright_red: 101, - bright_green: 102, - bright_yellow: 103, - bright_blue: 104, - bright_magenta: 105, - bright_cyan: 106, - bright_white: 107, - }, - style: { - reset: 0, - bold: 1, - faint: 2, - italicized: 3, - underlined: 4, - slowly_blinking: 5, - blinking: 5, - rapidly_blinking: 6, - negative: 7, - concealed: 8, - crossed_out: 9 - } - }.freeze - - class Config - ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze - RESET_SGR = "\e[0m".freeze - - def initialize(name, &block) - @definition = {} - block.call(self) - ESSENTIAL_DEFINE_NAMES.each do |name| - @definition[name] ||= { style: :reset, escape_sequence: RESET_SGR } - end - end - - attr_reader :definition - - def define(name, **values) - values[:escape_sequence] = format_to_sgr(values.to_a).freeze - @definition[name] = values - end - - def reconfigure - @definition.each_value do |values| - values.delete(:escape_sequence) - values[:escape_sequence] = format_to_sgr(values.to_a).freeze - end - end - - def [](name) - @definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}" - end - - private - - def sgr_rgb(key, value) - return nil unless rgb_expression?(value) - if Reline::Face.truecolor? - sgr_rgb_truecolor(key, value) - else - sgr_rgb_256color(key, value) - end - end - - def sgr_rgb_truecolor(key, value) - case key - when :foreground - "38;2;" - when :background - "48;2;" - end + value[1, 6].scan(/../).map(&:hex).join(";") - end - - def sgr_rgb_256color(key, value) - # 256 colors are - # 0..15: standard colors, high intensity colors - # 16..232: 216 colors (R, G, B each 6 steps) - # 233..255: grayscale colors (24 steps) - # This methods converts rgb_expression to 216 colors - rgb = value[1, 6].scan(/../).map(&:hex) - # Color steps are [0, 95, 135, 175, 215, 255] - r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 } - color = (16 + 36 * r + 6 * g + b) - case key - when :foreground - "38;5;#{color}" - when :background - "48;5;#{color}" - end - end - - def format_to_sgr(ordered_values) - sgr = "\e[" + ordered_values.map do |key_value| - key, value = key_value - case key - when :foreground, :background - case value - when Symbol - SGR_PARAMETERS[key][value] - when String - sgr_rgb(key, value) - end - when :style - [ value ].flatten.map do |style_name| - SGR_PARAMETERS[:style][style_name] - end.then do |sgr_parameters| - sgr_parameters.include?(nil) ? nil : sgr_parameters - end - end.then do |rendition_expression| - unless rendition_expression - raise ArgumentError, "invalid SGR parameter: #{value.inspect}" - end - rendition_expression - end - end.join(';') + "m" - sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr - end - - def rgb_expression?(color) - color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/) - end - end - - private_constant :SGR_PARAMETERS, :Config - - def self.truecolor? - @force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM']) - end - - def self.force_truecolor - @force_truecolor = true - @configs&.each_value(&:reconfigure) - end - - def self.[](name) - @configs[name] - end - - def self.config(name, &block) - @configs ||= {} - @configs[name] = Config.new(name, &block) - end - - def self.configs - @configs.transform_values(&:definition) - end - - def self.load_initial_configs - config(:default) do |conf| - conf.define :default, style: :reset - conf.define :enhanced, style: :reset - conf.define :scrollbar, style: :reset - end - config(:completion_dialog) do |conf| - conf.define :default, foreground: :bright_white, background: :gray - conf.define :enhanced, foreground: :black, background: :white - conf.define :scrollbar, foreground: :white, background: :gray - end - end - - def self.reset_to_initial_configs - @configs = {} - load_initial_configs - end -end diff --git a/lib/reline/history.rb b/lib/reline/history.rb deleted file mode 100644 index 47c68ba774..0000000000 --- a/lib/reline/history.rb +++ /dev/null @@ -1,76 +0,0 @@ -class Reline::History < Array - def initialize(config) - @config = config - end - - def to_s - 'HISTORY' - end - - def delete_at(index) - index = check_index(index) - super(index) - end - - def [](index) - index = check_index(index) unless index.is_a?(Range) - super(index) - end - - def []=(index, val) - index = check_index(index) - super(index, Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) - end - - def concat(*val) - val.each do |v| - push(*v) - end - end - - def push(*val) - # If history_size is zero, all histories are dropped. - return self if @config.history_size.zero? - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - diff = size + val.size - @config.history_size - if diff > 0 - if diff <= size - shift(diff) - else - diff -= size - clear - val.shift(diff) - end - end - end - super(*(val.map{ |v| - Reline::Unicode.safe_encode(v, Reline.encoding_system_needs) - })) - end - - def <<(val) - # If history_size is zero, all histories are dropped. - return self if @config.history_size.zero? - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - shift if size + 1 > @config.history_size - end - super(Reline::Unicode.safe_encode(val, Reline.encoding_system_needs)) - end - - private def check_index(index) - index += size if index < 0 - if index < -2147483648 or 2147483647 < index - raise RangeError.new("integer #{index} too big to convert to 'int'") - end - # If history_size is negative, history size is unlimited. - if @config.history_size.positive? - if index < -@config.history_size or @config.history_size < index - raise RangeError.new("index=<#{index}>") - end - end - raise IndexError.new("index=<#{index}>") if index < 0 or size <= index - index - end -end diff --git a/lib/reline/io.rb b/lib/reline/io.rb deleted file mode 100644 index c1dd1a56c8..0000000000 --- a/lib/reline/io.rb +++ /dev/null @@ -1,41 +0,0 @@ - -module Reline - class IO - RESET_COLOR = "\e[0m" - - def self.decide_io_gate - if ENV['TERM'] == 'dumb' - Reline::Dumb.new - else - require 'reline/io/ansi' - - case RbConfig::CONFIG['host_os'] - when /mswin|msys|mingw|cygwin|bccwin|wince|emc/ - require 'reline/io/windows' - io = Reline::Windows.new - if io.msys_tty? - Reline::ANSI.new - else - io - end - else - Reline::ANSI.new - end - end - end - - def dumb? - false - end - - def win? - false - end - - def reset_color_sequence - self.class::RESET_COLOR - end - end -end - -require 'reline/io/dumb' diff --git a/lib/reline/io/ansi.rb b/lib/reline/io/ansi.rb deleted file mode 100644 index a14ae18e2a..0000000000 --- a/lib/reline/io/ansi.rb +++ /dev/null @@ -1,348 +0,0 @@ -require 'io/console' -require 'io/wait' - -class Reline::ANSI < Reline::IO - CAPNAME_KEY_BINDINGS = { - 'khome' => :ed_move_to_beg, - 'kend' => :ed_move_to_end, - 'kdch1' => :key_delete, - 'kpp' => :ed_search_prev_history, - 'knp' => :ed_search_next_history, - 'kcuu1' => :ed_prev_history, - 'kcud1' => :ed_next_history, - 'kcuf1' => :ed_next_char, - 'kcub1' => :ed_prev_char, - } - - ANSI_CURSOR_KEY_BINDINGS = { - # Up - 'A' => [:ed_prev_history, {}], - # Down - 'B' => [:ed_next_history, {}], - # Right - 'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }], - # Left - 'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }], - # End - 'F' => [:ed_move_to_end, {}], - # Home - 'H' => [:ed_move_to_beg, {}], - } - - if Reline::Terminfo.enabled? - Reline::Terminfo.setupterm(0, 2) - end - - def initialize - @input = STDIN - @output = STDOUT - @buf = [] - @old_winch_handler = nil - end - - def encoding - @input.external_encoding || Encoding.default_external - end - - def set_default_key_bindings(config, allow_terminfo: true) - set_bracketed_paste_key_bindings(config) - set_default_key_bindings_ansi_cursor(config) - if allow_terminfo && Reline::Terminfo.enabled? - set_default_key_bindings_terminfo(config) - else - set_default_key_bindings_comprehensive_list(config) - end - { - [27, 91, 90] => :completion_journey_up, # S-Tab - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - end - { - # default bindings - [27, 32] => :em_set_mark, # M-<space> - [24, 24] => :em_exchange_mark, # C-x C-x - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - end - end - - def set_bracketed_paste_key_bindings(config) - [:emacs, :vi_insert, :vi_command].each do |keymap| - config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start) - end - end - - def set_default_key_bindings_ansi_cursor(config) - ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)| - bindings = [ - ["\e[#{char}", default_func], # CSI + char - ["\eO#{char}", default_func] # SS3 + char, application cursor key mode - ] - if modifiers[:ctrl] - # CSI + ctrl_key_modifier + char - bindings << ["\e[1;5#{char}", modifiers[:ctrl]] - end - if modifiers[:meta] - # CSI + meta_key_modifier + char - bindings << ["\e[1;3#{char}", modifiers[:meta]] - # Meta(ESC) + CSI + char - bindings << ["\e\e[#{char}", modifiers[:meta]] - end - bindings.each do |sequence, func| - key = sequence.bytes - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - end - end - - def set_default_key_bindings_terminfo(config) - key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding| - begin - key_code = Reline::Terminfo.tigetstr(capname) - [ key_code.bytes, key_binding ] - rescue Reline::Terminfo::TerminfoError - # capname is undefined - end - end.compact.to_h - - key_bindings.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - end - - def set_default_key_bindings_comprehensive_list(config) - { - # xterm - [27, 91, 51, 126] => :key_delete, # kdch1 - [27, 91, 53, 126] => :ed_search_prev_history, # kpp - [27, 91, 54, 126] => :ed_search_next_history, # knp - - # Console (80x25) - [27, 91, 49, 126] => :ed_move_to_beg, # Home - [27, 91, 52, 126] => :ed_move_to_end, # End - - # urxvt / exoterm - [27, 91, 55, 126] => :ed_move_to_beg, # Home - [27, 91, 56, 126] => :ed_move_to_end, # End - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - end - - def input=(val) - @input = val - end - - def output=(val) - @output = val - end - - def with_raw_input - if @input.tty? - @input.raw(intr: true) { yield } - else - yield - end - end - - def inner_getc(timeout_second) - unless @buf.empty? - return @buf.shift - end - until @input.wait_readable(0.01) - timeout_second -= 0.01 - return nil if timeout_second <= 0 - - Reline.core.line_editor.handle_signal - end - c = @input.getbyte - (c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c - rescue Errno::EIO - # Maybe the I/O has been closed. - nil - end - - START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT) - END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT) - def read_bracketed_paste - buffer = String.new(encoding: Encoding::ASCII_8BIT) - until buffer.end_with?(END_BRACKETED_PASTE) - c = inner_getc(Float::INFINITY) - break unless c - buffer << c - end - string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding) - string.valid_encoding? ? string : '' - end - - # if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second - def getc(timeout_second) - inner_getc(timeout_second) - end - - def in_pasting? - not empty_buffer? - end - - def empty_buffer? - unless @buf.empty? - return false - end - !@input.wait_readable(0) - end - - def ungetc(c) - @buf.unshift(c) - end - - def retrieve_keybuffer - begin - return unless @input.wait_readable(0.001) - str = @input.read_nonblock(1024) - str.bytes.each do |c| - @buf.push(c) - end - rescue EOFError - end - end - - def get_screen_size - s = @input.winsize - return s if s[0] > 0 && s[1] > 0 - s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i] - return s if s[0] > 0 && s[1] > 0 - [24, 80] - rescue Errno::ENOTTY, Errno::ENODEV - [24, 80] - end - - def set_screen_size(rows, columns) - @input.winsize = [rows, columns] - self - rescue Errno::ENOTTY, Errno::ENODEV - self - end - - private def cursor_pos_internal(timeout:) - match = nil - @input.raw do |stdin| - @output << "\e[6n" - @output.flush - timeout_at = Time.now + timeout - buf = +'' - while (wait = timeout_at - Time.now) > 0 && stdin.wait_readable(wait) - buf << stdin.readpartial(1024) - if (match = buf.match(/\e\[(?<row>\d+);(?<column>\d+)R/)) - buf = match.pre_match + match.post_match - break - end - end - buf.chars.reverse_each do |ch| - stdin.ungetc ch - end - end - [match[:column].to_i - 1, match[:row].to_i - 1] if match - end - - def cursor_pos - col, row = cursor_pos_internal(timeout: 0.5) if both_tty? - Reline::CursorPos.new(col || 0, row || 0) - end - - def both_tty? - @input.tty? && @output.tty? - end - - def move_cursor_column(x) - @output.write "\e[#{x + 1}G" - end - - def move_cursor_up(x) - if x > 0 - @output.write "\e[#{x}A" - elsif x < 0 - move_cursor_down(-x) - end - end - - def move_cursor_down(x) - if x > 0 - @output.write "\e[#{x}B" - elsif x < 0 - move_cursor_up(-x) - end - end - - def hide_cursor - seq = "\e[?25l" - if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? - begin - seq = Reline::Terminfo.tigetstr('civis') - rescue Reline::Terminfo::TerminfoError - # civis is undefined - end - end - @output.write seq - end - - def show_cursor - seq = "\e[?25h" - if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported? - begin - seq = Reline::Terminfo.tigetstr('cnorm') - rescue Reline::Terminfo::TerminfoError - # cnorm is undefined - end - end - @output.write seq - end - - def erase_after_cursor - @output.write "\e[K" - end - - # This only works when the cursor is at the bottom of the scroll range - # For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623 - def scroll_down(x) - return if x.zero? - # We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576 - @output.write "\n" * x - end - - def clear_screen - @output.write "\e[2J" - @output.write "\e[1;1H" - end - - def set_winch_handler(&handler) - @old_winch_handler = Signal.trap('WINCH', &handler) - @old_cont_handler = Signal.trap('CONT') do - @input.raw!(intr: true) if @input.tty? - # Rerender the screen. Note that screen size might be changed while suspended. - handler.call - end - rescue ArgumentError - # Signal.trap may raise an ArgumentError if the platform doesn't support the signal. - end - - def prep - # Enable bracketed paste - @output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty? - retrieve_keybuffer - nil - end - - def deprep(otio) - # Disable bracketed paste - @output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty? - Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler - Signal.trap('CONT', @old_cont_handler) if @old_cont_handler - end -end diff --git a/lib/reline/io/dumb.rb b/lib/reline/io/dumb.rb deleted file mode 100644 index 34bba98ba1..0000000000 --- a/lib/reline/io/dumb.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'io/wait' - -class Reline::Dumb < Reline::IO - RESET_COLOR = '' # Do not send color reset sequence - - def initialize(encoding: nil) - @input = STDIN - @buf = [] - @pasting = false - @encoding = encoding - @screen_size = [24, 80] - end - - def dumb? - true - end - - def encoding - if @encoding - @encoding - elsif RUBY_PLATFORM =~ /mswin|mingw/ - Encoding::UTF_8 - else - @input.external_encoding || Encoding::default_external - end - end - - def set_default_key_bindings(_) - end - - def input=(val) - @input = val - end - - def with_raw_input - yield - end - - def getc(_timeout_second) - unless @buf.empty? - return @buf.shift - end - c = nil - loop do - Reline.core.line_editor.handle_signal - result = @input.wait_readable(0.1) - next if result.nil? - c = @input.read(1) - break - end - c&.ord - end - - def ungetc(c) - @buf.unshift(c) - end - - def get_screen_size - @screen_size - end - - def cursor_pos - Reline::CursorPos.new(0, 0) - end - - def hide_cursor - end - - def show_cursor - end - - def move_cursor_column(val) - end - - def move_cursor_up(val) - end - - def move_cursor_down(val) - end - - def erase_after_cursor - end - - def scroll_down(val) - end - - def clear_screen - end - - def set_screen_size(rows, columns) - @screen_size = [rows, columns] - end - - def set_winch_handler(&handler) - end - - def in_pasting? - @pasting - end - - def prep - end - - def deprep(otio) - end -end diff --git a/lib/reline/io/windows.rb b/lib/reline/io/windows.rb deleted file mode 100644 index f718743193..0000000000 --- a/lib/reline/io/windows.rb +++ /dev/null @@ -1,513 +0,0 @@ -require 'fiddle/import' - -class Reline::Windows < Reline::IO - def initialize - @input_buf = [] - @output_buf = [] - - @output = STDOUT - @hsg = nil - @getwch = Win32API.new('msvcrt', '_getwch', [], 'I') - @kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I') - @GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L') - @GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L') - @SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L') - @GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L') - @FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L') - @ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L') - @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) - @hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE) - @GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L') - @ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L') - @GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L') - @GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I') - @FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L') - @SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L') - - @GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L') - @SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L') - @WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L') - - @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 - end - - def encoding - Encoding::UTF_8 - end - - def win? - true - end - - def win_legacy_console? - @legacy_console - end - - def set_default_key_bindings(config) - { - [224, 72] => :ed_prev_history, # ↑ - [224, 80] => :ed_next_history, # ↓ - [224, 77] => :ed_next_char, # → - [224, 75] => :ed_prev_char, # ← - [224, 83] => :key_delete, # Del - [224, 71] => :ed_move_to_beg, # Home - [224, 79] => :ed_move_to_end, # End - [ 0, 41] => :ed_unassigned, # input method on/off - [ 0, 72] => :ed_prev_history, # ↑ - [ 0, 80] => :ed_next_history, # ↓ - [ 0, 77] => :ed_next_char, # → - [ 0, 75] => :ed_prev_char, # ← - [ 0, 83] => :key_delete, # Del - [ 0, 71] => :ed_move_to_beg, # Home - [ 0, 79] => :ed_move_to_end # End - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - config.add_default_key_binding_by_keymap(:vi_command, key, func) - end - - { - [27, 32] => :em_set_mark, # M-<space> - [24, 24] => :em_exchange_mark, # C-x C-x - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - end - - # Emulate ANSI key sequence. - { - [27, 91, 90] => :completion_journey_up, # S-Tab - }.each_pair do |key, func| - config.add_default_key_binding_by_keymap(:emacs, key, func) - config.add_default_key_binding_by_keymap(:vi_insert, key, func) - end - end - - if defined? JRUBY_VERSION - require 'win32api' - else - class Win32API - DLL = {} - TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG} - POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*' - - WIN32_TYPES = "VPpNnLlIi" - DL_TYPES = "0SSI" - - def initialize(dllname, func, import, export = "0", calltype = :stdcall) - @proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1') - import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]} - export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)] - calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype] - - handle = DLL[dllname] ||= - begin - Fiddle.dlopen(dllname) - rescue Fiddle::DLError - raise unless File.extname(dllname).empty? - Fiddle.dlopen(dllname + ".dll") - end - - @func = Fiddle::Function.new(handle[func], import, export, calltype) - rescue Fiddle::DLError => e - raise LoadError, e.message, e.backtrace - end - - def call(*args) - import = @proto.split("") - args.each_with_index do |x, i| - args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S" - args[i], = [x].pack("I").unpack("i") if import[i] == "I" - end - ret, = @func.call(*args) - return ret || 0 - end - end - end - - VK_RETURN = 0x0D - VK_MENU = 0x12 # ALT key - VK_LMENU = 0xA4 - VK_CONTROL = 0x11 - VK_SHIFT = 0x10 - VK_DIVIDE = 0x6F - - KEY_EVENT = 0x01 - WINDOW_BUFFER_SIZE_EVENT = 0x04 - - CAPSLOCK_ON = 0x0080 - ENHANCED_KEY = 0x0100 - LEFT_ALT_PRESSED = 0x0002 - LEFT_CTRL_PRESSED = 0x0008 - NUMLOCK_ON = 0x0020 - RIGHT_ALT_PRESSED = 0x0001 - RIGHT_CTRL_PRESSED = 0x0004 - SCROLLLOCK_ON = 0x0040 - SHIFT_PRESSED = 0x0010 - - VK_TAB = 0x09 - VK_END = 0x23 - VK_HOME = 0x24 - VK_LEFT = 0x25 - VK_UP = 0x26 - VK_RIGHT = 0x27 - VK_DOWN = 0x28 - VK_DELETE = 0x2E - - STD_INPUT_HANDLE = -10 - STD_OUTPUT_HANDLE = -11 - FILE_TYPE_PIPE = 0x0003 - FILE_NAME_INFO = 2 - ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4 - - # Calling Win32API with console handle is reported to fail after executing some external command. - # We need to refresh console handle and retry the call again. - private def call_with_console_handle(win32func, *args) - val = win32func.call(@hConsoleHandle, *args) - return val if val != 0 - - @hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE) - win32func.call(@hConsoleHandle, *args) - end - - private def getconsolemode - mode = "\000\000\000\000" - call_with_console_handle(@GetConsoleMode, mode) - mode.unpack1('L') - end - - private def setconsolemode(mode) - call_with_console_handle(@SetConsoleMode, mode) - end - - #if @legacy_console - # setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING) - # @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0) - #end - - def msys_tty?(io = @hConsoleInputHandle) - # check if fd is a pipe - if @GetFileType.call(io) != FILE_TYPE_PIPE - return false - end - - bufsize = 1024 - p_buffer = "\0" * bufsize - res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2) - return false if res == 0 - - # get pipe name: p_buffer layout is: - # struct _FILE_NAME_INFO { - # DWORD FileNameLength; - # WCHAR FileName[1]; - # } FILE_NAME_INFO - len = p_buffer[0, 4].unpack1("L") - name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace) - - # Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX') - # or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX') - name =~ /(msys-|cygwin-).*-pty/ ? true : false - end - - KEY_MAP = [ - # It's treated as Meta+Enter on Windows. - [ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ], - [ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ], - - # It's treated as Meta+Space on Windows. - [ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ], - - # Emulate getwch() key sequences. - [ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ], - [ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ], - [ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ], - [ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ], - [ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ], - [ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ], - [ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ], - - # Emulate ANSI key sequence. - [ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ], - ] - - def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) - - # high-surrogate - if 0xD800 <= char_code and char_code <= 0xDBFF - @hsg = char_code - return - end - # low-surrogate - if 0xDC00 <= char_code and char_code <= 0xDFFF - if @hsg - char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00 - @hsg = nil - else - # no high-surrogate. ignored. - return - end - else - # ignore high-surrogate without low-surrogate if there - @hsg = nil - end - - key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state) - - match = KEY_MAP.find { |args,| key.match?(**args) } - unless match.nil? - @output_buf.concat(match.last) - return - end - - # no char, only control keys - return if key.char_code == 0 and key.control_keys.any? - - @output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL) - - @output_buf.concat(key.char.bytes) - end - - def check_input_event - num_of_events = 0.chr * 8 - while @output_buf.empty? - Reline.core.line_editor.handle_signal - if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec - # prevent for background consolemode change - @legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 - next - end - next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0 - input_records = 0.chr * 20 * 80 - read_event = 0.chr * 4 - if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0 - read_events = read_event.unpack1('L') - 0.upto(read_events) do |idx| - input_record = input_records[idx * 20, 20] - event = input_record[0, 2].unpack1('s*') - case event - when WINDOW_BUFFER_SIZE_EVENT - @winch_handler.() - when KEY_EVENT - key_down = input_record[4, 4].unpack1('l*') - repeat_count = input_record[8, 2].unpack1('s*') - virtual_key_code = input_record[10, 2].unpack1('s*') - virtual_scan_code = input_record[12, 2].unpack1('s*') - char_code = input_record[14, 2].unpack1('S*') - control_key_state = input_record[16, 2].unpack1('S*') - is_key_down = key_down.zero? ? false : true - if is_key_down - process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state) - end - end - end - end - end - end - - def with_raw_input - yield - end - - def getc(_timeout_second) - check_input_event - @output_buf.shift - end - - def ungetc(c) - @output_buf.unshift(c) - end - - def in_pasting? - not empty_buffer? - end - - def empty_buffer? - if not @output_buf.empty? - false - elsif @kbhit.call == 0 - true - else - false - end - end - - def get_console_screen_buffer_info - # CONSOLE_SCREEN_BUFFER_INFO - # [ 0,2] dwSize.X - # [ 2,2] dwSize.Y - # [ 4,2] dwCursorPositions.X - # [ 6,2] dwCursorPositions.Y - # [ 8,2] wAttributes - # [10,2] srWindow.Left - # [12,2] srWindow.Top - # [14,2] srWindow.Right - # [16,2] srWindow.Bottom - # [18,2] dwMaximumWindowSize.X - # [20,2] dwMaximumWindowSize.Y - csbi = 0.chr * 22 - return if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) == 0 - csbi - end - - def get_screen_size - unless csbi = get_console_screen_buffer_info - return [1, 1] - end - csbi[0, 4].unpack('SS').reverse - end - - def cursor_pos - unless csbi = get_console_screen_buffer_info - return Reline::CursorPos.new(0, 0) - end - x = csbi[4, 2].unpack1('s') - y = csbi[6, 2].unpack1('s') - Reline::CursorPos.new(x, y) - end - - def move_cursor_column(val) - call_with_console_handle(@SetConsoleCursorPosition, cursor_pos.y * 65536 + val) - end - - def move_cursor_up(val) - if val > 0 - y = cursor_pos.y - val - y = 0 if y < 0 - call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + cursor_pos.x) - elsif val < 0 - move_cursor_down(-val) - end - end - - def move_cursor_down(val) - if val > 0 - return unless csbi = get_console_screen_buffer_info - screen_height = get_screen_size.first - y = cursor_pos.y + val - y = screen_height - 1 if y > (screen_height - 1) - call_with_console_handle(@SetConsoleCursorPosition, (cursor_pos.y + val) * 65536 + cursor_pos.x) - elsif val < 0 - move_cursor_up(-val) - end - end - - def erase_after_cursor - return unless csbi = get_console_screen_buffer_info - attributes = csbi[8, 2].unpack1('S') - cursor = csbi[4, 4].unpack1('L') - written = 0.chr * 4 - call_with_console_handle(@FillConsoleOutputCharacter, 0x20, get_screen_size.last - cursor_pos.x, cursor, written) - call_with_console_handle(@FillConsoleOutputAttribute, attributes, get_screen_size.last - cursor_pos.x, cursor, written) - end - - def scroll_down(val) - return if val < 0 - return unless csbi = get_console_screen_buffer_info - buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s') - screen_height = window_bottom - window_top + 1 - val = screen_height if val > screen_height - - if @legacy_console || window_left != 0 - # unless ENABLE_VIRTUAL_TERMINAL, - # if srWindow.Left != 0 then it's conhost.exe hosted console - # and puts "\n" causes horizontal scroll. its glitch. - # FYI irb write from culumn 1, so this gives no gain. - scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4') - destination_origin = 0 # y * 65536 + x - fill = [' '.ord, attributes].pack('SS') - call_with_console_handle(@ScrollConsoleScreenBuffer, scroll_rectangle, nil, destination_origin, fill) - else - origin_x = x + 1 - origin_y = y - window_top + 1 - @output.write [ - (origin_y != screen_height) ? "\e[#{screen_height};H" : nil, - "\n" * val, - (origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil - ].join - end - end - - def clear_screen - if @legacy_console - return unless csbi = get_console_screen_buffer_info - buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s') - fill_length = buffer_width * (window_bottom - window_top + 1) - screen_topleft = window_top * 65536 - written = 0.chr * 4 - call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written) - call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written) - call_with_console_handle(@SetConsoleCursorPosition, screen_topleft) - else - @output.write "\e[2J" "\e[H" - end - end - - def set_screen_size(rows, columns) - raise NotImplementedError - end - - def hide_cursor - size = 100 - visible = 0 # 0 means false - cursor_info = [size, visible].pack('Li') - call_with_console_handle(@SetConsoleCursorInfo, cursor_info) - end - - def show_cursor - size = 100 - visible = 1 # 1 means true - cursor_info = [size, visible].pack('Li') - call_with_console_handle(@SetConsoleCursorInfo, cursor_info) - end - - def set_winch_handler(&handler) - @winch_handler = handler - end - - def prep - # do nothing - nil - end - - def deprep(otio) - # do nothing - end - - class KeyEventRecord - - attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys - - def initialize(virtual_key_code, char_code, control_key_state) - @virtual_key_code = virtual_key_code - @char_code = char_code - @control_key_state = control_key_state - @enhanced = control_key_state & ENHANCED_KEY != 0 - - (@control_keys = []).tap do |control_keys| - # symbols must be sorted to make comparison is easier later on - control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0 - control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0 - control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0 - end.freeze - end - - def char - @char_code.chr(Encoding::UTF_8) - end - - def enhanced? - @enhanced - end - - # Verifies if the arguments match with this key event. - # Nil arguments are ignored, but at least one must be passed as non-nil. - # To verify that no control keys were pressed, pass an empty array: `control_keys: []`. - def match?(control_keys: nil, virtual_key_code: nil, char_code: nil) - raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil? - - (control_keys.nil? || [*control_keys].sort == @control_keys) && - (virtual_key_code.nil? || @virtual_key_code == virtual_key_code) && - (char_code.nil? || char_code == @char_code) - end - - end -end diff --git a/lib/reline/key_actor.rb b/lib/reline/key_actor.rb deleted file mode 100644 index 0ac7604556..0000000000 --- a/lib/reline/key_actor.rb +++ /dev/null @@ -1,8 +0,0 @@ -module Reline::KeyActor -end - -require 'reline/key_actor/base' -require 'reline/key_actor/composite' -require 'reline/key_actor/emacs' -require 'reline/key_actor/vi_command' -require 'reline/key_actor/vi_insert' diff --git a/lib/reline/key_actor/base.rb b/lib/reline/key_actor/base.rb deleted file mode 100644 index ee28c7681e..0000000000 --- a/lib/reline/key_actor/base.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Reline::KeyActor::Base - def initialize(mapping = []) - @mapping = mapping - @matching_bytes = {} - @key_bindings = {} - end - - def get_method(key) - @mapping[key] - end - - def add(key, func) - (1...key.size).each do |size| - @matching_bytes[key.take(size)] = true - end - @key_bindings[key] = func - end - - def matching?(key) - @matching_bytes[key] - end - - def get(key) - @key_bindings[key] - end - - def clear - @matching_bytes.clear - @key_bindings.clear - end -end diff --git a/lib/reline/key_actor/composite.rb b/lib/reline/key_actor/composite.rb deleted file mode 100644 index 37e94ce6cf..0000000000 --- a/lib/reline/key_actor/composite.rb +++ /dev/null @@ -1,17 +0,0 @@ -class Reline::KeyActor::Composite - def initialize(key_actors) - @key_actors = key_actors - end - - def matching?(key) - @key_actors.any? { |key_actor| key_actor.matching?(key) } - end - - def get(key) - @key_actors.each do |key_actor| - func = key_actor.get(key) - return func if func - end - nil - end -end diff --git a/lib/reline/key_actor/emacs.rb b/lib/reline/key_actor/emacs.rb deleted file mode 100644 index ad84ee1d99..0000000000 --- a/lib/reline/key_actor/emacs.rb +++ /dev/null @@ -1,517 +0,0 @@ -module Reline::KeyActor - EMACS_MAPPING = [ - # 0 ^@ - :em_set_mark, - # 1 ^A - :ed_move_to_beg, - # 2 ^B - :ed_prev_char, - # 3 ^C - :ed_ignore, - # 4 ^D - :em_delete, - # 5 ^E - :ed_move_to_end, - # 6 ^F - :ed_next_char, - # 7 ^G - :ed_unassigned, - # 8 ^H - :em_delete_prev_char, - # 9 ^I - :complete, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_kill_line, - # 12 ^L - :ed_clear_screen, - # 13 ^M - :ed_newline, - # 14 ^N - :ed_next_history, - # 15 ^O - :ed_ignore, - # 16 ^P - :ed_prev_history, - # 17 ^Q - :ed_quoted_insert, - # 18 ^R - :vi_search_prev, - # 19 ^S - :vi_search_next, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :unix_line_discard, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :em_kill_region, - # 24 ^X - :ed_unassigned, - # 25 ^Y - :em_yank, - # 26 ^Z - :ed_ignore, - # 27 ^[ - :ed_unassigned, - # 28 ^\ - :ed_ignore, - # 29 ^] - :ed_ignore, - # 30 ^^ - :ed_unassigned, - # 31 ^_ - :undo, - # 32 SPACE - :ed_insert, - # 33 ! - :ed_insert, - # 34 " - :ed_insert, - # 35 # - :ed_insert, - # 36 $ - :ed_insert, - # 37 % - :ed_insert, - # 38 & - :ed_insert, - # 39 ' - :ed_insert, - # 40 ( - :ed_insert, - # 41 ) - :ed_insert, - # 42 * - :ed_insert, - # 43 + - :ed_insert, - # 44 , - :ed_insert, - # 45 - - :ed_insert, - # 46 . - :ed_insert, - # 47 / - :ed_insert, - # 48 0 - :ed_digit, - # 49 1 - :ed_digit, - # 50 2 - :ed_digit, - # 51 3 - :ed_digit, - # 52 4 - :ed_digit, - # 53 5 - :ed_digit, - # 54 6 - :ed_digit, - # 55 7 - :ed_digit, - # 56 8 - :ed_digit, - # 57 9 - :ed_digit, - # 58 : - :ed_insert, - # 59 ; - :ed_insert, - # 60 < - :ed_insert, - # 61 = - :ed_insert, - # 62 > - :ed_insert, - # 63 ? - :ed_insert, - # 64 @ - :ed_insert, - # 65 A - :ed_insert, - # 66 B - :ed_insert, - # 67 C - :ed_insert, - # 68 D - :ed_insert, - # 69 E - :ed_insert, - # 70 F - :ed_insert, - # 71 G - :ed_insert, - # 72 H - :ed_insert, - # 73 I - :ed_insert, - # 74 J - :ed_insert, - # 75 K - :ed_insert, - # 76 L - :ed_insert, - # 77 M - :ed_insert, - # 78 N - :ed_insert, - # 79 O - :ed_insert, - # 80 P - :ed_insert, - # 81 Q - :ed_insert, - # 82 R - :ed_insert, - # 83 S - :ed_insert, - # 84 T - :ed_insert, - # 85 U - :ed_insert, - # 86 V - :ed_insert, - # 87 W - :ed_insert, - # 88 X - :ed_insert, - # 89 Y - :ed_insert, - # 90 Z - :ed_insert, - # 91 [ - :ed_insert, - # 92 \ - :ed_insert, - # 93 ] - :ed_insert, - # 94 ^ - :ed_insert, - # 95 _ - :ed_insert, - # 96 ` - :ed_insert, - # 97 a - :ed_insert, - # 98 b - :ed_insert, - # 99 c - :ed_insert, - # 100 d - :ed_insert, - # 101 e - :ed_insert, - # 102 f - :ed_insert, - # 103 g - :ed_insert, - # 104 h - :ed_insert, - # 105 i - :ed_insert, - # 106 j - :ed_insert, - # 107 k - :ed_insert, - # 108 l - :ed_insert, - # 109 m - :ed_insert, - # 110 n - :ed_insert, - # 111 o - :ed_insert, - # 112 p - :ed_insert, - # 113 q - :ed_insert, - # 114 r - :ed_insert, - # 115 s - :ed_insert, - # 116 t - :ed_insert, - # 117 u - :ed_insert, - # 118 v - :ed_insert, - # 119 w - :ed_insert, - # 120 x - :ed_insert, - # 121 y - :ed_insert, - # 122 z - :ed_insert, - # 123 { - :ed_insert, - # 124 | - :ed_insert, - # 125 } - :ed_insert, - # 126 ~ - :ed_insert, - # 127 ^? - :em_delete_prev_char, - # 128 M-^@ - :ed_unassigned, - # 129 M-^A - :ed_unassigned, - # 130 M-^B - :ed_unassigned, - # 131 M-^C - :ed_unassigned, - # 132 M-^D - :ed_unassigned, - # 133 M-^E - :ed_unassigned, - # 134 M-^F - :ed_unassigned, - # 135 M-^G - :ed_unassigned, - # 136 M-^H - :ed_delete_prev_word, - # 137 M-^I - :ed_unassigned, - # 138 M-^J - :key_newline, - # 139 M-^K - :ed_unassigned, - # 140 M-^L - :ed_clear_screen, - # 141 M-^M - :key_newline, - # 142 M-^N - :ed_unassigned, - # 143 M-^O - :ed_unassigned, - # 144 M-^P - :ed_unassigned, - # 145 M-^Q - :ed_unassigned, - # 146 M-^R - :ed_unassigned, - # 147 M-^S - :ed_unassigned, - # 148 M-^T - :ed_unassigned, - # 149 M-^U - :ed_unassigned, - # 150 M-^V - :ed_unassigned, - # 151 M-^W - :ed_unassigned, - # 152 M-^X - :ed_unassigned, - # 153 M-^Y - :em_yank_pop, - # 154 M-^Z - :ed_unassigned, - # 155 M-^[ - :ed_unassigned, - # 156 M-^\ - :ed_unassigned, - # 157 M-^] - :ed_unassigned, - # 158 M-^^ - :ed_unassigned, - # 159 M-^_ - :redo, - # 160 M-SPACE - :em_set_mark, - # 161 M-! - :ed_unassigned, - # 162 M-" - :ed_unassigned, - # 163 M-# - :ed_unassigned, - # 164 M-$ - :ed_unassigned, - # 165 M-% - :ed_unassigned, - # 166 M-& - :ed_unassigned, - # 167 M-' - :ed_unassigned, - # 168 M-( - :ed_unassigned, - # 169 M-) - :ed_unassigned, - # 170 M-* - :ed_unassigned, - # 171 M-+ - :ed_unassigned, - # 172 M-, - :ed_unassigned, - # 173 M-- - :ed_unassigned, - # 174 M-. - :ed_unassigned, - # 175 M-/ - :ed_unassigned, - # 176 M-0 - :ed_argument_digit, - # 177 M-1 - :ed_argument_digit, - # 178 M-2 - :ed_argument_digit, - # 179 M-3 - :ed_argument_digit, - # 180 M-4 - :ed_argument_digit, - # 181 M-5 - :ed_argument_digit, - # 182 M-6 - :ed_argument_digit, - # 183 M-7 - :ed_argument_digit, - # 184 M-8 - :ed_argument_digit, - # 185 M-9 - :ed_argument_digit, - # 186 M-: - :ed_unassigned, - # 187 M-; - :ed_unassigned, - # 188 M-< - :ed_unassigned, - # 189 M-= - :ed_unassigned, - # 190 M-> - :ed_unassigned, - # 191 M-? - :ed_unassigned, - # 192 M-@ - :ed_unassigned, - # 193 M-A - :ed_unassigned, - # 194 M-B - :ed_prev_word, - # 195 M-C - :em_capitol_case, - # 196 M-D - :em_delete_next_word, - # 197 M-E - :ed_unassigned, - # 198 M-F - :em_next_word, - # 199 M-G - :ed_unassigned, - # 200 M-H - :ed_unassigned, - # 201 M-I - :ed_unassigned, - # 202 M-J - :ed_unassigned, - # 203 M-K - :ed_unassigned, - # 204 M-L - :em_lower_case, - # 205 M-M - :ed_unassigned, - # 206 M-N - :vi_search_next, - # 207 M-O - :ed_unassigned, - # 208 M-P - :vi_search_prev, - # 209 M-Q - :ed_unassigned, - # 210 M-R - :ed_unassigned, - # 211 M-S - :ed_unassigned, - # 212 M-T - :ed_unassigned, - # 213 M-U - :em_upper_case, - # 214 M-V - :ed_unassigned, - # 215 M-W - :ed_unassigned, - # 216 M-X - :ed_unassigned, - # 217 M-Y - :em_yank_pop, - # 218 M-Z - :ed_unassigned, - # 219 M-[ - :ed_unassigned, - # 220 M-\ - :ed_unassigned, - # 221 M-] - :ed_unassigned, - # 222 M-^ - :ed_unassigned, - # 223 M-_ - :ed_unassigned, - # 224 M-` - :ed_unassigned, - # 225 M-a - :ed_unassigned, - # 226 M-b - :ed_prev_word, - # 227 M-c - :em_capitol_case, - # 228 M-d - :em_delete_next_word, - # 229 M-e - :ed_unassigned, - # 230 M-f - :em_next_word, - # 231 M-g - :ed_unassigned, - # 232 M-h - :ed_unassigned, - # 233 M-i - :ed_unassigned, - # 234 M-j - :ed_unassigned, - # 235 M-k - :ed_unassigned, - # 236 M-l - :em_lower_case, - # 237 M-m - :ed_unassigned, - # 238 M-n - :vi_search_next, - # 239 M-o - :ed_unassigned, - # 240 M-p - :vi_search_prev, - # 241 M-q - :ed_unassigned, - # 242 M-r - :ed_unassigned, - # 243 M-s - :ed_unassigned, - # 244 M-t - :ed_transpose_words, - # 245 M-u - :em_upper_case, - # 246 M-v - :ed_unassigned, - # 247 M-w - :ed_unassigned, - # 248 M-x - :ed_unassigned, - # 249 M-y - :ed_unassigned, - # 250 M-z - :ed_unassigned, - # 251 M-{ - :ed_unassigned, - # 252 M-| - :ed_unassigned, - # 253 M-} - :ed_unassigned, - # 254 M-~ - :ed_unassigned, - # 255 M-^? - :ed_delete_prev_word - # EOF - ] -end diff --git a/lib/reline/key_actor/vi_command.rb b/lib/reline/key_actor/vi_command.rb deleted file mode 100644 index d972c5e67f..0000000000 --- a/lib/reline/key_actor/vi_command.rb +++ /dev/null @@ -1,518 +0,0 @@ -module Reline::KeyActor - VI_COMMAND_MAPPING = [ - # 0 ^@ - :ed_unassigned, - # 1 ^A - :ed_move_to_beg, - # 2 ^B - :ed_unassigned, - # 3 ^C - :ed_ignore, - # 4 ^D - :vi_end_of_transmission, - # 5 ^E - :ed_move_to_end, - # 6 ^F - :ed_unassigned, - # 7 ^G - :ed_unassigned, - # 8 ^H - :ed_prev_char, - # 9 ^I - :ed_unassigned, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_kill_line, - # 12 ^L - :ed_clear_screen, - # 13 ^M - :ed_newline, - # 14 ^N - :ed_next_history, - # 15 ^O - :ed_ignore, - # 16 ^P - :ed_prev_history, - # 17 ^Q - :ed_ignore, - # 18 ^R - :vi_search_prev, - # 19 ^S - :ed_ignore, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :vi_kill_line_prev, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :ed_delete_prev_word, - # 24 ^X - :ed_unassigned, - # 25 ^Y - :em_yank, - # 26 ^Z - :ed_unassigned, - # 27 ^[ - :ed_unassigned, - # 28 ^\ - :ed_ignore, - # 29 ^] - :ed_unassigned, - # 30 ^^ - :ed_unassigned, - # 31 ^_ - :ed_unassigned, - # 32 SPACE - :ed_next_char, - # 33 ! - :ed_unassigned, - # 34 " - :ed_unassigned, - # 35 # - :vi_comment_out, - # 36 $ - :ed_move_to_end, - # 37 % - :ed_unassigned, - # 38 & - :ed_unassigned, - # 39 ' - :ed_unassigned, - # 40 ( - :ed_unassigned, - # 41 ) - :ed_unassigned, - # 42 * - :ed_unassigned, - # 43 + - :ed_next_history, - # 44 , - :ed_unassigned, - # 45 - - :ed_prev_history, - # 46 . - :ed_unassigned, - # 47 / - :vi_search_prev, - # 48 0 - :vi_zero, - # 49 1 - :ed_argument_digit, - # 50 2 - :ed_argument_digit, - # 51 3 - :ed_argument_digit, - # 52 4 - :ed_argument_digit, - # 53 5 - :ed_argument_digit, - # 54 6 - :ed_argument_digit, - # 55 7 - :ed_argument_digit, - # 56 8 - :ed_argument_digit, - # 57 9 - :ed_argument_digit, - # 58 : - :ed_unassigned, - # 59 ; - :ed_unassigned, - # 60 < - :ed_unassigned, - # 61 = - :ed_unassigned, - # 62 > - :ed_unassigned, - # 63 ? - :vi_search_next, - # 64 @ - :vi_alias, - # 65 A - :vi_add_at_eol, - # 66 B - :vi_prev_big_word, - # 67 C - :vi_change_to_eol, - # 68 D - :ed_kill_line, - # 69 E - :vi_end_big_word, - # 70 F - :vi_prev_char, - # 71 G - :vi_to_history_line, - # 72 H - :ed_unassigned, - # 73 I - :vi_insert_at_bol, - # 74 J - :vi_join_lines, - # 75 K - :vi_search_prev, - # 76 L - :ed_unassigned, - # 77 M - :ed_unassigned, - # 78 N - :ed_unassigned, - # 79 O - :ed_unassigned, - # 80 P - :vi_paste_prev, - # 81 Q - :ed_unassigned, - # 82 R - :ed_unassigned, - # 83 S - :ed_unassigned, - # 84 T - :vi_to_prev_char, - # 85 U - :ed_unassigned, - # 86 V - :ed_unassigned, - # 87 W - :vi_next_big_word, - # 88 X - :ed_delete_prev_char, - # 89 Y - :ed_unassigned, - # 90 Z - :ed_unassigned, - # 91 [ - :ed_unassigned, - # 92 \ - :ed_unassigned, - # 93 ] - :ed_unassigned, - # 94 ^ - :vi_first_print, - # 95 _ - :ed_unassigned, - # 96 ` - :ed_unassigned, - # 97 a - :vi_add, - # 98 b - :vi_prev_word, - # 99 c - :vi_change_meta, - # 100 d - :vi_delete_meta, - # 101 e - :vi_end_word, - # 102 f - :vi_next_char, - # 103 g - :ed_unassigned, - # 104 h - :ed_prev_char, - # 105 i - :vi_insert, - # 106 j - :ed_next_history, - # 107 k - :ed_prev_history, - # 108 l - :ed_next_char, - # 109 m - :ed_unassigned, - # 110 n - :ed_unassigned, - # 111 o - :ed_unassigned, - # 112 p - :vi_paste_next, - # 113 q - :ed_unassigned, - # 114 r - :vi_replace_char, - # 115 s - :ed_unassigned, - # 116 t - :vi_to_next_char, - # 117 u - :ed_unassigned, - # 118 v - :vi_histedit, - # 119 w - :vi_next_word, - # 120 x - :ed_delete_next_char, - # 121 y - :vi_yank, - # 122 z - :ed_unassigned, - # 123 { - :ed_unassigned, - # 124 | - :vi_to_column, - # 125 } - :ed_unassigned, - # 126 ~ - :ed_unassigned, - # 127 ^? - :em_delete_prev_char, - # 128 M-^@ - :ed_unassigned, - # 129 M-^A - :ed_unassigned, - # 130 M-^B - :ed_unassigned, - # 131 M-^C - :ed_unassigned, - # 132 M-^D - :ed_unassigned, - # 133 M-^E - :ed_unassigned, - # 134 M-^F - :ed_unassigned, - # 135 M-^G - :ed_unassigned, - # 136 M-^H - :ed_unassigned, - # 137 M-^I - :ed_unassigned, - # 138 M-^J - :ed_unassigned, - # 139 M-^K - :ed_unassigned, - # 140 M-^L - :ed_unassigned, - # 141 M-^M - :ed_unassigned, - # 142 M-^N - :ed_unassigned, - # 143 M-^O - :ed_unassigned, - # 144 M-^P - :ed_unassigned, - # 145 M-^Q - :ed_unassigned, - # 146 M-^R - :ed_unassigned, - # 147 M-^S - :ed_unassigned, - # 148 M-^T - :ed_unassigned, - # 149 M-^U - :ed_unassigned, - # 150 M-^V - :ed_unassigned, - # 151 M-^W - :ed_unassigned, - # 152 M-^X - :ed_unassigned, - # 153 M-^Y - :ed_unassigned, - # 154 M-^Z - :ed_unassigned, - # 155 M-^[ - :ed_unassigned, - # 156 M-^\ - :ed_unassigned, - # 157 M-^] - :ed_unassigned, - # 158 M-^^ - :ed_unassigned, - # 159 M-^_ - :ed_unassigned, - # 160 M-SPACE - :ed_unassigned, - # 161 M-! - :ed_unassigned, - # 162 M-" - :ed_unassigned, - # 163 M-# - :ed_unassigned, - # 164 M-$ - :ed_unassigned, - # 165 M-% - :ed_unassigned, - # 166 M-& - :ed_unassigned, - # 167 M-' - :ed_unassigned, - # 168 M-( - :ed_unassigned, - # 169 M-) - :ed_unassigned, - # 170 M-* - :ed_unassigned, - # 171 M-+ - :ed_unassigned, - # 172 M-, - :ed_unassigned, - # 173 M-- - :ed_unassigned, - # 174 M-. - :ed_unassigned, - # 175 M-/ - :ed_unassigned, - # 176 M-0 - :ed_unassigned, - # 177 M-1 - :ed_unassigned, - # 178 M-2 - :ed_unassigned, - # 179 M-3 - :ed_unassigned, - # 180 M-4 - :ed_unassigned, - # 181 M-5 - :ed_unassigned, - # 182 M-6 - :ed_unassigned, - # 183 M-7 - :ed_unassigned, - # 184 M-8 - :ed_unassigned, - # 185 M-9 - :ed_unassigned, - # 186 M-: - :ed_unassigned, - # 187 M-; - :ed_unassigned, - # 188 M-< - :ed_unassigned, - # 189 M-= - :ed_unassigned, - # 190 M-> - :ed_unassigned, - # 191 M-? - :ed_unassigned, - # 192 M-@ - :ed_unassigned, - # 193 M-A - :ed_unassigned, - # 194 M-B - :ed_unassigned, - # 195 M-C - :ed_unassigned, - # 196 M-D - :ed_unassigned, - # 197 M-E - :ed_unassigned, - # 198 M-F - :ed_unassigned, - # 199 M-G - :ed_unassigned, - # 200 M-H - :ed_unassigned, - # 201 M-I - :ed_unassigned, - # 202 M-J - :ed_unassigned, - # 203 M-K - :ed_unassigned, - # 204 M-L - :ed_unassigned, - # 205 M-M - :ed_unassigned, - # 206 M-N - :ed_unassigned, - # 207 M-O - :ed_unassigned, - # 208 M-P - :ed_unassigned, - # 209 M-Q - :ed_unassigned, - # 210 M-R - :ed_unassigned, - # 211 M-S - :ed_unassigned, - # 212 M-T - :ed_unassigned, - # 213 M-U - :ed_unassigned, - # 214 M-V - :ed_unassigned, - # 215 M-W - :ed_unassigned, - # 216 M-X - :ed_unassigned, - # 217 M-Y - :ed_unassigned, - # 218 M-Z - :ed_unassigned, - # 219 M-[ - :ed_unassigned, - # 220 M-\ - :ed_unassigned, - # 221 M-] - :ed_unassigned, - # 222 M-^ - :ed_unassigned, - # 223 M-_ - :ed_unassigned, - # 224 M-` - :ed_unassigned, - # 225 M-a - :ed_unassigned, - # 226 M-b - :ed_unassigned, - # 227 M-c - :ed_unassigned, - # 228 M-d - :ed_unassigned, - # 229 M-e - :ed_unassigned, - # 230 M-f - :ed_unassigned, - # 231 M-g - :ed_unassigned, - # 232 M-h - :ed_unassigned, - # 233 M-i - :ed_unassigned, - # 234 M-j - :ed_unassigned, - # 235 M-k - :ed_unassigned, - # 236 M-l - :ed_unassigned, - # 237 M-m - :ed_unassigned, - # 238 M-n - :ed_unassigned, - # 239 M-o - :ed_unassigned, - # 240 M-p - :ed_unassigned, - # 241 M-q - :ed_unassigned, - # 242 M-r - :ed_unassigned, - # 243 M-s - :ed_unassigned, - # 244 M-t - :ed_unassigned, - # 245 M-u - :ed_unassigned, - # 246 M-v - :ed_unassigned, - # 247 M-w - :ed_unassigned, - # 248 M-x - :ed_unassigned, - # 249 M-y - :ed_unassigned, - # 250 M-z - :ed_unassigned, - # 251 M-{ - :ed_unassigned, - # 252 M-| - :ed_unassigned, - # 253 M-} - :ed_unassigned, - # 254 M-~ - :ed_unassigned, - # 255 M-^? - :ed_unassigned - # EOF - ] -end - diff --git a/lib/reline/key_actor/vi_insert.rb b/lib/reline/key_actor/vi_insert.rb deleted file mode 100644 index 312df1646b..0000000000 --- a/lib/reline/key_actor/vi_insert.rb +++ /dev/null @@ -1,517 +0,0 @@ -module Reline::KeyActor - VI_INSERT_MAPPING = [ - # 0 ^@ - :ed_unassigned, - # 1 ^A - :ed_insert, - # 2 ^B - :ed_insert, - # 3 ^C - :ed_insert, - # 4 ^D - :vi_list_or_eof, - # 5 ^E - :ed_insert, - # 6 ^F - :ed_insert, - # 7 ^G - :ed_insert, - # 8 ^H - :vi_delete_prev_char, - # 9 ^I - :complete, - # 10 ^J - :ed_newline, - # 11 ^K - :ed_insert, - # 12 ^L - :ed_insert, - # 13 ^M - :ed_newline, - # 14 ^N - :menu_complete, - # 15 ^O - :ed_insert, - # 16 ^P - :menu_complete_backward, - # 17 ^Q - :ed_ignore, - # 18 ^R - :vi_search_prev, - # 19 ^S - :vi_search_next, - # 20 ^T - :ed_transpose_chars, - # 21 ^U - :vi_kill_line_prev, - # 22 ^V - :ed_quoted_insert, - # 23 ^W - :ed_delete_prev_word, - # 24 ^X - :ed_insert, - # 25 ^Y - :em_yank, - # 26 ^Z - :ed_insert, - # 27 ^[ - :vi_command_mode, - # 28 ^\ - :ed_ignore, - # 29 ^] - :ed_insert, - # 30 ^^ - :ed_insert, - # 31 ^_ - :ed_insert, - # 32 SPACE - :ed_insert, - # 33 ! - :ed_insert, - # 34 " - :ed_insert, - # 35 # - :ed_insert, - # 36 $ - :ed_insert, - # 37 % - :ed_insert, - # 38 & - :ed_insert, - # 39 ' - :ed_insert, - # 40 ( - :ed_insert, - # 41 ) - :ed_insert, - # 42 * - :ed_insert, - # 43 + - :ed_insert, - # 44 , - :ed_insert, - # 45 - - :ed_insert, - # 46 . - :ed_insert, - # 47 / - :ed_insert, - # 48 0 - :ed_insert, - # 49 1 - :ed_insert, - # 50 2 - :ed_insert, - # 51 3 - :ed_insert, - # 52 4 - :ed_insert, - # 53 5 - :ed_insert, - # 54 6 - :ed_insert, - # 55 7 - :ed_insert, - # 56 8 - :ed_insert, - # 57 9 - :ed_insert, - # 58 : - :ed_insert, - # 59 ; - :ed_insert, - # 60 < - :ed_insert, - # 61 = - :ed_insert, - # 62 > - :ed_insert, - # 63 ? - :ed_insert, - # 64 @ - :ed_insert, - # 65 A - :ed_insert, - # 66 B - :ed_insert, - # 67 C - :ed_insert, - # 68 D - :ed_insert, - # 69 E - :ed_insert, - # 70 F - :ed_insert, - # 71 G - :ed_insert, - # 72 H - :ed_insert, - # 73 I - :ed_insert, - # 74 J - :ed_insert, - # 75 K - :ed_insert, - # 76 L - :ed_insert, - # 77 M - :ed_insert, - # 78 N - :ed_insert, - # 79 O - :ed_insert, - # 80 P - :ed_insert, - # 81 Q - :ed_insert, - # 82 R - :ed_insert, - # 83 S - :ed_insert, - # 84 T - :ed_insert, - # 85 U - :ed_insert, - # 86 V - :ed_insert, - # 87 W - :ed_insert, - # 88 X - :ed_insert, - # 89 Y - :ed_insert, - # 90 Z - :ed_insert, - # 91 [ - :ed_insert, - # 92 \ - :ed_insert, - # 93 ] - :ed_insert, - # 94 ^ - :ed_insert, - # 95 _ - :ed_insert, - # 96 ` - :ed_insert, - # 97 a - :ed_insert, - # 98 b - :ed_insert, - # 99 c - :ed_insert, - # 100 d - :ed_insert, - # 101 e - :ed_insert, - # 102 f - :ed_insert, - # 103 g - :ed_insert, - # 104 h - :ed_insert, - # 105 i - :ed_insert, - # 106 j - :ed_insert, - # 107 k - :ed_insert, - # 108 l - :ed_insert, - # 109 m - :ed_insert, - # 110 n - :ed_insert, - # 111 o - :ed_insert, - # 112 p - :ed_insert, - # 113 q - :ed_insert, - # 114 r - :ed_insert, - # 115 s - :ed_insert, - # 116 t - :ed_insert, - # 117 u - :ed_insert, - # 118 v - :ed_insert, - # 119 w - :ed_insert, - # 120 x - :ed_insert, - # 121 y - :ed_insert, - # 122 z - :ed_insert, - # 123 { - :ed_insert, - # 124 | - :ed_insert, - # 125 } - :ed_insert, - # 126 ~ - :ed_insert, - # 127 ^? - :vi_delete_prev_char, - # 128 M-^@ - :ed_unassigned, - # 129 M-^A - :ed_unassigned, - # 130 M-^B - :ed_unassigned, - # 131 M-^C - :ed_unassigned, - # 132 M-^D - :ed_unassigned, - # 133 M-^E - :ed_unassigned, - # 134 M-^F - :ed_unassigned, - # 135 M-^G - :ed_unassigned, - # 136 M-^H - :ed_unassigned, - # 137 M-^I - :ed_unassigned, - # 138 M-^J - :key_newline, - # 139 M-^K - :ed_unassigned, - # 140 M-^L - :ed_unassigned, - # 141 M-^M - :key_newline, - # 142 M-^N - :ed_unassigned, - # 143 M-^O - :ed_unassigned, - # 144 M-^P - :ed_unassigned, - # 145 M-^Q - :ed_unassigned, - # 146 M-^R - :ed_unassigned, - # 147 M-^S - :ed_unassigned, - # 148 M-^T - :ed_unassigned, - # 149 M-^U - :ed_unassigned, - # 150 M-^V - :ed_unassigned, - # 151 M-^W - :ed_unassigned, - # 152 M-^X - :ed_unassigned, - # 153 M-^Y - :ed_unassigned, - # 154 M-^Z - :ed_unassigned, - # 155 M-^[ - :ed_unassigned, - # 156 M-^\ - :ed_unassigned, - # 157 M-^] - :ed_unassigned, - # 158 M-^^ - :ed_unassigned, - # 159 M-^_ - :ed_unassigned, - # 160 M-SPACE - :ed_unassigned, - # 161 M-! - :ed_unassigned, - # 162 M-" - :ed_unassigned, - # 163 M-# - :ed_unassigned, - # 164 M-$ - :ed_unassigned, - # 165 M-% - :ed_unassigned, - # 166 M-& - :ed_unassigned, - # 167 M-' - :ed_unassigned, - # 168 M-( - :ed_unassigned, - # 169 M-) - :ed_unassigned, - # 170 M-* - :ed_unassigned, - # 171 M-+ - :ed_unassigned, - # 172 M-, - :ed_unassigned, - # 173 M-- - :ed_unassigned, - # 174 M-. - :ed_unassigned, - # 175 M-/ - :ed_unassigned, - # 176 M-0 - :ed_unassigned, - # 177 M-1 - :ed_unassigned, - # 178 M-2 - :ed_unassigned, - # 179 M-3 - :ed_unassigned, - # 180 M-4 - :ed_unassigned, - # 181 M-5 - :ed_unassigned, - # 182 M-6 - :ed_unassigned, - # 183 M-7 - :ed_unassigned, - # 184 M-8 - :ed_unassigned, - # 185 M-9 - :ed_unassigned, - # 186 M-: - :ed_unassigned, - # 187 M-; - :ed_unassigned, - # 188 M-< - :ed_unassigned, - # 189 M-= - :ed_unassigned, - # 190 M-> - :ed_unassigned, - # 191 M-? - :ed_unassigned, - # 192 M-@ - :ed_unassigned, - # 193 M-A - :ed_unassigned, - # 194 M-B - :ed_unassigned, - # 195 M-C - :ed_unassigned, - # 196 M-D - :ed_unassigned, - # 197 M-E - :ed_unassigned, - # 198 M-F - :ed_unassigned, - # 199 M-G - :ed_unassigned, - # 200 M-H - :ed_unassigned, - # 201 M-I - :ed_unassigned, - # 202 M-J - :ed_unassigned, - # 203 M-K - :ed_unassigned, - # 204 M-L - :ed_unassigned, - # 205 M-M - :ed_unassigned, - # 206 M-N - :ed_unassigned, - # 207 M-O - :ed_unassigned, - # 208 M-P - :ed_unassigned, - # 209 M-Q - :ed_unassigned, - # 210 M-R - :ed_unassigned, - # 211 M-S - :ed_unassigned, - # 212 M-T - :ed_unassigned, - # 213 M-U - :ed_unassigned, - # 214 M-V - :ed_unassigned, - # 215 M-W - :ed_unassigned, - # 216 M-X - :ed_unassigned, - # 217 M-Y - :ed_unassigned, - # 218 M-Z - :ed_unassigned, - # 219 M-[ - :ed_unassigned, - # 220 M-\ - :ed_unassigned, - # 221 M-] - :ed_unassigned, - # 222 M-^ - :ed_unassigned, - # 223 M-_ - :ed_unassigned, - # 224 M-` - :ed_unassigned, - # 225 M-a - :ed_unassigned, - # 226 M-b - :ed_unassigned, - # 227 M-c - :ed_unassigned, - # 228 M-d - :ed_unassigned, - # 229 M-e - :ed_unassigned, - # 230 M-f - :ed_unassigned, - # 231 M-g - :ed_unassigned, - # 232 M-h - :ed_unassigned, - # 233 M-i - :ed_unassigned, - # 234 M-j - :ed_unassigned, - # 235 M-k - :ed_unassigned, - # 236 M-l - :ed_unassigned, - # 237 M-m - :ed_unassigned, - # 238 M-n - :ed_unassigned, - # 239 M-o - :ed_unassigned, - # 240 M-p - :ed_unassigned, - # 241 M-q - :ed_unassigned, - # 242 M-r - :ed_unassigned, - # 243 M-s - :ed_unassigned, - # 244 M-t - :ed_unassigned, - # 245 M-u - :ed_unassigned, - # 246 M-v - :ed_unassigned, - # 247 M-w - :ed_unassigned, - # 248 M-x - :ed_unassigned, - # 249 M-y - :ed_unassigned, - # 250 M-z - :ed_unassigned, - # 251 M-{ - :ed_unassigned, - # 252 M-| - :ed_unassigned, - # 253 M-} - :ed_unassigned, - # 254 M-~ - :ed_unassigned, - # 255 M-^? - :ed_unassigned - # EOF - ] -end diff --git a/lib/reline/key_stroke.rb b/lib/reline/key_stroke.rb deleted file mode 100644 index ba40899685..0000000000 --- a/lib/reline/key_stroke.rb +++ /dev/null @@ -1,109 +0,0 @@ -class Reline::KeyStroke - ESC_BYTE = 27 - CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f - CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f) - - def initialize(config) - @config = config - end - - # Input exactly matches to a key sequence - MATCHING = :matching - # Input partially matches to a key sequence - MATCHED = :matched - # Input matches to a key sequence and the key sequence is a prefix of another key sequence - MATCHING_MATCHED = :matching_matched - # Input does not match to any key sequence - UNMATCHED = :unmatched - - def match_status(input) - matching = key_mapping.matching?(input) - matched = key_mapping.get(input) - - # FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor. - matched ||= input.size == 1 - matching ||= input == [ESC_BYTE] - - if matching && matched - MATCHING_MATCHED - elsif matching - MATCHING - elsif matched - MATCHED - elsif input[0] == ESC_BYTE - match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command)) - elsif input.size == 1 - MATCHED - else - UNMATCHED - end - end - - def expand(input) - matched_bytes = nil - (1..input.size).each do |i| - bytes = input.take(i) - status = match_status(bytes) - matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED - end - return [[], []] unless matched_bytes - - func = key_mapping.get(matched_bytes) - if func.is_a?(Array) - keys = func.map { |c| Reline::Key.new(c, c, false) } - elsif func - keys = [Reline::Key.new(func, func, false)] - elsif matched_bytes.size == 1 - keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)] - elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE - keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)] - else - keys = [] - end - - [keys, input.drop(matched_bytes.size)] - end - - private - - # returns match status of CSI/SS3 sequence and matched length - def match_unknown_escape_sequence(input, vi_mode: false) - idx = 0 - return UNMATCHED unless input[idx] == ESC_BYTE - idx += 1 - idx += 1 if input[idx] == ESC_BYTE - - case input[idx] - when nil - if idx == 1 # `ESC` - return MATCHING_MATCHED - else # `ESC ESC` - return MATCHING - end - when 91 # == '['.ord - # CSI sequence `ESC [ ... char` - idx += 1 - idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx]) - idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx]) - when 79 # == 'O'.ord - # SS3 sequence `ESC O char` - idx += 1 - else - # `ESC char` or `ESC ESC char` - return UNMATCHED if vi_mode - end - - case input.size - when idx - MATCHING - when idx + 1 - MATCHED - else - UNMATCHED - end - end - - def key_mapping - @config.key_bindings - end -end diff --git a/lib/reline/kill_ring.rb b/lib/reline/kill_ring.rb deleted file mode 100644 index 201f6f3ca0..0000000000 --- a/lib/reline/kill_ring.rb +++ /dev/null @@ -1,125 +0,0 @@ -class Reline::KillRing - include Enumerable - - module State - FRESH = :fresh - CONTINUED = :continued - PROCESSED = :processed - YANK = :yank - end - - RingPoint = Struct.new(:backward, :forward, :str) do - def initialize(str) - super(nil, nil, str) - end - - def ==(other) - equal?(other) - end - end - - class RingBuffer - attr_reader :size - attr_reader :head - - def initialize(max = 1024) - @max = max - @size = 0 - @head = nil # reading head of ring-shaped tape - end - - def <<(point) - if @size.zero? - @head = point - @head.backward = @head - @head.forward = @head - @size = 1 - elsif @size >= @max - tail = @head.forward - new_tail = tail.forward - @head.forward = point - point.backward = @head - new_tail.backward = point - point.forward = new_tail - @head = point - else - tail = @head.forward - @head.forward = point - point.backward = @head - tail.backward = point - point.forward = tail - @head = point - @size += 1 - end - end - - def empty? - @size.zero? - end - end - - def initialize(max = 1024) - @ring = RingBuffer.new(max) - @ring_pointer = nil - @buffer = nil - @state = State::FRESH - end - - def append(string, before_p = false) - case @state - when State::FRESH, State::YANK - @ring << RingPoint.new(+string) - @state = State::CONTINUED - when State::CONTINUED, State::PROCESSED - if before_p - @ring.head.str.prepend(string) - else - @ring.head.str.concat(string) - end - @state = State::CONTINUED - end - end - - def process - case @state - when State::FRESH - # nothing to do - when State::CONTINUED - @state = State::PROCESSED - when State::PROCESSED - @state = State::FRESH - when State::YANK - # nothing to do - end - end - - def yank - unless @ring.empty? - @state = State::YANK - @ring_pointer = @ring.head - @ring_pointer.str - else - nil - end - end - - def yank_pop - if @state == State::YANK - prev_yank = @ring_pointer.str - @ring_pointer = @ring_pointer.backward - [@ring_pointer.str, prev_yank] - else - nil - end - end - - def each - start = head = @ring.head - loop do - break if head.nil? - yield head.str - head = head.backward - break if head == start - end - end -end diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb deleted file mode 100644 index 4d0eb393e9..0000000000 --- a/lib/reline/line_editor.rb +++ /dev/null @@ -1,2530 +0,0 @@ -require 'reline/kill_ring' -require 'reline/unicode' - -require 'tempfile' - -class Reline::LineEditor - # TODO: Use "private alias_method" idiom after drop Ruby 2.5. - attr_reader :byte_pointer - attr_accessor :confirm_multiline_termination_proc - attr_accessor :completion_proc - attr_accessor :completion_append_character - attr_accessor :output_modifier_proc - attr_accessor :prompt_proc - attr_accessor :auto_indent_proc - attr_accessor :dig_perfect_match_proc - attr_writer :output - - VI_MOTIONS = %i{ - ed_prev_char - ed_next_char - vi_zero - ed_move_to_beg - ed_move_to_end - vi_to_column - vi_next_char - vi_prev_char - vi_next_word - vi_prev_word - vi_to_next_char - vi_to_prev_char - vi_end_word - vi_next_big_word - vi_prev_big_word - vi_end_big_word - } - - module CompletionState - NORMAL = :normal - COMPLETION = :completion - MENU = :menu - MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match - PERFECT_MATCH = :perfect_match - end - - RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true) - - CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer) - NullActionState = [nil, nil].freeze - - class MenuInfo - attr_reader :list - - def initialize(list) - @list = list - end - - def lines(screen_width) - return [] if @list.empty? - - list = @list.sort - sizes = list.map { |item| Reline::Unicode.calculate_width(item) } - item_width = sizes.max + 2 - num_cols = [screen_width / item_width, 1].max - num_rows = list.size.fdiv(num_cols).ceil - list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) } - aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose - aligned.map do |row| - row.join.rstrip - end - end - end - - MINIMUM_SCROLLBAR_HEIGHT = 1 - - def initialize(config) - @config = config - @completion_append_character = '' - @screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset - reset_variables - end - - def io_gate - Reline::IOGate - end - - def encoding - io_gate.encoding - end - - def set_pasting_state(in_pasting) - # While pasting, text to be inserted is stored to @continuous_insertion_buffer. - # After pasting, this buffer should be force inserted. - process_insert(force: true) if @in_pasting && !in_pasting - @in_pasting = in_pasting - end - - private def check_mode_string - if @config.show_mode_in_prompt - if @config.editing_mode_is?(:vi_command) - @config.vi_cmd_mode_string - elsif @config.editing_mode_is?(:vi_insert) - @config.vi_ins_mode_string - elsif @config.editing_mode_is?(:emacs) - @config.emacs_mode_string - else - '?' - end - end - end - - private def check_multiline_prompt(buffer, mode_string) - if @vi_arg - prompt = "(arg: #{@vi_arg}) " - elsif @searching_prompt - prompt = @searching_prompt - else - prompt = @prompt - end - if !@is_multiline - mode_string = check_mode_string - prompt = mode_string + prompt if mode_string - [prompt] + [''] * (buffer.size - 1) - elsif @prompt_proc - prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") } - prompt_list.map!{ prompt } if @vi_arg or @searching_prompt - prompt_list = [prompt] if prompt_list.empty? - prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string - prompt = prompt_list[@line_index] - prompt = prompt_list[0] if prompt.nil? - prompt = prompt_list.last if prompt.nil? - if buffer.size > prompt_list.size - (buffer.size - prompt_list.size).times do - prompt_list << prompt_list.last - end - end - prompt_list - else - prompt = mode_string + prompt if mode_string - [prompt] * buffer.size - end - end - - def reset(prompt = '') - @screen_size = Reline::IOGate.get_screen_size - reset_variables(prompt) - @rendered_screen.base_y = Reline::IOGate.cursor_pos.y - if ENV.key?('RELINE_ALT_SCROLLBAR') - @full_block = '::' - @upper_half_block = "''" - @lower_half_block = '..' - @block_elem_width = 2 - elsif Reline::IOGate.win? - @full_block = '█' - @upper_half_block = '▀' - @lower_half_block = '▄' - @block_elem_width = 1 - elsif encoding == Encoding::UTF_8 - @full_block = '█' - @upper_half_block = '▀' - @lower_half_block = '▄' - @block_elem_width = Reline::Unicode.calculate_width('█') - else - @full_block = '::' - @upper_half_block = "''" - @lower_half_block = '..' - @block_elem_width = 2 - end - end - - def handle_signal - handle_interrupted - handle_resized - end - - private def handle_resized - return unless @resized - - @screen_size = Reline::IOGate.get_screen_size - @resized = false - scroll_into_view - Reline::IOGate.move_cursor_up @rendered_screen.cursor_y - @rendered_screen.base_y = Reline::IOGate.cursor_pos.y - clear_rendered_screen_cache - render - end - - private def handle_interrupted - return unless @interrupted - - @interrupted = false - clear_dialogs - render - cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y - Reline::IOGate.scroll_down cursor_to_bottom_offset - Reline::IOGate.move_cursor_column 0 - clear_rendered_screen_cache - case @old_trap - when 'DEFAULT', 'SYSTEM_DEFAULT' - raise Interrupt - when 'IGNORE' - # Do nothing - when 'EXIT' - exit - else - @old_trap.call if @old_trap.respond_to?(:call) - end - end - - def set_signal_handlers - Reline::IOGate.set_winch_handler do - @resized = true - end - @old_trap = Signal.trap('INT') do - @interrupted = true - end - end - - def finalize - Signal.trap('INT', @old_trap) - end - - def eof? - @eof - end - - def reset_variables(prompt = '') - @prompt = prompt.gsub("\n", "\\n") - @mark_pointer = nil - @is_multiline = false - @finished = false - @history_pointer = nil - @kill_ring ||= Reline::KillRing.new - @vi_clipboard = '' - @vi_arg = nil - @waiting_proc = nil - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - @completion_journey_state = nil - @completion_state = CompletionState::NORMAL - @perfect_matched = nil - @menu_info = nil - @searching_prompt = nil - @just_cursor_moving = false - @eof = false - @continuous_insertion_buffer = String.new(encoding: encoding) - @scroll_partial_screen = 0 - @drop_terminate_spaces = false - @in_pasting = false - @auto_indent_proc = nil - @dialogs = [] - @interrupted = false - @resized = false - @cache = {} - @rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0) - @input_lines = [[[""], 0, 0]] - @input_lines_position = 0 - @undoing = false - @prev_action_state = NullActionState - @next_action_state = NullActionState - reset_line - end - - def reset_line - @byte_pointer = 0 - @buffer_of_lines = [String.new(encoding: encoding)] - @line_index = 0 - @cache.clear - @line_backup_in_history = nil - @multibyte_buffer = String.new(encoding: 'ASCII-8BIT') - end - - def multiline_on - @is_multiline = true - end - - def multiline_off - @is_multiline = false - end - - private def insert_new_line(cursor_line, next_line) - @buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: encoding)) - @buffer_of_lines[@line_index] = cursor_line - @line_index += 1 - @byte_pointer = 0 - if @auto_indent_proc && !@in_pasting - if next_line.empty? - ( - # For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false` - indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true) - indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false) - indent = indent2 || indent1 - @buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '') - ) - process_auto_indent @line_index, add_newline: true - else - process_auto_indent @line_index - 1, cursor_dependent: false - process_auto_indent @line_index, add_newline: true # Need for compatibility - process_auto_indent @line_index, cursor_dependent: false - end - end - end - - private def split_by_width(str, max_width, offset: 0) - Reline::Unicode.split_by_width(str, max_width, encoding, offset: offset) - end - - def current_byte_pointer_cursor - calculate_width(current_line.byteslice(0, @byte_pointer)) - end - - private def calculate_nearest_cursor(cursor) - line_to_calc = current_line - new_cursor_max = calculate_width(line_to_calc) - new_cursor = 0 - new_byte_pointer = 0 - height = 1 - max_width = screen_width - if @config.editing_mode_is?(:vi_command) - last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize) - if last_byte_size > 0 - last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size) - last_width = Reline::Unicode.get_mbchar_width(last_mbchar) - end_of_line_cursor = new_cursor_max - last_width - else - end_of_line_cursor = new_cursor_max - end - else - end_of_line_cursor = new_cursor_max - end - line_to_calc.grapheme_clusters.each do |gc| - mbchar = gc.encode(Encoding::UTF_8) - mbchar_width = Reline::Unicode.get_mbchar_width(mbchar) - now = new_cursor + mbchar_width - if now > end_of_line_cursor or now > cursor - break - end - new_cursor += mbchar_width - if new_cursor > max_width * height - height += 1 - end - new_byte_pointer += gc.bytesize - end - @byte_pointer = new_byte_pointer - end - - def with_cache(key, *deps) - cached_deps, value = @cache[key] - if cached_deps != deps - @cache[key] = [deps, value = yield(*deps, cached_deps, value)] - end - value - end - - def modified_lines - with_cache(__method__, whole_lines, finished?) do |whole, complete| - modify_lines(whole, complete) - end - end - - def prompt_list - with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string| - check_multiline_prompt(lines, mode_string) - end - end - - def screen_height - @screen_size.first - end - - def screen_width - @screen_size.last - end - - def screen_scroll_top - @scroll_partial_screen - end - - def wrapped_prompt_and_input_lines - with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value| - prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key - cached_wraps = {} - if prev_width == width - prev_n.times do |i| - cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i] - end - end - - n.times.map do |i| - prompt = prompts[i] || '' - line = lines[i] || '' - if (cached = cached_wraps[[prompt, line]]) - next cached - end - *wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact - wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact - wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] } - end - end - end - - def calculate_overlay_levels(overlay_levels) - levels = [] - overlay_levels.each do |x, w, l| - levels.fill(l, x, w) - end - levels - end - - def render_line_differential(old_items, new_items) - old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact) - new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width) - base_x = 0 - new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk| - width = chunk.size - if level == :skip - # do nothing - elsif level == :blank - Reline::IOGate.move_cursor_column base_x - @output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}" - else - x, w, content = new_items[level] - cover_begin = base_x != 0 && new_levels[base_x - 1] == level - cover_end = new_levels[base_x + width] == level - pos = 0 - unless x == base_x && w == width - content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true) - end - Reline::IOGate.move_cursor_column x + pos - @output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}" - end - base_x += width - end - if old_levels.size > new_levels.size - Reline::IOGate.move_cursor_column new_levels.size - Reline::IOGate.erase_after_cursor - end - end - - # Calculate cursor position in word wrapped content. - def wrapped_cursor_position - prompt_width = calculate_width(prompt_list[@line_index], true) - line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer) - wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact - wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1 - wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last) - [wrapped_cursor_x, wrapped_cursor_y] - end - - def clear_dialogs - @dialogs.each do |dialog| - dialog.contents = nil - dialog.trap_key = nil - end - end - - def update_dialogs(key = nil) - wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position - @dialogs.each do |dialog| - dialog.trap_key = nil - update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key) - end - end - - def render_finished - render_differential([], 0, 0) - lines = @buffer_of_lines.size.times.map do |i| - line = Reline::Unicode.strip_non_printing_start_end(prompt_list[i]) + modified_lines[i] - wrapped_lines, = split_by_width(line, screen_width) - wrapped_lines.last.empty? ? "#{line} " : line - end - @output.puts lines.map { |l| "#{l}\r\n" }.join - end - - def print_nomultiline_prompt - # Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence. - @output.write Reline::Unicode.strip_non_printing_start_end(@prompt) if @prompt && !@is_multiline - end - - def render - wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position - new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line| - prompt_width = Reline::Unicode.calculate_width(prompt, true) - [[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]] - end - if @menu_info - @menu_info.lines(screen_width).each do |item| - new_lines << [[0, Reline::Unicode.calculate_width(item), item]] - end - @menu_info = nil # TODO: do not change state here - end - - @dialogs.each_with_index do |dialog, index| - next unless dialog.contents - - x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top - y_range.each do |row| - next if row < 0 || row >= screen_height - - dialog_rows = new_lines[row] ||= [] - # index 0 is for prompt, index 1 is for line, index 2.. is for dialog - dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]] - end - end - - render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top - end - - # Reflects lines to be rendered and new cursor position to the screen - # by calculating the difference from the previous render. - - private def render_differential(new_lines, new_cursor_x, new_cursor_y) - rendered_lines = @rendered_screen.lines - cursor_y = @rendered_screen.cursor_y - if new_lines != rendered_lines - # Hide cursor while rendering to avoid cursor flickering. - Reline::IOGate.hide_cursor - num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min - if @rendered_screen.base_y + num_lines > screen_height - Reline::IOGate.scroll_down(num_lines - cursor_y - 1) - @rendered_screen.base_y = screen_height - num_lines - cursor_y = num_lines - 1 - end - num_lines.times do |i| - rendered_line = rendered_lines[i] || [] - line_to_render = new_lines[i] || [] - next if rendered_line == line_to_render - - Reline::IOGate.move_cursor_down i - cursor_y - cursor_y = i - unless rendered_lines[i] - Reline::IOGate.move_cursor_column 0 - Reline::IOGate.erase_after_cursor - end - render_line_differential(rendered_line, line_to_render) - end - @rendered_screen.lines = new_lines - Reline::IOGate.show_cursor - end - Reline::IOGate.move_cursor_column new_cursor_x - Reline::IOGate.move_cursor_down new_cursor_y - cursor_y - @rendered_screen.cursor_y = new_cursor_y - end - - private def clear_rendered_screen_cache - @rendered_screen.lines = [] - @rendered_screen.cursor_y = 0 - end - - def upper_space_height(wrapped_cursor_y) - wrapped_cursor_y - screen_scroll_top - end - - def rest_height(wrapped_cursor_y) - screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1 - end - - def rerender - render unless @in_pasting - end - - class DialogProcScope - CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer) - - def initialize(line_editor, config, proc_to_exec, context) - @line_editor = line_editor - @config = config - @proc_to_exec = proc_to_exec - @context = context - @cursor_pos = Reline::CursorPos.new - end - - def context - @context - end - - def retrieve_completion_block(set_completion_quote_character = false) - @line_editor.retrieve_completion_block(set_completion_quote_character) - end - - def call_completion_proc_with_checking_args(pre, target, post) - @line_editor.call_completion_proc_with_checking_args(pre, target, post) - end - - def set_dialog(dialog) - @dialog = dialog - end - - def dialog - @dialog - end - - def set_cursor_pos(col, row) - @cursor_pos.x = col - @cursor_pos.y = row - end - - def set_key(key) - @key = key - end - - def key - @key - end - - def cursor_pos - @cursor_pos - end - - def just_cursor_moving - @line_editor.instance_variable_get(:@just_cursor_moving) - end - - def screen_width - @line_editor.screen_width - end - - def screen_height - @line_editor.screen_height - end - - def preferred_dialog_height - _wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position - [@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max - end - - def completion_journey_data - @line_editor.dialog_proc_scope_completion_journey_data - end - - def config - @config - end - - def call - instance_exec(&@proc_to_exec) - end - end - - class Dialog - attr_reader :name, :contents, :width - attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key - - def initialize(name, config, proc_scope) - @name = name - @config = config - @proc_scope = proc_scope - @width = nil - @scroll_top = 0 - @trap_key = nil - end - - def set_cursor_pos(col, row) - @proc_scope.set_cursor_pos(col, row) - end - - def width=(v) - @width = v - end - - def contents=(contents) - @contents = contents - if contents and @width.nil? - @width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max - end - end - - def call(key) - @proc_scope.set_dialog(self) - @proc_scope.set_key(key) - dialog_render_info = @proc_scope.call - if @trap_key - if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap - @trap_key.each do |t| - @config.add_oneshot_key_binding(t, @name) - end - else - @config.add_oneshot_key_binding(@trap_key, @name) - end - end - dialog_render_info - end - end - - def add_dialog_proc(name, p, context = nil) - dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context)) - if index = @dialogs.find_index { |d| d.name == name } - @dialogs[index] = dialog - else - @dialogs << dialog - end - end - - DIALOG_DEFAULT_HEIGHT = 20 - - private def dialog_range(dialog, dialog_y) - x_range = dialog.column...dialog.column + dialog.width - y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size - [x_range, y_range] - end - - private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil) - dialog.set_cursor_pos(cursor_column, cursor_row) - dialog_render_info = dialog.call(key) - if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty? - dialog.contents = nil - dialog.trap_key = nil - return - end - contents = dialog_render_info.contents - pointer = dialog.pointer - if dialog_render_info.width - dialog.width = dialog_render_info.width - else - dialog.width = contents.map { |l| calculate_width(l, true) }.max - end - height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT - height = contents.size if contents.size < height - if contents.size > height - if dialog.pointer - if dialog.pointer < 0 - dialog.scroll_top = 0 - elsif (dialog.pointer - dialog.scroll_top) >= (height - 1) - dialog.scroll_top = dialog.pointer - (height - 1) - elsif (dialog.pointer - dialog.scroll_top) < 0 - dialog.scroll_top = dialog.pointer - end - pointer = dialog.pointer - dialog.scroll_top - else - dialog.scroll_top = 0 - end - contents = contents[dialog.scroll_top, height] - end - if dialog_render_info.scrollbar and dialog_render_info.contents.size > height - bar_max_height = height * 2 - moving_distance = (dialog_render_info.contents.size - height) * 2 - position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance) - bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i - bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT - scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i - else - scrollbar_pos = nil - end - dialog.column = dialog_render_info.pos.x - dialog.width += @block_elem_width if scrollbar_pos - diff = (dialog.column + dialog.width) - screen_width - if diff > 0 - dialog.column -= diff - end - if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height - dialog.vertical_offset = dialog_render_info.pos.y + 1 - elsif cursor_row >= height - dialog.vertical_offset = dialog_render_info.pos.y - height - else - dialog.vertical_offset = dialog_render_info.pos.y + 1 - end - if dialog.column < 0 - dialog.column = 0 - dialog.width = screen_width - end - face = Reline::Face[dialog_render_info.face || :default] - scrollbar_sgr = face[:scrollbar] - default_sgr = face[:default] - enhanced_sgr = face[:enhanced] - dialog.contents = contents.map.with_index do |item, i| - line_sgr = i == pointer ? enhanced_sgr : default_sgr - str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width) - str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true) - colored_content = "#{line_sgr}#{str}" - if scrollbar_pos - if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height) - colored_content + scrollbar_sgr + @full_block - elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height) - colored_content + scrollbar_sgr + @upper_half_block - elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height) - colored_content + scrollbar_sgr + @lower_half_block - else - colored_content + scrollbar_sgr + ' ' * @block_elem_width - end - else - colored_content - end - end - end - - private def modify_lines(before, complete) - if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete) - after.lines("\n").map { |l| l.chomp('') } - else - before.map { |l| Reline::Unicode.escape_for_print(l) } - end - end - - def editing_mode - @config.editing_mode - end - - private def menu(_target, list) - @menu_info = MenuInfo.new(list) - end - - private def complete_internal_proc(list, is_menu) - preposing, target, postposing = retrieve_completion_block - candidates = list.select { |i| - if i and not Encoding.compatible?(target.encoding, i.encoding) - raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}" - end - if @config.completion_ignore_case - i&.downcase&.start_with?(target.downcase) - else - i&.start_with?(target) - end - }.uniq - if is_menu - menu(target, candidates) - return nil - end - completed = candidates.inject { |memo, item| - begin - memo_mbchars = memo.unicode_normalize.grapheme_clusters - item_mbchars = item.unicode_normalize.grapheme_clusters - rescue Encoding::CompatibilityError - memo_mbchars = memo.grapheme_clusters - item_mbchars = item.grapheme_clusters - end - size = [memo_mbchars.size, item_mbchars.size].min - result = +'' - size.times do |i| - if @config.completion_ignore_case - if memo_mbchars[i].casecmp?(item_mbchars[i]) - result << memo_mbchars[i] - else - break - end - else - if memo_mbchars[i] == item_mbchars[i] - result << memo_mbchars[i] - else - break - end - end - end - result - } - - [target, preposing, completed, postposing, candidates] - end - - private def perform_completion(list, just_show_list) - case @completion_state - when CompletionState::NORMAL - @completion_state = CompletionState::COMPLETION - when CompletionState::PERFECT_MATCH - if @dig_perfect_match_proc - @dig_perfect_match_proc.(@perfect_matched) - else - @completion_state = CompletionState::COMPLETION - end - end - if just_show_list - is_menu = true - elsif @completion_state == CompletionState::MENU - is_menu = true - elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH - is_menu = true - else - is_menu = false - end - result = complete_internal_proc(list, is_menu) - if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH - @completion_state = CompletionState::PERFECT_MATCH - end - return if result.nil? - target, preposing, completed, postposing, candidates = result - return if completed.nil? - if target <= completed and (@completion_state == CompletionState::COMPLETION) - append_character = '' - if candidates.include?(completed) - if candidates.one? - append_character = completion_append_character.to_s - @completion_state = CompletionState::PERFECT_MATCH - else - @completion_state = CompletionState::MENU_WITH_PERFECT_MATCH - perform_completion(candidates, true) if @config.show_all_if_ambiguous - end - @perfect_matched = completed - else - @completion_state = CompletionState::MENU - perform_completion(candidates, true) if @config.show_all_if_ambiguous - end - unless just_show_list - @buffer_of_lines[@line_index] = (preposing + completed + append_character + postposing).split("\n")[@line_index] || String.new(encoding: encoding) - line_to_pointer = (preposing + completed + append_character).split("\n")[@line_index] || String.new(encoding: encoding) - @byte_pointer = line_to_pointer.bytesize - end - end - end - - def dialog_proc_scope_completion_journey_data - return nil unless @completion_journey_state - line_index = @completion_journey_state.line_index - pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" } - post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" } - DialogProcScope::CompletionJourneyData.new( - pre_lines.join + @completion_journey_state.pre, - @completion_journey_state.post + post_lines.join, - @completion_journey_state.list, - @completion_journey_state.pointer - ) - end - - private def move_completed_list(direction) - @completion_journey_state ||= retrieve_completion_journey_state - return false unless @completion_journey_state - - if (delta = { up: -1, down: +1 }[direction]) - @completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size - end - completed = @completion_journey_state.list[@completion_journey_state.pointer] - set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize) - true - end - - private def retrieve_completion_journey_state - preposing, target, postposing = retrieve_completion_block - list = call_completion_proc - return unless list.is_a?(Array) - - candidates = list.select{ |item| item.start_with?(target) } - return if candidates.empty? - - pre = preposing.split("\n", -1).last || '' - post = postposing.split("\n", -1).first || '' - CompletionJourneyState.new( - @line_index, pre, target, post, [target] + candidates, 0 - ) - end - - private def run_for_operators(key, method_symbol, &block) - if @vi_waiting_operator - if VI_MOTIONS.include?(method_symbol) - old_byte_pointer = @byte_pointer - @vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg - block.(true) - unless @waiting_proc - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - method_obj = method(@vi_waiting_operator) - wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff) - cleanup_waiting - end - else - # Ignores operator when not motion is given. - block.(false) - cleanup_waiting - end - @vi_arg = nil - else - block.(false) - end - end - - private def argumentable?(method_obj) - method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg } - end - - private def inclusive?(method_obj) - # If a motion method with the keyword argument "inclusive" follows the - # operator, it must contain the character at the cursor position. - method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive } - end - - def wrap_method_call(method_symbol, method_obj, key, with_operator = false) - if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil? - not_insertion = method_symbol != :ed_insert - process_insert(force: not_insertion) - end - if @vi_arg and argumentable?(method_obj) - if with_operator and inclusive?(method_obj) - method_obj.(key, arg: @vi_arg, inclusive: true) - else - method_obj.(key, arg: @vi_arg) - end - else - if with_operator and inclusive?(method_obj) - method_obj.(key, inclusive: true) - else - method_obj.(key) - end - end - end - - private def cleanup_waiting - @waiting_proc = nil - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - @searching_prompt = nil - @drop_terminate_spaces = false - end - - private def process_key(key, method_symbol) - if key.is_a?(Symbol) - cleanup_waiting - elsif @waiting_proc - old_byte_pointer = @byte_pointer - @waiting_proc.call(key) - if @vi_waiting_operator - byte_pointer_diff = @byte_pointer - old_byte_pointer - @byte_pointer = old_byte_pointer - method_obj = method(@vi_waiting_operator) - wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff) - cleanup_waiting - end - @kill_ring.process - return - end - - if method_symbol and respond_to?(method_symbol, true) - method_obj = method(method_symbol) - end - if method_symbol and key.is_a?(Symbol) - if @vi_arg and argumentable?(method_obj) - run_for_operators(key, method_symbol) do |with_operator| - wrap_method_call(method_symbol, method_obj, key, with_operator) - end - else - wrap_method_call(method_symbol, method_obj, key) if method_obj - end - @kill_ring.process - if @vi_arg - @vi_arg = nil - end - elsif @vi_arg - if key.chr =~ /[0-9]/ - ed_argument_digit(key) - else - if argumentable?(method_obj) - run_for_operators(key, method_symbol) do |with_operator| - wrap_method_call(method_symbol, method_obj, key, with_operator) - end - elsif method_obj - wrap_method_call(method_symbol, method_obj, key) - else - ed_insert(key) unless @config.editing_mode_is?(:vi_command) - end - @kill_ring.process - if @vi_arg - @vi_arg = nil - end - end - elsif method_obj - if method_symbol == :ed_argument_digit - wrap_method_call(method_symbol, method_obj, key) - else - run_for_operators(key, method_symbol) do |with_operator| - wrap_method_call(method_symbol, method_obj, key, with_operator) - end - end - @kill_ring.process - else - ed_insert(key) unless @config.editing_mode_is?(:vi_command) - end - end - - private def normal_char(key) - @multibyte_buffer << key.combined_char - if @multibyte_buffer.size > 1 - if @multibyte_buffer.dup.force_encoding(encoding).valid_encoding? - process_key(@multibyte_buffer.dup.force_encoding(encoding), nil) - @multibyte_buffer.clear - else - # invalid - return - end - else # single byte - return if key.char >= 128 # maybe, first byte of multi byte - method_symbol = @config.editing_mode.get_method(key.combined_char) - process_key(key.combined_char, method_symbol) - @multibyte_buffer.clear - end - if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize - byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer) - @byte_pointer -= byte_size - end - end - - def update(key) - modified = input_key(key) - unless @in_pasting - scroll_into_view - @just_cursor_moving = !modified - update_dialogs(key) - @just_cursor_moving = false - end - end - - def input_key(key) - save_old_buffer - @config.reset_oneshot_key_bindings - @dialogs.each do |dialog| - if key.char.instance_of?(Symbol) and key.char == dialog.name - return - end - end - if key.char.nil? - process_insert(force: true) - @eof = buffer_empty? - finish - return - end - @completion_occurs = false - - if key.char.is_a?(Symbol) - process_key(key.char, key.char) - else - normal_char(key) - end - - @prev_action_state, @next_action_state = @next_action_state, NullActionState - - unless @completion_occurs - @completion_state = CompletionState::NORMAL - @completion_journey_state = nil - end - - push_input_lines unless @undoing - @undoing = false - - if @in_pasting - clear_dialogs - return - end - - modified = @old_buffer_of_lines != @buffer_of_lines - if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion - # Auto complete starts only when edited - process_insert(force: true) - @completion_journey_state = retrieve_completion_journey_state - end - modified - end - - def save_old_buffer - @old_buffer_of_lines = @buffer_of_lines.dup - end - - def push_input_lines - if @old_buffer_of_lines == @buffer_of_lines - @input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index] - else - @input_lines = @input_lines[0..@input_lines_position] - @input_lines_position += 1 - @input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index]) - end - trim_input_lines - end - - MAX_INPUT_LINES = 100 - def trim_input_lines - if @input_lines.size > MAX_INPUT_LINES - @input_lines.shift - @input_lines_position -= 1 - end - end - - def scroll_into_view - _wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position - if wrapped_cursor_y < screen_scroll_top - @scroll_partial_screen = wrapped_cursor_y - end - if wrapped_cursor_y >= screen_scroll_top + screen_height - @scroll_partial_screen = wrapped_cursor_y - screen_height + 1 - end - end - - def call_completion_proc - result = retrieve_completion_block(true) - pre, target, post = result - result = call_completion_proc_with_checking_args(pre, target, post) - Reline.core.instance_variable_set(:@completion_quote_character, nil) - result - end - - def call_completion_proc_with_checking_args(pre, target, post) - if @completion_proc and target - argnum = @completion_proc.parameters.inject(0) { |result, item| - case item.first - when :req, :opt - result + 1 - when :rest - break 3 - end - } - case argnum - when 1 - result = @completion_proc.(target) - when 2 - result = @completion_proc.(target, pre) - when 3..Float::INFINITY - result = @completion_proc.(target, pre, post) - end - end - result - end - - private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false) - return if @in_pasting - return unless @auto_indent_proc - - line = @buffer_of_lines[line_index] - byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize - new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline) - return unless new_indent - - new_line = ' ' * new_indent + line.lstrip - @buffer_of_lines[line_index] = new_line - if @line_index == line_index - indent_diff = new_line.bytesize - line.bytesize - @byte_pointer = [@byte_pointer + indent_diff, 0].max - end - end - - def line() - @buffer_of_lines.join("\n") unless eof? - end - - def current_line - @buffer_of_lines[@line_index] - end - - def set_current_line(line, byte_pointer = nil) - cursor = current_byte_pointer_cursor - @buffer_of_lines[@line_index] = line - if byte_pointer - @byte_pointer = byte_pointer - else - calculate_nearest_cursor(cursor) - end - process_auto_indent - end - - def set_current_lines(lines, byte_pointer = nil, line_index = 0) - cursor = current_byte_pointer_cursor - @buffer_of_lines = lines - @line_index = line_index - if byte_pointer - @byte_pointer = byte_pointer - else - calculate_nearest_cursor(cursor) - end - process_auto_indent - end - - def retrieve_completion_block(set_completion_quote_character = false) - if Reline.completer_word_break_characters.empty? - word_break_regexp = nil - else - word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/ - end - if Reline.completer_quote_characters.empty? - quote_characters_regexp = nil - else - quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/ - end - before = current_line.byteslice(0, @byte_pointer) - rest = nil - break_pointer = nil - quote = nil - closing_quote = nil - escaped_quote = nil - i = 0 - while i < @byte_pointer do - slice = current_line.byteslice(i, @byte_pointer - i) - unless slice.valid_encoding? - i += 1 - next - end - if quote and slice.start_with?(closing_quote) - quote = nil - i += 1 - rest = nil - elsif quote and slice.start_with?(escaped_quote) - # skip - i += 2 - elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new " - rest = $' - quote = $& - closing_quote = /(?!\\)#{Regexp.escape(quote)}/ - escaped_quote = /\\#{Regexp.escape(quote)}/ - i += 1 - break_pointer = i - 1 - elsif word_break_regexp and not quote and slice =~ word_break_regexp - rest = $' - i += 1 - before = current_line.byteslice(i, @byte_pointer - i) - break_pointer = i - else - i += 1 - end - end - postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) - if rest - preposing = current_line.byteslice(0, break_pointer) - target = rest - if set_completion_quote_character and quote - Reline.core.instance_variable_set(:@completion_quote_character, quote) - if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote - insert_text(quote) - end - end - else - preposing = '' - if break_pointer - preposing = current_line.byteslice(0, break_pointer) - else - preposing = '' - end - target = before - end - lines = whole_lines - if @line_index > 0 - preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing - end - if (lines.size - 1) > @line_index - postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n") - end - [preposing.encode(encoding), target.encode(encoding), postposing.encode(encoding)] - end - - def confirm_multiline_termination - temp_buffer = @buffer_of_lines.dup - @confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n") - end - - def insert_multiline_text(text) - save_old_buffer - pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer) - post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..) - lines = (pre + Reline::Unicode.safe_encode(text, encoding).gsub(/\r\n?/, "\n") + post).split("\n", -1) - lines << '' if lines.empty? - @buffer_of_lines[@line_index, 1] = lines - @line_index += lines.size - 1 - @byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize - push_input_lines - end - - def insert_text(text) - if @buffer_of_lines[@line_index].bytesize == @byte_pointer - @buffer_of_lines[@line_index] += text - else - @buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text) - end - @byte_pointer += text.bytesize - process_auto_indent - end - - def delete_text(start = nil, length = nil) - if start.nil? and length.nil? - if @buffer_of_lines.size == 1 - @buffer_of_lines[@line_index] = '' - @byte_pointer = 0 - elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0 - @buffer_of_lines.pop - @line_index -= 1 - @byte_pointer = 0 - elsif @line_index < (@buffer_of_lines.size - 1) - @buffer_of_lines.delete_at(@line_index) - @byte_pointer = 0 - end - elsif not start.nil? and not length.nil? - if current_line - before = current_line.byteslice(0, start) - after = current_line.byteslice(start + length, current_line.bytesize) - set_current_line(before + after) - end - elsif start.is_a?(Range) - range = start - first = range.first - last = range.last - last = current_line.bytesize - 1 if last > current_line.bytesize - last += current_line.bytesize if last < 0 - first += current_line.bytesize if first < 0 - range = range.exclude_end? ? first...last : first..last - line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(encoding) - set_current_line(line) - else - set_current_line(current_line.byteslice(0, start)) - end - end - - def byte_pointer=(val) - @byte_pointer = val - end - - def whole_lines - @buffer_of_lines.dup - end - - def whole_buffer - whole_lines.join("\n") - end - - private def buffer_empty? - current_line.empty? and @buffer_of_lines.size == 1 - end - - def finished? - @finished - end - - def finish - @finished = true - @config.reset - end - - private def byteslice!(str, byte_pointer, size) - new_str = str.byteslice(0, byte_pointer) - new_str << str.byteslice(byte_pointer + size, str.bytesize) - [new_str, str.byteslice(byte_pointer, size)] - end - - private def byteinsert(str, byte_pointer, other) - new_str = str.byteslice(0, byte_pointer) - new_str << other - new_str << str.byteslice(byte_pointer, str.bytesize) - new_str - end - - private def calculate_width(str, allow_escape_code = false) - Reline::Unicode.calculate_width(str, allow_escape_code) - end - - private def key_delete(key) - if @config.editing_mode_is?(:vi_insert) - ed_delete_next_char(key) - elsif @config.editing_mode_is?(:emacs) - em_delete(key) - end - end - - private def key_newline(key) - if @is_multiline - next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer) - cursor_line = current_line.byteslice(0, @byte_pointer) - insert_new_line(cursor_line, next_line) - end - end - - private def complete(_key) - return if @config.disable_completion - - process_insert(force: true) - if @config.autocompletion - @completion_state = CompletionState::NORMAL - @completion_occurs = move_completed_list(:down) - else - @completion_journey_state = nil - result = call_completion_proc - if result.is_a?(Array) - @completion_occurs = true - perform_completion(result, false) - end - end - end - - private def completion_journey_move(direction) - return if @config.disable_completion - - process_insert(force: true) - @completion_state = CompletionState::NORMAL - @completion_occurs = move_completed_list(direction) - end - - private def menu_complete(_key) - completion_journey_move(:down) - end - - private def menu_complete_backward(_key) - completion_journey_move(:up) - end - - private def completion_journey_up(_key) - completion_journey_move(:up) if @config.autocompletion - end - - # Editline:: +ed-unassigned+ This editor command always results in an error. - # GNU Readline:: There is no corresponding macro. - private def ed_unassigned(key) end # do nothing - - private def process_insert(force: false) - return if @continuous_insertion_buffer.empty? or (@in_pasting and not force) - insert_text(@continuous_insertion_buffer) - @continuous_insertion_buffer.clear - end - - # Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters) - # In insert mode, insert the input character left of the cursor - # position. In replace mode, overwrite the character at the - # cursor and move the cursor to the right by one character - # position. Accept an argument to do this repeatedly. It is an - # error if the input character is the NUL character (+Ctrl-@+). - # Failure to enlarge the edit buffer also results in an error. - # Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append - # the input digit to the argument being read. Otherwise, call - # +ed-insert+. It is an error if the input character is not a - # digit or if the existing argument is already greater than a - # million. - # GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself. - private def ed_insert(key) - if key.instance_of?(String) - begin - key.encode(Encoding::UTF_8) - rescue Encoding::UndefinedConversionError - return - end - str = key - else - begin - key.chr.encode(Encoding::UTF_8) - rescue Encoding::UndefinedConversionError - return - end - str = key.chr - end - if @in_pasting - @continuous_insertion_buffer << str - return - elsif not @continuous_insertion_buffer.empty? - process_insert - end - - insert_text(str) - end - alias_method :ed_digit, :ed_insert - alias_method :self_insert, :ed_insert - - private def ed_quoted_insert(str, arg: 1) - @waiting_proc = proc { |key| - arg.times do - if key == "\C-j".ord or key == "\C-m".ord - key_newline(key) - elsif key == 0 - # Ignore NUL. - else - ed_insert(key) - end - end - @waiting_proc = nil - } - end - alias_method :quoted_insert, :ed_quoted_insert - - private def ed_next_char(key, arg: 1) - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if (@byte_pointer < current_line.bytesize) - @byte_pointer += byte_size - elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1 - @byte_pointer = 0 - @line_index += 1 - end - arg -= 1 - ed_next_char(key, arg: arg) if arg > 0 - end - alias_method :forward_char, :ed_next_char - - private def ed_prev_char(key, arg: 1) - if @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - @byte_pointer -= byte_size - elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0 - @line_index -= 1 - @byte_pointer = current_line.bytesize - end - arg -= 1 - ed_prev_char(key, arg: arg) if arg > 0 - end - alias_method :backward_char, :ed_prev_char - - private def vi_first_print(key) - @byte_pointer, = Reline::Unicode.vi_first_print(current_line) - end - - private def ed_move_to_beg(key) - @byte_pointer = 0 - end - alias_method :beginning_of_line, :ed_move_to_beg - alias_method :vi_zero, :ed_move_to_beg - - private def ed_move_to_end(key) - @byte_pointer = current_line.bytesize - end - alias_method :end_of_line, :ed_move_to_end - - private def generate_searcher(search_key) - search_word = String.new(encoding: encoding) - multibyte_buf = String.new(encoding: 'ASCII-8BIT') - hit_pointer = nil - lambda do |key| - search_again = false - case key - when "\C-h".ord, "\C-?".ord - grapheme_clusters = search_word.grapheme_clusters - if grapheme_clusters.size > 0 - grapheme_clusters.pop - search_word = grapheme_clusters.join - end - when "\C-r".ord, "\C-s".ord - search_again = true if search_key == key - search_key = key - else - multibyte_buf << key - if multibyte_buf.dup.force_encoding(encoding).valid_encoding? - search_word << multibyte_buf.dup.force_encoding(encoding) - multibyte_buf.clear - end - end - hit = nil - if not search_word.empty? and @line_backup_in_history&.include?(search_word) - hit_pointer = Reline::HISTORY.size - hit = @line_backup_in_history - else - if search_again - if search_word.empty? and Reline.last_incremental_search - search_word = Reline.last_incremental_search - end - if @history_pointer - case search_key - when "\C-r".ord - history_pointer_base = 0 - history = Reline::HISTORY[0..(@history_pointer - 1)] - when "\C-s".ord - history_pointer_base = @history_pointer + 1 - history = Reline::HISTORY[(@history_pointer + 1)..-1] - end - else - history_pointer_base = 0 - history = Reline::HISTORY - end - elsif @history_pointer - case search_key - when "\C-r".ord - history_pointer_base = 0 - history = Reline::HISTORY[0..@history_pointer] - when "\C-s".ord - history_pointer_base = @history_pointer - history = Reline::HISTORY[@history_pointer..-1] - end - else - history_pointer_base = 0 - history = Reline::HISTORY - end - case search_key - when "\C-r".ord - hit_index = history.rindex { |item| - item.include?(search_word) - } - when "\C-s".ord - hit_index = history.index { |item| - item.include?(search_word) - } - end - if hit_index - hit_pointer = history_pointer_base + hit_index - hit = Reline::HISTORY[hit_pointer] - end - end - case search_key - when "\C-r".ord - prompt_name = 'reverse-i-search' - when "\C-s".ord - prompt_name = 'i-search' - end - prompt_name = "failed #{prompt_name}" unless hit - [search_word, prompt_name, hit_pointer] - end - end - - private def incremental_search_history(key) - backup = @buffer_of_lines.dup, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history - searcher = generate_searcher(key) - @searching_prompt = "(reverse-i-search)`': " - termination_keys = ["\C-j".ord] - termination_keys.concat(@config.isearch_terminators.chars.map(&:ord)) if @config.isearch_terminators - @waiting_proc = ->(k) { - chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT) - if k == "\C-g".ord - # cancel search and restore buffer - @buffer_of_lines, @line_index, @byte_pointer, @history_pointer, @line_backup_in_history = backup - @searching_prompt = nil - @waiting_proc = nil - elsif !termination_keys.include?(k) && (chr.match?(/[[:print:]]/) || k == "\C-h".ord || k == "\C-?".ord || k == "\C-r".ord || k == "\C-s".ord) - search_word, prompt_name, hit_pointer = searcher.call(k) - Reline.last_incremental_search = search_word - @searching_prompt = "(%s)`%s'" % [prompt_name, search_word] - @searching_prompt += ': ' unless @is_multiline - move_history(hit_pointer, line: :end, cursor: :end) if hit_pointer - else - # terminaton_keys and other keys will terminalte - move_history(@history_pointer, line: :end, cursor: :start) - @searching_prompt = nil - @waiting_proc = nil - end - } - end - - private def vi_search_prev(key) - incremental_search_history(key) - end - alias_method :reverse_search_history, :vi_search_prev - - private def vi_search_next(key) - incremental_search_history(key) - end - alias_method :forward_search_history, :vi_search_next - - private def search_history(prefix, pointer_range) - pointer_range.each do |pointer| - lines = Reline::HISTORY[pointer].split("\n") - lines.each_with_index do |line, index| - return [pointer, index] if line.start_with?(prefix) - end - end - nil - end - - private def ed_search_prev_history(key, arg: 1) - substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer) - return if @history_pointer == 0 - return if @history_pointer.nil? && substr.empty? && !current_line.empty? - - history_range = 0...(@history_pointer || Reline::HISTORY.size) - h_pointer, line_index = search_history(substr, history_range.reverse_each) - return unless h_pointer - move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer) - arg -= 1 - set_next_action_state(:search_history, :empty) if substr.empty? - ed_search_prev_history(key, arg: arg) if arg > 0 - end - alias_method :history_search_backward, :ed_search_prev_history - - private def ed_search_next_history(key, arg: 1) - substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer) - return if @history_pointer.nil? - - history_range = @history_pointer + 1...Reline::HISTORY.size - h_pointer, line_index = search_history(substr, history_range) - return if h_pointer.nil? and not substr.empty? - - move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer) - arg -= 1 - set_next_action_state(:search_history, :empty) if substr.empty? - ed_search_next_history(key, arg: arg) if arg > 0 - end - alias_method :history_search_forward, :ed_search_next_history - - private def move_history(history_pointer, line:, cursor:) - history_pointer ||= Reline::HISTORY.size - return if history_pointer < 0 || history_pointer > Reline::HISTORY.size - old_history_pointer = @history_pointer || Reline::HISTORY.size - if old_history_pointer == Reline::HISTORY.size - @line_backup_in_history = whole_buffer - else - Reline::HISTORY[old_history_pointer] = whole_buffer - end - if history_pointer == Reline::HISTORY.size - buf = @line_backup_in_history - @history_pointer = @line_backup_in_history = nil - else - buf = Reline::HISTORY[history_pointer] - @history_pointer = history_pointer - end - @buffer_of_lines = buf.split("\n") - @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty? - @line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line - @byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor - end - - private def ed_prev_history(key, arg: 1) - if @line_index > 0 - cursor = current_byte_pointer_cursor - @line_index -= 1 - calculate_nearest_cursor(cursor) - return - end - move_history( - (@history_pointer || Reline::HISTORY.size) - 1, - line: :end, - cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, - ) - arg -= 1 - ed_prev_history(key, arg: arg) if arg > 0 - end - alias_method :previous_history, :ed_prev_history - - private def ed_next_history(key, arg: 1) - if @line_index < (@buffer_of_lines.size - 1) - cursor = current_byte_pointer_cursor - @line_index += 1 - calculate_nearest_cursor(cursor) - return - end - move_history( - (@history_pointer || Reline::HISTORY.size) + 1, - line: :start, - cursor: @config.editing_mode_is?(:vi_command) ? :start : :end, - ) - arg -= 1 - ed_next_history(key, arg: arg) if arg > 0 - end - alias_method :next_history, :ed_next_history - - private def ed_newline(key) - process_insert(force: true) - if @is_multiline - if @config.editing_mode_is?(:vi_command) - if @line_index < (@buffer_of_lines.size - 1) - ed_next_history(key) # means cursor down - else - # should check confirm_multiline_termination to finish? - finish - end - else - if @line_index == (@buffer_of_lines.size - 1) - if confirm_multiline_termination - finish - else - key_newline(key) - end - else - # should check confirm_multiline_termination to finish? - @line_index = @buffer_of_lines.size - 1 - @byte_pointer = current_line.bytesize - finish - end - end - else - finish - end - end - - private def em_delete_prev_char(key, arg: 1) - arg.times do - if @byte_pointer == 0 and @line_index > 0 - @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize - @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) - @line_index -= 1 - elsif @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size) - set_current_line(line, @byte_pointer - byte_size) - end - end - process_auto_indent - end - alias_method :backward_delete_char, :em_delete_prev_char - - # Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+, - # +Ctrl-U+) + Kill from the cursor to the end of the line. - # GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of - # the line. With a negative numeric argument, kill backward - # from the cursor to the beginning of the current line. - private def ed_kill_line(key) - if current_line.bytesize > @byte_pointer - line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer) - set_current_line(line, line.bytesize) - @kill_ring.append(deleted) - elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 - set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) - end - end - alias_method :kill_line, :ed_kill_line - - # Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line. - private def vi_change_to_eol(key) - ed_kill_line(key) - - @config.editing_mode = :vi_insert - end - - # Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the - # beginning of the edit buffer to the cursor and save it to the - # cut buffer. - # GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor - # to the beginning of the current line. - private def vi_kill_line_prev(key) - if @byte_pointer > 0 - line, deleted = byteslice!(current_line, 0, @byte_pointer) - set_current_line(line, 0) - @kill_ring.append(deleted, true) - end - end - alias_method :unix_line_discard, :vi_kill_line_prev - - # Editline:: +em-kill-line+ (not bound) Delete the entire contents of the - # edit buffer and save it to the cut buffer. +vi-kill-line-prev+ - # GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the - # current line, no matter where point is. - private def em_kill_line(key) - if current_line.size > 0 - @kill_ring.append(current_line.dup, true) - set_current_line('', 0) - end - end - alias_method :kill_whole_line, :em_kill_line - - private def em_delete(key) - if buffer_empty? and key == "\C-d".ord - @eof = true - finish - elsif @byte_pointer < current_line.bytesize - splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize) - mbchar = splitted_last.grapheme_clusters.first - line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize) - set_current_line(line) - elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1 - set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize) - end - end - alias_method :delete_char, :em_delete - - private def em_delete_or_list(key) - if current_line.empty? or @byte_pointer < current_line.bytesize - em_delete(key) - elsif !@config.autocompletion # show completed list - result = call_completion_proc - if result.is_a?(Array) - perform_completion(result, true) - end - end - end - alias_method :delete_char_or_list, :em_delete_or_list - - private def em_yank(key) - yanked = @kill_ring.yank - insert_text(yanked) if yanked - end - alias_method :yank, :em_yank - - private def em_yank_pop(key) - yanked, prev_yank = @kill_ring.yank_pop - if yanked - line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize) - set_current_line(line, @byte_pointer - prev_yank.bytesize) - insert_text(yanked) - end - end - alias_method :yank_pop, :em_yank_pop - - private def ed_clear_screen(key) - Reline::IOGate.clear_screen - @screen_size = Reline::IOGate.get_screen_size - @rendered_screen.base_y = 0 - clear_rendered_screen_cache - end - alias_method :clear_screen, :ed_clear_screen - - private def em_next_word(key) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - end - alias_method :forward_word, :em_next_word - - private def ed_prev_word(key) - if @byte_pointer > 0 - byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - end - end - alias_method :backward_word, :ed_prev_word - - private def em_delete_next_word(key) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer) - line, word = byteslice!(current_line, @byte_pointer, byte_size) - set_current_line(line) - @kill_ring.append(word) - end - end - alias_method :kill_word, :em_delete_next_word - - private def ed_delete_prev_word(key) - if @byte_pointer > 0 - byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer) - line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size) - set_current_line(line, @byte_pointer - byte_size) - @kill_ring.append(word, true) - end - end - alias_method :backward_kill_word, :ed_delete_prev_word - - private def ed_transpose_chars(key) - if @byte_pointer > 0 - if @byte_pointer < current_line.bytesize - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - @byte_pointer += byte_size - end - back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - if (@byte_pointer - back1_byte_size) > 0 - back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size) - back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size - line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size) - set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar)) - end - end - end - alias_method :transpose_chars, :ed_transpose_chars - - private def ed_transpose_words(key) - left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer) - before = current_line.byteslice(0, left_word_start) - left_word = current_line.byteslice(left_word_start, middle_start - left_word_start) - middle = current_line.byteslice(middle_start, right_word_start - middle_start) - right_word = current_line.byteslice(right_word_start, after_start - right_word_start) - after = current_line.byteslice(after_start, current_line.bytesize - after_start) - return if left_word.empty? or right_word.empty? - from_head_to_left_word = before + right_word + middle + left_word - set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize) - end - alias_method :transpose_words, :ed_transpose_words - - private def em_capitol_case(key) - if current_line.bytesize > @byte_pointer - byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer) - before = current_line.byteslice(0, @byte_pointer) - after = current_line.byteslice((@byte_pointer + byte_size)..-1) - set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize) - end - end - alias_method :capitalize_word, :em_capitol_case - - private def em_lower_case(key) - if current_line.bytesize > @byte_pointer - byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer) - part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| - mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar - }.join - rest = current_line.byteslice((@byte_pointer + byte_size)..-1) - line = current_line.byteslice(0, @byte_pointer) + part - set_current_line(line + rest, line.bytesize) - end - end - alias_method :downcase_word, :em_lower_case - - private def em_upper_case(key) - if current_line.bytesize > @byte_pointer - byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer) - part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar| - mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar - }.join - rest = current_line.byteslice((@byte_pointer + byte_size)..-1) - line = current_line.byteslice(0, @byte_pointer) + part - set_current_line(line + rest, line.bytesize) - end - end - alias_method :upcase_word, :em_upper_case - - private def em_kill_region(key) - if @byte_pointer > 0 - byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer) - line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size) - set_current_line(line, @byte_pointer - byte_size) - @kill_ring.append(deleted, true) - end - end - alias_method :unix_word_rubout, :em_kill_region - - private def copy_for_vi(text) - if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command) - @vi_clipboard = text - end - end - - private def vi_insert(key) - @config.editing_mode = :vi_insert - end - - private def vi_add(key) - @config.editing_mode = :vi_insert - ed_next_char(key) - end - - private def vi_command_mode(key) - ed_prev_char(key) - @config.editing_mode = :vi_command - end - alias_method :vi_movement_mode, :vi_command_mode - - private def vi_next_word(key, arg: 1) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces) - @byte_pointer += byte_size - end - arg -= 1 - vi_next_word(key, arg: arg) if arg > 0 - end - - private def vi_prev_word(key, arg: 1) - if @byte_pointer > 0 - byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - end - arg -= 1 - vi_prev_word(key, arg: arg) if arg > 0 - end - - private def vi_end_word(key, arg: 1, inclusive: false) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - vi_end_word(key, arg: arg) if arg > 0 - end - - private def vi_next_big_word(key, arg: 1) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - vi_next_big_word(key, arg: arg) if arg > 0 - end - - private def vi_prev_big_word(key, arg: 1) - if @byte_pointer > 0 - byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer) - @byte_pointer -= byte_size - end - arg -= 1 - vi_prev_big_word(key, arg: arg) if arg > 0 - end - - private def vi_end_big_word(key, arg: 1, inclusive: false) - if current_line.bytesize > @byte_pointer - byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer) - @byte_pointer += byte_size - end - arg -= 1 - if inclusive and arg.zero? - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - vi_end_big_word(key, arg: arg) if arg > 0 - end - - private def vi_delete_prev_char(key) - if @byte_pointer == 0 and @line_index > 0 - @byte_pointer = @buffer_of_lines[@line_index - 1].bytesize - @buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index) - @line_index -= 1 - process_auto_indent cursor_dependent: false - elsif @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - @byte_pointer -= byte_size - line, _ = byteslice!(current_line, @byte_pointer, byte_size) - set_current_line(line) - end - end - - private def vi_insert_at_bol(key) - ed_move_to_beg(key) - @config.editing_mode = :vi_insert - end - - private def vi_add_at_eol(key) - ed_move_to_end(key) - @config.editing_mode = :vi_insert - end - - private def ed_delete_prev_char(key, arg: 1) - deleted = +'' - arg.times do - if @byte_pointer > 0 - byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer) - @byte_pointer -= byte_size - line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) - set_current_line(line) - deleted.prepend(mbchar) - end - end - copy_for_vi(deleted) - end - - private def vi_change_meta(key, arg: nil) - if @vi_waiting_operator - set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil? - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - else - @drop_terminate_spaces = true - @vi_waiting_operator = :vi_change_meta_confirm - @vi_waiting_operator_arg = arg || 1 - end - end - - private def vi_change_meta_confirm(byte_pointer_diff) - vi_delete_meta_confirm(byte_pointer_diff) - @config.editing_mode = :vi_insert - @drop_terminate_spaces = false - end - - private def vi_delete_meta(key, arg: nil) - if @vi_waiting_operator - set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil? - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - else - @vi_waiting_operator = :vi_delete_meta_confirm - @vi_waiting_operator_arg = arg || 1 - end - end - - private def vi_delete_meta_confirm(byte_pointer_diff) - if byte_pointer_diff > 0 - line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff) - else - return - end - copy_for_vi(cut) - set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0)) - end - - private def vi_yank(key, arg: nil) - if @vi_waiting_operator - copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil? - @vi_waiting_operator = nil - @vi_waiting_operator_arg = nil - else - @vi_waiting_operator = :vi_yank_confirm - @vi_waiting_operator_arg = arg || 1 - end - end - - private def vi_yank_confirm(byte_pointer_diff) - if byte_pointer_diff > 0 - cut = current_line.byteslice(@byte_pointer, byte_pointer_diff) - elsif byte_pointer_diff < 0 - cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff) - else - return - end - copy_for_vi(cut) - end - - private def vi_list_or_eof(key) - if buffer_empty? - @eof = true - finish - else - ed_newline(key) - end - end - alias_method :vi_end_of_transmission, :vi_list_or_eof - alias_method :vi_eof_maybe, :vi_list_or_eof - - private def ed_delete_next_char(key, arg: 1) - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - unless current_line.empty? || byte_size == 0 - line, mbchar = byteslice!(current_line, @byte_pointer, byte_size) - copy_for_vi(mbchar) - if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size - byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer) - set_current_line(line, @byte_pointer - byte_size) - else - set_current_line(line, @byte_pointer) - end - end - arg -= 1 - ed_delete_next_char(key, arg: arg) if arg > 0 - end - - private def vi_to_history_line(key) - if Reline::HISTORY.empty? - return - end - move_history(0, line: :start, cursor: :start) - end - - private def vi_histedit(key) - path = Tempfile.open { |fp| - fp.write whole_lines.join("\n") - fp.path - } - system("#{ENV['EDITOR']} #{path}") - @buffer_of_lines = File.read(path).split("\n") - @buffer_of_lines = [String.new(encoding: encoding)] if @buffer_of_lines.empty? - @line_index = 0 - finish - end - - private def vi_paste_prev(key, arg: 1) - if @vi_clipboard.size > 0 - cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join - set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize) - end - arg -= 1 - vi_paste_prev(key, arg: arg) if arg > 0 - end - - private def vi_paste_next(key, arg: 1) - if @vi_clipboard.size > 0 - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard) - set_current_line(line, @byte_pointer + @vi_clipboard.bytesize) - end - arg -= 1 - vi_paste_next(key, arg: arg) if arg > 0 - end - - private def ed_argument_digit(key) - if @vi_arg.nil? - if key.chr.to_i.zero? - if key.anybits?(0b10000000) - unescaped_key = key ^ 0b10000000 - unless unescaped_key.chr.to_i.zero? - @vi_arg = unescaped_key.chr.to_i - end - end - else - @vi_arg = key.chr.to_i - end - else - @vi_arg = @vi_arg * 10 + key.chr.to_i - end - end - - private def vi_to_column(key, arg: 0) - # Implementing behavior of vi, not Readline's vi-mode. - @byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc| - mbchar_width = Reline::Unicode.get_mbchar_width(gc) - break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg - [total_byte_size + gc.bytesize, total_width + mbchar_width] - } - end - - private def vi_replace_char(key, arg: 1) - @waiting_proc = ->(k) { - if arg == 1 - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - before = current_line.byteslice(0, @byte_pointer) - remaining_point = @byte_pointer + byte_size - after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) - set_current_line(before + k.chr + after) - @waiting_proc = nil - elsif arg > 1 - byte_size = 0 - arg.times do - byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size) - end - before = current_line.byteslice(0, @byte_pointer) - remaining_point = @byte_pointer + byte_size - after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point) - replaced = k.chr * arg - set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize) - @waiting_proc = nil - end - } - end - - private def vi_next_char(key, arg: 1, inclusive: false) - @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) } - end - - private def vi_to_next_char(key, arg: 1, inclusive: false) - @waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) } - end - - private def search_next_char(key, arg, need_prev_char: false, inclusive: false) - if key.instance_of?(String) - inputted_char = key - else - inputted_char = key.chr - end - prev_total = nil - total = nil - found = false - current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar| - # total has [byte_size, cursor] - unless total - # skip cursor point - width = Reline::Unicode.get_mbchar_width(mbchar) - total = [mbchar.bytesize, width] - else - if inputted_char == mbchar - arg -= 1 - if arg.zero? - found = true - break - end - end - width = Reline::Unicode.get_mbchar_width(mbchar) - prev_total = total - total = [total.first + mbchar.bytesize, total.last + width] - end - end - if not need_prev_char and found and total - byte_size, _ = total - @byte_pointer += byte_size - elsif need_prev_char and found and prev_total - byte_size, _ = prev_total - @byte_pointer += byte_size - end - if inclusive - byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer) - if byte_size > 0 - @byte_pointer += byte_size - end - end - @waiting_proc = nil - end - - private def vi_prev_char(key, arg: 1) - @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) } - end - - private def vi_to_prev_char(key, arg: 1) - @waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) } - end - - private def search_prev_char(key, arg, need_next_char = false) - if key.instance_of?(String) - inputted_char = key - else - inputted_char = key.chr - end - prev_total = nil - total = nil - found = false - current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar| - # total has [byte_size, cursor] - unless total - # skip cursor point - width = Reline::Unicode.get_mbchar_width(mbchar) - total = [mbchar.bytesize, width] - else - if inputted_char == mbchar - arg -= 1 - if arg.zero? - found = true - break - end - end - width = Reline::Unicode.get_mbchar_width(mbchar) - prev_total = total - total = [total.first + mbchar.bytesize, total.last + width] - end - end - if not need_next_char and found and total - byte_size, _ = total - @byte_pointer -= byte_size - elsif need_next_char and found and prev_total - byte_size, _ = prev_total - @byte_pointer -= byte_size - end - @waiting_proc = nil - end - - private def vi_join_lines(key, arg: 1) - if @buffer_of_lines.size > @line_index + 1 - next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip - set_current_line(current_line + ' ' + next_line, current_line.bytesize) - end - arg -= 1 - vi_join_lines(key, arg: arg) if arg > 0 - end - - private def em_set_mark(key) - @mark_pointer = [@byte_pointer, @line_index] - end - alias_method :set_mark, :em_set_mark - - private def em_exchange_mark(key) - return unless @mark_pointer - new_pointer = [@byte_pointer, @line_index] - @byte_pointer, @line_index = @mark_pointer - @mark_pointer = new_pointer - end - alias_method :exchange_point_and_mark, :em_exchange_mark - - private def emacs_editing_mode(key) - @config.editing_mode = :emacs - end - - private def vi_editing_mode(key) - @config.editing_mode = :vi_insert - end - - private def undo(_key) - @undoing = true - - return if @input_lines_position <= 0 - - @input_lines_position -= 1 - target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position] - set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y) - end - - private def redo(_key) - @undoing = true - - return if @input_lines_position >= @input_lines.size - 1 - - @input_lines_position += 1 - target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position] - set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y) - end - - private def prev_action_state_value(type) - @prev_action_state[0] == type ? @prev_action_state[1] : nil - end - - private def set_next_action_state(type, value) - @next_action_state = [type, value] - end - - private def re_read_init_file(_key) - @config.reload - end -end diff --git a/lib/reline/reline.gemspec b/lib/reline/reline.gemspec deleted file mode 100644 index dfaf966728..0000000000 --- a/lib/reline/reline.gemspec +++ /dev/null @@ -1,30 +0,0 @@ - -begin - require_relative 'lib/reline/version' -rescue LoadError - require_relative 'version' -end - -Gem::Specification.new do |spec| - spec.name = 'reline' - spec.version = Reline::VERSION - spec.authors = ['aycabta'] - spec.email = ['aycabta@gmail.com'] - - spec.summary = %q{Alternative GNU Readline or Editline implementation by pure Ruby.} - spec.description = %q{Alternative GNU Readline or Editline implementation by pure Ruby.} - spec.homepage = 'https://github.com/ruby/reline' - spec.license = 'Ruby' - - spec.files = Dir['BSDL', 'COPYING', 'README.md', 'license_of_rb-readline', 'lib/**/*'] - spec.require_paths = ['lib'] - spec.metadata = { - "bug_tracker_uri" => "https://github.com/ruby/reline/issues", - "changelog_uri" => "https://github.com/ruby/reline/releases", - "source_code_uri" => "https://github.com/ruby/reline" - } - - spec.required_ruby_version = Gem::Requirement.new('>= 2.6') - - spec.add_dependency 'io-console', '~> 0.5' -end diff --git a/lib/reline/terminfo.rb b/lib/reline/terminfo.rb deleted file mode 100644 index c2b1f681b4..0000000000 --- a/lib/reline/terminfo.rb +++ /dev/null @@ -1,158 +0,0 @@ -begin - # Ignore warning `Add fiddle to your Gemfile or gemspec` in Ruby 3.4. - # terminfo.rb and ansi.rb supports fiddle unavailable environment. - verbose, $VERBOSE = $VERBOSE, nil - require 'fiddle' - require 'fiddle/import' -rescue LoadError - module Reline::Terminfo - def self.curses_dl - false - end - end -ensure - $VERBOSE = verbose -end - -module Reline::Terminfo - extend Fiddle::Importer - - class TerminfoError < StandardError; end - - def self.curses_dl_files - case RUBY_PLATFORM - when /mingw/, /mswin/ - # aren't supported - [] - when /cygwin/ - %w[cygncursesw-10.dll cygncurses-10.dll] - when /darwin/ - %w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib] - else - %w[libncursesw.so libcursesw.so libncurses.so libcurses.so] - end - end - - @curses_dl = false - def self.curses_dl - return @curses_dl unless @curses_dl == false - if Fiddle.const_defined?(:TYPE_VARIADIC) - curses_dl_files.each do |curses_name| - result = Fiddle::Handle.new(curses_name) - rescue Fiddle::DLError - next - else - @curses_dl = result - break - end - end - @curses_dl = nil if @curses_dl == false - @curses_dl - end -end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl) - -module Reline::Terminfo - dlload curses_dl - #extern 'int setupterm(char *term, int fildes, int *errret)' - @setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT) - #extern 'char *tigetstr(char *capname)' - @tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP) - begin - #extern 'char *tiparm(const char *str, ...)' - @tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP) - rescue Fiddle::DLError - # OpenBSD lacks tiparm - #extern 'char *tparm(const char *str, ...)' - @tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP) - end - begin - #extern 'int tigetflag(char *str)' - @tigetflag = Fiddle::Function.new(curses_dl['tigetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT) - rescue Fiddle::DLError - # OpenBSD lacks tigetflag - #extern 'int tgetflag(char *str)' - @tigetflag = Fiddle::Function.new(curses_dl['tgetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT) - end - begin - #extern 'int tigetnum(char *str)' - @tigetnum = Fiddle::Function.new(curses_dl['tigetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT) - rescue Fiddle::DLError - # OpenBSD lacks tigetnum - #extern 'int tgetnum(char *str)' - @tigetnum = Fiddle::Function.new(curses_dl['tgetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT) - end - - def self.setupterm(term, fildes) - errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE) - ret = @setupterm.(term, fildes, errret_int) - case ret - when 0 # OK - @term_supported = true - when -1 # ERR - @term_supported = false - end - end - - class StringWithTiparm < String - def tiparm(*args) # for method chain - Reline::Terminfo.tiparm(self, *args) - end - end - - def self.tigetstr(capname) - raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String) - capability = @tigetstr.(capname) - case capability.to_i - when 0, -1 - raise TerminfoError, "can't find capability: #{capname}" - end - StringWithTiparm.new(capability.to_s) - end - - def self.tiparm(str, *args) - new_args = [] - args.each do |a| - new_args << Fiddle::TYPE_INT << a - end - @tiparm.(str, *new_args).to_s - end - - def self.tigetflag(capname) - raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String) - flag = @tigetflag.(capname).to_i - case flag - when -1 - raise TerminfoError, "not boolean capability: #{capname}" - when 0 - raise TerminfoError, "can't find capability: #{capname}" - end - flag - end - - def self.tigetnum(capname) - raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String) - num = @tigetnum.(capname).to_i - case num - when -2 - raise TerminfoError, "not numeric capability: #{capname}" - when -1 - raise TerminfoError, "can't find capability: #{capname}" - end - num - end - - # NOTE: This means Fiddle and curses are enabled. - def self.enabled? - true - end - - def self.term_supported? - @term_supported - end -end if Reline::Terminfo.curses_dl - -module Reline::Terminfo - def self.enabled? - false - end -end unless Reline::Terminfo.curses_dl diff --git a/lib/reline/unicode.rb b/lib/reline/unicode.rb deleted file mode 100644 index 642f4d7e36..0000000000 --- a/lib/reline/unicode.rb +++ /dev/null @@ -1,687 +0,0 @@ -class Reline::Unicode - EscapedPairs = { - 0x00 => '^@', - 0x01 => '^A', # C-a - 0x02 => '^B', - 0x03 => '^C', - 0x04 => '^D', - 0x05 => '^E', - 0x06 => '^F', - 0x07 => '^G', - 0x08 => '^H', # Backspace - 0x09 => '^I', - 0x0A => '^J', - 0x0B => '^K', - 0x0C => '^L', - 0x0D => '^M', # Enter - 0x0E => '^N', - 0x0F => '^O', - 0x10 => '^P', - 0x11 => '^Q', - 0x12 => '^R', - 0x13 => '^S', - 0x14 => '^T', - 0x15 => '^U', - 0x16 => '^V', - 0x17 => '^W', - 0x18 => '^X', - 0x19 => '^Y', - 0x1A => '^Z', # C-z - 0x1B => '^[', # C-[ C-3 - 0x1D => '^]', # C-] - 0x1E => '^^', # C-~ C-6 - 0x1F => '^_', # C-_ C-7 - 0x7F => '^?', # C-? C-8 - } - EscapedChars = EscapedPairs.keys.map(&:chr) - - NON_PRINTING_START = "\1" - NON_PRINTING_END = "\2" - CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/ - OSC_REGEXP = /\e\]\d+(?:;[^;\a\e]+)*(?:\a|\e\\)/ - WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o - - def self.escape_for_print(str) - str.chars.map! { |gr| - case gr - when -"\n" - gr - when -"\t" - -' ' - else - EscapedPairs[gr.ord] || gr - end - }.join - end - - def self.safe_encode(str, encoding) - # Reline only supports utf-8 convertible string. - converted = str.encode(encoding, invalid: :replace, undef: :replace) - return converted if str.encoding == Encoding::UTF_8 || converted.encoding == Encoding::UTF_8 || converted.ascii_only? - - # This code is essentially doing the same thing as - # `str.encode(utf8, **replace_options).encode(encoding, **replace_options)` - # but also avoids unneccesary irreversible encoding conversion. - converted.gsub(/\X/) do |c| - c.encode(Encoding::UTF_8) - c - rescue Encoding::UndefinedConversionError - '?' - end - end - - require 'reline/unicode/east_asian_width' - - def self.get_mbchar_width(mbchar) - ord = mbchar.ord - if ord <= 0x1F # in EscapedPairs - return 2 - elsif ord <= 0x7E # printable ASCII chars - return 1 - end - utf8_mbchar = mbchar.encode(Encoding::UTF_8) - ord = utf8_mbchar.ord - chunk_index = EastAsianWidth::CHUNK_LAST.bsearch_index { |o| ord <= o } - size = EastAsianWidth::CHUNK_WIDTH[chunk_index] - if size == -1 - Reline.ambiguous_width - elsif size == 1 && utf8_mbchar.size >= 2 - second_char_ord = utf8_mbchar[1].ord - # Halfwidth Dakuten Handakuten - # Only these two character has Letter Modifier category and can be combined in a single grapheme cluster - (second_char_ord == 0xFF9E || second_char_ord == 0xFF9F) ? 2 : 1 - else - size - end - end - - def self.calculate_width(str, allow_escape_code = false) - if allow_escape_code - width = 0 - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| - case - when non_printing_start - in_zero_width = true - when non_printing_end - in_zero_width = false - when csi, osc - when gc - unless in_zero_width - width += get_mbchar_width(gc) - end - end - end - width - else - str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc| - w + get_mbchar_width(gc) - } - end - end - - def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0) - lines = [String.new(encoding: encoding)] - height = 1 - width = offset - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - seq = String.new(encoding: encoding) - rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| - case - when non_printing_start - in_zero_width = true - when non_printing_end - in_zero_width = false - when csi - lines.last << csi - unless in_zero_width - if csi == -"\e[m" || csi == -"\e[0m" - seq.clear - else - seq << csi - end - end - when osc - lines.last << osc - seq << osc unless in_zero_width - when gc - unless in_zero_width - mbchar_width = get_mbchar_width(gc) - if (width += mbchar_width) > max_width - width = mbchar_width - lines << nil - lines << seq.dup - height += 1 - end - end - lines.last << gc - end - end - # The cursor moves to next line in first - if width == max_width - lines << nil - lines << String.new(encoding: encoding) - height += 1 - end - [lines, height] - end - - def self.strip_non_printing_start_end(prompt) - prompt.gsub(/\x01([^\x02]*)(?:\x02|\z)/) { $1 } - end - - # Take a chunk of a String cut by width with escape sequences. - def self.take_range(str, start_col, max_width) - take_mbchar_range(str, start_col, max_width).first - end - - def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false) - chunk = String.new(encoding: str.encoding) - - end_col = start_col + width - total_width = 0 - rest = str.encode(Encoding::UTF_8) - in_zero_width = false - chunk_start_col = nil - chunk_end_col = nil - has_csi = false - rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc| - case - when non_printing_start - in_zero_width = true - when non_printing_end - in_zero_width = false - when csi - has_csi = true - chunk << csi - when osc - chunk << osc - when gc - if in_zero_width - chunk << gc - next - end - - mbchar_width = get_mbchar_width(gc) - prev_width = total_width - total_width += mbchar_width - - if (cover_begin || padding ? total_width <= start_col : prev_width < start_col) - # Current character haven't reached start_col yet - next - elsif padding && !cover_begin && prev_width < start_col && start_col < total_width - # Add preceding padding. This padding might have background color. - chunk << ' ' - chunk_start_col ||= start_col - chunk_end_col = total_width - next - elsif (cover_end ? prev_width < end_col : total_width <= end_col) - # Current character is in the range - chunk << gc - chunk_start_col ||= prev_width - chunk_end_col = total_width - break if total_width >= end_col - else - # Current character exceeds end_col - if padding && end_col < total_width - # Add succeeding padding. This padding might have background color. - chunk << ' ' - chunk_start_col ||= prev_width - chunk_end_col = end_col - end - break - end - end - end - chunk_start_col ||= start_col - chunk_end_col ||= start_col - if padding && chunk_end_col < end_col - # Append padding. This padding should not include background color. - chunk << "\e[0m" if has_csi - chunk << ' ' * (end_col - chunk_end_col) - chunk_end_col = end_col - end - [chunk, chunk_start_col, chunk_end_col - chunk_start_col] - end - - def self.get_next_mbchar_size(line, byte_pointer) - grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first - grapheme ? grapheme.bytesize : 0 - end - - def self.get_prev_mbchar_size(line, byte_pointer) - if byte_pointer.zero? - 0 - else - grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last - grapheme ? grapheme.bytesize : 0 - end - end - - def self.em_forward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - width += get_mbchar_width(mbchar) - byte_size += size - end - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.em_forward_word_with_capitalization(line, byte_pointer) - width = 0 - byte_size = 0 - new_str = String.new - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - new_str += mbchar - width += get_mbchar_width(mbchar) - byte_size += size - end - first = true - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - if first - new_str += mbchar.upcase - first = false - else - new_str += mbchar.downcase - end - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width, new_str] - end - - def self.em_backward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - width += get_mbchar_width(mbchar) - byte_size += size - end - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.em_big_backward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar =~ /\S/ - width += get_mbchar_width(mbchar) - byte_size += size - end - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar =~ /\s/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.ed_transpose_words(line, byte_pointer) - right_word_start = nil - size = get_next_mbchar_size(line, byte_pointer) - mbchar = line.byteslice(byte_pointer, size) - if size.zero? - # ' aaa bbb [cursor]' - byte_size = 0 - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - byte_size -= size - end - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size -= size - end - right_word_start = byte_pointer + byte_size - byte_size = 0 - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size += size - end - after_start = byte_pointer + byte_size - elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - # ' aaa bb[cursor]b' - byte_size = 0 - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size -= size - end - right_word_start = byte_pointer + byte_size - byte_size = 0 - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size += size - end - after_start = byte_pointer + byte_size - else - byte_size = 0 - while (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - byte_size += size - end - if (byte_pointer + byte_size) == (line.bytesize - 1) - # ' aaa bbb [cursor] ' - after_start = line.bytesize - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - byte_size -= size - end - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size -= size - end - right_word_start = byte_pointer + byte_size - else - # ' aaa [cursor] bbb ' - right_word_start = byte_pointer + byte_size - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size += size - end - after_start = byte_pointer + byte_size - end - end - byte_size = right_word_start - byte_pointer - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/ - byte_size -= size - end - middle_start = byte_pointer + byte_size - byte_size = middle_start - byte_pointer - while 0 < (byte_pointer + byte_size) - size = get_prev_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size - size, size) - break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/ - byte_size -= size - end - left_word_start = byte_pointer + byte_size - [left_word_start, middle_start, right_word_start, after_start] - end - - def self.vi_big_forward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar =~ /\s/ - width += get_mbchar_width(mbchar) - byte_size += size - end - while (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar =~ /\S/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.vi_big_forward_end_word(line, byte_pointer) - if (line.bytesize - 1) > byte_pointer - size = get_next_mbchar_size(line, byte_pointer) - mbchar = line.byteslice(byte_pointer, size) - width = get_mbchar_width(mbchar) - byte_size = size - else - return [0, 0] - end - while (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar =~ /\S/ - width += get_mbchar_width(mbchar) - byte_size += size - end - prev_width = width - prev_byte_size = byte_size - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar =~ /\s/ - prev_width = width - prev_byte_size = byte_size - width += get_mbchar_width(mbchar) - byte_size += size - end - [prev_byte_size, prev_width] - end - - def self.vi_big_backward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar =~ /\S/ - width += get_mbchar_width(mbchar) - byte_size += size - end - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - break if mbchar =~ /\s/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false) - if line.bytesize > byte_pointer - size = get_next_mbchar_size(line, byte_pointer) - mbchar = line.byteslice(byte_pointer, size) - if mbchar =~ /\w/ - started_by = :word - elsif mbchar =~ /\s/ - started_by = :space - else - started_by = :non_word_printable - end - width = get_mbchar_width(mbchar) - byte_size = size - else - return [0, 0] - end - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - case started_by - when :word - break if mbchar =~ /\W/ - when :space - break if mbchar =~ /\S/ - when :non_word_printable - break if mbchar =~ /\w|\s/ - end - width += get_mbchar_width(mbchar) - byte_size += size - end - return [byte_size, width] if drop_terminate_spaces - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - break if mbchar =~ /\S/ - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.vi_forward_end_word(line, byte_pointer) - if (line.bytesize - 1) > byte_pointer - size = get_next_mbchar_size(line, byte_pointer) - mbchar = line.byteslice(byte_pointer, size) - if mbchar =~ /\w/ - started_by = :word - elsif mbchar =~ /\s/ - started_by = :space - else - started_by = :non_word_printable - end - width = get_mbchar_width(mbchar) - byte_size = size - else - return [0, 0] - end - if (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - if mbchar =~ /\w/ - second = :word - elsif mbchar =~ /\s/ - second = :space - else - second = :non_word_printable - end - second_width = get_mbchar_width(mbchar) - second_byte_size = size - else - return [byte_size, width] - end - if second == :space - width += second_width - byte_size += second_byte_size - while (line.bytesize - 1) > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - if mbchar =~ /\S/ - if mbchar =~ /\w/ - started_by = :word - else - started_by = :non_word_printable - end - break - end - width += get_mbchar_width(mbchar) - byte_size += size - end - else - case [started_by, second] - when [:word, :non_word_printable], [:non_word_printable, :word] - started_by = second - else - width += second_width - byte_size += second_byte_size - started_by = second - end - end - prev_width = width - prev_byte_size = byte_size - while line.bytesize > (byte_pointer + byte_size) - size = get_next_mbchar_size(line, byte_pointer + byte_size) - mbchar = line.byteslice(byte_pointer + byte_size, size) - case started_by - when :word - break if mbchar =~ /\W/ - when :non_word_printable - break if mbchar =~ /[\w\s]/ - end - prev_width = width - prev_byte_size = byte_size - width += get_mbchar_width(mbchar) - byte_size += size - end - [prev_byte_size, prev_width] - end - - def self.vi_backward_word(line, byte_pointer) - width = 0 - byte_size = 0 - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - if mbchar =~ /\S/ - if mbchar =~ /\w/ - started_by = :word - else - started_by = :non_word_printable - end - break - end - width += get_mbchar_width(mbchar) - byte_size += size - end - while 0 < (byte_pointer - byte_size) - size = get_prev_mbchar_size(line, byte_pointer - byte_size) - mbchar = line.byteslice(byte_pointer - byte_size - size, size) - case started_by - when :word - break if mbchar =~ /\W/ - when :non_word_printable - break if mbchar =~ /[\w\s]/ - end - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end - - def self.vi_first_print(line) - width = 0 - byte_size = 0 - while (line.bytesize - 1) > byte_size - size = get_next_mbchar_size(line, byte_size) - mbchar = line.byteslice(byte_size, size) - if mbchar =~ /\S/ - break - end - width += get_mbchar_width(mbchar) - byte_size += size - end - [byte_size, width] - end -end diff --git a/lib/reline/unicode/east_asian_width.rb b/lib/reline/unicode/east_asian_width.rb deleted file mode 100644 index 106ca4881a..0000000000 --- a/lib/reline/unicode/east_asian_width.rb +++ /dev/null @@ -1,1267 +0,0 @@ -class Reline::Unicode::EastAsianWidth - # This is based on EastAsianWidth.txt - # UNICODE_VERSION = '15.1.0' - - CHUNK_LAST, CHUNK_WIDTH = [ - [0x1f, 2], - [0x7e, 1], - [0x7f, 2], - [0xa0, 1], - [0xa1, -1], - [0xa3, 1], - [0xa4, -1], - [0xa6, 1], - [0xa8, -1], - [0xa9, 1], - [0xaa, -1], - [0xac, 1], - [0xae, -1], - [0xaf, 1], - [0xb4, -1], - [0xb5, 1], - [0xba, -1], - [0xbb, 1], - [0xbf, -1], - [0xc5, 1], - [0xc6, -1], - [0xcf, 1], - [0xd0, -1], - [0xd6, 1], - [0xd8, -1], - [0xdd, 1], - [0xe1, -1], - [0xe5, 1], - [0xe6, -1], - [0xe7, 1], - [0xea, -1], - [0xeb, 1], - [0xed, -1], - [0xef, 1], - [0xf0, -1], - [0xf1, 1], - [0xf3, -1], - [0xf6, 1], - [0xfa, -1], - [0xfb, 1], - [0xfc, -1], - [0xfd, 1], - [0xfe, -1], - [0x100, 1], - [0x101, -1], - [0x110, 1], - [0x111, -1], - [0x112, 1], - [0x113, -1], - [0x11a, 1], - [0x11b, -1], - [0x125, 1], - [0x127, -1], - [0x12a, 1], - [0x12b, -1], - [0x130, 1], - [0x133, -1], - [0x137, 1], - [0x138, -1], - [0x13e, 1], - [0x142, -1], - [0x143, 1], - [0x144, -1], - [0x147, 1], - [0x14b, -1], - [0x14c, 1], - [0x14d, -1], - [0x151, 1], - [0x153, -1], - [0x165, 1], - [0x167, -1], - [0x16a, 1], - [0x16b, -1], - [0x1cd, 1], - [0x1ce, -1], - [0x1cf, 1], - [0x1d0, -1], - [0x1d1, 1], - [0x1d2, -1], - [0x1d3, 1], - [0x1d4, -1], - [0x1d5, 1], - [0x1d6, -1], - [0x1d7, 1], - [0x1d8, -1], - [0x1d9, 1], - [0x1da, -1], - [0x1db, 1], - [0x1dc, -1], - [0x250, 1], - [0x251, -1], - [0x260, 1], - [0x261, -1], - [0x2c3, 1], - [0x2c4, -1], - [0x2c6, 1], - [0x2c7, -1], - [0x2c8, 1], - [0x2cb, -1], - [0x2cc, 1], - [0x2cd, -1], - [0x2cf, 1], - [0x2d0, -1], - [0x2d7, 1], - [0x2db, -1], - [0x2dc, 1], - [0x2dd, -1], - [0x2de, 1], - [0x2df, -1], - [0x2ff, 1], - [0x36f, 0], - [0x390, 1], - [0x3a1, -1], - [0x3a2, 1], - [0x3a9, -1], - [0x3b0, 1], - [0x3c1, -1], - [0x3c2, 1], - [0x3c9, -1], - [0x400, 1], - [0x401, -1], - [0x40f, 1], - [0x44f, -1], - [0x450, 1], - [0x451, -1], - [0x482, 1], - [0x487, 0], - [0x590, 1], - [0x5bd, 0], - [0x5be, 1], - [0x5bf, 0], - [0x5c0, 1], - [0x5c2, 0], - [0x5c3, 1], - [0x5c5, 0], - [0x5c6, 1], - [0x5c7, 0], - [0x60f, 1], - [0x61a, 0], - [0x64a, 1], - [0x65f, 0], - [0x66f, 1], - [0x670, 0], - [0x6d5, 1], - [0x6dc, 0], - [0x6de, 1], - [0x6e4, 0], - [0x6e6, 1], - [0x6e8, 0], - [0x6e9, 1], - [0x6ed, 0], - [0x710, 1], - [0x711, 0], - [0x72f, 1], - [0x74a, 0], - [0x7a5, 1], - [0x7b0, 0], - [0x7ea, 1], - [0x7f3, 0], - [0x7fc, 1], - [0x7fd, 0], - [0x815, 1], - [0x819, 0], - [0x81a, 1], - [0x823, 0], - [0x824, 1], - [0x827, 0], - [0x828, 1], - [0x82d, 0], - [0x858, 1], - [0x85b, 0], - [0x897, 1], - [0x89f, 0], - [0x8c9, 1], - [0x8e1, 0], - [0x8e2, 1], - [0x902, 0], - [0x939, 1], - [0x93a, 0], - [0x93b, 1], - [0x93c, 0], - [0x940, 1], - [0x948, 0], - [0x94c, 1], - [0x94d, 0], - [0x950, 1], - [0x957, 0], - [0x961, 1], - [0x963, 0], - [0x980, 1], - [0x981, 0], - [0x9bb, 1], - [0x9bc, 0], - [0x9c0, 1], - [0x9c4, 0], - [0x9cc, 1], - [0x9cd, 0], - [0x9e1, 1], - [0x9e3, 0], - [0x9fd, 1], - [0x9fe, 0], - [0xa00, 1], - [0xa02, 0], - [0xa3b, 1], - [0xa3c, 0], - [0xa40, 1], - [0xa42, 0], - [0xa46, 1], - [0xa48, 0], - [0xa4a, 1], - [0xa4d, 0], - [0xa50, 1], - [0xa51, 0], - [0xa6f, 1], - [0xa71, 0], - [0xa74, 1], - [0xa75, 0], - [0xa80, 1], - [0xa82, 0], - [0xabb, 1], - [0xabc, 0], - [0xac0, 1], - [0xac5, 0], - [0xac6, 1], - [0xac8, 0], - [0xacc, 1], - [0xacd, 0], - [0xae1, 1], - [0xae3, 0], - [0xaf9, 1], - [0xaff, 0], - [0xb00, 1], - [0xb01, 0], - [0xb3b, 1], - [0xb3c, 0], - [0xb3e, 1], - [0xb3f, 0], - [0xb40, 1], - [0xb44, 0], - [0xb4c, 1], - [0xb4d, 0], - [0xb54, 1], - [0xb56, 0], - [0xb61, 1], - [0xb63, 0], - [0xb81, 1], - [0xb82, 0], - [0xbbf, 1], - [0xbc0, 0], - [0xbcc, 1], - [0xbcd, 0], - [0xbff, 1], - [0xc00, 0], - [0xc03, 1], - [0xc04, 0], - [0xc3b, 1], - [0xc3c, 0], - [0xc3d, 1], - [0xc40, 0], - [0xc45, 1], - [0xc48, 0], - [0xc49, 1], - [0xc4d, 0], - [0xc54, 1], - [0xc56, 0], - [0xc61, 1], - [0xc63, 0], - [0xc80, 1], - [0xc81, 0], - [0xcbb, 1], - [0xcbc, 0], - [0xcbe, 1], - [0xcbf, 0], - [0xcc5, 1], - [0xcc6, 0], - [0xccb, 1], - [0xccd, 0], - [0xce1, 1], - [0xce3, 0], - [0xcff, 1], - [0xd01, 0], - [0xd3a, 1], - [0xd3c, 0], - [0xd40, 1], - [0xd44, 0], - [0xd4c, 1], - [0xd4d, 0], - [0xd61, 1], - [0xd63, 0], - [0xd80, 1], - [0xd81, 0], - [0xdc9, 1], - [0xdca, 0], - [0xdd1, 1], - [0xdd4, 0], - [0xdd5, 1], - [0xdd6, 0], - [0xe30, 1], - [0xe31, 0], - [0xe33, 1], - [0xe3a, 0], - [0xe46, 1], - [0xe4e, 0], - [0xeb0, 1], - [0xeb1, 0], - [0xeb3, 1], - [0xebc, 0], - [0xec7, 1], - [0xece, 0], - [0xf17, 1], - [0xf19, 0], - [0xf34, 1], - [0xf35, 0], - [0xf36, 1], - [0xf37, 0], - [0xf38, 1], - [0xf39, 0], - [0xf70, 1], - [0xf7e, 0], - [0xf7f, 1], - [0xf84, 0], - [0xf85, 1], - [0xf87, 0], - [0xf8c, 1], - [0xf97, 0], - [0xf98, 1], - [0xfbc, 0], - [0xfc5, 1], - [0xfc6, 0], - [0x102c, 1], - [0x1030, 0], - [0x1031, 1], - [0x1037, 0], - [0x1038, 1], - [0x103a, 0], - [0x103c, 1], - [0x103e, 0], - [0x1057, 1], - [0x1059, 0], - [0x105d, 1], - [0x1060, 0], - [0x1070, 1], - [0x1074, 0], - [0x1081, 1], - [0x1082, 0], - [0x1084, 1], - [0x1086, 0], - [0x108c, 1], - [0x108d, 0], - [0x109c, 1], - [0x109d, 0], - [0x10ff, 1], - [0x115f, 2], - [0x135c, 1], - [0x135f, 0], - [0x1711, 1], - [0x1714, 0], - [0x1731, 1], - [0x1733, 0], - [0x1751, 1], - [0x1753, 0], - [0x1771, 1], - [0x1773, 0], - [0x17b3, 1], - [0x17b5, 0], - [0x17b6, 1], - [0x17bd, 0], - [0x17c5, 1], - [0x17c6, 0], - [0x17c8, 1], - [0x17d3, 0], - [0x17dc, 1], - [0x17dd, 0], - [0x180a, 1], - [0x180d, 0], - [0x180e, 1], - [0x180f, 0], - [0x1884, 1], - [0x1886, 0], - [0x18a8, 1], - [0x18a9, 0], - [0x191f, 1], - [0x1922, 0], - [0x1926, 1], - [0x1928, 0], - [0x1931, 1], - [0x1932, 0], - [0x1938, 1], - [0x193b, 0], - [0x1a16, 1], - [0x1a18, 0], - [0x1a1a, 1], - [0x1a1b, 0], - [0x1a55, 1], - [0x1a56, 0], - [0x1a57, 1], - [0x1a5e, 0], - [0x1a5f, 1], - [0x1a60, 0], - [0x1a61, 1], - [0x1a62, 0], - [0x1a64, 1], - [0x1a6c, 0], - [0x1a72, 1], - [0x1a7c, 0], - [0x1a7e, 1], - [0x1a7f, 0], - [0x1aaf, 1], - [0x1abd, 0], - [0x1abe, 1], - [0x1ace, 0], - [0x1aff, 1], - [0x1b03, 0], - [0x1b33, 1], - [0x1b34, 0], - [0x1b35, 1], - [0x1b3a, 0], - [0x1b3b, 1], - [0x1b3c, 0], - [0x1b41, 1], - [0x1b42, 0], - [0x1b6a, 1], - [0x1b73, 0], - [0x1b7f, 1], - [0x1b81, 0], - [0x1ba1, 1], - [0x1ba5, 0], - [0x1ba7, 1], - [0x1ba9, 0], - [0x1baa, 1], - [0x1bad, 0], - [0x1be5, 1], - [0x1be6, 0], - [0x1be7, 1], - [0x1be9, 0], - [0x1bec, 1], - [0x1bed, 0], - [0x1bee, 1], - [0x1bf1, 0], - [0x1c2b, 1], - [0x1c33, 0], - [0x1c35, 1], - [0x1c37, 0], - [0x1ccf, 1], - [0x1cd2, 0], - [0x1cd3, 1], - [0x1ce0, 0], - [0x1ce1, 1], - [0x1ce8, 0], - [0x1cec, 1], - [0x1ced, 0], - [0x1cf3, 1], - [0x1cf4, 0], - [0x1cf7, 1], - [0x1cf9, 0], - [0x1dbf, 1], - [0x1dff, 0], - [0x200f, 1], - [0x2010, -1], - [0x2012, 1], - [0x2016, -1], - [0x2017, 1], - [0x2019, -1], - [0x201b, 1], - [0x201d, -1], - [0x201f, 1], - [0x2022, -1], - [0x2023, 1], - [0x2027, -1], - [0x202f, 1], - [0x2030, -1], - [0x2031, 1], - [0x2033, -1], - [0x2034, 1], - [0x2035, -1], - [0x203a, 1], - [0x203b, -1], - [0x203d, 1], - [0x203e, -1], - [0x2073, 1], - [0x2074, -1], - [0x207e, 1], - [0x207f, -1], - [0x2080, 1], - [0x2084, -1], - [0x20ab, 1], - [0x20ac, -1], - [0x20cf, 1], - [0x20dc, 0], - [0x20e0, 1], - [0x20e1, 0], - [0x20e4, 1], - [0x20f0, 0], - [0x2102, 1], - [0x2103, -1], - [0x2104, 1], - [0x2105, -1], - [0x2108, 1], - [0x2109, -1], - [0x2112, 1], - [0x2113, -1], - [0x2115, 1], - [0x2116, -1], - [0x2120, 1], - [0x2122, -1], - [0x2125, 1], - [0x2126, -1], - [0x212a, 1], - [0x212b, -1], - [0x2152, 1], - [0x2154, -1], - [0x215a, 1], - [0x215e, -1], - [0x215f, 1], - [0x216b, -1], - [0x216f, 1], - [0x2179, -1], - [0x2188, 1], - [0x2189, -1], - [0x218f, 1], - [0x2199, -1], - [0x21b7, 1], - [0x21b9, -1], - [0x21d1, 1], - [0x21d2, -1], - [0x21d3, 1], - [0x21d4, -1], - [0x21e6, 1], - [0x21e7, -1], - [0x21ff, 1], - [0x2200, -1], - [0x2201, 1], - [0x2203, -1], - [0x2206, 1], - [0x2208, -1], - [0x220a, 1], - [0x220b, -1], - [0x220e, 1], - [0x220f, -1], - [0x2210, 1], - [0x2211, -1], - [0x2214, 1], - [0x2215, -1], - [0x2219, 1], - [0x221a, -1], - [0x221c, 1], - [0x2220, -1], - [0x2222, 1], - [0x2223, -1], - [0x2224, 1], - [0x2225, -1], - [0x2226, 1], - [0x222c, -1], - [0x222d, 1], - [0x222e, -1], - [0x2233, 1], - [0x2237, -1], - [0x223b, 1], - [0x223d, -1], - [0x2247, 1], - [0x2248, -1], - [0x224b, 1], - [0x224c, -1], - [0x2251, 1], - [0x2252, -1], - [0x225f, 1], - [0x2261, -1], - [0x2263, 1], - [0x2267, -1], - [0x2269, 1], - [0x226b, -1], - [0x226d, 1], - [0x226f, -1], - [0x2281, 1], - [0x2283, -1], - [0x2285, 1], - [0x2287, -1], - [0x2294, 1], - [0x2295, -1], - [0x2298, 1], - [0x2299, -1], - [0x22a4, 1], - [0x22a5, -1], - [0x22be, 1], - [0x22bf, -1], - [0x2311, 1], - [0x2312, -1], - [0x2319, 1], - [0x231b, 2], - [0x2328, 1], - [0x232a, 2], - [0x23e8, 1], - [0x23ec, 2], - [0x23ef, 1], - [0x23f0, 2], - [0x23f2, 1], - [0x23f3, 2], - [0x245f, 1], - [0x24e9, -1], - [0x24ea, 1], - [0x254b, -1], - [0x254f, 1], - [0x2573, -1], - [0x257f, 1], - [0x258f, -1], - [0x2591, 1], - [0x2595, -1], - [0x259f, 1], - [0x25a1, -1], - [0x25a2, 1], - [0x25a9, -1], - [0x25b1, 1], - [0x25b3, -1], - [0x25b5, 1], - [0x25b7, -1], - [0x25bb, 1], - [0x25bd, -1], - [0x25bf, 1], - [0x25c1, -1], - [0x25c5, 1], - [0x25c8, -1], - [0x25ca, 1], - [0x25cb, -1], - [0x25cd, 1], - [0x25d1, -1], - [0x25e1, 1], - [0x25e5, -1], - [0x25ee, 1], - [0x25ef, -1], - [0x25fc, 1], - [0x25fe, 2], - [0x2604, 1], - [0x2606, -1], - [0x2608, 1], - [0x2609, -1], - [0x260d, 1], - [0x260f, -1], - [0x2613, 1], - [0x2615, 2], - [0x261b, 1], - [0x261c, -1], - [0x261d, 1], - [0x261e, -1], - [0x263f, 1], - [0x2640, -1], - [0x2641, 1], - [0x2642, -1], - [0x2647, 1], - [0x2653, 2], - [0x265f, 1], - [0x2661, -1], - [0x2662, 1], - [0x2665, -1], - [0x2666, 1], - [0x266a, -1], - [0x266b, 1], - [0x266d, -1], - [0x266e, 1], - [0x266f, -1], - [0x267e, 1], - [0x267f, 2], - [0x2692, 1], - [0x2693, 2], - [0x269d, 1], - [0x269f, -1], - [0x26a0, 1], - [0x26a1, 2], - [0x26a9, 1], - [0x26ab, 2], - [0x26bc, 1], - [0x26be, 2], - [0x26bf, -1], - [0x26c3, 1], - [0x26c5, 2], - [0x26cd, -1], - [0x26ce, 2], - [0x26d3, -1], - [0x26d4, 2], - [0x26e1, -1], - [0x26e2, 1], - [0x26e3, -1], - [0x26e7, 1], - [0x26e9, -1], - [0x26ea, 2], - [0x26f1, -1], - [0x26f3, 2], - [0x26f4, -1], - [0x26f5, 2], - [0x26f9, -1], - [0x26fa, 2], - [0x26fc, -1], - [0x26fd, 2], - [0x26ff, -1], - [0x2704, 1], - [0x2705, 2], - [0x2709, 1], - [0x270b, 2], - [0x2727, 1], - [0x2728, 2], - [0x273c, 1], - [0x273d, -1], - [0x274b, 1], - [0x274c, 2], - [0x274d, 1], - [0x274e, 2], - [0x2752, 1], - [0x2755, 2], - [0x2756, 1], - [0x2757, 2], - [0x2775, 1], - [0x277f, -1], - [0x2794, 1], - [0x2797, 2], - [0x27af, 1], - [0x27b0, 2], - [0x27be, 1], - [0x27bf, 2], - [0x2b1a, 1], - [0x2b1c, 2], - [0x2b4f, 1], - [0x2b50, 2], - [0x2b54, 1], - [0x2b55, 2], - [0x2b59, -1], - [0x2cee, 1], - [0x2cf1, 0], - [0x2d7e, 1], - [0x2d7f, 0], - [0x2ddf, 1], - [0x2dff, 0], - [0x2e7f, 1], - [0x2e99, 2], - [0x2e9a, 1], - [0x2ef3, 2], - [0x2eff, 1], - [0x2fd5, 2], - [0x2fef, 1], - [0x3029, 2], - [0x302d, 0], - [0x303e, 2], - [0x3040, 1], - [0x3096, 2], - [0x3098, 1], - [0x309a, 0], - [0x30ff, 2], - [0x3104, 1], - [0x312f, 2], - [0x3130, 1], - [0x318e, 2], - [0x318f, 1], - [0x31e3, 2], - [0x31ee, 1], - [0x321e, 2], - [0x321f, 1], - [0x3247, 2], - [0x324f, -1], - [0x4dbf, 2], - [0x4dff, 1], - [0xa48c, 2], - [0xa48f, 1], - [0xa4c6, 2], - [0xa66e, 1], - [0xa66f, 0], - [0xa673, 1], - [0xa67d, 0], - [0xa69d, 1], - [0xa69f, 0], - [0xa6ef, 1], - [0xa6f1, 0], - [0xa801, 1], - [0xa802, 0], - [0xa805, 1], - [0xa806, 0], - [0xa80a, 1], - [0xa80b, 0], - [0xa824, 1], - [0xa826, 0], - [0xa82b, 1], - [0xa82c, 0], - [0xa8c3, 1], - [0xa8c5, 0], - [0xa8df, 1], - [0xa8f1, 0], - [0xa8fe, 1], - [0xa8ff, 0], - [0xa925, 1], - [0xa92d, 0], - [0xa946, 1], - [0xa951, 0], - [0xa95f, 1], - [0xa97c, 2], - [0xa97f, 1], - [0xa982, 0], - [0xa9b2, 1], - [0xa9b3, 0], - [0xa9b5, 1], - [0xa9b9, 0], - [0xa9bb, 1], - [0xa9bd, 0], - [0xa9e4, 1], - [0xa9e5, 0], - [0xaa28, 1], - [0xaa2e, 0], - [0xaa30, 1], - [0xaa32, 0], - [0xaa34, 1], - [0xaa36, 0], - [0xaa42, 1], - [0xaa43, 0], - [0xaa4b, 1], - [0xaa4c, 0], - [0xaa7b, 1], - [0xaa7c, 0], - [0xaaaf, 1], - [0xaab0, 0], - [0xaab1, 1], - [0xaab4, 0], - [0xaab6, 1], - [0xaab8, 0], - [0xaabd, 1], - [0xaabf, 0], - [0xaac0, 1], - [0xaac1, 0], - [0xaaeb, 1], - [0xaaed, 0], - [0xaaf5, 1], - [0xaaf6, 0], - [0xabe4, 1], - [0xabe5, 0], - [0xabe7, 1], - [0xabe8, 0], - [0xabec, 1], - [0xabed, 0], - [0xabff, 1], - [0xd7a3, 2], - [0xdfff, 1], - [0xf8ff, -1], - [0xfaff, 2], - [0xfb1d, 1], - [0xfb1e, 0], - [0xfdff, 1], - [0xfe0f, 0], - [0xfe19, 2], - [0xfe1f, 1], - [0xfe2f, 0], - [0xfe52, 2], - [0xfe53, 1], - [0xfe66, 2], - [0xfe67, 1], - [0xfe6b, 2], - [0xff00, 1], - [0xff60, 2], - [0xffdf, 1], - [0xffe6, 2], - [0xfffc, 1], - [0xfffd, -1], - [0x101fc, 1], - [0x101fd, 0], - [0x102df, 1], - [0x102e0, 0], - [0x10375, 1], - [0x1037a, 0], - [0x10a00, 1], - [0x10a03, 0], - [0x10a04, 1], - [0x10a06, 0], - [0x10a0b, 1], - [0x10a0f, 0], - [0x10a37, 1], - [0x10a3a, 0], - [0x10a3e, 1], - [0x10a3f, 0], - [0x10ae4, 1], - [0x10ae6, 0], - [0x10d23, 1], - [0x10d27, 0], - [0x10eaa, 1], - [0x10eac, 0], - [0x10efc, 1], - [0x10eff, 0], - [0x10f45, 1], - [0x10f50, 0], - [0x10f81, 1], - [0x10f85, 0], - [0x11000, 1], - [0x11001, 0], - [0x11037, 1], - [0x11046, 0], - [0x1106f, 1], - [0x11070, 0], - [0x11072, 1], - [0x11074, 0], - [0x1107e, 1], - [0x11081, 0], - [0x110b2, 1], - [0x110b6, 0], - [0x110b8, 1], - [0x110ba, 0], - [0x110c1, 1], - [0x110c2, 0], - [0x110ff, 1], - [0x11102, 0], - [0x11126, 1], - [0x1112b, 0], - [0x1112c, 1], - [0x11134, 0], - [0x11172, 1], - [0x11173, 0], - [0x1117f, 1], - [0x11181, 0], - [0x111b5, 1], - [0x111be, 0], - [0x111c8, 1], - [0x111cc, 0], - [0x111ce, 1], - [0x111cf, 0], - [0x1122e, 1], - [0x11231, 0], - [0x11233, 1], - [0x11234, 0], - [0x11235, 1], - [0x11237, 0], - [0x1123d, 1], - [0x1123e, 0], - [0x11240, 1], - [0x11241, 0], - [0x112de, 1], - [0x112df, 0], - [0x112e2, 1], - [0x112ea, 0], - [0x112ff, 1], - [0x11301, 0], - [0x1133a, 1], - [0x1133c, 0], - [0x1133f, 1], - [0x11340, 0], - [0x11365, 1], - [0x1136c, 0], - [0x1136f, 1], - [0x11374, 0], - [0x11437, 1], - [0x1143f, 0], - [0x11441, 1], - [0x11444, 0], - [0x11445, 1], - [0x11446, 0], - [0x1145d, 1], - [0x1145e, 0], - [0x114b2, 1], - [0x114b8, 0], - [0x114b9, 1], - [0x114ba, 0], - [0x114be, 1], - [0x114c0, 0], - [0x114c1, 1], - [0x114c3, 0], - [0x115b1, 1], - [0x115b5, 0], - [0x115bb, 1], - [0x115bd, 0], - [0x115be, 1], - [0x115c0, 0], - [0x115db, 1], - [0x115dd, 0], - [0x11632, 1], - [0x1163a, 0], - [0x1163c, 1], - [0x1163d, 0], - [0x1163e, 1], - [0x11640, 0], - [0x116aa, 1], - [0x116ab, 0], - [0x116ac, 1], - [0x116ad, 0], - [0x116af, 1], - [0x116b5, 0], - [0x116b6, 1], - [0x116b7, 0], - [0x1171c, 1], - [0x1171f, 0], - [0x11721, 1], - [0x11725, 0], - [0x11726, 1], - [0x1172b, 0], - [0x1182e, 1], - [0x11837, 0], - [0x11838, 1], - [0x1183a, 0], - [0x1193a, 1], - [0x1193c, 0], - [0x1193d, 1], - [0x1193e, 0], - [0x11942, 1], - [0x11943, 0], - [0x119d3, 1], - [0x119d7, 0], - [0x119d9, 1], - [0x119db, 0], - [0x119df, 1], - [0x119e0, 0], - [0x11a00, 1], - [0x11a0a, 0], - [0x11a32, 1], - [0x11a38, 0], - [0x11a3a, 1], - [0x11a3e, 0], - [0x11a46, 1], - [0x11a47, 0], - [0x11a50, 1], - [0x11a56, 0], - [0x11a58, 1], - [0x11a5b, 0], - [0x11a89, 1], - [0x11a96, 0], - [0x11a97, 1], - [0x11a99, 0], - [0x11c2f, 1], - [0x11c36, 0], - [0x11c37, 1], - [0x11c3d, 0], - [0x11c3e, 1], - [0x11c3f, 0], - [0x11c91, 1], - [0x11ca7, 0], - [0x11ca9, 1], - [0x11cb0, 0], - [0x11cb1, 1], - [0x11cb3, 0], - [0x11cb4, 1], - [0x11cb6, 0], - [0x11d30, 1], - [0x11d36, 0], - [0x11d39, 1], - [0x11d3a, 0], - [0x11d3b, 1], - [0x11d3d, 0], - [0x11d3e, 1], - [0x11d45, 0], - [0x11d46, 1], - [0x11d47, 0], - [0x11d8f, 1], - [0x11d91, 0], - [0x11d94, 1], - [0x11d95, 0], - [0x11d96, 1], - [0x11d97, 0], - [0x11ef2, 1], - [0x11ef4, 0], - [0x11eff, 1], - [0x11f01, 0], - [0x11f35, 1], - [0x11f3a, 0], - [0x11f3f, 1], - [0x11f40, 0], - [0x11f41, 1], - [0x11f42, 0], - [0x1343f, 1], - [0x13440, 0], - [0x13446, 1], - [0x13455, 0], - [0x16aef, 1], - [0x16af4, 0], - [0x16b2f, 1], - [0x16b36, 0], - [0x16f4e, 1], - [0x16f4f, 0], - [0x16f8e, 1], - [0x16f92, 0], - [0x16fdf, 1], - [0x16fe3, 2], - [0x16fe4, 0], - [0x16fef, 1], - [0x16ff1, 2], - [0x16fff, 1], - [0x187f7, 2], - [0x187ff, 1], - [0x18cd5, 2], - [0x18cff, 1], - [0x18d08, 2], - [0x1afef, 1], - [0x1aff3, 2], - [0x1aff4, 1], - [0x1affb, 2], - [0x1affc, 1], - [0x1affe, 2], - [0x1afff, 1], - [0x1b122, 2], - [0x1b131, 1], - [0x1b132, 2], - [0x1b14f, 1], - [0x1b152, 2], - [0x1b154, 1], - [0x1b155, 2], - [0x1b163, 1], - [0x1b167, 2], - [0x1b16f, 1], - [0x1b2fb, 2], - [0x1bc9c, 1], - [0x1bc9e, 0], - [0x1ceff, 1], - [0x1cf2d, 0], - [0x1cf2f, 1], - [0x1cf46, 0], - [0x1d166, 1], - [0x1d169, 0], - [0x1d17a, 1], - [0x1d182, 0], - [0x1d184, 1], - [0x1d18b, 0], - [0x1d1a9, 1], - [0x1d1ad, 0], - [0x1d241, 1], - [0x1d244, 0], - [0x1d9ff, 1], - [0x1da36, 0], - [0x1da3a, 1], - [0x1da6c, 0], - [0x1da74, 1], - [0x1da75, 0], - [0x1da83, 1], - [0x1da84, 0], - [0x1da9a, 1], - [0x1da9f, 0], - [0x1daa0, 1], - [0x1daaf, 0], - [0x1dfff, 1], - [0x1e006, 0], - [0x1e007, 1], - [0x1e018, 0], - [0x1e01a, 1], - [0x1e021, 0], - [0x1e022, 1], - [0x1e024, 0], - [0x1e025, 1], - [0x1e02a, 0], - [0x1e08e, 1], - [0x1e08f, 0], - [0x1e12f, 1], - [0x1e136, 0], - [0x1e2ad, 1], - [0x1e2ae, 0], - [0x1e2eb, 1], - [0x1e2ef, 0], - [0x1e4eb, 1], - [0x1e4ef, 0], - [0x1e8cf, 1], - [0x1e8d6, 0], - [0x1e943, 1], - [0x1e94a, 0], - [0x1f003, 1], - [0x1f004, 2], - [0x1f0ce, 1], - [0x1f0cf, 2], - [0x1f0ff, 1], - [0x1f10a, -1], - [0x1f10f, 1], - [0x1f12d, -1], - [0x1f12f, 1], - [0x1f169, -1], - [0x1f16f, 1], - [0x1f18d, -1], - [0x1f18e, 2], - [0x1f190, -1], - [0x1f19a, 2], - [0x1f1ac, -1], - [0x1f1ff, 1], - [0x1f202, 2], - [0x1f20f, 1], - [0x1f23b, 2], - [0x1f23f, 1], - [0x1f248, 2], - [0x1f24f, 1], - [0x1f251, 2], - [0x1f25f, 1], - [0x1f265, 2], - [0x1f2ff, 1], - [0x1f320, 2], - [0x1f32c, 1], - [0x1f335, 2], - [0x1f336, 1], - [0x1f37c, 2], - [0x1f37d, 1], - [0x1f393, 2], - [0x1f39f, 1], - [0x1f3ca, 2], - [0x1f3ce, 1], - [0x1f3d3, 2], - [0x1f3df, 1], - [0x1f3f0, 2], - [0x1f3f3, 1], - [0x1f3f4, 2], - [0x1f3f7, 1], - [0x1f43e, 2], - [0x1f43f, 1], - [0x1f440, 2], - [0x1f441, 1], - [0x1f4fc, 2], - [0x1f4fe, 1], - [0x1f53d, 2], - [0x1f54a, 1], - [0x1f54e, 2], - [0x1f54f, 1], - [0x1f567, 2], - [0x1f579, 1], - [0x1f57a, 2], - [0x1f594, 1], - [0x1f596, 2], - [0x1f5a3, 1], - [0x1f5a4, 2], - [0x1f5fa, 1], - [0x1f64f, 2], - [0x1f67f, 1], - [0x1f6c5, 2], - [0x1f6cb, 1], - [0x1f6cc, 2], - [0x1f6cf, 1], - [0x1f6d2, 2], - [0x1f6d4, 1], - [0x1f6d7, 2], - [0x1f6db, 1], - [0x1f6df, 2], - [0x1f6ea, 1], - [0x1f6ec, 2], - [0x1f6f3, 1], - [0x1f6fc, 2], - [0x1f7df, 1], - [0x1f7eb, 2], - [0x1f7ef, 1], - [0x1f7f0, 2], - [0x1f90b, 1], - [0x1f93a, 2], - [0x1f93b, 1], - [0x1f945, 2], - [0x1f946, 1], - [0x1f9ff, 2], - [0x1fa6f, 1], - [0x1fa7c, 2], - [0x1fa7f, 1], - [0x1fa88, 2], - [0x1fa8f, 1], - [0x1fabd, 2], - [0x1fabe, 1], - [0x1fac5, 2], - [0x1facd, 1], - [0x1fadb, 2], - [0x1fadf, 1], - [0x1fae8, 2], - [0x1faef, 1], - [0x1faf8, 2], - [0x1ffff, 1], - [0x2fffd, 2], - [0x2ffff, 1], - [0x3fffd, 2], - [0xe00ff, 1], - [0xe01ef, 0], - [0xeffff, 1], - [0xffffd, -1], - [0xfffff, 1], - [0x10fffd, -1], - [0x7fffffff, 1] - ].transpose.map(&:freeze) -end diff --git a/lib/reline/version.rb b/lib/reline/version.rb deleted file mode 100644 index b75d874adb..0000000000 --- a/lib/reline/version.rb +++ /dev/null @@ -1,3 +0,0 @@ -module Reline - VERSION = '0.5.10' -end diff --git a/lib/resolv.gemspec b/lib/resolv.gemspec index bfa2f9ff31..66aed34e01 100644 --- a/lib/resolv.gemspec +++ b/lib/resolv.gemspec @@ -21,9 +21,8 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + excludes = %W[/.git* /bin /test /*file /#{File.basename(__FILE__)}] + spec.files = IO.popen(%W[git -C #{__dir__} ls-files -z --] + excludes.map {|e| ":^#{e}"}, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] diff --git a/lib/resolv.rb b/lib/resolv.rb index 983540c415..fa7d4e2e47 100644 --- a/lib/resolv.rb +++ b/lib/resolv.rb @@ -3,11 +3,8 @@ require 'socket' require 'timeout' require 'io/wait' - -begin - require 'securerandom' -rescue LoadError -end +require 'securerandom' +require 'rbconfig' # Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can # handle multiple DNS requests concurrently without blocking the entire Ruby @@ -37,7 +34,8 @@ end class Resolv - VERSION = "0.4.0" + # The version string + VERSION = "0.7.0" ## # Looks up the first IP address for +name+. @@ -83,9 +81,22 @@ class Resolv ## # Creates a new Resolv using +resolvers+. + # + # If +resolvers+ is not given, a hash, or +nil+, uses a Hosts resolver and + # and a DNS resolver. If +resolvers+ is a hash, uses the hash as + # configuration for the DNS resolver. + + def initialize(resolvers=(arg_not_set = true; nil), use_ipv6: (keyword_not_set = true; nil)) + if !keyword_not_set && !arg_not_set + warn "Support for separate use_ipv6 keyword is deprecated, as it is ignored if an argument is provided. Do not provide a positional argument if using the use_ipv6 keyword argument.", uplevel: 1 + end - def initialize(resolvers=nil, use_ipv6: nil) - @resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))] + @resolvers = case resolvers + when Hash, nil + [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(resolvers || {}))] + else + resolvers + end end ## @@ -168,14 +179,15 @@ class Resolv # Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and + if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ begin - require 'win32/resolv' - DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL + require 'win32/resolv' unless defined?(Win32::Resolv) + hosts = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end end - DefaultFileName ||= '/etc/hosts' + # The default file name for host names + DefaultFileName = hosts || '/etc/hosts' ## # Creates a new Resolv::Hosts, using +filename+ for its data source. @@ -513,6 +525,8 @@ class Resolv } end + # :stopdoc: + def fetch_resource(name, typeclass) lazy_initialize truncated = {} @@ -615,16 +629,10 @@ class Resolv } end - if defined? SecureRandom - def self.random(arg) # :nodoc: - begin - SecureRandom.random_number(arg) - rescue NotImplementedError - rand(arg) - end - end - else - def self.random(arg) # :nodoc: + def self.random(arg) # :nodoc: + begin + SecureRandom.random_number(arg) + rescue NotImplementedError rand(arg) end end @@ -656,8 +664,20 @@ class Resolv } end - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - begin + case RUBY_PLATFORM + when *[ + # https://www.rfc-editor.org/rfc/rfc6056.txt + # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations + /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/, + /darwin/, # the same as FreeBSD + ] then + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + udpsock.bind(bind_host, 0) + end + else + # Sequential port assignment + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + # Ephemeral port number range recommended by RFC 6056 port = random(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX @@ -701,7 +721,8 @@ class Resolv begin reply, from = recv_reply(select_result[0]) rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD - Errno::ECONNRESET # Windows + Errno::ECONNRESET, # Windows + EOFError # No name server running on the server? # Don't wait anymore. raise ResolvTimeout @@ -910,8 +931,11 @@ class Resolv end def recv_reply(readable_socks) - len = readable_socks[0].read(2).unpack('n')[0] + len_data = readable_socks[0].read(2) + raise EOFError if len_data.nil? || len_data.bytesize != 2 + len = len_data.unpack('n')[0] reply = @socks[0].read(len) + raise EOFError if reply.nil? || reply.bytesize != len return reply, nil end @@ -980,13 +1004,13 @@ class Resolv next unless keyword case keyword when 'nameserver' - nameserver.concat(args) + nameserver.concat(args.each(&:freeze)) when 'domain' next if args.empty? - search = [args[0]] + search = [args[0].freeze] when 'search' next if args.empty? - search = args + search = args.each(&:freeze) when 'options' args.each {|arg| case arg @@ -997,22 +1021,21 @@ class Resolv end } } - return { :nameserver => nameserver, :search => search, :ndots => ndots } + return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename - config_hash = Config.parse_resolv_conf(filename) + Config.parse_resolv_conf(filename) + elsif defined?(Win32::Resolv) + search, nameserver = Win32::Resolv.get_resolv_info + config_hash = {} + config_hash[:nameserver] = nameserver if nameserver + config_hash[:search] = [search].flatten if search + config_hash else - if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - require 'win32/resolv' - search, nameserver = Win32::Resolv.get_resolv_info - config_hash = {} - config_hash[:nameserver] = nameserver if nameserver - config_hash[:search] = [search].flatten if search - end + {} end - config_hash || {} end def lazy_initialize @@ -1661,6 +1684,7 @@ class Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1681,7 +1705,10 @@ class Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -2107,7 +2134,14 @@ class Resolv attr_reader :ttl - ClassHash = {} # :nodoc: + ClassHash = Module.new do + module_function + + def []=(type_class_value, klass) + type_value, class_value = type_class_value + Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass) + end + end def encode_rdata(msg) # :nodoc: raise NotImplementedError.new @@ -2145,7 +2179,9 @@ class Resolv end def self.get_class(type_value, class_value) # :nodoc: - return ClassHash[[type_value, class_value]] || + cache = :"Type#{type_value}_Class#{class_value}" + + return (const_defined?(cache) && const_get(cache)) || Generic.create(type_value, class_value) end @@ -2574,7 +2610,7 @@ class Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags @@ -2895,15 +2931,21 @@ class Resolv class IPv4 - ## - # Regular expression IPv4 addresses must match. - Regex256 = /0 |1(?:[0-9][0-9]?)? |2(?:[0-4][0-9]?|5[0-5]?|[6-9])? - |[3-9][0-9]?/x + |[3-9][0-9]?/x # :nodoc: + + ## + # Regular expression IPv4 addresses must match. Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/ + ## + # Creates a new IPv4 address from +arg+ which may be: + # + # IPv4:: returns +arg+. + # String:: +arg+ must match the IPv4::Regex constant + def self.create(arg) case arg when IPv4 @@ -3212,13 +3254,15 @@ class Resolv end - module LOC + module LOC # :nodoc: ## # A Resolv::LOC::Size class Size + # Regular expression LOC size must match. + Regex = /^(\d+\.*\d*)[m]$/ ## @@ -3244,6 +3288,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(scalar) @scalar = scalar end @@ -3281,6 +3326,8 @@ class Resolv class Coord + # Regular expression LOC Coord must match. + Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/ ## @@ -3310,6 +3357,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(coordinates,orientation) unless coordinates.kind_of?(String) raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}") @@ -3372,6 +3420,8 @@ class Resolv class Alt + # Regular expression LOC Alt must match. + Regex = /^([+-]*\d+\.*\d*)[m]$/ ## @@ -3397,6 +3447,7 @@ class Resolv end end + # Internal use; use self.create. def initialize(altitude) @altitude = altitude end diff --git a/lib/ruby_vm/rjit/.document b/lib/ruby_vm/rjit/.document deleted file mode 100644 index 0a603afe3d..0000000000 --- a/lib/ruby_vm/rjit/.document +++ /dev/null @@ -1 +0,0 @@ -stats.rb diff --git a/lib/ruby_vm/rjit/assembler.rb b/lib/ruby_vm/rjit/assembler.rb deleted file mode 100644 index 42995e6c8c..0000000000 --- a/lib/ruby_vm/rjit/assembler.rb +++ /dev/null @@ -1,1160 +0,0 @@ -# frozen_string_literal: true -module RubyVM::RJIT - # 8-bit memory access - class BytePtr < Data.define(:reg, :disp); end - - # 32-bit memory access - class DwordPtr < Data.define(:reg, :disp); end - - # 64-bit memory access - QwordPtr = Array - - # SystemV x64 calling convention - C_ARGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] - C_RET = :rax - - # https://cdrdv2.intel.com/v1/dl/getContent/671110 - # Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture. - class Assembler - # rel8 jumps are made with labels - class Label < Data.define(:id, :name); end - - # rel32 is inserted as [Rel32, Rel32Pad..] and converted on #resolve_rel32 - class Rel32 < Data.define(:addr); end - Rel32Pad = Object.new - - # A set of ModR/M values encoded on #insn - class ModRM < Data.define(:mod, :reg, :rm); end - Mod00 = 0b00 # Mod 00: [reg] - Mod01 = 0b01 # Mod 01: [reg]+disp8 - Mod10 = 0b10 # Mod 10: [reg]+disp32 - Mod11 = 0b11 # Mod 11: reg - - # REX = 0100WR0B - REX_B = 0b01000001 - REX_R = 0b01000100 - REX_W = 0b01001000 - - # Operand matchers - R32 = -> (op) { op.is_a?(Symbol) && r32?(op) } - R64 = -> (op) { op.is_a?(Symbol) && r64?(op) } - IMM8 = -> (op) { op.is_a?(Integer) && imm8?(op) } - IMM32 = -> (op) { op.is_a?(Integer) && imm32?(op) } - IMM64 = -> (op) { op.is_a?(Integer) && imm64?(op) } - - def initialize - @bytes = [] - @labels = {} - @label_id = 0 - @comments = Hash.new { |h, k| h[k] = [] } - @blocks = Hash.new { |h, k| h[k] = [] } - @stub_starts = Hash.new { |h, k| h[k] = [] } - @stub_ends = Hash.new { |h, k| h[k] = [] } - @pos_markers = Hash.new { |h, k| h[k] = [] } - end - - def assemble(addr) - set_code_addrs(addr) - resolve_rel32(addr) - resolve_labels - - write_bytes(addr) - - @pos_markers.each do |write_pos, markers| - markers.each { |marker| marker.call(addr + write_pos) } - end - @bytes.size - ensure - @bytes.clear - end - - def size - @bytes.size - end - - # - # Instructions - # - - def add(dst, src) - case [dst, src] - # ADD r/m64, imm8 (Mod 00: [reg]) - in [QwordPtr[R64 => dst_reg], IMM8 => src_imm] - # REX.W + 83 /0 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg], - imm: imm8(src_imm), - ) - # ADD r/m64, imm8 (Mod 11: reg) - in [R64 => dst_reg, IMM8 => src_imm] - # REX.W + 83 /0 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg], - imm: imm8(src_imm), - ) - # ADD r/m64 imm32 (Mod 11: reg) - in [R64 => dst_reg, IMM32 => src_imm] - # REX.W + 81 /0 id - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x81, - mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg], - imm: imm32(src_imm), - ) - # ADD r/m64, r64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 01 /r - # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x01, - mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg], - ) - end - end - - def and(dst, src) - case [dst, src] - # AND r/m64, imm8 (Mod 11: reg) - in [R64 => dst_reg, IMM8 => src_imm] - # REX.W + 83 /4 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg], - imm: imm8(src_imm), - ) - # AND r/m64, imm32 (Mod 11: reg) - in [R64 => dst_reg, IMM32 => src_imm] - # REX.W + 81 /4 id - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x81, - mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg], - imm: imm32(src_imm), - ) - # AND r64, r/m64 (Mod 01: [reg]+disp8) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]] - # REX.W + 23 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x23, - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: imm8(src_disp), - ) - # AND r64, r/m64 (Mod 10: [reg]+disp32) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM32 => src_disp]] - # REX.W + 23 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x23, - mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg], - disp: imm32(src_disp), - ) - end - end - - def call(dst) - case dst - # CALL rel32 - in Integer => dst_addr - # E8 cd - # D: Operand 1: Offset - insn(opcode: 0xe8, imm: rel32(dst_addr)) - # CALL r/m64 (Mod 11: reg) - in R64 => dst_reg - # FF /2 - # M: Operand 1: ModRM:r/m (r) - insn( - opcode: 0xff, - mod_rm: ModRM[mod: Mod11, reg: 2, rm: dst_reg], - ) - end - end - - def cmove(dst, src) - case [dst, src] - # CMOVE r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 44 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x44], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovg(dst, src) - case [dst, src] - # CMOVG r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 4F /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x4f], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovge(dst, src) - case [dst, src] - # CMOVGE r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 4D /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x4d], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovl(dst, src) - case [dst, src] - # CMOVL r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 4C /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x4c], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovle(dst, src) - case [dst, src] - # CMOVLE r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 4E /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x4e], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovne(dst, src) - case [dst, src] - # CMOVNE r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 45 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x45], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovnz(dst, src) - case [dst, src] - # CMOVNZ r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 45 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x45], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - end - end - - def cmovz(dst, src) - case [dst, src] - # CMOVZ r64, r/m64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 0F 44 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x44], - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - # CMOVZ r64, r/m64 (Mod 01: [reg]+disp8) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]] - # REX.W + 0F 44 /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: [0x0f, 0x44], - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: imm8(src_disp), - ) - end - end - - def cmp(left, right) - case [left, right] - # CMP r/m8, imm8 (Mod 01: [reg]+disp8) - in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm] - # 80 /7 ib - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - opcode: 0x80, - mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg], - disp: left_disp, - imm: imm8(right_imm), - ) - # CMP r/m32, imm32 (Mod 01: [reg]+disp8) - in [DwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm] - # 81 /7 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - opcode: 0x81, - mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg], - disp: left_disp, - imm: imm32(right_imm), - ) - # CMP r/m64, imm8 (Mod 01: [reg]+disp8) - in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm] - # REX.W + 83 /7 ib - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg], - disp: left_disp, - imm: imm8(right_imm), - ) - # CMP r/m64, imm32 (Mod 01: [reg]+disp8) - in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm] - # REX.W + 81 /7 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x81, - mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg], - disp: left_disp, - imm: imm32(right_imm), - ) - # CMP r/m64, imm8 (Mod 10: [reg]+disp32) - in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM8 => right_imm] - # REX.W + 83 /7 ib - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod10, reg: 7, rm: left_reg], - disp: imm32(left_disp), - imm: imm8(right_imm), - ) - # CMP r/m64, imm8 (Mod 11: reg) - in [R64 => left_reg, IMM8 => right_imm] - # REX.W + 83 /7 ib - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg], - imm: imm8(right_imm), - ) - # CMP r/m64, imm32 (Mod 11: reg) - in [R64 => left_reg, IMM32 => right_imm] - # REX.W + 81 /7 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x81, - mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg], - imm: imm32(right_imm), - ) - # CMP r/m64, r64 (Mod 01: [reg]+disp8) - in [QwordPtr[R64 => left_reg, IMM8 => left_disp], R64 => right_reg] - # REX.W + 39 /r - # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x39, - mod_rm: ModRM[mod: Mod01, reg: right_reg, rm: left_reg], - disp: left_disp, - ) - # CMP r/m64, r64 (Mod 10: [reg]+disp32) - in [QwordPtr[R64 => left_reg, IMM32 => left_disp], R64 => right_reg] - # REX.W + 39 /r - # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x39, - mod_rm: ModRM[mod: Mod10, reg: right_reg, rm: left_reg], - disp: imm32(left_disp), - ) - # CMP r/m64, r64 (Mod 11: reg) - in [R64 => left_reg, R64 => right_reg] - # REX.W + 39 /r - # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x39, - mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg], - ) - end - end - - def jbe(dst) - case dst - # JBE rel8 - in Label => dst_label - # 76 cb - insn(opcode: 0x76, imm: dst_label) - # JBE rel32 - in Integer => dst_addr - # 0F 86 cd - insn(opcode: [0x0f, 0x86], imm: rel32(dst_addr)) - end - end - - def je(dst) - case dst - # JE rel8 - in Label => dst_label - # 74 cb - insn(opcode: 0x74, imm: dst_label) - # JE rel32 - in Integer => dst_addr - # 0F 84 cd - insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr)) - end - end - - def jl(dst) - case dst - # JL rel32 - in Integer => dst_addr - # 0F 8C cd - insn(opcode: [0x0f, 0x8c], imm: rel32(dst_addr)) - end - end - - def jmp(dst) - case dst - # JZ rel8 - in Label => dst_label - # EB cb - insn(opcode: 0xeb, imm: dst_label) - # JMP rel32 - in Integer => dst_addr - # E9 cd - insn(opcode: 0xe9, imm: rel32(dst_addr)) - # JMP r/m64 (Mod 01: [reg]+disp8) - in QwordPtr[R64 => dst_reg, IMM8 => dst_disp] - # FF /4 - insn(opcode: 0xff, mod_rm: ModRM[mod: Mod01, reg: 4, rm: dst_reg], disp: dst_disp) - # JMP r/m64 (Mod 11: reg) - in R64 => dst_reg - # FF /4 - insn(opcode: 0xff, mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg]) - end - end - - def jne(dst) - case dst - # JNE rel8 - in Label => dst_label - # 75 cb - insn(opcode: 0x75, imm: dst_label) - # JNE rel32 - in Integer => dst_addr - # 0F 85 cd - insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr)) - end - end - - def jnz(dst) - case dst - # JE rel8 - in Label => dst_label - # 75 cb - insn(opcode: 0x75, imm: dst_label) - # JNZ rel32 - in Integer => dst_addr - # 0F 85 cd - insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr)) - end - end - - def jo(dst) - case dst - # JO rel32 - in Integer => dst_addr - # 0F 80 cd - insn(opcode: [0x0f, 0x80], imm: rel32(dst_addr)) - end - end - - def jz(dst) - case dst - # JZ rel8 - in Label => dst_label - # 74 cb - insn(opcode: 0x74, imm: dst_label) - # JZ rel32 - in Integer => dst_addr - # 0F 84 cd - insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr)) - end - end - - def lea(dst, src) - case [dst, src] - # LEA r64,m (Mod 01: [reg]+disp8) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]] - # REX.W + 8D /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8d, - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: imm8(src_disp), - ) - # LEA r64,m (Mod 10: [reg]+disp32) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM32 => src_disp]] - # REX.W + 8D /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8d, - mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg], - disp: imm32(src_disp), - ) - end - end - - def mov(dst, src) - case dst - in R32 => dst_reg - case src - # MOV r32 r/m32 (Mod 01: [reg]+disp8) - in DwordPtr[R64 => src_reg, IMM8 => src_disp] - # 8B /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - opcode: 0x8b, - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: src_disp, - ) - # MOV r32, imm32 (Mod 11: reg) - in IMM32 => src_imm - # B8+ rd id - # OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64 - insn( - opcode: 0xb8, - rd: dst_reg, - imm: imm32(src_imm), - ) - end - in R64 => dst_reg - case src - # MOV r64, r/m64 (Mod 00: [reg]) - in QwordPtr[R64 => src_reg] - # REX.W + 8B /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8b, - mod_rm: ModRM[mod: Mod00, reg: dst_reg, rm: src_reg], - ) - # MOV r64, r/m64 (Mod 01: [reg]+disp8) - in QwordPtr[R64 => src_reg, IMM8 => src_disp] - # REX.W + 8B /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8b, - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: src_disp, - ) - # MOV r64, r/m64 (Mod 10: [reg]+disp32) - in QwordPtr[R64 => src_reg, IMM32 => src_disp] - # REX.W + 8B /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8b, - mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg], - disp: imm32(src_disp), - ) - # MOV r64, r/m64 (Mod 11: reg) - in R64 => src_reg - # REX.W + 8B /r - # RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x8b, - mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg], - ) - # MOV r/m64, imm32 (Mod 11: reg) - in IMM32 => src_imm - # REX.W + C7 /0 id - # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64 - insn( - prefix: REX_W, - opcode: 0xc7, - mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg], - imm: imm32(src_imm), - ) - # MOV r64, imm64 - in IMM64 => src_imm - # REX.W + B8+ rd io - # OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64 - insn( - prefix: REX_W, - opcode: 0xb8, - rd: dst_reg, - imm: imm64(src_imm), - ) - end - in DwordPtr[R64 => dst_reg, IMM8 => dst_disp] - case src - # MOV r/m32, imm32 (Mod 01: [reg]+disp8) - in IMM32 => src_imm - # C7 /0 id - # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64 - insn( - opcode: 0xc7, - mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg], - disp: dst_disp, - imm: imm32(src_imm), - ) - end - in QwordPtr[R64 => dst_reg] - case src - # MOV r/m64, imm32 (Mod 00: [reg]) - in IMM32 => src_imm - # REX.W + C7 /0 id - # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64 - insn( - prefix: REX_W, - opcode: 0xc7, - mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg], - imm: imm32(src_imm), - ) - # MOV r/m64, r64 (Mod 00: [reg]) - in R64 => src_reg - # REX.W + 89 /r - # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x89, - mod_rm: ModRM[mod: Mod00, reg: src_reg, rm: dst_reg], - ) - end - in QwordPtr[R64 => dst_reg, IMM8 => dst_disp] - # Optimize encoding when disp is 0 - return mov([dst_reg], src) if dst_disp == 0 - - case src - # MOV r/m64, imm32 (Mod 01: [reg]+disp8) - in IMM32 => src_imm - # REX.W + C7 /0 id - # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64 - insn( - prefix: REX_W, - opcode: 0xc7, - mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg], - disp: dst_disp, - imm: imm32(src_imm), - ) - # MOV r/m64, r64 (Mod 01: [reg]+disp8) - in R64 => src_reg - # REX.W + 89 /r - # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x89, - mod_rm: ModRM[mod: Mod01, reg: src_reg, rm: dst_reg], - disp: dst_disp, - ) - end - in QwordPtr[R64 => dst_reg, IMM32 => dst_disp] - case src - # MOV r/m64, imm32 (Mod 10: [reg]+disp32) - in IMM32 => src_imm - # REX.W + C7 /0 id - # MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64 - insn( - prefix: REX_W, - opcode: 0xc7, - mod_rm: ModRM[mod: Mod10, reg: 0, rm: dst_reg], - disp: imm32(dst_disp), - imm: imm32(src_imm), - ) - # MOV r/m64, r64 (Mod 10: [reg]+disp32) - in R64 => src_reg - # REX.W + 89 /r - # MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x89, - mod_rm: ModRM[mod: Mod10, reg: src_reg, rm: dst_reg], - disp: imm32(dst_disp), - ) - end - end - end - - def or(dst, src) - case [dst, src] - # OR r/m64, imm8 (Mod 11: reg) - in [R64 => dst_reg, IMM8 => src_imm] - # REX.W + 83 /1 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg], - imm: imm8(src_imm), - ) - # OR r/m64, imm32 (Mod 11: reg) - in [R64 => dst_reg, IMM32 => src_imm] - # REX.W + 81 /1 id - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x81, - mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg], - imm: imm32(src_imm), - ) - # OR r64, r/m64 (Mod 01: [reg]+disp8) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]] - # REX.W + 0B /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x0b, - mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg], - disp: imm8(src_disp), - ) - # OR r64, r/m64 (Mod 10: [reg]+disp32) - in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM32 => src_disp]] - # REX.W + 0B /r - # RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r) - insn( - prefix: REX_W, - opcode: 0x0b, - mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg], - disp: imm32(src_disp), - ) - end - end - - def push(src) - case src - # PUSH r64 - in R64 => src_reg - # 50+rd - # O: Operand 1: opcode + rd (r) - insn(opcode: 0x50, rd: src_reg) - end - end - - def pop(dst) - case dst - # POP r64 - in R64 => dst_reg - # 58+ rd - # O: Operand 1: opcode + rd (r) - insn(opcode: 0x58, rd: dst_reg) - end - end - - def ret - # RET - # Near return: A return to a procedure within the current code segment - insn(opcode: 0xc3) - end - - def sar(dst, src) - case [dst, src] - in [R64 => dst_reg, IMM8 => src_imm] - # REX.W + C1 /7 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8 - insn( - prefix: REX_W, - opcode: 0xc1, - mod_rm: ModRM[mod: Mod11, reg: 7, rm: dst_reg], - imm: imm8(src_imm), - ) - end - end - - def sub(dst, src) - case [dst, src] - # SUB r/m64, imm8 (Mod 11: reg) - in [R64 => dst_reg, IMM8 => src_imm] - # REX.W + 83 /5 ib - # MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0x83, - mod_rm: ModRM[mod: Mod11, reg: 5, rm: dst_reg], - imm: imm8(src_imm), - ) - # SUB r/m64, r64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 29 /r - # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x29, - mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg], - ) - end - end - - def test(left, right) - case [left, right] - # TEST r/m8*, imm8 (Mod 01: [reg]+disp8) - in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm] - # REX + F6 /0 ib - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - opcode: 0xf6, - mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg], - disp: left_disp, - imm: imm8(right_imm), - ) - # TEST r/m64, imm32 (Mod 01: [reg]+disp8) - in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm] - # REX.W + F7 /0 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0xf7, - mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg], - disp: left_disp, - imm: imm32(right_imm), - ) - # TEST r/m64, imm32 (Mod 10: [reg]+disp32) - in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM32 => right_imm] - # REX.W + F7 /0 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0xf7, - mod_rm: ModRM[mod: Mod10, reg: 0, rm: left_reg], - disp: imm32(left_disp), - imm: imm32(right_imm), - ) - # TEST r/m64, imm32 (Mod 11: reg) - in [R64 => left_reg, IMM32 => right_imm] - # REX.W + F7 /0 id - # MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32 - insn( - prefix: REX_W, - opcode: 0xf7, - mod_rm: ModRM[mod: Mod11, reg: 0, rm: left_reg], - imm: imm32(right_imm), - ) - # TEST r/m32, r32 (Mod 11: reg) - in [R32 => left_reg, R32 => right_reg] - # 85 /r - # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) - insn( - opcode: 0x85, - mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg], - ) - # TEST r/m64, r64 (Mod 11: reg) - in [R64 => left_reg, R64 => right_reg] - # REX.W + 85 /r - # MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x85, - mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg], - ) - end - end - - def xor(dst, src) - case [dst, src] - # XOR r/m64, r64 (Mod 11: reg) - in [R64 => dst_reg, R64 => src_reg] - # REX.W + 31 /r - # MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r) - insn( - prefix: REX_W, - opcode: 0x31, - mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg], - ) - end - end - - # - # Utilities - # - - attr_reader :comments - - def comment(message) - @comments[@bytes.size] << message - end - - # Mark the starting address of a block - def block(block) - @blocks[@bytes.size] << block - end - - # Mark the starting/ending addresses of a stub - def stub(stub) - @stub_starts[@bytes.size] << stub - yield - ensure - @stub_ends[@bytes.size] << stub - end - - def pos_marker(&block) - @pos_markers[@bytes.size] << block - end - - def new_label(name) - Label.new(id: @label_id += 1, name:) - end - - # @param [RubyVM::RJIT::Assembler::Label] label - def write_label(label) - @labels[label] = @bytes.size - end - - def incr_counter(name) - if C.rjit_opts.stats - comment("increment counter #{name}") - mov(:rax, C.rb_rjit_counters[name].to_i) - add([:rax], 1) # TODO: lock - end - end - - private - - def insn(prefix: 0, opcode:, rd: nil, mod_rm: nil, disp: nil, imm: nil) - # Determine prefix - if rd - prefix |= REX_B if extended_reg?(rd) - opcode += reg_code(rd) - end - if mod_rm - prefix |= REX_R if mod_rm.reg.is_a?(Symbol) && extended_reg?(mod_rm.reg) - prefix |= REX_B if mod_rm.rm.is_a?(Symbol) && extended_reg?(mod_rm.rm) - end - - # Encode insn - if prefix > 0 - @bytes.push(prefix) - end - @bytes.push(*Array(opcode)) - if mod_rm - mod_rm_byte = encode_mod_rm( - mod: mod_rm.mod, - reg: mod_rm.reg.is_a?(Symbol) ? reg_code(mod_rm.reg) : mod_rm.reg, - rm: mod_rm.rm.is_a?(Symbol) ? reg_code(mod_rm.rm) : mod_rm.rm, - ) - @bytes.push(mod_rm_byte) - end - if disp - @bytes.push(*Array(disp)) - end - if imm - @bytes.push(*imm) - end - end - - def reg_code(reg) - reg_code_extended(reg).first - end - - # Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte - # - # 7 6 5 4 3 2 1 0 - # +--+--+--+--+--+--+--+--+ - # | Mod | Reg/ | R/M | - # | | Opcode | | - # +--+--+--+--+--+--+--+--+ - # - # The r/m field can specify a register as an operand or it can be combined - # with the mod field to encode an addressing mode. - # - # /0: R/M is 0 (not used) - # /r: R/M is a register - def encode_mod_rm(mod:, reg: 0, rm: 0) - if mod > 0b11 - raise ArgumentError, "too large Mod: #{mod}" - end - if reg > 0b111 - raise ArgumentError, "too large Reg/Opcode: #{reg}" - end - if rm > 0b111 - raise ArgumentError, "too large R/M: #{rm}" - end - (mod << 6) + (reg << 3) + rm - end - - # ib: 1 byte - def imm8(imm) - unless imm8?(imm) - raise ArgumentError, "unexpected imm8: #{imm}" - end - [imm].pack('c').unpack('c*') # TODO: consider uimm - end - - # id: 4 bytes - def imm32(imm) - unless imm32?(imm) - raise ArgumentError, "unexpected imm32: #{imm}" - end - [imm].pack('l').unpack('c*') # TODO: consider uimm - end - - # io: 8 bytes - def imm64(imm) - unless imm64?(imm) - raise ArgumentError, "unexpected imm64: #{imm}" - end - imm_bytes(imm, 8) - end - - def imm_bytes(imm, num_bytes) - bytes = [] - bits = imm - num_bytes.times do - bytes << (bits & 0xff) - bits >>= 8 - end - if bits != 0 - raise ArgumentError, "unexpected imm with #{num_bytes} bytes: #{imm}" - end - bytes - end - - def rel32(addr) - [Rel32.new(addr), Rel32Pad, Rel32Pad, Rel32Pad] - end - - def set_code_addrs(write_addr) - (@bytes.size + 1).times do |index| - @blocks.fetch(index, []).each do |block| - block.start_addr = write_addr + index - end - @stub_starts.fetch(index, []).each do |stub| - stub.start_addr = write_addr + index - end - @stub_ends.fetch(index, []).each do |stub| - stub.end_addr = write_addr + index - end - end - end - - def resolve_rel32(write_addr) - @bytes.each_with_index do |byte, index| - if byte.is_a?(Rel32) - src_addr = write_addr + index + 4 # offset 4 bytes for rel32 itself - dst_addr = byte.addr - rel32 = dst_addr - src_addr - raise "unexpected offset: #{rel32}" unless imm32?(rel32) - imm32(rel32).each_with_index do |rel_byte, rel_index| - @bytes[index + rel_index] = rel_byte - end - end - end - end - - def resolve_labels - @bytes.each_with_index do |byte, index| - if byte.is_a?(Label) - src_index = index + 1 # offset 1 byte for rel8 itself - dst_index = @labels.fetch(byte) - rel8 = dst_index - src_index - raise "unexpected offset: #{rel8}" unless imm8?(rel8) - @bytes[index] = rel8 - end - end - end - - def write_bytes(addr) - Fiddle::Pointer.new(addr)[0, @bytes.size] = @bytes.pack('c*') - end - end - - module OperandMatcher - def imm8?(imm) - (-0x80..0x7f).include?(imm) - end - - def imm32?(imm) - (-0x8000_0000..0x7fff_ffff).include?(imm) # TODO: consider uimm - end - - def imm64?(imm) - (-0x8000_0000_0000_0000..0xffff_ffff_ffff_ffff).include?(imm) - end - - def r32?(reg) - if extended_reg?(reg) - reg.end_with?('d') - else - reg.start_with?('e') - end - end - - def r64?(reg) - if extended_reg?(reg) - reg.match?(/\Ar\d+\z/) - else - reg.start_with?('r') - end - end - - def extended_reg?(reg) - reg_code_extended(reg).last - end - - def reg_code_extended(reg) - case reg - # Not extended - when :al, :ax, :eax, :rax then [0, false] - when :cl, :cx, :ecx, :rcx then [1, false] - when :dl, :dx, :edx, :rdx then [2, false] - when :bl, :bx, :ebx, :rbx then [3, false] - when :ah, :sp, :esp, :rsp then [4, false] - when :ch, :bp, :ebp, :rbp then [5, false] - when :dh, :si, :esi, :rsi then [6, false] - when :bh, :di, :edi, :rdi then [7, false] - # Extended - when :r8b, :r8w, :r8d, :r8 then [0, true] - when :r9b, :r9w, :r9d, :r9 then [1, true] - when :r10b, :r10w, :r10d, :r10 then [2, true] - when :r11b, :r11w, :r11d, :r11 then [3, true] - when :r12b, :r12w, :r12d, :r12 then [4, true] - when :r13b, :r13w, :r13d, :r13 then [5, true] - when :r14b, :r14w, :r14d, :r14 then [6, true] - when :r15b, :r15w, :r15d, :r15 then [7, true] - else raise ArgumentError, "unexpected reg: #{reg.inspect}" - end - end - end - - class Assembler - include OperandMatcher - extend OperandMatcher - end -end diff --git a/lib/ruby_vm/rjit/block.rb b/lib/ruby_vm/rjit/block.rb deleted file mode 100644 index cfdaade8b1..0000000000 --- a/lib/ruby_vm/rjit/block.rb +++ /dev/null @@ -1,11 +0,0 @@ -class RubyVM::RJIT::Block < Struct.new( - :iseq, # @param `` - :pc, # @param [Integer] Starting PC - :ctx, # @param [RubyVM::RJIT::Context] **Starting** Context (TODO: freeze?) - :start_addr, # @param [Integer] Starting address of this block's JIT code - :entry_exit, # @param [Integer] Address of entry exit (optional) - :incoming, # @param [Array<RubyVM::RJIT::BranchStub>] Incoming branches - :invalidated, # @param [TrueClass,FalseClass] true if already invalidated -) - def initialize(incoming: [], invalidated: false, **) = super -end diff --git a/lib/ruby_vm/rjit/branch_stub.rb b/lib/ruby_vm/rjit/branch_stub.rb deleted file mode 100644 index b9fe78b744..0000000000 --- a/lib/ruby_vm/rjit/branch_stub.rb +++ /dev/null @@ -1,24 +0,0 @@ -module RubyVM::RJIT - # Branch shapes - Next0 = :Next0 # target0 is a fallthrough - Next1 = :Next1 # target1 is a fallthrough - Default = :Default # neither targets is a fallthrough - - class BranchStub < Struct.new( - :iseq, # @param [RubyVM::RJIT::CPointer::Struct_rb_iseq_struct] Branch target ISEQ - :shape, # @param [Symbol] Next0, Next1, or Default - :target0, # @param [RubyVM::RJIT::BranchTarget] First branch target - :target1, # @param [RubyVM::RJIT::BranchTarget,NilClass] Second branch target (optional) - :compile, # @param [Proc] A callback to (re-)generate this branch stub - :start_addr, # @param [Integer] Stub source start address to be re-generated - :end_addr, # @param [Integer] Stub source end address to be re-generated - ) - end - - class BranchTarget < Struct.new( - :pc, - :ctx, - :address, - ) - end -end diff --git a/lib/ruby_vm/rjit/c_pointer.rb b/lib/ruby_vm/rjit/c_pointer.rb deleted file mode 100644 index db00c4cd11..0000000000 --- a/lib/ruby_vm/rjit/c_pointer.rb +++ /dev/null @@ -1,394 +0,0 @@ -module RubyVM::RJIT - # Every class under this namespace is a pointer. Even if the type is - # immediate, it shouldn't be dereferenced until `*` is called. - module CPointer - # Note: We'd like to avoid alphabetic method names to avoid a conflict - # with member methods. to_i and to_s are considered an exception. - class Struct - # @param name [String] - # @param sizeof [Integer] - # @param members [Hash{ Symbol => [RubyVM::RJIT::CType::*, Integer, TrueClass] }] - def initialize(addr, sizeof, members) - @addr = addr - @sizeof = sizeof - @members = members - end - - # Get a raw address - def to_i - @addr - end - - # Serialized address for generated code - def to_s - "0x#{@addr.to_s(16)}" - end - - # Pointer diff - def -(struct) - raise ArgumentError if self.class != struct.class - (@addr - struct.to_i) / @sizeof - end - - # Primitive API that does no automatic dereference - # TODO: remove this? - # @param member [Symbol] - def [](member) - type, offset = @members.fetch(member) - type.new(@addr + offset / 8) - end - - private - - # @param member [Symbol] - # @param value [Object] - def []=(member, value) - type, offset = @members.fetch(member) - type[@addr + offset / 8] = value - end - - # @param size [Integer] - # @param members [Hash{ Symbol => [Integer, RubyVM::RJIT::CType::*] }] - def self.define(size, members) - Class.new(self) do - # Return the size of this type - define_singleton_method(:size) { size } - - # Return the offset to a field - define_singleton_method(:offsetof) do |field, *fields| - member, offset = members.fetch(field) - offset /= 8 - unless fields.empty? - offset += member.offsetof(*fields) - end - offset - end - - # Return member names - define_singleton_method(:members) { members.keys } - - define_method(:initialize) do |addr = nil| - if addr.nil? # TODO: get rid of this feature later - addr = Fiddle.malloc(size) - end - super(addr, size, members) - end - - members.each do |member, (type, offset, to_ruby)| - # Intelligent API that does automatic dereference - define_method(member) do - value = self[member] - if value.respond_to?(:*) - value = value.* - end - if to_ruby - value = C.to_ruby(value) - end - value - end - - define_method("#{member}=") do |value| - if to_ruby - value = C.to_value(value) - end - self[member] = value - end - end - end - end - end - - # Note: We'd like to avoid alphabetic method names to avoid a conflict - # with member methods. to_i is considered an exception. - class Union - # @param _name [String] To be used when it starts defining a union pointer class - # @param sizeof [Integer] - # @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }] - def initialize(addr, sizeof, members) - @addr = addr - @sizeof = sizeof - @members = members - end - - # Get a raw address - def to_i - @addr - end - - # Move addr to access this pointer like an array - def +(index) - raise ArgumentError unless index.is_a?(Integer) - self.class.new(@addr + index * @sizeof) - end - - # Pointer diff - def -(union) - raise ArgumentError if self.class != union.class - (@addr - union.instance_variable_get(:@addr)) / @sizeof - end - - # @param sizeof [Integer] - # @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }] - def self.define(sizeof, members) - Class.new(self) do - # Return the size of this type - define_singleton_method(:sizeof) { sizeof } - - # Part of Struct's offsetof implementation - define_singleton_method(:offsetof) do |field, *fields| - member = members.fetch(field) - offset = 0 - unless fields.empty? - offset += member.offsetof(*fields) - end - offset - end - - define_method(:initialize) do |addr| - super(addr, sizeof, members) - end - - members.each do |member, type| - # Intelligent API that does automatic dereference - define_method(member) do - value = type.new(@addr) - if value.respond_to?(:*) - value = value.* - end - value - end - end - end - end - end - - class Immediate - # @param addr [Integer] - # @param size [Integer] - # @param pack [String] - def initialize(addr, size, pack) - @addr = addr - @size = size - @pack = pack - end - - # Get a raw address - def to_i - @addr - end - - # Move addr to addess this pointer like an array - def +(index) - Immediate.new(@addr + index * @size, @size, @pack) - end - - # Dereference - def * - self[0] - end - - # Array access - def [](index) - return nil if @addr == 0 - Fiddle::Pointer.new(@addr + index * @size)[0, @size].unpack1(@pack) - end - - # Array set - def []=(index, value) - Fiddle::Pointer.new(@addr + index * @size)[0, @size] = [value].pack(@pack) - end - - # Serialized address for generated code. Used for embedding things like body->iseq_encoded. - def to_s - "0x#{Integer(@addr).to_s(16)}" - end - - # @param fiddle_type [Integer] Fiddle::TYPE_* - def self.define(fiddle_type) - size = Fiddle::PackInfo::SIZE_MAP.fetch(fiddle_type) - pack = Fiddle::PackInfo::PACK_MAP.fetch(fiddle_type) - - Class.new(self) do - define_method(:initialize) do |addr| - super(addr, size, pack) - end - - define_singleton_method(:size) do - size - end - - # Type-level []=: Used by struct fields - define_singleton_method(:[]=) do |addr, value| - Fiddle::Pointer.new(addr)[0, size] = [value].pack(pack) - end - end - end - end - - # -Fiddle::TYPE_CHAR Immediate with special handling of true/false - class Bool < Immediate.define(-Fiddle::TYPE_CHAR) - # Dereference - def * - return nil if @addr == 0 - super != 0 - end - - def self.[]=(addr, value) - super(addr, value ? 1 : 0) - end - end - - # Basically Immediate but without #* to skip auto-dereference of structs. - class Array - attr_reader :type - - # @param addr [Integer] - # @param type [Class] RubyVM::RJIT::CType::* - def initialize(addr, type) - @addr = addr - @type = type - end - - # Array access - def [](index) - @type.new(@addr)[index] - end - - # Array set - # @param index [Integer] - # @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself or an object that return an address with to_i - def []=(index, value) - @type.new(@addr)[index] = value - end - - private - - def self.define(block) - Class.new(self) do - define_method(:initialize) do |addr| - super(addr, block.call) - end - end - end - end - - class Pointer - attr_reader :type - - # @param addr [Integer] - # @param type [Class] RubyVM::RJIT::CType::* - def initialize(addr, type) - @addr = addr - @type = type - end - - # Move addr to addess this pointer like an array - def +(index) - raise ArgumentError unless index.is_a?(Integer) - Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP, @type) - end - - # Dereference - def * - return nil if dest_addr == 0 - @type.new(dest_addr) - end - - # Array access - def [](index) - (self + index).* - end - - # Array set - # @param index [Integer] - # @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself or an object that return an address with to_i - def []=(index, value) - Fiddle::Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP)[0, Fiddle::SIZEOF_VOIDP] = - [value.to_i].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP]) - end - - # Get a raw address - def to_i - @addr - end - - private - - def dest_addr - Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_VOIDP].unpack1(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP]) - end - - def self.define(block) - Class.new(self) do - define_method(:initialize) do |addr| - super(addr, block.call) - end - - # Type-level []=: Used by struct fields - # @param addr [Integer] - # @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself, or an object that return an address with to_i - define_singleton_method(:[]=) do |addr, value| - value = value.to_i - Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP] = [value].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP]) - end - end - end - end - - class BitField - # @param addr [Integer] - # @param width [Integer] - # @param offset [Integer] - def initialize(addr, width, offset) - @addr = addr - @width = width - @offset = offset - end - - # Dereference - def * - byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack('c').first - if @width == 1 - bit = (1 & (byte >> @offset)) - bit == 1 - elsif @width <= 8 && @offset == 0 - bitmask = @width.times.map { |i| 1 << i }.sum - byte & bitmask - else - raise NotImplementedError.new("not-implemented bit field access: width=#{@width} offset=#{@offset}") - end - end - - # @param width [Integer] - # @param offset [Integer] - def self.define(width, offset) - Class.new(self) do - define_method(:initialize) do |addr| - super(addr, width, offset) - end - end - end - end - - # Give a name to a dynamic CPointer class to see it on inspect - def self.with_class_name(prefix, name, cache: false, &block) - return block.call if !name.nil? && name.empty? - - # Use a cached result only if cache: true - class_name = "#{prefix}_#{name}" - klass = - if cache && self.const_defined?(class_name) - self.const_get(class_name) - else - block.call - end - - # Give it a name unless it's already defined - unless self.const_defined?(class_name) - self.const_set(class_name, klass) - end - - klass - end - end -end diff --git a/lib/ruby_vm/rjit/c_type.rb b/lib/ruby_vm/rjit/c_type.rb deleted file mode 100644 index 3b313a658b..0000000000 --- a/lib/ruby_vm/rjit/c_type.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'fiddle' -require 'fiddle/pack' -require_relative 'c_pointer' - -module RubyVM::RJIT - module CType - module Struct - # @param name [String] - # @param members [Hash{ Symbol => [Integer, RubyVM::RJIT::CType::*] }] - def self.new(name, sizeof, **members) - name = members.keys.join('_') if name.empty? - CPointer.with_class_name('Struct', name) do - CPointer::Struct.define(sizeof, members) - end - end - end - - module Union - # @param name [String] - # @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }] - def self.new(name, sizeof, **members) - name = members.keys.join('_') if name.empty? - CPointer.with_class_name('Union', name) do - CPointer::Union.define(sizeof, members) - end - end - end - - module Immediate - # @param fiddle_type [Integer] - def self.new(fiddle_type) - name = Fiddle.constants.find do |const| - const.start_with?('TYPE_') && Fiddle.const_get(const) == fiddle_type.abs - end&.to_s - name.delete_prefix!('TYPE_') - if fiddle_type.negative? - name.prepend('U') - end - CPointer.with_class_name('Immediate', name, cache: true) do - CPointer::Immediate.define(fiddle_type) - end - end - - # @param type [String] - def self.parse(ctype) - new(Fiddle::Importer.parse_ctype(ctype)) - end - - def self.find(size, signed) - fiddle_type = TYPE_MAP.fetch(size) - fiddle_type = -fiddle_type unless signed - new(fiddle_type) - end - - TYPE_MAP = Fiddle::PackInfo::SIZE_MAP.map { |type, size| [size, type.abs] }.to_h - private_constant :TYPE_MAP - end - - module Bool - def self.new - CPointer::Bool - end - end - - class Array - def self.new(&block) - CPointer.with_class_name('Array', block.object_id.to_s) do - CPointer::Array.define(block) - end - end - end - - class Pointer - # This takes a block to avoid "stack level too deep" on a cyclic reference - # @param block [Proc] - def self.new(&block) - CPointer.with_class_name('Pointer', block.object_id.to_s) do - CPointer::Pointer.define(block) - end - end - end - - module BitField - # @param width [Integer] - # @param offset [Integer] - def self.new(width, offset) - CPointer.with_class_name('BitField', "#{offset}_#{width}") do - CPointer::BitField.define(width, offset) - end - end - end - - # Types that are referenced but not part of code generation targets - Stub = ::Struct.new(:name) - - # Types that it failed to figure out from the header - Unknown = Module.new - end -end diff --git a/lib/ruby_vm/rjit/code_block.rb b/lib/ruby_vm/rjit/code_block.rb deleted file mode 100644 index 260bd98671..0000000000 --- a/lib/ruby_vm/rjit/code_block.rb +++ /dev/null @@ -1,91 +0,0 @@ -module RubyVM::RJIT - class CodeBlock - # @param mem_block [Integer] JIT buffer address - # @param mem_size [Integer] JIT buffer size - # @param outliend [TrueClass,FalseClass] true for outlined CodeBlock - def initialize(mem_block:, mem_size:, outlined: false) - @comments = Hash.new { |h, k| h[k] = [] } if dump_disasm? - @mem_block = mem_block - @mem_size = mem_size - @write_pos = 0 - @outlined = outlined - end - - # @param asm [RubyVM::RJIT::Assembler] - def write(asm) - return 0 if @write_pos + asm.size >= @mem_size - - start_addr = write_addr - - # Write machine code - C.mprotect_write(@mem_block, @mem_size) - @write_pos += asm.assemble(start_addr) - C.mprotect_exec(@mem_block, @mem_size) - - end_addr = write_addr - - # Convert comment indexes to addresses - asm.comments.each do |index, comments| - @comments[start_addr + index] += comments if dump_disasm? - end - asm.comments.clear - - # Dump disasm if --rjit-dump-disasm - if C.rjit_opts.dump_disasm && start_addr < end_addr - dump_disasm(start_addr, end_addr) - end - start_addr - end - - def set_write_addr(addr) - @write_pos = addr - @mem_block - @comments.delete(addr) if dump_disasm? - end - - def with_write_addr(addr) - old_write_pos = @write_pos - set_write_addr(addr) - yield - ensure - @write_pos = old_write_pos - end - - def write_addr - @mem_block + @write_pos - end - - def include?(addr) - (@mem_block...(@mem_block + @mem_size)).include?(addr) - end - - def dump_disasm(from, to, io: STDOUT, color: true, test: false) - C.dump_disasm(from, to, test:).each do |address, mnemonic, op_str| - @comments.fetch(address, []).each do |comment| - io.puts colorize(" # #{comment}", bold: true, color:) - end - io.puts colorize(" 0x#{format("%x", address)}: #{mnemonic} #{op_str}", color:) - end - io.puts - end - - private - - def colorize(text, bold: false, color:) - return text unless color - buf = +'' - buf << "\e[1m" if bold - buf << "\e[34m" if @outlined - buf << text - buf << "\e[0m" - buf - end - - def bold(text) - "\e[1m#{text}\e[0m" - end - - def dump_disasm? - C.rjit_opts.dump_disasm - end - end -end diff --git a/lib/ruby_vm/rjit/compiler.rb b/lib/ruby_vm/rjit/compiler.rb deleted file mode 100644 index e5c3adf0ec..0000000000 --- a/lib/ruby_vm/rjit/compiler.rb +++ /dev/null @@ -1,518 +0,0 @@ -require 'ruby_vm/rjit/assembler' -require 'ruby_vm/rjit/block' -require 'ruby_vm/rjit/branch_stub' -require 'ruby_vm/rjit/code_block' -require 'ruby_vm/rjit/context' -require 'ruby_vm/rjit/entry_stub' -require 'ruby_vm/rjit/exit_compiler' -require 'ruby_vm/rjit/insn_compiler' -require 'ruby_vm/rjit/instruction' -require 'ruby_vm/rjit/invariants' -require 'ruby_vm/rjit/jit_state' -require 'ruby_vm/rjit/type' - -module RubyVM::RJIT - # Compilation status - KeepCompiling = :KeepCompiling - CantCompile = :CantCompile - EndBlock = :EndBlock - - # Ruby constants - Qtrue = Fiddle::Qtrue - Qfalse = Fiddle::Qfalse - Qnil = Fiddle::Qnil - Qundef = Fiddle::Qundef - - # Callee-saved registers - # TODO: support using r12/r13 here - EC = :r14 - CFP = :r15 - SP = :rbx - - # Scratch registers: rax, rcx, rdx - - # Mark objects in this Array during GC - GC_REFS = [] - - # Maximum number of versions per block - # 1 means always create generic versions - MAX_VERSIONS = 4 - - class Compiler - attr_accessor :write_pos - - def self.decode_insn(encoded) - INSNS.fetch(C.rb_vm_insn_decode(encoded)) - end - - def initialize - mem_size = C.rjit_opts.exec_mem_size * 1024 * 1024 - mem_block = C.mmap(mem_size) - @cb = CodeBlock.new(mem_block: mem_block, mem_size: mem_size / 2) - @ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true) - @exit_compiler = ExitCompiler.new - @insn_compiler = InsnCompiler.new(@cb, @ocb, @exit_compiler) - Invariants.initialize(@cb, @ocb, self, @exit_compiler) - end - - # Compile an ISEQ from its entry point. - # @param iseq `RubyVM::RJIT::CPointer::Struct_rb_iseq_t` - # @param cfp `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t` - def compile(iseq, cfp) - return unless supported_platform? - pc = cfp.pc.to_i - jit = JITState.new(iseq:, cfp:) - asm = Assembler.new - compile_prologue(asm, iseq, pc) - compile_block(asm, jit:, pc:) - iseq.body.jit_entry = @cb.write(asm) - rescue Exception => e - STDERR.puts "#{e.class}: #{e.message}" - STDERR.puts e.backtrace - exit 1 - end - - # Compile an entry. - # @param entry [RubyVM::RJIT::EntryStub] - def entry_stub_hit(entry_stub, cfp) - # Compile a new entry guard as a next entry - pc = cfp.pc.to_i - next_entry = Assembler.new.then do |asm| - compile_entry_chain_guard(asm, cfp.iseq, pc) - @cb.write(asm) - end - - # Try to find an existing compiled version of this block - ctx = Context.new - block = find_block(cfp.iseq, pc, ctx) - if block - # If an existing block is found, generate a jump to the block. - asm = Assembler.new - asm.jmp(block.start_addr) - @cb.write(asm) - else - # If this block hasn't yet been compiled, generate blocks after the entry guard. - asm = Assembler.new - jit = JITState.new(iseq: cfp.iseq, cfp:) - compile_block(asm, jit:, pc:, ctx:) - @cb.write(asm) - - block = jit.block - end - - # Regenerate the previous entry - @cb.with_write_addr(entry_stub.start_addr) do - # The last instruction of compile_entry_chain_guard is jne - asm = Assembler.new - asm.jne(next_entry) - @cb.write(asm) - end - - return block.start_addr - rescue Exception => e - STDERR.puts e.full_message - exit 1 - end - - # Compile a branch stub. - # @param branch_stub [RubyVM::RJIT::BranchStub] - # @param cfp `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t` - # @param target0_p [TrueClass,FalseClass] - # @return [Integer] The starting address of the compiled branch stub - def branch_stub_hit(branch_stub, cfp, target0_p) - # Update cfp->pc for `jit.at_current_insn?` - target = target0_p ? branch_stub.target0 : branch_stub.target1 - cfp.pc = target.pc - - # Reuse an existing block if it already exists - block = find_block(branch_stub.iseq, target.pc, target.ctx) - - # If the branch stub's jump is the last code, allow overwriting part of - # the old branch code with the new block code. - fallthrough = block.nil? && @cb.write_addr == branch_stub.end_addr - if fallthrough - # If the branch stub's jump is the last code, allow overwriting part of - # the old branch code with the new block code. - @cb.set_write_addr(branch_stub.start_addr) - branch_stub.shape = target0_p ? Next0 : Next1 - Assembler.new.tap do |branch_asm| - branch_stub.compile.call(branch_asm) - @cb.write(branch_asm) - end - end - - # Reuse or generate a block - if block - target.address = block.start_addr - else - jit = JITState.new(iseq: branch_stub.iseq, cfp:) - target.address = Assembler.new.then do |asm| - compile_block(asm, jit:, pc: target.pc, ctx: target.ctx.dup) - @cb.write(asm) - end - block = jit.block - end - block.incoming << branch_stub # prepare for invalidate_block - - # Re-generate the branch code for non-fallthrough cases - unless fallthrough - @cb.with_write_addr(branch_stub.start_addr) do - branch_asm = Assembler.new - branch_stub.compile.call(branch_asm) - @cb.write(branch_asm) - end - end - - return target.address - rescue Exception => e - STDERR.puts e.full_message - exit 1 - end - - # @param iseq `RubyVM::RJIT::CPointer::Struct_rb_iseq_t` - # @param pc [Integer] - def invalidate_blocks(iseq, pc) - list_blocks(iseq, pc).each do |block| - invalidate_block(block) - end - - # If they were the ISEQ's first blocks, re-compile RJIT entry as well - if iseq.body.iseq_encoded.to_i == pc - iseq.body.jit_entry = 0 - iseq.body.jit_entry_calls = 0 - end - end - - def invalidate_block(block) - iseq = block.iseq - # Avoid touching GCed ISEQs. We assume it won't be re-entered. - return unless C.imemo_type_p(iseq, C.imemo_iseq) - - # Remove this block from the version array - remove_block(iseq, block) - - # Invalidate the block with entry exit - unless block.invalidated - @cb.with_write_addr(block.start_addr) do - asm = Assembler.new - asm.comment('invalidate_block') - asm.jmp(block.entry_exit) - @cb.write(asm) - end - block.invalidated = true - end - - # Re-stub incoming branches - block.incoming.each do |branch_stub| - target = [branch_stub.target0, branch_stub.target1].compact.find do |target| - target.pc == block.pc && target.ctx == block.ctx - end - next if target.nil? - # TODO: Could target.address be a stub address? Is invalidation not needed in that case? - - # If the target being re-generated is currently a fallthrough block, - # the fallthrough code must be rewritten with a jump to the stub. - if target.address == branch_stub.end_addr - branch_stub.shape = Default - end - - target.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(block.ctx, ocb_asm, branch_stub, target == branch_stub.target0) - @ocb.write(ocb_asm) - end - @cb.with_write_addr(branch_stub.start_addr) do - branch_asm = Assembler.new - branch_stub.compile.call(branch_asm) - @cb.write(branch_asm) - end - end - end - - private - - # Callee-saved: rbx, rsp, rbp, r12, r13, r14, r15 - # Caller-saved: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11 - # - # @param asm [RubyVM::RJIT::Assembler] - def compile_prologue(asm, iseq, pc) - asm.comment('RJIT entry point') - - # Save callee-saved registers used by JITed code - asm.push(CFP) - asm.push(EC) - asm.push(SP) - - # Move arguments EC and CFP to dedicated registers - asm.mov(EC, :rdi) - asm.mov(CFP, :rsi) - - # Load sp to a dedicated register - asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # rbx = cfp->sp - - # Setup cfp->jit_return - asm.mov(:rax, leave_exit) - asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax) - - # We're compiling iseqs that we *expect* to start at `insn_idx`. But in - # the case of optional parameters, the interpreter can set the pc to a - # different location depending on the optional parameters. If an iseq - # has optional parameters, we'll add a runtime check that the PC we've - # compiled for is the same PC that the interpreter wants us to run with. - # If they don't match, then we'll take a side exit. - if iseq.body.param.flags.has_opt - compile_entry_chain_guard(asm, iseq, pc) - end - end - - def compile_entry_chain_guard(asm, iseq, pc) - entry_stub = EntryStub.new - stub_addr = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_entry_stub(ocb_asm, entry_stub) - @ocb.write(ocb_asm) - end - - asm.comment('guard expected PC') - asm.mov(:rax, pc) - asm.cmp([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) - - asm.stub(entry_stub) do - asm.jne(stub_addr) - end - end - - # @param asm [RubyVM::RJIT::Assembler] - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - def compile_block(asm, jit:, pc:, ctx: Context.new) - # Mark the block start address and prepare an exit code storage - ctx = limit_block_versions(jit.iseq, pc, ctx) - block = Block.new(iseq: jit.iseq, pc:, ctx: ctx.dup) - jit.block = block - asm.block(block) - - iseq = jit.iseq - asm.comment("Block: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, pc)}") - - # Compile each insn - index = (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size - while index < iseq.body.iseq_size - # Set the current instruction - insn = self.class.decode_insn(iseq.body.iseq_encoded[index]) - jit.pc = (iseq.body.iseq_encoded + index).to_i - jit.stack_size_for_pc = ctx.stack_size - jit.side_exit_for_pc.clear - - # If previous instruction requested to record the boundary - if jit.record_boundary_patch_point - # Generate an exit to this instruction and record it - exit_pos = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_side_exit(jit.pc, ctx, ocb_asm) - @ocb.write(ocb_asm) - end - Invariants.record_global_inval_patch(asm, exit_pos) - jit.record_boundary_patch_point = false - end - - # In debug mode, verify our existing assumption - if C.rjit_opts.verify_ctx && jit.at_current_insn? - verify_ctx(jit, ctx) - end - - case status = @insn_compiler.compile(jit, ctx, asm, insn) - when KeepCompiling - # For now, reset the chain depth after each instruction as only the - # first instruction in the block can concern itself with the depth. - ctx.chain_depth = 0 - - index += insn.len - when EndBlock - # TODO: pad nops if entry exit exists (not needed for x86_64?) - break - when CantCompile - # Rewind stack_size using ctx.with_stack_size to allow stack_size changes - # before you return CantCompile. - @exit_compiler.compile_side_exit(jit.pc, ctx.with_stack_size(jit.stack_size_for_pc), asm) - - # If this is the first instruction, this block never needs to be invalidated. - if block.pc == iseq.body.iseq_encoded.to_i + index * C.VALUE.size - block.invalidated = true - end - - break - else - raise "compiling #{insn.name} returned unexpected status: #{status.inspect}" - end - end - - incr_counter(:compiled_block_count) - add_block(iseq, block) - end - - def leave_exit - @leave_exit ||= Assembler.new.then do |asm| - @exit_compiler.compile_leave_exit(asm) - @ocb.write(asm) - end - end - - def incr_counter(name) - if C.rjit_opts.stats - C.rb_rjit_counters[name][0] += 1 - end - end - - # Produce a generic context when the block version limit is hit for the block - def limit_block_versions(iseq, pc, ctx) - # Guard chains implement limits separately, do nothing - if ctx.chain_depth > 0 - return ctx.dup - end - - # If this block version we're about to add will hit the version limit - if list_blocks(iseq, pc).size + 1 >= MAX_VERSIONS - # Produce a generic context that stores no type information, - # but still respects the stack_size and sp_offset constraints. - # This new context will then match all future requests. - generic_ctx = Context.new - generic_ctx.stack_size = ctx.stack_size - generic_ctx.sp_offset = ctx.sp_offset - - if ctx.diff(generic_ctx) == TypeDiff::Incompatible - raise 'should substitute a compatible context' - end - - return generic_ctx - end - - return ctx.dup - end - - def list_blocks(iseq, pc) - rjit_blocks(iseq)[pc] - end - - # @param [Integer] pc - # @param [RubyVM::RJIT::Context] ctx - # @return [RubyVM::RJIT::Block,NilClass] - def find_block(iseq, pc, ctx) - versions = rjit_blocks(iseq)[pc] - - best_version = nil - best_diff = Float::INFINITY - - versions.each do |block| - # Note that we always prefer the first matching - # version found because of inline-cache chains - case ctx.diff(block.ctx) - in TypeDiff::Compatible[diff] if diff < best_diff - best_version = block - best_diff = diff - else - end - end - - return best_version - end - - # @param [RubyVM::RJIT::Block] block - def add_block(iseq, block) - rjit_blocks(iseq)[block.pc] << block - end - - # @param [RubyVM::RJIT::Block] block - def remove_block(iseq, block) - rjit_blocks(iseq)[block.pc].delete(block) - end - - def rjit_blocks(iseq) - # Guard against ISEQ GC at random moments - - unless C.imemo_type_p(iseq, C.imemo_iseq) - return Hash.new { |h, k| h[k] = [] } - end - - unless iseq.body.rjit_blocks - iseq.body.rjit_blocks = Hash.new { |blocks, pc| blocks[pc] = [] } - # For some reason, rb_rjit_iseq_mark didn't protect this Hash - # from being freed. So we rely on GC_REFS to keep the Hash. - GC_REFS << iseq.body.rjit_blocks - end - iseq.body.rjit_blocks - end - - def iseq_lineno(iseq, pc) - C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size) - rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError) - -1 - end - - # Verify the ctx's types and mappings against the compile-time stack, self, and locals. - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - def verify_ctx(jit, ctx) - # Only able to check types when at current insn - assert(jit.at_current_insn?) - - self_val = jit.peek_at_self - self_val_type = Type.from(self_val) - - # Verify self operand type - assert_compatible(self_val_type, ctx.get_opnd_type(SelfOpnd)) - - # Verify stack operand types - [ctx.stack_size, MAX_TEMP_TYPES].min.times do |i| - learned_mapping, learned_type = ctx.get_opnd_mapping(StackOpnd[i]) - stack_val = jit.peek_at_stack(i) - val_type = Type.from(stack_val) - - case learned_mapping - in MapToSelf - if C.to_value(self_val) != C.to_value(stack_val) - raise "verify_ctx: stack value was mapped to self, but values did not match:\n"\ - "stack: #{stack_val.inspect}, self: #{self_val.inspect}" - end - in MapToLocal[local_idx] - local_val = jit.peek_at_local(local_idx) - if C.to_value(local_val) != C.to_value(stack_val) - raise "verify_ctx: stack value was mapped to local, but values did not match:\n"\ - "stack: #{stack_val.inspect}, local: #{local_val.inspect}" - end - in MapToStack - # noop - end - - # If the actual type differs from the learned type - assert_compatible(val_type, learned_type) - end - - # Verify local variable types - local_table_size = jit.iseq.body.local_table_size - [local_table_size, MAX_TEMP_TYPES].min.times do |i| - learned_type = ctx.get_local_type(i) - local_val = jit.peek_at_local(i) - local_type = Type.from(local_val) - - assert_compatible(local_type, learned_type) - end - end - - def assert_compatible(actual_type, ctx_type) - if actual_type.diff(ctx_type) == TypeDiff::Incompatible - raise "verify_ctx: ctx type (#{ctx_type.type.inspect}) is incompatible with actual type (#{actual_type.type.inspect})" - end - end - - def assert(cond) - unless cond - raise "'#{cond.inspect}' was not true" - end - end - - def supported_platform? - return @supported_platform if defined?(@supported_platform) - @supported_platform = RUBY_PLATFORM.match?(/x86_64/).tap do |supported| - warn "warning: RJIT does not support #{RUBY_PLATFORM} yet" unless supported - end - end - end -end diff --git a/lib/ruby_vm/rjit/context.rb b/lib/ruby_vm/rjit/context.rb deleted file mode 100644 index a2a7ecc6dc..0000000000 --- a/lib/ruby_vm/rjit/context.rb +++ /dev/null @@ -1,377 +0,0 @@ -module RubyVM::RJIT - # Maximum number of temp value types we keep track of - MAX_TEMP_TYPES = 8 - # Maximum number of local variable types we keep track of - MAX_LOCAL_TYPES = 8 - - # Operand to a YARV bytecode instruction - SelfOpnd = :SelfOpnd # The value is self - StackOpnd = Data.define(:index) # Temporary stack operand with stack index - - # Potential mapping of a value on the temporary stack to self, - # a local variable, or constant so that we can track its type - MapToStack = :MapToStack # Normal stack value - MapToSelf = :MapToSelf # Temp maps to the self operand - MapToLocal = Data.define(:local_index) # Temp maps to a local variable with index - - class Context < Struct.new( - :stack_size, # @param [Integer] The number of values on the stack - :sp_offset, # @param [Integer] JIT sp offset relative to the interpreter's sp - :chain_depth, # @param [Integer] jit_chain_guard depth - :local_types, # @param [Array<RubyVM::RJIT::Type>] Local variable types we keep track of - :temp_types, # @param [Array<RubyVM::RJIT::Type>] Temporary variable types we keep track of - :self_type, # @param [RubyVM::RJIT::Type] Type we track for self - :temp_mapping, # @param [Array<Symbol>] Mapping of temp stack entries to types we track - ) - def initialize( - stack_size: 0, - sp_offset: 0, - chain_depth: 0, - local_types: [Type::Unknown] * MAX_LOCAL_TYPES, - temp_types: [Type::Unknown] * MAX_TEMP_TYPES, - self_type: Type::Unknown, - temp_mapping: [MapToStack] * MAX_TEMP_TYPES - ) = super - - # Deep dup by default for safety - def dup - ctx = super - ctx.local_types = ctx.local_types.dup - ctx.temp_types = ctx.temp_types.dup - ctx.temp_mapping = ctx.temp_mapping.dup - ctx - end - - # Create a new Context instance with a given stack_size and sp_offset adjusted - # accordingly. This is useful when you want to virtually rewind a stack_size for - # generating a side exit while considering past sp_offset changes on gen_save_sp. - def with_stack_size(stack_size) - ctx = self.dup - ctx.sp_offset -= ctx.stack_size - stack_size - ctx.stack_size = stack_size - ctx - end - - def stack_opnd(depth_from_top) - [SP, C.VALUE.size * (self.sp_offset - 1 - depth_from_top)] - end - - def sp_opnd(offset_bytes = 0) - [SP, (C.VALUE.size * self.sp_offset) + offset_bytes] - end - - # Push one new value on the temp stack with an explicit mapping - # Return a pointer to the new stack top - def stack_push_mapping(mapping_temp_type) - stack_size = self.stack_size - - # Keep track of the type and mapping of the value - if stack_size < MAX_TEMP_TYPES - mapping, temp_type = mapping_temp_type - self.temp_mapping[stack_size] = mapping - self.temp_types[stack_size] = temp_type - - case mapping - in MapToLocal[idx] - assert(idx < MAX_LOCAL_TYPES) - else - end - end - - self.stack_size += 1 - self.sp_offset += 1 - - return self.stack_opnd(0) - end - - # Push one new value on the temp stack - # Return a pointer to the new stack top - def stack_push(val_type) - return self.stack_push_mapping([MapToStack, val_type]) - end - - # Push the self value on the stack - def stack_push_self - return self.stack_push_mapping([MapToStack, Type::Unknown]) - end - - # Push a local variable on the stack - def stack_push_local(local_idx) - if local_idx >= MAX_LOCAL_TYPES - return self.stack_push(Type::Unknown) - end - - return self.stack_push_mapping([MapToLocal[local_idx], Type::Unknown]) - end - - # Pop N values off the stack - # Return a pointer to the stack top before the pop operation - def stack_pop(n = 1) - assert(n <= self.stack_size) - - top = self.stack_opnd(0) - - # Clear the types of the popped values - n.times do |i| - idx = self.stack_size - i - 1 - - if idx < MAX_TEMP_TYPES - self.temp_types[idx] = Type::Unknown - self.temp_mapping[idx] = MapToStack - end - end - - self.stack_size -= n - self.sp_offset -= n - - return top - end - - def shift_stack(argc) - assert(argc < self.stack_size) - - method_name_index = self.stack_size - argc - 1 - - (method_name_index...(self.stack_size - 1)).each do |i| - if i + 1 < MAX_TEMP_TYPES - self.temp_types[i] = self.temp_types[i + 1] - self.temp_mapping[i] = self.temp_mapping[i + 1] - end - end - self.stack_pop(1) - end - - # Get the type of an instruction operand - def get_opnd_type(opnd) - case opnd - in SelfOpnd - self.self_type - in StackOpnd[idx] - assert(idx < self.stack_size) - stack_idx = self.stack_size - 1 - idx - - # If outside of tracked range, do nothing - if stack_idx >= MAX_TEMP_TYPES - return Type::Unknown - end - - mapping = self.temp_mapping[stack_idx] - - case mapping - in MapToSelf - self.self_type - in MapToStack - self.temp_types[self.stack_size - 1 - idx] - in MapToLocal[idx] - assert(idx < MAX_LOCAL_TYPES) - self.local_types[idx] - end - end - end - - # Get the currently tracked type for a local variable - def get_local_type(idx) - self.local_types[idx] || Type::Unknown - end - - # Upgrade (or "learn") the type of an instruction operand - # This value must be compatible and at least as specific as the previously known type. - # If this value originated from self, or an lvar, the learned type will be - # propagated back to its source. - def upgrade_opnd_type(opnd, opnd_type) - case opnd - in SelfOpnd - self.self_type = self.self_type.upgrade(opnd_type) - in StackOpnd[idx] - assert(idx < self.stack_size) - stack_idx = self.stack_size - 1 - idx - - # If outside of tracked range, do nothing - if stack_idx >= MAX_TEMP_TYPES - return - end - - mapping = self.temp_mapping[stack_idx] - - case mapping - in MapToSelf - self.self_type = self.self_type.upgrade(opnd_type) - in MapToStack - self.temp_types[stack_idx] = self.temp_types[stack_idx].upgrade(opnd_type) - in MapToLocal[idx] - assert(idx < MAX_LOCAL_TYPES) - self.local_types[idx] = self.local_types[idx].upgrade(opnd_type) - end - end - end - - # Get both the type and mapping (where the value originates) of an operand. - # This is can be used with stack_push_mapping or set_opnd_mapping to copy - # a stack value's type while maintaining the mapping. - def get_opnd_mapping(opnd) - opnd_type = self.get_opnd_type(opnd) - - case opnd - in SelfOpnd - return [MapToSelf, opnd_type] - in StackOpnd[idx] - assert(idx < self.stack_size) - stack_idx = self.stack_size - 1 - idx - - if stack_idx < MAX_TEMP_TYPES - return [self.temp_mapping[stack_idx], opnd_type] - else - # We can't know the source of this stack operand, so we assume it is - # a stack-only temporary. type will be UNKNOWN - assert(opnd_type == Type::Unknown) - return [MapToStack, opnd_type] - end - end - end - - # Overwrite both the type and mapping of a stack operand. - def set_opnd_mapping(opnd, mapping_opnd_type) - case opnd - in SelfOpnd - raise 'self always maps to self' - in StackOpnd[idx] - assert(idx < self.stack_size) - stack_idx = self.stack_size - 1 - idx - - # If outside of tracked range, do nothing - if stack_idx >= MAX_TEMP_TYPES - return - end - - mapping, opnd_type = mapping_opnd_type - self.temp_mapping[stack_idx] = mapping - - # Only used when mapping == MAP_STACK - self.temp_types[stack_idx] = opnd_type - end - end - - # Set the type of a local variable - def set_local_type(local_idx, local_type) - if local_idx >= MAX_LOCAL_TYPES - return - end - - # If any values on the stack map to this local we must detach them - MAX_TEMP_TYPES.times do |stack_idx| - case self.temp_mapping[stack_idx] - in MapToStack - # noop - in MapToSelf - # noop - in MapToLocal[idx] - if idx == local_idx - self.temp_types[stack_idx] = self.local_types[idx] - self.temp_mapping[stack_idx] = MapToStack - else - # noop - end - end - end - - self.local_types[local_idx] = local_type - end - - # Erase local variable type information - # eg: because of a call we can't track - def clear_local_types - # When clearing local types we must detach any stack mappings to those - # locals. Even if local values may have changed, stack values will not. - MAX_TEMP_TYPES.times do |stack_idx| - case self.temp_mapping[stack_idx] - in MapToStack - # noop - in MapToSelf - # noop - in MapToLocal[local_idx] - self.temp_types[stack_idx] = self.local_types[local_idx] - self.temp_mapping[stack_idx] = MapToStack - end - end - - # Clear the local types - self.local_types = [Type::Unknown] * MAX_LOCAL_TYPES - end - - # Compute a difference score for two context objects - def diff(dst) - # Self is the source context (at the end of the predecessor) - src = self - - # Can only lookup the first version in the chain - if dst.chain_depth != 0 - return TypeDiff::Incompatible - end - - # Blocks with depth > 0 always produce new versions - # Sidechains cannot overlap - if src.chain_depth != 0 - return TypeDiff::Incompatible - end - - if dst.stack_size != src.stack_size - return TypeDiff::Incompatible - end - - if dst.sp_offset != src.sp_offset - return TypeDiff::Incompatible - end - - # Difference sum - diff = 0 - - # Check the type of self - diff += case src.self_type.diff(dst.self_type) - in TypeDiff::Compatible[diff] then diff - in TypeDiff::Incompatible then return TypeDiff::Incompatible - end - - # For each local type we track - src.local_types.size.times do |i| - t_src = src.local_types[i] - t_dst = dst.local_types[i] - diff += case t_src.diff(t_dst) - in TypeDiff::Compatible[diff] then diff - in TypeDiff::Incompatible then return TypeDiff::Incompatible - end - end - - # For each value on the temp stack - src.stack_size.times do |i| - src_mapping, src_type = src.get_opnd_mapping(StackOpnd[i]) - dst_mapping, dst_type = dst.get_opnd_mapping(StackOpnd[i]) - - # If the two mappings aren't the same - if src_mapping != dst_mapping - if dst_mapping == MapToStack - # We can safely drop information about the source of the temp - # stack operand. - diff += 1 - else - return TypeDiff::Incompatible - end - end - - diff += case src_type.diff(dst_type) - in TypeDiff::Compatible[diff] then diff - in TypeDiff::Incompatible then return TypeDiff::Incompatible - end - end - - return TypeDiff::Compatible[diff] - end - - private - - def assert(cond) - unless cond - raise "'#{cond.inspect}' was not true" - end - end - end -end diff --git a/lib/ruby_vm/rjit/entry_stub.rb b/lib/ruby_vm/rjit/entry_stub.rb deleted file mode 100644 index 9bcef14053..0000000000 --- a/lib/ruby_vm/rjit/entry_stub.rb +++ /dev/null @@ -1,7 +0,0 @@ -module RubyVM::RJIT - class EntryStub < Struct.new( - :start_addr, # @param [Integer] Stub source start address to be re-generated - :end_addr, # @param [Integer] Stub source end address to be re-generated - ) - end -end diff --git a/lib/ruby_vm/rjit/exit_compiler.rb b/lib/ruby_vm/rjit/exit_compiler.rb deleted file mode 100644 index 1ced2141a4..0000000000 --- a/lib/ruby_vm/rjit/exit_compiler.rb +++ /dev/null @@ -1,164 +0,0 @@ -module RubyVM::RJIT - class ExitCompiler - def initialize = freeze - - # Used for invalidating a block on entry. - # @param pc [Integer] - # @param asm [RubyVM::RJIT::Assembler] - def compile_entry_exit(pc, ctx, asm, cause:) - # Fix pc/sp offsets for the interpreter - save_pc_and_sp(pc, ctx, asm, reset_sp_offset: false) - - # Increment per-insn exit counter - count_insn_exit(pc, asm) - - # Restore callee-saved registers - asm.comment("#{cause}: entry exit") - asm.pop(SP) - asm.pop(EC) - asm.pop(CFP) - - asm.mov(C_RET, Qundef) - asm.ret - end - - # Set to cfp->jit_return by default for leave insn - # @param asm [RubyVM::RJIT::Assembler] - def compile_leave_exit(asm) - asm.comment('default cfp->jit_return') - - # Restore callee-saved registers - asm.pop(SP) - asm.pop(EC) - asm.pop(CFP) - - # :rax is written by #leave - asm.ret - end - - # Fire cfunc events on invalidation by TracePoint - # @param asm [RubyVM::RJIT::Assembler] - def compile_full_cfunc_return(asm) - # This chunk of code expects REG_EC to be filled properly and - # RAX to contain the return value of the C method. - - asm.comment('full cfunc return') - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], :rax) - asm.call(C.rjit_full_cfunc_return) - - # TODO: count the exit - - # Restore callee-saved registers - asm.pop(SP) - asm.pop(EC) - asm.pop(CFP) - - asm.mov(C_RET, Qundef) - asm.ret - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def compile_side_exit(pc, ctx, asm) - # Fix pc/sp offsets for the interpreter - save_pc_and_sp(pc, ctx.dup, asm) # dup to avoid sp_offset update - - # Increment per-insn exit counter - count_insn_exit(pc, asm) - - # Restore callee-saved registers - asm.comment("exit to interpreter on #{pc_to_insn(pc).name}") - asm.pop(SP) - asm.pop(EC) - asm.pop(CFP) - - asm.mov(C_RET, Qundef) - asm.ret - end - - # @param asm [RubyVM::RJIT::Assembler] - # @param entry_stub [RubyVM::RJIT::EntryStub] - def compile_entry_stub(asm, entry_stub) - # Call rb_rjit_entry_stub_hit - asm.comment('entry stub hit') - asm.mov(C_ARGS[0], to_value(entry_stub)) - asm.call(C.rb_rjit_entry_stub_hit) - - # Jump to the address returned by rb_rjit_entry_stub_hit - asm.jmp(:rax) - end - - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - # @param branch_stub [RubyVM::RJIT::BranchStub] - # @param target0_p [TrueClass,FalseClass] - def compile_branch_stub(ctx, asm, branch_stub, target0_p) - # Call rb_rjit_branch_stub_hit - iseq = branch_stub.iseq - if C.rjit_opts.dump_disasm && C.imemo_type_p(iseq, C.imemo_iseq) # Guard against ISEQ GC at random moments - asm.comment("branch stub hit: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, target0_p ? branch_stub.target0.pc : branch_stub.target1.pc)}") - end - asm.mov(:rdi, to_value(branch_stub)) - asm.mov(:esi, ctx.sp_offset) - asm.mov(:edx, target0_p ? 1 : 0) - asm.call(C.rb_rjit_branch_stub_hit) - - # Jump to the address returned by rb_rjit_branch_stub_hit - asm.jmp(:rax) - end - - private - - def pc_to_insn(pc) - Compiler.decode_insn(C.VALUE.new(pc).*) - end - - # @param pc [Integer] - # @param asm [RubyVM::RJIT::Assembler] - def count_insn_exit(pc, asm) - if C.rjit_opts.stats - insn = Compiler.decode_insn(C.VALUE.new(pc).*) - asm.comment("increment insn exit: #{insn.name}") - asm.mov(:rax, (C.rjit_insn_exits + insn.bin).to_i) - asm.add([:rax], 1) # TODO: lock - end - if C.rjit_opts.trace_exits - asm.comment('rjit_record_exit_stack') - asm.mov(C_ARGS[0], pc) - asm.call(C.rjit_record_exit_stack) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def save_pc_and_sp(pc, ctx, asm, reset_sp_offset: true) - # Update pc (TODO: manage PC offset?) - asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP") - asm.mov(:rax, pc) # rax = jit.pc - asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax - - # Update sp - if ctx.sp_offset != 0 - asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size - asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp - if reset_sp_offset - ctx.sp_offset = 0 - end - end - end - - def to_value(obj) - GC_REFS << obj - C.to_value(obj) - end - - def iseq_lineno(iseq, pc) - C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size) - rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError) - -1 - end - end -end diff --git a/lib/ruby_vm/rjit/hooks.rb b/lib/ruby_vm/rjit/hooks.rb deleted file mode 100644 index ea9d7bf5a8..0000000000 --- a/lib/ruby_vm/rjit/hooks.rb +++ /dev/null @@ -1,36 +0,0 @@ -module RubyVM::RJIT - module Hooks # :nodoc: all - def self.on_bop_redefined(_redefined_flag, _bop) - # C.rjit_cancel_all("BOP is redefined") - end - - def self.on_cme_invalidate(cme) - cme = C.rb_callable_method_entry_struct.new(cme) - Invariants.on_cme_invalidate(cme) - end - - def self.on_ractor_spawn - # C.rjit_cancel_all("Ractor is spawned") - end - - # Global constant changes like const_set - def self.on_constant_state_changed(id) - Invariants.on_constant_state_changed(id) - end - - # ISEQ-specific constant invalidation - def self.on_constant_ic_update(iseq, ic, insn_idx) - iseq = C.rb_iseq_t.new(iseq) - ic = C.IC.new(ic) - Invariants.on_constant_ic_update(iseq, ic, insn_idx) - end - - def self.on_tracing_invalidate_all(_new_iseq_events) - Invariants.on_tracing_invalidate_all - end - - def self.on_update_references - Invariants.on_update_references - end - end -end diff --git a/lib/ruby_vm/rjit/insn_compiler.rb b/lib/ruby_vm/rjit/insn_compiler.rb deleted file mode 100644 index a33ba9f468..0000000000 --- a/lib/ruby_vm/rjit/insn_compiler.rb +++ /dev/null @@ -1,6046 +0,0 @@ -# frozen_string_literal: true -module RubyVM::RJIT - class InsnCompiler - # struct rb_calling_info. Storing flags instead of ci. - CallingInfo = Struct.new(:argc, :flags, :kwarg, :ci_addr, :send_shift, :block_handler) do - def kw_splat = flags & C::VM_CALL_KW_SPLAT != 0 - end - - # @param ocb [CodeBlock] - # @param exit_compiler [RubyVM::RJIT::ExitCompiler] - def initialize(cb, ocb, exit_compiler) - @ocb = ocb - @exit_compiler = exit_compiler - - @cfunc_codegen_table = {} - register_cfunc_codegen_funcs - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - # @param insn `RubyVM::RJIT::Instruction` - def compile(jit, ctx, asm, insn) - asm.incr_counter(:rjit_insns_count) - - stack = ctx.stack_size.times.map do |stack_idx| - ctx.get_opnd_type(StackOpnd[ctx.stack_size - stack_idx - 1]).type - end - locals = jit.iseq.body.local_table_size.times.map do |local_idx| - (ctx.local_types[local_idx] || Type::Unknown).type - end - - insn_idx = format('%04d', (jit.pc.to_i - jit.iseq.body.iseq_encoded.to_i) / C.VALUE.size) - asm.comment("Insn: #{insn_idx} #{insn.name} (stack: [#{stack.join(', ')}], locals: [#{locals.join(', ')}])") - - # 83/102 - case insn.name - when :nop then nop(jit, ctx, asm) - when :getlocal then getlocal(jit, ctx, asm) - when :setlocal then setlocal(jit, ctx, asm) - when :getblockparam then getblockparam(jit, ctx, asm) - # setblockparam - when :getblockparamproxy then getblockparamproxy(jit, ctx, asm) - when :getspecial then getspecial(jit, ctx, asm) - # setspecial - when :getinstancevariable then getinstancevariable(jit, ctx, asm) - when :setinstancevariable then setinstancevariable(jit, ctx, asm) - when :getclassvariable then getclassvariable(jit, ctx, asm) - when :setclassvariable then setclassvariable(jit, ctx, asm) - when :opt_getconstant_path then opt_getconstant_path(jit, ctx, asm) - when :getconstant then getconstant(jit, ctx, asm) - # setconstant - when :getglobal then getglobal(jit, ctx, asm) - # setglobal - when :putnil then putnil(jit, ctx, asm) - when :putself then putself(jit, ctx, asm) - when :putobject then putobject(jit, ctx, asm) - when :putspecialobject then putspecialobject(jit, ctx, asm) - when :putstring then putstring(jit, ctx, asm) - when :putchilledstring then putchilledstring(jit, ctx, asm) - when :concatstrings then concatstrings(jit, ctx, asm) - when :anytostring then anytostring(jit, ctx, asm) - when :toregexp then toregexp(jit, ctx, asm) - when :intern then intern(jit, ctx, asm) - when :newarray then newarray(jit, ctx, asm) - when :duparray then duparray(jit, ctx, asm) - # duphash - when :expandarray then expandarray(jit, ctx, asm) - when :concatarray then concatarray(jit, ctx, asm) - when :splatarray then splatarray(jit, ctx, asm) - when :newhash then newhash(jit, ctx, asm) - when :newrange then newrange(jit, ctx, asm) - when :pop then pop(jit, ctx, asm) - when :dup then dup(jit, ctx, asm) - when :dupn then dupn(jit, ctx, asm) - when :swap then swap(jit, ctx, asm) - # opt_reverse - when :topn then topn(jit, ctx, asm) - when :setn then setn(jit, ctx, asm) - when :adjuststack then adjuststack(jit, ctx, asm) - when :defined then defined(jit, ctx, asm) - when :definedivar then definedivar(jit, ctx, asm) - # checkmatch - when :checkkeyword then checkkeyword(jit, ctx, asm) - # checktype - # defineclass - # definemethod - # definesmethod - when :send then send(jit, ctx, asm) - when :opt_send_without_block then opt_send_without_block(jit, ctx, asm) - when :objtostring then objtostring(jit, ctx, asm) - when :opt_str_freeze then opt_str_freeze(jit, ctx, asm) - when :opt_ary_freeze then opt_ary_freeze(jit, ctx, asm) - when :opt_hash_freeze then opt_hash_freeze(jit, ctx, asm) - when :opt_nil_p then opt_nil_p(jit, ctx, asm) - # opt_str_uminus - when :opt_newarray_send then opt_newarray_send(jit, ctx, asm) - when :invokesuper then invokesuper(jit, ctx, asm) - when :invokeblock then invokeblock(jit, ctx, asm) - when :leave then leave(jit, ctx, asm) - when :throw then throw(jit, ctx, asm) - when :jump then jump(jit, ctx, asm) - when :branchif then branchif(jit, ctx, asm) - when :branchunless then branchunless(jit, ctx, asm) - when :branchnil then branchnil(jit, ctx, asm) - # once - when :opt_case_dispatch then opt_case_dispatch(jit, ctx, asm) - when :opt_plus then opt_plus(jit, ctx, asm) - when :opt_minus then opt_minus(jit, ctx, asm) - when :opt_mult then opt_mult(jit, ctx, asm) - when :opt_div then opt_div(jit, ctx, asm) - when :opt_mod then opt_mod(jit, ctx, asm) - when :opt_eq then opt_eq(jit, ctx, asm) - when :opt_neq then opt_neq(jit, ctx, asm) - when :opt_lt then opt_lt(jit, ctx, asm) - when :opt_le then opt_le(jit, ctx, asm) - when :opt_gt then opt_gt(jit, ctx, asm) - when :opt_ge then opt_ge(jit, ctx, asm) - when :opt_ltlt then opt_ltlt(jit, ctx, asm) - when :opt_and then opt_and(jit, ctx, asm) - when :opt_or then opt_or(jit, ctx, asm) - when :opt_aref then opt_aref(jit, ctx, asm) - when :opt_aset then opt_aset(jit, ctx, asm) - # opt_aset_with - # opt_aref_with - when :opt_length then opt_length(jit, ctx, asm) - when :opt_size then opt_size(jit, ctx, asm) - when :opt_empty_p then opt_empty_p(jit, ctx, asm) - when :opt_succ then opt_succ(jit, ctx, asm) - when :opt_not then opt_not(jit, ctx, asm) - when :opt_regexpmatch2 then opt_regexpmatch2(jit, ctx, asm) - # invokebuiltin - when :opt_invokebuiltin_delegate then opt_invokebuiltin_delegate(jit, ctx, asm) - when :opt_invokebuiltin_delegate_leave then opt_invokebuiltin_delegate_leave(jit, ctx, asm) - when :getlocal_WC_0 then getlocal_WC_0(jit, ctx, asm) - when :getlocal_WC_1 then getlocal_WC_1(jit, ctx, asm) - when :setlocal_WC_0 then setlocal_WC_0(jit, ctx, asm) - when :setlocal_WC_1 then setlocal_WC_1(jit, ctx, asm) - when :putobject_INT2FIX_0_ then putobject_INT2FIX_0_(jit, ctx, asm) - when :putobject_INT2FIX_1_ then putobject_INT2FIX_1_(jit, ctx, asm) - else CantCompile - end - end - - private - - # - # Insns - # - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def nop(jit, ctx, asm) - # Do nothing - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getlocal(jit, ctx, asm) - idx = jit.operand(0) - level = jit.operand(1) - jit_getlocal_generic(jit, ctx, asm, idx:, level:) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getlocal_WC_0(jit, ctx, asm) - idx = jit.operand(0) - jit_getlocal_generic(jit, ctx, asm, idx:, level: 0) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getlocal_WC_1(jit, ctx, asm) - idx = jit.operand(0) - jit_getlocal_generic(jit, ctx, asm, idx:, level: 1) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setlocal(jit, ctx, asm) - idx = jit.operand(0) - level = jit.operand(1) - jit_setlocal_generic(jit, ctx, asm, idx:, level:) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setlocal_WC_0(jit, ctx, asm) - idx = jit.operand(0) - jit_setlocal_generic(jit, ctx, asm, idx:, level: 0) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setlocal_WC_1(jit, ctx, asm) - idx = jit.operand(0) - jit_setlocal_generic(jit, ctx, asm, idx:, level: 1) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getblockparam(jit, ctx, asm) - # EP level - level = jit.operand(1) - - # Save the PC and SP because we might allocate - jit_prepare_routine_call(jit, ctx, asm) - - # A mirror of the interpreter code. Checking for the case - # where it's pushing rb_block_param_proxy. - side_exit = side_exit(jit, ctx) - - # Load environment pointer EP from CFP - ep_reg = :rax - jit_get_ep(asm, level, reg: ep_reg) - - # Bail when VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) is non zero - # FIXME: This is testing bits in the same place that the WB check is testing. - # We should combine these at some point - asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) - - # If the frame flag has been modified, then the actual proc value is - # already in the EP and we should just use the value. - frame_flag_modified = asm.new_label('frame_flag_modified') - asm.jnz(frame_flag_modified) - - # This instruction writes the block handler to the EP. If we need to - # fire a write barrier for the write, then exit (we'll let the - # interpreter handle it so it can fire the write barrier). - # flags & VM_ENV_FLAG_WB_REQUIRED - asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_ENV_FLAG_WB_REQUIRED) - - # if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0 - asm.jnz(side_exit) - - # Convert the block handler in to a proc - # call rb_vm_bh_to_procval(const rb_execution_context_t *ec, VALUE block_handler) - asm.mov(C_ARGS[0], EC) - # The block handler for the current frame - # note, VM_ASSERT(VM_ENV_LOCAL_P(ep)) - asm.mov(C_ARGS[1], [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) - asm.call(C.rb_vm_bh_to_procval) - - # Load environment pointer EP from CFP (again) - ep_reg = :rcx - jit_get_ep(asm, level, reg: ep_reg) - - # Write the value at the environment pointer - idx = jit.operand(0) - offs = -(C.VALUE.size * idx) - asm.mov([ep_reg, offs], C_RET); - - # Set the frame modified flag - asm.mov(:rax, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS]) # flag_check - asm.or(:rax, C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) # modified_flag - asm.mov([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], :rax) - - asm.write_label(frame_flag_modified) - - # Push the proc on the stack - stack_ret = ctx.stack_push(Type::Unknown) - ep_reg = :rax - jit_get_ep(asm, level, reg: ep_reg) - asm.mov(:rax, [ep_reg, offs]) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # setblockparam - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getblockparamproxy(jit, ctx, asm) - # To get block_handler - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - starting_context = ctx.dup # make a copy for use with jit_chain_guard - - # A mirror of the interpreter code. Checking for the case - # where it's pushing rb_block_param_proxy. - side_exit = side_exit(jit, ctx) - - # EP level - level = jit.operand(1) - - # Peek at the block handler so we can check whether it's nil - comptime_handler = jit.peek_at_block_handler(level) - - # When a block handler is present, it should always be a GC-guarded - # pointer (VM_BH_ISEQ_BLOCK_P) - if comptime_handler != 0 && comptime_handler & 0x3 != 0x1 - asm.incr_counter(:getblockpp_not_gc_guarded) - return CantCompile - end - - # Load environment pointer EP from CFP - ep_reg = :rax - jit_get_ep(asm, level, reg: ep_reg) - - # Bail when VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) is non zero - asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) - asm.jnz(counted_exit(side_exit, :getblockpp_block_param_modified)) - - # Load the block handler for the current frame - # note, VM_ASSERT(VM_ENV_LOCAL_P(ep)) - block_handler = :rax - asm.mov(block_handler, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) - - # Specialize compilation for the case where no block handler is present - if comptime_handler == 0 - # Bail if there is a block handler - asm.cmp(block_handler, 0) - - jit_chain_guard(:jnz, jit, starting_context, asm, counted_exit(side_exit, :getblockpp_block_handler_none)) - - putobject(jit, ctx, asm, val: Qnil) - else - # Block handler is a tagged pointer. Look at the tag. 0x03 is from VM_BH_ISEQ_BLOCK_P(). - asm.and(block_handler, 0x3) - - # Bail unless VM_BH_ISEQ_BLOCK_P(bh). This also checks for null. - asm.cmp(block_handler, 0x1) - - jit_chain_guard(:jnz, jit, starting_context, asm, counted_exit(side_exit, :getblockpp_not_iseq_block)) - - # Push rb_block_param_proxy. It's a root, so no need to use jit_mov_gc_ptr. - top = ctx.stack_push(Type::BlockParamProxy) - asm.mov(:rax, C.rb_block_param_proxy) - asm.mov(top, :rax) - end - - jump_to_next_insn(jit, ctx, asm) - - EndBlock - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getspecial(jit, ctx, asm) - # This takes two arguments, key and type - # key is only used when type == 0 - # A non-zero type determines which type of backref to fetch - #rb_num_t key = jit.jit_get_arg(0); - rtype = jit.operand(1) - - if rtype == 0 - # not yet implemented - return CantCompile; - elsif rtype & 0x01 != 0 - # Fetch a "special" backref based on a char encoded by shifting by 1 - - # Can raise if matchdata uninitialized - jit_prepare_routine_call(jit, ctx, asm) - - # call rb_backref_get() - asm.comment('rb_backref_get') - asm.call(C.rb_backref_get) - - asm.mov(C_ARGS[0], C_RET) # backref - case [rtype >> 1].pack('c') - in ?& - asm.comment("rb_reg_last_match") - asm.call(C.rb_reg_last_match) - in ?` - asm.comment("rb_reg_match_pre") - asm.call(C.rb_reg_match_pre) - in ?' - asm.comment("rb_reg_match_post") - asm.call(C.rb_reg_match_post) - in ?+ - asm.comment("rb_reg_match_last") - asm.call(C.rb_reg_match_last) - end - - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - else - # Fetch the N-th match from the last backref based on type shifted by 1 - - # Can raise if matchdata uninitialized - jit_prepare_routine_call(jit, ctx, asm) - - # call rb_backref_get() - asm.comment('rb_backref_get') - asm.call(C.rb_backref_get) - - # rb_reg_nth_match((int)(type >> 1), backref); - asm.comment('rb_reg_nth_match') - asm.mov(C_ARGS[0], rtype >> 1) - asm.mov(C_ARGS[1], C_RET) # backref - asm.call(C.rb_reg_nth_match) - - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - end - - # setspecial - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getinstancevariable(jit, ctx, asm) - # Specialize on a compile-time receiver, and split a block for chain guards - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - id = jit.operand(0) - comptime_obj = jit.peek_at_self - - jit_getivar(jit, ctx, asm, comptime_obj, id, nil, SelfOpnd) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setinstancevariable(jit, ctx, asm) - starting_context = ctx.dup # make a copy for use with jit_chain_guard - - # Defer compilation so we can specialize on a runtime `self` - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - ivar_name = jit.operand(0) - comptime_receiver = jit.peek_at_self - - # If the comptime receiver is frozen, writing an IV will raise an exception - # and we don't want to JIT code to deal with that situation. - if C.rb_obj_frozen_p(comptime_receiver) - asm.incr_counter(:setivar_frozen) - return CantCompile - end - - # Check if the comptime receiver is a T_OBJECT - receiver_t_object = C::BUILTIN_TYPE(comptime_receiver) == C::T_OBJECT - - # If the receiver isn't a T_OBJECT, or uses a custom allocator, - # then just write out the IV write as a function call. - # too-complex shapes can't use index access, so we use rb_ivar_get for them too. - if !receiver_t_object || shape_too_complex?(comptime_receiver) || ctx.chain_depth >= 10 - asm.comment('call rb_vm_setinstancevariable') - - ic = jit.operand(1) - - # The function could raise exceptions. - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) - - # Get the operands from the stack - val_opnd = ctx.stack_pop(1) - - # Call rb_vm_setinstancevariable(iseq, obj, id, val, ic); - asm.mov(:rdi, jit.iseq.to_i) - asm.mov(:rsi, [CFP, C.rb_control_frame_t.offsetof(:self)]) - asm.mov(:rdx, ivar_name) - asm.mov(:rcx, val_opnd) - asm.mov(:r8, ic) - asm.call(C.rb_vm_setinstancevariable) - else - # Get the iv index - shape_id = C.rb_shape_get_shape_id(comptime_receiver) - ivar_index = C.rb_shape_get_iv_index(shape_id, ivar_name) - - # Get the receiver - asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) - - # Generate a side exit - side_exit = side_exit(jit, ctx) - - # Upgrade type - guard_object_is_heap(jit, ctx, asm, :rax, SelfOpnd, :setivar_not_heap) - - asm.comment('guard shape') - asm.cmp(DwordPtr[:rax, C.rb_shape_id_offset], shape_id) - megamorphic_side_exit = counted_exit(side_exit, :setivar_megamorphic) - jit_chain_guard(:jne, jit, starting_context, asm, megamorphic_side_exit) - - # If we don't have an instance variable index, then we need to - # transition out of the current shape. - if ivar_index.nil? - shape = C.rb_shape_get_shape_by_id(shape_id) - - current_capacity = shape.capacity - dest_shape = C.rb_shape_get_next_no_warnings(shape, comptime_receiver, ivar_name) - new_shape_id = C.rb_shape_id(dest_shape) - - if new_shape_id == C::OBJ_TOO_COMPLEX_SHAPE_ID - asm.incr_counter(:setivar_too_complex) - return CantCompile - end - - ivar_index = shape.next_iv_index - - # If the new shape has a different capacity, we need to - # reallocate the object. - needs_extension = dest_shape.capacity != shape.capacity - - if needs_extension - # Generate the C call so that runtime code will increase - # the capacity and set the buffer. - asm.mov(C_ARGS[0], :rax) - asm.mov(C_ARGS[1], current_capacity) - asm.mov(C_ARGS[2], dest_shape.capacity) - asm.call(C.rb_ensure_iv_list_size) - - # Load the receiver again after the function call - asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) - end - - write_val = ctx.stack_pop(1) - jit_write_iv(asm, comptime_receiver, :rax, :rcx, ivar_index, write_val, needs_extension) - - # Store the new shape - asm.comment('write shape') - asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) # reload after jit_write_iv - asm.mov(DwordPtr[:rax, C.rb_shape_id_offset], new_shape_id) - else - # If the iv index already exists, then we don't need to - # transition to a new shape. The reason is because we find - # the iv index by searching up the shape tree. If we've - # made the transition already, then there's no reason to - # update the shape on the object. Just set the IV. - write_val = ctx.stack_pop(1) - jit_write_iv(asm, comptime_receiver, :rax, :rcx, ivar_index, write_val, false) - end - - skip_wb = asm.new_label('skip_wb') - # If the value we're writing is an immediate, we don't need to WB - asm.test(write_val, C::RUBY_IMMEDIATE_MASK) - asm.jnz(skip_wb) - - # If the value we're writing is nil or false, we don't need to WB - asm.cmp(write_val, Qnil) - asm.jbe(skip_wb) - - asm.comment('write barrier') - asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:self)]) # reload after jit_write_iv - asm.mov(C_ARGS[1], write_val) - asm.call(C.rb_gc_writebarrier) - - asm.write_label(skip_wb) - end - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getclassvariable(jit, ctx, asm) - # rb_vm_getclassvariable can raise exceptions. - jit_prepare_routine_call(jit, ctx, asm) - - asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:iseq)]) - asm.mov(C_ARGS[1], CFP) - asm.mov(C_ARGS[2], jit.operand(0)) - asm.mov(C_ARGS[3], jit.operand(1)) - asm.call(C.rb_vm_getclassvariable) - - top = ctx.stack_push(Type::Unknown) - asm.mov(top, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setclassvariable(jit, ctx, asm) - # rb_vm_setclassvariable can raise exceptions. - jit_prepare_routine_call(jit, ctx, asm) - - asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:iseq)]) - asm.mov(C_ARGS[1], CFP) - asm.mov(C_ARGS[2], jit.operand(0)) - asm.mov(C_ARGS[3], ctx.stack_pop(1)) - asm.mov(C_ARGS[4], jit.operand(1)) - asm.call(C.rb_vm_setclassvariable) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_getconstant_path(jit, ctx, asm) - # Cut the block for invalidation - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - ic = C.iseq_inline_constant_cache.new(jit.operand(0)) - idlist = ic.segments - - # Make sure there is an exit for this block as the interpreter might want - # to invalidate this block from rb_rjit_constant_ic_update(). - # For now, we always take an entry exit even if it was a side exit. - Invariants.ensure_block_entry_exit(jit, cause: 'opt_getconstant_path') - - # See vm_ic_hit_p(). The same conditions are checked in yjit_constant_ic_update(). - ice = ic.entry - if ice.nil? - # In this case, leave a block that unconditionally side exits - # for the interpreter to invalidate. - asm.incr_counter(:optgetconst_not_cached) - return CantCompile - end - - if ice.ic_cref # with cref - # Cache is keyed on a certain lexical scope. Use the interpreter's cache. - side_exit = side_exit(jit, ctx) - - # Call function to verify the cache. It doesn't allocate or call methods. - asm.mov(C_ARGS[0], ic.to_i) - asm.mov(C_ARGS[1], [CFP, C.rb_control_frame_t.offsetof(:ep)]) - asm.call(C.rb_vm_ic_hit_p) - - # Check the result. SysV only specifies one byte for _Bool return values, - # so it's important we only check one bit to ignore the higher bits in the register. - asm.test(C_RET, 1) - asm.jz(counted_exit(side_exit, :optgetconst_cache_miss)) - - asm.mov(:rax, ic.to_i) # inline_cache - asm.mov(:rax, [:rax, C.iseq_inline_constant_cache.offsetof(:entry)]) # ic_entry - asm.mov(:rax, [:rax, C.iseq_inline_constant_cache_entry.offsetof(:value)]) # ic_entry_val - - # Push ic->entry->value - stack_top = ctx.stack_push(Type::Unknown) - asm.mov(stack_top, :rax) - else # without cref - # TODO: implement this - # Optimize for single ractor mode. - # if !assume_single_ractor_mode(jit, ocb) - # return CantCompile - # end - - # Invalidate output code on any constant writes associated with - # constants referenced within the current block. - Invariants.assume_stable_constant_names(jit, idlist) - - putobject(jit, ctx, asm, val: ice.value) - end - - jump_to_next_insn(jit, ctx, asm) - EndBlock - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getconstant(jit, ctx, asm) - id = jit.operand(0) - - # vm_get_ev_const can raise exceptions. - jit_prepare_routine_call(jit, ctx, asm) - - allow_nil_opnd = ctx.stack_pop(1) - klass_opnd = ctx.stack_pop(1) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], klass_opnd) - asm.mov(C_ARGS[2], id) - asm.mov(C_ARGS[3], allow_nil_opnd) - asm.call(C.rb_vm_get_ev_const) - - top = ctx.stack_push(Type::Unknown) - asm.mov(top, C_RET) - - KeepCompiling - end - - # setconstant - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def getglobal(jit, ctx, asm) - gid = jit.operand(0) - - # Save the PC and SP because we might make a Ruby call for warning - jit_prepare_routine_call(jit, ctx, asm) - - asm.mov(C_ARGS[0], gid) - asm.call(C.rb_gvar_get) - - top = ctx.stack_push(Type::Unknown) - asm.mov(top, C_RET) - - KeepCompiling - end - - # setglobal - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putnil(jit, ctx, asm) - putobject(jit, ctx, asm, val: Qnil) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putself(jit, ctx, asm) - stack_top = ctx.stack_push_self - asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) - asm.mov(stack_top, :rax) - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putobject(jit, ctx, asm, val: jit.operand(0)) - # Push it to the stack - val_type = Type.from(C.to_ruby(val)) - stack_top = ctx.stack_push(val_type) - if asm.imm32?(val) - asm.mov(stack_top, val) - else # 64-bit immediates can't be directly written to memory - asm.mov(:rax, val) - asm.mov(stack_top, :rax) - end - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putspecialobject(jit, ctx, asm) - object_type = jit.operand(0) - if object_type == C::VM_SPECIAL_OBJECT_VMCORE - stack_top = ctx.stack_push(Type::UnknownHeap) - asm.mov(:rax, C.rb_mRubyVMFrozenCore) - asm.mov(stack_top, :rax) - KeepCompiling - else - # TODO: implement for VM_SPECIAL_OBJECT_CBASE and - # VM_SPECIAL_OBJECT_CONST_BASE - CantCompile - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putstring(jit, ctx, asm) - put_val = jit.operand(0, ruby: true) - - # Save the PC and SP because the callee will allocate - jit_prepare_routine_call(jit, ctx, asm) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], to_value(put_val)) - asm.mov(C_ARGS[2], 0) - asm.call(C.rb_ec_str_resurrect) - - stack_top = ctx.stack_push(Type::TString) - asm.mov(stack_top, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putchilledstring(jit, ctx, asm) - put_val = jit.operand(0, ruby: true) - - # Save the PC and SP because the callee will allocate - jit_prepare_routine_call(jit, ctx, asm) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], to_value(put_val)) - asm.mov(C_ARGS[2], 1) - asm.call(C.rb_ec_str_resurrect) - - stack_top = ctx.stack_push(Type::TString) - asm.mov(stack_top, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def concatstrings(jit, ctx, asm) - n = jit.operand(0) - - # Save the PC and SP because we are allocating - jit_prepare_routine_call(jit, ctx, asm) - - asm.lea(:rax, ctx.sp_opnd(-C.VALUE.size * n)) - - # call rb_str_concat_literals(size_t n, const VALUE *strings); - asm.mov(C_ARGS[0], n) - asm.mov(C_ARGS[1], :rax) - asm.call(C.rb_str_concat_literals) - - ctx.stack_pop(n) - stack_ret = ctx.stack_push(Type::TString) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def anytostring(jit, ctx, asm) - # Save the PC and SP since we might call #to_s - jit_prepare_routine_call(jit, ctx, asm) - - str = ctx.stack_pop(1) - val = ctx.stack_pop(1) - - asm.mov(C_ARGS[0], str) - asm.mov(C_ARGS[1], val) - asm.call(C.rb_obj_as_string_result) - - # Push the return value - stack_ret = ctx.stack_push(Type::TString) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def toregexp(jit, ctx, asm) - opt = jit.operand(0, signed: true) - cnt = jit.operand(1) - - # Save the PC and SP because this allocates an object and could - # raise an exception. - jit_prepare_routine_call(jit, ctx, asm) - - asm.lea(:rax, ctx.sp_opnd(-C.VALUE.size * cnt)) # values_ptr - ctx.stack_pop(cnt) - - asm.mov(C_ARGS[0], 0) - asm.mov(C_ARGS[1], cnt) - asm.mov(C_ARGS[2], :rax) # values_ptr - asm.call(C.rb_ary_tmp_new_from_values) - - # Save the array so we can clear it later - asm.push(C_RET) - asm.push(C_RET) # Alignment - - asm.mov(C_ARGS[0], C_RET) - asm.mov(C_ARGS[1], opt) - asm.call(C.rb_reg_new_ary) - - # The actual regex is in RAX now. Pop the temp array from - # rb_ary_tmp_new_from_values into C arg regs so we can clear it - asm.pop(:rcx) # Alignment - asm.pop(:rcx) # ary - - # The value we want to push on the stack is in RAX right now - stack_ret = ctx.stack_push(Type::UnknownHeap) - asm.mov(stack_ret, C_RET) - - # Clear the temp array. - asm.mov(C_ARGS[0], :rcx) # ary - asm.call(C.rb_ary_clear) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def intern(jit, ctx, asm) - # Save the PC and SP because we might allocate - jit_prepare_routine_call(jit, ctx, asm); - - str = ctx.stack_pop(1) - asm.mov(C_ARGS[0], str) - asm.call(C.rb_str_intern) - - # Push the return value - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def newarray(jit, ctx, asm) - n = jit.operand(0) - - # Save the PC and SP because we are allocating - jit_prepare_routine_call(jit, ctx, asm) - - # If n is 0, then elts is never going to be read, so we can just pass null - if n == 0 - values_ptr = 0 - else - asm.comment('load pointer to array elts') - offset_magnitude = C.VALUE.size * n - values_opnd = ctx.sp_opnd(-(offset_magnitude)) - asm.lea(:rax, values_opnd) - values_ptr = :rax - end - - # call rb_ec_ary_new_from_values(struct rb_execution_context_struct *ec, long n, const VALUE *elts); - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], n) - asm.mov(C_ARGS[2], values_ptr) - asm.call(C.rb_ec_ary_new_from_values) - - ctx.stack_pop(n) - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def duparray(jit, ctx, asm) - ary = jit.operand(0) - - # Save the PC and SP because we are allocating - jit_prepare_routine_call(jit, ctx, asm) - - # call rb_ary_resurrect(VALUE ary); - asm.comment('call rb_ary_resurrect') - asm.mov(C_ARGS[0], ary) - asm.call(C.rb_ary_resurrect) - - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # duphash - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def expandarray(jit, ctx, asm) - # Both arguments are rb_num_t which is unsigned - num = jit.operand(0) - flag = jit.operand(1) - - # If this instruction has the splat flag, then bail out. - if flag & 0x01 != 0 - asm.incr_counter(:expandarray_splat) - return CantCompile - end - - # If this instruction has the postarg flag, then bail out. - if flag & 0x02 != 0 - asm.incr_counter(:expandarray_postarg) - return CantCompile - end - - side_exit = side_exit(jit, ctx) - - array_opnd = ctx.stack_opnd(0) - array_stack_opnd = StackOpnd[0] - - # num is the number of requested values. If there aren't enough in the - # array then we're going to push on nils. - if ctx.get_opnd_type(array_stack_opnd) == Type::Nil - ctx.stack_pop(1) # pop after using the type info - # special case for a, b = nil pattern - # push N nils onto the stack - num.times do - push_opnd = ctx.stack_push(Type::Nil) - asm.mov(push_opnd, Qnil) - end - return KeepCompiling - end - - # Move the array from the stack and check that it's an array. - asm.mov(:rax, array_opnd) - guard_object_is_array(jit, ctx, asm, :rax, :rcx, array_stack_opnd, :expandarray_not_array) - ctx.stack_pop(1) # pop after using the type info - - # If we don't actually want any values, then just return. - if num == 0 - return KeepCompiling - end - - jit_array_len(asm, :rax, :rcx) - - # Only handle the case where the number of values in the array is greater - # than or equal to the number of values requested. - asm.cmp(:rcx, num) - asm.jl(counted_exit(side_exit, :expandarray_rhs_too_small)) - - # Conditionally load the address of the heap array into REG1. - # (struct RArray *)(obj)->as.heap.ptr - #asm.mov(:rax, array_opnd) - asm.mov(:rcx, [:rax, C.RBasic.offsetof(:flags)]) - asm.test(:rcx, C::RARRAY_EMBED_FLAG); - asm.mov(:rcx, [:rax, C.RArray.offsetof(:as, :heap, :ptr)]) - - # Load the address of the embedded array into REG1. - # (struct RArray *)(obj)->as.ary - asm.lea(:rax, [:rax, C.RArray.offsetof(:as, :ary)]) - - asm.cmovnz(:rcx, :rax) - - # Loop backward through the array and push each element onto the stack. - (num - 1).downto(0).each do |i| - top = ctx.stack_push(Type::Unknown) - asm.mov(:rax, [:rcx, i * C.VALUE.size]) - asm.mov(top, :rax) - end - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def concatarray(jit, ctx, asm) - # Save the PC and SP because the callee may allocate - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) - - # Get the operands from the stack - ary2st_opnd = ctx.stack_pop(1) - ary1_opnd = ctx.stack_pop(1) - - # Call rb_vm_concat_array(ary1, ary2st) - asm.mov(C_ARGS[0], ary1_opnd) - asm.mov(C_ARGS[1], ary2st_opnd) - asm.call(C.rb_vm_concat_array) - - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def splatarray(jit, ctx, asm) - flag = jit.operand(0) - - # Save the PC and SP because the callee may allocate - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) - - # Get the operands from the stack - ary_opnd = ctx.stack_pop(1) - - # Call rb_vm_splat_array(flag, ary) - asm.mov(C_ARGS[0], flag) - asm.mov(C_ARGS[1], ary_opnd) - asm.call(C.rb_vm_splat_array) - - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def newhash(jit, ctx, asm) - num = jit.operand(0) - - # Save the PC and SP because we are allocating - jit_prepare_routine_call(jit, ctx, asm) - - if num != 0 - # val = rb_hash_new_with_size(num / 2); - asm.mov(C_ARGS[0], num / 2) - asm.call(C.rb_hash_new_with_size) - - # Save the allocated hash as we want to push it after insertion - asm.push(C_RET) - asm.push(C_RET) # x86 alignment - - # Get a pointer to the values to insert into the hash - asm.lea(:rcx, ctx.stack_opnd(num - 1)) - - # rb_hash_bulk_insert(num, STACK_ADDR_FROM_TOP(num), val); - asm.mov(C_ARGS[0], num) - asm.mov(C_ARGS[1], :rcx) - asm.mov(C_ARGS[2], C_RET) - asm.call(C.rb_hash_bulk_insert) - - asm.pop(:rax) - asm.pop(:rax) - - ctx.stack_pop(num) - stack_ret = ctx.stack_push(Type::Hash) - asm.mov(stack_ret, :rax) - else - # val = rb_hash_new(); - asm.call(C.rb_hash_new) - stack_ret = ctx.stack_push(Type::Hash) - asm.mov(stack_ret, C_RET) - end - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def newrange(jit, ctx, asm) - flag = jit.operand(0) - - # rb_range_new() allocates and can raise - jit_prepare_routine_call(jit, ctx, asm) - - # val = rb_range_new(low, high, (int)flag); - asm.mov(C_ARGS[0], ctx.stack_opnd(1)) - asm.mov(C_ARGS[1], ctx.stack_opnd(0)) - asm.mov(C_ARGS[2], flag) - asm.call(C.rb_range_new) - - ctx.stack_pop(2) - stack_ret = ctx.stack_push(Type::UnknownHeap) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def pop(jit, ctx, asm) - ctx.stack_pop - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def dup(jit, ctx, asm) - dup_val = ctx.stack_opnd(0) - mapping, tmp_type = ctx.get_opnd_mapping(StackOpnd[0]) - - loc0 = ctx.stack_push_mapping([mapping, tmp_type]) - asm.mov(:rax, dup_val) - asm.mov(loc0, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def dupn(jit, ctx, asm) - n = jit.operand(0) - - # In practice, seems to be only used for n==2 - if n != 2 - return CantCompile - end - - opnd1 = ctx.stack_opnd(1) - opnd0 = ctx.stack_opnd(0) - - mapping1 = ctx.get_opnd_mapping(StackOpnd[1]) - mapping0 = ctx.get_opnd_mapping(StackOpnd[0]) - - dst1 = ctx.stack_push_mapping(mapping1) - asm.mov(:rax, opnd1) - asm.mov(dst1, :rax) - - dst0 = ctx.stack_push_mapping(mapping0) - asm.mov(:rax, opnd0) - asm.mov(dst0, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def swap(jit, ctx, asm) - stack_swap(jit, ctx, asm, 0, 1) - KeepCompiling - end - - # opt_reverse - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def topn(jit, ctx, asm) - n = jit.operand(0) - - top_n_val = ctx.stack_opnd(n) - mapping = ctx.get_opnd_mapping(StackOpnd[n]) - loc0 = ctx.stack_push_mapping(mapping) - asm.mov(:rax, top_n_val) - asm.mov(loc0, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def setn(jit, ctx, asm) - n = jit.operand(0) - - top_val = ctx.stack_pop(0) - dst_opnd = ctx.stack_opnd(n) - asm.mov(:rax, top_val) - asm.mov(dst_opnd, :rax) - - mapping = ctx.get_opnd_mapping(StackOpnd[0]) - ctx.set_opnd_mapping(StackOpnd[n], mapping) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def adjuststack(jit, ctx, asm) - n = jit.operand(0) - ctx.stack_pop(n) - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def defined(jit, ctx, asm) - op_type = jit.operand(0) - obj = jit.operand(1, ruby: true) - pushval = jit.operand(2, ruby: true) - - # Save the PC and SP because the callee may allocate - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) - - # Get the operands from the stack - v_opnd = ctx.stack_pop(1) - - # Call vm_defined(ec, reg_cfp, op_type, obj, v) - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], CFP) - asm.mov(C_ARGS[2], op_type) - asm.mov(C_ARGS[3], to_value(obj)) - asm.mov(C_ARGS[4], v_opnd) - asm.call(C.rb_vm_defined) - - asm.test(C_RET, 255) - asm.mov(:rax, Qnil) - asm.mov(:rcx, to_value(pushval)) - asm.cmovnz(:rax, :rcx) - - # Push the return value onto the stack - out_type = if C::SPECIAL_CONST_P(pushval) - Type::UnknownImm - else - Type::Unknown - end - stack_ret = ctx.stack_push(out_type) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def definedivar(jit, ctx, asm) - # Defer compilation so we can specialize base on a runtime receiver - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - ivar_name = jit.operand(0) - # Value that will be pushed on the stack if the ivar is defined. In practice this is always the - # string "instance-variable". If the ivar is not defined, nil will be pushed instead. - pushval = jit.operand(2, ruby: true) - - # Get the receiver - recv = :rcx - asm.mov(recv, [CFP, C.rb_control_frame_t.offsetof(:self)]) - - # Specialize base on compile time values - comptime_receiver = jit.peek_at_self - - if shape_too_complex?(comptime_receiver) - # Fall back to calling rb_ivar_defined - - # Save the PC and SP because the callee may allocate - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) # clobbers :rax - - # Call rb_ivar_defined(recv, ivar_name) - asm.mov(C_ARGS[0], recv) - asm.mov(C_ARGS[1], ivar_name) - asm.call(C.rb_ivar_defined) - - # if (rb_ivar_defined(recv, ivar_name)) { - # val = pushval; - # } - asm.test(C_RET, 255) - asm.mov(:rax, Qnil) - asm.mov(:rcx, to_value(pushval)) - asm.cmovnz(:rax, :rcx) - - # Push the return value onto the stack - out_type = C::SPECIAL_CONST_P(pushval) ? Type::UnknownImm : Type::Unknown - stack_ret = ctx.stack_push(out_type) - asm.mov(stack_ret, :rax) - - return KeepCompiling - end - - shape_id = C.rb_shape_get_shape_id(comptime_receiver) - ivar_exists = C.rb_shape_get_iv_index(shape_id, ivar_name) - - side_exit = side_exit(jit, ctx) - - # Guard heap object (recv_opnd must be used before stack_pop) - guard_object_is_heap(jit, ctx, asm, recv, SelfOpnd) - - shape_opnd = DwordPtr[recv, C.rb_shape_id_offset] - - asm.comment('guard shape') - asm.cmp(shape_opnd, shape_id) - jit_chain_guard(:jne, jit, ctx, asm, side_exit) - - result = ivar_exists ? C.to_value(pushval) : Qnil - putobject(jit, ctx, asm, val: result) - - # Jump to next instruction. This allows guard chains to share the same successor. - jump_to_next_insn(jit, ctx, asm) - - return EndBlock - end - - # checkmatch - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def checkkeyword(jit, ctx, asm) - # When a keyword is unspecified past index 32, a hash will be used - # instead. This can only happen in iseqs taking more than 32 keywords. - if jit.iseq.body.param.keyword.num >= 32 - return CantCompile - end - - # The EP offset to the undefined bits local - bits_offset = jit.operand(0) - - # The index of the keyword we want to check - index = jit.operand(1, signed: true) - - # Load environment pointer EP - ep_reg = :rax - jit_get_ep(asm, 0, reg: ep_reg) - - # VALUE kw_bits = *(ep - bits) - bits_opnd = [ep_reg, C.VALUE.size * -bits_offset] - - # unsigned int b = (unsigned int)FIX2ULONG(kw_bits); - # if ((b & (0x01 << idx))) { - # - # We can skip the FIX2ULONG conversion by shifting the bit we test - bit_test = 0x01 << (index + 1) - asm.test(bits_opnd, bit_test) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmovz(:rax, :rcx) - - stack_ret = ctx.stack_push(Type::UnknownImm) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # checktype - # defineclass - # definemethod - # definesmethod - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def send(jit, ctx, asm) - # Specialize on a compile-time receiver, and split a block for chain guards - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - cd = C.rb_call_data.new(jit.operand(0)) - blockiseq = jit.operand(1) - - # calling->ci - mid = C.vm_ci_mid(cd.ci) - calling = build_calling(ci: cd.ci, block_handler: blockiseq) - - if calling.flags & C::VM_CALL_FORWARDING != 0 - return CantCompile - end - - # vm_sendish - cme, comptime_recv_klass = jit_search_method(jit, ctx, asm, mid, calling) - if cme == CantCompile - return CantCompile - end - jit_call_general(jit, ctx, asm, mid, calling, cme, comptime_recv_klass) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_send_without_block(jit, ctx, asm, cd: C.rb_call_data.new(jit.operand(0))) - # Specialize on a compile-time receiver, and split a block for chain guards - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - # calling->ci - mid = C.vm_ci_mid(cd.ci) - calling = build_calling(ci: cd.ci, block_handler: C::VM_BLOCK_HANDLER_NONE) - - # vm_sendish - cme, comptime_recv_klass = jit_search_method(jit, ctx, asm, mid, calling) - if cme == CantCompile - return CantCompile - end - jit_call_general(jit, ctx, asm, mid, calling, cme, comptime_recv_klass) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def objtostring(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - recv = ctx.stack_opnd(0) - comptime_recv = jit.peek_at_stack(0) - - if C.RB_TYPE_P(comptime_recv, C::RUBY_T_STRING) - side_exit = side_exit(jit, ctx) - - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[0], comptime_recv, side_exit) - # No work needed. The string value is already on the top of the stack. - KeepCompiling - else - cd = C.rb_call_data.new(jit.operand(0)) - opt_send_without_block(jit, ctx, asm, cd:) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_ary_freeze(jit, ctx, asm) - unless Invariants.assume_bop_not_redefined(jit, C::ARRAY_REDEFINED_OP_FLAG, C::BOP_FREEZE) - return CantCompile; - end - - ary = jit.operand(0, ruby: true) - - # Push the return value onto the stack - stack_ret = ctx.stack_push(Type::CArray) - asm.mov(:rax, to_value(ary)) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_hash_freeze(jit, ctx, asm) - unless Invariants.assume_bop_not_redefined(jit, C::HASH_REDEFINED_OP_FLAG, C::BOP_FREEZE) - return CantCompile; - end - - hash = jit.operand(0, ruby: true) - - # Push the return value onto the stack - stack_ret = ctx.stack_push(Type::CHash) - asm.mov(:rax, to_value(hash)) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_str_freeze(jit, ctx, asm) - unless Invariants.assume_bop_not_redefined(jit, C::STRING_REDEFINED_OP_FLAG, C::BOP_FREEZE) - return CantCompile; - end - - str = jit.operand(0, ruby: true) - - # Push the return value onto the stack - stack_ret = ctx.stack_push(Type::CString) - asm.mov(:rax, to_value(str)) - asm.mov(stack_ret, :rax) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_nil_p(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # opt_str_uminus - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_newarray_send(jit, ctx, asm) - type = C.ID2SYM jit.operand(1) - - case type - when :min then opt_newarray_min(jit, ctx, asm) - when :max then opt_newarray_max(jit, ctx, asm) - when :hash then opt_newarray_hash(jit, ctx, asm) - else - return CantCompile - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_newarray_min(jit, ctx, asm) - num = jit.operand(0) - - # Save the PC and SP because we may allocate - jit_prepare_routine_call(jit, ctx, asm) - - offset_magnitude = C.VALUE.size * num - values_opnd = ctx.sp_opnd(-offset_magnitude) - asm.lea(:rax, values_opnd) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], num) - asm.mov(C_ARGS[2], :rax) - asm.call(C.rb_vm_opt_newarray_min) - - ctx.stack_pop(num) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_newarray_max(jit, ctx, asm) - num = jit.operand(0) - - # Save the PC and SP because we may allocate - jit_prepare_routine_call(jit, ctx, asm) - - offset_magnitude = C.VALUE.size * num - values_opnd = ctx.sp_opnd(-offset_magnitude) - asm.lea(:rax, values_opnd) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], num) - asm.mov(C_ARGS[2], :rax) - asm.call(C.rb_vm_opt_newarray_max) - - ctx.stack_pop(num) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_newarray_hash(jit, ctx, asm) - num = jit.operand(0) - - # Save the PC and SP because we may allocate - jit_prepare_routine_call(jit, ctx, asm) - - offset_magnitude = C.VALUE.size * num - values_opnd = ctx.sp_opnd(-offset_magnitude) - asm.lea(:rax, values_opnd) - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], num) - asm.mov(C_ARGS[2], :rax) - asm.call(C.rb_vm_opt_newarray_hash) - - ctx.stack_pop(num) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def invokesuper(jit, ctx, asm) - cd = C.rb_call_data.new(jit.operand(0)) - block = jit.operand(1) - - # Defer compilation so we can specialize on class of receiver - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - me = C.rb_vm_frame_method_entry(jit.cfp) - if me.nil? - return CantCompile - end - - # FIXME: We should track and invalidate this block when this cme is invalidated - current_defined_class = me.defined_class - mid = me.def.original_id - - if me.to_i != C.rb_callable_method_entry(current_defined_class, me.called_id).to_i - # Though we likely could generate this call, as we are only concerned - # with the method entry remaining valid, assume_method_lookup_stable - # below requires that the method lookup matches as well - return CantCompile - end - - # vm_search_normal_superclass - rbasic_klass = C.to_ruby(C.RBasic.new(C.to_value(current_defined_class)).klass) - if C::BUILTIN_TYPE(current_defined_class) == C::RUBY_T_ICLASS && C::BUILTIN_TYPE(rbasic_klass) == C::RUBY_T_MODULE && \ - C::FL_TEST_RAW(rbasic_klass, C::RMODULE_IS_REFINEMENT) - return CantCompile - end - comptime_superclass = C.rb_class_get_superclass(C.RCLASS_ORIGIN(current_defined_class)) - - ci = cd.ci - argc = C.vm_ci_argc(ci) - - ci_flags = C.vm_ci_flag(ci) - - # Don't JIT calls that aren't simple - # Note, not using VM_CALL_ARGS_SIMPLE because sometimes we pass a block. - - if ci_flags & C::VM_CALL_KWARG != 0 - asm.incr_counter(:send_keywords) - return CantCompile - end - if ci_flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:send_kw_splat) - return CantCompile - end - if ci_flags & C::VM_CALL_ARGS_BLOCKARG != 0 - asm.incr_counter(:send_block_arg) - return CantCompile - end - - # Ensure we haven't rebound this method onto an incompatible class. - # In the interpreter we try to avoid making this check by performing some - # cheaper calculations first, but since we specialize on the method entry - # and so only have to do this once at compile time this is fine to always - # check and side exit. - comptime_recv = jit.peek_at_stack(argc) - unless C.obj_is_kind_of(comptime_recv, current_defined_class) - return CantCompile - end - - # Do method lookup - cme = C.rb_callable_method_entry(comptime_superclass, mid) - - if cme.nil? - return CantCompile - end - - # Check that we'll be able to write this method dispatch before generating checks - cme_def_type = cme.def.type - if cme_def_type != C::VM_METHOD_TYPE_ISEQ && cme_def_type != C::VM_METHOD_TYPE_CFUNC - # others unimplemented - return CantCompile - end - - asm.comment('guard known me') - lep_opnd = :rax - jit_get_lep(jit, asm, reg: lep_opnd) - ep_me_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_ME_CREF] - - asm.mov(:rcx, me.to_i) - asm.cmp(ep_me_opnd, :rcx) - asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_me_changed)) - - if block == C::VM_BLOCK_HANDLER_NONE - # Guard no block passed - # rb_vm_frame_block_handler(GET_EC()->cfp) == VM_BLOCK_HANDLER_NONE - # note, we assume VM_ASSERT(VM_ENV_LOCAL_P(ep)) - # - # TODO: this could properly forward the current block handler, but - # would require changes to gen_send_* - asm.comment('guard no block given') - ep_specval_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL] - asm.cmp(ep_specval_opnd, C::VM_BLOCK_HANDLER_NONE) - asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_block)) - end - - # We need to assume that both our current method entry and the super - # method entry we invoke remain stable - Invariants.assume_method_lookup_stable(jit, me) - Invariants.assume_method_lookup_stable(jit, cme) - - # Method calls may corrupt types - ctx.clear_local_types - - calling = build_calling(ci:, block_handler: block) - case cme_def_type - in C::VM_METHOD_TYPE_ISEQ - iseq = def_iseq_ptr(cme.def) - frame_type = C::VM_FRAME_MAGIC_METHOD | C::VM_ENV_FLAG_LOCAL - jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type:) - in C::VM_METHOD_TYPE_CFUNC - jit_call_cfunc(jit, ctx, asm, cme, calling) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def invokeblock(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - # Get call info - cd = C.rb_call_data.new(jit.operand(0)) - calling = build_calling(ci: cd.ci, block_handler: :captured) - - # Get block_handler - cfp = jit.cfp - lep = C.rb_vm_ep_local_ep(cfp.ep) - comptime_handler = lep[C::VM_ENV_DATA_INDEX_SPECVAL] - - # Handle each block_handler type - if comptime_handler == C::VM_BLOCK_HANDLER_NONE # no block given - asm.incr_counter(:invokeblock_none) - CantCompile - elsif comptime_handler & 0x3 == 0x1 # VM_BH_ISEQ_BLOCK_P - asm.comment('get local EP') - ep_reg = :rax - jit_get_lep(jit, asm, reg: ep_reg) - asm.mov(:rax, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler_opnd - - asm.comment('guard block_handler type') - side_exit = side_exit(jit, ctx) - asm.mov(:rcx, :rax) - asm.and(:rcx, 0x3) # block_handler is a tagged pointer - asm.cmp(:rcx, 0x1) # VM_BH_ISEQ_BLOCK_P - tag_changed_exit = counted_exit(side_exit, :invokeblock_tag_changed) - jit_chain_guard(:jne, jit, ctx, asm, tag_changed_exit) - - comptime_captured = C.rb_captured_block.new(comptime_handler & ~0x3) - comptime_iseq = comptime_captured.code.iseq - - asm.comment('guard known ISEQ') - asm.and(:rax, ~0x3) # captured - asm.mov(:rax, [:rax, C.VALUE.size * 2]) # captured->iseq - asm.mov(:rcx, comptime_iseq.to_i) - asm.cmp(:rax, :rcx) - block_changed_exit = counted_exit(side_exit, :invokeblock_iseq_block_changed) - jit_chain_guard(:jne, jit, ctx, asm, block_changed_exit) - - jit_call_iseq(jit, ctx, asm, nil, calling, comptime_iseq, frame_type: C::VM_FRAME_MAGIC_BLOCK) - elsif comptime_handler & 0x3 == 0x3 # VM_BH_IFUNC_P - # We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet. - if calling.flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:invokeblock_ifunc_args_splat) - return CantCompile - end - if calling.flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:invokeblock_ifunc_kw_splat) - return CantCompile - end - - asm.comment('get local EP') - jit_get_lep(jit, asm, reg: :rax) - asm.mov(:rcx, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler_opnd - - asm.comment('guard block_handler type'); - side_exit = side_exit(jit, ctx) - asm.mov(:rax, :rcx) # block_handler_opnd - asm.and(:rax, 0x3) # tag_opnd: block_handler is a tagged pointer - asm.cmp(:rax, 0x3) # VM_BH_IFUNC_P - tag_changed_exit = counted_exit(side_exit, :invokeblock_tag_changed) - jit_chain_guard(:jne, jit, ctx, asm, tag_changed_exit) - - # The cfunc may not be leaf - jit_prepare_routine_call(jit, ctx, asm) # clobbers :rax - - asm.comment('call ifunc') - asm.and(:rcx, ~0x3) # captured_opnd - asm.lea(:rax, ctx.sp_opnd(-calling.argc * C.VALUE.size)) # argv - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], :rcx) # captured_opnd - asm.mov(C_ARGS[2], calling.argc) - asm.mov(C_ARGS[3], :rax) # argv - asm.call(C.rb_vm_yield_with_cfunc) - - ctx.stack_pop(calling.argc) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - # cfunc calls may corrupt types - ctx.clear_local_types - - # Share the successor with other chains - jump_to_next_insn(jit, ctx, asm) - EndBlock - elsif symbol?(comptime_handler) - asm.incr_counter(:invokeblock_symbol) - CantCompile - else # Proc - asm.incr_counter(:invokeblock_proc) - CantCompile - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def leave(jit, ctx, asm) - assert_equal(ctx.stack_size, 1) - - jit_check_ints(jit, ctx, asm) - - asm.comment('pop stack frame') - asm.lea(:rax, [CFP, C.rb_control_frame_t.size]) - asm.mov(CFP, :rax) - asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], :rax) - - # Return a value (for compile_leave_exit) - ret_opnd = ctx.stack_pop - asm.mov(:rax, ret_opnd) - - # Set caller's SP and push a value to its stack (for JIT) - asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # Note: SP is in the position after popping a receiver and arguments - asm.mov([SP], :rax) - - # Jump to cfp->jit_return - asm.jmp([CFP, -C.rb_control_frame_t.size + C.rb_control_frame_t.offsetof(:jit_return)]) - - EndBlock - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def throw(jit, ctx, asm) - throw_state = jit.operand(0) - asm.mov(:rcx, ctx.stack_pop(1)) # throwobj - - # THROW_DATA_NEW allocates. Save SP for GC and PC for allocation tracing as - # well as handling the catch table. However, not using jit_prepare_routine_call - # since we don't need a patch point for this implementation. - jit_save_pc(jit, asm) # clobbers rax - jit_save_sp(ctx, asm) - - # rb_vm_throw verifies it's a valid throw, sets ec->tag->state, and returns throw - # data, which is throwobj or a vm_throw_data wrapping it. When ec->tag->state is - # set, JIT code callers will handle the throw with vm_exec_handle_exception. - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], CFP) - asm.mov(C_ARGS[2], throw_state) - # asm.mov(C_ARGS[3], :rcx) # same reg - asm.call(C.rb_vm_throw) - - asm.comment('exit from throw') - asm.pop(SP) - asm.pop(EC) - asm.pop(CFP) - - # return C_RET as C_RET - asm.ret - EndBlock - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jump(jit, ctx, asm) - # Check for interrupts, but only on backward branches that may create loops - jump_offset = jit.operand(0, signed: true) - if jump_offset < 0 - jit_check_ints(jit, ctx, asm) - end - - pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset) - jit_direct_jump(jit.iseq, pc, ctx, asm) - EndBlock - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def branchif(jit, ctx, asm) - # Check for interrupts, but only on backward branches that may create loops - jump_offset = jit.operand(0, signed: true) - if jump_offset < 0 - jit_check_ints(jit, ctx, asm) - end - - # Get the branch target instruction offsets - next_pc = jit.pc + C.VALUE.size * jit.insn.len - jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset) - - val_type = ctx.get_opnd_type(StackOpnd[0]) - val_opnd = ctx.stack_pop(1) - - if (result = val_type.known_truthy) != nil - target_pc = result ? jump_pc : next_pc - jit_direct_jump(jit.iseq, target_pc, ctx, asm) - else - # This `test` sets ZF only for Qnil and Qfalse, which let jz jump. - asm.test(val_opnd, ~Qnil) - - # Set stubs - branch_stub = BranchStub.new( - iseq: jit.iseq, - shape: Default, - target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target - target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.target1.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false) - @ocb.write(ocb_asm) - end - - # Jump to target0 on jnz - branch_stub.compile = compile_branchif(branch_stub) - branch_stub.compile.call(asm) - end - - EndBlock - end - - def compile_branchif(branch_stub) # Proc escapes arguments in memory - proc do |branch_asm| - branch_asm.comment("branchif #{branch_stub.shape}") - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.jnz(branch_stub.target0.address) - branch_asm.jmp(branch_stub.target1.address) - in Next0 - branch_asm.jz(branch_stub.target1.address) - in Next1 - branch_asm.jnz(branch_stub.target0.address) - end - end - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def branchunless(jit, ctx, asm) - # Check for interrupts, but only on backward branches that may create loops - jump_offset = jit.operand(0, signed: true) - if jump_offset < 0 - jit_check_ints(jit, ctx, asm) - end - - # Get the branch target instruction offsets - next_pc = jit.pc + C.VALUE.size * jit.insn.len - jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset) - - val_type = ctx.get_opnd_type(StackOpnd[0]) - val_opnd = ctx.stack_pop(1) - - if (result = val_type.known_truthy) != nil - target_pc = result ? next_pc : jump_pc - jit_direct_jump(jit.iseq, target_pc, ctx, asm) - else - # This `test` sets ZF only for Qnil and Qfalse, which let jz jump. - asm.test(val_opnd, ~Qnil) - - # Set stubs - branch_stub = BranchStub.new( - iseq: jit.iseq, - shape: Default, - target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target - target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.target1.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false) - @ocb.write(ocb_asm) - end - - # Jump to target0 on jz - branch_stub.compile = compile_branchunless(branch_stub) - branch_stub.compile.call(asm) - end - - EndBlock - end - - def compile_branchunless(branch_stub) # Proc escapes arguments in memory - proc do |branch_asm| - branch_asm.comment("branchunless #{branch_stub.shape}") - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.jz(branch_stub.target0.address) - branch_asm.jmp(branch_stub.target1.address) - in Next0 - branch_asm.jnz(branch_stub.target1.address) - in Next1 - branch_asm.jz(branch_stub.target0.address) - end - end - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def branchnil(jit, ctx, asm) - # Check for interrupts, but only on backward branches that may create loops - jump_offset = jit.operand(0, signed: true) - if jump_offset < 0 - jit_check_ints(jit, ctx, asm) - end - - # Get the branch target instruction offsets - next_pc = jit.pc + C.VALUE.size * jit.insn.len - jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset) - - val_type = ctx.get_opnd_type(StackOpnd[0]) - val_opnd = ctx.stack_pop(1) - - if (result = val_type.known_nil) != nil - target_pc = result ? jump_pc : next_pc - jit_direct_jump(jit.iseq, target_pc, ctx, asm) - else - asm.cmp(val_opnd, Qnil) - - # Set stubs - branch_stub = BranchStub.new( - iseq: jit.iseq, - shape: Default, - target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target - target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.target1.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false) - @ocb.write(ocb_asm) - end - - # Jump to target0 on je - branch_stub.compile = compile_branchnil(branch_stub) - branch_stub.compile.call(asm) - end - - EndBlock - end - - def compile_branchnil(branch_stub) # Proc escapes arguments in memory - proc do |branch_asm| - branch_asm.comment("branchnil #{branch_stub.shape}") - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.je(branch_stub.target0.address) - branch_asm.jmp(branch_stub.target1.address) - in Next0 - branch_asm.jne(branch_stub.target1.address) - in Next1 - branch_asm.je(branch_stub.target0.address) - end - end - end - end - - # once - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_case_dispatch(jit, ctx, asm) - # Normally this instruction would lookup the key in a hash and jump to an - # offset based on that. - # Instead we can take the fallback case and continue with the next - # instruction. - # We'd hope that our jitted code will be sufficiently fast without the - # hash lookup, at least for small hashes, but it's worth revisiting this - # assumption in the future. - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - starting_context = ctx.dup - - case_hash = jit.operand(0, ruby: true) - else_offset = jit.operand(1) - - # Try to reorder case/else branches so that ones that are actually used come first. - # Supporting only Fixnum for now so that the implementation can be an equality check. - key_opnd = ctx.stack_pop(1) - comptime_key = jit.peek_at_stack(0) - - # Check that all cases are fixnums to avoid having to register BOP assumptions on - # all the types that case hashes support. This spends compile time to save memory. - if fixnum?(comptime_key) && comptime_key <= 2**32 && C.rb_hash_keys(case_hash).all? { |key| fixnum?(key) } - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_EQQ) - return CantCompile - end - - # Check if the key is the same value - asm.cmp(key_opnd, to_value(comptime_key)) - side_exit = side_exit(jit, starting_context) - jit_chain_guard(:jne, jit, starting_context, asm, side_exit) - - # Get the offset for the compile-time key - offset = C.rb_hash_stlike_lookup(case_hash, comptime_key) - # NOTE: If we hit the else branch with various values, it could negatively impact the performance. - jump_offset = offset || else_offset - - # Jump to the offset of case or else - target_pc = jit.pc + (jit.insn.len + jump_offset) * C.VALUE.size - jit_direct_jump(jit.iseq, target_pc, ctx, asm) - EndBlock - else - KeepCompiling # continue with === branches - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_plus(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - comptime_recv = jit.peek_at_stack(1) - comptime_obj = jit.peek_at_stack(0) - - if fixnum?(comptime_recv) && fixnum?(comptime_obj) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_PLUS) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - obj_opnd = ctx.stack_pop - recv_opnd = ctx.stack_pop - - asm.mov(:rax, recv_opnd) - asm.sub(:rax, 1) # untag - asm.mov(:rcx, obj_opnd) - asm.add(:rax, :rcx) - asm.jo(side_exit(jit, ctx)) - - dst_opnd = ctx.stack_push(Type::Fixnum) - asm.mov(dst_opnd, :rax) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_minus(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - comptime_recv = jit.peek_at_stack(1) - comptime_obj = jit.peek_at_stack(0) - - if fixnum?(comptime_recv) && fixnum?(comptime_obj) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_MINUS) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - obj_opnd = ctx.stack_pop - recv_opnd = ctx.stack_pop - - asm.mov(:rax, recv_opnd) - asm.mov(:rcx, obj_opnd) - asm.sub(:rax, :rcx) - asm.jo(side_exit(jit, ctx)) - asm.add(:rax, 1) # re-tag - - dst_opnd = ctx.stack_push(Type::Fixnum) - asm.mov(dst_opnd, :rax) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_mult(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_div(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_mod(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - if two_fixnums_on_stack?(jit) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_MOD) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - # Get the operands and destination from the stack - arg1 = ctx.stack_pop(1) - arg0 = ctx.stack_pop(1) - - # Check for arg0 % 0 - asm.cmp(arg1, 0) - asm.je(side_exit(jit, ctx)) - - # Call rb_fix_mod_fix(VALUE recv, VALUE obj) - asm.mov(C_ARGS[0], arg0) - asm.mov(C_ARGS[1], arg1) - asm.call(C.rb_fix_mod_fix) - - # Push the return value onto the stack - stack_ret = ctx.stack_push(Type::Fixnum) - asm.mov(stack_ret, C_RET) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_eq(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - if jit_equality_specialized(jit, ctx, asm, true) - jump_to_next_insn(jit, ctx, asm) - EndBlock - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_neq(jit, ctx, asm) - # opt_neq is passed two rb_call_data as arguments: - # first for ==, second for != - neq_cd = C.rb_call_data.new(jit.operand(1)) - opt_send_without_block(jit, ctx, asm, cd: neq_cd) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_lt(jit, ctx, asm) - jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovl, bop: C::BOP_LT) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_le(jit, ctx, asm) - jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovle, bop: C::BOP_LE) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_gt(jit, ctx, asm) - jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovg, bop: C::BOP_GT) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_ge(jit, ctx, asm) - jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovge, bop: C::BOP_GE) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_ltlt(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_and(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - if two_fixnums_on_stack?(jit) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_AND) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - # Get the operands and destination from the stack - arg1 = ctx.stack_pop(1) - arg0 = ctx.stack_pop(1) - - asm.comment('bitwise and') - asm.mov(:rax, arg0) - asm.and(:rax, arg1) - - # Push the return value onto the stack - dst = ctx.stack_push(Type::Fixnum) - asm.mov(dst, :rax) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_or(jit, ctx, asm) - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - if two_fixnums_on_stack?(jit) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_OR) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - # Get the operands and destination from the stack - asm.comment('bitwise or') - arg1 = ctx.stack_pop(1) - arg0 = ctx.stack_pop(1) - - # Do the bitwise or arg0 | arg1 - asm.mov(:rax, arg0) - asm.or(:rax, arg1) - - # Push the return value onto the stack - dst = ctx.stack_push(Type::Fixnum) - asm.mov(dst, :rax) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_aref(jit, ctx, asm) - cd = C.rb_call_data.new(jit.operand(0)) - argc = C.vm_ci_argc(cd.ci) - - if argc != 1 - asm.incr_counter(:optaref_argc_not_one) - return CantCompile - end - - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - comptime_recv = jit.peek_at_stack(1) - comptime_obj = jit.peek_at_stack(0) - - side_exit = side_exit(jit, ctx) - - if C.rb_class_of(comptime_recv) == Array && fixnum?(comptime_obj) - unless Invariants.assume_bop_not_redefined(jit, C::ARRAY_REDEFINED_OP_FLAG, C::BOP_AREF) - return CantCompile - end - - idx_opnd = ctx.stack_opnd(0) - recv_opnd = ctx.stack_opnd(1) - - not_array_exit = counted_exit(side_exit, :optaref_recv_not_array) - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv_opnd, StackOpnd[1], comptime_recv, not_array_exit) - - # Bail if idx is not a FIXNUM - asm.mov(:rax, idx_opnd) - asm.test(:rax, C::RUBY_FIXNUM_FLAG) - asm.jz(counted_exit(side_exit, :optaref_arg_not_fixnum)) - - # Call VALUE rb_ary_entry_internal(VALUE ary, long offset). - # It never raises or allocates, so we don't need to write to cfp->pc. - asm.sar(:rax, 1) # Convert fixnum to int - asm.mov(C_ARGS[0], recv_opnd) - asm.mov(C_ARGS[1], :rax) - asm.call(C.rb_ary_entry_internal) - - # Pop the argument and the receiver - ctx.stack_pop(2) - - # Push the return value onto the stack - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - # Let guard chains share the same successor - jump_to_next_insn(jit, ctx, asm) - EndBlock - elsif C.rb_class_of(comptime_recv) == Hash - unless Invariants.assume_bop_not_redefined(jit, C::HASH_REDEFINED_OP_FLAG, C::BOP_AREF) - return CantCompile - end - - recv_opnd = ctx.stack_opnd(1) - - # Guard that the receiver is a Hash - not_hash_exit = counted_exit(side_exit, :optaref_recv_not_hash) - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv_opnd, StackOpnd[1], comptime_recv, not_hash_exit) - - # Prepare to call rb_hash_aref(). It might call #hash on the key. - jit_prepare_routine_call(jit, ctx, asm) - - asm.comment('call rb_hash_aref') - key_opnd = ctx.stack_opnd(0) - recv_opnd = ctx.stack_opnd(1) - asm.mov(:rdi, recv_opnd) - asm.mov(:rsi, key_opnd) - asm.call(C.rb_hash_aref) - - # Pop the key and the receiver - ctx.stack_pop(2) - - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - # Let guard chains share the same successor - jump_to_next_insn(jit, ctx, asm) - EndBlock - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_aset(jit, ctx, asm) - # Defer compilation so we can specialize on a runtime `self` - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - comptime_recv = jit.peek_at_stack(2) - comptime_key = jit.peek_at_stack(1) - - # Get the operands from the stack - recv = ctx.stack_opnd(2) - key = ctx.stack_opnd(1) - _val = ctx.stack_opnd(0) - - if C.rb_class_of(comptime_recv) == Array && fixnum?(comptime_key) - side_exit = side_exit(jit, ctx) - - # Guard receiver is an Array - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[2], comptime_recv, side_exit) - - # Guard key is a fixnum - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_key), key, StackOpnd[1], comptime_key, side_exit) - - # We might allocate or raise - jit_prepare_routine_call(jit, ctx, asm) - - asm.comment('call rb_ary_store') - recv = ctx.stack_opnd(2) - key = ctx.stack_opnd(1) - val = ctx.stack_opnd(0) - asm.mov(:rax, key) - asm.sar(:rax, 1) # FIX2LONG(key) - asm.mov(C_ARGS[0], recv) - asm.mov(C_ARGS[1], :rax) - asm.mov(C_ARGS[2], val) - asm.call(C.rb_ary_store) - - # rb_ary_store returns void - # stored value should still be on stack - val = ctx.stack_opnd(0) - - # Push the return value onto the stack - ctx.stack_pop(3) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(:rax, val) - asm.mov(stack_ret, :rax) - - jump_to_next_insn(jit, ctx, asm) - EndBlock - elsif C.rb_class_of(comptime_recv) == Hash - side_exit = side_exit(jit, ctx) - - # Guard receiver is a Hash - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[2], comptime_recv, side_exit) - - # We might allocate or raise - jit_prepare_routine_call(jit, ctx, asm) - - # Call rb_hash_aset - recv = ctx.stack_opnd(2) - key = ctx.stack_opnd(1) - val = ctx.stack_opnd(0) - asm.mov(C_ARGS[0], recv) - asm.mov(C_ARGS[1], key) - asm.mov(C_ARGS[2], val) - asm.call(C.rb_hash_aset) - - # Push the return value onto the stack - ctx.stack_pop(3) - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - jump_to_next_insn(jit, ctx, asm) - EndBlock - else - opt_send_without_block(jit, ctx, asm) - end - end - - # opt_aset_with - # opt_aref_with - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_length(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_size(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_empty_p(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_succ(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_not(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_regexpmatch2(jit, ctx, asm) - opt_send_without_block(jit, ctx, asm) - end - - # invokebuiltin - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_invokebuiltin_delegate(jit, ctx, asm) - bf = C.rb_builtin_function.new(jit.operand(0)) - bf_argc = bf.argc - start_index = jit.operand(1) - - # ec, self, and arguments - if bf_argc + 2 > C_ARGS.size - return CantCompile - end - - # If the calls don't allocate, do they need up to date PC, SP? - jit_prepare_routine_call(jit, ctx, asm) - - # Call the builtin func (ec, recv, arg1, arg2, ...) - asm.comment('call builtin func') - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], [CFP, C.rb_control_frame_t.offsetof(:self)]) - - # Copy arguments from locals - if bf_argc > 0 - # Load environment pointer EP from CFP - asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:ep)]) - - bf_argc.times do |i| - table_size = jit.iseq.body.local_table_size - offs = -table_size - C::VM_ENV_DATA_SIZE + 1 + start_index + i - asm.mov(C_ARGS[2 + i], [:rax, offs * C.VALUE.size]) - end - end - asm.call(bf.func_ptr) - - # Push the return value - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - KeepCompiling - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def opt_invokebuiltin_delegate_leave(jit, ctx, asm) - opt_invokebuiltin_delegate(jit, ctx, asm) - # opt_invokebuiltin_delegate is always followed by leave insn - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putobject_INT2FIX_0_(jit, ctx, asm) - putobject(jit, ctx, asm, val: C.to_value(0)) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def putobject_INT2FIX_1_(jit, ctx, asm) - putobject(jit, ctx, asm, val: C.to_value(1)) - end - - # - # C func - # - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_true(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 0 - asm.comment('nil? == true') - ctx.stack_pop(1) - stack_ret = ctx.stack_push(Type::True) - asm.mov(stack_ret, Qtrue) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_false(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 0 - asm.comment('nil? == false') - ctx.stack_pop(1) - stack_ret = ctx.stack_push(Type::False) - asm.mov(stack_ret, Qfalse) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_kernel_is_a(jit, ctx, asm, argc, known_recv_class) - if argc != 1 - return false - end - - # If this is a super call we might not know the class - if known_recv_class.nil? - return false - end - - # Important note: The output code will simply `return true/false`. - # Correctness follows from: - # - `known_recv_class` implies there is a guard scheduled before here - # for a particular `CLASS_OF(lhs)`. - # - We guard that rhs is identical to the compile-time sample - # - In general, for any two Class instances A, B, `A < B` does not change at runtime. - # Class#superclass is stable. - - sample_rhs = jit.peek_at_stack(0) - sample_lhs = jit.peek_at_stack(1) - - # We are not allowing module here because the module hierarchy can change at runtime. - if C.RB_TYPE_P(sample_rhs, C::RUBY_T_CLASS) - return false - end - sample_is_a = C.obj_is_kind_of(sample_lhs, sample_rhs) - - side_exit = side_exit(jit, ctx) - asm.comment('Kernel#is_a?') - asm.mov(:rax, to_value(sample_rhs)) - asm.cmp(ctx.stack_opnd(0), :rax) - asm.jne(counted_exit(side_exit, :send_is_a_class_mismatch)) - - ctx.stack_pop(2) - - if sample_is_a - stack_ret = ctx.stack_push(Type::True) - asm.mov(stack_ret, Qtrue) - else - stack_ret = ctx.stack_push(Type::False) - asm.mov(stack_ret, Qfalse) - end - return true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_kernel_instance_of(jit, ctx, asm, argc, known_recv_class) - if argc != 1 - return false - end - - # If this is a super call we might not know the class - if known_recv_class.nil? - return false - end - - # Important note: The output code will simply `return true/false`. - # Correctness follows from: - # - `known_recv_class` implies there is a guard scheduled before here - # for a particular `CLASS_OF(lhs)`. - # - We guard that rhs is identical to the compile-time sample - # - For a particular `CLASS_OF(lhs)`, `rb_obj_class(lhs)` does not change. - # (because for any singleton class `s`, `s.superclass.equal?(s.attached_object.class)`) - - sample_rhs = jit.peek_at_stack(0) - sample_lhs = jit.peek_at_stack(1) - - # Filters out cases where the C implementation raises - unless C.RB_TYPE_P(sample_rhs, C::RUBY_T_CLASS) || C.RB_TYPE_P(sample_rhs, C::RUBY_T_MODULE) - return false - end - - # We need to grab the class here to deal with singleton classes. - # Instance of grabs the "real class" of the object rather than the - # singleton class. - sample_lhs_real_class = C.rb_obj_class(sample_lhs) - - sample_instance_of = (sample_lhs_real_class == sample_rhs) - - side_exit = side_exit(jit, ctx) - asm.comment('Kernel#instance_of?') - asm.mov(:rax, to_value(sample_rhs)) - asm.cmp(ctx.stack_opnd(0), :rax) - asm.jne(counted_exit(side_exit, :send_instance_of_class_mismatch)) - - ctx.stack_pop(2) - - if sample_instance_of - stack_ret = ctx.stack_push(Type::True) - asm.mov(stack_ret, Qtrue) - else - stack_ret = ctx.stack_push(Type::False) - asm.mov(stack_ret, Qfalse) - end - return true; - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_obj_not(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 0 - recv_type = ctx.get_opnd_type(StackOpnd[0]) - - case recv_type.known_truthy - in false - asm.comment('rb_obj_not(nil_or_false)') - ctx.stack_pop(1) - out_opnd = ctx.stack_push(Type::True) - asm.mov(out_opnd, Qtrue) - in true - # Note: recv_type != Type::Nil && recv_type != Type::False. - asm.comment('rb_obj_not(truthy)') - ctx.stack_pop(1) - out_opnd = ctx.stack_push(Type::False) - asm.mov(out_opnd, Qfalse) - in nil - asm.comment('rb_obj_not') - - recv = ctx.stack_pop - # This `test` sets ZF only for Qnil and Qfalse, which let cmovz set. - asm.test(recv, ~Qnil) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmovz(:rax, :rcx) - - stack_ret = ctx.stack_push(Type::UnknownImm) - asm.mov(stack_ret, :rax) - end - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_obj_equal(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - asm.comment('equal?') - obj1 = ctx.stack_pop(1) - obj2 = ctx.stack_pop(1) - - asm.mov(:rax, obj1) - asm.mov(:rcx, obj2) - asm.cmp(:rax, :rcx) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmove(:rax, :rcx) - - stack_ret = ctx.stack_push(Type::UnknownImm) - asm.mov(stack_ret, :rax) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_obj_not_equal(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - jit_equality_specialized(jit, ctx, asm, false) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_mod_eqq(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - - asm.comment('Module#===') - # By being here, we know that the receiver is a T_MODULE or a T_CLASS, because Module#=== can - # only live on these objects. With that, we can call rb_obj_is_kind_of() without - # jit_prepare_routine_call() or a control frame push because it can't raise, allocate, or call - # Ruby methods with these inputs. - # Note the difference in approach from Kernel#is_a? because we don't get a free guard for the - # right hand side. - lhs = ctx.stack_opnd(1) # the module - rhs = ctx.stack_opnd(0) - asm.mov(C_ARGS[0], rhs); - asm.mov(C_ARGS[1], lhs); - asm.call(C.rb_obj_is_kind_of) - - # Return the result - ctx.stack_pop(2) - stack_ret = ctx.stack_push(Type::UnknownImm) - asm.mov(stack_ret, C_RET) - - return true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_int_equal(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - return false unless two_fixnums_on_stack?(jit) - - guard_two_fixnums(jit, ctx, asm) - - # Compare the arguments - asm.comment('rb_int_equal') - arg1 = ctx.stack_pop(1) - arg0 = ctx.stack_pop(1) - asm.mov(:rax, arg1) - asm.cmp(arg0, :rax) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmove(:rax, :rcx) - - stack_ret = ctx.stack_push(Type::UnknownImm) - asm.mov(stack_ret, :rax) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_int_mul(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - return false unless two_fixnums_on_stack?(jit) - - guard_two_fixnums(jit, ctx, asm) - - asm.comment('rb_int_mul') - y_opnd = ctx.stack_pop - x_opnd = ctx.stack_pop - asm.mov(C_ARGS[0], x_opnd) - asm.mov(C_ARGS[1], y_opnd) - asm.call(C.rb_fix_mul_fix) - - ret_opnd = ctx.stack_push(Type::Unknown) - asm.mov(ret_opnd, C_RET) - true - end - - def jit_rb_int_div(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - return false unless two_fixnums_on_stack?(jit) - - guard_two_fixnums(jit, ctx, asm) - - asm.comment('rb_int_div') - y_opnd = ctx.stack_pop - x_opnd = ctx.stack_pop - asm.mov(:rax, y_opnd) - asm.cmp(:rax, C.to_value(0)) - asm.je(side_exit(jit, ctx)) - - asm.mov(C_ARGS[0], x_opnd) - asm.mov(C_ARGS[1], :rax) - asm.call(C.rb_fix_div_fix) - - ret_opnd = ctx.stack_push(Type::Unknown) - asm.mov(ret_opnd, C_RET) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_int_aref(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - return false unless two_fixnums_on_stack?(jit) - - guard_two_fixnums(jit, ctx, asm) - - asm.comment('rb_int_aref') - y_opnd = ctx.stack_pop - x_opnd = ctx.stack_pop - - asm.mov(C_ARGS[0], x_opnd) - asm.mov(C_ARGS[1], y_opnd) - asm.call(C.rb_fix_aref) - - ret_opnd = ctx.stack_push(Type::UnknownImm) - asm.mov(ret_opnd, C_RET) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_empty_p(jit, ctx, asm, argc, known_recv_class) - recv_opnd = ctx.stack_pop(1) - out_opnd = ctx.stack_push(Type::UnknownImm) - - asm.comment('get string length') - asm.mov(:rax, recv_opnd) - str_len_opnd = [:rax, C.RString.offsetof(:len)] - - asm.cmp(str_len_opnd, 0) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmove(:rax, :rcx) - asm.mov(out_opnd, :rax) - - return true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_to_s(jit, ctx, asm, argc, known_recv_class) - return false if argc != 0 - if known_recv_class == String - asm.comment('to_s on plain string') - # The method returns the receiver, which is already on the stack. - # No stack movement. - return true - end - false - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_bytesize(jit, ctx, asm, argc, known_recv_class) - asm.comment('String#bytesize') - - recv = ctx.stack_pop(1) - asm.mov(C_ARGS[0], recv) - asm.call(C.rb_str_bytesize) - - out_opnd = ctx.stack_push(Type::Fixnum) - asm.mov(out_opnd, C_RET) - - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_concat(jit, ctx, asm, argc, known_recv_class) - # The << operator can accept integer codepoints for characters - # as the argument. We only specially optimise string arguments. - # If the peeked-at compile time argument is something other than - # a string, assume it won't be a string later either. - comptime_arg = jit.peek_at_stack(0) - unless C.RB_TYPE_P(comptime_arg, C::RUBY_T_STRING) - return false - end - - # Guard that the concat argument is a string - asm.mov(:rax, ctx.stack_opnd(0)) - guard_object_is_string(jit, ctx, asm, :rax, :rcx, StackOpnd[0]) - - # Guard buffers from GC since rb_str_buf_append may allocate. During the VM lock on GC, - # other Ractors may trigger global invalidation, so we need ctx.clear_local_types. - # PC is used on errors like Encoding::CompatibilityError raised by rb_str_buf_append. - jit_prepare_routine_call(jit, ctx, asm) - - concat_arg = ctx.stack_pop(1) - recv = ctx.stack_pop(1) - - # Test if string encodings differ. If different, use rb_str_append. If the same, - # use rb_yjit_str_simple_append, which calls rb_str_cat. - asm.comment('<< on strings') - - # Take receiver's object flags XOR arg's flags. If any - # string-encoding flags are different between the two, - # the encodings don't match. - recv_reg = :rax - asm.mov(recv_reg, recv) - concat_arg_reg = :rcx - asm.mov(concat_arg_reg, concat_arg) - asm.mov(recv_reg, [recv_reg, C.RBasic.offsetof(:flags)]) - asm.mov(concat_arg_reg, [concat_arg_reg, C.RBasic.offsetof(:flags)]) - asm.xor(recv_reg, concat_arg_reg) - asm.test(recv_reg, C::RUBY_ENCODING_MASK) - - # Push once, use the resulting operand in both branches below. - stack_ret = ctx.stack_push(Type::TString) - - enc_mismatch = asm.new_label('enc_mismatch') - asm.jnz(enc_mismatch) - - # If encodings match, call the simple append function and jump to return - asm.mov(C_ARGS[0], recv) - asm.mov(C_ARGS[1], concat_arg) - asm.call(C.rjit_str_simple_append) - ret_label = asm.new_label('func_return') - asm.mov(stack_ret, C_RET) - asm.jmp(ret_label) - - # If encodings are different, use a slower encoding-aware concatenate - asm.write_label(enc_mismatch) - asm.mov(C_ARGS[0], recv) - asm.mov(C_ARGS[1], concat_arg) - asm.call(C.rb_str_buf_append) - asm.mov(stack_ret, C_RET) - # Drop through to return - - asm.write_label(ret_label) - - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_uplus(jit, ctx, asm, argc, _known_recv_class) - if argc != 0 - return false - end - - # We allocate when we dup the string - jit_prepare_routine_call(jit, ctx, asm) - - asm.comment('Unary plus on string') - asm.mov(:rax, ctx.stack_pop(1)) # recv_opnd - asm.mov(:rcx, [:rax, C.RBasic.offsetof(:flags)]) # flags_opnd - asm.test(:rcx, C::RUBY_FL_FREEZE) - - ret_label = asm.new_label('stack_ret') - - # String#+@ can only exist on T_STRING - stack_ret = ctx.stack_push(Type::TString) - - # If the string isn't frozen, we just return it. - asm.mov(stack_ret, :rax) # recv_opnd - asm.jz(ret_label) - - # Str is frozen - duplicate it - asm.mov(C_ARGS[0], :rax) # recv_opnd - asm.call(C.rb_str_dup) - asm.mov(stack_ret, C_RET) - - asm.write_label(ret_label) - - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_str_getbyte(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - asm.comment('rb_str_getbyte') - - index_opnd = ctx.stack_pop - str_opnd = ctx.stack_pop - asm.mov(C_ARGS[0], str_opnd) - asm.mov(C_ARGS[1], index_opnd) - asm.call(C.rb_str_getbyte) - - ret_opnd = ctx.stack_push(Type::Fixnum) - asm.mov(ret_opnd, C_RET) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_ary_empty_p(jit, ctx, asm, argc, _known_recv_class) - array_reg = :rax - asm.mov(array_reg, ctx.stack_pop(1)) - jit_array_len(asm, array_reg, :rcx) - - asm.test(:rcx, :rcx) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmovz(:rax, :rcx) - - out_opnd = ctx.stack_push(Type::UnknownImm) - asm.mov(out_opnd, :rax) - - return true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_ary_push(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 1 - asm.comment('rb_ary_push') - - jit_prepare_routine_call(jit, ctx, asm) - - item_opnd = ctx.stack_pop - ary_opnd = ctx.stack_pop - asm.mov(C_ARGS[0], ary_opnd) - asm.mov(C_ARGS[1], item_opnd) - asm.call(C.rb_ary_push) - - ret_opnd = ctx.stack_push(Type::TArray) - asm.mov(ret_opnd, C_RET) - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_obj_respond_to(jit, ctx, asm, argc, known_recv_class) - # respond_to(:sym) or respond_to(:sym, true) - if argc != 1 && argc != 2 - return false - end - - if known_recv_class.nil? - return false - end - - recv_class = known_recv_class - - # Get the method_id from compile time. We will later add a guard against it. - mid_sym = jit.peek_at_stack(argc - 1) - unless static_symbol?(mid_sym) - return false - end - mid = C.rb_sym2id(mid_sym) - - # This represents the value of the "include_all" argument and whether it's known - allow_priv = if argc == 1 - # Default is false - false - else - # Get value from type information (may or may not be known) - ctx.get_opnd_type(StackOpnd[0]).known_truthy - end - - target_cme = C.rb_callable_method_entry_or_negative(recv_class, mid) - - # Should never be null, as in that case we will be returned a "negative CME" - assert_equal(false, target_cme.nil?) - - cme_def_type = C.UNDEFINED_METHOD_ENTRY_P(target_cme) ? C::VM_METHOD_TYPE_UNDEF : target_cme.def.type - - if cme_def_type == C::VM_METHOD_TYPE_REFINED - return false - end - - visibility = if cme_def_type == C::VM_METHOD_TYPE_UNDEF - C::METHOD_VISI_UNDEF - else - C.METHOD_ENTRY_VISI(target_cme) - end - - result = - case [visibility, allow_priv] - in C::METHOD_VISI_UNDEF, _ then Qfalse # No method => false - in C::METHOD_VISI_PUBLIC, _ then Qtrue # Public method => true regardless of include_all - in _, true then Qtrue # include_all => always true - else return false # not public and include_all not known, can't compile - end - - if result != Qtrue - # Only if respond_to_missing? hasn't been overridden - # In the future, we might want to jit the call to respond_to_missing? - unless Invariants.assume_method_basic_definition(jit, recv_class, C.idRespond_to_missing) - return false - end - end - - # Invalidate this block if method lookup changes for the method being queried. This works - # both for the case where a method does or does not exist, as for the latter we asked for a - # "negative CME" earlier. - Invariants.assume_method_lookup_stable(jit, target_cme) - - # Generate a side exit - side_exit = side_exit(jit, ctx) - - if argc == 2 - # pop include_all argument (we only use its type info) - ctx.stack_pop(1) - end - - sym_opnd = ctx.stack_pop(1) - _recv_opnd = ctx.stack_pop(1) - - # This is necessary because we have no guarantee that sym_opnd is a constant - asm.comment('guard known mid') - asm.mov(:rax, to_value(mid_sym)) - asm.cmp(sym_opnd, :rax) - asm.jne(side_exit) - - putobject(jit, ctx, asm, val: result) - - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_rb_f_block_given_p(jit, ctx, asm, argc, _known_recv_class) - asm.comment('block_given?') - - # Same as rb_vm_frame_block_handler - jit_get_lep(jit, asm, reg: :rax) - asm.mov(:rax, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler - - ctx.stack_pop(1) - out_opnd = ctx.stack_push(Type::UnknownImm) - - # Return `block_handler != VM_BLOCK_HANDLER_NONE` - asm.cmp(:rax, C::VM_BLOCK_HANDLER_NONE) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.cmovne(:rax, :rcx) # block_given - asm.mov(out_opnd, :rax) - - true - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_thread_s_current(jit, ctx, asm, argc, _known_recv_class) - return false if argc != 0 - asm.comment('Thread.current') - ctx.stack_pop(1) - - # ec->thread_ptr - asm.mov(:rax, [EC, C.rb_execution_context_t.offsetof(:thread_ptr)]) - - # thread->self - asm.mov(:rax, [:rax, C.rb_thread_struct.offsetof(:self)]) - - stack_ret = ctx.stack_push(Type::UnknownHeap) - asm.mov(stack_ret, :rax) - true - end - - # - # Helpers - # - - def register_cfunc_codegen_funcs - # Specialization for C methods. See register_cfunc_method for details. - register_cfunc_method(BasicObject, :!, :jit_rb_obj_not) - - register_cfunc_method(NilClass, :nil?, :jit_rb_true) - register_cfunc_method(Kernel, :nil?, :jit_rb_false) - register_cfunc_method(Kernel, :is_a?, :jit_rb_kernel_is_a) - register_cfunc_method(Kernel, :kind_of?, :jit_rb_kernel_is_a) - register_cfunc_method(Kernel, :instance_of?, :jit_rb_kernel_instance_of) - - register_cfunc_method(BasicObject, :==, :jit_rb_obj_equal) - register_cfunc_method(BasicObject, :equal?, :jit_rb_obj_equal) - register_cfunc_method(BasicObject, :!=, :jit_rb_obj_not_equal) - register_cfunc_method(Kernel, :eql?, :jit_rb_obj_equal) - register_cfunc_method(Module, :==, :jit_rb_obj_equal) - register_cfunc_method(Module, :===, :jit_rb_mod_eqq) - register_cfunc_method(Symbol, :==, :jit_rb_obj_equal) - register_cfunc_method(Symbol, :===, :jit_rb_obj_equal) - register_cfunc_method(Integer, :==, :jit_rb_int_equal) - register_cfunc_method(Integer, :===, :jit_rb_int_equal) - - # rb_str_to_s() methods in string.c - register_cfunc_method(String, :empty?, :jit_rb_str_empty_p) - register_cfunc_method(String, :to_s, :jit_rb_str_to_s) - register_cfunc_method(String, :to_str, :jit_rb_str_to_s) - register_cfunc_method(String, :bytesize, :jit_rb_str_bytesize) - register_cfunc_method(String, :<<, :jit_rb_str_concat) - register_cfunc_method(String, :+@, :jit_rb_str_uplus) - - # rb_ary_empty_p() method in array.c - register_cfunc_method(Array, :empty?, :jit_rb_ary_empty_p) - - register_cfunc_method(Kernel, :respond_to?, :jit_obj_respond_to) - register_cfunc_method(Kernel, :block_given?, :jit_rb_f_block_given_p) - - # Thread.current - register_cfunc_method(C.rb_singleton_class(Thread), :current, :jit_thread_s_current) - - #--- - register_cfunc_method(Array, :<<, :jit_rb_ary_push) - register_cfunc_method(Integer, :*, :jit_rb_int_mul) - register_cfunc_method(Integer, :/, :jit_rb_int_div) - register_cfunc_method(Integer, :[], :jit_rb_int_aref) - register_cfunc_method(String, :getbyte, :jit_rb_str_getbyte) - end - - def register_cfunc_method(klass, mid_sym, func) - mid = C.rb_intern(mid_sym.to_s) - me = C.rb_method_entry_at(klass, mid) - - assert_equal(false, me.nil?) - - # Only cfuncs are supported - method_serial = me.def.method_serial - - @cfunc_codegen_table[method_serial] = method(func) - end - - def lookup_cfunc_codegen(cme_def) - @cfunc_codegen_table[cme_def.method_serial] - end - - def stack_swap(_jit, ctx, asm, offset0, offset1) - stack0_mem = ctx.stack_opnd(offset0) - stack1_mem = ctx.stack_opnd(offset1) - - mapping0 = ctx.get_opnd_mapping(StackOpnd[offset0]) - mapping1 = ctx.get_opnd_mapping(StackOpnd[offset1]) - - asm.mov(:rax, stack0_mem) - asm.mov(:rcx, stack1_mem) - asm.mov(stack0_mem, :rcx) - asm.mov(stack1_mem, :rax) - - ctx.set_opnd_mapping(StackOpnd[offset0], mapping1) - ctx.set_opnd_mapping(StackOpnd[offset1], mapping0) - end - - def jit_getlocal_generic(jit, ctx, asm, idx:, level:) - # Load environment pointer EP (level 0) from CFP - ep_reg = :rax - jit_get_ep(asm, level, reg: ep_reg) - - # Load the local from the block - # val = *(vm_get_ep(GET_EP(), level) - idx); - asm.mov(:rax, [ep_reg, -idx * C.VALUE.size]) - - # Write the local at SP - stack_top = if level == 0 - local_idx = ep_offset_to_local_idx(jit.iseq, idx) - ctx.stack_push_local(local_idx) - else - ctx.stack_push(Type::Unknown) - end - - asm.mov(stack_top, :rax) - KeepCompiling - end - - def jit_setlocal_generic(jit, ctx, asm, idx:, level:) - value_type = ctx.get_opnd_type(StackOpnd[0]) - - # Load environment pointer EP at level - ep_reg = :rax - jit_get_ep(asm, level, reg: ep_reg) - - # Write barriers may be required when VM_ENV_FLAG_WB_REQUIRED is set, however write barriers - # only affect heap objects being written. If we know an immediate value is being written we - # can skip this check. - unless value_type.imm? - # flags & VM_ENV_FLAG_WB_REQUIRED - flags_opnd = [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS] - asm.test(flags_opnd, C::VM_ENV_FLAG_WB_REQUIRED) - - # if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0 - asm.jnz(side_exit(jit, ctx)) - end - - if level == 0 - local_idx = ep_offset_to_local_idx(jit.iseq, idx) - ctx.set_local_type(local_idx, value_type) - end - - # Pop the value to write from the stack - stack_top = ctx.stack_pop(1) - - # Write the value at the environment pointer - asm.mov(:rcx, stack_top) - asm.mov([ep_reg, -(C.VALUE.size * idx)], :rcx) - - KeepCompiling - end - - # Compute the index of a local variable from its slot index - def ep_offset_to_local_idx(iseq, ep_offset) - # Layout illustration - # This is an array of VALUE - # | VM_ENV_DATA_SIZE | - # v v - # low addr <+-------+-------+-------+-------+------------------+ - # |local 0|local 1| ... |local n| .... | - # +-------+-------+-------+-------+------------------+ - # ^ ^ ^ ^ - # +-------+---local_table_size----+ cfp->ep--+ - # | | - # +------------------ep_offset---------------+ - # - # See usages of local_var_name() from iseq.c for similar calculation. - - # Equivalent of iseq->body->local_table_size - local_table_size = iseq.body.local_table_size - op = ep_offset - C::VM_ENV_DATA_SIZE - local_idx = local_table_size - op - 1 - assert_equal(true, local_idx >= 0 && local_idx < local_table_size) - local_idx - end - - # Compute the index of a local variable from its slot index - def slot_to_local_idx(iseq, slot_idx) - # Layout illustration - # This is an array of VALUE - # | VM_ENV_DATA_SIZE | - # v v - # low addr <+-------+-------+-------+-------+------------------+ - # |local 0|local 1| ... |local n| .... | - # +-------+-------+-------+-------+------------------+ - # ^ ^ ^ ^ - # +-------+---local_table_size----+ cfp->ep--+ - # | | - # +------------------slot_idx----------------+ - # - # See usages of local_var_name() from iseq.c for similar calculation. - - local_table_size = iseq.body.local_table_size - op = slot_idx - C::VM_ENV_DATA_SIZE - local_table_size - op - 1 - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def guard_object_is_heap(jit, ctx, asm, object, object_opnd, counter = nil) - object_type = ctx.get_opnd_type(object_opnd) - if object_type.heap? - return - end - - side_exit = side_exit(jit, ctx) - side_exit = counted_exit(side_exit, counter) if counter - - asm.comment('guard object is heap') - # Test that the object is not an immediate - asm.test(object, C::RUBY_IMMEDIATE_MASK) - asm.jnz(side_exit) - - # Test that the object is not false - asm.cmp(object, Qfalse) - asm.je(side_exit) - - if object_type.diff(Type::UnknownHeap) != TypeDiff::Incompatible - ctx.upgrade_opnd_type(object_opnd, Type::UnknownHeap) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def guard_object_is_array(jit, ctx, asm, object_reg, flags_reg, object_opnd, counter = nil) - object_type = ctx.get_opnd_type(object_opnd) - if object_type.array? - return - end - - guard_object_is_heap(jit, ctx, asm, object_reg, object_opnd, counter) - - side_exit = side_exit(jit, ctx) - side_exit = counted_exit(side_exit, counter) if counter - - asm.comment('guard object is array') - # Pull out the type mask - asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)]) - asm.and(flags_reg, C::RUBY_T_MASK) - - # Compare the result with T_ARRAY - asm.cmp(flags_reg, C::RUBY_T_ARRAY) - asm.jne(side_exit) - - if object_type.diff(Type::TArray) != TypeDiff::Incompatible - ctx.upgrade_opnd_type(object_opnd, Type::TArray) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def guard_object_is_string(jit, ctx, asm, object_reg, flags_reg, object_opnd, counter = nil) - object_type = ctx.get_opnd_type(object_opnd) - if object_type.string? - return - end - - guard_object_is_heap(jit, ctx, asm, object_reg, object_opnd, counter) - - side_exit = side_exit(jit, ctx) - side_exit = counted_exit(side_exit, counter) if counter - - asm.comment('guard object is string') - # Pull out the type mask - asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)]) - asm.and(flags_reg, C::RUBY_T_MASK) - - # Compare the result with T_STRING - asm.cmp(flags_reg, C::RUBY_T_STRING) - asm.jne(side_exit) - - if object_type.diff(Type::TString) != TypeDiff::Incompatible - ctx.upgrade_opnd_type(object_opnd, Type::TString) - end - end - - # clobbers object_reg - def guard_object_is_not_ruby2_keyword_hash(asm, object_reg, flags_reg, side_exit) - asm.comment('guard object is not ruby2 keyword hash') - - not_ruby2_keyword = asm.new_label('not_ruby2_keyword') - asm.test(object_reg, C::RUBY_IMMEDIATE_MASK) - asm.jnz(not_ruby2_keyword) - - asm.cmp(object_reg, Qfalse) - asm.je(not_ruby2_keyword) - - asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)]) - type_reg = object_reg - asm.mov(type_reg, flags_reg) - asm.and(type_reg, C::RUBY_T_MASK) - - asm.cmp(type_reg, C::RUBY_T_HASH) - asm.jne(not_ruby2_keyword) - - asm.test(flags_reg, C::RHASH_PASS_AS_KEYWORDS) - asm.jnz(side_exit) - - asm.write_label(not_ruby2_keyword) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_chain_guard(opcode, jit, ctx, asm, side_exit, limit: 20) - opcode => :je | :jne | :jnz | :jz - - if ctx.chain_depth < limit - deeper = ctx.dup - deeper.chain_depth += 1 - - branch_stub = BranchStub.new( - iseq: jit.iseq, - shape: Default, - target0: BranchTarget.new(ctx: deeper, pc: jit.pc), - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(deeper, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.compile = compile_jit_chain_guard(branch_stub, opcode:) - branch_stub.compile.call(asm) - else - asm.public_send(opcode, side_exit) - end - end - - def compile_jit_chain_guard(branch_stub, opcode:) # Proc escapes arguments in memory - proc do |branch_asm| - # Not using `asm.comment` here since it's usually put before cmp/test before this. - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.public_send(opcode, branch_stub.target0.address) - end - end - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_guard_known_klass(jit, ctx, asm, known_klass, obj_opnd, insn_opnd, comptime_obj, side_exit, limit: 10) - # Only memory operand is supported for now - assert_equal(true, obj_opnd.is_a?(Array)) - - known_klass = C.to_value(known_klass) - val_type = ctx.get_opnd_type(insn_opnd) - if val_type.known_class == known_klass - # We already know from type information that this is a match - return - end - - # Touching this as Ruby could crash for FrozenCore - if known_klass == C.rb_cNilClass - assert(!val_type.heap?) - assert(val_type.unknown?) - - asm.comment('guard object is nil') - asm.cmp(obj_opnd, Qnil) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::Nil) - elsif known_klass == C.rb_cTrueClass - assert(!val_type.heap?) - assert(val_type.unknown?) - - asm.comment('guard object is true') - asm.cmp(obj_opnd, Qtrue) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::True) - elsif known_klass == C.rb_cFalseClass - assert(!val_type.heap?) - assert(val_type.unknown?) - - asm.comment('guard object is false') - asm.cmp(obj_opnd, Qfalse) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::False) - elsif known_klass == C.rb_cInteger && fixnum?(comptime_obj) - # We will guard fixnum and bignum as though they were separate classes - # BIGNUM can be handled by the general else case below - assert(val_type.unknown?) - - asm.comment('guard object is fixnum') - asm.test(obj_opnd, C::RUBY_FIXNUM_FLAG) - jit_chain_guard(:jz, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::Fixnum) - elsif known_klass == C.rb_cSymbol && static_symbol?(comptime_obj) - assert(!val_type.heap?) - # We will guard STATIC vs DYNAMIC as though they were separate classes - # DYNAMIC symbols can be handled by the general else case below - if val_type != Type::ImmSymbol || !val_type.imm? - assert(val_type.unknown?) - - asm.comment('guard object is static symbol') - assert_equal(8, C::RUBY_SPECIAL_SHIFT) - asm.cmp(BytePtr[*obj_opnd], C::RUBY_SYMBOL_FLAG) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::ImmSymbol) - end - elsif known_klass == C.rb_cFloat && flonum?(comptime_obj) - assert(!val_type.heap?) - if val_type != Type::Flonum || !val_type.imm? - assert(val_type.unknown?) - - # We will guard flonum vs heap float as though they were separate classes - asm.comment('guard object is flonum') - asm.mov(:rax, obj_opnd) - asm.and(:rax, C::RUBY_FLONUM_MASK) - asm.cmp(:rax, C::RUBY_FLONUM_FLAG) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - ctx.upgrade_opnd_type(insn_opnd, Type::Flonum) - end - elsif C.RCLASS_SINGLETON_P(known_klass) && comptime_obj == C.rb_class_attached_object(known_klass) - # Singleton classes are attached to one specific object, so we can - # avoid one memory access (and potentially the is_heap check) by - # looking for the expected object directly. - # Note that in case the sample instance has a singleton class that - # doesn't attach to the sample instance, it means the sample instance - # has an empty singleton class that hasn't been materialized yet. In - # this case, comparing against the sample instance doesn't guarantee - # that its singleton class is empty, so we can't avoid the memory - # access. As an example, `Object.new.singleton_class` is an object in - # this situation. - asm.comment('guard known object with singleton class') - asm.mov(:rax, to_value(comptime_obj)) - asm.cmp(obj_opnd, :rax) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - elsif val_type == Type::CString && known_klass == C.rb_cString - # guard elided because the context says we've already checked - assert_equal(C.to_value(C.rb_class_of(comptime_obj)), C.rb_cString) - else - assert(!val_type.imm?) - - # Load memory to a register - asm.mov(:rax, obj_opnd) - obj_opnd = :rax - - # Check that the receiver is a heap object - # Note: if we get here, the class doesn't have immediate instances. - unless val_type.heap? - asm.comment('guard not immediate') - asm.test(obj_opnd, C::RUBY_IMMEDIATE_MASK) - jit_chain_guard(:jnz, jit, ctx, asm, side_exit, limit:) - asm.cmp(obj_opnd, Qfalse) - jit_chain_guard(:je, jit, ctx, asm, side_exit, limit:) - end - - # Bail if receiver class is different from known_klass - klass_opnd = [obj_opnd, C.RBasic.offsetof(:klass)] - asm.comment("guard known class #{known_klass}") - asm.mov(:rcx, known_klass) - asm.cmp(klass_opnd, :rcx) - jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:) - - if known_klass == C.rb_cString - # Upgrading to Type::CString here is incorrect. - # The guard we put only checks RBASIC_CLASS(obj), - # which adding a singleton class can change. We - # additionally need to know the string is frozen - # to claim Type::CString. - ctx.upgrade_opnd_type(insn_opnd, Type::TString) - elsif known_klass == C.rb_cArray - ctx.upgrade_opnd_type(insn_opnd, Type::TArray) - end - end - end - - # @param jit [RubyVM::RJIT::JITState] - def two_fixnums_on_stack?(jit) - comptime_recv = jit.peek_at_stack(1) - comptime_arg = jit.peek_at_stack(0) - return fixnum?(comptime_recv) && fixnum?(comptime_arg) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def guard_two_fixnums(jit, ctx, asm) - # Get stack operands without popping them - arg1 = ctx.stack_opnd(0) - arg0 = ctx.stack_opnd(1) - - # Get the stack operand types - arg1_type = ctx.get_opnd_type(StackOpnd[0]) - arg0_type = ctx.get_opnd_type(StackOpnd[1]) - - if arg0_type.heap? || arg1_type.heap? - asm.comment('arg is heap object') - asm.jmp(side_exit(jit, ctx)) - return - end - - if arg0_type != Type::Fixnum && arg0_type.specific? - asm.comment('arg0 not fixnum') - asm.jmp(side_exit(jit, ctx)) - return - end - - if arg1_type != Type::Fixnum && arg1_type.specific? - asm.comment('arg1 not fixnum') - asm.jmp(side_exit(jit, ctx)) - return - end - - assert(!arg0_type.heap?) - assert(!arg1_type.heap?) - assert(arg0_type == Type::Fixnum || arg0_type.unknown?) - assert(arg1_type == Type::Fixnum || arg1_type.unknown?) - - # If not fixnums at run-time, fall back - if arg0_type != Type::Fixnum - asm.comment('guard arg0 fixnum') - asm.test(arg0, C::RUBY_FIXNUM_FLAG) - jit_chain_guard(:jz, jit, ctx, asm, side_exit(jit, ctx)) - end - if arg1_type != Type::Fixnum - asm.comment('guard arg1 fixnum') - asm.test(arg1, C::RUBY_FIXNUM_FLAG) - jit_chain_guard(:jz, jit, ctx, asm, side_exit(jit, ctx)) - end - - # Set stack types in context - ctx.upgrade_opnd_type(StackOpnd[0], Type::Fixnum) - ctx.upgrade_opnd_type(StackOpnd[1], Type::Fixnum) - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_fixnum_cmp(jit, ctx, asm, opcode:, bop:) - opcode => :cmovl | :cmovle | :cmovg | :cmovge - - unless jit.at_current_insn? - defer_compilation(jit, ctx, asm) - return EndBlock - end - - comptime_recv = jit.peek_at_stack(1) - comptime_obj = jit.peek_at_stack(0) - - if fixnum?(comptime_recv) && fixnum?(comptime_obj) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, bop) - return CantCompile - end - - # Check that both operands are fixnums - guard_two_fixnums(jit, ctx, asm) - - obj_opnd = ctx.stack_pop - recv_opnd = ctx.stack_pop - - asm.mov(:rax, obj_opnd) - asm.cmp(recv_opnd, :rax) - asm.mov(:rax, Qfalse) - asm.mov(:rcx, Qtrue) - asm.public_send(opcode, :rax, :rcx) - - dst_opnd = ctx.stack_push(Type::UnknownImm) - asm.mov(dst_opnd, :rax) - - KeepCompiling - else - opt_send_without_block(jit, ctx, asm) - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_equality_specialized(jit, ctx, asm, gen_eq) - # Create a side-exit to fall back to the interpreter - side_exit = side_exit(jit, ctx) - - a_opnd = ctx.stack_opnd(1) - b_opnd = ctx.stack_opnd(0) - - comptime_a = jit.peek_at_stack(1) - comptime_b = jit.peek_at_stack(0) - - if two_fixnums_on_stack?(jit) - unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_EQ) - return false - end - - guard_two_fixnums(jit, ctx, asm) - - asm.comment('check fixnum equality') - asm.mov(:rax, a_opnd) - asm.mov(:rcx, b_opnd) - asm.cmp(:rax, :rcx) - asm.mov(:rax, gen_eq ? Qfalse : Qtrue) - asm.mov(:rcx, gen_eq ? Qtrue : Qfalse) - asm.cmove(:rax, :rcx) - - # Push the output on the stack - ctx.stack_pop(2) - dst = ctx.stack_push(Type::UnknownImm) - asm.mov(dst, :rax) - - true - elsif C.rb_class_of(comptime_a) == String && C.rb_class_of(comptime_b) == String - unless Invariants.assume_bop_not_redefined(jit, C::STRING_REDEFINED_OP_FLAG, C::BOP_EQ) - # if overridden, emit the generic version - return false - end - - # Guard that a is a String - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_a), a_opnd, StackOpnd[1], comptime_a, side_exit) - - equal_label = asm.new_label(:equal) - ret_label = asm.new_label(:ret) - - # If they are equal by identity, return true - asm.mov(:rax, a_opnd) - asm.mov(:rcx, b_opnd) - asm.cmp(:rax, :rcx) - asm.je(equal_label) - - # Otherwise guard that b is a T_STRING (from type info) or String (from runtime guard) - btype = ctx.get_opnd_type(StackOpnd[0]) - unless btype.string? - # Note: any T_STRING is valid here, but we check for a ::String for simplicity - # To pass a mutable static variable (rb_cString) requires an unsafe block - jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_b), b_opnd, StackOpnd[0], comptime_b, side_exit) - end - - asm.comment('call rb_str_eql_internal') - asm.mov(C_ARGS[0], a_opnd) - asm.mov(C_ARGS[1], b_opnd) - asm.call(gen_eq ? C.rb_str_eql_internal : C.rjit_str_neq_internal) - - # Push the output on the stack - ctx.stack_pop(2) - dst = ctx.stack_push(Type::UnknownImm) - asm.mov(dst, C_RET) - asm.jmp(ret_label) - - asm.write_label(equal_label) - asm.mov(dst, gen_eq ? Qtrue : Qfalse) - - asm.write_label(ret_label) - - true - else - false - end - end - - # NOTE: This clobbers :rax - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_prepare_routine_call(jit, ctx, asm) - jit.record_boundary_patch_point = true - jit_save_pc(jit, asm) - jit_save_sp(ctx, asm) - - # In case the routine calls Ruby methods, it can set local variables - # through Kernel#binding and other means. - ctx.clear_local_types - end - - # NOTE: This clobbers :rax - # @param jit [RubyVM::RJIT::JITState] - # @param asm [RubyVM::RJIT::Assembler] - def jit_save_pc(jit, asm, comment: 'save PC to CFP') - next_pc = jit.pc + jit.insn.len * C.VALUE.size # Use the next one for backtrace and side exits - asm.comment(comment) - asm.mov(:rax, next_pc) - asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) - end - - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_save_sp(ctx, asm) - if ctx.sp_offset != 0 - asm.comment('save SP to CFP') - asm.lea(SP, ctx.sp_opnd) - asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) - ctx.sp_offset = 0 - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jump_to_next_insn(jit, ctx, asm) - reset_depth = ctx.dup - reset_depth.chain_depth = 0 - - next_pc = jit.pc + jit.insn.len * C.VALUE.size - - # We are at the end of the current instruction. Record the boundary. - if jit.record_boundary_patch_point - exit_pos = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_side_exit(next_pc, ctx, ocb_asm) - @ocb.write(ocb_asm) - end - Invariants.record_global_inval_patch(asm, exit_pos) - jit.record_boundary_patch_point = false - end - - jit_direct_jump(jit.iseq, next_pc, reset_depth, asm, comment: 'jump_to_next_insn') - end - - # rb_vm_check_ints - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_check_ints(jit, ctx, asm) - asm.comment('RUBY_VM_CHECK_INTS(ec)') - asm.mov(:eax, DwordPtr[EC, C.rb_execution_context_t.offsetof(:interrupt_flag)]) - asm.test(:eax, :eax) - asm.jnz(side_exit(jit, ctx)) - end - - # See get_lvar_level in compile.c - def get_lvar_level(iseq) - level = 0 - while iseq.to_i != iseq.body.local_iseq.to_i - level += 1 - iseq = iseq.body.parent_iseq - end - return level - end - - # GET_LEP - # @param jit [RubyVM::RJIT::JITState] - # @param asm [RubyVM::RJIT::Assembler] - def jit_get_lep(jit, asm, reg:) - level = get_lvar_level(jit.iseq) - jit_get_ep(asm, level, reg:) - end - - # vm_get_ep - # @param asm [RubyVM::RJIT::Assembler] - def jit_get_ep(asm, level, reg:) - asm.mov(reg, [CFP, C.rb_control_frame_t.offsetof(:ep)]) - level.times do - # GET_PREV_EP: ep[VM_ENV_DATA_INDEX_SPECVAL] & ~0x03 - asm.mov(reg, [reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) - asm.and(reg, ~0x03) - end - end - - # vm_getivar - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_getivar(jit, ctx, asm, comptime_obj, ivar_id, obj_opnd, obj_yarv_opnd) - side_exit = side_exit(jit, ctx) - starting_ctx = ctx.dup # copy for jit_chain_guard - - # Guard not special const - if C::SPECIAL_CONST_P(comptime_obj) - asm.incr_counter(:getivar_special_const) - return CantCompile - end - - case C::BUILTIN_TYPE(comptime_obj) - when C::T_OBJECT - # This is the only supported case for now (ROBJECT_IVPTR) - else - # General case. Call rb_ivar_get(). - # VALUE rb_ivar_get(VALUE obj, ID id) - asm.comment('call rb_ivar_get()') - asm.mov(C_ARGS[0], obj_opnd ? obj_opnd : [CFP, C.rb_control_frame_t.offsetof(:self)]) - asm.mov(C_ARGS[1], ivar_id) - - # The function could raise exceptions. - jit_prepare_routine_call(jit, ctx, asm) # clobbers obj_opnd and :rax - - asm.call(C.rb_ivar_get) - - if obj_opnd # attr_reader - ctx.stack_pop - end - - # Push the ivar on the stack - out_opnd = ctx.stack_push(Type::Unknown) - asm.mov(out_opnd, C_RET) - - # Jump to next instruction. This allows guard chains to share the same successor. - jump_to_next_insn(jit, ctx, asm) - return EndBlock - end - - asm.mov(:rax, obj_opnd ? obj_opnd : [CFP, C.rb_control_frame_t.offsetof(:self)]) - guard_object_is_heap(jit, ctx, asm, :rax, obj_yarv_opnd, :getivar_not_heap) - - shape_id = C.rb_shape_get_shape_id(comptime_obj) - if shape_id == C::OBJ_TOO_COMPLEX_SHAPE_ID - asm.incr_counter(:getivar_too_complex) - return CantCompile - end - - asm.comment('guard shape') - asm.cmp(DwordPtr[:rax, C.rb_shape_id_offset], shape_id) - jit_chain_guard(:jne, jit, starting_ctx, asm, counted_exit(side_exit, :getivar_megamorphic)) - - if obj_opnd - ctx.stack_pop # pop receiver for attr_reader - end - - index = C.rb_shape_get_iv_index(shape_id, ivar_id) - # If there is no IVAR index, then the ivar was undefined - # when we entered the compiler. That means we can just return - # nil for this shape + iv name - if index.nil? - stack_opnd = ctx.stack_push(Type::Nil) - val_opnd = Qnil - else - asm.comment('ROBJECT_IVPTR') - if C::FL_TEST_RAW(comptime_obj, C::ROBJECT_EMBED) - # Access embedded array - asm.mov(:rax, [:rax, C.RObject.offsetof(:as, :ary) + (index * C.VALUE.size)]) - else - # Pull out an ivar table on heap - asm.mov(:rax, [:rax, C.RObject.offsetof(:as, :heap, :ivptr)]) - # Read the table - asm.mov(:rax, [:rax, index * C.VALUE.size]) - end - stack_opnd = ctx.stack_push(Type::Unknown) - val_opnd = :rax - end - asm.mov(stack_opnd, val_opnd) - - # Let guard chains share the same successor - jump_to_next_insn(jit, ctx, asm) - EndBlock - end - - def jit_write_iv(asm, comptime_receiver, recv_reg, temp_reg, ivar_index, set_value, needs_extension) - # Compile time self is embedded and the ivar index lands within the object - embed_test_result = C::FL_TEST_RAW(comptime_receiver, C::ROBJECT_EMBED) && !needs_extension - - if embed_test_result - # Find the IV offset - offs = C.RObject.offsetof(:as, :ary) + ivar_index * C.VALUE.size - - # Write the IV - asm.comment('write IV') - asm.mov(temp_reg, set_value) - asm.mov([recv_reg, offs], temp_reg) - else - # Compile time value is *not* embedded. - - # Get a pointer to the extended table - asm.mov(recv_reg, [recv_reg, C.RObject.offsetof(:as, :heap, :ivptr)]) - - # Write the ivar in to the extended table - asm.comment("write IV"); - asm.mov(temp_reg, set_value) - asm.mov([recv_reg, C.VALUE.size * ivar_index], temp_reg) - end - end - - # vm_caller_setup_arg_block: Handle VM_CALL_ARGS_BLOCKARG cases. - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def guard_block_arg(jit, ctx, asm, calling) - if calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 - block_arg_type = ctx.get_opnd_type(StackOpnd[0]) - case block_arg_type - in Type::Nil - calling.block_handler = C::VM_BLOCK_HANDLER_NONE - in Type::BlockParamProxy - calling.block_handler = C.rb_block_param_proxy - else - asm.incr_counter(:send_block_arg) - return CantCompile - end - end - end - - # vm_search_method - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_search_method(jit, ctx, asm, mid, calling) - assert_equal(true, jit.at_current_insn?) - - # Generate a side exit - side_exit = side_exit(jit, ctx) - - # kw_splat is not supported yet - if calling.flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:send_kw_splat) - return CantCompile - end - - # Get a compile-time receiver and its class - recv_idx = calling.argc + (calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 ? 1 : 0) # blockarg is not popped yet - recv_idx += calling.send_shift - comptime_recv = jit.peek_at_stack(recv_idx) - comptime_recv_klass = C.rb_class_of(comptime_recv) - - # Guard the receiver class (part of vm_search_method_fastpath) - recv_opnd = ctx.stack_opnd(recv_idx) - megamorphic_exit = counted_exit(side_exit, :send_klass_megamorphic) - jit_guard_known_klass(jit, ctx, asm, comptime_recv_klass, recv_opnd, StackOpnd[recv_idx], comptime_recv, megamorphic_exit) - - # Do method lookup (vm_cc_cme(cc) != NULL) - cme = C.rb_callable_method_entry(comptime_recv_klass, mid) - if cme.nil? - asm.incr_counter(:send_missing_cme) - return CantCompile # We don't support vm_call_method_name - end - - # Invalidate on redefinition (part of vm_search_method_fastpath) - Invariants.assume_method_lookup_stable(jit, cme) - - return cme, comptime_recv_klass - end - - # vm_call_general - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_general(jit, ctx, asm, mid, calling, cme, known_recv_class) - jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class) - end - - # vm_call_method - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - # @param send_shift [Integer] The number of shifts needed for VM_CALL_OPT_SEND - def jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class) - # The main check of vm_call_method before vm_call_method_each_type - case C::METHOD_ENTRY_VISI(cme) - in C::METHOD_VISI_PUBLIC - # You can always call public methods - in C::METHOD_VISI_PRIVATE - # Allow only callsites without a receiver - if calling.flags & C::VM_CALL_FCALL == 0 - asm.incr_counter(:send_private) - return CantCompile - end - in C::METHOD_VISI_PROTECTED - # If the method call is an FCALL, it is always valid - if calling.flags & C::VM_CALL_FCALL == 0 - # otherwise we need an ancestry check to ensure the receiver is valid to be called as protected - jit_protected_callee_ancestry_guard(asm, cme, side_exit(jit, ctx)) - end - end - - # Get a compile-time receiver - recv_idx = calling.argc + (calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 ? 1 : 0) # blockarg is not popped yet - recv_idx += calling.send_shift - comptime_recv = jit.peek_at_stack(recv_idx) - recv_opnd = ctx.stack_opnd(recv_idx) - - jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - end - - # Generate ancestry guard for protected callee. - # Calls to protected callees only go through when self.is_a?(klass_that_defines_the_callee). - def jit_protected_callee_ancestry_guard(asm, cme, side_exit) - # See vm_call_method(). - def_class = cme.defined_class - # Note: PC isn't written to current control frame as rb_is_kind_of() shouldn't raise. - # VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass); - - asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:self)]) - asm.mov(C_ARGS[1], to_value(def_class)) - asm.call(C.rb_obj_is_kind_of) - asm.test(C_RET, C_RET) - asm.jz(counted_exit(side_exit, :send_protected_check_failed)) - end - - # vm_call_method_each_type - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - case cme.def.type - in C::VM_METHOD_TYPE_ISEQ - iseq = def_iseq_ptr(cme.def) - jit_call_iseq(jit, ctx, asm, cme, calling, iseq) - in C::VM_METHOD_TYPE_NOTIMPLEMENTED - asm.incr_counter(:send_notimplemented) - return CantCompile - in C::VM_METHOD_TYPE_CFUNC - jit_call_cfunc(jit, ctx, asm, cme, calling, known_recv_class:) - in C::VM_METHOD_TYPE_ATTRSET - jit_call_attrset(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd) - in C::VM_METHOD_TYPE_IVAR - jit_call_ivar(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd) - in C::VM_METHOD_TYPE_MISSING - asm.incr_counter(:send_missing) - return CantCompile - in C::VM_METHOD_TYPE_BMETHOD - jit_call_bmethod(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - in C::VM_METHOD_TYPE_ALIAS - jit_call_alias(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - in C::VM_METHOD_TYPE_OPTIMIZED - jit_call_optimized(jit, ctx, asm, cme, calling, known_recv_class) - in C::VM_METHOD_TYPE_UNDEF - asm.incr_counter(:send_undef) - return CantCompile - in C::VM_METHOD_TYPE_ZSUPER - asm.incr_counter(:send_zsuper) - return CantCompile - in C::VM_METHOD_TYPE_REFINED - asm.incr_counter(:send_refined) - return CantCompile - end - end - - # vm_call_iseq_setup - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: nil) - argc = calling.argc - flags = calling.flags - send_shift = calling.send_shift - - # When you have keyword arguments, there is an extra object that gets - # placed on the stack the represents a bitmap of the keywords that were not - # specified at the call site. We need to keep track of the fact that this - # value is present on the stack in order to properly set up the callee's - # stack pointer. - doing_kw_call = iseq.body.param.flags.has_kw - supplying_kws = flags & C::VM_CALL_KWARG != 0 - - if flags & C::VM_CALL_TAILCALL != 0 - # We can't handle tailcalls - asm.incr_counter(:send_tailcall) - return CantCompile - end - - # No support for callees with these parameters yet as they require allocation - # or complex handling. - if iseq.body.param.flags.has_post - asm.incr_counter(:send_iseq_has_opt) - return CantCompile - end - if iseq.body.param.flags.has_kwrest - asm.incr_counter(:send_iseq_has_kwrest) - return CantCompile - end - - # In order to handle backwards compatibility between ruby 3 and 2 - # ruby2_keywords was introduced. It is called only on methods - # with splat and changes they way they handle them. - # We are just going to not compile these. - # https://www.rubydoc.info/stdlib/core/Proc:ruby2_keywords - if iseq.body.param.flags.ruby2_keywords && flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_iseq_ruby2_keywords) - return CantCompile - end - - iseq_has_rest = iseq.body.param.flags.has_rest - if iseq_has_rest && calling.block_handler == :captured - asm.incr_counter(:send_iseq_has_rest_and_captured) - return CantCompile - end - - if iseq_has_rest && iseq.body.param.flags.has_kw && supplying_kws - asm.incr_counter(:send_iseq_has_rest_and_kw_supplied) - return CantCompile - end - - # If we have keyword arguments being passed to a callee that only takes - # positionals, then we need to allocate a hash. For now we're going to - # call that too complex and bail. - if supplying_kws && !iseq.body.param.flags.has_kw - asm.incr_counter(:send_iseq_has_no_kw) - return CantCompile - end - - # If we have a method accepting no kwargs (**nil), exit if we have passed - # it any kwargs. - if supplying_kws && iseq.body.param.flags.accepts_no_kwarg - asm.incr_counter(:send_iseq_accepts_no_kwarg) - return CantCompile - end - - # For computing number of locals to set up for the callee - num_params = iseq.body.param.size - - # Block parameter handling. This mirrors setup_parameters_complex(). - if iseq.body.param.flags.has_block - if iseq.body.local_iseq.to_i == iseq.to_i - num_params -= 1 - else - # In this case (param.flags.has_block && local_iseq != iseq), - # the block argument is setup as a local variable and requires - # materialization (allocation). Bail. - asm.incr_counter(:send_iseq_materialized_block) - return CantCompile - end - end - - if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_ZSUPER != 0 - # zsuper methods are super calls without any arguments. - # They are also marked as splat, but don't actually have an array - # they pull arguments from, instead we need to change to call - # a different method with the current stack. - asm.incr_counter(:send_iseq_zsuper) - return CantCompile - end - - start_pc_offset = 0 - required_num = iseq.body.param.lead_num - - # This struct represents the metadata about the caller-specified - # keyword arguments. - kw_arg = calling.kwarg - kw_arg_num = if kw_arg.nil? - 0 - else - kw_arg.keyword_len - end - - # Arity handling and optional parameter setup - opts_filled = argc - required_num - kw_arg_num - opt_num = iseq.body.param.opt_num - opts_missing = opt_num - opts_filled - - if doing_kw_call && flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_iseq_splat_with_kw) - return CantCompile - end - - if flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:send_iseq_kw_splat) - return CantCompile - end - - if iseq_has_rest && opt_num != 0 - asm.incr_counter(:send_iseq_has_rest_and_optional) - return CantCompile - end - - if opts_filled < 0 && flags & C::VM_CALL_ARGS_SPLAT == 0 - # Too few arguments and no splat to make up for it - asm.incr_counter(:send_iseq_arity_error) - return CantCompile - end - - if opts_filled > opt_num && !iseq_has_rest - # Too many arguments and no place to put them (i.e. rest arg) - asm.incr_counter(:send_iseq_arity_error) - return CantCompile - end - - block_arg = flags & C::VM_CALL_ARGS_BLOCKARG != 0 - - # Guard block_arg_type - if guard_block_arg(jit, ctx, asm, calling) == CantCompile - return CantCompile - end - - # If we have unfilled optional arguments and keyword arguments then we - # would need to adjust the arguments location to account for that. - # For now we aren't handling this case. - if doing_kw_call && opts_missing > 0 - asm.incr_counter(:send_iseq_missing_optional_kw) - return CantCompile - end - - # We will handle splat case later - if opt_num > 0 && flags & C::VM_CALL_ARGS_SPLAT == 0 - num_params -= opts_missing - start_pc_offset = iseq.body.param.opt_table[opts_filled] - end - - if doing_kw_call - # Here we're calling a method with keyword arguments and specifying - # keyword arguments at this call site. - - # This struct represents the metadata about the callee-specified - # keyword parameters. - keyword = iseq.body.param.keyword - keyword_num = keyword.num - keyword_required_num = keyword.required_num - - required_kwargs_filled = 0 - - if keyword_num > 30 - # We have so many keywords that (1 << num) encoded as a FIXNUM - # (which shifts it left one more) no longer fits inside a 32-bit - # immediate. - asm.incr_counter(:send_iseq_too_many_kwargs) - return CantCompile - end - - # Check that the kwargs being passed are valid - if supplying_kws - # This is the list of keyword arguments that the callee specified - # in its initial declaration. - # SAFETY: see compile.c for sizing of this slice. - callee_kwargs = keyword_num.times.map { |i| keyword.table[i] } - - # Here we're going to build up a list of the IDs that correspond to - # the caller-specified keyword arguments. If they're not in the - # same order as the order specified in the callee declaration, then - # we're going to need to generate some code to swap values around - # on the stack. - caller_kwargs = [] - kw_arg.keyword_len.times do |kwarg_idx| - sym = C.to_ruby(kw_arg[:keywords][kwarg_idx]) - caller_kwargs << C.rb_sym2id(sym) - end - - # First, we're going to be sure that the names of every - # caller-specified keyword argument correspond to a name in the - # list of callee-specified keyword parameters. - caller_kwargs.each do |caller_kwarg| - search_result = callee_kwargs.map.with_index.find { |kwarg, _| kwarg == caller_kwarg } - - case search_result - in nil - # If the keyword was never found, then we know we have a - # mismatch in the names of the keyword arguments, so we need to - # bail. - asm.incr_counter(:send_iseq_kwargs_mismatch) - return CantCompile - in _, callee_idx if callee_idx < keyword_required_num - # Keep a count to ensure all required kwargs are specified - required_kwargs_filled += 1 - else - end - end - end - assert_equal(true, required_kwargs_filled <= keyword_required_num) - if required_kwargs_filled != keyword_required_num - asm.incr_counter(:send_iseq_kwargs_mismatch) - return CantCompile - end - end - - # Check if we need the arg0 splat handling of vm_callee_setup_block_arg - arg_setup_block = (calling.block_handler == :captured) # arg_setup_type: arg_setup_block (invokeblock) - block_arg0_splat = arg_setup_block && argc == 1 && - (iseq.body.param.flags.has_lead || opt_num > 1) && - !iseq.body.param.flags.ambiguous_param0 - if block_arg0_splat - # If block_arg0_splat, we still need side exits after splat, but - # doing push_splat_args here disallows it. So bail out. - if flags & C::VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest - asm.incr_counter(:invokeblock_iseq_arg0_args_splat) - return CantCompile - end - # The block_arg0_splat implementation is for the rb_simple_iseq_p case, - # but doing_kw_call means it's not a simple ISEQ. - if doing_kw_call - asm.incr_counter(:invokeblock_iseq_arg0_has_kw) - return CantCompile - end - # The block_arg0_splat implementation cannot deal with optional parameters. - # This is a setup_parameters_complex() situation and interacts with the - # starting position of the callee. - if opt_num > 1 - asm.incr_counter(:invokeblock_iseq_arg0_optional) - return CantCompile - end - end - if flags & C::VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest - array = jit.peek_at_stack(block_arg ? 1 : 0) - splat_array_length = if array.nil? - 0 - else - array.length - end - - if opt_num == 0 && required_num != splat_array_length + argc - 1 - asm.incr_counter(:send_iseq_splat_arity_error) - return CantCompile - end - end - - # Don't compile forwardable iseqs - if iseq.body.param.flags.forwardable - return CantCompile - end - - # We will not have CantCompile from here. - - if block_arg - ctx.stack_pop(1) - end - - if calling.block_handler == C::VM_BLOCK_HANDLER_NONE && iseq.body.builtin_attrs & C::BUILTIN_ATTR_LEAF != 0 - if jit_leaf_builtin_func(jit, ctx, asm, flags, iseq) - return KeepCompiling - end - end - - # Number of locals that are not parameters - num_locals = iseq.body.local_table_size - num_params - - # Stack overflow check - # Note that vm_push_frame checks it against a decremented cfp, hence the multiply by 2. - # #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin) - asm.comment('stack overflow check') - locals_offs = C.VALUE.size * (num_locals + iseq.body.stack_max) + 2 * C.rb_control_frame_t.size - asm.lea(:rax, ctx.sp_opnd(locals_offs)) - asm.cmp(CFP, :rax) - asm.jbe(counted_exit(side_exit(jit, ctx), :send_stackoverflow)) - - # push_splat_args does stack manipulation so we can no longer side exit - if splat_array_length - remaining_opt = (opt_num + required_num) - (splat_array_length + (argc - 1)) - - if opt_num > 0 - # We are going to jump to the correct offset based on how many optional - # params are remaining. - offset = opt_num - remaining_opt - start_pc_offset = iseq.body.param.opt_table[offset] - end - # We are going to assume that the splat fills - # all the remaining arguments. In the generated code - # we test if this is true and if not side exit. - argc = argc - 1 + splat_array_length + remaining_opt - push_splat_args(splat_array_length, jit, ctx, asm) - - remaining_opt.times do - # We need to push nil for the optional arguments - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, Qnil) - end - end - - # This is a .send call and we need to adjust the stack - if flags & C::VM_CALL_OPT_SEND != 0 - handle_opt_send_shift_stack(asm, argc, ctx, send_shift:) - end - - if iseq_has_rest - # We are going to allocate so setting pc and sp. - jit_save_pc(jit, asm) # clobbers rax - jit_save_sp(ctx, asm) - - if flags & C::VM_CALL_ARGS_SPLAT != 0 - non_rest_arg_count = argc - 1 - # We start by dupping the array because someone else might have - # a reference to it. - array = ctx.stack_pop(1) - asm.mov(C_ARGS[0], array) - asm.call(C.rb_ary_dup) - array = C_RET - if non_rest_arg_count > required_num - # If we have more arguments than required, we need to prepend - # the items from the stack onto the array. - diff = (non_rest_arg_count - required_num) - - # diff is >0 so no need to worry about null pointer - asm.comment('load pointer to array elements') - offset_magnitude = C.VALUE.size * diff - values_opnd = ctx.sp_opnd(-offset_magnitude) - values_ptr = :rcx - asm.lea(values_ptr, values_opnd) - - asm.comment('prepend stack values to rest array') - asm.mov(C_ARGS[0], diff) - asm.mov(C_ARGS[1], values_ptr) - asm.mov(C_ARGS[2], array) - asm.call(C.rb_ary_unshift_m) - ctx.stack_pop(diff) - - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - # We now should have the required arguments - # and an array of all the rest arguments - argc = required_num + 1 - elsif non_rest_arg_count < required_num - # If we have fewer arguments than required, we need to take some - # from the array and move them to the stack. - diff = (required_num - non_rest_arg_count) - # This moves the arguments onto the stack. But it doesn't modify the array. - move_rest_args_to_stack(array, diff, jit, ctx, asm) - - # We will now slice the array to give us a new array of the correct size - asm.mov(C_ARGS[0], array) - asm.mov(C_ARGS[1], diff) - asm.call(C.rjit_rb_ary_subseq_length) - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - - # We now should have the required arguments - # and an array of all the rest arguments - argc = required_num + 1 - else - # The arguments are equal so we can just push to the stack - assert_equal(non_rest_arg_count, required_num) - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, array) - end - else - assert_equal(true, argc >= required_num) - n = (argc - required_num) - argc = required_num + 1 - # If n is 0, then elts is never going to be read, so we can just pass null - if n == 0 - values_ptr = 0 - else - asm.comment('load pointer to array elements') - offset_magnitude = C.VALUE.size * n - values_opnd = ctx.sp_opnd(-offset_magnitude) - values_ptr = :rcx - asm.lea(values_ptr, values_opnd) - end - - asm.mov(C_ARGS[0], EC) - asm.mov(C_ARGS[1], n) - asm.mov(C_ARGS[2], values_ptr) - asm.call(C.rb_ec_ary_new_from_values) - - ctx.stack_pop(n) - stack_ret = ctx.stack_push(Type::TArray) - asm.mov(stack_ret, C_RET) - end - end - - if doing_kw_call - # Here we're calling a method with keyword arguments and specifying - # keyword arguments at this call site. - - # Number of positional arguments the callee expects before the first - # keyword argument - args_before_kw = required_num + opt_num - - # This struct represents the metadata about the caller-specified - # keyword arguments. - ci_kwarg = calling.kwarg - caller_keyword_len = if ci_kwarg.nil? - 0 - else - ci_kwarg.keyword_len - end - - # This struct represents the metadata about the callee-specified - # keyword parameters. - keyword = iseq.body.param.keyword - - asm.comment('keyword args') - - # This is the list of keyword arguments that the callee specified - # in its initial declaration. - callee_kwargs = keyword.table - total_kwargs = keyword.num - - # Here we're going to build up a list of the IDs that correspond to - # the caller-specified keyword arguments. If they're not in the - # same order as the order specified in the callee declaration, then - # we're going to need to generate some code to swap values around - # on the stack. - caller_kwargs = [] - - caller_keyword_len.times do |kwarg_idx| - sym = C.to_ruby(ci_kwarg[:keywords][kwarg_idx]) - caller_kwargs << C.rb_sym2id(sym) - end - kwarg_idx = caller_keyword_len - - unspecified_bits = 0 - - keyword_required_num = keyword.required_num - (keyword_required_num...total_kwargs).each do |callee_idx| - already_passed = false - callee_kwarg = callee_kwargs[callee_idx] - - caller_keyword_len.times do |caller_idx| - if caller_kwargs[caller_idx] == callee_kwarg - already_passed = true - break - end - end - - unless already_passed - # Reserve space on the stack for each default value we'll be - # filling in (which is done in the next loop). Also increments - # argc so that the callee's SP is recorded correctly. - argc += 1 - default_arg = ctx.stack_push(Type::Unknown) - - # callee_idx - keyword->required_num is used in a couple of places below. - req_num = keyword.required_num - extra_args = callee_idx - req_num - - # VALUE default_value = keyword->default_values[callee_idx - keyword->required_num]; - default_value = keyword.default_values[extra_args] - - if default_value == Qundef - # Qundef means that this value is not constant and must be - # recalculated at runtime, so we record it in unspecified_bits - # (Qnil is then used as a placeholder instead of Qundef). - unspecified_bits |= 0x01 << extra_args - default_value = Qnil - end - - asm.mov(:rax, default_value) - asm.mov(default_arg, :rax) - - caller_kwargs[kwarg_idx] = callee_kwarg - kwarg_idx += 1 - end - end - - assert_equal(kwarg_idx, total_kwargs) - - # Next, we're going to loop through every keyword that was - # specified by the caller and make sure that it's in the correct - # place. If it's not we're going to swap it around with another one. - total_kwargs.times do |kwarg_idx| - callee_kwarg = callee_kwargs[kwarg_idx] - - # If the argument is already in the right order, then we don't - # need to generate any code since the expected value is already - # in the right place on the stack. - if callee_kwarg == caller_kwargs[kwarg_idx] - next - end - - # In this case the argument is not in the right place, so we - # need to find its position where it _should_ be and swap with - # that location. - ((kwarg_idx + 1)...total_kwargs).each do |swap_idx| - if callee_kwarg == caller_kwargs[swap_idx] - # First we're going to generate the code that is going - # to perform the actual swapping at runtime. - offset0 = argc - 1 - swap_idx - args_before_kw - offset1 = argc - 1 - kwarg_idx - args_before_kw - stack_swap(jit, ctx, asm, offset0, offset1) - - # Next we're going to do some bookkeeping on our end so - # that we know the order that the arguments are - # actually in now. - caller_kwargs[kwarg_idx], caller_kwargs[swap_idx] = - caller_kwargs[swap_idx], caller_kwargs[kwarg_idx] - - break - end - end - end - - # Keyword arguments cause a special extra local variable to be - # pushed onto the stack that represents the parameters that weren't - # explicitly given a value and have a non-constant default. - asm.mov(ctx.stack_opnd(-1), C.to_value(unspecified_bits)) - end - - # Same as vm_callee_setup_block_arg_arg0_check and vm_callee_setup_block_arg_arg0_splat - # on vm_callee_setup_block_arg for arg_setup_block. This is done after CALLER_SETUP_ARG - # and CALLER_REMOVE_EMPTY_KW_SPLAT, so this implementation is put here. This may need - # side exits, so you still need to allow side exits here if block_arg0_splat is true. - # Note that you can't have side exits after this arg0 splat. - if block_arg0_splat - asm.incr_counter(:send_iseq_block_arg0_splat) - return CantCompile - end - - # Create a context for the callee - callee_ctx = Context.new - - # Set the argument types in the callee's context - argc.times do |arg_idx| - stack_offs = argc - arg_idx - 1 - arg_type = ctx.get_opnd_type(StackOpnd[stack_offs]) - callee_ctx.set_local_type(arg_idx, arg_type) - end - - recv_type = if calling.block_handler == :captured - Type::Unknown # we don't track the type information of captured->self for now - else - ctx.get_opnd_type(StackOpnd[argc]) - end - callee_ctx.upgrade_opnd_type(SelfOpnd, recv_type) - - # Setup the new frame - frame_type ||= C::VM_FRAME_MAGIC_METHOD | C::VM_ENV_FLAG_LOCAL - jit_push_frame( - jit, ctx, asm, cme, flags, argc, frame_type, calling.block_handler, - iseq: iseq, - local_size: num_locals, - stack_max: iseq.body.stack_max, - prev_ep:, - doing_kw_call:, - ) - - # Directly jump to the entry point of the callee - pc = (iseq.body.iseq_encoded + start_pc_offset).to_i - jit_direct_jump(iseq, pc, callee_ctx, asm) - - EndBlock - end - - def jit_leaf_builtin_func(jit, ctx, asm, flags, iseq) - builtin_func = builtin_function(iseq) - if builtin_func.nil? - return false - end - - # this is a .send call not currently supported for builtins - if flags & C::VM_CALL_OPT_SEND != 0 - return false - end - - builtin_argc = builtin_func.argc - if builtin_argc + 1 >= C_ARGS.size - return false - end - - asm.comment('inlined leaf builtin') - - # The callee may allocate, e.g. Integer#abs on a Bignum. - # Save SP for GC, save PC for allocation tracing, and prepare - # for global invalidation after GC's VM lock contention. - jit_prepare_routine_call(jit, ctx, asm) - - # Call the builtin func (ec, recv, arg1, arg2, ...) - asm.mov(C_ARGS[0], EC) - - # Copy self and arguments - (0..builtin_argc).each do |i| - stack_opnd = ctx.stack_opnd(builtin_argc - i) - asm.mov(C_ARGS[i + 1], stack_opnd) - end - ctx.stack_pop(builtin_argc + 1) - asm.call(builtin_func.func_ptr) - - # Push the return value - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - return true - end - - # vm_call_cfunc - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_cfunc(jit, ctx, asm, cme, calling, known_recv_class: nil) - argc = calling.argc - flags = calling.flags - - cfunc = cme.def.body.cfunc - cfunc_argc = cfunc.argc - - # If the function expects a Ruby array of arguments - if cfunc_argc < 0 && cfunc_argc != -1 - asm.incr_counter(:send_cfunc_ruby_array_varg) - return CantCompile - end - - # We aren't handling a vararg cfuncs with splat currently. - if flags & C::VM_CALL_ARGS_SPLAT != 0 && cfunc_argc == -1 - asm.incr_counter(:send_args_splat_cfunc_var_args) - return CantCompile - end - - if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_ZSUPER != 0 - # zsuper methods are super calls without any arguments. - # They are also marked as splat, but don't actually have an array - # they pull arguments from, instead we need to change to call - # a different method with the current stack. - asm.incr_counter(:send_args_splat_cfunc_zuper) - return CantCompile; - end - - # In order to handle backwards compatibility between ruby 3 and 2 - # ruby2_keywords was introduced. It is called only on methods - # with splat and changes they way they handle them. - # We are just going to not compile these. - # https://docs.ruby-lang.org/en/3.2/Module.html#method-i-ruby2_keywords - if jit.iseq.body.param.flags.ruby2_keywords && flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_args_splat_cfunc_ruby2_keywords) - return CantCompile; - end - - kw_arg = calling.kwarg - kw_arg_num = if kw_arg.nil? - 0 - else - kw_arg.keyword_len - end - - if kw_arg_num != 0 && flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_cfunc_splat_with_kw) - return CantCompile - end - - if c_method_tracing_currently_enabled? - # Don't JIT if tracing c_call or c_return - asm.incr_counter(:send_cfunc_tracing) - return CantCompile - end - - # Delegate to codegen for C methods if we have it. - if kw_arg.nil? && flags & C::VM_CALL_OPT_SEND == 0 && flags & C::VM_CALL_ARGS_SPLAT == 0 && (cfunc_argc == -1 || argc == cfunc_argc) - known_cfunc_codegen = lookup_cfunc_codegen(cme.def) - if known_cfunc_codegen&.call(jit, ctx, asm, argc, known_recv_class) - # cfunc codegen generated code. Terminate the block so - # there isn't multiple calls in the same block. - jump_to_next_insn(jit, ctx, asm) - return EndBlock - end - end - - # Check for interrupts - jit_check_ints(jit, ctx, asm) - - # Stack overflow check - # #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin) - # REG_CFP <= REG_SP + 4 * SIZEOF_VALUE + sizeof(rb_control_frame_t) - asm.comment('stack overflow check') - asm.lea(:rax, ctx.sp_opnd(C.VALUE.size * 4 + 2 * C.rb_control_frame_t.size)) - asm.cmp(CFP, :rax) - asm.jbe(counted_exit(side_exit(jit, ctx), :send_stackoverflow)) - - # Number of args which will be passed through to the callee - # This is adjusted by the kwargs being combined into a hash. - passed_argc = if kw_arg.nil? - argc - else - argc - kw_arg_num + 1 - end - - # If the argument count doesn't match - if cfunc_argc >= 0 && cfunc_argc != passed_argc && flags & C::VM_CALL_ARGS_SPLAT == 0 - asm.incr_counter(:send_cfunc_argc_mismatch) - return CantCompile - end - - # Don't JIT functions that need C stack arguments for now - if cfunc_argc >= 0 && passed_argc + 1 > C_ARGS.size - asm.incr_counter(:send_cfunc_toomany_args) - return CantCompile - end - - block_arg = flags & C::VM_CALL_ARGS_BLOCKARG != 0 - - # Guard block_arg_type - if guard_block_arg(jit, ctx, asm, calling) == CantCompile - return CantCompile - end - - if block_arg - ctx.stack_pop(1) - end - - # push_splat_args does stack manipulation so we can no longer side exit - if flags & C::VM_CALL_ARGS_SPLAT != 0 - assert_equal(true, cfunc_argc >= 0) - required_args = cfunc_argc - (argc - 1) - # + 1 because we pass self - if required_args + 1 >= C_ARGS.size - asm.incr_counter(:send_cfunc_toomany_args) - return CantCompile - end - - # We are going to assume that the splat fills - # all the remaining arguments. So the number of args - # should just equal the number of args the cfunc takes. - # In the generated code we test if this is true - # and if not side exit. - argc = cfunc_argc - passed_argc = argc - push_splat_args(required_args, jit, ctx, asm) - end - - # This is a .send call and we need to adjust the stack - if flags & C::VM_CALL_OPT_SEND != 0 - handle_opt_send_shift_stack(asm, argc, ctx, send_shift: calling.send_shift) - end - - # Points to the receiver operand on the stack - - # Store incremented PC into current control frame in case callee raises. - jit_save_pc(jit, asm) - - # Increment the stack pointer by 3 (in the callee) - # sp += 3 - - frame_type = C::VM_FRAME_MAGIC_CFUNC | C::VM_FRAME_FLAG_CFRAME | C::VM_ENV_FLAG_LOCAL - if kw_arg - frame_type |= C::VM_FRAME_FLAG_CFRAME_KW - end - - jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, calling.block_handler) - - if kw_arg - # Build a hash from all kwargs passed - asm.comment('build_kwhash') - imemo_ci = calling.ci_addr - # we assume all callinfos with kwargs are on the GC heap - assert_equal(true, C.imemo_type_p(imemo_ci, C.imemo_callinfo)) - asm.mov(C_ARGS[0], imemo_ci) - asm.lea(C_ARGS[1], ctx.sp_opnd(0)) - asm.call(C.rjit_build_kwhash) - - # Replace the stack location at the start of kwargs with the new hash - stack_opnd = ctx.stack_opnd(argc - passed_argc) - asm.mov(stack_opnd, C_RET) - end - - # Copy SP because REG_SP will get overwritten - sp = :rax - asm.lea(sp, ctx.sp_opnd(0)) - - # Pop the C function arguments from the stack (in the caller) - ctx.stack_pop(argc + 1) - - # Write interpreter SP into CFP. - # Needed in case the callee yields to the block. - jit_save_sp(ctx, asm) - - # Non-variadic method - case cfunc_argc - in (0..) # Non-variadic method - # Copy the arguments from the stack to the C argument registers - # self is the 0th argument and is at index argc from the stack top - (0..passed_argc).each do |i| - asm.mov(C_ARGS[i], [sp, -(argc + 1 - i) * C.VALUE.size]) - end - in -1 # Variadic method: rb_f_puts(int argc, VALUE *argv, VALUE recv) - # The method gets a pointer to the first argument - # rb_f_puts(int argc, VALUE *argv, VALUE recv) - asm.mov(C_ARGS[0], passed_argc) - asm.lea(C_ARGS[1], [sp, -argc * C.VALUE.size]) # argv - asm.mov(C_ARGS[2], [sp, -(argc + 1) * C.VALUE.size]) # recv - end - - # Call the C function - # VALUE ret = (cfunc->func)(recv, argv[0], argv[1]); - # cfunc comes from compile-time cme->def, which we assume to be stable. - # Invalidation logic is in yjit_method_lookup_change() - asm.comment('call C function') - asm.mov(:rax, cfunc.func) - asm.call(:rax) # TODO: use rel32 if close enough - - # Record code position for TracePoint patching. See full_cfunc_return(). - Invariants.record_global_inval_patch(asm, full_cfunc_return) - - # Push the return value on the Ruby stack - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - - # Pop the stack frame (ec->cfp++) - # Instead of recalculating, we can reuse the previous CFP, which is stored in a callee-saved - # register - asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], CFP) - - # cfunc calls may corrupt types - ctx.clear_local_types - - # Note: the return block of jit_call_iseq has ctx->sp_offset == 1 - # which allows for sharing the same successor. - - # Jump (fall through) to the call continuation block - # We do this to end the current block after the call - assert_equal(1, ctx.sp_offset) - jump_to_next_insn(jit, ctx, asm) - EndBlock - end - - # vm_call_attrset - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_attrset(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd) - argc = calling.argc - flags = calling.flags - send_shift = calling.send_shift - - if flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_attrset_splat) - return CantCompile - end - if flags & C::VM_CALL_KWARG != 0 - asm.incr_counter(:send_attrset_kwarg) - return CantCompile - elsif argc != 1 || !C.RB_TYPE_P(comptime_recv, C::RUBY_T_OBJECT) - asm.incr_counter(:send_attrset_method) - return CantCompile - elsif c_method_tracing_currently_enabled? - # Can't generate code for firing c_call and c_return events - # See :attr-tracing: - asm.incr_counter(:send_c_tracingg) - return CantCompile - elsif flags & C::VM_CALL_ARGS_BLOCKARG != 0 - asm.incr_counter(:send_block_arg) - return CantCompile - end - - ivar_name = cme.def.body.attr.id - - # This is a .send call and we need to adjust the stack - if flags & C::VM_CALL_OPT_SEND != 0 - handle_opt_send_shift_stack(asm, argc, ctx, send_shift:) - end - - # Save the PC and SP because the callee may allocate - # Note that this modifies REG_SP, which is why we do it first - jit_prepare_routine_call(jit, ctx, asm) - - # Get the operands from the stack - val_opnd = ctx.stack_pop(1) - recv_opnd = ctx.stack_pop(1) - - # Call rb_vm_set_ivar_id with the receiver, the ivar name, and the value - asm.mov(C_ARGS[0], recv_opnd) - asm.mov(C_ARGS[1], ivar_name) - asm.mov(C_ARGS[2], val_opnd) - asm.call(C.rb_vm_set_ivar_id) - - out_opnd = ctx.stack_push(Type::Unknown) - asm.mov(out_opnd, C_RET) - - KeepCompiling - end - - # vm_call_ivar (+ part of vm_call_method_each_type) - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_ivar(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd) - argc = calling.argc - flags = calling.flags - - if flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_ivar_splat) - return CantCompile - end - - if argc != 0 - asm.incr_counter(:send_arity) - return CantCompile - end - - # We don't support handle_opt_send_shift_stack for this yet. - if flags & C::VM_CALL_OPT_SEND != 0 - asm.incr_counter(:send_ivar_opt_send) - return CantCompile - end - - ivar_id = cme.def.body.attr.id - - # Not handling block_handler - if flags & C::VM_CALL_ARGS_BLOCKARG != 0 - asm.incr_counter(:send_block_arg) - return CantCompile - end - - jit_getivar(jit, ctx, asm, comptime_recv, ivar_id, recv_opnd, StackOpnd[0]) - end - - # vm_call_bmethod - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_bmethod(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - proc_addr = cme.def.body.bmethod.proc - - proc_t = C.rb_yjit_get_proc_ptr(proc_addr) - proc_block = proc_t.block - - if proc_block.type != C.block_type_iseq - asm.incr_counter(:send_bmethod_not_iseq) - return CantCompile - end - - capture = proc_block.as.captured - iseq = capture.code.iseq - - # TODO: implement this - # Optimize for single ractor mode and avoid runtime check for - # "defined with an un-shareable Proc in a different Ractor" - # if !assume_single_ractor_mode(jit, ocb) - # return CantCompile; - # end - - # Passing a block to a block needs logic different from passing - # a block to a method and sometimes requires allocation. Bail for now. - if calling.block_handler != C::VM_BLOCK_HANDLER_NONE - asm.incr_counter(:send_bmethod_blockarg) - return CantCompile - end - - jit_call_iseq( - jit, ctx, asm, cme, calling, iseq, - frame_type: C::VM_FRAME_MAGIC_BLOCK | C::VM_FRAME_FLAG_BMETHOD | C::VM_FRAME_FLAG_LAMBDA, - prev_ep: capture.ep, - ) - end - - # vm_call_alias - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_alias(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - cme = C.rb_aliased_callable_method_entry(cme) - jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class) - end - - # vm_call_optimized - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_optimized(jit, ctx, asm, cme, calling, known_recv_class) - if calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 - # Not working yet - asm.incr_counter(:send_block_arg) - return CantCompile - end - - case cme.def.body.optimized.type - in C::OPTIMIZED_METHOD_TYPE_SEND - jit_call_opt_send(jit, ctx, asm, cme, calling, known_recv_class) - in C::OPTIMIZED_METHOD_TYPE_CALL - jit_call_opt_call(jit, ctx, asm, cme, calling.flags, calling.argc, calling.block_handler, known_recv_class, send_shift: calling.send_shift) - in C::OPTIMIZED_METHOD_TYPE_BLOCK_CALL - asm.incr_counter(:send_optimized_block_call) - return CantCompile - in C::OPTIMIZED_METHOD_TYPE_STRUCT_AREF - jit_call_opt_struct_aref(jit, ctx, asm, cme, calling.flags, calling.argc, calling.block_handler, known_recv_class, send_shift: calling.send_shift) - in C::OPTIMIZED_METHOD_TYPE_STRUCT_ASET - asm.incr_counter(:send_optimized_struct_aset) - return CantCompile - end - end - - # vm_call_opt_send - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_opt_send(jit, ctx, asm, cme, calling, known_recv_class) - if jit_caller_setup_arg(jit, ctx, asm, calling.flags) == CantCompile - return CantCompile - end - - if calling.argc == 0 - asm.incr_counter(:send_optimized_send_no_args) - return CantCompile - end - - calling.argc -= 1 - # We aren't handling `send(:send, ...)` yet. This might work, but not tested yet. - if calling.send_shift > 0 - asm.incr_counter(:send_optimized_send_send) - return CantCompile - end - # Lazily handle stack shift in handle_opt_send_shift_stack - calling.send_shift += 1 - - jit_call_symbol(jit, ctx, asm, cme, calling, known_recv_class, C::VM_CALL_FCALL) - end - - # vm_call_opt_call - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_opt_call(jit, ctx, asm, cme, flags, argc, block_handler, known_recv_class, send_shift:) - if block_handler != C::VM_BLOCK_HANDLER_NONE - asm.incr_counter(:send_optimized_call_block) - return CantCompile - end - - if flags & C::VM_CALL_KWARG != 0 - asm.incr_counter(:send_optimized_call_kwarg) - return CantCompile - end - - if flags & C::VM_CALL_ARGS_SPLAT != 0 - asm.incr_counter(:send_optimized_call_splat) - return CantCompile - end - - # TODO: implement this - # Optimize for single ractor mode and avoid runtime check for - # "defined with an un-shareable Proc in a different Ractor" - # if !assume_single_ractor_mode(jit, ocb) - # return CantCompile - # end - - # If this is a .send call we need to adjust the stack - if flags & C::VM_CALL_OPT_SEND != 0 - handle_opt_send_shift_stack(asm, argc, ctx, send_shift:) - end - - # About to reset the SP, need to load this here - recv_idx = argc # blockarg is not supported. send_shift is already handled. - asm.mov(:rcx, ctx.stack_opnd(recv_idx)) # recv - - # Save the PC and SP because the callee can make Ruby calls - jit_prepare_routine_call(jit, ctx, asm) # NOTE: clobbers rax - - asm.lea(:rax, ctx.sp_opnd(0)) # sp - - kw_splat = flags & C::VM_CALL_KW_SPLAT - - asm.mov(C_ARGS[0], :rcx) - asm.mov(C_ARGS[1], EC) - asm.mov(C_ARGS[2], argc) - asm.lea(C_ARGS[3], [:rax, -argc * C.VALUE.size]) # stack_argument_pointer. NOTE: C_ARGS[3] is rcx - asm.mov(C_ARGS[4], kw_splat) - asm.mov(C_ARGS[5], C::VM_BLOCK_HANDLER_NONE) - asm.call(C.rjit_optimized_call) - - ctx.stack_pop(argc + 1) - - stack_ret = ctx.stack_push(Type::Unknown) - asm.mov(stack_ret, C_RET) - return KeepCompiling - end - - # vm_call_opt_struct_aref - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_opt_struct_aref(jit, ctx, asm, cme, flags, argc, block_handler, known_recv_class, send_shift:) - if argc != 0 - asm.incr_counter(:send_optimized_struct_aref_error) - return CantCompile - end - - if c_method_tracing_currently_enabled? - # Don't JIT if tracing c_call or c_return - asm.incr_counter(:send_cfunc_tracing) - return CantCompile - end - - off = cme.def.body.optimized.index - - recv_idx = argc # blockarg is not supported - recv_idx += send_shift - comptime_recv = jit.peek_at_stack(recv_idx) - - # This is a .send call and we need to adjust the stack - if flags & C::VM_CALL_OPT_SEND != 0 - handle_opt_send_shift_stack(asm, argc, ctx, send_shift:) - end - - # All structs from the same Struct class should have the same - # length. So if our comptime_recv is embedded all runtime - # structs of the same class should be as well, and the same is - # true of the converse. - embedded = C::FL_TEST_RAW(comptime_recv, C::RSTRUCT_EMBED_LEN_MASK) - - asm.comment('struct aref') - asm.mov(:rax, ctx.stack_pop(1)) # recv - - if embedded - asm.mov(:rax, [:rax, C.RStruct.offsetof(:as, :ary) + (C.VALUE.size * off)]) - else - asm.mov(:rax, [:rax, C.RStruct.offsetof(:as, :heap, :ptr)]) - asm.mov(:rax, [:rax, C.VALUE.size * off]) - end - - ret = ctx.stack_push(Type::Unknown) - asm.mov(ret, :rax) - - jump_to_next_insn(jit, ctx, asm) - EndBlock - end - - # vm_call_opt_send (lazy part) - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def handle_opt_send_shift_stack(asm, argc, ctx, send_shift:) - # We don't support `send(:send, ...)` for now. - assert_equal(1, send_shift) - - asm.comment('shift stack') - (0...argc).reverse_each do |i| - opnd = ctx.stack_opnd(i) - opnd2 = ctx.stack_opnd(i + 1) - asm.mov(:rax, opnd) - asm.mov(opnd2, :rax) - end - - ctx.shift_stack(argc) - end - - # vm_call_symbol - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_call_symbol(jit, ctx, asm, cme, calling, known_recv_class, flags) - flags |= C::VM_CALL_OPT_SEND | (calling.kw_splat ? C::VM_CALL_KW_SPLAT : 0) - - comptime_symbol = jit.peek_at_stack(calling.argc) - if comptime_symbol.class != String && !static_symbol?(comptime_symbol) - asm.incr_counter(:send_optimized_send_not_sym_or_str) - return CantCompile - end - - mid = C.get_symbol_id(comptime_symbol) - if mid == 0 - asm.incr_counter(:send_optimized_send_null_mid) - return CantCompile - end - - asm.comment("Guard #{comptime_symbol.inspect} is on stack") - class_changed_exit = counted_exit(side_exit(jit, ctx), :send_optimized_send_mid_class_changed) - jit_guard_known_klass( - jit, ctx, asm, C.rb_class_of(comptime_symbol), ctx.stack_opnd(calling.argc), - StackOpnd[calling.argc], comptime_symbol, class_changed_exit, - ) - asm.mov(C_ARGS[0], ctx.stack_opnd(calling.argc)) - asm.call(C.rb_get_symbol_id) - asm.cmp(C_RET, mid) - id_changed_exit = counted_exit(side_exit(jit, ctx), :send_optimized_send_mid_id_changed) - jit_chain_guard(:jne, jit, ctx, asm, id_changed_exit) - - # rb_callable_method_entry_with_refinements - calling.flags = flags - cme, _ = jit_search_method(jit, ctx, asm, mid, calling) - if cme == CantCompile - return CantCompile - end - - if flags & C::VM_CALL_FCALL != 0 - return jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class) - end - - raise NotImplementedError # unreachable for now - end - - # vm_push_frame - # - # Frame structure: - # | args | locals | cme/cref | block_handler/prev EP | frame type (EP here) | stack bottom (SP here) - # - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, block_handler, iseq: nil, local_size: 0, stack_max: 0, prev_ep: nil, doing_kw_call: nil) - # Save caller SP and PC before pushing a callee frame for backtrace and side exits - asm.comment('save SP to caller CFP') - recv_idx = argc # blockarg is already popped - recv_idx += (block_handler == :captured) ? 0 : 1 # receiver is not on stack when captured->self is used - if iseq - # Skip setting this to SP register. This cfp->sp will be copied to SP on leave insn. - asm.lea(:rax, ctx.sp_opnd(C.VALUE.size * -recv_idx)) # Pop receiver and arguments to prepare for side exits - asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], :rax) - else - asm.lea(SP, ctx.sp_opnd(C.VALUE.size * -recv_idx)) - asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) - ctx.sp_offset = recv_idx - end - jit_save_pc(jit, asm, comment: 'save PC to caller CFP') - - sp_offset = ctx.sp_offset + 3 + local_size + (doing_kw_call ? 1 : 0) # callee_sp - local_size.times do |i| - asm.comment('set local variables') if i == 0 - local_index = sp_offset + i - local_size - 3 - asm.mov([SP, C.VALUE.size * local_index], Qnil) - end - - asm.comment('set up EP with managing data') - ep_offset = sp_offset - 1 - # ep[-2]: cref_or_me - asm.mov(:rax, cme.to_i) - asm.mov([SP, C.VALUE.size * (ep_offset - 2)], :rax) - # ep[-1]: block handler or prev env ptr (specval) - if prev_ep - asm.mov(:rax, prev_ep.to_i | 1) # tagged prev ep - asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax) - elsif block_handler == :captured - # Set captured->ep, saving captured in :rcx for captured->self - ep_reg = :rcx - jit_get_lep(jit, asm, reg: ep_reg) - asm.mov(:rcx, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler - asm.and(:rcx, ~0x3) # captured - asm.mov(:rax, [:rcx, C.VALUE.size]) # captured->ep - asm.or(:rax, 0x1) # GC_GUARDED_PTR - asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax) - elsif block_handler == C::VM_BLOCK_HANDLER_NONE - asm.mov([SP, C.VALUE.size * (ep_offset - 1)], C::VM_BLOCK_HANDLER_NONE) - elsif block_handler == C.rb_block_param_proxy - # vm_caller_setup_arg_block: block_code == rb_block_param_proxy - jit_get_lep(jit, asm, reg: :rax) # VM_CF_BLOCK_HANDLER: VM_CF_LEP - asm.mov(:rax, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # VM_CF_BLOCK_HANDLER: VM_ENV_BLOCK_HANDLER - asm.mov([CFP, C.rb_control_frame_t.offsetof(:block_code)], :rax) # reg_cfp->block_code = handler - asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax) # return handler; - else # assume blockiseq - asm.mov(:rax, block_handler) - asm.mov([CFP, C.rb_control_frame_t.offsetof(:block_code)], :rax) - asm.lea(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) # VM_CFP_TO_CAPTURED_BLOCK - asm.or(:rax, 1) # VM_BH_FROM_ISEQ_BLOCK - asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax) - end - # ep[-0]: ENV_FLAGS - asm.mov([SP, C.VALUE.size * (ep_offset - 0)], frame_type) - - asm.comment('set up new frame') - cfp_offset = -C.rb_control_frame_t.size # callee CFP - # For ISEQ, JIT code will set it as needed. However, C func needs 0 there for svar frame detection. - if iseq.nil? - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:pc)], 0) - end - asm.mov(:rax, iseq.to_i) - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:iseq)], :rax) - if block_handler == :captured - asm.mov(:rax, [:rcx]) # captured->self - else - self_index = ctx.sp_offset - (1 + argc) # blockarg has been popped - asm.mov(:rax, [SP, C.VALUE.size * self_index]) - end - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:self)], :rax) - asm.lea(:rax, [SP, C.VALUE.size * ep_offset]) - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:ep)], :rax) - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:block_code)], 0) - # Update SP register only for ISEQ calls. SP-relative operations should be done above this. - sp_reg = iseq ? SP : :rax - asm.lea(sp_reg, [SP, C.VALUE.size * sp_offset]) - asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:sp)], sp_reg) - - # cfp->jit_return is used only for ISEQs - if iseq - # The callee might change locals through Kernel#binding and other means. - ctx.clear_local_types - - # Stub cfp->jit_return - return_ctx = ctx.dup - return_ctx.stack_pop(argc + ((block_handler == :captured) ? 0 : 1)) # Pop args and receiver. blockarg has been popped - return_ctx.stack_push(Type::Unknown) # push callee's return value - return_ctx.sp_offset = 1 # SP is in the position after popping a receiver and arguments - return_ctx.chain_depth = 0 - branch_stub = BranchStub.new( - iseq: jit.iseq, - shape: Default, - target0: BranchTarget.new(ctx: return_ctx, pc: jit.pc + jit.insn.len * C.VALUE.size), - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(return_ctx, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.compile = compile_jit_return(branch_stub, cfp_offset:) - branch_stub.compile.call(asm) - end - - asm.comment('switch to callee CFP') - # Update CFP register only for ISEQ calls - cfp_reg = iseq ? CFP : :rax - asm.lea(cfp_reg, [CFP, cfp_offset]) - asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], cfp_reg) - end - - def compile_jit_return(branch_stub, cfp_offset:) # Proc escapes arguments in memory - proc do |branch_asm| - branch_asm.comment('set jit_return to callee CFP') - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.mov(:rax, branch_stub.target0.address) - branch_asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:jit_return)], :rax) - end - end - end - end - - # CALLER_SETUP_ARG: Return CantCompile if not supported - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def jit_caller_setup_arg(jit, ctx, asm, flags) - if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:send_args_splat_kw_splat) - return CantCompile - elsif flags & C::VM_CALL_ARGS_SPLAT != 0 - # splat is not supported in this path - asm.incr_counter(:send_args_splat) - return CantCompile - elsif flags & C::VM_CALL_KW_SPLAT != 0 - asm.incr_counter(:send_args_kw_splat) - return CantCompile - elsif flags & C::VM_CALL_KWARG != 0 - asm.incr_counter(:send_kwarg) - return CantCompile - end - end - - # Pushes arguments from an array to the stack. Differs from push splat because - # the array can have items left over. - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def move_rest_args_to_stack(array, num_args, jit, ctx, asm) - side_exit = side_exit(jit, ctx) - - asm.comment('move_rest_args_to_stack') - - # array is :rax - array_len_opnd = :rcx - jit_array_len(asm, array, array_len_opnd) - - asm.comment('Side exit if length is less than required') - asm.cmp(array_len_opnd, num_args) - asm.jl(counted_exit(side_exit, :send_iseq_has_rest_and_splat_not_equal)) - - asm.comment('Push arguments from array') - - # Load the address of the embedded array - # (struct RArray *)(obj)->as.ary - array_reg = array - - # Conditionally load the address of the heap array - # (struct RArray *)(obj)->as.heap.ptr - flags_opnd = [array_reg, C.RBasic.offsetof(:flags)] - asm.test(flags_opnd, C::RARRAY_EMBED_FLAG) - heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)] - # Load the address of the embedded array - # (struct RArray *)(obj)->as.ary - ary_opnd = :rdx # NOTE: array :rax is used after move_rest_args_to_stack too - asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)]) - asm.mov(ary_opnd, heap_ptr_opnd) - asm.cmovnz(ary_opnd, :rcx) - - num_args.times do |i| - top = ctx.stack_push(Type::Unknown) - asm.mov(:rcx, [ary_opnd, i * C.VALUE.size]) - asm.mov(top, :rcx) - end - end - - # vm_caller_setup_arg_splat (+ CALLER_SETUP_ARG): - # Pushes arguments from an array to the stack that are passed with a splat (i.e. *args). - # It optimistically compiles to a static size that is the exact number of arguments needed for the function. - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def push_splat_args(required_args, jit, ctx, asm) - side_exit = side_exit(jit, ctx) - - asm.comment('push_splat_args') - - array_opnd = ctx.stack_opnd(0) - array_stack_opnd = StackOpnd[0] - array_reg = :rax - asm.mov(array_reg, array_opnd) - - guard_object_is_array(jit, ctx, asm, array_reg, :rcx, array_stack_opnd, :send_args_splat_not_array) - - array_len_opnd = :rcx - jit_array_len(asm, array_reg, array_len_opnd) - - asm.comment('Side exit if length is not equal to remaining args') - asm.cmp(array_len_opnd, required_args) - asm.jne(counted_exit(side_exit, :send_args_splat_length_not_equal)) - - asm.comment('Check last argument is not ruby2keyword hash') - - ary_opnd = :rcx - jit_array_ptr(asm, array_reg, ary_opnd) # clobbers array_reg - - last_array_value = :rax - asm.mov(last_array_value, [ary_opnd, (required_args - 1) * C.VALUE.size]) - - ruby2_exit = counted_exit(side_exit, :send_args_splat_ruby2_hash); - guard_object_is_not_ruby2_keyword_hash(asm, last_array_value, :rcx, ruby2_exit) # clobbers :rax - - asm.comment('Push arguments from array') - array_opnd = ctx.stack_pop(1) - - if required_args > 0 - # Load the address of the embedded array - # (struct RArray *)(obj)->as.ary - array_reg = :rax - asm.mov(array_reg, array_opnd) - - # Conditionally load the address of the heap array - # (struct RArray *)(obj)->as.heap.ptr - flags_opnd = [array_reg, C.RBasic.offsetof(:flags)] - asm.test(flags_opnd, C::RARRAY_EMBED_FLAG) - heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)] - # Load the address of the embedded array - # (struct RArray *)(obj)->as.ary - asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)]) - asm.mov(:rax, heap_ptr_opnd) - asm.cmovnz(:rax, :rcx) - ary_opnd = :rax - - (0...required_args).each do |i| - top = ctx.stack_push(Type::Unknown) - asm.mov(:rcx, [ary_opnd, i * C.VALUE.size]) - asm.mov(top, :rcx) - end - - asm.comment('end push_each') - end - end - - # Generate RARRAY_LEN. For array_opnd, use Opnd::Reg to reduce memory access, - # and use Opnd::Mem to save registers. - def jit_array_len(asm, array_reg, len_reg) - asm.comment('get array length for embedded or heap') - - # Pull out the embed flag to check if it's an embedded array. - asm.mov(len_reg, [array_reg, C.RBasic.offsetof(:flags)]) - - # Get the length of the array - asm.and(len_reg, C::RARRAY_EMBED_LEN_MASK) - asm.sar(len_reg, C::RARRAY_EMBED_LEN_SHIFT) - - # Conditionally move the length of the heap array - asm.test([array_reg, C.RBasic.offsetof(:flags)], C::RARRAY_EMBED_FLAG) - - # Select the array length value - asm.cmovz(len_reg, [array_reg, C.RArray.offsetof(:as, :heap, :len)]) - end - - # Generate RARRAY_CONST_PTR (part of RARRAY_AREF) - def jit_array_ptr(asm, array_reg, ary_opnd) # clobbers array_reg - asm.comment('get array pointer for embedded or heap') - - flags_opnd = [array_reg, C.RBasic.offsetof(:flags)] - asm.test(flags_opnd, C::RARRAY_EMBED_FLAG) - # Load the address of the embedded array - # (struct RArray *)(obj)->as.ary - asm.mov(ary_opnd, [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]) - asm.lea(array_reg, [array_reg, C.RArray.offsetof(:as, :ary)]) # clobbers array_reg - asm.cmovnz(ary_opnd, array_reg) - end - - def assert(cond) - assert_equal(cond, true) - end - - def assert_equal(left, right) - if left != right - raise "'#{left.inspect}' was not '#{right.inspect}'" - end - end - - def fixnum?(obj) - (C.to_value(obj) & C::RUBY_FIXNUM_FLAG) == C::RUBY_FIXNUM_FLAG - end - - def flonum?(obj) - (C.to_value(obj) & C::RUBY_FLONUM_MASK) == C::RUBY_FLONUM_FLAG - end - - def symbol?(obj) - static_symbol?(obj) || dynamic_symbol?(obj) - end - - def static_symbol?(obj) - (C.to_value(obj) & 0xff) == C::RUBY_SYMBOL_FLAG - end - - def dynamic_symbol?(obj) - return false if C::SPECIAL_CONST_P(obj) - C.RB_TYPE_P(obj, C::RUBY_T_SYMBOL) - end - - def shape_too_complex?(obj) - C.rb_shape_get_shape_id(obj) == C::OBJ_TOO_COMPLEX_SHAPE_ID - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - # @param asm [RubyVM::RJIT::Assembler] - def defer_compilation(jit, ctx, asm) - # Make a stub to compile the current insn - if ctx.chain_depth != 0 - raise "double defer!" - end - ctx.chain_depth += 1 - jit_direct_jump(jit.iseq, jit.pc, ctx, asm, comment: 'defer_compilation') - end - - def jit_direct_jump(iseq, pc, ctx, asm, comment: 'jit_direct_jump') - branch_stub = BranchStub.new( - iseq:, - shape: Default, - target0: BranchTarget.new(ctx:, pc:), - ) - branch_stub.target0.address = Assembler.new.then do |ocb_asm| - @exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true) - @ocb.write(ocb_asm) - end - branch_stub.compile = compile_jit_direct_jump(branch_stub, comment:) - branch_stub.compile.call(asm) - end - - def compile_jit_direct_jump(branch_stub, comment:) # Proc escapes arguments in memory - proc do |branch_asm| - branch_asm.comment(comment) - branch_asm.stub(branch_stub) do - case branch_stub.shape - in Default - branch_asm.jmp(branch_stub.target0.address) - in Next0 - # Just write the block without a jump - end - end - end - end - - # @param jit [RubyVM::RJIT::JITState] - # @param ctx [RubyVM::RJIT::Context] - def side_exit(jit, ctx) - # We use the latest ctx.sp_offset to generate a side exit to tolerate sp_offset changes by jit_save_sp. - # However, we want to simulate an old stack_size when we take a side exit. We do that by adjusting the - # sp_offset because gen_outlined_exit uses ctx.sp_offset to move SP. - ctx = ctx.with_stack_size(jit.stack_size_for_pc) - - jit.side_exit_for_pc[ctx.sp_offset] ||= Assembler.new.then do |asm| - @exit_compiler.compile_side_exit(jit.pc, ctx, asm) - @ocb.write(asm) - end - end - - def counted_exit(side_exit, name) - asm = Assembler.new - asm.incr_counter(name) - asm.jmp(side_exit) - @ocb.write(asm) - end - - def def_iseq_ptr(cme_def) - C.rb_iseq_check(cme_def.body.iseq.iseqptr) - end - - def to_value(obj) - GC_REFS << obj - C.to_value(obj) - end - - def full_cfunc_return - @full_cfunc_return ||= Assembler.new.then do |asm| - @exit_compiler.compile_full_cfunc_return(asm) - @ocb.write(asm) - end - end - - def c_method_tracing_currently_enabled? - C.rb_rjit_global_events & (C::RUBY_EVENT_C_CALL | C::RUBY_EVENT_C_RETURN) != 0 - end - - # Return a builtin function if a given iseq consists of only that builtin function - def builtin_function(iseq) - opt_invokebuiltin_delegate_leave = INSNS.values.find { |i| i.name == :opt_invokebuiltin_delegate_leave } - leave = INSNS.values.find { |i| i.name == :leave } - if iseq.body.iseq_size == opt_invokebuiltin_delegate_leave.len + leave.len && - C.rb_vm_insn_decode(iseq.body.iseq_encoded[0]) == opt_invokebuiltin_delegate_leave.bin && - C.rb_vm_insn_decode(iseq.body.iseq_encoded[opt_invokebuiltin_delegate_leave.len]) == leave.bin - C.rb_builtin_function.new(iseq.body.iseq_encoded[1]) - end - end - - def build_calling(ci:, block_handler:) - CallingInfo.new( - argc: C.vm_ci_argc(ci), - flags: C.vm_ci_flag(ci), - kwarg: C.vm_ci_kwarg(ci), - ci_addr: ci.to_i, - send_shift: 0, - block_handler:, - ) - end - end -end diff --git a/lib/ruby_vm/rjit/invariants.rb b/lib/ruby_vm/rjit/invariants.rb deleted file mode 100644 index 5b061d1994..0000000000 --- a/lib/ruby_vm/rjit/invariants.rb +++ /dev/null @@ -1,155 +0,0 @@ -require 'set' - -module RubyVM::RJIT - class Invariants - class << self - # Called by RubyVM::RJIT::Compiler to lazily initialize this - # @param cb [CodeBlock] - # @param ocb [CodeBlock] - # @param compiler [RubyVM::RJIT::Compiler] - # @param exit_compiler [RubyVM::RJIT::ExitCompiler] - def initialize(cb, ocb, compiler, exit_compiler) - @cb = cb - @ocb = ocb - @compiler = compiler - @exit_compiler = exit_compiler - @bop_blocks = Set.new # TODO: actually invalidate this - @cme_blocks = Hash.new { |h, k| h[k] = Set.new } - @const_blocks = Hash.new { |h, k| h[k] = Set.new } - @patches = {} - - # freeze # workaround a binding.irb issue. TODO: resurrect this - end - - # @param jit [RubyVM::RJIT::JITState] - # @param klass [Integer] - # @param op [Integer] - def assume_bop_not_redefined(jit, klass, op) - return false unless C.BASIC_OP_UNREDEFINED_P(klass, op) - - ensure_block_entry_exit(jit, cause: 'assume_bop_not_redefined') - @bop_blocks << jit.block - true - end - - # @param jit [RubyVM::RJIT::JITState] - def assume_method_lookup_stable(jit, cme) - ensure_block_entry_exit(jit, cause: 'assume_method_lookup_stable') - @cme_blocks[cme.to_i] << jit.block - end - - # @param jit [RubyVM::RJIT::JITState] - def assume_method_basic_definition(jit, klass, mid) - if C.rb_method_basic_definition_p(klass, mid) - cme = C.rb_callable_method_entry(klass, mid) - assume_method_lookup_stable(jit, cme) - true - else - false - end - end - - def assume_stable_constant_names(jit, idlist) - (0..).each do |i| - break if (id = idlist[i]) == 0 - @const_blocks[id] << jit.block - end - end - - # @param asm [RubyVM::RJIT::Assembler] - def record_global_inval_patch(asm, target) - asm.pos_marker do |address| - if @patches.key?(address) - raise 'multiple patches in the same address' - end - @patches[address] = target - end - end - - def on_cme_invalidate(cme) - @cme_blocks.fetch(cme.to_i, []).each do |block| - @cb.with_write_addr(block.start_addr) do - asm = Assembler.new - asm.comment('on_cme_invalidate') - asm.jmp(block.entry_exit) - @cb.write(asm) - end - # TODO: re-generate branches that refer to this block - end - @cme_blocks.delete(cme.to_i) - end - - def on_constant_ic_update(iseq, ic, insn_idx) - # TODO: check multi ractor as well - if ic.entry.ic_cref - # No need to recompile the slowpath - return - end - - pc = iseq.body.iseq_encoded + insn_idx - insn_name = Compiler.decode_insn(pc.*).name - if insn_name != :opt_getconstant_path && insn_name != :trace_opt_getconstant_path - raise 'insn_idx was not at opt_getconstant_path' - end - if ic.to_i != pc[1] - raise 'insn_idx + 1 was not at the updated IC' - end - @compiler.invalidate_blocks(iseq, pc.to_i) - end - - def on_constant_state_changed(id) - @const_blocks.fetch(id, []).each do |block| - @compiler.invalidate_block(block) - end - end - - def on_tracing_invalidate_all - invalidate_all - end - - def on_update_references - # Give up. In order to support GC.compact, you'd have to update ISEQ - # addresses in BranchStub, etc. Ideally, we'd need to update moved - # pointers in JITed code here, but we just invalidate all for now. - invalidate_all - end - - # @param jit [RubyVM::RJIT::JITState] - # @param block [RubyVM::RJIT::Block] - def ensure_block_entry_exit(jit, cause:) - block = jit.block - if block.entry_exit.nil? - block.entry_exit = Assembler.new.then do |asm| - @exit_compiler.compile_entry_exit(block.pc, block.ctx, asm, cause:) - @ocb.write(asm) - end - end - end - - private - - def invalidate_all - # On-Stack Replacement - @patches.each do |address, target| - # TODO: assert patches don't overlap each other - @cb.with_write_addr(address) do - asm = Assembler.new - asm.comment('on_tracing_invalidate_all') - asm.jmp(target) - @cb.write(asm) - end - end - @patches.clear - - C.rjit_for_each_iseq do |iseq| - # Avoid entering past code - iseq.body.jit_entry = 0 - # Avoid reusing past code - iseq.body.rjit_blocks.clear if iseq.body.rjit_blocks - # Compile this again if not converted to trace_* insns - iseq.body.jit_entry_calls = 0 - end - end - end - end -end diff --git a/lib/ruby_vm/rjit/jit_state.rb b/lib/ruby_vm/rjit/jit_state.rb deleted file mode 100644 index 02a713474e..0000000000 --- a/lib/ruby_vm/rjit/jit_state.rb +++ /dev/null @@ -1,65 +0,0 @@ -module RubyVM::RJIT - class JITState < Struct.new( - :iseq, # @param `RubyVM::RJIT::CPointer::Struct_rb_iseq_t` - :pc, # @param [Integer] The JIT target PC - :cfp, # @param `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t` The JIT source CFP (before RJIT is called) - :block, # @param [RubyVM::RJIT::Block] - :stack_size_for_pc, # @param [Integer] - :side_exit_for_pc, # @param [Hash{ Integer => Integer }] { sp_offset => address } - :record_boundary_patch_point, # @param [TrueClass,FalseClass] - ) - def initialize(side_exit_for_pc: {}, record_boundary_patch_point: false, **) = super - - def insn - Compiler.decode_insn(C.VALUE.new(pc).*) - end - - def operand(index, signed: false, ruby: false) - addr = pc + (index + 1) * Fiddle::SIZEOF_VOIDP - value = Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP].unpack(signed ? 'q' : 'Q')[0] - if ruby - value = C.to_ruby(value) - end - value - end - - def at_current_insn? - pc == cfp.pc.to_i - end - - def peek_at_local(n) - local_table_size = iseq.body.local_table_size - offset = -C::VM_ENV_DATA_SIZE - local_table_size + n + 1 - value = (cfp.ep + offset).* - C.to_ruby(value) - end - - def peek_at_stack(depth_from_top) - raise 'not at current insn' unless at_current_insn? - offset = -(1 + depth_from_top) - # rb_rjit_branch_stub_hit updates SP, so you don't need to worry about sp_offset - value = (cfp.sp + offset).* - C.to_ruby(value) - end - - def peek_at_self - C.to_ruby(cfp.self) - end - - def peek_at_block_handler(level) - ep = ep_at_level(cfp, level:) - ep[C::VM_ENV_DATA_INDEX_SPECVAL] - end - - private - - def ep_at_level(cfp, level:) - ep = cfp.ep - level.times do - # VM_ENV_PREV_EP - ep = C.VALUE.new(ep[C::VM_ENV_DATA_INDEX_SPECVAL] & ~0x03) - end - ep - end - end -end diff --git a/lib/ruby_vm/rjit/stats.rb b/lib/ruby_vm/rjit/stats.rb deleted file mode 100644 index 7e353c698e..0000000000 --- a/lib/ruby_vm/rjit/stats.rb +++ /dev/null @@ -1,191 +0,0 @@ -# frozen_string_literal: true -module RubyVM::RJIT - # Return a Hash for \RJIT statistics. \--rjit-stats makes more information available. - def self.runtime_stats - stats = {} - - # Insn exits - INSNS.each_value do |insn| - exits = C.rjit_insn_exits[insn.bin] - if exits > 0 - stats[:"exit_#{insn.name}"] = exits - end - end - - # Runtime stats - C.rb_rjit_runtime_counters.members.each do |member| - stats[member] = C.rb_rjit_counters.public_send(member) - end - stats[:vm_insns_count] = C.rb_vm_insns_count - - # Other stats are calculated here - stats[:side_exit_count] = stats.select { |name, _count| name.start_with?('exit_') }.sum(&:last) - if stats[:vm_insns_count] > 0 - retired_in_rjit = stats[:rjit_insns_count] - stats[:side_exit_count] - stats[:total_insns_count] = retired_in_rjit + stats[:vm_insns_count] - stats[:ratio_in_rjit] = 100.0 * retired_in_rjit / stats[:total_insns_count] - else - stats.delete(:vm_insns_count) - end - - stats - end - - # :nodoc: all - class << self - private - - # --yjit-stats at_exit - def print_stats - stats = runtime_stats - $stderr.puts("***RJIT: Printing RJIT statistics on exit***") - - print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons') - print_counters(stats, prefix: 'invokeblock_', prompt: 'invokeblock exit reasons') - print_counters(stats, prefix: 'invokesuper_', prompt: 'invokesuper exit reasons') - print_counters(stats, prefix: 'getblockpp_', prompt: 'getblockparamproxy exit reasons') - print_counters(stats, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons') - print_counters(stats, prefix: 'setivar_', prompt: 'setinstancevariable exit reasons') - print_counters(stats, prefix: 'optaref_', prompt: 'opt_aref exit reasons') - print_counters(stats, prefix: 'optgetconst_', prompt: 'opt_getconstant_path exit reasons') - print_counters(stats, prefix: 'expandarray_', prompt: 'expandarray exit reasons') - - $stderr.puts "compiled_block_count: #{format_number(13, stats[:compiled_block_count])}" - $stderr.puts "side_exit_count: #{format_number(13, stats[:side_exit_count])}" - $stderr.puts "total_insns_count: #{format_number(13, stats[:total_insns_count])}" if stats.key?(:total_insns_count) - $stderr.puts "vm_insns_count: #{format_number(13, stats[:vm_insns_count])}" if stats.key?(:vm_insns_count) - $stderr.puts "rjit_insns_count: #{format_number(13, stats[:rjit_insns_count])}" - $stderr.puts "ratio_in_rjit: #{format('%12.1f', stats[:ratio_in_rjit])}%" if stats.key?(:ratio_in_rjit) - - print_exit_counts(stats) - end - - def print_counters(stats, prefix:, prompt:) - $stderr.puts("#{prompt}: ") - counters = stats.filter { |key, _| key.start_with?(prefix) } - counters.filter! { |_, value| value != 0 } - counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } - - if counters.empty? - $stderr.puts(" (all relevant counters are zero)") - return - end - - counters = counters.to_a - counters.sort_by! { |(_, counter_value)| counter_value } - longest_name_length = counters.max_by { |(name, _)| name.length }.first.length - total = counters.sum { |(_, counter_value)| counter_value } - - counters.reverse_each do |(name, value)| - percentage = value.fdiv(total) * 100 - $stderr.printf(" %*s %s (%4.1f%%)\n", longest_name_length, name, format_number(10, value), percentage) - end - end - - def print_exit_counts(stats, how_many: 20, padding: 2) - exits = stats.filter_map { |name, count| [name.to_s.delete_prefix('exit_'), count] if name.start_with?('exit_') }.to_h - return if exits.empty? - - top_exits = exits.sort_by { |_name, count| -count }.first(how_many).to_h - total_exits = exits.values.sum - $stderr.puts "Top-#{top_exits.size} most frequent exit ops (#{format("%.1f", 100.0 * top_exits.values.sum / total_exits)}% of exits):" - - name_width = top_exits.map { |name, _count| name.length }.max + padding - count_width = top_exits.map { |_name, count| format_number(10, count).length }.max + padding - top_exits.each do |name, count| - ratio = 100.0 * count / total_exits - $stderr.puts "#{format("%#{name_width}s", name)}: #{format_number(count_width, count)} (#{format('%4.1f', ratio)}%)" - end - end - - # Format large numbers with comma separators for readability - def format_number(pad, number) - integer, decimal = number.to_s.split('.') - d_groups = integer.chars.reverse.each_slice(3) - with_commas = d_groups.map(&:join).join(',').reverse - [with_commas, decimal].compact.join('.').rjust(pad, ' ') - end - - # --yjit-trace-exits at_exit - def dump_trace_exits - filename = "#{Dir.pwd}/rjit_exit_locations.dump" - File.binwrite(filename, Marshal.dump(exit_traces)) - $stderr.puts("RJIT exit locations dumped to:\n#{filename}") - end - - # Convert rb_rjit_raw_samples and rb_rjit_line_samples into a StackProf format. - def exit_traces - results = C.rjit_exit_traces - raw_samples = results[:raw].dup - line_samples = results[:lines].dup - frames = results[:frames].dup - samples_count = 0 - - # Loop through the instructions and set the frame hash with the data. - # We use nonexistent.def for the file name, otherwise insns.def will be displayed - # and that information isn't useful in this context. - RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id| - frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} } - results[:frames][frame_id] = frame_hash - frames[frame_id] = frame_hash - end - - # Loop through the raw_samples and build the hashes for StackProf. - # The loop is based off an example in the StackProf documentation and therefore - # this functionality can only work with that library. - # - # Raw Samples: - # [ length, frame1, frame2, frameN, ..., instruction, count - # - # Line Samples - # [ length, line_1, line_2, line_n, ..., dummy value, count - i = 0 - while i < raw_samples.length - stack_length = raw_samples[i] + 1 - i += 1 # consume the stack length - - prev_frame_id = nil - stack_length.times do |idx| - idx += i - frame_id = raw_samples[idx] - - if prev_frame_id - prev_frame = frames[prev_frame_id] - prev_frame[:edges][frame_id] ||= 0 - prev_frame[:edges][frame_id] += 1 - end - - frame_info = frames[frame_id] - frame_info[:total_samples] += 1 - - frame_info[:lines][line_samples[idx]] ||= [0, 0] - frame_info[:lines][line_samples[idx]][0] += 1 - - prev_frame_id = frame_id - end - - i += stack_length # consume the stack - - top_frame_id = prev_frame_id - top_frame_line = 1 - - sample_count = raw_samples[i] - - frames[top_frame_id][:samples] += sample_count - frames[top_frame_id][:lines] ||= {} - frames[top_frame_id][:lines][top_frame_line] ||= [0, 0] - frames[top_frame_id][:lines][top_frame_line][1] += sample_count - - samples_count += sample_count - i += 1 - end - - results[:samples] = samples_count - # Set missed_samples and gc_samples to 0 as their values - # don't matter to us in this context. - results[:missed_samples] = 0 - results[:gc_samples] = 0 - results - end - end -end diff --git a/lib/ruby_vm/rjit/type.rb b/lib/ruby_vm/rjit/type.rb deleted file mode 100644 index 119692014b..0000000000 --- a/lib/ruby_vm/rjit/type.rb +++ /dev/null @@ -1,221 +0,0 @@ -module RubyVM::RJIT - # Represent the type of a value (local/stack/self) in RJIT - Type = Data.define(:type) do - # Check if the type is an immediate - def imm? - case self - in Type::UnknownImm then true - in Type::Nil then true - in Type::True then true - in Type::False then true - in Type::Fixnum then true - in Type::Flonum then true - in Type::ImmSymbol then true - else false - end - end - - # Returns true when the type is not specific. - def unknown? - case self - in Type::Unknown | Type::UnknownImm | Type::UnknownHeap then true - else false - end - end - - # Returns true when we know the VALUE is a specific handle type, - # such as a static symbol ([Type::ImmSymbol], i.e. true from RB_STATIC_SYM_P()). - # Opposite of [Self::is_unknown]. - def specific? - !self.unknown? - end - - # Check if the type is a heap object - def heap? - case self - in Type::UnknownHeap then true - in Type::TArray then true - in Type::Hash then true - in Type::HeapSymbol then true - in Type::TString then true - in Type::CString then true - in Type::BlockParamProxy then true - else false - end - end - - # Check if it's a T_ARRAY object - def array? - case self - in Type::TArray then true - else false - end - end - - # Check if it's a T_STRING object (both TString and CString are T_STRING) - def string? - case self - in Type::TString then true - in Type::CString then true - else false - end - end - - # Returns the class if it is known, otherwise nil - def known_class - case self - in Type::Nil then C.rb_cNilClass - in Type::True then C.rb_cTrueClass - in Type::False then C.rb_cFalseClass - in Type::Fixnum then C.rb_cInteger - in Type::Flonum then C.rb_cFloat - in Type::ImmSymbol | Type::HeapSymbol then C.rb_cSymbol - in Type::CString then C.rb_cString - else nil - end - end - - # Returns a boolean representing whether the value is truthy if known, otherwise nil - def known_truthy - case self - in Type::Nil then false - in Type::False then false - in Type::UnknownHeap then false - in Type::Unknown | Type::UnknownImm then nil - else true - end - end - - # Returns a boolean representing whether the value is equal to nil if known, otherwise nil - def known_nil - case [self, self.known_truthy] - in Type::Nil, _ then true - in Type::False, _ then false # Qfalse is not nil - in _, true then false # if truthy, can't be nil - in _, _ then nil # otherwise unknown - end - end - - def diff(dst) - # Perfect match, difference is zero - if self == dst - return TypeDiff::Compatible[0] - end - - # Any type can flow into an unknown type - if dst == Type::Unknown - return TypeDiff::Compatible[1] - end - - # A CString is also a TString. - if self == Type::CString && dst == Type::TString - return TypeDiff::Compatible[1] - end - - # Specific heap type into unknown heap type is imperfect but valid - if self.heap? && dst == Type::UnknownHeap - return TypeDiff::Compatible[1] - end - - # Specific immediate type into unknown immediate type is imperfect but valid - if self.imm? && dst == Type::UnknownImm - return TypeDiff::Compatible[1] - end - - # Incompatible types - return TypeDiff::Incompatible - end - - def upgrade(new_type) - assert(new_type.diff(self) != TypeDiff::Incompatible) - new_type - end - - private - - def assert(cond) - unless cond - raise "'#{cond.inspect}' was not true" - end - end - end - - # This returns an appropriate Type based on a known value - class << Type - def from(val) - if C::SPECIAL_CONST_P(val) - if fixnum?(val) - Type::Fixnum - elsif val.nil? - Type::Nil - elsif val == true - Type::True - elsif val == false - Type::False - elsif static_symbol?(val) - Type::ImmSymbol - elsif flonum?(val) - Type::Flonum - else - raise "Illegal value: #{val.inspect}" - end - else - val_class = C.to_value(C.rb_class_of(val)) - if val_class == C.rb_cString && C.rb_obj_frozen_p(val) - return Type::CString - end - if C.to_value(val) == C.rb_block_param_proxy - return Type::BlockParamProxy - end - case C::BUILTIN_TYPE(val) - in C::RUBY_T_ARRAY - Type::TArray - in C::RUBY_T_HASH - Type::Hash - in C::RUBY_T_STRING - Type::TString - else - Type::UnknownHeap - end - end - end - - private - - def fixnum?(obj) - (C.to_value(obj) & C::RUBY_FIXNUM_FLAG) == C::RUBY_FIXNUM_FLAG - end - - def flonum?(obj) - (C.to_value(obj) & C::RUBY_FLONUM_MASK) == C::RUBY_FLONUM_FLAG - end - - def static_symbol?(obj) - (C.to_value(obj) & 0xff) == C::RUBY_SYMBOL_FLAG - end - end - - # List of types - Type::Unknown = Type[:Unknown] - Type::UnknownImm = Type[:UnknownImm] - Type::UnknownHeap = Type[:UnknownHeap] - Type::Nil = Type[:Nil] - Type::True = Type[:True] - Type::False = Type[:False] - Type::Fixnum = Type[:Fixnum] - Type::Flonum = Type[:Flonum] - Type::Hash = Type[:Hash] - Type::ImmSymbol = Type[:ImmSymbol] - Type::HeapSymbol = Type[:HeapSymbol] - - Type::TString = Type[:TString] # An object with the T_STRING flag set, possibly an rb_cString - Type::CString = Type[:CString] # An un-subclassed string of type rb_cString (can have instance vars in some cases) - Type::TArray = Type[:TArray] # An object with the T_ARRAY flag set, possibly an rb_cArray - - Type::BlockParamProxy = Type[:BlockParamProxy] # A special sentinel value indicating the block parameter should be read from - - module TypeDiff - Compatible = Data.define(:diversion) # The smaller, the more compatible. - Incompatible = :Incompatible - end -end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 22dd895e3f..b52dd1b9d3 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,16 +9,15 @@ require "rbconfig" module Gem - VERSION = "3.6.0.dev" + VERSION = "4.1.0.dev" end -# Must be first since it unloads the prelude from 1.9.2 -require_relative "rubygems/compatibility" - require_relative "rubygems/defaults" require_relative "rubygems/deprecate" require_relative "rubygems/errors" require_relative "rubygems/target_rbconfig" +require_relative "rubygems/win_platform" +require_relative "rubygems/util/atomic_file_writer" ## # RubyGems is the Ruby standard for publishing and managing third party @@ -39,7 +38,7 @@ require_relative "rubygems/target_rbconfig" # Further RubyGems documentation can be found at: # # * {RubyGems Guides}[https://guides.rubygems.org] -# * {RubyGems API}[https://www.rubydoc.info/github/rubygems/rubygems] (also available from +# * {RubyGems API}[https://www.rubydoc.info/github/ruby/rubygems] (also available from # <tt>gem server</tt>) # # == RubyGems Plugins @@ -71,7 +70,7 @@ require_relative "rubygems/target_rbconfig" # == Bugs # # You can submit bugs to the -# {RubyGems bug tracker}[https://github.com/rubygems/rubygems/issues] +# {RubyGems bug tracker}[https://github.com/ruby/rubygems/issues] # on GitHub # # == Credits @@ -107,7 +106,7 @@ require_relative "rubygems/target_rbconfig" # # == License # -# See {LICENSE.txt}[rdoc-ref:lib/rubygems/LICENSE.txt] for permissions. +# See {LICENSE.txt}[https://github.com/ruby/rubygems/blob/master/LICENSE.txt] for permissions. # # Thanks! # @@ -116,18 +115,6 @@ require_relative "rubygems/target_rbconfig" module Gem RUBYGEMS_DIR = __dir__ - ## - # An Array of Regexps that match windows Ruby platforms. - - WIN_PATTERNS = [ - /bccwin/i, - /cygwin/i, - /djgpp/i, - /mingw/i, - /mswin/i, - /wince/i, - ].freeze - GEM_DEP_FILES = %w[ gem.deps.rb gems.rb @@ -156,7 +143,12 @@ module Gem specifications/default ].freeze - @@win_platform = nil + ## + # The default value for SOURCE_DATE_EPOCH if not specified. + # We want a date after 1980-01-01, to prevent issues with Zip files. + # This particular timestamp is for 1980-01-02 00:00:00 GMT. + + DEFAULT_SOURCE_DATE_EPOCH = 315_619_200 @configuration = nil @gemdeps = nil @@ -220,7 +212,7 @@ module Gem finish_resolve rs end - def self.finish_resolve(request_set=Gem::RequestSet.new) + def self.finish_resolve(request_set = Gem::RequestSet.new) request_set.import Gem::Specification.unresolved_deps.values request_set.import Gem.loaded_specs.values.map {|s| Gem::Dependency.new(s.name, s.version) } @@ -242,6 +234,16 @@ module Gem find_spec_for_exe(name, exec_name, requirements).bin_file exec_name end + def self.find_and_activate_spec_for_exe(name, exec_name, requirements) + spec = find_spec_for_exe name, exec_name, requirements + Gem::LOADED_SPECS_MUTEX.synchronize do + spec.activate + finish_resolve + end + spec + end + private_class_method :find_and_activate_spec_for_exe + def self.find_spec_for_exe(name, exec_name, requirements) raise ArgumentError, "you must supply exec_name" unless exec_name @@ -267,6 +269,42 @@ module Gem private_class_method :find_spec_for_exe ## + # Find and load the full path to the executable for gem +name+. If the + # +exec_name+ is not given, an exception will be raised, otherwise the + # specified executable's path is returned. +requirements+ allows + # you to specify specific gem versions. + # + # A side effect of this method is that it will activate the gem that + # contains the executable. + # + # This method should *only* be used in bin stub files. + + def self.activate_and_load_bin_path(name, exec_name = nil, *requirements) + spec = find_and_activate_spec_for_exe name, exec_name, requirements + + if spec.name == "bundler" + # Old versions of Bundler need a workaround to support nested `bundle + # exec` invocations by overriding `Gem.activate_bin_path`. However, + # RubyGems now uses this new `Gem.activate_and_load_bin_path` helper in + # binstubs, which is of course not overridden in Bundler since it didn't + # exist at the time. So, include the override here to workaround that. + load ENV["BUNDLE_BIN_PATH"] if ENV["BUNDLE_BIN_PATH"] && spec.version <= Gem::Version.create("2.5.22") + + # Make sure there's no version of Bundler in `$LOAD_PATH` that's different + # from the version we just activated. If that was the case (it happens + # when testing Bundler from ruby/ruby), we would load Bundler extensions + # to RubyGems from the copy in `$LOAD_PATH` but then load the binstub from + # an installed copy, causing those copies to be mixed and yet more + # redefinition warnings. + # + require_path = $LOAD_PATH.resolve_feature_path("bundler").last.delete_suffix("/bundler.rb") + Gem.load_bundler_extensions(spec.version) if spec.full_require_paths.include?(require_path) + end + + load spec.bin_file(exec_name) + end + + ## # Find the full path to the executable for gem +name+. If the +exec_name+ # is not given, an exception will be raised, otherwise the # specified executable's path is returned. +requirements+ allows @@ -278,12 +316,7 @@ module Gem # This method should *only* be used in bin stub files. def self.activate_bin_path(name, exec_name = nil, *requirements) # :nodoc: - spec = find_spec_for_exe name, exec_name, requirements - Gem::LOADED_SPECS_MUTEX.synchronize do - spec.activate - finish_resolve - end - spec.bin_file exec_name + find_and_activate_spec_for_exe(name, exec_name, requirements).bin_file exec_name end ## @@ -296,7 +329,7 @@ module Gem ## # The path where gem executables are to be installed. - def self.bindir(install_dir=Gem.dir) + def self.bindir(install_dir = Gem.dir) return File.join install_dir, "bin" unless install_dir.to_s == Gem.default_dir.to_s Gem.default_bindir @@ -305,7 +338,7 @@ module Gem ## # The path were rubygems plugins are to be installed. - def self.plugindir(install_dir=Gem.dir) + def self.plugindir(install_dir = Gem.dir) File.join install_dir, "plugins" end @@ -489,16 +522,16 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Note that find_files will return all files even if they are from different # versions of the same gem. See also find_latest_files - def self.find_files(glob, check_load_path=true) + def self.find_files(glob, check_load_path = true) files = [] files = find_files_from_load_path glob if check_load_path gem_specifications = @gemdeps ? Gem.loaded_specs.values : Gem::Specification.stubs - files.concat gem_specifications.map {|spec| + files.concat gem_specifications.flat_map {|spec| spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") - }.flatten + } # $LOAD_PATH might contain duplicate entries or reference # the spec dirs directly, so we prune. @@ -509,9 +542,9 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} def self.find_files_from_load_path(glob) # :nodoc: glob_with_suffixes = "#{glob}#{Gem.suffix_pattern}" - $LOAD_PATH.map do |load_path| + $LOAD_PATH.flat_map do |load_path| Gem::Util.glob_files_in_dir(glob_with_suffixes, load_path) - end.flatten.select {|file| File.file? file } + end.select {|file| File.file? file } end ## @@ -526,14 +559,14 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # Unlike find_files, find_latest_files will return only files from the # latest version of a gem. - def self.find_latest_files(glob, check_load_path=true) + def self.find_latest_files(glob, check_load_path = true) files = [] files = find_files_from_load_path glob if check_load_path - files.concat Gem::Specification.latest_specs(true).map {|spec| + files.concat Gem::Specification.latest_specs(true).flat_map {|spec| spec.matches_for_glob("#{glob}#{Gem.suffix_pattern}") - }.flatten + } # $LOAD_PATH might contain duplicate entries or reference # the spec dirs directly, so we prune. @@ -630,6 +663,30 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## + # Load Bundler extensions to RubyGems, making sure to avoid redefinition + # warnings in platform constants + + def self.load_bundler_extensions(version) + return unless version <= Gem::Version.create("2.6.9") + + previous_platforms = {} + + platform_const_list = ["JAVA", "MSWIN", "MSWIN64", "MINGW", "X64_MINGW_LEGACY", "X64_MINGW", "UNIVERSAL_MINGW", "WINDOWS", "X64_LINUX", "X64_LINUX_MUSL"] + + platform_const_list.each do |platform| + previous_platforms[platform] = Gem::Platform.const_get(platform) + Gem::Platform.send(:remove_const, platform) + end + + require "bundler/rubygems_ext" + + platform_const_list.each do |platform| + Gem::Platform.send(:remove_const, platform) if Gem::Platform.const_defined?(platform) + Gem::Platform.const_set(platform, previous_platforms[platform]) + end + end + + ## # The file name and line number of the caller of the caller of this method. # # +depth+ is how many layers up the call stack it should go. @@ -777,14 +834,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## - # Safely write a file in binary mode on all platforms. + # Atomically write a file in binary mode on all platforms. def self.write_binary(path, data) - File.binwrite(path, data) - rescue Errno::ENOSPC - # If we ran out of space but the file exists, it's *guaranteed* to be corrupted. - File.delete(path) if File.exist?(path) - raise + Gem::AtomicFileWriter.open(path) do |file| + file.write(data) + end end ## @@ -815,7 +870,14 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} File.open(path, mode) do |io| begin - io.flock(File::LOCK_EX) + # Try to get a lock without blocking. + # If we do, the file is locked. + # Otherwise, explain why we're waiting and get a lock, but block this time. + if io.flock(File::LOCK_EX | File::LOCK_NB) != 0 + warn "Waiting for another process to let go of lock: #{path}" + io.flock(File::LOCK_EX) + end + io.puts(Process.pid) rescue Errno::ENOSYS, Errno::ENOTSUP end yield io @@ -1016,18 +1078,6 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} end ## - # Is this a windows platform? - - def self.win_platform? - if @@win_platform.nil? - ruby_platform = RbConfig::CONFIG["host_os"] - @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? - end - - @@win_platform - end - - ## # Is this a java platform? def self.java_platform? @@ -1148,8 +1198,7 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} ## # If the SOURCE_DATE_EPOCH environment variable is set, returns it's value. - # Otherwise, returns the time that +Gem.source_date_epoch_string+ was - # first called in the same format as SOURCE_DATE_EPOCH. + # Otherwise, returns DEFAULT_SOURCE_DATE_EPOCH as a string. # # NOTE(@duckinator): The implementation is a tad weird because we want to: # 1. Make builds reproducible by default, by having this function always @@ -1164,15 +1213,12 @@ An Array (#{env.inspect}) was passed in from #{caller[3]} # https://reproducible-builds.org/specs/source-date-epoch/ def self.source_date_epoch_string - # The value used if $SOURCE_DATE_EPOCH is not set. - @default_source_date_epoch ||= Time.now.to_i.to_s - specified_epoch = ENV["SOURCE_DATE_EPOCH"] # If it's empty or just whitespace, treat it like it wasn't set at all. specified_epoch = nil if !specified_epoch.nil? && specified_epoch.strip.empty? - epoch = specified_epoch || @default_source_date_epoch + epoch = specified_epoch || DEFAULT_SOURCE_DATE_EPOCH.to_s epoch.strip end diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index a09e8ed0e1..0ed7fc60bb 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -34,15 +34,6 @@ class Gem::BasicSpecification internal_init end - def self.default_specifications_dir - Gem.default_specifications_dir - end - - class << self - extend Gem::Deprecate - rubygems_deprecate :default_specifications_dir, "Gem.default_specifications_dir" - end - ## # The path to the gem.build_complete file within the extension install # directory. @@ -199,6 +190,9 @@ class Gem::BasicSpecification File.expand_path(File.join(gems_dir, full_name, "data", name)) end + extend Gem::Deprecate + rubygems_deprecate :datadir, :none, "4.1" + ## # Full path of the target library file. # If the file is not in this gem, return nil. @@ -256,6 +250,13 @@ class Gem::BasicSpecification raise NotImplementedError end + def installable_on_platform?(target_platform) # :nodoc: + return true if [Gem::Platform::RUBY, nil, target_platform].include?(platform) + return true if Gem::Platform.new(platform) === target_platform + + false + end + def raw_require_paths # :nodoc: raise NotImplementedError end diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index 4ebbad1c0c..c930c2e19c 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,7 +2,10 @@ module Gem::BundlerVersionFinder def self.bundler_version + return if bundle_config_version == "system" + v = ENV["BUNDLER_VERSION"] + v = nil if v&.empty? v ||= bundle_update_bundler_version return if v == true @@ -64,9 +67,12 @@ module Gem::BundlerVersionFinder return unless gemfile - lockfile = case gemfile - when "gems.rb" then "gems.locked" - else "#{gemfile}.lock" + lockfile = ENV["BUNDLE_LOCKFILE"] + lockfile = nil if lockfile&.empty? + + lockfile ||= case gemfile + when "gems.rb" then "gems.locked" + else "#{gemfile}.lock" end return unless File.file?(lockfile) @@ -74,4 +80,33 @@ module Gem::BundlerVersionFinder File.read(lockfile) end private_class_method :lockfile_contents + + def self.bundle_config_version + config_file = bundler_config_file + return unless config_file && File.file?(config_file) + + contents = File.read(config_file) + contents =~ /^BUNDLE_VERSION:\s*["']?([^"'\s]+)["']?\s*$/ + + $1 + end + private_class_method :bundle_config_version + + def self.bundler_config_file + # see Bundler::Settings#global_config_file and local_config_file + # global + if ENV["BUNDLE_CONFIG"] && !ENV["BUNDLE_CONFIG"].empty? + ENV["BUNDLE_CONFIG"] + elsif ENV["BUNDLE_USER_CONFIG"] && !ENV["BUNDLE_USER_CONFIG"].empty? + ENV["BUNDLE_USER_CONFIG"] + elsif ENV["BUNDLE_USER_HOME"] && !ENV["BUNDLE_USER_HOME"].empty? + ENV["BUNDLE_USER_HOME"] + "config" + elsif Gem.user_home && !Gem.user_home.empty? + Gem.user_home + ".bundle/config" + else + # local + "config" + end + end + private_class_method :bundler_config_file end diff --git a/lib/rubygems/command.rb b/lib/rubygems/command.rb index ec498a8b94..d38363f293 100644 --- a/lib/rubygems/command.rb +++ b/lib/rubygems/command.rb @@ -117,7 +117,7 @@ class Gem::Command # Unhandled arguments (gem names, files, etc.) are left in # <tt>options[:args]</tt>. - def initialize(command, summary=nil, defaults={}) + def initialize(command, summary = nil, defaults = {}) @command = command @summary = summary @program_name = "gem #{command}" @@ -650,9 +650,6 @@ RubyGems is a package manager for Ruby. gem help platforms gem platforms guide gem help <COMMAND> show help on COMMAND (e.g. 'gem help install') - gem server present a web page at - http://localhost:8808/ - with info about installed gems Further information: https://guides.rubygems.org HELP diff --git a/lib/rubygems/command_manager.rb b/lib/rubygems/command_manager.rb index 15834ce4dd..76b2fba835 100644 --- a/lib/rubygems/command_manager.rb +++ b/lib/rubygems/command_manager.rb @@ -58,7 +58,6 @@ class Gem::CommandManager :owner, :pristine, :push, - :query, :rdoc, :rebuild, :search, @@ -118,7 +117,7 @@ class Gem::CommandManager ## # Register the Symbol +command+ as a gem command. - def register_command(command, obj=false) + def register_command(command, obj = false) @commands[command] = obj end @@ -148,7 +147,7 @@ class Gem::CommandManager ## # Run the command specified by +args+. - def run(args, build_args=nil) + def run(args, build_args = nil) process_args(args, build_args) rescue StandardError, Gem::Timeout::Error => ex if ex.respond_to?(:detailed_message) @@ -165,7 +164,7 @@ class Gem::CommandManager terminate_interaction(1) end - def process_args(args, build_args=nil) + def process_args(args, build_args = nil) if args.empty? say Gem::Command::HELP terminate_interaction 1 diff --git a/lib/rubygems/commands/build_command.rb b/lib/rubygems/commands/build_command.rb index 2ec8324141..cfe1f8ec3c 100644 --- a/lib/rubygems/commands/build_command.rb +++ b/lib/rubygems/commands/build_command.rb @@ -25,13 +25,6 @@ class Gem::Commands::BuildCommand < Gem::Command add_option "-o", "--output FILE", "output gem with the given filename" do |value, options| options[:output] = value end - - add_option "-C PATH", "Run as if gem build was started in <PATH> instead of the current working directory." do |value, options| - options[:build_path] = value - end - deprecate_option "-C", - version: "4.0", - extra_msg: "-C is a global flag now. Use `gem -C PATH build GEMSPEC_FILE [options]` instead" end def arguments # :nodoc: diff --git a/lib/rubygems/commands/cert_command.rb b/lib/rubygems/commands/cert_command.rb index 72dcf1dd17..fe03841ddb 100644 --- a/lib/rubygems/commands/cert_command.rb +++ b/lib/rubygems/commands/cert_command.rb @@ -158,7 +158,7 @@ class Gem::Commands::CertCommand < Gem::Command cert = Gem::Security.create_cert_email( email, key, - (Gem::Security::ONE_DAY * expiration_length_days) + Gem::Security::ONE_DAY * expiration_length_days ) Gem::Security.write cert, "gem-public_cert.pem" diff --git a/lib/rubygems/commands/cleanup_command.rb b/lib/rubygems/commands/cleanup_command.rb index bc24aaf753..c89a24eee9 100644 --- a/lib/rubygems/commands/cleanup_command.rb +++ b/lib/rubygems/commands/cleanup_command.rb @@ -113,9 +113,9 @@ If no gems are named all gems in GEM_HOME are cleaned. @candidate_gems = if options[:args].empty? Gem::Specification.to_a else - options[:args].map do |gem_name| + options[:args].flat_map do |gem_name| Gem::Specification.find_all_by_name gem_name - end.flatten + end end end diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb index bd0cbce8ba..d4f9871868 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -102,7 +102,7 @@ prefix or only the files that are requireable. end def files_in_default_gem(spec) - spec.files.map do |file| + spec.files.filter_map do |file| if file.start_with?("#{spec.bindir}/") [RbConfig::CONFIG["bindir"], file.delete_prefix("#{spec.bindir}/")] else @@ -119,7 +119,7 @@ prefix or only the files that are requireable. [resolve.delete_suffix(requirable_part), requirable_part] end - end.compact + end end def gem_contents(name) @@ -189,8 +189,8 @@ prefix or only the files that are requireable. end def specification_directories # :nodoc: - options[:specdirs].map do |i| + options[:specdirs].flat_map do |i| [i, File.join(i, "specifications")] - end.flatten + end end end diff --git a/lib/rubygems/commands/environment_command.rb b/lib/rubygems/commands/environment_command.rb index 8ed0996069..aea8c0d7de 100644 --- a/lib/rubygems/commands/environment_command.rb +++ b/lib/rubygems/commands/environment_command.rb @@ -15,6 +15,7 @@ class Gem::Commands::EnvironmentCommand < Gem::Command version display the gem format version remotesources display the remote gem servers platform display the supported gem platforms + credentials display the path where credentials are stored <omitted> display everything EOF args.gsub(/^\s+/, "") @@ -88,6 +89,8 @@ lib/rubygems/defaults/operating_system.rb Gem.sources.to_a.join("\n") when /^platform/ then Gem.platforms.join(File::PATH_SEPARATOR) + when /^credentials/, /^creds/ then + Gem.configuration.credentials_path when nil then show_environment else @@ -114,6 +117,8 @@ lib/rubygems/defaults/operating_system.rb out << " - USER INSTALLATION DIRECTORY: #{Gem.user_dir}\n" + out << " - CREDENTIALS FILE: #{Gem.configuration.credentials_path}\n" + out << " - RUBYGEMS PREFIX: #{Gem.prefix}\n" unless Gem.prefix.nil? out << " - RUBY EXECUTABLE: #{Gem.ruby}\n" diff --git a/lib/rubygems/commands/exec_command.rb b/lib/rubygems/commands/exec_command.rb index 7985b0fda6..c24ebbf711 100644 --- a/lib/rubygems/commands/exec_command.rb +++ b/lib/rubygems/commands/exec_command.rb @@ -195,7 +195,7 @@ to the same gem path as user-installed gems. argv = ARGV.clone ARGV.replace options[:args] - exe = executable = options[:executable] + executable = options[:executable] contains_executable = Gem.loaded_specs.values.select do |spec| spec.executables.include?(executable) @@ -206,13 +206,22 @@ to the same gem path as user-installed gems. end if contains_executable.empty? - if (spec = Gem.loaded_specs[executable]) && (exe = spec.executable) - contains_executable << spec - else + spec = Gem.loaded_specs[executable] + + if spec.nil? || spec.executables.empty? alert_error "Failed to load executable `#{executable}`," \ " are you sure the gem `#{options[:gem_name]}` contains it?" terminate_interaction 1 end + + if spec.executables.size > 1 + alert_error "Ambiguous which executable from gem `#{executable}` should be run: " \ + "the options are #{spec.executables.sort}, specify one via COMMAND, and use `-g` and `-v` to specify gem and version" + terminate_interaction 1 + end + + contains_executable << spec + executable = spec.executable end if contains_executable.size > 1 @@ -222,8 +231,11 @@ to the same gem path as user-installed gems. terminate_interaction 1 end - load Gem.activate_bin_path(contains_executable.first.name, exe, ">= 0.a") + old_exe = $0 + $0 = executable + load Gem.activate_bin_path(contains_executable.first.name, executable, ">= 0.a") ensure + $0 = old_exe if old_exe ARGV.replace argv end diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb index 2888b6c55a..70d32013ba 100644 --- a/lib/rubygems/commands/install_command.rb +++ b/lib/rubygems/commands/install_command.rb @@ -239,11 +239,7 @@ You can use `i` command instead of `install`. # Loads post-install hooks def load_hooks # :nodoc: - if options[:install_as_default] - require_relative "../install_default_message" - else - require_relative "../install_message" - end + require_relative "../install_message" require_relative "../rdoc" end diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index ec791d310a..942b75fba1 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -88,6 +88,10 @@ If you have made modifications to an installed gem, the pristine command will revert them. All extensions are rebuilt and all bin stubs for the gem are regenerated after checking for modifications. +Rebuilding extensions also refreshes C-extension gems against updated system +libraries (for example after OS or package upgrades) to avoid mismatches like +outdated library version warnings. + If the cached gem cannot be found it will be downloaded. If --no-extensions is provided pristine will not attempt to restore a gem @@ -120,9 +124,9 @@ extensions will be restored. elsif options[:only_missing_extensions] specification_record.select(&:missing_extensions?) else - get_all_gem_names.sort.map do |gem_name| + get_all_gem_names.sort.flat_map do |gem_name| specification_record.find_all_by_name(gem_name, options[:version]).reverse - end.flatten + end end specs = specs.select {|spec| spec.platform == RUBY_ENGINE || Gem::Platform.local === spec.platform || spec.platform == Gem::Platform::RUBY } @@ -137,11 +141,14 @@ extensions will be restored. specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first - unless only_executables_or_plugins? + only_executables = options[:only_executables] + only_plugins = options[:only_plugins] + + unless only_executables || only_plugins # Default gemspecs include changes provided by ruby-core installer that # can't currently be pristined (inclusion of compiled extension targets in # the file list). So stick to resetting executables if it's a default gem. - options[:only_executables] = true if spec.default_gem? + only_executables = true if spec.default_gem? end if options.key? :skip @@ -151,14 +158,14 @@ extensions will be restored. end end - unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins? + unless spec.extensions.empty? || options[:extensions] || only_executables || only_plugins say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist?(gem) || only_executables_or_plugins? + unless File.exist?(gem) || only_executables || only_plugins require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." @@ -194,10 +201,10 @@ extensions will be restored. bin_dir: bin_dir, } - if options[:only_executables] + if only_executables installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_bin - elsif options[:only_plugins] + elsif only_plugins installer = Gem::Installer.for_spec(spec, installer_options) installer.generate_plugins else @@ -208,10 +215,4 @@ extensions will be restored. say "Restored #{spec.full_name_with_location}" end end - - private - - def only_executables_or_plugins? - options[:only_executables] || options[:only_plugins] - end end diff --git a/lib/rubygems/commands/push_command.rb b/lib/rubygems/commands/push_command.rb index 591ddc3a80..d2ce86703b 100644 --- a/lib/rubygems/commands/push_command.rb +++ b/lib/rubygems/commands/push_command.rb @@ -30,7 +30,7 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo end def initialize - super "push", "Push a gem up to the gem server", host: host + super "push", "Push a gem up to the gem server", host: host, attestations: [] @user_defined_host = false @@ -45,6 +45,11 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo @user_defined_host = true end + add_option("--attestation FILE", + "Push with sigstore attestations") do |value, options| + options[:attestations] << value + end + @host = nil end @@ -87,11 +92,20 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo private def send_push_request(name, args) - rubygems_api_request(*args, scope: get_push_scope) do |request| - request.body = Gem.read_binary name - request.add_field "Content-Length", request.body.size - request.add_field "Content-Type", "application/octet-stream" - request.add_field "Authorization", api_key + scope = get_push_scope + rubygems_api_request(*args, scope: scope) do |request| + body = Gem.read_binary name + if options[:attestations].any? + request.set_form([ + ["gem", body, { filename: name, content_type: "application/octet-stream" }], + get_attestations_part, + ], "multipart/form-data") + else + request.body = body + request.add_field "Content-Type", "application/octet-stream" + request.add_field "Content-Length", request.body.size + end + request.add_field "Authorization", api_key end end @@ -107,4 +121,15 @@ The push command will use ~/.gem/credentials to authenticate to a server, but yo def get_push_scope :push_rubygem end + + def get_attestations_part + bundles = "[" + options[:attestations].map do |attestation| + Gem.read_binary(attestation) + end.join(",") + "]" + [ + "attestations", + bundles, + { content_type: "application/json" }, + ] + end end diff --git a/lib/rubygems/commands/query_command.rb b/lib/rubygems/commands/query_command.rb deleted file mode 100644 index 3b527974a3..0000000000 --- a/lib/rubygems/commands/query_command.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -require_relative "../command" -require_relative "../query_utils" -require_relative "../deprecate" - -class Gem::Commands::QueryCommand < Gem::Command - extend Gem::Deprecate - rubygems_deprecate_command - - include Gem::QueryUtils - - alias_method :warning_without_suggested_alternatives, :deprecation_warning - def deprecation_warning - warning_without_suggested_alternatives - - message = "It is recommended that you use `gem search` or `gem list` instead.\n" - alert_warning message unless Gem::Deprecate.skip - end - - def initialize(name = "query", summary = "Query gem information in local or remote repositories") - super name, summary, - domain: :local, details: false, versions: true, - installed: nil, version: Gem::Requirement.default - - add_option("-n", "--name-matches REGEXP", - "Name of gem(s) to query on matches the", - "provided REGEXP") do |value, options| - options[:name] = /#{value}/i - end - - add_query_options - end - - def description # :nodoc: - <<-EOF -The query command is the basis for the list and search commands. - -You should really use the list and search commands instead. This command -is too hard to use. - EOF - end -end diff --git a/lib/rubygems/commands/rdoc_command.rb b/lib/rubygems/commands/rdoc_command.rb index 977c90b8c4..62c4bf8ce9 100644 --- a/lib/rubygems/commands/rdoc_command.rb +++ b/lib/rubygems/commands/rdoc_command.rb @@ -64,9 +64,9 @@ Use --overwrite to force rebuilding of documentation. specs = if options[:all] Gem::Specification.to_a else - get_all_gem_names.map do |name| + get_all_gem_names.flat_map do |name| Gem::Specification.find_by_name name, options[:version] - end.flatten.uniq + end.uniq end if specs.empty? diff --git a/lib/rubygems/commands/rebuild_command.rb b/lib/rubygems/commands/rebuild_command.rb index 77a474ef1d..23b9d7b3ba 100644 --- a/lib/rubygems/commands/rebuild_command.rb +++ b/lib/rubygems/commands/rebuild_command.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "date" require "digest" require "fileutils" require "tmpdir" diff --git a/lib/rubygems/commands/setup_command.rb b/lib/rubygems/commands/setup_command.rb index c367d312de..175599967c 100644 --- a/lib/rubygems/commands/setup_command.rb +++ b/lib/rubygems/commands/setup_command.rb @@ -7,8 +7,8 @@ require_relative "../command" # RubyGems checkout or tarball. class Gem::Commands::SetupCommand < Gem::Command - HISTORY_HEADER = %r{^#\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} - VERSION_MATCHER = %r{^#\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + HISTORY_HEADER = %r{^##\s*[\d.a-zA-Z]+\s*/\s*\d{4}-\d{2}-\d{2}\s*$} + VERSION_MATCHER = %r{^##\s*([\d.a-zA-Z]+)\s*/\s*\d{4}-\d{2}-\d{2}\s*$} ENV_PATHS = %w[/usr/bin/env /bin/env].freeze @@ -107,15 +107,6 @@ class Gem::Commands::SetupCommand < Gem::Command @verbose = nil end - def check_ruby_version - required_version = Gem::Requirement.new ">= 2.6.0" - - unless required_version.satisfied_by? Gem.ruby_version - alert_error "Expected Ruby version #{required_version}, is #{Gem.ruby_version}" - terminate_interaction 1 - end - end - def defaults_str # :nodoc: "--format-executable --document ri --regenerate-binstubs" end @@ -148,8 +139,6 @@ By default, this RubyGems will install gem as: def execute @verbose = Gem.configuration.really_verbose - check_ruby_version - require "fileutils" if Gem.configuration.really_verbose extend FileUtils::Verbose @@ -404,16 +393,20 @@ By default, this RubyGems will install gem as: Dir.chdir("bundler") do built_gem = Gem::Package.build(new_bundler_spec) begin - Gem::Installer.at( + installer = Gem::Installer.at( built_gem, env_shebang: options[:env_shebang], format_executable: options[:format_executable], force: options[:force], - install_as_default: true, bin_dir: bin_dir, install_dir: default_dir, wrappers: true - ).install + ) + # We need to install only executable and default spec files. + # lib/bundler.rb and lib/bundler/* are available under the site_ruby directory. + installer.extract_bin + installer.generate_bin + installer.write_default_spec ensure FileUtils.rm_f built_gem end diff --git a/lib/rubygems/commands/sources_command.rb b/lib/rubygems/commands/sources_command.rb index 976f4a4ea2..7e5c2a2465 100644 --- a/lib/rubygems/commands/sources_command.rb +++ b/lib/rubygems/commands/sources_command.rb @@ -18,6 +18,14 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:add] = value end + add_option "--append SOURCE_URI", "Append source (can be used multiple times)" do |value, options| + options[:append] = value + end + + add_option "-p", "--prepend SOURCE_URI", "Prepend source (can be used multiple times)" do |value, options| + options[:prepend] = value + end + add_option "-l", "--list", "List sources" do |value, options| options[:list] = value end @@ -26,8 +34,7 @@ class Gem::Commands::SourcesCommand < Gem::Command options[:remove] = value end - add_option "-c", "--clear-all", - "Remove all sources (clear the cache)" do |value, options| + add_option "-c", "--clear-all", "Remove all sources (clear the cache)" do |value, options| options[:clear_all] = value end @@ -68,6 +75,60 @@ class Gem::Commands::SourcesCommand < Gem::Command end end + def append_source(source_uri) # :nodoc: + check_rubygems_https source_uri + + source = Gem::Source.new source_uri + + check_typo_squatting(source) + + begin + source.load_specs :released + was_present = Gem.sources.include?(source) + Gem.sources.append source + Gem.configuration.write + + if was_present + say "#{source_uri} moved to end of sources" + else + say "#{source_uri} added to sources" + end + rescue Gem::URI::Error, ArgumentError + say "#{source_uri} is not a URI" + terminate_interaction 1 + rescue Gem::RemoteFetcher::FetchError => e + say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" + terminate_interaction 1 + end + end + + def prepend_source(source_uri) # :nodoc: + check_rubygems_https source_uri + + source = Gem::Source.new source_uri + + check_typo_squatting(source) + + begin + source.load_specs :released + was_present = Gem.sources.include?(source) + Gem.sources.prepend source + Gem.configuration.write + + if was_present + say "#{source_uri} moved to top of sources" + else + say "#{source_uri} added to sources" + end + rescue Gem::URI::Error, ArgumentError + say "#{source_uri} is not a URI" + terminate_interaction 1 + rescue Gem::RemoteFetcher::FetchError => e + say "Error fetching #{Gem::Uri.redact(source.uri)}:\n\t#{e.message}" + terminate_interaction 1 + end + end + def check_typo_squatting(source) if source.typo_squatting?("rubygems.org") question = <<-QUESTION.chomp @@ -128,7 +189,7 @@ yourself to use your own gem server. Without any arguments the sources lists your currently configured sources: $ gem sources - *** CURRENT SOURCES *** + *** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW *** https://rubygems.org @@ -147,33 +208,49 @@ Since all of these sources point to the same set of gems you only need one of them in your list. https://rubygems.org is recommended as it brings the protections of an SSL connection to gem downloads. -To add a source use the --add argument: +To add a private gem source use the --prepend argument to insert it before +the default source. This is usually the best place for private gem sources: - $ gem sources --add https://rubygems.org - https://rubygems.org added to sources + $ gem sources --prepend https://my.private.source + https://my.private.source added to sources RubyGems will check to see if gems can be installed from the source given before it is added. +To add or move a source after all other sources, use --append: + + $ gem sources --append https://rubygems.org + https://rubygems.org moved to end of sources + To remove a source use the --remove argument: - $ gem sources --remove https://rubygems.org/ - https://rubygems.org/ removed from sources + $ gem sources --remove https://my.private.source/ + https://my.private.source/ removed from sources EOF end def list # :nodoc: - say "*** CURRENT SOURCES ***" + if configured_sources + header = "*** CURRENT SOURCES ***" + list = configured_sources + else + header = "*** NO CONFIGURED SOURCES, DEFAULT SOURCES LISTED BELOW ***" + list = Gem.sources + end + + say header say - Gem.sources.each do |src| + list.each do |src| say src end end def list? # :nodoc: !(options[:add] || + options[:prepend] || + options[:append] || options[:clear_all] || options[:remove] || options[:update]) @@ -182,11 +259,13 @@ To remove a source use the --remove argument: def execute clear_all if options[:clear_all] - source_uri = options[:add] - add_source source_uri if source_uri + add_source options[:add] if options[:add] + + prepend_source options[:prepend] if options[:prepend] + + append_source options[:append] if options[:append] - source_uri = options[:remove] - remove_source source_uri if source_uri + remove_source options[:remove] if options[:remove] update if options[:update] @@ -194,13 +273,21 @@ To remove a source use the --remove argument: end def remove_source(source_uri) # :nodoc: - if Gem.sources.include? source_uri - Gem.sources.delete source_uri + source = Gem::Source.new source_uri + + if configured_sources&.include? source + Gem.sources.delete source Gem.configuration.write - say "#{source_uri} removed from sources" + if default_sources.include?(source) && configured_sources.one? + alert_warning "Removing a default source when it is the only source has no effect. Add a different source to #{config_file_name} if you want to stop using it as a source." + else + say "#{source_uri} removed from sources" + end + elsif configured_sources + say "source #{source_uri} cannot be removed because it's not present in #{config_file_name}" else - say "source #{source_uri} not present in cache" + say "source #{source_uri} cannot be removed because there are no configured sources in #{config_file_name}" end end @@ -224,4 +311,21 @@ To remove a source use the --remove argument: say "*** Unable to remove #{desc} source cache ***" end end + + private + + def default_sources + Gem::SourceList.from(Gem.default_sources) + end + + def configured_sources + return @configured_sources if defined?(@configured_sources) + + configuration_sources = Gem.configuration.sources + @configured_sources = Gem::SourceList.from(configuration_sources) if configuration_sources + end + + def config_file_name + Gem.configuration.config_file_name + end end diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb index 8e80d46856..d9740d814a 100644 --- a/lib/rubygems/commands/update_command.rb +++ b/lib/rubygems/commands/update_command.rb @@ -317,16 +317,10 @@ command to remove old versions. # # Oldest version we support downgrading to. This is the version that - # originally ships with the first patch version of each ruby, because we never - # test each ruby against older rubygems, so we can't really guarantee it - # works. Version list can be checked here: https://stdgems.org/rubygems + # originally ships with the oldest supported patch version of ruby. # def oldest_supported_version @oldest_supported_version ||= - if Gem.ruby_version > Gem::Version.new("3.1.a") - Gem::Version.new("3.3.3") - else - Gem::Version.new("3.2.3") - end + Gem::Version.new("3.3.3") end end diff --git a/lib/rubygems/compatibility.rb b/lib/rubygems/compatibility.rb deleted file mode 100644 index 0d9df56f8a..0000000000 --- a/lib/rubygems/compatibility.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -#-- -# This file contains all sorts of little compatibility hacks that we've -# had to introduce over the years. Quarantining them into one file helps -# us know when we can get rid of them. -# -# Ruby 1.9.x has introduced some things that are awkward, and we need to -# support them, so we define some constants to use later. -# -# TODO remove at RubyGems 4 -#++ - -module Gem - # :stopdoc: - - RubyGemsVersion = VERSION - deprecate_constant(:RubyGemsVersion) - - RbConfigPriorities = %w[ - MAJOR - MINOR - TEENY - EXEEXT RUBY_SO_NAME arch bindir datadir libdir ruby_install_name - ruby_version rubylibprefix sitedir sitelibdir vendordir vendorlibdir - rubylibdir - ].freeze - - if defined?(ConfigMap) - RbConfigPriorities.each do |key| - ConfigMap[key.to_sym] = RbConfig::CONFIG[key] - end - else - ## - # Configuration settings from ::RbConfig - ConfigMap = Hash.new do |cm, key| - cm[key] = RbConfig::CONFIG[key.to_s] - end - deprecate_constant(:ConfigMap) - end -end diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb index a2bcb6dfbc..e58a83f6b7 100644 --- a/lib/rubygems/config_file.rb +++ b/lib/rubygems/config_file.rb @@ -345,7 +345,7 @@ if you believe they were disclosed to a third party. require "fileutils" FileUtils.mkdir_p(dirname) - permissions = 0o600 & (~File.umask) + permissions = 0o600 & ~File.umask File.open(credentials_path, "w", permissions) do |f| f.write self.class.dump_with_rubygems_yaml(config) end diff --git a/lib/rubygems/core_ext/kernel_require.rb b/lib/rubygems/core_ext/kernel_require.rb index 073966b696..3a9bdbdc9d 100644 --- a/lib/rubygems/core_ext/kernel_require.rb +++ b/lib/rubygems/core_ext/kernel_require.rb @@ -64,8 +64,11 @@ module Kernel rp end - Kernel.send(:gem, name, Gem::Requirement.default_prerelease) unless - resolved_path + next if resolved_path + + Kernel.send(:gem, name, Gem::Requirement.default_prerelease) + + Gem.load_bundler_extensions(Gem.loaded_specs[name].version) if name == "bundler" next end diff --git a/lib/rubygems/defaults.rb b/lib/rubygems/defaults.rb index 1bd208feb9..90f09fc191 100644 --- a/lib/rubygems/defaults.rb +++ b/lib/rubygems/defaults.rb @@ -13,7 +13,7 @@ module Gem # An Array of the default sources that come with RubyGems def self.default_sources - %w[https://rubygems.org/] + @default_sources ||= %w[https://rubygems.org/] end ## @@ -239,7 +239,7 @@ module Gem # Enables automatic installation into user directory def self.default_user_install # :nodoc: - if !ENV.key?("GEM_HOME") && (File.exist?(Gem.dir) && !File.writable?(Gem.dir)) + if !ENV.key?("GEM_HOME") && File.exist?(Gem.dir) && !File.writable?(Gem.dir) Gem.ui.say "Defaulting to user installation because default installation directory (#{Gem.dir}) is not writable." return true end diff --git a/lib/rubygems/dependency.rb b/lib/rubygems/dependency.rb index d1ec9222af..1e91f493a6 100644 --- a/lib/rubygems/dependency.rb +++ b/lib/rubygems/dependency.rb @@ -217,7 +217,7 @@ class Gem::Dependency # NOTE: Unlike #matches_spec? this method does not return true when the # version is a prerelease version unless this is a prerelease dependency. - def match?(obj, version=nil, allow_prerelease=false) + def match?(obj, version = nil, allow_prerelease = false) if !version name = obj.name version = obj.version diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb index b119dca1cf..6a6dfa5c20 100644 --- a/lib/rubygems/dependency_installer.rb +++ b/lib/rubygems/dependency_installer.rb @@ -7,14 +7,12 @@ require_relative "installer" require_relative "spec_fetcher" require_relative "user_interaction" require_relative "available_set" -require_relative "deprecate" ## # Installs a gem along with all its dependencies from local and remote gems. class Gem::DependencyInstaller include Gem::UserInteraction - extend Gem::Deprecate DEFAULT_OPTIONS = { # :nodoc: env_shebang: false, @@ -28,7 +26,6 @@ class Gem::DependencyInstaller wrappers: true, build_args: nil, build_docs_in_background: false, - install_as_default: false, }.freeze ## @@ -86,8 +83,8 @@ class Gem::DependencyInstaller @user_install = options[:user_install] @wrappers = options[:wrappers] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @build_docs_in_background = options[:build_docs_in_background] - @install_as_default = options[:install_as_default] @dir_mode = options[:dir_mode] @data_mode = options[:data_mode] @prog_mode = options[:prog_mode] @@ -121,78 +118,6 @@ class Gem::DependencyInstaller @domain == :both || @domain == :remote end - ## - # Returns a list of pairs of gemspecs and source_uris that match - # Gem::Dependency +dep+ from both local (Dir.pwd) and remote (Gem.sources) - # sources. Gems are sorted with newer gems preferred over older gems, and - # local gems preferred over remote gems. - - def find_gems_with_sources(dep, best_only=false) # :nodoc: - set = Gem::AvailableSet.new - - if consider_local? - sl = Gem::Source::Local.new - - if spec = sl.find_gem(dep.name) - if dep.matches_spec? spec - set.add spec, sl - end - end - end - - if consider_remote? - begin - # This is pulled from #spec_for_dependency to allow - # us to filter tuples before fetching specs. - tuples, errors = Gem::SpecFetcher.fetcher.search_for_dependency dep - - if best_only && !tuples.empty? - tuples.sort! do |a,b| - if b[0].version == a[0].version - if b[0].platform != Gem::Platform::RUBY - 1 - else - -1 - end - else - b[0].version <=> a[0].version - end - end - tuples = [tuples.first] - end - - specs = [] - tuples.each do |tup, source| - spec = source.fetch_spec(tup) - rescue Gem::RemoteFetcher::FetchError => e - errors << Gem::SourceFetchProblem.new(source, e) - else - specs << [spec, source] - end - - if @errors - @errors += errors - else - @errors = errors - end - - set << specs - rescue Gem::RemoteFetcher::FetchError => e - # FIX if there is a problem talking to the network, we either need to always tell - # the user (no really_verbose) or fail hard, not silently tell them that we just - # couldn't find their requested gem. - verbose do - "Error fetching remote data:\t\t#{e.message}\n" \ - "Falling back to local-only install" - end - @domain = :local - end - end - - set - end - rubygems_deprecate :find_gems_with_sources - def in_background(what) # :nodoc: fork_happened = false if @build_docs_in_background && Process.respond_to?(:fork) @@ -230,6 +155,7 @@ class Gem::DependencyInstaller options = { bin_dir: @bin_dir, build_args: @build_args, + build_jobs: @build_jobs, document: @document, env_shebang: @env_shebang, force: @force, @@ -240,7 +166,6 @@ class Gem::DependencyInstaller user_install: @user_install, wrappers: @wrappers, build_root: @build_root, - install_as_default: @install_as_default, dir_mode: @dir_mode, data_mode: @data_mode, prog_mode: @prog_mode, diff --git a/lib/rubygems/dependency_list.rb b/lib/rubygems/dependency_list.rb index ad5e59e8c1..d50cfe2d54 100644 --- a/lib/rubygems/dependency_list.rb +++ b/lib/rubygems/dependency_list.rb @@ -7,7 +7,6 @@ #++ require_relative "vendored_tsort" -require_relative "deprecate" ## # Gem::DependencyList is used for installing and uninstalling gems in the @@ -140,7 +139,7 @@ class Gem::DependencyList # If removing the gemspec creates breaks a currently ok dependency, then it # is NOT ok to remove the gemspec. - def ok_to_remove?(full_name, check_dev=true) + def ok_to_remove?(full_name, check_dev = true) gem_to_remove = find_name full_name # If the state is inconsistent, at least don't crash diff --git a/lib/rubygems/deprecate.rb b/lib/rubygems/deprecate.rb index 7d24f9cbfc..eb503bb269 100644 --- a/lib/rubygems/deprecate.rb +++ b/lib/rubygems/deprecate.rb @@ -1,75 +1,75 @@ # frozen_string_literal: true -## -# Provides 3 methods for declaring when something is going away. -# -# +deprecate(name, repl, year, month)+: -# Indicate something may be removed on/after a certain date. -# -# +rubygems_deprecate(name, replacement=:none)+: -# Indicate something will be removed in the next major RubyGems version, -# and (optionally) a replacement for it. -# -# +rubygems_deprecate_command+: -# Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be -# removed in the next RubyGems version. -# -# Also provides +skip_during+ for temporarily turning off deprecation warnings. -# This is intended to be used in the test suite, so deprecation warnings -# don't cause test failures if you need to make sure stderr is otherwise empty. -# -# -# Example usage of +deprecate+ and +rubygems_deprecate+: -# -# class Legacy -# def self.some_class_method -# # ... -# end -# -# def some_instance_method -# # ... -# end -# -# def some_old_method -# # ... -# end -# -# extend Gem::Deprecate -# deprecate :some_instance_method, "X.z", 2011, 4 -# rubygems_deprecate :some_old_method, "Modern#some_new_method" -# -# class << self -# extend Gem::Deprecate -# deprecate :some_class_method, :none, 2011, 4 -# end -# end -# -# -# Example usage of +rubygems_deprecate_command+: -# -# class Gem::Commands::QueryCommand < Gem::Command -# extend Gem::Deprecate -# rubygems_deprecate_command -# -# # ... -# end -# -# -# Example usage of +skip_during+: -# -# class TestSomething < Gem::Testcase -# def test_some_thing_with_deprecations -# Gem::Deprecate.skip_during do -# actual_stdout, actual_stderr = capture_output do -# Gem.something_deprecated -# end -# assert_empty actual_stdout -# assert_equal(expected, actual_stderr) -# end -# end -# end - module Gem + ## + # Provides 3 methods for declaring when something is going away. + # + # <tt>deprecate(name, repl, year, month)</tt>: + # Indicate something may be removed on/after a certain date. + # + # <tt>rubygems_deprecate(name, replacement=:none)</tt>: + # Indicate something will be removed in the next major RubyGems version, + # and (optionally) a replacement for it. + # + # +rubygems_deprecate_command+: + # Indicate a RubyGems command (in +lib/rubygems/commands/*.rb+) will be + # removed in the next RubyGems version. + # + # Also provides +skip_during+ for temporarily turning off deprecation warnings. + # This is intended to be used in the test suite, so deprecation warnings + # don't cause test failures if you need to make sure stderr is otherwise empty. + # + # + # Example usage of +deprecate+ and +rubygems_deprecate+: + # + # class Legacy + # def self.some_class_method + # # ... + # end + # + # def some_instance_method + # # ... + # end + # + # def some_old_method + # # ... + # end + # + # extend Gem::Deprecate + # deprecate :some_instance_method, "X.z", 2011, 4 + # rubygems_deprecate :some_old_method, "Modern#some_new_method" + # + # class << self + # extend Gem::Deprecate + # deprecate :some_class_method, :none, 2011, 4 + # end + # end + # + # + # Example usage of +rubygems_deprecate_command+: + # + # class Gem::Commands::QueryCommand < Gem::Command + # extend Gem::Deprecate + # rubygems_deprecate_command + # + # # ... + # end + # + # + # Example usage of +skip_during+: + # + # class TestSomething < Gem::Testcase + # def test_some_thing_with_deprecations + # Gem::Deprecate.skip_during do + # actual_stdout, actual_stderr = capture_output do + # Gem.something_deprecated + # end + # assert_empty actual_stdout + # assert_equal(expected, actual_stderr) + # end + # end + # end + module Deprecate def self.skip # :nodoc: @skip ||= false @@ -126,17 +126,18 @@ module Gem # telling the user of +repl+ (unless +repl+ is :none) and the # Rubygems version that it is planned to go away. - def rubygems_deprecate(name, replacement=:none) + def rubygems_deprecate(name, replacement = :none, version = nil) class_eval do old = "_deprecated_#{name}" alias_method old, name define_method name do |*args, &block| klass = is_a? Module target = klass ? "#{self}." : "#{self.class}#" + version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "NOTE: #{target}#{name} is deprecated", replacement == :none ? " with no replacement" : "; use #{replacement} instead", - ". It will be removed in Rubygems #{Gem::Deprecate.next_rubygems_major_version}", + ". It will be removed in Rubygems #{version}", "\n#{target}#{name} called from #{Gem.location_of_caller.join(":")}", ] warn "#{msg.join}." unless Gem::Deprecate.skip @@ -147,13 +148,14 @@ module Gem end # Deprecation method to deprecate Rubygems commands - def rubygems_deprecate_command(version = Gem::Deprecate.next_rubygems_major_version) + def rubygems_deprecate_command(version = nil) class_eval do define_method "deprecated?" do true end define_method "deprecation_warning" do + version ||= Gem::Deprecate.next_rubygems_major_version msg = [ "#{command} command is deprecated", ". It will be removed in Rubygems #{version}.\n", diff --git a/lib/rubygems/doctor.rb b/lib/rubygems/doctor.rb index 56b7c081eb..4f26260d83 100644 --- a/lib/rubygems/doctor.rb +++ b/lib/rubygems/doctor.rb @@ -113,7 +113,7 @@ class Gem::Doctor next if installed_specs.include? basename next if /^rubygems-\d/.match?(basename) next if sub_directory == "specifications" && basename == "default" - next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ (basename) + next if sub_directory == "plugins" && Gem.plugin_suffix_regexp =~ basename type = File.directory?(child) ? "directory" : "file" diff --git a/lib/rubygems/errors.rb b/lib/rubygems/errors.rb index 57fb3eb120..4bbc5217e0 100644 --- a/lib/rubygems/errors.rb +++ b/lib/rubygems/errors.rb @@ -26,7 +26,7 @@ module Gem # system. Instead of rescuing from this class, make sure to rescue from the # superclass Gem::LoadError to catch all types of load errors. class MissingSpecError < Gem::LoadError - def initialize(name, requirement, extra_message=nil) + def initialize(name, requirement, extra_message = nil) @name = name @requirement = requirement @extra_message = extra_message diff --git a/lib/rubygems/exceptions.rb b/lib/rubygems/exceptions.rb index 793324b875..40485bbadf 100644 --- a/lib/rubygems/exceptions.rb +++ b/lib/rubygems/exceptions.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" require_relative "unknown_command_spell_checker" ## @@ -21,20 +20,11 @@ class Gem::UnknownCommandError < Gem::Exception end def self.attach_correctable - return if defined?(@attached) + return if method_defined?(:corrections) - if defined?(DidYouMean::SPELL_CHECKERS) && defined?(DidYouMean::Correctable) - if DidYouMean.respond_to?(:correct_error) - DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) - else - DidYouMean::SPELL_CHECKERS["Gem::UnknownCommandError"] = - Gem::UnknownCommandSpellChecker - - prepend DidYouMean::Correctable - end + if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error) + DidYouMean.correct_error(Gem::UnknownCommandError, Gem::UnknownCommandSpellChecker) end - - @attached = true end end @@ -110,7 +100,7 @@ class Gem::SpecificGemNotFoundException < Gem::GemNotFoundException # and +version+. Any +errors+ encountered when attempting to find the gem # are also stored. - def initialize(name, version, errors=nil) + def initialize(name, version, errors = nil) super "Could not find a valid gem '#{name}' (#{version}) locally or in a repository" @name = name @@ -261,7 +251,7 @@ class Gem::UnsatisfiableDependencyError < Gem::DependencyError # Creates a new UnsatisfiableDependencyError for the unsatisfiable # Gem::Resolver::DependencyRequest +dep+ - def initialize(dep, platform_mismatch=nil) + def initialize(dep, platform_mismatch = nil) if platform_mismatch && !platform_mismatch.empty? plats = platform_mismatch.map {|x| x.platform.to_s }.sort.uniq super "Unable to resolve dependency: No match for '#{dep}' on this platform. Found: #{plats.join(", ")}" diff --git a/lib/rubygems/ext/builder.rb b/lib/rubygems/ext/builder.rb index 12eb62ef16..62d36bcf48 100644 --- a/lib/rubygems/ext/builder.rb +++ b/lib/rubygems/ext/builder.rb @@ -7,11 +7,13 @@ #++ require_relative "../user_interaction" -require_relative "../shellwords" class Gem::Ext::Builder include Gem::UserInteraction + class NoMakefileError < Gem::InstallError + end + attr_accessor :build_args # :nodoc: def self.class_name @@ -20,19 +22,30 @@ class Gem::Ext::Builder end def self.make(dest_path, results, make_dir = Dir.pwd, sitedir = nil, targets = ["clean", "", "install"], - target_rbconfig: Gem.target_rbconfig) + target_rbconfig: Gem.target_rbconfig, n_jobs: nil) unless File.exist? File.join(make_dir, "Makefile") - raise Gem::InstallError, "Makefile not found" + # No makefile exists, nothing to do. + raise NoMakefileError, "No Makefile found in #{make_dir}" end # try to find make program from Ruby configure arguments first target_rbconfig["configure_args"] =~ /with-make-prog\=(\w+)/ make_program_name = ENV["MAKE"] || ENV["make"] || $1 make_program_name ||= RUBY_PLATFORM.include?("mswin") ? "nmake" : "make" - make_program = Shellwords.split(make_program_name) + make_program = shellsplit(make_program_name) + is_nmake = /\bnmake/i.match?(make_program_name) # The installation of the bundled gems is failed when DESTDIR is empty in mswin platform. - destdir = /\bnmake/i !~ make_program_name || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + destdir = !is_nmake || ENV["DESTDIR"] && ENV["DESTDIR"] != "" ? format("DESTDIR=%s", ENV["DESTDIR"]) : "" + + # nmake doesn't support parallel build + unless is_nmake + have_make_arguments = make_program.size > 1 + + if !have_make_arguments && !ENV["MAKEFLAGS"] && n_jobs + make_program << "-j#{n_jobs}" + end + end env = [destdir] @@ -58,7 +71,7 @@ class Gem::Ext::Builder def self.ruby # Gem.ruby is quoted if it contains whitespace - cmd = Shellwords.split(Gem.ruby) + cmd = shellsplit(Gem.ruby) # This load_path is only needed when running rubygems test without a proper installation. # Prepending it in a normal installation will cause problem with order of $LOAD_PATH. @@ -83,7 +96,7 @@ class Gem::Ext::Builder p(command) end results << "current directory: #{dir}" - results << Shellwords.join(command) + results << shelljoin(command) require "open3" # Set $SOURCE_DATE_EPOCH for the subprocess. @@ -127,18 +140,29 @@ class Gem::Ext::Builder end end + def self.shellsplit(command) + require "shellwords" + + Shellwords.split(command) + end + + def self.shelljoin(command) + require "shellwords" + + Shellwords.join(command) + end + ## # Creates a new extension builder for +spec+. If the +spec+ does not yet # have build arguments, saved, set +build_args+ which is an ARGV-style # array. - def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig) + def initialize(spec, build_args = spec.build_args, target_rbconfig = Gem.target_rbconfig, build_jobs = nil) @spec = spec @build_args = build_args @gem_dir = spec.full_gem_path @target_rbconfig = target_rbconfig - - @ran_rake = false + @build_jobs = build_jobs end ## @@ -151,10 +175,9 @@ class Gem::Ext::Builder when /configure/ then Gem::Ext::ConfigureBuilder when /rakefile/i, /mkrf_conf/i then - @ran_rake = true Gem::Ext::RakeBuilder when /CMakeLists.txt/ then - Gem::Ext::CmakeBuilder + Gem::Ext::CmakeBuilder.new when /Cargo.toml/ then Gem::Ext::CargoBuilder.new else @@ -193,7 +216,7 @@ EOF FileUtils.mkdir_p dest_path results = builder.build(extension, dest_path, - results, @build_args, lib_dir, extension_dir, @target_rbconfig) + results, @build_args, lib_dir, extension_dir, @target_rbconfig, n_jobs: @build_jobs) verbose { results.join("\n") } @@ -224,8 +247,6 @@ EOF FileUtils.rm_f @spec.gem_build_complete_path @spec.extensions.each do |extension| - break if @ran_rake - build_extension extension, dest_path end diff --git a/lib/rubygems/ext/cargo_builder.rb b/lib/rubygems/ext/cargo_builder.rb index 81b28c3c77..42dca3b102 100644 --- a/lib/rubygems/ext/cargo_builder.rb +++ b/lib/rubygems/ext/cargo_builder.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "../shellwords" - # This class is used by rubygems to build Rust extensions. It is a thin-wrapper # over the `cargo rustc` command which takes care of building Rust code in a way # that Ruby can use. @@ -17,7 +15,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder end def build(extension, dest_path, results, args = [], lib_dir = nil, cargo_dir = Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "tempfile" require "fileutils" @@ -159,7 +157,11 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder # We want to use the same linker that Ruby uses, so that the linker flags from # mkmf work properly. def linker_args - cc_flag = Shellwords.split(makefile_config("CC")) + cc_flag = self.class.shellsplit(makefile_config("CC")) + # Avoid to ccache like tool from Rust build + # see https://github.com/ruby/rubygems/pull/8521#issuecomment-2689854359 + # ex. CC="ccache gcc" or CC="sccache clang --any --args" + cc_flag.shift if cc_flag.size >= 2 && !cc_flag[1].start_with?("-") linker = cc_flag.shift link_args = cc_flag.flat_map {|a| ["-C", "link-arg=#{a}"] } @@ -178,7 +180,7 @@ class Gem::Ext::CargoBuilder < Gem::Ext::Builder def libruby_args(dest_dir) libs = makefile_config(ruby_static? ? "LIBRUBYARG_STATIC" : "LIBRUBYARG_SHARED") - raw_libs = Shellwords.split(libs) + raw_libs = self.class.shellsplit(libs) raw_libs.flat_map {|l| ldflag_to_link_modifier(l) } end @@ -252,8 +254,7 @@ EOF def rustc_dynamic_linker_flags(dest_dir, crate_name) split_flags("DLDFLAGS"). - map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) }. - compact. + filter_map {|arg| maybe_resolve_ldflag_variable(arg, dest_dir, crate_name) }. flat_map {|arg| ldflag_to_link_modifier(arg) } end @@ -262,7 +263,7 @@ EOF end def split_flags(var) - Shellwords.split(RbConfig::CONFIG.fetch(var, "")) + self.class.shellsplit(RbConfig::CONFIG.fetch(var, "")) end def ldflag_to_link_modifier(arg) diff --git a/lib/rubygems/ext/cmake_builder.rb b/lib/rubygems/ext/cmake_builder.rb index 34564f668d..e660ed558b 100644 --- a/lib/rubygems/ext/cmake_builder.rb +++ b/lib/rubygems/ext/cmake_builder.rb @@ -1,21 +1,110 @@ # frozen_string_literal: true +# This builder creates extensions defined using CMake. Its is invoked if a Gem's spec file +# sets the `extension` property to a string that contains `CMakeLists.txt`. +# +# In general, CMake projects are built in two steps: +# +# * configure +# * build +# +# The builder follow this convention. First it runs a configuration step and then it runs a build step. +# +# CMake projects can be quite configurable - it is likely you will want to specify options when +# installing a gem. To pass options to CMake specify them after `--` in the gem install command. For example: +# +# gem install <gem_name> -- --preset <preset_name> +# +# Note that options are ONLY sent to the configure step - it is not currently possible to specify +# options for the build step. If this becomes and issue then the CMake builder can be updated to +# support build options. +# +# Useful options to know are: +# +# -G to specify a generator (-G Ninja is recommended) +# -D<CMAKE_VARIABLE> to set a CMake variable (for example -DCMAKE_BUILD_TYPE=Release) +# --preset <preset_name> to use a preset +# +# If the Gem author provides presets, via CMakePresets.json file, you will likely want to use one of them. +# If not, you may wish to specify a generator. Ninja is recommended because it can build projects in parallel +# and thus much faster than building them serially like Make does. + class Gem::Ext::CmakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, cmake_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + attr_accessor :runner, :profile + def initialize + @runner = self.class.method(:run) + @profile = :release + end + + def build(extension, dest_path, results, args = [], lib_dir = nil, cmake_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for CMake extensions. Ignoring" end - unless File.exist?(File.join(cmake_dir, "Makefile")) - require_relative "../command" - cmd = ["cmake", ".", "-DCMAKE_INSTALL_PREFIX=#{dest_path}", *Gem::Command.build_args] + # Figure the build dir + build_dir = File.join(cmake_dir, "build") - run cmd, results, class_name, cmake_dir - end + # Check if the gem defined presets + check_presets(cmake_dir, args, results) + + # Configure + configure(cmake_dir, build_dir, dest_path, args, results) - make dest_path, results, cmake_dir, target_rbconfig: target_rbconfig + # Compile + compile(cmake_dir, build_dir, args, results) results end + + def configure(cmake_dir, build_dir, install_dir, args, results) + cmd = ["cmake", + cmake_dir, + "-B", + build_dir, + "-DCMAKE_RUNTIME_OUTPUT_DIRECTORY=#{install_dir}", # Windows + "-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=#{install_dir}", # Not Windows + *Gem::Command.build_args, + *args] + + runner.call(cmd, results, "cmake_configure", cmake_dir) + end + + def compile(cmake_dir, build_dir, args, results) + cmd = ["cmake", + "--build", + build_dir.to_s, + "--config", + @profile.to_s] + + runner.call(cmd, results, "cmake_compile", cmake_dir) + end + + private + + def check_presets(cmake_dir, args, results) + # Return if the user specified a preset + return unless args.grep(/--preset/i).empty? + + cmd = ["cmake", + "--list-presets"] + + presets = Array.new + begin + runner.call(cmd, presets, "cmake_presets", cmake_dir) + + # Remove the first two lines of the array which is the current_directory and the command + # that was run + presets = presets[2..].join + results << <<~EOS + The gem author provided a list of presets that can be used to build the gem. To use a preset specify it on the command line: + + gem install <gem_name> -- --preset <preset_name> + + #{presets} + EOS + rescue Gem::InstallError + # Do nothing, CMakePresets.json was not included in the Gem + end + end end diff --git a/lib/rubygems/ext/configure_builder.rb b/lib/rubygems/ext/configure_builder.rb index d91b1ec5e8..230b214b3c 100644 --- a/lib/rubygems/ext/configure_builder.rb +++ b/lib/rubygems/ext/configure_builder.rb @@ -7,8 +7,8 @@ #++ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, configure_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, configure_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for configure-based extensions. Ignoring" end @@ -19,7 +19,7 @@ class Gem::Ext::ConfigureBuilder < Gem::Ext::Builder run cmd, results, class_name, configure_dir end - make dest_path, results, configure_dir, target_rbconfig: target_rbconfig + make dest_path, results, configure_dir, target_rbconfig: target_rbconfig, n_jobs: n_jobs results end diff --git a/lib/rubygems/ext/ext_conf_builder.rb b/lib/rubygems/ext/ext_conf_builder.rb index e652a221f8..822454355d 100644 --- a/lib/rubygems/ext/ext_conf_builder.rb +++ b/lib/rubygems/ext/ext_conf_builder.rb @@ -7,8 +7,8 @@ #++ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) require "fileutils" require "tempfile" @@ -41,7 +41,7 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder ENV["DESTDIR"] = nil - make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig + make dest_path, results, extension_dir, tmp_dest_relative, target_rbconfig: target_rbconfig, n_jobs: n_jobs full_tmp_dest = File.join(extension_dir, tmp_dest_relative) @@ -66,6 +66,10 @@ class Gem::Ext::ExtConfBuilder < Gem::Ext::Builder end results + rescue Gem::Ext::Builder::NoMakefileError => error + results << error.message + results << "Skipping make for #{extension} as no Makefile was found." + # We are good, do not re-raise the error. ensure FileUtils.rm_rf tmp_dest if tmp_dest end diff --git a/lib/rubygems/ext/rake_builder.rb b/lib/rubygems/ext/rake_builder.rb index 42393a4a06..d702d7f339 100644 --- a/lib/rubygems/ext/rake_builder.rb +++ b/lib/rubygems/ext/rake_builder.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "../shellwords" - #-- # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others. # All rights reserved. @@ -9,8 +7,8 @@ require_relative "../shellwords" #++ class Gem::Ext::RakeBuilder < Gem::Ext::Builder - def self.build(extension, dest_path, results, args=[], lib_dir=nil, extension_dir=Dir.pwd, - target_rbconfig=Gem.target_rbconfig) + def self.build(extension, dest_path, results, args = [], lib_dir = nil, extension_dir = Dir.pwd, + target_rbconfig = Gem.target_rbconfig, n_jobs: nil) if target_rbconfig.path warn "--target-rbconfig is not yet supported for Rake extensions. Ignoring" end @@ -22,7 +20,7 @@ class Gem::Ext::RakeBuilder < Gem::Ext::Builder rake = ENV["rake"] if rake - rake = Shellwords.split(rake) + rake = shellsplit(rake) else begin rake = ruby << "-rrubygems" << Gem.bin_path("rake", "rake") diff --git a/lib/rubygems/gem_runner.rb b/lib/rubygems/gem_runner.rb index 8335a0ad03..e60cebd0cb 100644 --- a/lib/rubygems/gem_runner.rb +++ b/lib/rubygems/gem_runner.rb @@ -8,7 +8,6 @@ require_relative "../rubygems" require_relative "command_manager" -require_relative "deprecate" ## # Run an instance of the gem program. @@ -29,6 +28,7 @@ class Gem::GemRunner # Run the gem command with the following arguments. def run(args) + validate_encoding args build_args = extract_build_args args do_configuration args @@ -72,6 +72,14 @@ class Gem::GemRunner private + def validate_encoding(args) + invalid_arg = args.find {|arg| !arg.valid_encoding? } + + if invalid_arg + raise Gem::OptionParser::InvalidArgument.new("'#{invalid_arg.scrub}' has invalid encoding") + end + end + def do_configuration(args) Gem.configuration = @config_file_class.new(args) Gem.use_paths Gem.configuration[:gemhome], Gem.configuration[:gempath] diff --git a/lib/rubygems/gemcutter_utilities.rb b/lib/rubygems/gemcutter_utilities.rb index 43ee68f99f..afe7957f43 100644 --- a/lib/rubygems/gemcutter_utilities.rb +++ b/lib/rubygems/gemcutter_utilities.rb @@ -62,6 +62,10 @@ module Gem::GemcutterUtilities options[:otp] || ENV["GEM_HOST_OTP_CODE"] end + def webauthn_enabled? + options[:webauthn] + end + ## # The host to connect to either from the RUBYGEMS_HOST environment variable # or from the user's configuration @@ -249,6 +253,8 @@ module Gem::GemcutterUtilities req["OTP"] = otp if otp block.call(req) end + ensure + options[:otp] = nil if webauthn_enabled? end def fetch_otp(credentials) @@ -257,7 +263,10 @@ module Gem::GemcutterUtilities port = server.addr[1].to_s url_with_port = "#{webauthn_url}?port=#{port}" - say "You have enabled multi-factor authentication. Please visit #{url_with_port} to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." + say "You have enabled multi-factor authentication. Please visit the following URL to authenticate via security device. If you can't verify using WebAuthn but have OTP enabled, you can re-run the gem signin command with the `--otp [your_code]` option." + say "" + say url_with_port + say "" threads = [WebauthnListener.listener_thread(host, server), WebauthnPoller.poll_thread(options, host, webauthn_url, credentials)] otp_thread = wait_for_otp_thread(*threads) @@ -269,6 +278,8 @@ module Gem::GemcutterUtilities terminate_interaction(1) end + options[:webauthn] = true + say "You are verified with a security device. You may close the browser window." otp_thread[:otp] else @@ -308,7 +319,7 @@ module Gem::GemcutterUtilities end def get_scope_params(scope) - scope_params = { index_rubygems: true } + scope_params = { index_rubygems: true, push_rubygem: true } if scope scope_params = { scope => true } diff --git a/lib/rubygems/gemcutter_utilities/webauthn_listener.rb b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb index abf65efe37..3f56a077c9 100644 --- a/lib/rubygems/gemcutter_utilities/webauthn_listener.rb +++ b/lib/rubygems/gemcutter_utilities/webauthn_listener.rb @@ -85,10 +85,17 @@ module Gem::GemcutterUtilities end def parse_otp_from_uri(uri) - require "cgi" + query = uri.query + return unless query && !query.empty? - return if uri.query.nil? - CGI.parse(uri.query).dig("code", 0) + query.split("&") do |param| + key, value = param.split("=", 2) + if value && Gem::URI.decode_www_form_component(key) == "code" + return Gem::URI.decode_www_form_component(value) + end + end + + nil end class SocketResponder diff --git a/lib/rubygems/install_default_message.rb b/lib/rubygems/install_default_message.rb deleted file mode 100644 index 0640eaaf08..0000000000 --- a/lib/rubygems/install_default_message.rb +++ /dev/null @@ -1,13 +0,0 @@ -# frozen_string_literal: true - -require_relative "../rubygems" -require_relative "user_interaction" - -## -# A post-install hook that displays "Successfully installed -# some_gem-1.0 as a default gem" - -Gem.post_install do |installer| - ui = Gem::DefaultUserInteraction.ui - ui.say "Successfully installed #{installer.spec.full_name} as a default gem" -end diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb index 0d0f0dc211..66cb5c049b 100644 --- a/lib/rubygems/install_update_options.rb +++ b/lib/rubygems/install_update_options.rb @@ -31,6 +31,15 @@ module Gem::InstallUpdateOptions options[:bin_dir] = File.expand_path(value) end + add_option(:"Install/Update", "-j", "--build-jobs VALUE", Integer, + "Specify the number of jobs to pass to `make` when installing", + "gems with native extensions.", + "Defaults to the number of processors.", + "This option is ignored on the mswin platform or", + "if the MAKEFLAGS environment variable is set.") do |value, options| + options[:build_jobs] = value + end + add_option(:"Install/Update", "--document [TYPES]", Array, "Generate documentation for installed gems", "List the documentation types you wish to", @@ -158,10 +167,9 @@ module Gem::InstallUpdateOptions options[:without_groups].concat v.map(&:intern) end - add_option(:"Install/Update", "--default", + add_option(:Deprecated, "--default", "Add the gem's full specification to", "specifications/default and extract only its bin") do |v,_o| - options[:install_as_default] = v end add_option(:"Install/Update", "--explain", diff --git a/lib/rubygems/installer.rb b/lib/rubygems/installer.rb index 7f5d913ac4..914e413677 100644 --- a/lib/rubygems/installer.rb +++ b/lib/rubygems/installer.rb @@ -8,7 +8,6 @@ require_relative "installer_uninstaller_utils" require_relative "exceptions" -require_relative "deprecate" require_relative "package" require_relative "ext" require_relative "user_interaction" @@ -27,8 +26,6 @@ require_relative "user_interaction" # file. See Gem.pre_install and Gem.post_install for details. class Gem::Installer - extend Gem::Deprecate - ## # Paths where env(1) might live. Some systems are broken and have it in # /bin @@ -67,23 +64,6 @@ class Gem::Installer attr_reader :package class << self - # - # Changes in rubygems to lazily loading `rubygems/command` (in order to - # lazily load `optparse` as a side effect) affect bundler's custom installer - # which uses `Gem::Command` without requiring it (up until bundler 2.2.29). - # This hook is to compensate for that missing require. - # - # TODO: Remove when rubygems no longer supports running on bundler older - # than 2.2.29. - - def inherited(klass) - if klass.name == "Bundler::RubyGemsGemInstaller" - require "rubygems/command" - end - - super(klass) - end - ## # Overrides the executable format. # @@ -170,7 +150,7 @@ class Gem::Installer # process. If not set, then Gem::Command.build_args is used # :post_install_message:: Print gem post install message if true - def initialize(package, options={}) + def initialize(package, options = {}) require "fileutils" @options = options @@ -228,8 +208,7 @@ class Gem::Installer ruby_executable = true existing = io.read.slice(/ ^\s*( - gem \s | - load \s Gem\.bin_path\( | + Gem\.activate_and_load_bin_path\( | load \s Gem\.activate_bin_path\( ) (['"])(.*?)(\2), @@ -292,11 +271,7 @@ class Gem::Installer run_pre_install_hooks # Set loaded_from to ensure extension_dir is correct - if @options[:install_as_default] - spec.loaded_from = default_spec_file - else - spec.loaded_from = spec_file - end + spec.loaded_from = spec_file # Completely remove any previous gem files FileUtils.rm_rf gem_dir @@ -305,24 +280,17 @@ class Gem::Installer dir_mode = options[:dir_mode] FileUtils.mkdir_p gem_dir, mode: dir_mode && 0o755 - if @options[:install_as_default] - extract_bin - write_default_spec - else - extract_files + extract_files - build_extensions - write_build_info_file - run_post_build_hooks - end + build_extensions + write_build_info_file + run_post_build_hooks generate_bin generate_plugins - unless @options[:install_as_default] - write_spec - write_cache_file - end + write_spec + write_cache_file File.chmod(dir_mode, gem_dir) if dir_mode @@ -411,15 +379,6 @@ class Gem::Installer end ## - # Unpacks the gem into the given directory. - - def unpack(directory) - @gem_dir = directory - extract_files - end - rubygems_deprecate :unpack - - ## # The location of the spec file that is installed. # @@ -427,12 +386,18 @@ class Gem::Installer File.join gem_home, "specifications", "#{spec.full_name}.gemspec" end + def default_spec_dir + dir = File.join(gem_home, "specifications", "default") + FileUtils.mkdir_p dir + dir + end + ## # The location of the default spec file for default gems. # def default_spec_file - File.join gem_home, "specifications", "default", "#{spec.full_name}.gemspec" + File.join default_spec_dir, "#{spec.full_name}.gemspec" end ## @@ -670,6 +635,7 @@ class Gem::Installer @build_root = options[:build_root] @build_args = options[:build_args] + @build_jobs = options[:build_jobs] @gem_home = @install_dir || user_install_dir || Gem.dir @@ -749,54 +715,53 @@ class Gem::Installer def app_script_text(bin_file_name) # NOTE: that the `load` lines cannot be indented, as old RG versions match # against the beginning of the line - <<-TEXT -#{shebang bin_file_name} -# -# This file was generated by RubyGems. -# -# The application '#{spec.name}' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'rubygems' -#{gemdeps_load(spec.name)} -version = "#{Gem::Requirement.default_prerelease}" - -str = ARGV.first -if str - str = str.b[/\\A_(.*)_\\z/, 1] - if str and Gem::Version.correct?(str) - #{explicit_version_requirement(spec.name)} - ARGV.shift - end -end + <<~TEXT + #{shebang bin_file_name} + # + # This file was generated by RubyGems. + # + # The application '#{spec.name}' is installed as part of a gem, and + # this file is here to facilitate running it. + # + + require 'rubygems' + #{gemdeps_load(spec.name)} + version = "#{Gem::Requirement.default_prerelease}" + + str = ARGV.first + if str + str = str.b[/\\A_(.*)_\\z/, 1] + if str and Gem::Version.correct?(str) + #{explicit_version_requirement(spec.name)} + ARGV.shift + end + end -if Gem.respond_to?(:activate_bin_path) -load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) -else -gem #{spec.name.dump}, version -load Gem.bin_path(#{spec.name.dump}, #{bin_file_name.dump}, version) -end -TEXT + if Gem.respond_to?(:activate_and_load_bin_path) + Gem.activate_and_load_bin_path('#{spec.name}', '#{bin_file_name}', version) + else + load Gem.activate_bin_path('#{spec.name}', '#{bin_file_name}', version) + end + TEXT end def gemdeps_load(name) return "" if name == "bundler" - <<-TEXT + <<~TEXT -Gem.use_gemdeps -TEXT + Gem.use_gemdeps + TEXT end def explicit_version_requirement(name) code = "version = str" return code unless name == "bundler" - code += <<-TEXT + code += <<~TEXT - ENV['BUNDLER_VERSION'] = str -TEXT + ENV['BUNDLER_VERSION'] = str + TEXT end ## @@ -811,9 +776,9 @@ TEXT if File.exist?(File.join(bindir, ruby_exe)) # stub & ruby.exe within same folder. Portable - <<-TEXT -@ECHO OFF -@"%~dp0#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{ruby_exe}" "%~dpn0" %* TEXT elsif bindir.downcase.start_with? rb_topdir.downcase # stub within ruby folder, but not standard bin. Portable @@ -821,16 +786,16 @@ TEXT from = Pathname.new bindir to = Pathname.new "#{rb_topdir}/bin" rel = to.relative_path_from from - <<-TEXT -@ECHO OFF -@"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* + <<~TEXT + @ECHO OFF + @"%~dp0#{rel}/#{ruby_exe}" "%~dpn0" %* TEXT else # outside ruby folder, maybe -user-install or bundler. Portable, but ruby # is dependent on PATH - <<-TEXT -@ECHO OFF -@#{ruby_exe} "%~dpn0" %* + <<~TEXT + @ECHO OFF + @#{ruby_exe} "%~dpn0" %* TEXT end end @@ -839,7 +804,7 @@ TEXT # configure scripts and rakefiles or mkrf_conf files. def build_extensions - builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig + builder = Gem::Ext::Builder.new spec, build_args, Gem.target_rbconfig, build_jobs builder.build_extensions end @@ -908,11 +873,7 @@ TEXT ensure_loadable_spec - if options[:install_as_default] - Gem.ensure_default_gem_subdirectories gem_home - else - Gem.ensure_gem_subdirectories gem_home - end + Gem.ensure_gem_subdirectories gem_home return true if @force @@ -953,11 +914,8 @@ TEXT end def ensure_writable_dir(dir) # :nodoc: - begin - Dir.mkdir dir, *[options[:dir_mode] && 0o755].compact - rescue SystemCallError - raise unless File.directory? dir - end + require "fileutils" + FileUtils.mkdir_p dir, mode: options[:dir_mode] && 0o755 raise Gem::FilePermissionError.new(dir) unless File.writable? dir end @@ -984,6 +942,15 @@ TEXT end end + def build_jobs + @build_jobs ||= begin + require "etc" + Etc.nprocessors + 1 + rescue LoadError + 1 + end + end + def rb_config Gem.target_rbconfig end diff --git a/lib/rubygems/local_remote_options.rb b/lib/rubygems/local_remote_options.rb index 51a61213a5..3b88c43149 100644 --- a/lib/rubygems/local_remote_options.rb +++ b/lib/rubygems/local_remote_options.rb @@ -134,13 +134,13 @@ module Gem::LocalRemoteOptions # Is local fetching enabled? def local? - options[:domain] == :local || options[:domain] == :both + [:local, :both].include?(options[:domain]) end ## # Is remote fetching enabled? def remote? - options[:domain] == :remote || options[:domain] == :both + [:remote, :both].include?(options[:domain]) end end diff --git a/lib/rubygems/name_tuple.rb b/lib/rubygems/name_tuple.rb index 3f4a6fcf3d..cbdf4d7ac5 100644 --- a/lib/rubygems/name_tuple.rb +++ b/lib/rubygems/name_tuple.rb @@ -6,7 +6,7 @@ # wrap the data returned from the indexes. class Gem::NameTuple - def initialize(name, version, platform=Gem::Platform::RUBY) + def initialize(name, version, platform = Gem::Platform::RUBY) @name = name @version = version @@ -81,6 +81,12 @@ class Gem::NameTuple [@name, @version, @platform] end + alias_method :deconstruct, :to_a + + def deconstruct_keys(keys) + { name: @name, version: @version, platform: @platform } + end + def inspect # :nodoc: "#<Gem::NameTuple #{@name}, #{@version}, #{@platform}>" end diff --git a/lib/rubygems/package.rb b/lib/rubygems/package.rb index c855423ed7..6b21ff1b95 100644 --- a/lib/rubygems/package.rb +++ b/lib/rubygems/package.rb @@ -7,6 +7,7 @@ # rubocop:enable Style/AsciiComments +require_relative "win_platform" require_relative "security" require_relative "user_interaction" @@ -267,7 +268,7 @@ class Gem::Package tar.add_file_simple file, stat.mode, stat.size do |dst_io| File.open file, "rb" do |src_io| - copy_stream(src_io, dst_io) + copy_stream(src_io, dst_io, stat.size) end end end @@ -436,8 +437,6 @@ EOM symlinks << [full_name, link_target, destination, real_destination] end - FileUtils.rm_rf destination - mkdir = if entry.directory? destination @@ -451,8 +450,14 @@ EOM end if entry.file? - File.open(destination, "wb") {|out| copy_stream(entry, out) } - FileUtils.chmod file_mode(entry.header.mode) & ~File.umask, destination + File.open(destination, "wb") do |out| + copy_stream(tar.io, out, entry.size) + # Flush needs to happen before chmod because there could be data + # in the IO buffer that needs to be written, and that could be + # written after the chmod (on close) which would mess up the perms + out.flush + out.chmod file_mode(entry.header.mode) & ~File.umask + end end verbose destination @@ -514,10 +519,12 @@ EOM destination end - def normalize_path(pathname) - if Gem.win_platform? + if Gem.win_platform? + def normalize_path(pathname) # :nodoc: pathname.downcase - else + end + else + def normalize_path(pathname) # :nodoc: pathname end end @@ -635,6 +642,8 @@ EOM raise Gem::Package::FormatError.new e.message, @gem end + private + ## # Verifies the +checksums+ against the +digests+. This check is not # cryptographically secure. Missing checksums are ignored. @@ -715,12 +724,12 @@ EOM end if RUBY_ENGINE == "truffleruby" - def copy_stream(src, dst) # :nodoc: - dst.write src.read + def copy_stream(src, dst, size) # :nodoc: + dst.write src.read(size) end else - def copy_stream(src, dst) # :nodoc: - IO.copy_stream(src, dst) + def copy_stream(src, dst, size) # :nodoc: + IO.copy_stream(src, dst, size) end end diff --git a/lib/rubygems/package/tar_header.rb b/lib/rubygems/package/tar_header.rb index 0ebcbd789d..dd20d65080 100644 --- a/lib/rubygems/package/tar_header.rb +++ b/lib/rubygems/package/tar_header.rb @@ -56,7 +56,7 @@ class Gem::Package::TarHeader ## # Pack format for a tar header - PACK_FORMAT = "a100" + # name + PACK_FORMAT = ("a100" + # name "a8" + # mode "a8" + # uid "a8" + # gid @@ -71,12 +71,12 @@ class Gem::Package::TarHeader "a32" + # gname "a8" + # devmajor "a8" + # devminor - "a155" # prefix + "a155").freeze # prefix ## # Unpack format for a tar header - UNPACK_FORMAT = "A100" + # name + UNPACK_FORMAT = ("A100" + # name "A8" + # mode "A8" + # uid "A8" + # gid @@ -91,7 +91,7 @@ class Gem::Package::TarHeader "A32" + # gname "A8" + # devmajor "A8" + # devminor - "A155" # prefix + "A155").freeze # prefix attr_reader(*FIELDS) diff --git a/lib/rubygems/package/tar_reader.rb b/lib/rubygems/package/tar_reader.rb index 25f9b2f945..b66a8a62bc 100644 --- a/lib/rubygems/package/tar_reader.rb +++ b/lib/rubygems/package/tar_reader.rb @@ -30,6 +30,8 @@ class Gem::Package::TarReader nil end + attr_reader :io # :nodoc: + ## # Creates a new tar file reader on +io+ which needs to respond to #pos, # #eof?, #read, #getc and #pos= diff --git a/lib/rubygems/package/tar_writer.rb b/lib/rubygems/package/tar_writer.rb index b24bdb63e7..39fed9e2af 100644 --- a/lib/rubygems/package/tar_writer.rb +++ b/lib/rubygems/package/tar_writer.rb @@ -95,10 +95,11 @@ class Gem::Package::TarWriter end ## - # Adds file +name+ with permissions +mode+, and yields an IO for writing the - # file to + # Adds file +name+ with permissions +mode+ and mtime +mtime+ (sets + # Gem.source_date_epoch if not specified), and yields an IO for + # writing the file to - def add_file(name, mode) # :yields: io + def add_file(name, mode, mtime = nil) # :yields: io check_closed name, prefix = split_name name @@ -118,7 +119,7 @@ class Gem::Package::TarWriter header = Gem::Package::TarHeader.new name: name, mode: mode, size: size, prefix: prefix, - mtime: Gem.source_date_epoch + mtime: mtime || Gem.source_date_epoch @io.write header @io.pos = final_pos diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 450c214167..367b00e7e1 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" - ## # Available list of platforms for targeting Gem installations. # @@ -21,15 +19,6 @@ class Gem::Platform end end - def self.match(platform) - match_platforms?(platform, Gem.platforms) - end - - class << self - extend Gem::Deprecate - rubygems_deprecate :match, "Gem::Platform.match_spec? or match_gem?" - end - def self.match_platforms?(platform, platforms) platform = Gem::Platform.new(platform) unless platform.is_a?(Gem::Platform) platforms.any? do |local_platform| @@ -88,56 +77,45 @@ class Gem::Platform when Array then @cpu, @os, @version = arch when String then - arch = arch.split "-" - - if arch.length > 2 && !arch.last.match?(/\d+(\.\d+)?$/) # reassemble x86-linux-{libc} - extra = arch.pop - arch.last << "-#{extra}" - end + cpu, os = arch.sub(/-+$/, "").split("-", 2) - cpu = arch.shift - - @cpu = case cpu - when /i\d86/ then "x86" - else cpu - end - - if arch.length == 2 && arch.last.match?(/^\d+(\.\d+)?$/) # for command-line - @os, @version = arch - return + @cpu = if cpu&.match?(/i\d86/) + "x86" + else + cpu end - os, = arch if os.nil? @cpu = nil os = cpu end # legacy jruby @os, @version = case os - when /aix(\d+)?/ then ["aix", $1] - when /cygwin/ then ["cygwin", nil] - when /darwin(\d+)?/ then ["darwin", $1] - when /^macruby$/ then ["macruby", nil] - when /freebsd(\d+)?/ then ["freebsd", $1] - when /^java$/, /^jruby$/ then ["java", nil] - when /^java([\d.]*)/ then ["java", $1] - when /^dalvik(\d+)?$/ then ["dalvik", $1] - when /^dotnet$/ then ["dotnet", nil] - when /^dotnet([\d.]*)/ then ["dotnet", $1] - when /linux-?(\w+)?/ then ["linux", $1] - when /mingw32/ then ["mingw32", nil] - when /mingw-?(\w+)?/ then ["mingw", $1] - when /(mswin\d+)(\_(\d+))?/ then + when /aix-?(\d+)?/ then ["aix", $1] + when /cygwin/ then ["cygwin", nil] + when /darwin-?(\d+)?/ then ["darwin", $1] + when "macruby" then ["macruby", nil] + when /^macruby-?(\d+(?:\.\d+)*)?/ then ["macruby", $1] + when /freebsd-?(\d+)?/ then ["freebsd", $1] + when "java", "jruby" then ["java", nil] + when /^java-?(\d+(?:\.\d+)*)?/ then ["java", $1] + when /^dalvik-?(\d+)?$/ then ["dalvik", $1] + when /^dotnet$/ then ["dotnet", nil] + when /^dotnet-?(\d+(?:\.\d+)*)?/ then ["dotnet", $1] + when /linux-?(\w+)?/ then ["linux", $1] + when /mingw32/ then ["mingw32", nil] + when /mingw-?(\w+)?/ then ["mingw", $1] + when /(mswin\d+)(?:[_-](\d+))?/ then os = $1 - version = $3 - @cpu = "x86" if @cpu.nil? && os =~ /32$/ + version = $2 + @cpu = "x86" if @cpu.nil? && os.end_with?("32") [os, version] - when /netbsdelf/ then ["netbsdelf", nil] - when /openbsd(\d+\.\d+)?/ then ["openbsd", $1] - when /solaris(\d+\.\d+)?/ then ["solaris", $1] - when /wasi/ then ["wasi", nil] + when /netbsdelf/ then ["netbsdelf", nil] + when /openbsd-?(\d+\.\d+)?/ then ["openbsd", $1] + when /solaris-?(\d+\.\d+)?/ then ["solaris", $1] + when /wasi/ then ["wasi", nil] # test - when /^(\w+_platform)(\d+)?/ then [$1, $2] + when /^(\w+_platform)-?(\d+)?/ then [$1, $2] else ["unknown", nil] end when Gem::Platform then @@ -154,7 +132,38 @@ class Gem::Platform end def to_s - to_a.compact.join "-" + to_a.compact.join(@cpu.nil? ? "" : "-") + end + + ## + # Deconstructs the platform into an array for pattern matching. + # Returns [cpu, os, version]. + # + # Gem::Platform.new("x86_64-linux").deconstruct #=> ["x86_64", "linux", nil] + # + # This enables array pattern matching: + # + # case Gem::Platform.new("arm64-darwin-21") + # in ["arm64", "darwin", version] + # # version => "21" + # end + alias_method :deconstruct, :to_a + + ## + # Deconstructs the platform into a hash for pattern matching. + # Returns a hash with keys +:cpu+, +:os+, and +:version+. + # + # Gem::Platform.new("x86_64-darwin-20").deconstruct_keys(nil) + # #=> { cpu: "x86_64", os: "darwin", version: "20" } + # + # This enables hash pattern matching: + # + # case Gem::Platform.new("x86_64-linux") + # in cpu: "x86_64", os: "linux" + # # Matches Linux on x86_64 + # end + def deconstruct_keys(keys) + { cpu: @cpu, os: @os, version: @version } end ## @@ -266,4 +275,118 @@ class Gem::Platform # This will be replaced with Gem::Platform::local. CURRENT = "current" + + JAVA = Gem::Platform.new("java") # :nodoc: + MSWIN = Gem::Platform.new("mswin32") # :nodoc: + MSWIN64 = Gem::Platform.new("mswin64") # :nodoc: + MINGW = Gem::Platform.new("x86-mingw32") # :nodoc: + X64_MINGW_LEGACY = Gem::Platform.new("x64-mingw32") # :nodoc: + X64_MINGW = Gem::Platform.new("x64-mingw-ucrt") # :nodoc: + UNIVERSAL_MINGW = Gem::Platform.new("universal-mingw") # :nodoc: + WINDOWS = [MSWIN, MSWIN64, UNIVERSAL_MINGW].freeze # :nodoc: + X64_LINUX = Gem::Platform.new("x86_64-linux") # :nodoc: + X64_LINUX_MUSL = Gem::Platform.new("x86_64-linux-musl") # :nodoc: + + GENERICS = [JAVA, *WINDOWS].freeze # :nodoc: + private_constant :GENERICS + + GENERIC_CACHE = GENERICS.each_with_object({}) {|g, h| h[g] = g } # :nodoc: + private_constant :GENERIC_CACHE + + class << self + ## + # Returns the generic platform for the given platform. + + def generic(platform) + return Gem::Platform::RUBY if platform.nil? || platform == Gem::Platform::RUBY + + GENERIC_CACHE[platform] ||= begin + found = GENERICS.find do |match| + platform === match + end + found || Gem::Platform::RUBY + end + end + + ## + # Returns the platform specificity match for the given spec platform and user platform. + + def platform_specificity_match(spec_platform, user_platform) + return -1 if spec_platform == user_platform + return 1_000_000 if spec_platform.nil? || spec_platform == Gem::Platform::RUBY || user_platform == Gem::Platform::RUBY + + os_match(spec_platform, user_platform) + + cpu_match(spec_platform, user_platform) * 10 + + version_match(spec_platform, user_platform) * 100 + end + + ## + # Sorts and filters the best platform match for the given matching specs and platform. + + def sort_and_filter_best_platform_match(matching, platform) + return matching if matching.one? + + exact = matching.select {|spec| spec.platform == platform } + return exact if exact.any? + + sorted_matching = sort_best_platform_match(matching, platform) + exemplary_spec = sorted_matching.first + + sorted_matching.take_while {|spec| same_specificity?(platform, spec, exemplary_spec) && same_deps?(spec, exemplary_spec) } + end + + ## + # Sorts the best platform match for the given matching specs and platform. + + def sort_best_platform_match(matching, platform) + matching.sort_by.with_index do |spec, i| + [ + platform_specificity_match(spec.platform, platform), + i, # for stable sort + ] + end + end + + private + + def same_specificity?(platform, spec, exemplary_spec) + platform_specificity_match(spec.platform, platform) == platform_specificity_match(exemplary_spec.platform, platform) + end + + def same_deps?(spec, exemplary_spec) + spec.required_ruby_version == exemplary_spec.required_ruby_version && + spec.required_rubygems_version == exemplary_spec.required_rubygems_version && + spec.dependencies.sort == exemplary_spec.dependencies.sort + end + + def os_match(spec_platform, user_platform) + if spec_platform.os == user_platform.os + 0 + else + 1 + end + end + + def cpu_match(spec_platform, user_platform) + if spec_platform.cpu == user_platform.cpu + 0 + elsif spec_platform.cpu == "arm" && user_platform.cpu.to_s.start_with?("arm") + 0 + elsif spec_platform.cpu.nil? || spec_platform.cpu == "universal" + 1 + else + 2 + end + end + + def version_match(spec_platform, user_platform) + if spec_platform.version == user_platform.version + 0 + elsif spec_platform.version.nil? + 1 + else + 2 + end + end + end end diff --git a/lib/rubygems/psych_tree.rb b/lib/rubygems/psych_tree.rb index 24857adb9d..8b4c425a33 100644 --- a/lib/rubygems/psych_tree.rb +++ b/lib/rubygems/psych_tree.rb @@ -22,7 +22,7 @@ module Gem def register(target, obj) end - # This is ported over from the yaml_tree in 1.9.3 + # This is ported over from the YAMLTree implementation in Ruby 1.9.3 def format_time(time) if time.utc? time.strftime("%Y-%m-%d %H:%M:%S.%9N Z") diff --git a/lib/rubygems/query_utils.rb b/lib/rubygems/query_utils.rb index ea05969422..9849370b1a 100644 --- a/lib/rubygems/query_utils.rb +++ b/lib/rubygems/query_utils.rb @@ -311,7 +311,7 @@ module Gem::QueryUtils label = "Installed at" specs.each do |s| version = s.version.to_s - default = ", default" if s.default_gem? + default = s.default_gem? ? ", default" : "" entry << "\n" << " #{label} (#{version}#{default}): #{s.base_dir}" label = " " * label.length end diff --git a/lib/rubygems/rdoc.rb b/lib/rubygems/rdoc.rb index 907dcd9431..3524b161b2 100644 --- a/lib/rubygems/rdoc.rb +++ b/lib/rubygems/rdoc.rb @@ -5,9 +5,22 @@ require_relative "../rubygems" begin require "rdoc/rubygems_hook" module Gem - RDoc = ::RDoc::RubygemsHook - end + ## + # Returns whether RDoc defines its own install hooks through a RubyGems + # plugin. This and whatever is guarded by it can be removed once no + # supported Ruby ships with RDoc older than 6.9.0. + + def self.rdoc_hooks_defined_via_plugin? + Gem::Version.new(::RDoc::VERSION) >= Gem::Version.new("6.9.0") + end - Gem.done_installing(&Gem::RDoc.method(:generation_hook)) + if rdoc_hooks_defined_via_plugin? + RDoc = ::RDoc::RubyGemsHook + else + RDoc = ::RDoc::RubygemsHook + + Gem.done_installing(&Gem::RDoc.method(:generation_hook)) + end + end rescue LoadError end diff --git a/lib/rubygems/remote_fetcher.rb b/lib/rubygems/remote_fetcher.rb index 4b5c74e0ea..805f7aaf82 100644 --- a/lib/rubygems/remote_fetcher.rb +++ b/lib/rubygems/remote_fetcher.rb @@ -72,7 +72,7 @@ class Gem::RemoteFetcher # +headers+: A set of additional HTTP headers to be sent to the server when # fetching the gem. - def initialize(proxy=nil, dns=nil, headers={}) + def initialize(proxy = nil, dns = nil, headers = {}) require_relative "core_ext/tcpsocket_init" if Gem.configuration.ipv4_fallback_enabled require_relative "vendored_net_http" require_relative "vendor/uri/lib/uri" @@ -82,6 +82,7 @@ class Gem::RemoteFetcher @proxy = proxy @pools = {} @pool_lock = Thread::Mutex.new + @pool_size = 1 @cert_files = Gem::Request.get_cert_files @headers = headers @@ -245,11 +246,14 @@ class Gem::RemoteFetcher def fetch_path(uri, mtime = nil, head = false) uri = Gem::Uri.new uri - unless uri.scheme - raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" - end + method = { + "http" => "fetch_http", + "https" => "fetch_http", + "s3" => "fetch_s3", + "file" => "fetch_file", + }.fetch(uri.scheme) { raise ArgumentError, "uri scheme is invalid: #{uri.scheme.inspect}" } - data = send "fetch_#{uri.scheme}", uri, mtime, head + data = send method, uri, mtime, head if data && !head && uri.to_s.end_with?(".gz") begin @@ -267,7 +271,7 @@ class Gem::RemoteFetcher def fetch_s3(uri, mtime = nil, head = false) begin - public_uri = s3_uri_signer(uri).sign + public_uri = s3_uri_signer(uri, head ? "HEAD" : "GET").sign rescue Gem::S3URISigner::ConfigurationError, Gem::S3URISigner::InstanceProfileError => e raise FetchError.new(e.message, "s3://#{uri.host}") end @@ -275,8 +279,8 @@ class Gem::RemoteFetcher end # we have our own signing code here to avoid a dependency on the aws-sdk gem - def s3_uri_signer(uri) - Gem::S3URISigner.new(uri) + def s3_uri_signer(uri, method) + Gem::S3URISigner.new(uri, method) end ## @@ -335,7 +339,7 @@ class Gem::RemoteFetcher def pools_for(proxy) @pool_lock.synchronize do - @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files + @pools[proxy] ||= Gem::Request::ConnectionPools.new proxy, @cert_files, @pool_size end end end diff --git a/lib/rubygems/request/connection_pools.rb b/lib/rubygems/request/connection_pools.rb index 6c1b04ab65..01e7e0629a 100644 --- a/lib/rubygems/request/connection_pools.rb +++ b/lib/rubygems/request/connection_pools.rb @@ -7,11 +7,12 @@ class Gem::Request::ConnectionPools # :nodoc: attr_accessor :client end - def initialize(proxy_uri, cert_files) + def initialize(proxy_uri, cert_files, pool_size = 1) @proxy_uri = proxy_uri @cert_files = cert_files @pools = {} @pool_mutex = Thread::Mutex.new + @pool_size = pool_size end def pool_for(uri) @@ -20,9 +21,9 @@ class Gem::Request::ConnectionPools # :nodoc: @pool_mutex.synchronize do @pools[key] ||= if https? uri - Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPSPool.new(http_args, @cert_files, @proxy_uri, @pool_size) else - Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri) + Gem::Request::HTTPPool.new(http_args, @cert_files, @proxy_uri, @pool_size) end end end diff --git a/lib/rubygems/request/http_pool.rb b/lib/rubygems/request/http_pool.rb index 52543de41f..468502ca6b 100644 --- a/lib/rubygems/request/http_pool.rb +++ b/lib/rubygems/request/http_pool.rb @@ -9,12 +9,14 @@ class Gem::Request::HTTPPool # :nodoc: attr_reader :cert_files, :proxy_uri - def initialize(http_args, cert_files, proxy_uri) + def initialize(http_args, cert_files, proxy_uri, pool_size) @http_args = http_args @cert_files = cert_files @proxy_uri = proxy_uri - @queue = Thread::SizedQueue.new 1 - @queue << nil + @pool_size = pool_size + + @queue = Thread::SizedQueue.new @pool_size + setup_queue end def checkout @@ -31,7 +33,8 @@ class Gem::Request::HTTPPool # :nodoc: connection.finish end end - @queue.push(nil) + + setup_queue end private @@ -44,4 +47,8 @@ class Gem::Request::HTTPPool # :nodoc: connection.start connection end + + def setup_queue + @pool_size.times { @queue.push(nil) } + end end diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb index 875df7e019..5a855fdb10 100644 --- a/lib/rubygems/request_set.rb +++ b/lib/rubygems/request_set.rb @@ -181,13 +181,10 @@ class Gem::RequestSet # Install requested gems after they have been downloaded sorted_requests.each do |req| - if req.installed? + if req.installed? && @always_install.none? {|spec| spec == req.spec.spec } req.spec.spec.build_extensions - - if @always_install.none? {|spec| spec == req.spec.spec } - yield req, nil if block_given? - next - end + yield req, nil if block_given? + next end spec = diff --git a/lib/rubygems/request_set/gem_dependency_api.rb b/lib/rubygems/request_set/gem_dependency_api.rb index 4347d22ccb..99d96f928b 100644 --- a/lib/rubygems/request_set/gem_dependency_api.rb +++ b/lib/rubygems/request_set/gem_dependency_api.rb @@ -330,7 +330,7 @@ class Gem::RequestSet::GemDependencyAPI # git: :: # Install this dependency from a git repository: # - # gem 'private_gem', git: git@my.company.example:private_gem.git' + # gem 'private_gem', git: 'git@my.company.example:private_gem.git' # # gist: :: # Install this dependency from the gist ID: diff --git a/lib/rubygems/request_set/lockfile.rb b/lib/rubygems/request_set/lockfile.rb index c446b3ae51..da6dd329bc 100644 --- a/lib/rubygems/request_set/lockfile.rb +++ b/lib/rubygems/request_set/lockfile.rb @@ -38,7 +38,7 @@ class Gem::RequestSet::Lockfile end ## - # Creates a new Lockfile for the given +request_set+ and +gem_deps_file+ + # Creates a new Lockfile for the given Gem::RequestSet and +gem_deps_file+ # location. def self.build(request_set, gem_deps_file, dependencies = nil) diff --git a/lib/rubygems/requirement.rb b/lib/rubygems/requirement.rb index d9796c4208..0d3f98eb0f 100644 --- a/lib/rubygems/requirement.rb +++ b/lib/rubygems/requirement.rb @@ -22,7 +22,7 @@ class Gem::Requirement SOURCE_SET_REQUIREMENT = Struct.new(:for_lockfile).new "!" # :nodoc: - quoted = OPS.keys.map {|k| Regexp.quote k }.join "|" + quoted = Regexp.union(OPS.keys) PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{Gem::Version::VERSION_PATTERN})\\s*".freeze # :nodoc: ## @@ -106,13 +106,15 @@ class Gem::Requirement unless PATTERN =~ obj.to_s raise BadRequirementError, "Illformed requirement [#{obj.inspect}]" end + op = -($1 || "=") + version = -$2 - if $1 == ">=" && $2 == "0" + if op == ">=" && version == "0" DefaultRequirement - elsif $1 == ">=" && $2 == "0.a" + elsif op == ">=" && version == "0.a" DefaultPrereleaseRequirement else - [-($1 || "="), Gem::Version.new($2)] + [op, Gem::Version.new(version)] end end @@ -201,7 +203,8 @@ class Gem::Requirement def marshal_load(array) # :nodoc: @requirements = array[0] - raise TypeError, "wrong @requirements" unless Array === @requirements + raise TypeError, "wrong @requirements" unless Array === @requirements && + @requirements.all? {|r| r.size == 2 && (r.first.is_a?(String) || r[0] = "=") && r.last.is_a?(Gem::Version) } end def yaml_initialize(tag, vals) # :nodoc: @@ -238,7 +241,7 @@ class Gem::Requirement def satisfied_by?(version) raise ArgumentError, "Need a Gem::Version: #{version.inspect}" unless Gem::Version === version - requirements.all? {|op, rv| OPS[op].call version, rv } + requirements.all? {|op, rv| OPS.fetch(op).call version, rv } end alias_method :===, :satisfied_by? diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb index 115c716b6b..bc4fef893e 100644 --- a/lib/rubygems/resolver.rb +++ b/lib/rubygems/resolver.rb @@ -2,7 +2,6 @@ require_relative "dependency" require_relative "exceptions" -require_relative "util/list" ## # Given a set of Gem::Dependency objects as +needed+ and a way to query the @@ -59,7 +58,7 @@ class Gem::Resolver def self.compose_sets(*sets) sets.compact! - sets = sets.map do |set| + sets = sets.flat_map do |set| case set when Gem::Resolver::BestSet then set @@ -68,7 +67,7 @@ class Gem::Resolver else set end - end.flatten + end case sets.length when 0 then @@ -144,7 +143,7 @@ class Gem::Resolver [spec, activation_request] end - def requests(s, act, reqs=[]) # :nodoc: + def requests(s, act, reqs = []) # :nodoc: return reqs if @ignore_dependencies s.fetch_development_dependencies if @development @@ -183,7 +182,7 @@ class Gem::Resolver # Proceed with resolution! Returns an array of ActivationRequest objects. def resolve - Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.map(&:payload).compact + Gem::Molinillo::Resolver.new(self, self).resolve(@needed.map {|d| DependencyRequest.new d, nil }).tsort.filter_map(&:payload) rescue Gem::Molinillo::VersionConflict => e conflict = e.conflicts.values.first raise Gem::DependencyResolutionError, Conflict.new(conflict.requirement_trees.first.first, conflict.existing, conflict.requirement) @@ -241,7 +240,7 @@ class Gem::Resolver sources.each do |source| groups[source]. - sort_by {|spec| [spec.version, spec.platform =~ Gem::Platform.local ? 1 : 0] }. # rubocop:disable Performance/RegexpMatch + sort_by {|spec| [spec.version, -Gem::Platform.platform_specificity_match(spec.platform, Gem::Platform.local)] }. map {|spec| ActivationRequest.new spec, dependency }. each {|activation_request| activation_requests << activation_request } end diff --git a/lib/rubygems/resolver/api_set/gem_parser.rb b/lib/rubygems/resolver/api_set/gem_parser.rb index 643b857107..7dd9a89ebc 100644 --- a/lib/rubygems/resolver/api_set/gem_parser.rb +++ b/lib/rubygems/resolver/api_set/gem_parser.rb @@ -1,15 +1,12 @@ # frozen_string_literal: true class Gem::Resolver::APISet::GemParser - EMPTY_ARRAY = [].freeze - private_constant :EMPTY_ARRAY - def parse(line) version_and_platform, rest = line.split(" ", 2) version, platform = version_and_platform.split("-", 2) dependencies, requirements = rest.split("|", 2).map! {|s| s.split(",") } if rest - dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : EMPTY_ARRAY - requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : EMPTY_ARRAY + dependencies = dependencies ? dependencies.map! {|d| parse_dependency(d) } : [] + requirements = requirements ? requirements.map! {|d| parse_dependency(d) } : [] [version, platform, dependencies, requirements] end diff --git a/lib/rubygems/resolver/best_set.rb b/lib/rubygems/resolver/best_set.rb index e2307f6e02..e647a2c11b 100644 --- a/lib/rubygems/resolver/best_set.rb +++ b/lib/rubygems/resolver/best_set.rb @@ -21,7 +21,7 @@ class Gem::Resolver::BestSet < Gem::Resolver::ComposedSet def pick_sets # :nodoc: @sources.each_source do |source| - @sets << source.dependency_resolver_set + @sets << source.dependency_resolver_set(@prerelease) end end diff --git a/lib/rubygems/resolver/composed_set.rb b/lib/rubygems/resolver/composed_set.rb index 8a714ad447..e67dd41754 100644 --- a/lib/rubygems/resolver/composed_set.rb +++ b/lib/rubygems/resolver/composed_set.rb @@ -44,16 +44,16 @@ class Gem::Resolver::ComposedSet < Gem::Resolver::Set end def errors - @errors + @sets.map(&:errors).flatten + @errors + @sets.flat_map(&:errors) end ## # Finds all specs matching +req+ in all sets. def find_all(req) - @sets.map do |s| + @sets.flat_map do |s| s.find_all req - end.flatten + end end ## diff --git a/lib/rubygems/resolver/conflict.rb b/lib/rubygems/resolver/conflict.rb index 367a36b43d..77c3add4b3 100644 --- a/lib/rubygems/resolver/conflict.rb +++ b/lib/rubygems/resolver/conflict.rb @@ -21,7 +21,7 @@ class Gem::Resolver::Conflict # Creates a new resolver conflict when +dependency+ is in conflict with an # already +activated+ specification. - def initialize(dependency, activated, failed_dep=dependency) + def initialize(dependency, activated, failed_dep = dependency) @dependency = dependency @activated = activated @failed_dep = failed_dep diff --git a/lib/rubygems/resolver/git_set.rb b/lib/rubygems/resolver/git_set.rb index 89342ff80d..2912378fe7 100644 --- a/lib/rubygems/resolver/git_set.rb +++ b/lib/rubygems/resolver/git_set.rb @@ -36,7 +36,6 @@ class Gem::Resolver::GitSet < Gem::Resolver::Set def initialize # :nodoc: super() - @git = ENV["git"] || "git" @need_submodules = {} @repositories = {} @root_dir = Gem.dir diff --git a/lib/rubygems/resolver/index_set.rb b/lib/rubygems/resolver/index_set.rb index 0b4f376452..cddaf8773f 100644 --- a/lib/rubygems/resolver/index_set.rb +++ b/lib/rubygems/resolver/index_set.rb @@ -65,11 +65,11 @@ class Gem::Resolver::IndexSet < Gem::Resolver::Set q.breakable - names = @all.values.map do |tuples| + names = @all.values.flat_map do |tuples| tuples.map do |_, tuple| tuple.full_name end - end.flatten + end q.seplist names do |name| q.text name diff --git a/lib/rubygems/resolver/source_set.rb b/lib/rubygems/resolver/source_set.rb index 296cf41078..074b473edc 100644 --- a/lib/rubygems/resolver/source_set.rb +++ b/lib/rubygems/resolver/source_set.rb @@ -42,6 +42,6 @@ class Gem::Resolver::SourceSet < Gem::Resolver::Set def get_set(name) link = @links[name] - @sets[link] ||= Gem::Source.new(link).dependency_resolver_set if link + @sets[link] ||= Gem::Source.new(link).dependency_resolver_set(@prerelease) if link end end diff --git a/lib/rubygems/s3_uri_signer.rb b/lib/rubygems/s3_uri_signer.rb index 7c95a9d4f5..148cba38c4 100644 --- a/lib/rubygems/s3_uri_signer.rb +++ b/lib/rubygems/s3_uri_signer.rb @@ -1,11 +1,14 @@ # frozen_string_literal: true require_relative "openssl" +require_relative "user_interaction" ## # S3URISigner implements AWS SigV4 for S3 Source to avoid a dependency on the aws-sdk-* gems # More on AWS SigV4: https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-authenticating-requests.html class Gem::S3URISigner + include Gem::UserInteraction + class ConfigurationError < Gem::Exception def initialize(message) super message @@ -27,9 +30,11 @@ class Gem::S3URISigner end attr_accessor :uri + attr_accessor :method - def initialize(uri) + def initialize(uri, method) @uri = uri + @method = method end ## @@ -38,7 +43,7 @@ class Gem::S3URISigner s3_config = fetch_s3_config current_time = Time.now.utc - date_time = current_time.strftime("%Y%m%dT%H%m%SZ") + date_time = current_time.strftime("%Y%m%dT%H%M%SZ") date = date_time[0,8] credential_info = "#{date}/#{s3_config.region}/s3/aws4_request" @@ -73,7 +78,7 @@ class Gem::S3URISigner def generate_canonical_request(canonical_host, query_params) [ - "GET", + method.upcase, uri.path, query_params, "host:#{canonical_host}", @@ -145,17 +150,40 @@ class Gem::S3URISigner require_relative "request/connection_pools" require "json" - iam_info = ec2_metadata_request(EC2_IAM_INFO) + # First try V2 fallback to V1 + res = nil + begin + res = ec2_metadata_credentials_imds_v2 + rescue InstanceProfileError + alert_warning "Unable to access ec2 credentials via IMDSv2, falling back to IMDSv1" + res = ec2_metadata_credentials_imds_v1 + end + res + end + + def ec2_metadata_credentials_imds_v2 + token = ec2_metadata_token + iam_info = ec2_metadata_request(EC2_IAM_INFO, token:) # Expected format: arn:aws:iam::<id>:instance-profile/<role_name> role_name = iam_info["InstanceProfileArn"].split("/").last - ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name) + ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token:) end - def ec2_metadata_request(url) - uri = Gem::URI(url) - @request_pool ||= create_request_pool(uri) - request = Gem::Request.new(uri, Gem::Net::HTTP::Get, nil, @request_pool) - response = request.fetch + def ec2_metadata_credentials_imds_v1 + iam_info = ec2_metadata_request(EC2_IAM_INFO, token: nil) + # Expected format: arn:aws:iam::<id>:instance-profile/<role_name> + role_name = iam_info["InstanceProfileArn"].split("/").last + ec2_metadata_request(EC2_IAM_SECURITY_CREDENTIALS + role_name, token: nil) + end + + def ec2_metadata_request(url, token:) + request = ec2_iam_request(Gem::URI(url), Gem::Net::HTTP::Get) + + response = request.fetch do |req| + if token + req.add_field "X-aws-ec2-metadata-token", token + end + end case response when Gem::Net::HTTPOK then @@ -165,6 +193,26 @@ class Gem::S3URISigner end end + def ec2_metadata_token + request = ec2_iam_request(Gem::URI(EC2_IAM_TOKEN), Gem::Net::HTTP::Put) + + response = request.fetch do |req| + req.add_field "X-aws-ec2-metadata-token-ttl-seconds", 60 + end + + case response + when Gem::Net::HTTPOK then + response.body + else + raise InstanceProfileError.new("Unable to fetch AWS metadata from #{uri}: #{response.message} #{response.code}") + end + end + + def ec2_iam_request(uri, verb) + @request_pool ||= create_request_pool(uri) + Gem::Request.new(uri, verb, nil, @request_pool) + end + def create_request_pool(uri) proxy_uri = Gem::Request.proxy_uri(Gem::Request.get_proxy_from_env(uri.scheme)) certs = Gem::Request.get_cert_files @@ -172,6 +220,7 @@ class Gem::S3URISigner end BASE64_URI_TRANSLATE = { "+" => "%2B", "/" => "%2F", "=" => "%3D", "\n" => "" }.freeze + EC2_IAM_TOKEN = "http://169.254.169.254/latest/api/token" EC2_IAM_INFO = "http://169.254.169.254/latest/meta-data/iam/info" EC2_IAM_SECURITY_CREDENTIALS = "http://169.254.169.254/latest/meta-data/iam/security-credentials/" end diff --git a/lib/rubygems/safe_marshal/reader.rb b/lib/rubygems/safe_marshal/reader.rb index 740be113e5..4362d65fd6 100644 --- a/lib/rubygems/safe_marshal/reader.rb +++ b/lib/rubygems/safe_marshal/reader.rb @@ -20,6 +20,12 @@ module Gem class EOFError < Error end + class DataTooShortError < Error + end + + class NegativeLengthError < Error + end + def initialize(io) @io = io end @@ -27,7 +33,7 @@ module Gem def read! read_header root = read_element - raise UnconsumedBytesError unless @io.eof? + raise UnconsumedBytesError, "expected EOF, got #{@io.read(10).inspect}... after top-level element #{root.class}" unless @io.eof? root end @@ -41,8 +47,16 @@ module Gem raise UnsupportedVersionError, "Unsupported marshal version #{v.bytes.map(&:ord).join(".")}, expected #{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" unless v == MARSHAL_VERSION end + def read_bytes(n) + raise NegativeLengthError if n < 0 + str = @io.read(n) + raise EOFError, "expected #{n} bytes, got EOF" if str.nil? + raise DataTooShortError, "expected #{n} bytes, got #{str.inspect}" unless str.bytesize == n + str + end + def read_byte - @io.getbyte + @io.getbyte || raise(EOFError, "Unexpected EOF") end def read_integer @@ -67,8 +81,6 @@ module Gem read_byte | (read_byte << 8) | -0x10000 when 0xFF read_byte | -0x100 - when nil - raise EOFError, "Unexpected EOF" else signed = (b ^ 128) - 128 if b >= 128 @@ -107,8 +119,6 @@ module Gem when 47 then read_regexp # ?/ when 83 then read_struct # ?S when 67 then read_user_class # ?C - when nil - raise EOFError, "Unexpected EOF" else raise Error, "Unknown marshal type discriminator #{type.chr.inspect} (#{type})" end @@ -127,7 +137,7 @@ module Gem Elements::Symbol.new(byte.chr) end else - name = -@io.read(len) + name = read_bytes(len) Elements::Symbol.new(name) end end @@ -138,7 +148,7 @@ module Gem def read_string length = read_integer return EMPTY_STRING if length == 0 - str = @io.read(length) + str = read_bytes(length) Elements::String.new(str) end @@ -152,7 +162,7 @@ module Gem def read_user_defined name = read_element - binary_string = @io.read(read_integer) + binary_string = read_bytes(read_integer) Elements::UserDefined.new(name, binary_string) end @@ -162,6 +172,7 @@ module Gem def read_array length = read_integer return EMPTY_ARRAY if length == 0 + raise NegativeLengthError if length < 0 elements = Array.new(length) do read_element end @@ -170,7 +181,9 @@ module Gem def read_object_with_ivars object = read_element - ivars = Array.new(read_integer) do + length = read_integer + raise NegativeLengthError if length < 0 + ivars = Array.new(length) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) @@ -239,7 +252,9 @@ module Gem end def read_hash_with_default_value - pairs = Array.new(read_integer) do + length = read_integer + raise NegativeLengthError if length < 0 + pairs = Array.new(length) do [read_element, read_element] end default = read_element @@ -249,7 +264,9 @@ module Gem def read_object name = read_element object = Elements::Object.new(name) - ivars = Array.new(read_integer) do + length = read_integer + raise NegativeLengthError if length < 0 + ivars = Array.new(length) do [read_element, read_element] end Elements::WithIvars.new(object, ivars) @@ -260,13 +277,13 @@ module Gem end def read_float - string = @io.read(read_integer) + string = read_bytes(read_integer) Elements::Float.new(string) end def read_bignum sign = read_byte - data = @io.read(read_integer * 2) + data = read_bytes(read_integer * 2) Elements::Bignum.new(sign, data) end diff --git a/lib/rubygems/safe_marshal/visitors/to_ruby.rb b/lib/rubygems/safe_marshal/visitors/to_ruby.rb index a9f1d048d4..a1f9481776 100644 --- a/lib/rubygems/safe_marshal/visitors/to_ruby.rb +++ b/lib/rubygems/safe_marshal/visitors/to_ruby.rb @@ -45,7 +45,7 @@ module Gem::SafeMarshal idx = 0 # not idiomatic, but there's a huge number of IMEMOs allocated here, so we avoid the block # because this is such a hot path when doing a bundle install with the full index - until idx == size + while idx < size push_stack idx array << visit(elements[idx]) idx += 1 @@ -98,16 +98,21 @@ module Gem::SafeMarshal end s = e.object.binary_string + # 122 is the largest integer that can be represented in marshal in a single byte + raise TimeTooLargeError.new("binary string too large", stack: formatted_stack) if s.bytesize > 122 marshal_string = "\x04\bIu:\tTime".b - marshal_string.concat(s.size + 5) + marshal_string.concat(s.bytesize + 5) marshal_string << s + # internal is limited to 5, so no overflow is possible marshal_string.concat(internal.size + 5) internal.each do |k, v| + k = k.name + # ivar name can't be too large because only known ivars are in the internal ivars list marshal_string.concat(":") - marshal_string.concat(k.size + 5) - marshal_string.concat(k.to_s) + marshal_string.concat(k.bytesize + 5) + marshal_string.concat(k) dumped = Marshal.dump(v) dumped[0, 2] = "" marshal_string.concat(dumped) @@ -171,11 +176,11 @@ module Gem::SafeMarshal end def visit_Gem_SafeMarshal_Elements_ObjectLink(o) - @objects[o.offset] + @objects.fetch(o.offset) end def visit_Gem_SafeMarshal_Elements_SymbolLink(o) - @symbols[o.offset] + @symbols.fetch(o.offset) end def visit_Gem_SafeMarshal_Elements_UserDefined(o) @@ -219,16 +224,18 @@ module Gem::SafeMarshal end def visit_Gem_SafeMarshal_Elements_Float(f) - case f.string - when "inf" - ::Float::INFINITY - when "-inf" - -::Float::INFINITY - when "nan" - ::Float::NAN - else - f.string.to_f - end + register_object( + case f.string + when "inf" + ::Float::INFINITY + when "-inf" + -::Float::INFINITY + when "nan" + ::Float::NAN + else + f.string.to_f + end + ) end def visit_Gem_SafeMarshal_Elements_Bignum(b) @@ -374,6 +381,12 @@ module Gem::SafeMarshal class Error < StandardError end + class TimeTooLargeError < Error + def initialize(message, stack:) + super "#{message} @ #{stack.join "."}" + end + end + class UnpermittedSymbolError < Error def initialize(symbol:, stack:) @symbol = symbol diff --git a/lib/rubygems/security/policy.rb b/lib/rubygems/security/policy.rb index 7b86ac5763..128958ab80 100644 --- a/lib/rubygems/security/policy.rb +++ b/lib/rubygems/security/policy.rb @@ -143,7 +143,7 @@ class Gem::Security::Policy end ## - # Ensures the root of +chain+ has a trusted certificate in +trust_dir+ and + # Ensures the root of +chain+ has a trusted certificate in Gem::Security.trust_dir and # the digests of the two certificates match according to +digester+ def check_trust(chain, digester, trust_dir) diff --git a/lib/rubygems/security/signer.rb b/lib/rubygems/security/signer.rb index 5732fb57fd..eeeeb52906 100644 --- a/lib/rubygems/security/signer.rb +++ b/lib/rubygems/security/signer.rb @@ -52,7 +52,7 @@ class Gem::Security::Signer re_signed_cert = Gem::Security.re_sign( expired_cert, private_key, - (Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days) + Gem::Security::ONE_DAY * Gem.configuration.cert_expiration_length_days ) Gem::Security.write(re_signed_cert, expired_cert_path) diff --git a/lib/rubygems/shellwords.rb b/lib/rubygems/shellwords.rb deleted file mode 100644 index 741dccb363..0000000000 --- a/lib/rubygems/shellwords.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -autoload :Shellwords, "shellwords" diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb index bee5681dab..f203b7312b 100644 --- a/lib/rubygems/source.rb +++ b/lib/rubygems/source.rb @@ -67,28 +67,11 @@ class Gem::Source ## # Returns a Set that can fetch specifications from this source. - - def dependency_resolver_set # :nodoc: - return Gem::Resolver::IndexSet.new self if uri.scheme == "file" - - fetch_uri = if uri.host == "rubygems.org" - index_uri = uri.dup - index_uri.host = "index.rubygems.org" - index_uri - else - uri - end - - bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" - - begin - fetcher = Gem::RemoteFetcher.fetcher - response = fetcher.fetch_path bundler_api_uri, nil, true - rescue Gem::RemoteFetcher::FetchError - Gem::Resolver::IndexSet.new self - else - Gem::Resolver::APISet.new response.uri + "./info/" - end + # + # The set will optionally fetch prereleases if requested. + # + def dependency_resolver_set(prerelease = false) + new_dependency_resolver_set.tap {|set| set.prerelease = prerelease } end def hash # :nodoc: @@ -119,7 +102,7 @@ class Gem::Source end ## - # Fetches a specification for the given +name_tuple+. + # Fetches a specification for the given Gem::NameTuple. def fetch_spec(name_tuple) fetcher = Gem::RemoteFetcher.fetcher @@ -207,7 +190,7 @@ class Gem::Source # Downloads +spec+ and writes it to +dir+. See also # Gem::RemoteFetcher#download. - def download(spec, dir=Dir.pwd) + def download(spec, dir = Dir.pwd) fetcher = Gem::RemoteFetcher.fetcher fetcher.download spec, uri.to_s, dir end @@ -227,13 +210,36 @@ class Gem::Source end end - def typo_squatting?(host, distance_threshold=4) + def typo_squatting?(host, distance_threshold = 4) return if @uri.host.nil? levenshtein_distance(@uri.host, host).between? 1, distance_threshold end private + def new_dependency_resolver_set + return Gem::Resolver::IndexSet.new self if uri.scheme == "file" + + fetch_uri = if uri.host == "rubygems.org" + index_uri = uri.dup + index_uri.host = "index.rubygems.org" + index_uri + else + uri + end + + bundler_api_uri = enforce_trailing_slash(fetch_uri) + "versions" + + begin + fetcher = Gem::RemoteFetcher.fetcher + response = fetcher.fetch_path bundler_api_uri, nil, true + rescue Gem::RemoteFetcher::FetchError + Gem::Resolver::IndexSet.new self + else + Gem::Resolver::APISet.new response.uri + "./info/" + end + end + def enforce_trailing_slash(uri) uri.merge(uri.path.gsub(%r{/+$}, "") + "/") end diff --git a/lib/rubygems/source/git.rb b/lib/rubygems/source/git.rb index 34f6851bc4..baf2f9dd4c 100644 --- a/lib/rubygems/source/git.rb +++ b/lib/rubygems/source/git.rb @@ -58,7 +58,6 @@ class Gem::Source::Git < Gem::Source @remote = true @root_dir = Gem.dir - @git = ENV["git"] || "git" end def <=>(other) @@ -81,6 +80,10 @@ class Gem::Source::Git < Gem::Source @need_submodules == other.need_submodules end + def git_command + ENV.fetch("git", "git") + end + ## # Checks out the files for the repository into the install_dir. @@ -90,18 +93,18 @@ class Gem::Source::Git < Gem::Source return false unless File.exist? repo_cache_dir unless File.exist? install_dir - system @git, "clone", "--quiet", "--no-checkout", + system git_command, "clone", "--quiet", "--no-checkout", repo_cache_dir, install_dir end Dir.chdir install_dir do - system @git, "fetch", "--quiet", "--force", "--tags", install_dir + system git_command, "fetch", "--quiet", "--force", "--tags", install_dir - success = system @git, "reset", "--quiet", "--hard", rev_parse + success = system git_command, "reset", "--quiet", "--hard", rev_parse if @need_submodules require "open3" - _, status = Open3.capture2e(@git, "submodule", "update", "--quiet", "--init", "--recursive") + _, status = Open3.capture2e(git_command, "submodule", "update", "--quiet", "--init", "--recursive") success &&= status.success? end @@ -118,11 +121,11 @@ class Gem::Source::Git < Gem::Source if File.exist? repo_cache_dir Dir.chdir repo_cache_dir do - system @git, "fetch", "--quiet", "--force", "--tags", + system git_command, "fetch", "--quiet", "--force", "--tags", @repository, "refs/heads/*:refs/heads/*" end else - system @git, "clone", "--quiet", "--bare", "--no-hardlinks", + system git_command, "clone", "--quiet", "--bare", "--no-hardlinks", @repository, repo_cache_dir end end @@ -182,7 +185,7 @@ class Gem::Source::Git < Gem::Source hash = nil Dir.chdir repo_cache_dir do - hash = Gem::Util.popen(@git, "rev-parse", @reference).strip + hash = Gem::Util.popen(git_command, "rev-parse", @reference).strip end raise Gem::Exception, @@ -201,7 +204,7 @@ class Gem::Source::Git < Gem::Source return [] unless install_dir Dir.chdir install_dir do - Dir["{,*,*/*}.gemspec"].map do |spec_file| + Dir["{,*,*/*}.gemspec"].filter_map do |spec_file| directory = File.dirname spec_file file = File.basename spec_file @@ -218,7 +221,7 @@ class Gem::Source::Git < Gem::Source end spec end - end.compact + end end end diff --git a/lib/rubygems/source_list.rb b/lib/rubygems/source_list.rb index 33db64fbc1..19bf4595c4 100644 --- a/lib/rubygems/source_list.rb +++ b/lib/rubygems/source_list.rb @@ -60,6 +60,42 @@ class Gem::SourceList end ## + # Prepends +obj+ to the beginning of the source list which may be a Gem::Source, Gem::URI or URI + # Moves +obj+ to the beginning of the list if already present. + # String. + + def prepend(obj) + src = case obj + when Gem::Source + obj + else + Gem::Source.new(obj) + end + + @sources.delete(src) if @sources.include?(src) + @sources.unshift(src) + src + end + + ## + # Appends +obj+ to the end of the source list, moving it if already present. + # +obj+ may be a Gem::Source, Gem::URI or URI String. + # Moves +obj+ to the end of the list if already present. + + def append(obj) + src = case obj + when Gem::Source + obj + else + Gem::Source.new(obj) + end + + @sources.delete(src) if @sources.include?(src) + @sources << src + src + end + + ## # Replaces this SourceList with the sources in +other+ See #<< for # acceptable items in +other+. diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb index cf92dd73ee..835dedf948 100644 --- a/lib/rubygems/spec_fetcher.rb +++ b/lib/rubygems/spec_fetcher.rb @@ -83,7 +83,7 @@ class Gem::SpecFetcher # # If +matching_platform+ is false, gems for all platforms are returned. - def search_for_dependency(dependency, matching_platform=true) + def search_for_dependency(dependency, matching_platform = true) found = {} rejected_specs = {} @@ -130,7 +130,7 @@ class Gem::SpecFetcher ## # Return all gem name tuples who's names match +obj+ - def detect(type=:complete) + def detect(type = :complete) tuples = [] list, _ = available_specs(type) @@ -150,7 +150,7 @@ class Gem::SpecFetcher # # If +matching_platform+ is false, gems for all platforms are returned. - def spec_for_dependency(dependency, matching_platform=true) + def spec_for_dependency(dependency, matching_platform = true) tuples, errors = search_for_dependency(dependency, matching_platform) specs = [] @@ -182,20 +182,31 @@ class Gem::SpecFetcher min_length = gem_name.length - max max_length = gem_name.length + max + gem_name_with_postfix = "#{gem_name}ruby" + gem_name_with_prefix = "ruby#{gem_name}" + matches = names.filter_map do |n| len = n.name.length - # If the length is min_length or shorter, we've done `max` deletions. - # If the length is max_length or longer, we've done `max` insertions. - # These would both be rejected later, so we skip early for performance. - next if len <= min_length || len >= max_length - # If the gem doesn't support the current platform, bail early. next unless n.match_platform? + # If the length is min_length or shorter, we've done `max` deletions. + # This would be rejected later, so we skip it for performance. + next if len <= min_length + # The candidate name, normalized the same as gem_name. normalized_name = n.name.downcase normalized_name.tr!("_-", "") + # If the gem is "{NAME}-ruby" and "ruby-{NAME}", we want to return it. + # But we already removed hyphens, so we check "{NAME}ruby" and "ruby{NAME}". + next [n.name, 0] if normalized_name == gem_name_with_postfix + next [n.name, 0] if normalized_name == gem_name_with_prefix + + # If the length is max_length or longer, we've done `max` insertions. + # This would be rejected later, so we skip it for performance. + next if len >= max_length + # If we found an exact match (after stripping underscores and hyphens), # that's our most likely candidate. # Return it immediately, and skip the rest of the loop. @@ -269,7 +280,7 @@ class Gem::SpecFetcher # Retrieves NameTuples from +source+ of the given +type+ (:prerelease, # etc.). If +gracefully_ignore+ is true, errors are ignored. - def tuples_for(source, type, gracefully_ignore=false) # :nodoc: + def tuples_for(source, type, gracefully_ignore = false) # :nodoc: @caches[type][source.uri] ||= source.load_specs(type).sort_by(&:name) rescue Gem::RemoteFetcher::FetchError diff --git a/lib/rubygems/specification.rb b/lib/rubygems/specification.rb index 11facfb45c..3d1f2dad91 100644 --- a/lib/rubygems/specification.rb +++ b/lib/rubygems/specification.rb @@ -7,12 +7,10 @@ # See LICENSE.txt for permissions. #++ -require_relative "deprecate" require_relative "basic_specification" require_relative "stub_specification" require_relative "platform" require_relative "specification_record" -require_relative "util/list" require "rbconfig" @@ -38,8 +36,6 @@ require "rbconfig" # items you may add to a specification. class Gem::Specification < Gem::BasicSpecification - extend Gem::Deprecate - # REFACTOR: Consider breaking out this version stuff into a separate # module. There's enough special stuff around it that it may justify # a separate class. @@ -464,10 +460,7 @@ class Gem::Specification < Gem::BasicSpecification # spec.platform = Gem::Platform.local def platform=(platform) - if @original_platform.nil? || - @original_platform == Gem::Platform::RUBY - @original_platform = platform - end + @original_platform = platform case platform when Gem::Platform::CURRENT then @@ -491,8 +484,6 @@ class Gem::Specification < Gem::BasicSpecification end @platform = @new_platform.to_s - - invalidate_memoized_attributes end ## @@ -741,14 +732,6 @@ class Gem::Specification < Gem::BasicSpecification attr_accessor :autorequire # :nodoc: ## - # Sets the default executable for this gem. - # - # Deprecated: You must now specify the executable name to Gem.bin_path. - - attr_writer :default_executable - rubygems_deprecate :default_executable= - - ## # Allows deinstallation of gems with legacy platforms. attr_writer :original_platform # :nodoc: @@ -1005,7 +988,7 @@ class Gem::Specification < Gem::BasicSpecification def self.find_in_unresolved_tree(path) unresolved_specs.each do |spec| spec.traverse do |_from_spec, _dep, to_spec, trail| - if to_spec.has_conflicts? || to_spec.conficts_when_loaded_with?(trail) + if to_spec.has_conflicts? || to_spec.conflicts_when_loaded_with?(trail) :next else return trail.reverse if to_spec.contains_requirable_file? path @@ -1017,7 +1000,7 @@ class Gem::Specification < Gem::BasicSpecification end def self.unresolved_specs - unresolved_deps.values.map(&:to_specs).flatten + unresolved_deps.values.flat_map(&:to_specs) end private_class_method :unresolved_specs @@ -1076,7 +1059,7 @@ class Gem::Specification < Gem::BasicSpecification result[spec.name] = spec end - result.map(&:last).flatten.sort_by(&:name) + result.flat_map(&:last).sort_by(&:name) end ## @@ -1202,21 +1185,30 @@ class Gem::Specification < Gem::BasicSpecification Gem.pre_reset_hooks.each(&:call) @specification_record = nil clear_load_cache - unresolved = unresolved_deps - unless unresolved.empty? - warn "WARN: Unresolved or ambiguous specs during Gem::Specification.reset:" - unresolved.values.each do |dep| - warn " #{dep}" - - versions = find_all_by_name(dep.name).uniq(&:full_name) - unless versions.empty? - warn " Available/installed versions of this gem:" - versions.each {|s| warn " - #{s.version}" } + + unless unresolved_deps.empty? + unresolved = unresolved_deps.filter_map do |name, dep| + matching_versions = find_all_by_name(name) + next if dep.latest_version? && matching_versions.any?(&:default_gem?) + + [dep, matching_versions.uniq(&:full_name)] + end.to_h + + unless unresolved.empty? + warn "WARN: Unresolved or ambiguous specs during Gem::Specification.reset:" + unresolved.each do |dep, versions| + warn " #{dep}" + + unless versions.empty? + warn " Available/installed versions of this gem:" + versions.each {|s| warn " - #{s.version}" } + end end + warn "WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>'" + warn "Please report a bug if this causes problems." end - warn "WARN: Clearing out unresolved specs. Try 'gem cleanup <gem>'" - warn "Please report a bug if this causes problems." - unresolved.clear + + unresolved_deps.clear end Gem.post_reset_hooks.each(&:call) end @@ -1308,17 +1300,15 @@ class Gem::Specification < Gem::BasicSpecification spec.instance_variable_set :@summary, array[5] spec.instance_variable_set :@required_ruby_version, array[6] spec.instance_variable_set :@required_rubygems_version, array[7] - spec.instance_variable_set :@original_platform, array[8] + spec.platform = array[8] spec.instance_variable_set :@dependencies, array[9] # offset due to rubyforge_project removal spec.instance_variable_set :@email, array[11] spec.instance_variable_set :@authors, array[12] spec.instance_variable_set :@description, array[13] spec.instance_variable_set :@homepage, array[14] - spec.instance_variable_set :@has_rdoc, array[15] - spec.instance_variable_set :@new_platform, array[16] - spec.instance_variable_set :@platform, array[16].to_s - spec.instance_variable_set :@licenses, [array[17]] + # offset due to has_rdoc removal + spec.instance_variable_set :@licenses, array[17] spec.instance_variable_set :@metadata, array[18] spec.instance_variable_set :@loaded, false spec.instance_variable_set :@activated, false @@ -1411,13 +1401,11 @@ class Gem::Specification < Gem::BasicSpecification raise e end - begin - specs = spec_dep.to_specs.uniq(&:full_name) - rescue Gem::MissingSpecError => e - raise Gem::MissingSpecError.new(e.name, e.requirement, "at: #{spec_file}") - end + specs = spec_dep.matching_specs(true).uniq(&:full_name) - if specs.size == 1 + if specs.size == 0 + raise Gem::MissingSpecError.new(spec_dep.name, spec_dep.requirement, "at: #{spec_file}") + elsif specs.size == 1 specs.first.activate else name = spec_dep.name @@ -1618,14 +1606,14 @@ class Gem::Specification < Gem::BasicSpecification # spec's cached gem. def cache_dir - @cache_dir ||= File.join base_dir, "cache" + File.join base_dir, "cache" end ## # Returns the full path to the cached gem for this spec. def cache_file - @cache_file ||= File.join cache_dir, "#{full_name}.gem" + File.join cache_dir, "#{full_name}.gem" end ## @@ -1647,7 +1635,7 @@ class Gem::Specification < Gem::BasicSpecification ## # return true if there will be conflict when spec if loaded together with the list of specs. - def conficts_when_loaded_with?(list_of_specs) # :nodoc: + def conflicts_when_loaded_with?(list_of_specs) # :nodoc: result = list_of_specs.any? do |spec| spec.runtime_dependencies.any? {|dep| (dep.name == name) && !satisfies_requirement?(dep) } end @@ -1715,24 +1703,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # The default executable for this gem. - # - # Deprecated: The name of the gem is assumed to be the name of the - # executable now. See Gem.bin_path. - - def default_executable # :nodoc: - if defined?(@default_executable) && @default_executable - result = @default_executable - elsif @executables && @executables.size == 1 - result = Array(@executables).first - else - result = nil - end - result - end - rubygems_deprecate :default_executable - - ## # The default value for specification attribute +name+ def default_value(name) @@ -1755,7 +1725,7 @@ class Gem::Specification < Gem::BasicSpecification # # [depending_gem, dependency, [list_of_gems_that_satisfy_dependency]] - def dependent_gems(check_dev=true) + def dependent_gems(check_dev = true) out = [] Gem::Specification.each do |spec| deps = check_dev ? spec.dependencies : spec.runtime_dependencies @@ -1775,7 +1745,7 @@ class Gem::Specification < Gem::BasicSpecification # Returns all specs that matches this spec's runtime dependencies. def dependent_specs - runtime_dependencies.map(&:to_specs).flatten + runtime_dependencies.flat_map(&:to_specs) end ## @@ -1813,15 +1783,8 @@ class Gem::Specification < Gem::BasicSpecification def encode_with(coder) # :nodoc: coder.add "name", @name coder.add "version", @version - platform = case @original_platform - when nil, "" then - "ruby" - when String then - @original_platform - else - @original_platform.to_s - end - coder.add "platform", platform + coder.add "platform", platform.to_s + coder.add "original_platform", original_platform.to_s if platform.to_s != original_platform.to_s attributes = @@attributes.map(&:to_s) - %w[name version platform] attributes.each do |name| @@ -1908,10 +1871,6 @@ class Gem::Specification < Gem::BasicSpecification spec end - def full_name - @full_name ||= super - end - ## # Work around old bundler versions removing my methods # Can be removed once RubyGems can no longer install Bundler 2.5 @@ -1925,29 +1884,6 @@ class Gem::Specification < Gem::BasicSpecification end ## - # Deprecated and ignored, defaults to true. - # - # Formerly used to indicate this gem was RDoc-capable. - - def has_rdoc # :nodoc: - true - end - rubygems_deprecate :has_rdoc - - ## - # Deprecated and ignored. - # - # Formerly used to indicate this gem was RDoc-capable. - - def has_rdoc=(ignored) # :nodoc: - @has_rdoc = true - end - rubygems_deprecate :has_rdoc= - - alias_method :has_rdoc?, :has_rdoc # :nodoc: - rubygems_deprecate :has_rdoc? - - ## # True if this gem has files in test_files def has_unit_tests? # :nodoc: @@ -2049,17 +1985,6 @@ class Gem::Specification < Gem::BasicSpecification end end - ## - # Expire memoized instance variables that can incorrectly generate, replace - # or miss files due changes in certain attributes used to compute them. - - def invalidate_memoized_attributes - @full_name = nil - @cache_file = nil - end - - private :invalidate_memoized_attributes - def inspect # :nodoc: if $DEBUG super @@ -2098,8 +2023,6 @@ class Gem::Specification < Gem::BasicSpecification def internal_init # :nodoc: super @bin_dir = nil - @cache_dir = nil - @cache_file = nil @doc_dir = nil @ri_dir = nil @spec_dir = nil @@ -2149,11 +2072,11 @@ class Gem::Specification < Gem::BasicSpecification @files.concat(@extra_rdoc_files) end - @files = @files.uniq if @files - @extensions = @extensions.uniq if @extensions - @test_files = @test_files.uniq if @test_files - @executables = @executables.uniq if @executables - @extra_rdoc_files = @extra_rdoc_files.uniq if @extra_rdoc_files + @files = @files.uniq.sort if @files + @extensions = @extensions.uniq.sort if @extensions + @test_files = @test_files.uniq.sort if @test_files + @executables = @executables.uniq.sort if @executables + @extra_rdoc_files = @extra_rdoc_files.uniq.sort if @extra_rdoc_files end ## @@ -2452,8 +2375,6 @@ class Gem::Specification < Gem::BasicSpecification :required_rubygems_version, :specification_version, :version, - :has_rdoc, - :default_executable, :metadata, :signing_key, ] @@ -2591,29 +2512,11 @@ class Gem::Specification < Gem::BasicSpecification Gem::SpecificationPolicy.new(self).validate_for_resolution end - def validate_metadata - Gem::SpecificationPolicy.new(self).validate_metadata - end - rubygems_deprecate :validate_metadata - - def validate_dependencies - Gem::SpecificationPolicy.new(self).validate_dependencies - end - rubygems_deprecate :validate_dependencies - - def validate_permissions - Gem::SpecificationPolicy.new(self).validate_permissions - end - rubygems_deprecate :validate_permissions - ## # Set the version to +version+. def version=(version) - @version = Gem::Version.create(version) - return if @version.nil? - - invalidate_memoized_attributes + @version = version.nil? ? version : Gem::Version.create(version) end def stubbed? @@ -2626,13 +2529,12 @@ class Gem::Specification < Gem::BasicSpecification when "date" # Force Date to go through the extra coerce logic in date= self.date = val + when "platform" + self.platform = val else instance_variable_set "@#{ivar}", val end end - - @original_platform = @platform # for backwards compatibility - self.platform = Gem::Platform.new @platform end ## diff --git a/lib/rubygems/specification_policy.rb b/lib/rubygems/specification_policy.rb index d79ee7df92..e5008a24db 100644 --- a/lib/rubygems/specification_policy.rb +++ b/lib/rubygems/specification_policy.rb @@ -190,9 +190,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: ## # Checks that the gem does not depend on itself. - # Checks that dependencies use requirements as we recommend. Warnings are - # issued when dependencies are open-ended or overly strict for semantic - # versioning. def validate_dependencies # :nodoc: warning_messages = [] @@ -200,39 +197,6 @@ duplicate dependency on #{dep}, (#{prev.requirement}) use: if dep.name == @specification.name # warn on self reference warning_messages << "Self referencing dependency is unnecessary and strongly discouraged." end - - prerelease_dep = dep.requirements_list.any? do |req| - Gem::Requirement.new(req).prerelease? - end - - warning_messages << "prerelease dependency on #{dep} is not recommended" if - prerelease_dep && !@specification.version.prerelease? - - open_ended = dep.requirement.requirements.all? do |op, version| - !version.prerelease? && [">", ">="].include?(op) - end - - next unless open_ended - op, dep_version = dep.requirement.requirements.first - - segments = dep_version.segments - - base = segments.first 2 - - recommendation = if [">", ">="].include?(op) && segments == [0] - " use a bounded requirement, such as \"~> x.y\"" - else - bugfix = if op == ">" - ", \"> #{dep_version}\"" - elsif op == ">=" && base != segments - ", \">= #{dep_version}\"" - end - - " if #{dep.name} is semantically versioned, use:\n" \ - " add_#{dep.type}_dependency \"#{dep.name}\", \"~> #{base.join "."}\"#{bugfix}" - end - - warning_messages << ["open-ended dependency on #{dep} is not recommended", recommendation].join("\n") + "\n" end if warning_messages.any? warning_messages.each {|warning_message| warning warning_message } diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb index 195a355496..d08410096f 100644 --- a/lib/rubygems/specification_record.rb +++ b/lib/rubygems/specification_record.rb @@ -73,7 +73,7 @@ module Gem end ## - # Adds +spec+ to the the record, keeping the collection properly sorted. + # Adds +spec+ to the record, keeping the collection properly sorted. def add_spec(spec) return if all.include? spec diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem index 8afb219058..8afb219058 100644 --- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA_R3.pem +++ b/lib/rubygems/ssl_certs/rubygems.org/GlobalSign.pem diff --git a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem b/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem deleted file mode 100644 index f4ce4ca43d..0000000000 --- a/lib/rubygems/ssl_certs/rubygems.org/GlobalSignRootCA.pem +++ /dev/null @@ -1,21 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- diff --git a/lib/rubygems/text.rb b/lib/rubygems/text.rb index da0795b771..88d4ce59b4 100644 --- a/lib/rubygems/text.rb +++ b/lib/rubygems/text.rb @@ -21,7 +21,7 @@ module Gem::Text # Wraps +text+ to +wrap+ characters and optionally indents by +indent+ # characters - def format_text(text, wrap, indent=0) + def format_text(text, wrap, indent = 0) result = [] work = clean_text(text) diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb index 471c29b6e4..fe4c3a80cf 100644 --- a/lib/rubygems/uninstaller.rb +++ b/lib/rubygems/uninstaller.rb @@ -10,7 +10,6 @@ require "fileutils" require_relative "../rubygems" require_relative "installer_uninstaller_utils" require_relative "dependency_list" -require_relative "rdoc" require_relative "user_interaction" ## @@ -43,10 +42,25 @@ class Gem::Uninstaller attr_reader :spec ## - # Constructs an uninstaller that will uninstall +gem+ + # Constructs an uninstaller that will uninstall gem named +gem+. + # +options+ is a Hash with the following keys: + # + # :version:: Version requirement for the gem to uninstall. If not specified, + # uses Gem::Requirement.default. + # :install_dir:: The directory where the gem is installed. If not specified, + # uses Gem.dir. + # :executables:: Whether executables should be removed without confirmation or not. If nil, asks the user explicitly. + # :all:: If more than one version matches the requirement, whether to forcefully remove all matching versions or ask the user to select specific matching versions that should be removed. + # :ignore:: Ignore broken dependency checks when uninstalling. + # :bin_dir:: Directory containing executables to remove. If not specified, + # uses Gem.bindir. + # :format_executable:: In order to find executables to be removed, format executable names using Gem::Installer.exec_format. + # :abort_on_dependent:: Directly abort uninstallation if dependencies would be broken, rather than asking the user for confirmation. + # :check_dev:: When checking if uninstalling gem would leave broken dependencies around, also consider development dependencies. + # :force:: Set both :all and :ignore to true for forced uninstallation. + # :user_install:: Uninstall from user gem directory instead of system directory. def initialize(gem, options = {}) - # TODO: document the valid options @gem = gem @version = options[:version] || Gem::Requirement.default @install_dir = options[:install_dir] @@ -58,10 +72,6 @@ class Gem::Uninstaller @bin_dir = options[:bin_dir] @format_executable = options[:format_executable] @abort_on_dependent = options[:abort_on_dependent] - - # Indicate if development dependencies should be checked when - # uninstalling. (default: false) - # @check_dev = options[:check_dev] if options[:force] diff --git a/lib/rubygems/uri.rb b/lib/rubygems/uri.rb index a44aaceba5..d729c67d26 100644 --- a/lib/rubygems/uri.rb +++ b/lib/rubygems/uri.rb @@ -30,7 +30,7 @@ class Gem::Uri begin Gem::URI.parse(uri) rescue Gem::URI::InvalidURIError - Gem::URI.parse(Gem::URI::DEFAULT_PARSER.escape(uri)) + Gem::URI.parse(Gem::URI::RFC2396_PARSER.escape(uri)) end end diff --git a/lib/rubygems/uri_formatter.rb b/lib/rubygems/uri_formatter.rb index 517ce33637..8856fdadd2 100644 --- a/lib/rubygems/uri_formatter.rb +++ b/lib/rubygems/uri_formatter.rb @@ -17,7 +17,8 @@ class Gem::UriFormatter # Creates a new URI formatter for +uri+. def initialize(uri) - require "cgi" + require "cgi/escape" + require "cgi/util" unless defined?(CGI::EscapeExt) @uri = uri end diff --git a/lib/rubygems/user_interaction.rb b/lib/rubygems/user_interaction.rb index 0172c4ee89..9fe3e755c4 100644 --- a/lib/rubygems/user_interaction.rb +++ b/lib/rubygems/user_interaction.rb @@ -6,7 +6,6 @@ # See LICENSE.txt for permissions. #++ -require_relative "deprecate" require_relative "text" ## @@ -170,8 +169,6 @@ end # Gem::StreamUI implements a simple stream based user interface. class Gem::StreamUI - extend Gem::Deprecate - ## # The input stream @@ -193,7 +190,7 @@ class Gem::StreamUI # then special operations (like asking for passwords) will use the TTY # commands to disable character echo. - def initialize(in_stream, out_stream, err_stream=$stderr, usetty=true) + def initialize(in_stream, out_stream, err_stream = $stderr, usetty = true) @ins = in_stream @outs = out_stream @errs = err_stream @@ -246,7 +243,7 @@ class Gem::StreamUI # to a tty, raises an exception if default is nil, otherwise returns # default. - def ask_yes_no(question, default=nil) + def ask_yes_no(question, default = nil) unless tty? if default.nil? raise Gem::OperationNotSupportedError, @@ -325,14 +322,14 @@ class Gem::StreamUI ## # Display a statement. - def say(statement="") + def say(statement = "") @outs.puts statement end ## # Display an informational alert. Will ask +question+ if it is not nil. - def alert(statement, question=nil) + def alert(statement, question = nil) @outs.puts "INFO: #{statement}" ask(question) if question end @@ -340,7 +337,7 @@ class Gem::StreamUI ## # Display a warning on stderr. Will ask +question+ if it is not nil. - def alert_warning(statement, question=nil) + def alert_warning(statement, question = nil) @errs.puts "WARNING: #{statement}" ask(question) if question end @@ -349,7 +346,7 @@ class Gem::StreamUI # Display an error message in a location expected to get error messages. # Will ask +question+ if it is not nil. - def alert_error(statement, question=nil) + def alert_error(statement, question = nil) @errs.puts "ERROR: #{statement}" ask(question) if question end diff --git a/lib/rubygems/util.rb b/lib/rubygems/util.rb index 51f9c2029f..ee4106c6ce 100644 --- a/lib/rubygems/util.rb +++ b/lib/rubygems/util.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "deprecate" - ## # This module contains various utility methods as module methods. @@ -57,26 +55,6 @@ module Gem::Util end ## - # Invokes system, but silences all output. - - def self.silent_system(*command) - opt = { out: IO::NULL, err: [:child, :out] } - if Hash === command.last - opt.update(command.last) - cmds = command[0...-1] - else - cmds = command.dup - end - system(*(cmds << opt)) - end - - class << self - extend Gem::Deprecate - - rubygems_deprecate :silent_system - end - - ## # Enumerates the parents of +directory+. def self.traverse_parents(directory, &block) diff --git a/lib/rubygems/util/atomic_file_writer.rb b/lib/rubygems/util/atomic_file_writer.rb new file mode 100644 index 0000000000..32767c6a79 --- /dev/null +++ b/lib/rubygems/util/atomic_file_writer.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true + +# Based on ActiveSupport's AtomicFile implementation +# Copyright (c) David Heinemeier Hansson +# https://github.com/rails/rails/blob/main/activesupport/lib/active_support/core_ext/file/atomic.rb +# Licensed under the MIT License + +module Gem + class AtomicFileWriter + ## + # Write to a file atomically. Useful for situations where you don't + # want other processes or threads to see half-written files. + + def self.open(file_name) + require "securerandom" unless defined?(SecureRandom) + + old_stat = begin + File.stat(file_name) + rescue SystemCallError + nil + end + + # Names can't be longer than 255B + tmp_suffix = ".tmp.#{SecureRandom.hex}" + dirname = File.dirname(file_name) + basename = File.basename(file_name) + tmp_path = File.join(dirname, ".#{basename.byteslice(0, 254 - tmp_suffix.bytesize)}#{tmp_suffix}") + + flags = File::RDWR | File::CREAT | File::EXCL | File::BINARY + flags |= File::SHARE_DELETE if defined?(File::SHARE_DELETE) + + File.open(tmp_path, flags) do |temp_file| + temp_file.binmode + if old_stat + # Set correct permissions on new file + begin + File.chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + File.chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end + + return_val = yield temp_file + rescue StandardError => error + begin + temp_file.close + rescue StandardError + nil + end + + begin + File.unlink(temp_file.path) + rescue StandardError + nil + end + + raise error + else + begin + File.rename(temp_file.path, file_name) + rescue StandardError + begin + File.unlink(temp_file.path) + rescue StandardError + end + + raise + end + + return_val + end + end + end +end diff --git a/lib/rubygems/util/licenses.rb b/lib/rubygems/util/licenses.rb index 192ae30b9b..fbb7b55075 100644 --- a/lib/rubygems/util/licenses.rb +++ b/lib/rubygems/util/licenses.rb @@ -59,12 +59,15 @@ class Gem::Licenses Artistic-1.0-Perl Artistic-1.0-cl8 Artistic-2.0 + Artistic-dist + Aspell-RU BSD-1-Clause BSD-2-Clause BSD-2-Clause-Darwin BSD-2-Clause-Patent BSD-2-Clause-Views BSD-2-Clause-first-lines + BSD-2-Clause-pkgconf-disclaimer BSD-3-Clause BSD-3-Clause-Attribution BSD-3-Clause-Clear @@ -104,6 +107,7 @@ class Gem::Licenses Bitstream-Vera BlueOak-1.0.0 Boehm-GC + Boehm-GC-without-fee Borceux Brian-Gladman-2-Clause Brian-Gladman-3-Clause @@ -163,6 +167,8 @@ class Gem::Licenses CC-BY-SA-3.0-IGO CC-BY-SA-4.0 CC-PDDC + CC-PDM-1.0 + CC-SA-1.0 CC0-1.0 CDDL-1.0 CDDL-1.1 @@ -202,6 +208,7 @@ class Gem::Licenses Cornell-Lossless-JPEG Cronyx Crossword + CryptoSwift CrystalStacker Cube D-FSL-1.0 @@ -212,6 +219,10 @@ class Gem::Licenses DRL-1.0 DRL-1.1 DSDP + DocBook-DTD + DocBook-Schema + DocBook-Stylesheet + DocBook-XML Dotseqn ECL-1.0 ECL-2.0 @@ -234,7 +245,10 @@ class Gem::Licenses FSFAP-no-warranty-disclaimer FSFUL FSFULLR + FSFULLRSD FSFULLRWD + FSL-1.1-ALv2 + FSL-1.1-MIT FTL Fair Ferguson-Twofish @@ -270,11 +284,14 @@ class Gem::Licenses GPL-2.0-or-later GPL-3.0-only GPL-3.0-or-later + Game-Programming-Gems Giftware Glide Glulxe Graphics-Gems Gutmann + HDF5 + HIDAPI HP-1986 HP-1989 HPND @@ -285,6 +302,7 @@ class Gem::Licenses HPND-Kevlin-Henney HPND-MIT-disclaimer HPND-Markus-Kuhn + HPND-Netrek HPND-Pbmplus HPND-UC HPND-UC-export-US @@ -316,6 +334,7 @@ class Gem::Licenses Imlib2 Info-ZIP Inner-Net-2.0 + InnoSetup Intel Intel-ACPI Interbase-1.0 @@ -360,9 +379,11 @@ class Gem::Licenses Linux-man-pages-copyleft-2-para Linux-man-pages-copyleft-var Lucida-Bitmap-Fonts + MIPS MIT MIT-0 MIT-CMU + MIT-Click MIT-Festival MIT-Khronos-old MIT-Modern-Variant @@ -415,10 +436,10 @@ class Gem::Licenses NPL-1.1 NPOSL-3.0 NRL + NTIA-PD NTP NTP-0 Naumen - Net-SNMP NetCDF Newsletr Nokia @@ -500,6 +521,7 @@ class Gem::Licenses RSCPL Rdisc Ruby + Ruby-pty SAX-PD SAX-PD-2.0 SCEA @@ -513,19 +535,23 @@ class Gem::Licenses SISSL SISSL-1.2 SL + SMAIL-GPL SMLNJ SMPPL SNIA + SOFA SPL-1.0 SSH-OpenSSH SSH-short SSLeay-standalone SSPL-1.0 + SUL-1.0 SWL Saxpath SchemeReport Sendmail Sendmail-8.23 + Sendmail-Open-Source-1.1 SimPL-2.0 Sleepycat Soundex @@ -551,17 +577,22 @@ class Gem::Licenses TU-Berlin-1.0 TU-Berlin-2.0 TermReadKey + ThirdEye + TrustedQSL UCAR UCL-1.0 UMich-Merit UPL-1.0 URT-RLE + Ubuntu-font-1.0 Unicode-3.0 Unicode-DFS-2015 Unicode-DFS-2016 Unicode-TOU UnixCrypt Unlicense + Unlicense-libtelnet + Unlicense-libwhirlpool VOSTROM VSL-1.0 Vim @@ -574,6 +605,7 @@ class Gem::Licenses Wsuipa X11 X11-distribute-modifications-variant + X11-swapped XFree86-1.1 XSkat Xdebug-1.03 @@ -592,6 +624,7 @@ class Gem::Licenses Zimbra-1.4 Zlib any-OSI + any-OSI-perl-modules bcrypt-Solar-Designer blessing bzip2-1.0.6 @@ -608,10 +641,13 @@ class Gem::Licenses etalab-2.0 fwlw gSOAP-1.3b + generic-xts gnuplot gtkbook hdparm iMatix + jove + libpng-1.6.35 libpng-2.0 libselinux-1.0 libtiff @@ -619,10 +655,12 @@ class Gem::Licenses lsof magaz mailprio + man2html metamail mpi-permissive mpich2 mplus + ngrep pkgconf pnmstitch psfrag @@ -636,6 +674,7 @@ class Gem::Licenses threeparttable ulem w3m + wwl xinetd xkeyboard-config-Zinoviev xlock @@ -671,6 +710,7 @@ class Gem::Licenses LGPL-2.1+ LGPL-3.0 LGPL-3.0+ + Net-SNMP Nunit StandardML-NJ bzip2-1.0.5 @@ -691,9 +731,11 @@ class Gem::Licenses Bison-exception-1.24 Bison-exception-2.2 Bootloader-exception + CGAL-linking-exception CLISP-exception-2.0 Classpath-exception-2.0 DigiRule-FOSS-exception + Digia-Qt-LGPL-exception-1.1 FLTK-exception Fawkes-Runtime-exception Font-exception-2.0 @@ -703,6 +745,7 @@ class Gem::Licenses GNAT-exception GNOME-examples-exception GNU-compiler-exception + GPL-3.0-389-ds-base-exception GPL-3.0-interface-exception GPL-3.0-linking-exception GPL-3.0-linking-source-exception @@ -710,6 +753,7 @@ class Gem::Licenses GStreamer-exception-2005 GStreamer-exception-2008 Gmsh-exception + Independent-modules-exception KiCad-libraries-exception LGPL-3.0-linking-exception LLGPL @@ -738,13 +782,18 @@ class Gem::Licenses WxWindows-exception-3.1 cryptsetup-OpenSSL-exception eCos-exception-2.0 + erlang-otp-linking-exception fmt-exception freertos-exception-2.0 gnu-javamail-exception + harbour-exception i2p-gpl-java-exception libpri-OpenH323-exception mif-exception + mxml-exception openvpn-openssl-exception + polyparse-exception + romic-exception stunnel-exception u-boot-exception-2.0 vsftpd-openssl-exception diff --git a/lib/rubygems/util/list.rb b/lib/rubygems/util/list.rb deleted file mode 100644 index 2899e8a2b9..0000000000 --- a/lib/rubygems/util/list.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gem - # The Gem::List class is currently unused and will be removed in the next major rubygems version - class List # :nodoc: - include Enumerable - attr_accessor :value, :tail - - def initialize(value = nil, tail = nil) - @value = value - @tail = tail - end - - def each - n = self - while n - yield n.value - n = n.tail - end - end - - def to_a - super.reverse - end - - def prepend(value) - List.new value, self - end - - def pretty_print(q) # :nodoc: - q.pp to_a - end - - def self.prepend(list, value) - return List.new(value) unless list - List.new value, list - end - end - deprecate_constant :List -end diff --git a/lib/rubygems/validator.rb b/lib/rubygems/validator.rb index 57e0747eb4..eb5b513570 100644 --- a/lib/rubygems/validator.rb +++ b/lib/rubygems/validator.rb @@ -59,7 +59,7 @@ class Gem::Validator #-- # TODO needs further cleanup - def alien(gems=[]) + def alien(gems = []) errors = Hash.new {|h,k| h[k] = {} } Gem::Specification.each do |spec| diff --git a/lib/bundler/vendor/connection_pool/.document b/lib/rubygems/vendor/.document index 0c43bbd6b3..0c43bbd6b3 100644 --- a/lib/bundler/vendor/connection_pool/.document +++ b/lib/rubygems/vendor/.document diff --git a/lib/rubygems/vendor/molinillo/.document b/lib/rubygems/vendor/molinillo/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/molinillo/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/net-http/.document b/lib/rubygems/vendor/net-http/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/net-http/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/net-http/lib/net/http.rb b/lib/rubygems/vendor/net-http/lib/net/http.rb index 1a7074819d..4800cd25f1 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http.rb @@ -46,7 +46,7 @@ module Gem::Net #:nodoc: # == Strategies # # - If you will make only a few GET requests, - # consider using {OpenURI}[rdoc-ref:OpenURI]. + # consider using {OpenURI}[https://docs.ruby-lang.org/en/master/OpenURI.html]. # - If you will make only a few requests of all kinds, # consider using the various singleton convenience methods in this class. # Each of the following methods automatically starts and finishes @@ -67,6 +67,8 @@ module Gem::Net #:nodoc: # Gem::Net::HTTP.post(uri, data) # params = {title: 'foo', body: 'bar', userId: 1} # Gem::Net::HTTP.post_form(uri, params) + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # Gem::Net::HTTP.put(uri, data) # # - If performance is important, consider using sessions, which lower request overhead. # This {session}[rdoc-ref:Gem::Net::HTTP@Sessions] has multiple requests for @@ -100,14 +102,14 @@ module Gem::Net #:nodoc: # # == URIs # - # On the internet, a URI + # On the internet, a Gem::URI # ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier]) # is a string that identifies a particular resource. # It consists of some or all of: scheme, hostname, path, query, and fragment; - # see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. + # see {Gem::URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax]. # - # A Ruby {Gem::URI::Generic}[rdoc-ref:Gem::URI::Generic] object - # represents an internet URI. + # A Ruby {Gem::URI::Generic}[https://docs.ruby-lang.org/en/master/Gem::URI/Generic.html] object + # represents an internet Gem::URI. # It provides, among others, methods # +scheme+, +hostname+, +path+, +query+, and +fragment+. # @@ -142,7 +144,7 @@ module Gem::Net #:nodoc: # # === Queries # - # A host-specific query adds name/value pairs to the URI: + # A host-specific query adds name/value pairs to the Gem::URI: # # _uri = uri.dup # params = {userId: 1, completed: false} @@ -152,7 +154,7 @@ module Gem::Net #:nodoc: # # === Fragments # - # A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect + # A {Gem::URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect # in \Gem::Net::HTTP; # the same data is returned, regardless of whether a fragment is included. # @@ -325,9 +327,9 @@ module Gem::Net #:nodoc: # res = http.request(req) # end # - # Or if you simply want to make a GET request, you may pass in a URI + # Or if you simply want to make a GET request, you may pass in a Gem::URI # object that has an \HTTPS URL. \Gem::Net::HTTP automatically turns on TLS - # verification if the URI object has a 'https' :URI scheme: + # verification if the Gem::URI object has a 'https' Gem::URI scheme: # # uri # => #<Gem::URI::HTTPS https://jsonplaceholder.typicode.com/> # Gem::Net::HTTP.get(uri) @@ -372,7 +374,7 @@ module Gem::Net #:nodoc: # # When environment variable <tt>'http_proxy'</tt> # is set to a \Gem::URI string, - # the returned +http+ will have the server at that URI as its proxy; + # the returned +http+ will have the server at that Gem::URI as its proxy; # note that the \Gem::URI string must have a protocol # such as <tt>'http'</tt> or <tt>'https'</tt>: # @@ -456,6 +458,10 @@ module Gem::Net #:nodoc: # # == What's Here # + # First, what's elsewhere. Class Gem::Net::HTTP: + # + # - Inherits from {class Object}[https://docs.ruby-lang.org/en/master/Object.html#class-Object-label-What-27s+Here]. + # # This is a categorized summary of methods and attributes. # # === \Gem::Net::HTTP Objects @@ -469,8 +475,7 @@ module Gem::Net #:nodoc: # # - {::start}[rdoc-ref:Gem::Net::HTTP.start]: # Begins a new session in a new \Gem::Net::HTTP object. - # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?] - # (aliased as {#active?}[rdoc-ref:Gem::Net::HTTP#active?]): + # - {#started?}[rdoc-ref:Gem::Net::HTTP#started?]: # Returns whether in a session. # - {#finish}[rdoc-ref:Gem::Net::HTTP#finish]: # Ends an active session. @@ -520,6 +525,8 @@ module Gem::Net #:nodoc: # Sends a POST request with form data and returns a response object. # - {::post}[rdoc-ref:Gem::Net::HTTP.post]: # Sends a POST request with data and returns a response object. + # - {::put}[rdoc-ref:Gem::Net::HTTP.put]: + # Sends a PUT request with data and returns a response object. # - {#copy}[rdoc-ref:Gem::Net::HTTP#copy]: # Sends a COPY request and returns a response object. # - {#delete}[rdoc-ref:Gem::Net::HTTP#delete]: @@ -548,18 +555,15 @@ module Gem::Net #:nodoc: # Sends a PUT request and returns a response object. # - {#request}[rdoc-ref:Gem::Net::HTTP#request]: # Sends a request and returns a response object. - # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get] - # (aliased as {#get2}[rdoc-ref:Gem::Net::HTTP#get2]): + # - {#request_get}[rdoc-ref:Gem::Net::HTTP#request_get]: # Sends a GET request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head] - # (aliased as {#head2}[rdoc-ref:Gem::Net::HTTP#head2]): + # - {#request_head}[rdoc-ref:Gem::Net::HTTP#request_head]: # Sends a HEAD request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. - # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post] - # (aliased as {#post2}[rdoc-ref:Gem::Net::HTTP#post2]): + # - {#request_post}[rdoc-ref:Gem::Net::HTTP#request_post]: # Sends a POST request and forms a response object; # if a block given, calls the block with the object, # otherwise returns the object. @@ -597,8 +601,7 @@ module Gem::Net #:nodoc: # Returns whether +self+ is a proxy class. # - {#proxy?}[rdoc-ref:Gem::Net::HTTP#proxy?]: # Returns whether +self+ has a proxy. - # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address] - # (aliased as {#proxyaddr}[rdoc-ref:Gem::Net::HTTP#proxyaddr]): + # - {#proxy_address}[rdoc-ref:Gem::Net::HTTP#proxy_address]: # Returns the proxy address. # - {#proxy_from_env?}[rdoc-ref:Gem::Net::HTTP#proxy_from_env?]: # Returns whether the proxy is taken from an environment variable. @@ -710,8 +713,7 @@ module Gem::Net #:nodoc: # === \HTTP Version # # - {::version_1_2?}[rdoc-ref:Gem::Net::HTTP.version_1_2?] - # (aliased as {::is_version_1_2?}[rdoc-ref:Gem::Net::HTTP.is_version_1_2?] - # and {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): + # (aliased as {::version_1_2}[rdoc-ref:Gem::Net::HTTP.version_1_2]): # Returns true; retained for compatibility. # # === Debugging @@ -722,7 +724,7 @@ module Gem::Net #:nodoc: class HTTP < Protocol # :stopdoc: - VERSION = "0.4.1" + VERSION = "0.9.1" HTTPVersion = '1.1' begin require 'zlib' @@ -788,7 +790,7 @@ module Gem::Net #:nodoc: # "completed": false # } # - # With URI object +uri+ and optional hash argument +headers+: + # With Gem::URI object +uri+ and optional hash argument +headers+: # # uri = Gem::URI('https://jsonplaceholder.typicode.com/todos/1') # headers = {'Content-type' => 'application/json; charset=UTF-8'} @@ -861,7 +863,7 @@ module Gem::Net #:nodoc: # Posts data to a host; returns a Gem::Net::HTTPResponse object. # - # Argument +url+ must be a URI; + # Argument +url+ must be a Gem::URI; # argument +data+ must be a hash: # # _uri = uri.dup @@ -889,6 +891,39 @@ module Gem::Net #:nodoc: } end + # Sends a PUT request to the server; returns a Gem::Net::HTTPResponse object. + # + # Argument +url+ must be a URL; + # argument +data+ must be a string: + # + # _uri = uri.dup + # _uri.path = '/posts' + # data = '{"title": "foo", "body": "bar", "userId": 1}' + # headers = {'content-type': 'application/json'} + # res = Gem::Net::HTTP.put(_uri, data, headers) # => #<Gem::Net::HTTPCreated 201 Created readbody=true> + # puts res.body + # + # Output: + # + # { + # "title": "foo", + # "body": "bar", + # "userId": 1, + # "id": 101 + # } + # + # Related: + # + # - Gem::Net::HTTP::Put: request class for \HTTP method +PUT+. + # - Gem::Net::HTTP#put: convenience method for \HTTP method +PUT+. + # + def HTTP.put(url, data, header = nil) + start(url.hostname, url.port, + :use_ssl => url.scheme == 'https' ) {|http| + http.put(url, data, header) + } + end + # # \HTTP session management # @@ -1062,7 +1097,7 @@ module Gem::Net #:nodoc: # For proxy-defining arguments +p_addr+ through +p_no_proxy+, # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. # - def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil) + def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil, p_use_ssl = nil) http = super address, port if proxy_class? then # from Gem::Net::HTTP::Proxy() @@ -1071,6 +1106,7 @@ module Gem::Net #:nodoc: http.proxy_port = @proxy_port http.proxy_user = @proxy_user http.proxy_pass = @proxy_pass + http.proxy_use_ssl = @proxy_use_ssl elsif p_addr == :ENV then http.proxy_from_env = true else @@ -1082,34 +1118,68 @@ module Gem::Net #:nodoc: http.proxy_port = p_port || default_port http.proxy_user = p_user http.proxy_pass = p_pass + http.proxy_use_ssl = p_use_ssl end http end + class << HTTP + # Allows to set the default configuration that will be used + # when creating a new connection. + # + # Example: + # + # Gem::Net::HTTP.default_configuration = { + # read_timeout: 1, + # write_timeout: 1 + # } + # http = Gem::Net::HTTP.new(hostname) + # http.open_timeout # => 60 + # http.read_timeout # => 1 + # http.write_timeout # => 1 + # + attr_accessor :default_configuration + end + # Creates a new \Gem::Net::HTTP object for the specified server address, # without opening the TCP connection or initializing the \HTTP session. # The +address+ should be a DNS hostname or IP address. def initialize(address, port = nil) # :nodoc: + defaults = { + keep_alive_timeout: 2, + close_on_empty_response: false, + open_timeout: 60, + read_timeout: 60, + write_timeout: 60, + continue_timeout: nil, + max_retries: 1, + debug_output: nil, + response_body_encoding: false, + ignore_eof: true + } + options = defaults.merge(self.class.default_configuration || {}) + @address = address @port = (port || HTTP.default_port) @ipaddr = nil @local_host = nil @local_port = nil @curr_http_version = HTTPVersion - @keep_alive_timeout = 2 + @keep_alive_timeout = options[:keep_alive_timeout] @last_communicated = nil - @close_on_empty_response = false + @close_on_empty_response = options[:close_on_empty_response] @socket = nil @started = false - @open_timeout = 60 - @read_timeout = 60 - @write_timeout = 60 - @continue_timeout = nil - @max_retries = 1 - @debug_output = nil - @response_body_encoding = false - @ignore_eof = true + @open_timeout = options[:open_timeout] + @read_timeout = options[:read_timeout] + @write_timeout = options[:write_timeout] + @continue_timeout = options[:continue_timeout] + @max_retries = options[:max_retries] + @debug_output = options[:debug_output] + @response_body_encoding = options[:response_body_encoding] + @ignore_eof = options[:ignore_eof] + @tcpsocket_supports_open_timeout = nil @proxy_from_env = false @proxy_uri = nil @@ -1117,6 +1187,7 @@ module Gem::Net #:nodoc: @proxy_port = nil @proxy_user = nil @proxy_pass = nil + @proxy_use_ssl = nil @use_ssl = false @ssl_context = nil @@ -1217,7 +1288,7 @@ module Gem::Net #:nodoc: # - The name of an encoding. # - An alias for an encoding name. # - # See {Encoding}[rdoc-ref:Encoding]. + # See {Encoding}[https://docs.ruby-lang.org/en/master/Encoding.html]. # # Examples: # @@ -1252,6 +1323,10 @@ module Gem::Net #:nodoc: # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. attr_writer :proxy_pass + # Sets whether the proxy uses SSL; + # see {Proxy Server}[rdoc-ref:Gem::Net::HTTP@Proxy+Server]. + attr_writer :proxy_use_ssl + # Returns the IP address for the connection. # # If the session has not been started, @@ -1440,23 +1515,6 @@ module Gem::Net #:nodoc: @use_ssl = flag end - SSL_IVNAMES = [ - :@ca_file, - :@ca_path, - :@cert, - :@cert_store, - :@ciphers, - :@extra_chain_cert, - :@key, - :@ssl_timeout, - :@ssl_version, - :@min_version, - :@max_version, - :@verify_callback, - :@verify_depth, - :@verify_mode, - :@verify_hostname, - ] # :nodoc: SSL_ATTRIBUTES = [ :ca_file, :ca_path, @@ -1473,7 +1531,9 @@ module Gem::Net #:nodoc: :verify_depth, :verify_mode, :verify_hostname, - ] # :nodoc: + ].freeze # :nodoc: + + SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: # Sets or returns the path to a CA certification file in PEM format. attr_accessor :ca_file @@ -1490,11 +1550,11 @@ module Gem::Net #:nodoc: attr_accessor :cert_store # Sets or returns the available SSL ciphers. - # See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D]. + # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. attr_accessor :ciphers # Sets or returns the extra X509 certificates to be added to the certificate chain. - # See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate]. + # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. attr_accessor :extra_chain_cert # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. @@ -1504,15 +1564,15 @@ module Gem::Net #:nodoc: attr_accessor :ssl_timeout # Sets or returns the SSL version. - # See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D]. + # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. attr_accessor :ssl_version # Sets or returns the minimum SSL version. - # See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D]. + # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. attr_accessor :min_version # Sets or returns the maximum SSL version. - # See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D]. + # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. attr_accessor :max_version # Sets or returns the callback for the server certification verification. @@ -1528,7 +1588,7 @@ module Gem::Net #:nodoc: # Sets or returns whether to verify that the server certificate is valid # for the hostname. - # See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode]. + # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. attr_accessor :verify_hostname # Returns the X509 certificate chain (an array of strings) @@ -1576,6 +1636,21 @@ module Gem::Net #:nodoc: self end + # Finishes the \HTTP session: + # + # http = Gem::Net::HTTP.new(hostname) + # http.start + # http.started? # => true + # http.finish # => nil + # http.started? # => false + # + # Raises IOError if not in a session. + def finish + raise IOError, 'HTTP session not yet started' unless started? + do_finish + end + + # :stopdoc: def do_start connect @started = true @@ -1598,19 +1673,26 @@ module Gem::Net #:nodoc: end debug "opening connection to #{conn_addr}:#{conn_port}..." - s = Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { - begin - TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) - rescue => e - raise e, "Failed to open TCP connection to " + - "#{conn_addr}:#{conn_port} (#{e.message})" + begin + s = timeouted_connect(conn_addr, conn_port) + rescue => e + if (defined?(IO::TimeoutError) && e.is_a?(IO::TimeoutError)) || e.is_a?(Errno::ETIMEDOUT) # for compatibility with previous versions + e = Gem::Net::OpenTimeout.new(e) end - } + raise e, "Failed to open TCP connection to " + + "#{conn_addr}:#{conn_port} (#{e.message})" + end s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) debug "opened" if use_ssl? if proxy? - plain_sock = BufferedIO.new(s, read_timeout: @read_timeout, + if @proxy_use_ssl + proxy_sock = OpenSSL::SSL::SSLSocket.new(s) + ssl_socket_connect(proxy_sock, @open_timeout) + else + proxy_sock = s + end + proxy_sock = BufferedIO.new(proxy_sock, read_timeout: @read_timeout, write_timeout: @write_timeout, continue_timeout: @continue_timeout, debug_output: @debug_output) @@ -1621,8 +1703,8 @@ module Gem::Net #:nodoc: buf << "Proxy-Authorization: Basic #{credential}\r\n" end buf << "\r\n" - plain_sock.write(buf) - HTTPResponse.read_new(plain_sock).value + proxy_sock.write(buf) + HTTPResponse.read_new(proxy_sock).value # assuming nothing left in buffers after successful CONNECT response end @@ -1692,23 +1774,30 @@ module Gem::Net #:nodoc: end private :connect - def on_connect + tcp_socket_parameters = TCPSocket.instance_method(:initialize).parameters + TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT = if tcp_socket_parameters != [[:rest]] + tcp_socket_parameters.include?([:key, :open_timeout]) + else + # Use Socket.tcp to find out since there is no parameters information for TCPSocket#initialize + # See discussion in https://github.com/ruby/net-http/pull/224 + Socket.method(:tcp).parameters.include?([:key, :open_timeout]) end - private :on_connect + private_constant :TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT - # Finishes the \HTTP session: - # - # http = Gem::Net::HTTP.new(hostname) - # http.start - # http.started? # => true - # http.finish # => nil - # http.started? # => false - # - # Raises IOError if not in a session. - def finish - raise IOError, 'HTTP session not yet started' unless started? - do_finish + def timeouted_connect(conn_addr, conn_port) + if TCP_SOCKET_NEW_HAS_OPEN_TIMEOUT + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port, open_timeout: @open_timeout) + else + Gem::Timeout.timeout(@open_timeout, Gem::Net::OpenTimeout) { + TCPSocket.open(conn_addr, conn_port, @local_host, @local_port) + } + end + end + private :timeouted_connect + + def on_connect end + private :on_connect def do_finish @started = false @@ -1730,13 +1819,14 @@ module Gem::Net #:nodoc: @proxy_port = nil @proxy_user = nil @proxy_pass = nil + @proxy_use_ssl = nil # Creates an \HTTP proxy class which behaves like \Gem::Net::HTTP, but # performs all access via the specified proxy. # # This class is obsolete. You may pass these same parameters directly to # \Gem::Net::HTTP.new. See Gem::Net::HTTP.new for details of the arguments. - def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc: + def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_use_ssl = nil) #:nodoc: return self unless p_addr Class.new(self) { @@ -1754,9 +1844,12 @@ module Gem::Net #:nodoc: @proxy_user = p_user @proxy_pass = p_pass + @proxy_use_ssl = p_use_ssl } end + # :startdoc: + class << HTTP # Returns true if self is a class which was created by HTTP::Proxy. def proxy_class? @@ -1778,6 +1871,9 @@ module Gem::Net #:nodoc: # Returns the password for accessing the proxy, or +nil+ if none; # see Gem::Net::HTTP@Proxy+Server. attr_reader :proxy_pass + + # Use SSL when talking to the proxy. If Gem::Net::HTTP does not use a proxy, nil. + attr_reader :proxy_use_ssl end # Returns +true+ if a proxy server is defined, +false+ otherwise; @@ -1793,7 +1889,7 @@ module Gem::Net #:nodoc: @proxy_from_env end - # The proxy URI determined from the environment for this connection. + # The proxy Gem::URI determined from the environment for this connection. def proxy_uri # :nodoc: return if @proxy_uri == false @proxy_uri ||= Gem::URI::HTTP.new( @@ -1848,9 +1944,11 @@ module Gem::Net #:nodoc: alias proxyport proxy_port #:nodoc: obsolete private + # :stopdoc: def unescape(value) - require 'cgi/util' + require 'cgi/escape' + require 'cgi/util' unless defined?(CGI::EscapeExt) CGI.unescape(value) end @@ -1875,6 +1973,7 @@ module Gem::Net #:nodoc: path end end + # :startdoc: # # HTTP operations @@ -2012,6 +2111,11 @@ module Gem::Net #:nodoc: # http = Gem::Net::HTTP.new(hostname) # http.put('/todos/1', data) # => #<Gem::Net::HTTPOK 200 OK readbody=true> # + # Related: + # + # - Gem::Net::HTTP::Put: request class for \HTTP method PUT. + # - Gem::Net::HTTP.put: sends PUT request, returns response body. + # def put(path, data, initheader = nil) request(Put.new(path, initheader), data) end @@ -2324,7 +2428,9 @@ module Gem::Net #:nodoc: res end - IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc: + # :stopdoc: + + IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/.freeze # :nodoc: def transport_request(req) count = 0 @@ -2350,7 +2456,10 @@ module Gem::Net #:nodoc: res } res.reading_body(@socket, req.response_body_permitted?) { - yield res if block_given? + if block_given? + count = max_retries # Don't restart in the middle of a download + yield res + end } rescue Gem::Net::OpenTimeout raise @@ -2478,6 +2587,11 @@ module Gem::Net #:nodoc: alias_method :D, :debug end + # for backward compatibility until Ruby 4.0 + # https://bugs.ruby-lang.org/issues/20900 + # https://github.com/bblimke/webmock/pull/1081 + HTTPSession = HTTP + deprecate_constant :HTTPSession end require_relative 'http/exceptions' @@ -2492,5 +2606,3 @@ require_relative 'http/response' require_relative 'http/responses' require_relative 'http/proxy_delta' - -require_relative 'http/backward' diff --git a/lib/rubygems/vendor/net-http/lib/net/http/backward.rb b/lib/rubygems/vendor/net-http/lib/net/http/backward.rb deleted file mode 100644 index 10dbc16224..0000000000 --- a/lib/rubygems/vendor/net-http/lib/net/http/backward.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true -# for backward compatibility - -# :enddoc: - -class Gem::Net::HTTP - ProxyMod = ProxyDelta - deprecate_constant :ProxyMod -end - -module Gem::Net::NetPrivate - HTTPRequest = ::Gem::Net::HTTPRequest - deprecate_constant :HTTPRequest -end - -module Gem::Net - HTTPSession = HTTP - - HTTPInformationCode = HTTPInformation - HTTPSuccessCode = HTTPSuccess - HTTPRedirectionCode = HTTPRedirection - HTTPRetriableCode = HTTPRedirection - HTTPClientErrorCode = HTTPClientError - HTTPFatalErrorCode = HTTPClientError - HTTPServerErrorCode = HTTPServerError - HTTPResponseReceiver = HTTPResponse - - HTTPResponceReceiver = HTTPResponse # Typo since 2001 - - deprecate_constant :HTTPSession, - :HTTPInformationCode, - :HTTPSuccessCode, - :HTTPRedirectionCode, - :HTTPRetriableCode, - :HTTPClientErrorCode, - :HTTPFatalErrorCode, - :HTTPServerErrorCode, - :HTTPResponseReceiver, - :HTTPResponceReceiver -end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb index c629c0113b..218df9a8bd 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/exceptions.rb @@ -3,7 +3,7 @@ module Gem::Net # Gem::Net::HTTP exception class. # You cannot use Gem::Net::HTTPExceptions directly; instead, you must use # its subclasses. - module HTTPExceptions + module HTTPExceptions # :nodoc: def initialize(msg, res) #:nodoc: super msg @response = res @@ -12,6 +12,7 @@ module Gem::Net alias data response #:nodoc: obsolete end + # :stopdoc: class HTTPError < ProtocolError include HTTPExceptions end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb index 5cfe75a7cd..d6496d4ac1 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/generic_request.rb @@ -19,16 +19,13 @@ class Gem::Net::HTTPGenericRequest if Gem::URI === uri_or_path then raise ArgumentError, "not an HTTP Gem::URI" unless Gem::URI::HTTP === uri_or_path - hostname = uri_or_path.hostname + hostname = uri_or_path.host raise ArgumentError, "no host component for Gem::URI" unless (hostname && hostname.length > 0) @uri = uri_or_path.dup - host = @uri.hostname.dup - host << ":" << @uri.port.to_s if @uri.port != @uri.default_port @path = uri_or_path.request_uri raise ArgumentError, "no HTTP request path given" unless @path else @uri = nil - host = nil raise ArgumentError, "no HTTP request path given" unless uri_or_path raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? @path = uri_or_path.dup @@ -51,7 +48,7 @@ class Gem::Net::HTTPGenericRequest initialize_http_header initheader self['Accept'] ||= '*/*' self['User-Agent'] ||= 'Ruby' - self['Host'] ||= host if host + self['Host'] ||= @uri.authority if @uri @body = nil @body_stream = nil @body_data = nil @@ -102,6 +99,31 @@ class Gem::Net::HTTPGenericRequest "\#<#{self.class} #{@method}>" end + # Returns a string representation of the request with the details for pp: + # + # require 'pp' + # post = Gem::Net::HTTP::Post.new(uri) + # post.inspect # => "#<Gem::Net::HTTP::Post POST>" + # post.pretty_inspect + # # => #<Gem::Net::HTTP::Post + # POST + # path="/" + # headers={"accept-encoding" => ["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], + # "accept" => ["*/*"], + # "user-agent" => ["Ruby"], + # "host" => ["www.ruby-lang.org"]}> + # + def pretty_print(q) + q.object_group(self) { + q.breakable + q.text @method + q.breakable + q.text "path="; q.pp @path + q.breakable + q.text "headers="; q.pp to_hash + } + end + ## # Don't automatically decode response content-encoding if the user indicates # they want to handle it. @@ -220,7 +242,7 @@ class Gem::Net::HTTPGenericRequest end if host = self['host'] - host.sub!(/:.*/m, '') + host = Gem::URI.parse("//#{host}").host # Remove a port component from the existing Host header elsif host = @uri.host else host = addr @@ -239,6 +261,8 @@ class Gem::Net::HTTPGenericRequest private + # :stopdoc: + class Chunker #:nodoc: def initialize(sock) @sock = sock @@ -260,7 +284,6 @@ class Gem::Net::HTTPGenericRequest def send_request_with_body(sock, ver, path, body) self.content_length = body.bytesize delete 'Transfer-Encoding' - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout sock.write body @@ -271,7 +294,6 @@ class Gem::Net::HTTPGenericRequest raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" end - supply_default_content_type write_header sock, ver, path wait_for_continue sock, ver if sock.continue_timeout if chunked? @@ -373,12 +395,6 @@ class Gem::Net::HTTPGenericRequest buf.clear end - def supply_default_content_type - return if content_type() - warn 'net/http: Content-Type did not set; using application/x-www-form-urlencoded', uplevel: 1 if $VERBOSE - set_content_type 'application/x-www-form-urlencoded' - end - ## # Waits up to the continue timeout for a response from the server provided # we're speaking HTTP 1.1 and are expecting a 100-continue response. @@ -411,4 +427,3 @@ class Gem::Net::HTTPGenericRequest end end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/header.rb b/lib/rubygems/vendor/net-http/lib/net/http/header.rb index 1488e60068..bc68cd2eef 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/header.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/header.rb @@ -179,7 +179,9 @@ # - #each_value: Passes each string field value to the block. # module Gem::Net::HTTPHeader + # The maximum length of HTTP header keys. MAX_KEY_LENGTH = 1024 + # The maximum length of HTTP header values. MAX_FIELD_LENGTH = 65536 def initialize_http_header(initheader) #:nodoc: @@ -267,6 +269,7 @@ module Gem::Net::HTTPHeader end end + # :stopdoc: private def set_field(key, val) case val when Enumerable @@ -294,6 +297,7 @@ module Gem::Net::HTTPHeader ary.push val end end + # :startdoc: # Returns the array field value for the given +key+, # or +nil+ if there is no such field; @@ -490,8 +494,8 @@ module Gem::Net::HTTPHeader alias canonical_each each_capitalized - def capitalize(name) - name.to_s.split(/-/).map {|s| s.capitalize }.join('-') + def capitalize(name) # :nodoc: + name.to_s.split('-'.freeze).map {|s| s.capitalize }.join('-'.freeze) end private :capitalize @@ -957,12 +961,12 @@ module Gem::Net::HTTPHeader @header['proxy-authorization'] = [basic_encode(account, password)] end - def basic_encode(account, password) + def basic_encode(account, password) # :nodoc: 'Basic ' + ["#{account}:#{password}"].pack('m0') end private :basic_encode -# Returns whether the HTTP session is to be closed. + # Returns whether the HTTP session is to be closed. def connection_close? token = /(?:\A|,)\s*close\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} @@ -970,7 +974,7 @@ module Gem::Net::HTTPHeader false end -# Returns whether the HTTP session is to be kept alive. + # Returns whether the HTTP session is to be kept alive. def connection_keep_alive? token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i @header['connection']&.grep(token) {return true} diff --git a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb index 1a57ddc7c2..f990761042 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/requests.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/requests.rb @@ -29,6 +29,7 @@ # - Gem::Net::HTTP#get: sends +GET+ request, returns response object. # class Gem::Net::HTTP::Get < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'GET' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -60,6 +61,7 @@ end # - Gem::Net::HTTP#head: sends +HEAD+ request, returns response object. # class Gem::Net::HTTP::Head < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'HEAD' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = false @@ -95,6 +97,7 @@ end # - Gem::Net::HTTP#post: sends +POST+ request, returns response object. # class Gem::Net::HTTP::Post < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'POST' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -124,7 +127,13 @@ end # - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes. # - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no. # +# Related: +# +# - Gem::Net::HTTP.put: sends +PUT+ request, returns response object. +# - Gem::Net::HTTP#put: sends +PUT+ request, returns response object. +# class Gem::Net::HTTP::Put < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PUT' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -157,6 +166,7 @@ end # - Gem::Net::HTTP#delete: sends +DELETE+ request, returns response object. # class Gem::Net::HTTP::Delete < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'DELETE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -188,6 +198,7 @@ end # - Gem::Net::HTTP#options: sends +OPTIONS+ request, returns response object. # class Gem::Net::HTTP::Options < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'OPTIONS' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -219,6 +230,7 @@ end # - Gem::Net::HTTP#trace: sends +TRACE+ request, returns response object. # class Gem::Net::HTTP::Trace < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'TRACE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -253,6 +265,7 @@ end # - Gem::Net::HTTP#patch: sends +PATCH+ request, returns response object. # class Gem::Net::HTTP::Patch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -280,6 +293,7 @@ end # - Gem::Net::HTTP#propfind: sends +PROPFIND+ request, returns response object. # class Gem::Net::HTTP::Propfind < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPFIND' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -303,6 +317,7 @@ end # - Gem::Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object. # class Gem::Net::HTTP::Proppatch < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'PROPPATCH' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -326,6 +341,7 @@ end # - Gem::Net::HTTP#mkcol: sends +MKCOL+ request, returns response object. # class Gem::Net::HTTP::Mkcol < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MKCOL' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -349,6 +365,7 @@ end # - Gem::Net::HTTP#copy: sends +COPY+ request, returns response object. # class Gem::Net::HTTP::Copy < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'COPY' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -372,6 +389,7 @@ end # - Gem::Net::HTTP#move: sends +MOVE+ request, returns response object. # class Gem::Net::HTTP::Move < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'MOVE' REQUEST_HAS_BODY = false RESPONSE_HAS_BODY = true @@ -395,6 +413,7 @@ end # - Gem::Net::HTTP#lock: sends +LOCK+ request, returns response object. # class Gem::Net::HTTP::Lock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'LOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true @@ -418,8 +437,8 @@ end # - Gem::Net::HTTP#unlock: sends +UNLOCK+ request, returns response object. # class Gem::Net::HTTP::Unlock < Gem::Net::HTTPRequest + # :stopdoc: METHOD = 'UNLOCK' REQUEST_HAS_BODY = true RESPONSE_HAS_BODY = true end - diff --git a/lib/rubygems/vendor/net-http/lib/net/http/response.rb b/lib/rubygems/vendor/net-http/lib/net/http/response.rb index cbbd191d87..dc164f1504 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/response.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/response.rb @@ -153,6 +153,7 @@ class Gem::Net::HTTPResponse end private + # :stopdoc: def read_status_line(sock) str = sock.readline @@ -259,7 +260,7 @@ class Gem::Net::HTTPResponse # header. attr_accessor :ignore_eof - def inspect + def inspect # :nodoc: "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" end diff --git a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb index 0f26ae6c26..62ce1cba1b 100644 --- a/lib/rubygems/vendor/net-http/lib/net/http/responses.rb +++ b/lib/rubygems/vendor/net-http/lib/net/http/responses.rb @@ -4,7 +4,9 @@ module Gem::Net + # Unknown HTTP response class HTTPUnknownResponse < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -19,6 +21,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response]. # class HTTPInformation < HTTPResponse + # :stopdoc: HAS_BODY = false EXCEPTION_TYPE = HTTPError # end @@ -34,6 +37,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success]. # class HTTPSuccess < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPError # end @@ -49,6 +53,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection]. # class HTTPRedirection < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPRetriableError # end @@ -63,6 +68,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors]. # class HTTPClientError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPClientException # end @@ -77,6 +83,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors]. # class HTTPServerError < HTTPResponse + # :stopdoc: HAS_BODY = true EXCEPTION_TYPE = HTTPFatalError # end @@ -94,6 +101,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100]. # class HTTPContinue < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -111,6 +119,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101]. # class HTTPSwitchProtocol < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -127,6 +136,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102]. # class HTTPProcessing < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -145,6 +155,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103]. # class HTTPEarlyHints < HTTPInformation + # :stopdoc: HAS_BODY = false end @@ -162,6 +173,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200]. # class HTTPOK < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -179,6 +191,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201]. # class HTTPCreated < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -196,6 +209,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202]. # class HTTPAccepted < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -215,6 +229,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203]. # class HTTPNonAuthoritativeInformation < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -232,6 +247,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204]. # class HTTPNoContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -250,6 +266,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205]. # class HTTPResetContent < HTTPSuccess + # :stopdoc: HAS_BODY = false end @@ -268,6 +285,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206]. # class HTTPPartialContent < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -285,6 +303,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207]. # class HTTPMultiStatus < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -304,6 +323,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208]. # class HTTPAlreadyReported < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -321,6 +341,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226]. # class HTTPIMUsed < HTTPSuccess + # :stopdoc: HAS_BODY = true end @@ -338,6 +359,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300]. # class HTTPMultipleChoices < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMultipleChoice = HTTPMultipleChoices @@ -356,6 +378,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301]. # class HTTPMovedPermanently < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -373,6 +396,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302]. # class HTTPFound < HTTPRedirection + # :stopdoc: HAS_BODY = true end HTTPMovedTemporarily = HTTPFound @@ -390,6 +414,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303]. # class HTTPSeeOther < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -407,6 +432,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304]. # class HTTPNotModified < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -423,6 +449,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305]. # class HTTPUseProxy < HTTPRedirection + # :stopdoc: HAS_BODY = false end @@ -440,6 +467,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307]. # class HTTPTemporaryRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -456,6 +484,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308]. # class HTTPPermanentRedirect < HTTPRedirection + # :stopdoc: HAS_BODY = true end @@ -472,6 +501,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400]. # class HTTPBadRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -488,6 +518,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401]. # class HTTPUnauthorized < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -504,6 +535,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402]. # class HTTPPaymentRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -521,6 +553,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403]. # class HTTPForbidden < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -537,6 +570,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404]. # class HTTPNotFound < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -553,6 +587,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405]. # class HTTPMethodNotAllowed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -570,6 +605,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406]. # class HTTPNotAcceptable < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -586,6 +622,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407]. # class HTTPProxyAuthenticationRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -602,6 +639,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408]. # class HTTPRequestTimeout < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestTimeOut = HTTPRequestTimeout @@ -619,6 +657,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409]. # class HTTPConflict < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -636,6 +675,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410]. # class HTTPGone < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -653,6 +693,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411]. # class HTTPLengthRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -670,6 +711,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412]. # class HTTPPreconditionFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -686,6 +728,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413]. # class HTTPPayloadTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestEntityTooLarge = HTTPPayloadTooLarge @@ -703,6 +746,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414]. # class HTTPURITooLong < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestURITooLong = HTTPURITooLong @@ -721,6 +765,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415]. # class HTTPUnsupportedMediaType < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -737,6 +782,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416]. # class HTTPRangeNotSatisfiable < HTTPClientError + # :stopdoc: HAS_BODY = true end HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable @@ -754,6 +800,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417]. # class HTTPExpectationFailed < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -774,6 +821,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421]. # class HTTPMisdirectedRequest < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -790,6 +838,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422]. # class HTTPUnprocessableEntity < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -805,6 +854,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423]. # class HTTPLocked < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -821,6 +871,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424]. # class HTTPFailedDependency < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -840,6 +891,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426]. # class HTTPUpgradeRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -856,6 +908,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428]. # class HTTPPreconditionRequired < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -872,6 +925,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429]. # class HTTPTooManyRequests < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -889,6 +943,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431]. # class HTTPRequestHeaderFieldsTooLarge < HTTPClientError + # :stopdoc: HAS_BODY = true end @@ -906,6 +961,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451]. # class HTTPUnavailableForLegalReasons < HTTPClientError + # :stopdoc: HAS_BODY = true end # 444 No Response - Nginx @@ -926,6 +982,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500]. # class HTTPInternalServerError < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -943,6 +1000,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501]. # class HTTPNotImplemented < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -960,6 +1018,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502]. # class HTTPBadGateway < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -977,6 +1036,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503]. # class HTTPServiceUnavailable < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -994,6 +1054,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504]. # class HTTPGatewayTimeout < HTTPServerError + # :stopdoc: HAS_BODY = true end HTTPGatewayTimeOut = HTTPGatewayTimeout @@ -1011,6 +1072,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505]. # class HTTPVersionNotSupported < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1027,6 +1089,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506]. # class HTTPVariantAlsoNegotiates < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1043,6 +1106,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507]. # class HTTPInsufficientStorage < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1059,6 +1123,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508]. # class HTTPLoopDetected < HTTPServerError + # :stopdoc: HAS_BODY = true end # 509 Bandwidth Limit Exceeded - Apache bw/limited extension @@ -1076,6 +1141,7 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510]. # class HTTPNotExtended < HTTPServerError + # :stopdoc: HAS_BODY = true end @@ -1092,19 +1158,21 @@ module Gem::Net # - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511]. # class HTTPNetworkAuthenticationRequired < HTTPServerError + # :stopdoc: HAS_BODY = true end end class Gem::Net::HTTPResponse + # :stopdoc: CODE_CLASS_TO_OBJ = { '1' => Gem::Net::HTTPInformation, '2' => Gem::Net::HTTPSuccess, '3' => Gem::Net::HTTPRedirection, '4' => Gem::Net::HTTPClientError, '5' => Gem::Net::HTTPServerError - } + }.freeze CODE_TO_OBJ = { '100' => Gem::Net::HTTPContinue, '101' => Gem::Net::HTTPSwitchProtocol, @@ -1170,5 +1238,5 @@ class Gem::Net::HTTPResponse '508' => Gem::Net::HTTPLoopDetected, '510' => Gem::Net::HTTPNotExtended, '511' => Gem::Net::HTTPNetworkAuthenticationRequired, - } + }.freeze end diff --git a/lib/rubygems/vendor/net-protocol/.document b/lib/rubygems/vendor/net-protocol/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/net-protocol/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/optparse/.document b/lib/rubygems/vendor/optparse/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/optparse/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/optparse/lib/optparse.rb b/lib/rubygems/vendor/optparse/lib/optparse.rb index 00dc7c8a67..d39d9dd4e0 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse.rb @@ -7,7 +7,7 @@ # # See Gem::OptionParser for documentation. # - +require 'set' unless defined?(Set) #-- # == Developer Documentation (not for RDoc output) @@ -143,7 +143,7 @@ # Used: # # $ ruby optparse-test.rb -r -# optparse-test.rb:9:in `<main>': missing argument: -r (Gem::OptionParser::MissingArgument) +# optparse-test.rb:9:in '<main>': missing argument: -r (Gem::OptionParser::MissingArgument) # $ ruby optparse-test.rb -r my-library # You required my-library! # @@ -236,7 +236,7 @@ # $ ruby optparse-test.rb --user 2 # #<struct User id=2, name="Gandalf"> # $ ruby optparse-test.rb --user 3 -# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError) +# optparse-test.rb:15:in 'block in find_user': No User Found for id 3 (RuntimeError) # # === Store options to a Hash # @@ -425,7 +425,9 @@ # If you have any questions, file a ticket at http://bugs.ruby-lang.org. # class Gem::OptionParser - Gem::OptionParser::Version = "0.4.0" + # The version string + VERSION = "0.8.0" + Version = VERSION # for compatibility # :stopdoc: NoArgument = [NO_ARGUMENT = :NONE, nil].freeze @@ -438,6 +440,8 @@ class Gem::OptionParser # and resolved against a list of acceptable values. # module Completion + # :nodoc: + def self.regexp(key, icase) Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase) end @@ -459,7 +463,11 @@ class Gem::OptionParser candidates end - def candidate(key, icase = false, pat = nil) + def self.completable?(key) + String.try_convert(key) or defined?(key.id2name) + end + + def candidate(key, icase = false, pat = nil, &_) Completion.candidate(key, icase, pat, &method(:each)) end @@ -494,7 +502,6 @@ class Gem::OptionParser end end - # # Map from option/keyword string to object with completion. # @@ -502,7 +509,6 @@ class Gem::OptionParser include Completion end - # # Individual switch class. Not important to the user. # @@ -510,6 +516,8 @@ class Gem::OptionParser # RequiredArgument, etc. # class Switch + # :nodoc: + attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block # @@ -542,11 +550,11 @@ class Gem::OptionParser def initialize(pattern = nil, conv = nil, short = nil, long = nil, arg = nil, - desc = ([] if short or long), block = nil, &_block) + desc = ([] if short or long), block = nil, values = nil, &_block) raise if Array === pattern block ||= _block - @pattern, @conv, @short, @long, @arg, @desc, @block = - pattern, conv, short, long, arg, desc, block + @pattern, @conv, @short, @long, @arg, @desc, @block, @values = + pattern, conv, short, long, arg, desc, block, values end # @@ -579,11 +587,15 @@ class Gem::OptionParser # exception. # def conv_arg(arg, val = []) # :nodoc: + v, = *val if conv val = conv.call(*val) else val = proc {|v| v}.call(*val) end + if @values + @values.include?(val) or raise InvalidArgument, v + end return arg, block, val end private :conv_arg @@ -664,7 +676,7 @@ class Gem::OptionParser (sopts+lopts).each do |opt| # "(-x -c -r)-l[left justify]" - if /^--\[no-\](.+)$/ =~ opt + if /\A--\[no-\](.+)$/ =~ opt o = $1 yield("--#{o}", desc.join("")) yield("--no-#{o}", desc.join("")) @@ -697,6 +709,11 @@ class Gem::OptionParser q.object_group(self) {pretty_print_contents(q)} end + def omitted_argument(val) # :nodoc: + val.pop if val.size == 3 and val.last.nil? + val + end + # # Switch that takes no arguments. # @@ -710,10 +727,10 @@ class Gem::OptionParser conv_arg(arg) end - def self.incompatible_argument_styles(*) + def self.incompatible_argument_styles(*) # :nodoc: end - def self.pattern + def self.pattern # :nodoc: Object end @@ -730,7 +747,7 @@ class Gem::OptionParser # # Raises an exception if argument is not present. # - def parse(arg, argv) + def parse(arg, argv, &_) unless arg raise MissingArgument if argv.empty? arg = argv.shift @@ -755,7 +772,7 @@ class Gem::OptionParser if arg conv_arg(*parse_arg(arg, &error)) else - conv_arg(arg) + omitted_argument conv_arg(arg) end end @@ -774,13 +791,14 @@ class Gem::OptionParser # def parse(arg, argv, &error) if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0])) - return nil, block, nil + return nil, block end opt = (val = parse_arg(val, &error))[1] val = conv_arg(*val) if opt and !arg argv.shift else + omitted_argument val val[0] = nil end val @@ -798,6 +816,8 @@ class Gem::OptionParser # matching pattern and converter pair. Also provides summary feature. # class List + # :nodoc: + # Map from acceptable argument types to pattern and converter pairs. attr_reader :atype @@ -837,7 +857,7 @@ class Gem::OptionParser def accept(t, pat = /.*/m, &block) if pat pat.respond_to?(:match) or - raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2)) + raise TypeError, "has no 'match'", ParseError.filter_backtrace(caller(2)) else pat = t if t.respond_to?(:match) end @@ -1020,7 +1040,6 @@ class Gem::OptionParser DefaultList.short['-'] = Switch::NoArgument.new {} DefaultList.long[''] = Switch::NoArgument.new {throw :terminate} - COMPSYS_HEADER = <<'XXX' # :nodoc: typeset -A opt_args @@ -1033,11 +1052,31 @@ XXX to << "#compdef #{name}\n" to << COMPSYS_HEADER visit(:compsys, {}, {}) {|o, d| - to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n] + to << %Q[ "#{o}[#{d.gsub(/[\\\"\[\]]/, '\\\\\&')}]" \\\n] } to << " '*:file:_files' && return 0\n" end + def help_exit + if $stdout.tty? && (pager = ENV.values_at(*%w[RUBY_PAGER PAGER]).find {|e| e && !e.empty?}) + less = ENV["LESS"] + args = [{"LESS" => "#{less} -Fe"}, pager, "w"] + print = proc do |f| + f.puts help + rescue Errno::EPIPE + # pager terminated + end + if Process.respond_to?(:fork) and false + IO.popen("-") {|f| f ? Process.exec(*args, in: f) : print.call($stdout)} + # unreachable + end + IO.popen(*args, &print) + else + puts help + end + exit + end + # # Default options for ARGV, which never appear in option summary. # @@ -1049,8 +1088,7 @@ XXX # Officious['help'] = proc do |parser| Switch::NoArgument.new do |arg| - puts parser.help - exit + parser.help_exit end end @@ -1071,7 +1109,7 @@ XXX # Officious['*-completion-zsh'] = proc do |parser| Switch::OptionalArgument.new do |arg| - parser.compsys(STDOUT, arg) + parser.compsys($stdout, arg) exit end end @@ -1129,6 +1167,10 @@ XXX default.to_i + 1 end end + + # + # See self.inc + # def inc(*args) self.class.inc(*args) end @@ -1167,11 +1209,19 @@ XXX def terminate(arg = nil) self.class.terminate(arg) end + # + # See #terminate. + # def self.terminate(arg = nil) throw :terminate, arg end @stack = [DefaultList] + # + # Returns the global top option list. + # + # Do not use directly. + # def self.top() DefaultList end # @@ -1192,9 +1242,9 @@ XXX # # Directs to reject specified class argument. # - # +t+:: Argument class specifier, any object including Class. + # +type+:: Argument class specifier, any object including Class. # - # reject(t) + # reject(type) # def reject(*args, &blk) top.reject(*args, &blk) end # @@ -1245,7 +1295,15 @@ XXX # to $0. # def program_name - @program_name || File.basename($0, '.*') + @program_name || strip_ext(File.basename($0)) + end + + private def strip_ext(name) # :nodoc: + exts = /#{ + require "rbconfig" + Regexp.union(*RbConfig::CONFIG["EXECUTABLE_EXTS"]&.split(" ")) + }\z/o + name.sub(exts, "") end # for experimental cascading :-) @@ -1284,10 +1342,24 @@ XXX end end + # + # Shows warning message with the program name + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#warn. + # def warn(mesg = $!) super("#{program_name}: #{mesg}") end + # + # Shows message with the program name then aborts. + # + # +mesg+:: Message, defaulted to +$!+. + # + # See Kernel#abort. + # def abort(mesg = $!) super("#{program_name}: #{mesg}") end @@ -1309,6 +1381,9 @@ XXX # # Pushes a new List. # + # If a block is given, yields +self+ and returns the result of the + # block, otherwise returns +self+. + # def new @stack.push(List.new) if block_given? @@ -1407,6 +1482,7 @@ XXX klass = nil q, a = nil has_arg = false + values = nil opts.each do |o| # argument class @@ -1420,7 +1496,7 @@ XXX end # directly specified pattern(any object possible to match) - if (!(String === o || Symbol === o)) and o.respond_to?(:match) + if !Completion.completable?(o) and o.respond_to?(:match) pattern = notwice(o, pattern, 'pattern') if pattern.respond_to?(:convert) conv = pattern.method(:convert).to_proc @@ -1434,7 +1510,12 @@ XXX case o when Proc, Method block = notwice(o, block, 'block') - when Array, Hash + when Array, Hash, Set + if Array === o + o, v = o.partition {|v,| Completion.completable?(v)} + values = notwice(v, values, 'values') unless v.empty? + next if o.empty? + end case pattern when CompletingHash when nil @@ -1444,11 +1525,13 @@ XXX raise ArgumentError, "argument pattern given twice" end o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}} + when Range + values = notwice(o, values, 'values') when Module raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4)) when *ArgumentStyle.keys style = notwice(ArgumentStyle[o], style, 'style') - when /^--no-([^\[\]=\s]*)(.+)?/ + when /\A--no-([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') not_pattern, not_conv = search(:atype, o) unless not_style @@ -1459,7 +1542,7 @@ XXX (q = q.downcase).tr!('_', '-') long << "no-#{q}" nolong << q - when /^--\[no-\]([^\[\]=\s]*)(.+)?/ + when /\A--\[no-\]([^\[\]=\s]*)(.+)?/ q, a = $1, $2 o = notwice(a ? Object : TrueClass, klass, 'type') if a @@ -1472,7 +1555,7 @@ XXX not_pattern, not_conv = search(:atype, FalseClass) unless not_style not_style = Switch::NoArgument nolong << "no-#{o}" - when /^--([^\[\]=\s]*)(.+)?/ + when /\A--([^\[\]=\s]*)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1482,7 +1565,7 @@ XXX ldesc << "--#{q}" (o = q.downcase).tr!('_', '-') long << o - when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ + when /\A-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/ q, a = $1, $2 o = notwice(Object, klass, 'type') if a @@ -1493,7 +1576,7 @@ XXX end sdesc << "-#{q}" short << Regexp.new(q) - when /^-(.)(.+)?/ + when /\A-(.)(.+)?/ q, a = $1, $2 if a o = notwice(NilClass, klass, 'type') @@ -1502,7 +1585,7 @@ XXX end sdesc << "-#{q}" short << q - when /^=/ + when /\A=/ style = notwice(default_style.guess(arg = o), style, 'style') default_pattern, conv = search(:atype, Object) unless default_pattern else @@ -1511,12 +1594,18 @@ XXX end default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern + if Range === values and klass + unless (!values.begin or klass === values.begin) and + (!values.end or klass === values.end) + raise ArgumentError, "range does not match class" + end + end if !(short.empty? and long.empty?) if has_arg and default_style == Switch::NoArgument default_style = Switch::RequiredArgument end s = (style || default_style).new(pattern || default_pattern, - conv, sdesc, ldesc, arg, desc, block) + conv, sdesc, ldesc, arg, desc, block, values) elsif !block if style or pattern raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller) @@ -1525,13 +1614,19 @@ XXX else short << pattern s = (style || default_style).new(pattern, - conv, nil, nil, arg, desc, block) + conv, nil, nil, arg, desc, block, values) end return s, short, long, (not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style), nolong end + # ---- + # Option definition phase methods + # + # These methods are used to define options, or to construct an + # Gem::OptionParser instance in other words. + # :call-seq: # define(*params, &block) # @@ -1607,6 +1702,13 @@ XXX top.append(string, nil, nil) end + # ---- + # Arguments parse phase methods + # + # These methods parse +argv+, convert, and store the results by + # calling handlers. As these methods do not modify +self+, +self+ + # can be frozen. + # # Parses command line arguments +argv+ in order. When a block is given, # each non-option argument is yielded. When optional +into+ keyword @@ -1616,21 +1718,21 @@ XXX # # Returns the rest of +argv+ left unparsed. # - def order(*argv, into: nil, &nonopt) + def order(*argv, **keywords, &nonopt) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - order!(argv, into: into, &nonopt) + order!(argv, **keywords, &nonopt) end # # Same as #order, but removes switches destructively. # Non-option arguments remain in +argv+. # - def order!(argv = default_argv, into: nil, &nonopt) + def order!(argv = default_argv, into: nil, **keywords, &nonopt) setter = ->(name, val) {into[name.to_sym] = val} if into - parse_in_order(argv, setter, &nonopt) + parse_in_order(argv, setter, **keywords, &nonopt) end - def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc: + def parse_in_order(argv = default_argv, setter = nil, exact: require_exact, **, &nonopt) # :nodoc: opt, arg, val, rest = nil nonopt ||= proc {|a| throw :terminate, a} argv.unshift(arg) if arg = catch(:terminate) { @@ -1641,19 +1743,24 @@ XXX opt, rest = $1, $2 opt.tr!('_', '-') begin - sw, = complete(:long, opt, true) - if require_exact && !sw.long.include?(arg) - throw :terminate, arg unless raise_unknown - raise InvalidOption, arg + if exact + sw, = search(:long, opt) + else + sw, = complete(:long, opt, true) end rescue ParseError throw :terminate, arg unless raise_unknown raise $!.set_option(arg, true) + else + unless sw + throw :terminate, arg unless raise_unknown + raise InvalidOption, arg + end end begin opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)} - val = cb.call(val) if cb - setter.call(sw.switch_name, val) if setter + val = callback!(cb, 1, val) if cb + callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, rest) end @@ -1671,7 +1778,7 @@ XXX val = arg.delete_prefix('-') has_arg = true rescue InvalidOption - raise if require_exact + raise if exact # if no short options match, try completion with long # options. sw, = complete(:long, opt) @@ -1691,8 +1798,8 @@ XXX end begin argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-') - val = cb.call(val) if cb - setter.call(sw.switch_name, val) if setter + val = callback!(cb, 1, val) if cb + callback!(setter, 2, sw.switch_name, val) if setter rescue ParseError raise $!.set_option(arg, arg.length > 2) end @@ -1718,6 +1825,19 @@ XXX end private :parse_in_order + # Calls callback with _val_. + def callback!(cb, max_arity, *args) # :nodoc: + args.compact! + + if (size = args.size) < max_arity and cb.to_proc.lambda? + (arity = cb.arity) < 0 and arity = (1-arity) + arity = max_arity if arity > max_arity + args[arity - 1] = nil if arity > size + end + cb.call(*args) + end + private :callback! + # # Parses command line arguments +argv+ in permutation mode and returns # list of non-option arguments. When optional +into+ keyword @@ -1725,18 +1845,18 @@ XXX # <code>[]=</code> method (so it can be Hash, or OpenStruct, or other # similar object). # - def permute(*argv, into: nil) + def permute(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - permute!(argv, into: into) + permute!(argv, **keywords) end # # Same as #permute, but removes switches destructively. # Non-option arguments remain in +argv+. # - def permute!(argv = default_argv, into: nil) + def permute!(argv = default_argv, **keywords) nonopts = [] - order!(argv, into: into, &nonopts.method(:<<)) + order!(argv, **keywords) {|nonopt| nonopts << nonopt} argv[0, 0] = nonopts argv end @@ -1748,20 +1868,20 @@ XXX # values are stored there via <code>[]=</code> method (so it can be Hash, # or OpenStruct, or other similar object). # - def parse(*argv, into: nil) + def parse(*argv, **keywords) argv = argv[0].dup if argv.size == 1 and Array === argv[0] - parse!(argv, into: into) + parse!(argv, **keywords) end # # Same as #parse, but removes switches destructively. # Non-option arguments remain in +argv+. # - def parse!(argv = default_argv, into: nil) + def parse!(argv = default_argv, **keywords) if ENV.include?('POSIXLY_CORRECT') - order!(argv, into: into) + order!(argv, **keywords) else - permute!(argv, into: into) + permute!(argv, **keywords) end end @@ -1784,18 +1904,21 @@ XXX # # params[:bar] = "x" # --bar x # # params[:zot] = "z" # --zot Z # - def getopts(*args, symbolize_names: false) + def getopts(*args, symbolize_names: false, **keywords) argv = Array === args.first ? args.shift : default_argv single_options, *long_options = *args result = {} + setter = (symbolize_names ? + ->(name, val) {result[name.to_sym] = val} + : ->(name, val) {result[name] = val}) single_options.scan(/(.)(:)?/) do |opt, val| if val - result[opt] = nil + setter[opt, nil] define("-#{opt} VAL") else - result[opt] = false + setter[opt, false] define("-#{opt}") end end if single_options @@ -1804,16 +1927,16 @@ XXX arg, desc = arg.split(';', 2) opt, val = arg.split(':', 2) if val - result[opt] = val.empty? ? nil : val + setter[opt, (val unless val.empty?)] define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact) else - result[opt] = false + setter[opt, false] define("--#{opt}", *[desc].compact) end end - parse_in_order(argv, result.method(:[]=)) - symbolize_names ? result.transform_keys(&:to_sym) : result + parse_in_order(argv, setter, **keywords) + result end # @@ -1863,7 +1986,7 @@ XXX visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw} } exc = ambiguous ? AmbiguousOption : InvalidOption - raise exc.new(opt, additional: self.method(:additional_message).curry[typ]) + raise exc.new(opt, additional: proc {|o| additional_message(typ, o)}) end private :complete @@ -1881,6 +2004,9 @@ XXX DidYouMean.formatter.message_for(all_candidates & checker.correct(opt)) end + # + # Return candidates for +word+. + # def candidate(word) list = [] case word @@ -1922,26 +2048,34 @@ XXX # The optional +into+ keyword argument works exactly like that accepted in # method #parse. # - def load(filename = nil, into: nil) + def load(filename = nil, **keywords) unless filename basename = File.basename($0, '.*') - return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil + return true if load(File.expand_path("~/.options/#{basename}"), **keywords) rescue nil basename << ".options" + if !(xdg = ENV['XDG_CONFIG_HOME']) or xdg.empty? + # https://specifications.freedesktop.org/basedir-spec/latest/#variables + # + # If $XDG_CONFIG_HOME is either not set or empty, a default + # equal to $HOME/.config should be used. + xdg = ['~/.config', true] + end return [ - # XDG - ENV['XDG_CONFIG_HOME'], - '~/.config', + xdg, + *ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR), # Haiku - '~/config/settings', - ].any? {|dir| + ['~/config/settings', true], + ].any? {|dir, expand| next if !dir or dir.empty? - load(File.expand_path(basename, dir), into: into) rescue nil + filename = File.join(dir, basename) + filename = File.expand_path(filename) if expand + load(filename, **keywords) rescue nil } end begin - parse(*File.readlines(filename, chomp: true), into: into) + parse(*File.readlines(filename, chomp: true), **keywords) true rescue Errno::ENOENT, Errno::ENOTDIR false @@ -1954,10 +2088,10 @@ XXX # # +env+ defaults to the basename of the program. # - def environment(env = File.basename($0, '.*')) + def environment(env = File.basename($0, '.*'), **keywords) env = ENV[env] || ENV[env.upcase] or return require 'shellwords' - parse(*Shellwords.shellwords(env)) + parse(*Shellwords.shellwords(env), **keywords) end # @@ -2123,6 +2257,7 @@ XXX # Reason which caused the error. Reason = 'parse error' + # :nodoc: def initialize(*args, additional: nil) @additional = additional @arg0, = args @@ -2142,9 +2277,10 @@ XXX argv end + DIR = File.join(__dir__, '') def self.filter_backtrace(array) unless $DEBUG - array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~)) + array.delete_if {|bt| bt.start_with?(DIR)} end array end @@ -2273,19 +2409,19 @@ XXX # Parses +self+ destructively in order and returns +self+ containing the # rest arguments left unparsed. # - def order!(&blk) options.order!(self, &blk) end + def order!(**keywords, &blk) options.order!(self, **keywords, &blk) end # # Parses +self+ destructively in permutation mode and returns +self+ # containing the rest arguments left unparsed. # - def permute!() options.permute!(self) end + def permute!(**keywords) options.permute!(self, **keywords) end # # Parses +self+ destructively and returns +self+ containing the # rest arguments left unparsed. # - def parse!() options.parse!(self) end + def parse!(**keywords) options.parse!(self, **keywords) end # # Substitution of getopts is possible as follows. Also see @@ -2298,8 +2434,8 @@ XXX # rescue Gem::OptionParser::ParseError # end # - def getopts(*args, symbolize_names: false) - options.getopts(self, *args, symbolize_names: symbolize_names) + def getopts(*args, symbolize_names: false, **keywords) + options.getopts(self, *args, symbolize_names: symbolize_names, **keywords) end # @@ -2309,7 +2445,8 @@ XXX super obj.instance_eval {@optparse = nil} end - def initialize(*args) + + def initialize(*args) # :nodoc: super @optparse = nil end diff --git a/lib/rubygems/vendor/optparse/lib/optparse/ac.rb b/lib/rubygems/vendor/optparse/lib/optparse/ac.rb index e84d01bf91..28a5b1b33e 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse/ac.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse/ac.rb @@ -1,7 +1,11 @@ # frozen_string_literal: false require_relative '../optparse' +# +# autoconf-like options. +# class Gem::OptionParser::AC < Gem::OptionParser + # :stopdoc: private def _check_ac_args(name, block) @@ -14,6 +18,7 @@ class Gem::OptionParser::AC < Gem::OptionParser end ARG_CONV = proc {|val| val.nil? ? true : val} + private_constant :ARG_CONV def _ac_arg_enable(prefix, name, help_string, block) _check_ac_args(name, block) @@ -29,16 +34,27 @@ class Gem::OptionParser::AC < Gem::OptionParser enable end + # :startdoc: + public + # Define <tt>--enable</tt> / <tt>--disable</tt> style option + # + # Appears as <tt>--enable-<i>name</i></tt> in help message. def ac_arg_enable(name, help_string, &block) _ac_arg_enable("enable", name, help_string, block) end + # Define <tt>--enable</tt> / <tt>--disable</tt> style option + # + # Appears as <tt>--disable-<i>name</i></tt> in help message. def ac_arg_disable(name, help_string, &block) _ac_arg_enable("disable", name, help_string, block) end + # Define <tt>--with</tt> / <tt>--without</tt> style option + # + # Appears as <tt>--with-<i>name</i></tt> in help message. def ac_arg_with(name, help_string, &block) _check_ac_args(name, block) diff --git a/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb b/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb index 6987a5ed62..70762f033b 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse/kwargs.rb @@ -7,12 +7,17 @@ class Gem::OptionParser # # :include: ../../doc/optparse/creates_option.rdoc # - def define_by_keywords(options, meth, **opts) - meth.parameters.each do |type, name| + # Defines options which set in to _options_ for keyword parameters + # of _method_. + # + # Parameters for each keywords are given as elements of _params_. + # + def define_by_keywords(options, method, **params) + method.parameters.each do |type, name| case type when :key, :keyreq op, cl = *(type == :key ? %w"[ ]" : ["", ""]) - define("--#{name}=#{op}#{name.upcase}#{cl}", *opts[name]) do |o| + define("--#{name}=#{op}#{name.upcase}#{cl}", *params[name]) do |o| options[name] = o end end diff --git a/lib/rubygems/vendor/optparse/lib/optparse/version.rb b/lib/rubygems/vendor/optparse/lib/optparse/version.rb index 5d79e9db44..e39889ae87 100644 --- a/lib/rubygems/vendor/optparse/lib/optparse/version.rb +++ b/lib/rubygems/vendor/optparse/lib/optparse/version.rb @@ -2,6 +2,11 @@ # Gem::OptionParser internal utility class << Gem::OptionParser + # + # Shows version string in packages if Version is defined. + # + # +pkgs+:: package list + # def show_version(*pkgs) progname = ARGV.options.program_name result = false @@ -47,6 +52,8 @@ class << Gem::OptionParser result end + # :stopdoc: + def each_const(path, base = ::Object) path.split(/::|\//).inject(base) do |klass, name| raise NameError, path unless Module === klass @@ -68,4 +75,6 @@ class << Gem::OptionParser end end end + + # :startdoc: end diff --git a/lib/rubygems/vendor/resolv/.document b/lib/rubygems/vendor/resolv/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/resolv/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/resolv/lib/resolv.rb b/lib/rubygems/vendor/resolv/lib/resolv.rb index 0f5ded3b76..168df21f3e 100644 --- a/lib/rubygems/vendor/resolv/lib/resolv.rb +++ b/lib/rubygems/vendor/resolv/lib/resolv.rb @@ -1,13 +1,9 @@ # frozen_string_literal: true require 'socket' -require_relative '../../timeout/lib/timeout' +require_relative '../../../vendored_timeout' require 'io/wait' - -begin - require_relative '../../../vendored_securerandom' -rescue LoadError -end +require_relative '../../../vendored_securerandom' # Gem::Resolv is a thread-aware DNS resolver library written in Ruby. Gem::Resolv can # handle multiple DNS requests concurrently without blocking the entire Ruby @@ -37,7 +33,7 @@ end class Gem::Resolv - VERSION = "0.4.0" + VERSION = "0.6.2" ## # Looks up the first IP address for +name+. @@ -83,9 +79,22 @@ class Gem::Resolv ## # Creates a new Gem::Resolv using +resolvers+. + # + # If +resolvers+ is not given, a hash, or +nil+, uses a Hosts resolver and + # and a DNS resolver. If +resolvers+ is a hash, uses the hash as + # configuration for the DNS resolver. + + def initialize(resolvers=(arg_not_set = true; nil), use_ipv6: (keyword_not_set = true; nil)) + if !keyword_not_set && !arg_not_set + warn "Support for separate use_ipv6 keyword is deprecated, as it is ignored if an argument is provided. Do not provide a positional argument if using the use_ipv6 keyword argument.", uplevel: 1 + end - def initialize(resolvers=nil, use_ipv6: nil) - @resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))] + @resolvers = case resolvers + when Hash, nil + [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(resolvers || {}))] + else + resolvers + end end ## @@ -164,13 +173,16 @@ class Gem::Resolv class ResolvTimeout < Gem::Timeout::Error; end + WINDOWS = /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM || ::RbConfig::CONFIG['host_os'] =~ /mswin/ + private_constant :WINDOWS + ## # Gem::Resolv::Hosts is a hostname resolver that uses the system hosts file. class Hosts - if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and + if WINDOWS begin - require 'win32/resolv' + require 'win32/resolv' unless defined?(Win32::Resolv) DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL rescue LoadError end @@ -396,13 +408,15 @@ class Gem::Resolv # be a Gem::Resolv::IPv4 or Gem::Resolv::IPv6 def each_address(name) - each_resource(name, Resource::IN::A) {|resource| yield resource.address} if use_ipv6? each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address} end + each_resource(name, Resource::IN::A) {|resource| yield resource.address} end def use_ipv6? # :nodoc: + @config.lazy_initialize unless @config.instance_variable_get(:@initialized) + use_ipv6 = @config.use_ipv6? unless use_ipv6.nil? return use_ipv6 @@ -513,35 +527,40 @@ class Gem::Resolv def fetch_resource(name, typeclass) lazy_initialize - begin - requester = make_udp_requester + truncated = {} + requesters = {} + udp_requester = begin + make_udp_requester rescue Errno::EACCES # fall back to TCP end senders = {} + begin - @config.resolv(name) {|candidate, tout, nameserver, port| - requester ||= make_tcp_requester(nameserver, port) + @config.resolv(name) do |candidate, tout, nameserver, port| msg = Message.new msg.rd = 1 msg.add_question(candidate, typeclass) - unless sender = senders[[candidate, nameserver, port]] + + requester = requesters.fetch([nameserver, port]) do + if !truncated[candidate] && udp_requester + udp_requester + else + requesters[[nameserver, port]] = make_tcp_requester(nameserver, port) + end + end + + unless sender = senders[[candidate, requester, nameserver, port]] sender = requester.sender(msg, candidate, nameserver, port) next if !sender - senders[[candidate, nameserver, port]] = sender + senders[[candidate, requester, nameserver, port]] = sender end reply, reply_name = requester.request(sender, tout) case reply.rcode when RCode::NoError if reply.tc == 1 and not Requester::TCP === requester - requester.close # Retry via TCP: - requester = make_tcp_requester(nameserver, port) - senders = {} - # This will use TCP for all remaining candidates (assuming the - # current candidate does not already respond successfully via - # TCP). This makes sense because we already know the full - # response will not fit in an untruncated UDP packet. + truncated[candidate] = true redo else yield(reply, reply_name) @@ -552,9 +571,10 @@ class Gem::Resolv else raise Config::OtherResolvError.new(reply_name.to_s) end - } + end ensure - requester&.close + udp_requester&.close + requesters.each_value { |requester| requester&.close } end end @@ -569,6 +589,11 @@ class Gem::Resolv def make_tcp_requester(host, port) # :nodoc: return Requester::TCP.new(host, port) + rescue Errno::ECONNREFUSED + # Treat a refused TCP connection attempt to a nameserver like a timeout, + # as Gem::Resolv::DNS::Config#resolv considers ResolvTimeout exceptions as a + # hint to try the next nameserver: + raise ResolvTimeout end def extract_resources(msg, name, typeclass) # :nodoc: @@ -602,16 +627,10 @@ class Gem::Resolv } end - if defined? Gem::SecureRandom - def self.random(arg) # :nodoc: - begin - Gem::SecureRandom.random_number(arg) - rescue NotImplementedError - rand(arg) - end - end - else - def self.random(arg) # :nodoc: + def self.random(arg) # :nodoc: + begin + Gem::SecureRandom.random_number(arg) + rescue NotImplementedError rand(arg) end end @@ -643,8 +662,20 @@ class Gem::Resolv } end - def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: - begin + case RUBY_PLATFORM + when *[ + # https://www.rfc-editor.org/rfc/rfc6056.txt + # Appendix A. Survey of the Algorithms in Use by Some Popular Implementations + /freebsd/, /linux/, /netbsd/, /openbsd/, /solaris/, + /darwin/, # the same as FreeBSD + ] then + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + udpsock.bind(bind_host, 0) + end + else + # Sequential port assignment + def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc: + # Ephemeral port number range recommended by RFC 6056 port = random(1024..65535) udpsock.bind(bind_host, port) rescue Errno::EADDRINUSE, # POSIX @@ -967,13 +998,13 @@ class Gem::Resolv next unless keyword case keyword when 'nameserver' - nameserver.concat(args) + nameserver.concat(args.each(&:freeze)) when 'domain' next if args.empty? - search = [args[0]] + search = [args[0].freeze] when 'search' next if args.empty? - search = args + search = args.each(&:freeze) when 'options' args.each {|arg| case arg @@ -984,22 +1015,22 @@ class Gem::Resolv end } } - return { :nameserver => nameserver, :search => search, :ndots => ndots } + return { :nameserver => nameserver.freeze, :search => search.freeze, :ndots => ndots.freeze }.freeze end def Config.default_config_hash(filename="/etc/resolv.conf") if File.exist? filename - config_hash = Config.parse_resolv_conf(filename) + Config.parse_resolv_conf(filename) + elsif WINDOWS + require 'win32/resolv' unless defined?(Win32::Resolv) + search, nameserver = Win32::Resolv.get_resolv_info + config_hash = {} + config_hash[:nameserver] = nameserver if nameserver + config_hash[:search] = [search].flatten if search + config_hash else - if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - require 'win32/resolv' - search, nameserver = Win32::Resolv.get_resolv_info - config_hash = {} - config_hash[:nameserver] = nameserver if nameserver - config_hash[:search] = [search].flatten if search - end + {} end - config_hash || {} end def lazy_initialize @@ -1648,6 +1679,7 @@ class Gem::Resolv prev_index = @index save_index = nil d = [] + size = -1 while true raise DecodeError.new("limit exceeded") if @limit <= @index case @data.getbyte(@index) @@ -1668,7 +1700,10 @@ class Gem::Resolv end @index = idx else - d << self.get_label + l = self.get_label + d << l + size += 1 + l.string.bytesize + raise DecodeError.new("name label data exceed 255 octets") if size > 255 end end end @@ -1800,7 +1835,6 @@ class Gem::Resolv end end - ## # Base class for SvcParam. [RFC9460] @@ -2095,7 +2129,14 @@ class Gem::Resolv attr_reader :ttl - ClassHash = {} # :nodoc: + ClassHash = Module.new do + module_function + + def []=(type_class_value, klass) + type_value, class_value = type_class_value + Resource.const_set(:"Type#{type_value}_Class#{class_value}", klass) + end + end def encode_rdata(msg) # :nodoc: raise NotImplementedError.new @@ -2133,7 +2174,9 @@ class Gem::Resolv end def self.get_class(type_value, class_value) # :nodoc: - return ClassHash[[type_value, class_value]] || + cache = :"Type#{type_value}_Class#{class_value}" + + return (const_defined?(cache) && const_get(cache)) || Generic.create(type_value, class_value) end @@ -2499,7 +2542,6 @@ class Gem::Resolv attr_reader :altitude - def encode_rdata(msg) # :nodoc: msg.put_bytes(@version) msg.put_bytes(@ssize.scalar) @@ -2563,7 +2605,7 @@ class Gem::Resolv end ## - # Flags for this proprty: + # Flags for this property: # - Bit 0 : 0 = not critical, 1 = critical attr_reader :flags @@ -3439,4 +3481,3 @@ class Gem::Resolv AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ end - diff --git a/lib/rubygems/vendor/securerandom/.document b/lib/rubygems/vendor/securerandom/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/securerandom/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/securerandom/lib/random/formatter.rb b/lib/rubygems/vendor/securerandom/lib/random/formatter.rb deleted file mode 100644 index 3544033340..0000000000 --- a/lib/rubygems/vendor/securerandom/lib/random/formatter.rb +++ /dev/null @@ -1,373 +0,0 @@ -# -*- coding: us-ascii -*- -# frozen_string_literal: true - -# == \Random number formatter. -# -# Formats generated random numbers in many manners. When <tt>'random/formatter'</tt> -# is required, several methods are added to empty core module <tt>Gem::Random::Formatter</tt>, -# making them available as Random's instance and module methods. -# -# Standard library Gem::SecureRandom is also extended with the module, and the methods -# described below are available as a module methods in it. -# -# === Examples -# -# Generate random hexadecimal strings: -# -# require 'rubygems/vendor/securerandom/lib/random/formatter' -# -# prng = Random.new -# prng.hex(10) #=> "52750b30ffbc7de3b362" -# prng.hex(10) #=> "92b15d6c8dc4beb5f559" -# prng.hex(13) #=> "39b290146bea6ce975c37cfc23" -# # or just -# Random.hex #=> "1aed0c631e41be7f77365415541052ee" -# -# Generate random base64 strings: -# -# prng.base64(10) #=> "EcmTPZwWRAozdA==" -# prng.base64(10) #=> "KO1nIU+p9DKxGg==" -# prng.base64(12) #=> "7kJSM/MzBJI+75j8" -# Random.base64(4) #=> "bsQ3fQ==" -# -# Generate random binary strings: -# -# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301" -# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" -# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43" -# -# Generate alphanumeric strings: -# -# prng.alphanumeric(10) #=> "S8baxMJnPl" -# prng.alphanumeric(10) #=> "aOxAg8BAJe" -# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP" -# -# Generate UUIDs: -# -# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" -# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" -# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd" -# -# All methods are available in the standard library Gem::SecureRandom, too: -# -# Gem::SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf" - -module Gem::Random::Formatter - - # Generate a random binary string. - # - # The argument _n_ specifies the length of the result string. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in future. - # - # The result may contain any byte: "\x00" - "\xff". - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" - # # or - # prng = Random.new - # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" - def random_bytes(n=nil) - n = n ? n.to_int : 16 - gen_random(n) - end - - # Generate a random hexadecimal string. - # - # The argument _n_ specifies the length, in bytes, of the random number to be generated. - # The length of the resulting hexadecimal string is twice of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain 0-9 and a-f. - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" - # # or - # prng = Random.new - # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61" - def hex(n=nil) - random_bytes(n).unpack1("H*") - end - - # Generate a random base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" - # # or - # prng = Random.new - # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" - # - # See RFC 3548 for the definition of base64. - def base64(n=nil) - [random_bytes(n)].pack("m0") - end - - # Generate a random URL-safe base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The boolean argument _padding_ specifies the padding. - # If it is false or nil, padding is not generated. - # Otherwise padding is generated. - # By default, padding is not generated because "=" may be used as a URL delimiter. - # - # The result may contain A-Z, a-z, 0-9, "-" and "_". - # "=" is also used if _padding_ is true. - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" - # # or - # prng = Random.new - # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" - # - # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" - # prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" - # - # See RFC 3548 for the definition of URL-safe base64. - def urlsafe_base64(n=nil, padding=false) - s = [random_bytes(n)].pack("m0") - s.tr!("+/", "-_") - s.delete!("=") unless padding - s - end - - # Generate a random v4 UUID (Universally Unique IDentifier). - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" - # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" - # # or - # prng = Random.new - # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" - # - # The version 4 UUID is purely random (except the version). - # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. - # - # The result contains 122 random bits (15.25 random bytes). - # - # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID. - # - def uuid - ary = random_bytes(16).unpack("NnnnnN") - ary[2] = (ary[2] & 0x0fff) | 0x4000 - ary[3] = (ary[3] & 0x3fff) | 0x8000 - "%08x-%04x-%04x-%04x-%04x%08x" % ary - end - - alias uuid_v4 uuid - - # Generate a random v7 UUID (Universally Unique IDentifier). - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e" - # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5" - # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23" - # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31" - # # |<--sorted-->| |<----- random ---->| - # - # # or - # prng = Random.new - # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98" - # - # The version 7 UUID starts with the least significant 48 bits of a 64 bit - # Unix timestamp (milliseconds since the epoch) and fills the remaining bits - # with random data, excluding the version and variant bits. - # - # This allows version 7 UUIDs to be sorted by creation time. Time ordered - # UUIDs can be used for better database index locality of newly inserted - # records, which may have a significant performance benefit compared to random - # data inserts. - # - # The result contains 74 random bits (9.25 random bytes). - # - # Note that this method cannot be made reproducable because its output - # includes not only random bits but also timestamp. - # - # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/] - # for details of UUIDv7. - # - # ==== Monotonicity - # - # UUIDv7 has millisecond precision by default, so multiple UUIDs created - # within the same millisecond are not issued in monotonically increasing - # order. To create UUIDs that are time-ordered with sub-millisecond - # precision, up to 12 bits of additional timestamp may added with - # +extra_timestamp_bits+. The extra timestamp precision comes at the expense - # of random bits. Setting <tt>extra_timestamp_bits: 12</tt> provides ~244ns - # of precision, but only 62 random bits (7.75 random bytes). - # - # prng = Random.new - # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) } - # # => - # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a", - # "0188d4c7-13da-753b-83a5-7fb9b2afaeea", - # "0188d4c7-13da-754a-88ea-ac0baeedd8db", - # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"] - # # |<--- sorted --->| |<-- random --->| - # - # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) } - # # => - # ["0188d4c7-3333-7a95-850a-de6edb858f7e", - # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order - # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order - # "0188d4c7-3333-7af9-87c3-8f612edac82e"] - # # |<--- sorted -->||<---- random --->| - # - # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based - # on UTC, which excludes leap seconds and can rollback the clock. To avoid - # this, the system clock can synchronize with an NTP server configured to use - # a "leap smear" approach. NTP or PTP will also be needed to synchronize - # across distributed nodes. - # - # Counters and other mechanisms for stronger guarantees of monotonicity are - # not implemented. Applications with stricter requirements should follow - # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters] - # of the specification. - # - def uuid_v7(extra_timestamp_bits: 0) - case (extra_timestamp_bits = Integer(extra_timestamp_bits)) - when 0 # min timestamp precision - ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond) - rand = random_bytes(10) - rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version - rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant - "%08x-%04x-%s" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - rand.unpack("H4H4H12").join("-") - ] - - when 12 # max timestamp precision - ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) - .divmod(1_000_000) - extra_bits = ns * 4096 / 1_000_000 - rand = random_bytes(8) - rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant - "%08x-%04x-7%03x-%s" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - extra_bits, - rand.unpack("H4H12").join("-") - ] - - when (0..12) # the generic version is slower than the special cases above - rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN") - rand_mask_bits = 12 - extra_timestamp_bits - ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond) - .divmod(1_000_000) - "%08x-%04x-%04x-%04x-%04x%08x" % [ - (ms & 0x0000_ffff_ffff_0000) >> 16, - (ms & 0x0000_0000_0000_ffff), - 0x7000 | - ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) | - rand_a & ((1 << rand_mask_bits) - 1), - 0x8000 | (rand_b1 & 0x3fff), - rand_b2, - rand_b3 - ] - - else - raise ArgumentError, "extra_timestamp_bits must be in 0..12" - end - end - - # Internal interface to Random; Generate random data _n_ bytes. - private def gen_random(n) - self.bytes(n) - end - - # Generate a string that randomly draws from a - # source array of characters. - # - # The argument _source_ specifies the array of characters from which - # to generate the string. - # The argument _n_ specifies the length, in characters, of the string to be - # generated. - # - # The result may contain whatever characters are in the source array. - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" - # prng.choose([*'0'..'9'], 5) #=> "27309" - private def choose(source, n) - size = source.size - m = 1 - limit = size - while limit * size <= 0x100000000 - limit *= size - m += 1 - end - result = ''.dup - while m <= n - rs = random_number(limit) - is = rs.digits(size) - (m-is.length).times { is << 0 } - result << source.values_at(*is).join('') - n -= m - end - if 0 < n - rs = random_number(limit) - is = rs.digits(size) - if is.length < n - (n-is.length).times { is << 0 } - else - is.pop while n < is.length - end - result.concat source.values_at(*is).join('') - end - result - end - - # The default character list for #alphanumeric. - ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] - - # Generate a random alphanumeric string. - # - # The argument _n_ specifies the length, in characters, of the alphanumeric - # string to be generated. - # The argument _chars_ specifies the character list which the result is - # consist of. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified. - # - # require 'rubygems/vendor/securerandom/lib/random/formatter' - # - # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR" - # # or - # prng = Random.new - # prng.alphanumeric(10) #=> "i6K93NdqiH" - # - # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952" - # # or - # prng = Random.new - # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''." - def alphanumeric(n = nil, chars: ALPHANUMERIC) - n = 16 if n.nil? - choose(chars, n) - end -end diff --git a/lib/rubygems/vendor/securerandom/lib/securerandom.rb b/lib/rubygems/vendor/securerandom/lib/securerandom.rb index f83d2a74fc..b6f1d71ad3 100644 --- a/lib/rubygems/vendor/securerandom/lib/securerandom.rb +++ b/lib/rubygems/vendor/securerandom/lib/securerandom.rb @@ -1,7 +1,7 @@ # -*- coding: us-ascii -*- # frozen_string_literal: true -require_relative 'random/formatter' +require 'random/formatter' # == Secure random number generator interface. # @@ -18,7 +18,7 @@ require_relative 'random/formatter' # * /dev/urandom # * Win32 # -# Gem::SecureRandom is extended by the Gem::Random::Formatter module which +# Gem::SecureRandom is extended by the Random::Formatter module which # defines the following methods: # # * alphanumeric @@ -41,7 +41,7 @@ require_relative 'random/formatter' module Gem::SecureRandom # The version - VERSION = "0.3.1" + VERSION = "0.4.1" class << self # Returns a random binary string containing +size+ bytes. @@ -51,6 +51,12 @@ module Gem::SecureRandom return gen_random(n) end + # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end if RUBY_VERSION < '3.3' + private # :stopdoc: @@ -88,9 +94,9 @@ module Gem::SecureRandom # :startdoc: - # Generate random data bytes for Gem::Random::Formatter + # Generate random data bytes for Random::Formatter public :gen_random end end -Gem::SecureRandom.extend(Gem::Random::Formatter) +Gem::SecureRandom.extend(Random::Formatter) diff --git a/lib/rubygems/vendor/timeout/.document b/lib/rubygems/vendor/timeout/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/timeout/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/timeout/lib/timeout.rb b/lib/rubygems/vendor/timeout/lib/timeout.rb index df97d64ca0..376b8c0e2b 100644 --- a/lib/rubygems/vendor/timeout/lib/timeout.rb +++ b/lib/rubygems/vendor/timeout/lib/timeout.rb @@ -4,7 +4,7 @@ # == Synopsis # # require 'rubygems/vendor/timeout/lib/timeout' -# status = Gem::Timeout::timeout(5) { +# status = Gem::Timeout.timeout(5) { # # Something that should be interrupted if it takes more than 5 seconds... # } # @@ -13,28 +13,25 @@ # Gem::Timeout provides a way to auto-terminate a potentially long-running # operation if it hasn't finished in a fixed amount of time. # -# Previous versions didn't use a module for namespacing, however -# #timeout is provided for backwards compatibility. You -# should prefer Gem::Timeout.timeout instead. -# # == Copyright # # Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc. # Copyright:: (C) 2000 Information-technology Promotion Agency, Japan module Gem::Timeout - VERSION = "0.4.1" + # The version + VERSION = "0.4.4" # Internal error raised to when a timeout is triggered. class ExitException < Exception - def exception(*) + def exception(*) # :nodoc: self end end # Raised by Gem::Timeout.timeout when the block times out. class Error < RuntimeError - def self.handle_timeout(message) + def self.handle_timeout(message) # :nodoc: exc = ExitException.new(message) begin @@ -126,6 +123,9 @@ module Gem::Timeout def self.ensure_timeout_thread_created unless @timeout_thread and @timeout_thread.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the @timeout_thread. + return if TIMEOUT_THREAD_MUTEX.owned? TIMEOUT_THREAD_MUTEX.synchronize do unless @timeout_thread and @timeout_thread.alive? @timeout_thread = create_timeout_thread @@ -144,9 +144,10 @@ module Gem::Timeout # Perform an operation in a block, raising an error if it takes longer than # +sec+ seconds to complete. # - # +sec+:: Number of seconds to wait for the block to terminate. Any number - # may be used, including Floats to specify fractional seconds. A + # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number + # or nil may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. + # Any negative number will raise an ArgumentError. # +klass+:: Exception Class to raise if the block fails to terminate # in +sec+ seconds. Omitting will use the default, Gem::Timeout::Error # +message+:: Error message to raise with Exception Class. @@ -168,6 +169,7 @@ module Gem::Timeout # a module method, so you can call it directly as Gem::Timeout.timeout(). def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? + raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec message ||= "execution expired" diff --git a/lib/rubygems/vendor/tsort/.document b/lib/rubygems/vendor/tsort/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/tsort/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/uri/.document b/lib/rubygems/vendor/uri/.document deleted file mode 100644 index 0c43bbd6b3..0000000000 --- a/lib/rubygems/vendor/uri/.document +++ /dev/null @@ -1 +0,0 @@ -# Vendored files do not need to be documented diff --git a/lib/rubygems/vendor/uri/lib/uri.rb b/lib/rubygems/vendor/uri/lib/uri.rb index f1ccc167cc..4691b122b2 100644 --- a/lib/rubygems/vendor/uri/lib/uri.rb +++ b/lib/rubygems/vendor/uri/lib/uri.rb @@ -1,6 +1,6 @@ # frozen_string_literal: false # Gem::URI is a module providing classes to handle Uniform Resource Identifiers -# (RFC2396[http://tools.ietf.org/html/rfc2396]). +# (RFC2396[https://www.rfc-editor.org/rfc/rfc2396]). # # == Features # @@ -47,14 +47,14 @@ # A good place to view an RFC spec is http://www.ietf.org/rfc.html. # # Here is a list of all related RFC's: -# - RFC822[http://tools.ietf.org/html/rfc822] -# - RFC1738[http://tools.ietf.org/html/rfc1738] -# - RFC2255[http://tools.ietf.org/html/rfc2255] -# - RFC2368[http://tools.ietf.org/html/rfc2368] -# - RFC2373[http://tools.ietf.org/html/rfc2373] -# - RFC2396[http://tools.ietf.org/html/rfc2396] -# - RFC2732[http://tools.ietf.org/html/rfc2732] -# - RFC3986[http://tools.ietf.org/html/rfc3986] +# - RFC822[https://www.rfc-editor.org/rfc/rfc822] +# - RFC1738[https://www.rfc-editor.org/rfc/rfc1738] +# - RFC2255[https://www.rfc-editor.org/rfc/rfc2255] +# - RFC2368[https://www.rfc-editor.org/rfc/rfc2368] +# - RFC2373[https://www.rfc-editor.org/rfc/rfc2373] +# - RFC2396[https://www.rfc-editor.org/rfc/rfc2396] +# - RFC2732[https://www.rfc-editor.org/rfc/rfc2732] +# - RFC3986[https://www.rfc-editor.org/rfc/rfc3986] # # == Class tree # diff --git a/lib/rubygems/vendor/uri/lib/uri/common.rb b/lib/rubygems/vendor/uri/lib/uri/common.rb index da2651084c..e9bdfa6a07 100644 --- a/lib/rubygems/vendor/uri/lib/uri/common.rb +++ b/lib/rubygems/vendor/uri/lib/uri/common.rb @@ -13,26 +13,54 @@ require_relative "rfc2396_parser" require_relative "rfc3986_parser" module Gem::URI - include RFC2396_REGEXP + # The default parser instance for RFC 2396. + RFC2396_PARSER = RFC2396_Parser.new + Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) - REGEXP = RFC2396_REGEXP - Parser = RFC2396_Parser + # The default parser instance for RFC 3986. RFC3986_PARSER = RFC3986_Parser.new Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) - RFC2396_PARSER = RFC2396_Parser.new - Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) - # Gem::URI::Parser.new - DEFAULT_PARSER = Parser.new - DEFAULT_PARSER.pattern.each_pair do |sym, str| - unless REGEXP::PATTERN.const_defined?(sym) - REGEXP::PATTERN.const_set(sym, str) + # The default parser instance. + DEFAULT_PARSER = RFC3986_PARSER + Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) + + # Set the default parser instance. + def self.parser=(parser = RFC3986_PARSER) + remove_const(:Parser) if defined?(::Gem::URI::Parser) + const_set("Parser", parser.class) + + remove_const(:PARSER) if defined?(::Gem::URI::PARSER) + const_set("PARSER", parser) + + remove_const(:REGEXP) if defined?(::Gem::URI::REGEXP) + remove_const(:PATTERN) if defined?(::Gem::URI::PATTERN) + if Parser == RFC2396_Parser + const_set("REGEXP", Gem::URI::RFC2396_REGEXP) + const_set("PATTERN", Gem::URI::RFC2396_REGEXP::PATTERN) + end + + Parser.new.regexp.each_pair do |sym, str| + remove_const(sym) if const_defined?(sym, false) + const_set(sym, str) end end - DEFAULT_PARSER.regexp.each_pair do |sym, str| - const_set(sym, str) + self.parser = RFC3986_PARSER + + def self.const_missing(const) # :nodoc: + if const == :REGEXP + warn "Gem::URI::REGEXP is obsolete. Use Gem::URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE + Gem::URI::RFC2396_REGEXP + elsif value = RFC2396_PARSER.regexp[const] + warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + value + elsif value = RFC2396_Parser.const_get(const) + warn "Gem::URI::#{const} is obsolete. Use Gem::URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE + value + else + super + end end - Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) module Util # :nodoc: def make_components_hash(klass, array_hash) @@ -66,7 +94,41 @@ module Gem::URI module_function :make_components_hash end - module Schemes + module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -79,7 +141,7 @@ module Gem::URI # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) - Schemes.const_set(scheme.to_s.upcase, klass) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -97,14 +159,14 @@ module Gem::URI # # Related: Gem::URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -123,12 +185,10 @@ module Gem::URI # # => #<Gem::URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) uri_class = INITIAL_SCHEMES[const_name] - uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) - Schemes.const_get(const_name, false) - end + uri_class ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -170,7 +230,7 @@ module Gem::URI # ["fragment", "top"]] # def self.split(uri) - RFC3986_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \Gem::URI object constructed from the given string +uri+: @@ -180,11 +240,11 @@ module Gem::URI # Gem::URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => #<Gem::URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first Gem::URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid Gem::URI characters. # def self.parse(uri) - RFC3986_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given Gem::URI strings +str+ @@ -211,7 +271,7 @@ module Gem::URI # # => #<Gem::URI::HTTP http://example.com/foo/bar> # def self.join(*str) - RFC3986_PARSER.join(*str) + DEFAULT_PARSER.join(*str) end # @@ -240,7 +300,7 @@ module Gem::URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "Gem::URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -277,14 +337,14 @@ module Gem::URI # def self.regexp(schemes = nil)# :nodoc: warn "Gem::URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end - TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze + TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: @@ -382,6 +442,8 @@ module Gem::URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # Gem::URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -396,6 +458,8 @@ module Gem::URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) @@ -403,7 +467,7 @@ module Gem::URI private_class_method :_decode_uri_component # Returns a URL-encoded string derived from the given - # {Enumerable}[https://docs.ruby-lang.org/en/master/Enumerable.html#module-Enumerable-label-Enumerable+in+Ruby+Classes] + # {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes] # +enum+. # # The result is suitable for use as form data @@ -472,7 +536,7 @@ module Gem::URI # each +key+/+value+ pair is converted to one or more fields: # # - If +value+ is - # {Array-convertible}[https://docs.ruby-lang.org/en/master/implicit_conversion_rdoc.html#label-Array-Convertible+Objects], + # {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects], # each element +ele+ in +value+ is paired with +key+ to form a field: # # name = Gem::URI.encode_www_form_component(key, enc) @@ -530,7 +594,7 @@ module Gem::URI # each subarray is a name/value pair (both are strings). # Each returned string has encoding +enc+, # and has had invalid characters removed via - # {String#scrub}[https://docs.ruby-lang.org/en/master/String.html#method-i-scrub]. + # {String#scrub}[rdoc-ref:String#scrub]. # # A simple example: # @@ -834,6 +898,7 @@ module Gem # Returns a \Gem::URI object derived from the given +uri+, # which may be a \Gem::URI string or an existing \Gem::URI object: # + # require 'rubygems/vendor/uri/lib/uri' # # Returns a new Gem::URI. # uri = Gem::URI('http://github.com/ruby/ruby') # # => #<Gem::URI::HTTP http://github.com/ruby/ruby> @@ -841,6 +906,8 @@ module Gem # Gem::URI(uri) # # => #<Gem::URI::HTTP http://github.com/ruby/ruby> # + # You must require 'rubygems/vendor/uri/lib/uri' to use this method. + # def URI(uri) if uri.is_a?(Gem::URI::Generic) uri diff --git a/lib/rubygems/vendor/uri/lib/uri/file.rb b/lib/rubygems/vendor/uri/lib/uri/file.rb index d419b26055..391c499716 100644 --- a/lib/rubygems/vendor/uri/lib/uri/file.rb +++ b/lib/rubygems/vendor/uri/lib/uri/file.rb @@ -47,7 +47,7 @@ module Gem::URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # - # uri3 = Gem::URI::File.build({:path => Gem::URI::escape('/path/my file.txt')}) + # uri3 = Gem::URI::File.build({:path => Gem::URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) @@ -70,17 +70,17 @@ module Gem::URI # raise InvalidURIError def check_userinfo(user) - raise Gem::URI::InvalidURIError, "can not set userinfo for file Gem::URI" + raise Gem::URI::InvalidURIError, "cannot set userinfo for file Gem::URI" end # raise InvalidURIError def check_user(user) - raise Gem::URI::InvalidURIError, "can not set user for file Gem::URI" + raise Gem::URI::InvalidURIError, "cannot set user for file Gem::URI" end # raise InvalidURIError def check_password(user) - raise Gem::URI::InvalidURIError, "can not set password for file Gem::URI" + raise Gem::URI::InvalidURIError, "cannot set password for file Gem::URI" end # do nothing diff --git a/lib/rubygems/vendor/uri/lib/uri/ftp.rb b/lib/rubygems/vendor/uri/lib/uri/ftp.rb index 100498ffb2..7517813029 100644 --- a/lib/rubygems/vendor/uri/lib/uri/ftp.rb +++ b/lib/rubygems/vendor/uri/lib/uri/ftp.rb @@ -17,7 +17,7 @@ module Gem::URI # This class will be redesigned because of difference of implementations; # the structure of its path. draft-hoffman-ftp-uri-04 is a draft but it # is a good summary about the de facto spec. - # http://tools.ietf.org/html/draft-hoffman-ftp-uri-04 + # https://datatracker.ietf.org/doc/html/draft-hoffman-ftp-uri-04 # class FTP < Generic # A Default port of 21 for Gem::URI::FTP. diff --git a/lib/rubygems/vendor/uri/lib/uri/generic.rb b/lib/rubygems/vendor/uri/lib/uri/generic.rb index 72c52aa8ee..d0bc77dfda 100644 --- a/lib/rubygems/vendor/uri/lib/uri/generic.rb +++ b/lib/rubygems/vendor/uri/lib/uri/generic.rb @@ -73,7 +73,7 @@ module Gem::URI # # At first, tries to create a new Gem::URI::Generic instance using # Gem::URI::Generic::build. But, if exception Gem::URI::InvalidComponentError is raised, - # then it does Gem::URI::Escape.escape all Gem::URI components and tries again. + # then it does Gem::URI::RFC2396_PARSER.escape all Gem::URI components and tries again. # def self.build2(args) begin @@ -82,7 +82,7 @@ module Gem::URI if args.kind_of?(Array) return self.build(args.collect{|x| if x.is_a?(String) - DEFAULT_PARSER.escape(x) + Gem::URI::RFC2396_PARSER.escape(x) else x end @@ -91,7 +91,7 @@ module Gem::URI tmp = {} args.each do |key, value| tmp[key] = if value - DEFAULT_PARSER.escape(value) + Gem::URI::RFC2396_PARSER.escape(value) else value end @@ -126,9 +126,9 @@ module Gem::URI end end else - component = self.class.component rescue ::Gem::URI::Generic::COMPONENT + component = self.component rescue ::Gem::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module Gem::URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module Gem::URI # Returns the parser to be used. # - # Unless a Gem::URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module Gem::URI end # - # Checks the scheme +v+ component against the Gem::URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module Gem::URI # # Checks the user +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -393,7 +393,7 @@ module Gem::URI def check_user(v) if @opaque raise InvalidURIError, - "can not set user with opaque" + "cannot set user with opaque" end return v unless v @@ -409,7 +409,7 @@ module Gem::URI # # Checks the password +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -417,7 +417,7 @@ module Gem::URI def check_password(v, user = @user) if @opaque raise InvalidURIError, - "can not set password with opaque" + "cannot set password with opaque" end return v unless v @@ -511,7 +511,7 @@ module Gem::URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module Gem::URI # See also Gem::URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module Gem::URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after Gem::URI decoding. def decoded_user Gem::URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module Gem::URI # # Checks the host +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -596,7 +602,7 @@ module Gem::URI if @opaque raise InvalidURIError, - "can not set host with registry or opaque" + "cannot set host with registry or opaque" elsif parser.regexp[:HOST] !~ v raise InvalidComponentError, "bad component(expected host component): #{v}" @@ -615,6 +621,13 @@ module Gem::URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module Gem::URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module Gem::URI # # Checks the port +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -685,7 +699,7 @@ module Gem::URI if @opaque raise InvalidURIError, - "can not set port with registry or opaque" + "cannot set port with registry or opaque" elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v raise InvalidComponentError, "bad component(expected port component): #{v.inspect}" @@ -729,26 +743,27 @@ module Gem::URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end def check_registry(v) # :nodoc: - raise InvalidURIError, "can not set registry" + raise InvalidURIError, "cannot set registry" end private :check_registry - def set_registry(v) #:nodoc: - raise InvalidURIError, "can not set registry" + def set_registry(v) # :nodoc: + raise InvalidURIError, "cannot set registry" end protected :set_registry - def registry=(v) - raise InvalidURIError, "can not set registry" + def registry=(v) # :nodoc: + raise InvalidURIError, "cannot set registry" end # # Checks the path +v+ component for RFC2396 compliance - # and against the Gem::URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module Gem::URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the Gem::URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -866,7 +881,7 @@ module Gem::URI # hier_part = ( net_path | abs_path ) [ "?" query ] if @host || @port || @user || @path # userinfo = @user + ':' + @password raise InvalidURIError, - "can not set opaque with host, port, userinfo or path" + "cannot set opaque with host, port, userinfo or path" elsif v && parser.regexp[:OPAQUE] !~ v raise InvalidComponentError, "bad component(expected opaque component): #{v}" @@ -905,7 +920,7 @@ module Gem::URI end # - # Checks the fragment +v+ component against the Gem::URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -945,7 +960,7 @@ module Gem::URI # == Description # # Gem::URI has components listed in order of decreasing significance from left to right, - # see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3. + # see RFC3986 https://www.rfc-editor.org/rfc/rfc3986 1.2.3. # # == Usage # @@ -1121,7 +1136,7 @@ module Gem::URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1133,17 +1148,14 @@ module Gem::URI base.fragment=(nil) # RFC2396, Section 5.2, 4) - if !authority - base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path - else - # RFC2396, Section 5.2, 4) - base.set_path(rel.path) if rel.path + if authority + base.set_authority(*authority) + base.set_path(rel.path) + elsif base.path && rel.path + base.set_path(merge_path(base.path, rel.path)) end # RFC2396, Section 5.2, 7) - base.set_userinfo(rel.userinfo) if rel.userinfo - base.set_host(rel.host) if rel.host - base.set_port(rel.port) if rel.port base.query = rel.query if rel.query base.fragment=(rel.fragment) if rel.fragment @@ -1235,7 +1247,7 @@ module Gem::URI return rel, rel end - # you can modify `rel', but can not `oth'. + # you can modify `rel', but cannot `oth'. return oth, rel end private :route_from0 @@ -1260,7 +1272,7 @@ module Gem::URI # #=> #<Gem::URI::Generic /main.rbx?page=1> # def route_from(oth) - # you can modify `rel', but can not `oth'. + # you can modify `rel', but cannot `oth'. begin oth, rel = route_from0(oth) rescue @@ -1364,6 +1376,9 @@ module Gem::URI str << ':' str << @port.to_s end + if (@host || @port) && !@path.empty? && !@path.start_with?('/') + str << '/' + end str << @path if @query str << '?' @@ -1389,29 +1404,18 @@ module Gem::URI end end + # Returns the hash value. def hash self.component_ary.hash end + # Compares with _oth_ for Hash. def eql?(oth) self.class == oth.class && parser == oth.parser && self.component_ary.eql?(oth.component_ary) end -=begin - ---- Gem::URI::Generic#===(oth) - -=end -# def ===(oth) -# raise NotImplementedError -# end - -=begin -=end - - # Returns an Array of the components defined from the COMPONENT Array. def component_ary component.collect do |x| @@ -1448,7 +1452,7 @@ module Gem::URI end end - def inspect + def inspect # :nodoc: "#<#{self.class} #{self}>" end @@ -1536,7 +1540,7 @@ module Gem::URI else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/rubygems/vendor/uri/lib/uri/http.rb b/lib/rubygems/vendor/uri/lib/uri/http.rb index bef43490a3..99c78358ac 100644 --- a/lib/rubygems/vendor/uri/lib/uri/http.rb +++ b/lib/rubygems/vendor/uri/lib/uri/http.rb @@ -61,6 +61,18 @@ module Gem::URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # @@ -85,7 +97,7 @@ module Gem::URI # == Description # # Returns the authority for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2. + # https://www.rfc-editor.org/rfc/rfc3986#section-3.2. # # # Example: @@ -106,7 +118,7 @@ module Gem::URI # == Description # # Returns the origin for an HTTP uri, as defined in - # https://datatracker.ietf.org/doc/html/rfc6454. + # https://www.rfc-editor.org/rfc/rfc6454. # # # Example: diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb index 735a269f2c..2bb4181649 100644 --- a/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb +++ b/lib/rubygems/vendor/uri/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module Gem::URI # # == Synopsis # - # Gem::URI::Parser.new([opts]) + # Gem::URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module Gem::URI # # == Examples # - # p = Gem::URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = Gem::URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<Gem::URI::HTTP http://example.jp/%uABCD> # Gem::URI.parse(u.to_s) #=> raises Gem::URI::InvalidURIError # @@ -108,12 +108,12 @@ module Gem::URI # The Hash of patterns. # - # See also Gem::URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also Gem::URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split Gem::URI against +regexp[:ABS_URI]+. @@ -140,11 +140,11 @@ module Gem::URI if !scheme raise InvalidURIError, - "bad Gem::URI(absolute but no scheme): #{uri}" + "bad Gem::URI (absolute but no scheme): #{uri}" end if !opaque && (!path && (!host && !registry)) raise InvalidURIError, - "bad Gem::URI(absolute but no path): #{uri}" + "bad Gem::URI (absolute but no path): #{uri}" end when @regexp[:REL_URI] @@ -173,7 +173,7 @@ module Gem::URI # server = [ [ userinfo "@" ] hostport ] else - raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri}" + raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri}" end path = '' if !path && !opaque # (see RFC2396 Section 5.2) @@ -202,8 +202,7 @@ module Gem::URI # # == Usage # - # p = Gem::URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # Gem::URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<Gem::URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module Gem::URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also Gem::URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module Gem::URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -321,14 +320,14 @@ module Gem::URI str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } end - @@to_s = Kernel.instance_method(:to_s) - if @@to_s.respond_to?(:bind_call) - def inspect - @@to_s.bind_call(self) + TO_S = Kernel.instance_method(:to_s) # :nodoc: + if TO_S.respond_to?(:bind_call) + def inspect # :nodoc: + TO_S.bind_call(self) end else - def inspect - @@to_s.bind(self).call + def inspect # :nodoc: + TO_S.bind(self).call end end @@ -524,6 +523,8 @@ module Gem::URI ret end + # Returns +uri+ as-is if it is Gem::URI, or convert it to Gem::URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(Gem::URI::Generic) uri @@ -536,4 +537,11 @@ module Gem::URI end end # class Parser + + # Backward compatibility for Gem::URI::REGEXP::PATTERN::* + RFC2396_Parser.new.pattern.each_pair do |sym, str| + unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) + RFC2396_REGEXP::PATTERN.const_set(sym, str) + end + end end # module Gem::URI diff --git a/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb b/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb index 728bb55674..3b6961abf6 100644 --- a/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb +++ b/lib/rubygems/vendor/uri/lib/uri/rfc3986_parser.rb @@ -78,7 +78,7 @@ module Gem::URI begin uri = uri.to_str rescue NoMethodError - raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}" + raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri.inspect}" end uri.ascii_only? or raise InvalidURIError, "Gem::URI must be ascii only #{uri.dump}" @@ -127,7 +127,7 @@ module Gem::URI m["fragment"] ] else - raise InvalidURIError, "bad Gem::URI(is not Gem::URI?): #{uri.inspect}" + raise InvalidURIError, "bad Gem::URI (is not Gem::URI?): #{uri.inspect}" end end @@ -135,12 +135,35 @@ module Gem::URI Gem::URI.for(*self.split(uri), self) end - def join(*uris) # :nodoc: uris[0] = convert_to_uri(uris[0]) uris.inject :merge end + # Compatibility for RFC2396 parser + def extract(str, schemes = nil, &block) # :nodoc: + warn "Gem::URI::RFC3986_PARSER.extract is obsolete. Use Gem::URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE + RFC2396_PARSER.extract(str, schemes, &block) + end + + # Compatibility for RFC2396 parser + def make_regexp(schemes = nil) # :nodoc: + warn "Gem::URI::RFC3986_PARSER.make_regexp is obsolete. Use Gem::URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE + RFC2396_PARSER.make_regexp(schemes) + end + + # Compatibility for RFC2396 parser + def escape(str, unsafe = nil) # :nodoc: + warn "Gem::URI::RFC3986_PARSER.escape is obsolete. Use Gem::URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE + unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) + end + + # Compatibility for RFC2396 parser + def unescape(str, escaped = nil) # :nodoc: + warn "Gem::URI::RFC3986_PARSER.unescape is obsolete. Use Gem::URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE + escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) + end + @@to_s = Kernel.instance_method(:to_s) if @@to_s.respond_to?(:bind_call) def inspect diff --git a/lib/rubygems/vendor/uri/lib/uri/version.rb b/lib/rubygems/vendor/uri/lib/uri/version.rb index 080744095c..7ee577887b 100644 --- a/lib/rubygems/vendor/uri/lib/uri/version.rb +++ b/lib/rubygems/vendor/uri/lib/uri/version.rb @@ -1,6 +1,6 @@ module Gem::URI # :stopdoc: - VERSION_CODE = '001301'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/rubygems/vendored_securerandom.rb b/lib/rubygems/vendored_securerandom.rb index 0ce26905c4..859b6d7d7a 100644 --- a/lib/rubygems/vendored_securerandom.rb +++ b/lib/rubygems/vendored_securerandom.rb @@ -1,4 +1,3 @@ # frozen_string_literal: true -module Gem::Random; end require_relative "vendor/securerandom/lib/securerandom" diff --git a/lib/rubygems/version.rb b/lib/rubygems/version.rb index d9cd91bffa..90fe1b3c24 100644 --- a/lib/rubygems/version.rb +++ b/lib/rubygems/version.rb @@ -171,9 +171,7 @@ class Gem::Version # True if the +version+ string matches RubyGems' requirements. def self.correct?(version) - nil_versions_are_discouraged! if version.nil? - - ANCHORED_VERSION_PATTERN.match?(version.to_s) + version.nil? || ANCHORED_VERSION_PATTERN.match?(version.to_s) end ## @@ -182,15 +180,10 @@ class Gem::Version # # ver1 = Version.create('1.3.17') # -> (Version object) # ver2 = Version.create(ver1) # -> (ver1) - # ver3 = Version.create(nil) # -> nil def self.create(input) if self === input # check yourself before you wreck yourself input - elsif input.nil? - nil_versions_are_discouraged! - - nil else new input end @@ -206,14 +199,6 @@ class Gem::Version @@all[version] ||= super end - def self.nil_versions_are_discouraged! - unless Gem::Deprecate.skip - warn "nil versions are discouraged and will be deprecated in Rubygems 4" - end - end - - private_class_method :nil_versions_are_discouraged! - ## # Constructs a Version from the +version+ string. A version string is a # series of digits or ASCII letters separated by dots. @@ -224,7 +209,7 @@ class Gem::Version end # If version is an empty string convert it to 0 - version = 0 if version.is_a?(String) && /\A\s*\Z/.match?(version) + version = 0 if version.nil? || (version.is_a?(String) && /\A\s*\Z/.match?(version)) @version = version.to_s @@ -288,7 +273,10 @@ class Gem::Version # 1.3.5 and earlier) compatibility. def marshal_load(array) - initialize array[0] + string = array[0] + raise TypeError, "wrong version string" unless string.is_a?(String) + + initialize string end def yaml_initialize(tag, map) # :nodoc: @@ -351,11 +339,14 @@ class Gem::Version ## # Compares this version with +other+ returning -1, 0, or 1 if the # other version is larger, the same, or smaller than this - # one. Attempts to compare to something that's not a - # <tt>Gem::Version</tt> or a valid version String return +nil+. + # one. +other+ must be an instance of Gem::Version, comparing with + # other types may raise an exception. def <=>(other) - return self <=> self.class.new(other) if (String === other) && self.class.correct?(other) + if String === other + return unless self.class.correct?(other) + return self <=> self.class.new(other) + end return unless Gem::Version === other return 0 if @version == other.version || canonical_segments == other.canonical_segments @@ -365,13 +356,13 @@ class Gem::Version lhsize = lhsegments.size rhsize = rhsegments.size - limit = (lhsize > rhsize ? lhsize : rhsize) - 1 + limit = (lhsize > rhsize ? rhsize : lhsize) i = 0 - while i <= limit - lhs = lhsegments[i] || 0 - rhs = rhsegments[i] || 0 + while i < limit + lhs = lhsegments[i] + rhs = rhsegments[i] i += 1 next if lhs == rhs @@ -381,6 +372,24 @@ class Gem::Version return lhs <=> rhs end + lhs = lhsegments[i] + + if lhs.nil? + rhs = rhsegments[i] + + while i < rhsize + return 1 if String === rhs + return -1 unless rhs.zero? + rhs = rhsegments[i += 1] + end + else + while i < lhsize + return -1 if String === lhs + return 1 unless lhs.zero? + lhs = lhsegments[i += 1] + end + end + 0 end diff --git a/lib/rubygems/win_platform.rb b/lib/rubygems/win_platform.rb new file mode 100644 index 0000000000..78e968fe49 --- /dev/null +++ b/lib/rubygems/win_platform.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require "rbconfig" + +module Gem + ## + # An Array of Regexps that match windows Ruby platforms. + + WIN_PATTERNS = [ + /bccwin/i, + /cygwin/i, + /djgpp/i, + /mingw/i, + /mswin/i, + /wince/i, + ].freeze + + @@win_platform = nil + + ## + # Is this a windows platform? + + def self.win_platform? + if @@win_platform.nil? + ruby_platform = RbConfig::CONFIG["host_os"] + @@win_platform = !WIN_PATTERNS.find {|r| ruby_platform =~ r }.nil? + end + + @@win_platform + end +end diff --git a/lib/securerandom.gemspec b/lib/securerandom.gemspec index 0b023486a6..fe46c11013 100644 --- a/lib/securerandom.gemspec +++ b/lib/securerandom.gemspec @@ -14,9 +14,10 @@ Gem::Specification.new do |spec| spec.summary = %q{Interface for secure random number generator.} spec.description = %q{Interface for secure random number generator.} spec.homepage = "https://github.com/ruby/securerandom" - spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0") + spec.required_ruby_version = Gem::Requirement.new(">= 3.1.0") spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.metadata["changelog_uri"] = spec.homepage + "/releases" spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage diff --git a/lib/securerandom.rb b/lib/securerandom.rb index 81757f3100..6079fdb5c4 100644 --- a/lib/securerandom.rb +++ b/lib/securerandom.rb @@ -41,7 +41,7 @@ require 'random/formatter' module SecureRandom # The version - VERSION = "0.3.1" + VERSION = "0.4.1" class << self # Returns a random binary string containing +size+ bytes. @@ -51,6 +51,12 @@ module SecureRandom return gen_random(n) end + # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end if RUBY_VERSION < '3.3' + private # :stopdoc: diff --git a/lib/set.rb b/lib/set.rb deleted file mode 100644 index 8ac9ce45b1..0000000000 --- a/lib/set.rb +++ /dev/null @@ -1,852 +0,0 @@ -# frozen_string_literal: true -# :markup: markdown -# -# set.rb - defines the Set class -# -# Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org> -# -# Documentation by Akinori MUSHA and Gavin Sinclair. -# -# All rights reserved. You can redistribute and/or modify it under the same -# terms as Ruby. - - -## -# This library provides the Set class, which implements a collection -# of unordered values with no duplicates. It is a hybrid of Array's -# intuitive inter-operation facilities and Hash's fast lookup. -# -# The method `to_set` is added to Enumerable for convenience. -# -# Set is easy to use with Enumerable objects (implementing `each`). -# Most of the initializer methods and binary operators accept generic -# Enumerable objects besides sets and arrays. An Enumerable object -# can be converted to Set using the `to_set` method. -# -# Set uses Hash as storage, so you must note the following points: -# -# * Equality of elements is determined according to Object#eql? and -# Object#hash. Use Set#compare_by_identity to make a set compare -# its elements by their identity. -# * Set assumes that the identity of each element does not change -# while it is stored. Modifying an element of a set will render the -# set to an unreliable state. -# * When a string is to be stored, a frozen copy of the string is -# stored instead unless the original string is already frozen. -# -# ## Comparison -# -# The comparison operators `<`, `>`, `<=`, and `>=` are implemented as -# shorthand for the {proper_,}{subset?,superset?} methods. The `<=>` -# operator reflects this order, or return `nil` for sets that both -# have distinct elements (`{x, y}` vs. `{x, z}` for example). -# -# ## Example -# -# ```ruby -# require 'set' -# s1 = Set[1, 2] #=> #<Set: {1, 2}> -# s2 = [1, 2].to_set #=> #<Set: {1, 2}> -# s1 == s2 #=> true -# s1.add("foo") #=> #<Set: {1, 2, "foo"}> -# s1.merge([2, 6]) #=> #<Set: {1, 2, "foo", 6}> -# s1.subset?(s2) #=> false -# s2.subset?(s1) #=> true -# ``` -# -# ## Contact -# -# - Akinori MUSHA <<knu@iDaemons.org>> (current maintainer) -# -# ## What's Here -# -# First, what's elsewhere. \Class \Set: -# -# - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here]. -# - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here], -# which provides dozens of additional methods. -# -# In particular, class \Set does not have many methods of its own -# for fetching or for iterating. -# Instead, it relies on those in \Enumerable. -# -# Here, class \Set provides methods that are useful for: -# -# - [Creating a Set](#class-Set-label-Methods+for+Creating+a+Set) -# - [Set Operations](#class-Set-label-Methods+for+Set+Operations) -# - [Comparing](#class-Set-label-Methods+for+Comparing) -# - [Querying](#class-Set-label-Methods+for+Querying) -# - [Assigning](#class-Set-label-Methods+for+Assigning) -# - [Deleting](#class-Set-label-Methods+for+Deleting) -# - [Converting](#class-Set-label-Methods+for+Converting) -# - [Iterating](#class-Set-label-Methods+for+Iterating) -# - [And more....](#class-Set-label-Other+Methods) -# -# ### Methods for Creating a \Set -# -# - ::[]: -# Returns a new set containing the given objects. -# - ::new: -# Returns a new set containing either the given objects -# (if no block given) or the return values from the called block -# (if a block given). -# -# ### Methods for \Set Operations -# -# - [|](#method-i-7C) (aliased as #union and #+): -# Returns a new set containing all elements from +self+ -# and all elements from a given enumerable (no duplicates). -# - [&](#method-i-26) (aliased as #intersection): -# Returns a new set containing all elements common to +self+ -# and a given enumerable. -# - [-](#method-i-2D) (aliased as #difference): -# Returns a copy of +self+ with all elements -# in a given enumerable removed. -# - [\^](#method-i-5E): -# Returns a new set containing all elements from +self+ -# and a given enumerable except those common to both. -# -# ### Methods for Comparing -# -# - [<=>](#method-i-3C-3D-3E): -# Returns -1, 0, or 1 as +self+ is less than, equal to, -# or greater than a given object. -# - [==](#method-i-3D-3D): -# Returns whether +self+ and a given enumerable are equal, -# as determined by Object#eql?. -# - \#compare_by_identity?: -# Returns whether the set considers only identity -# when comparing elements. -# -# ### Methods for Querying -# -# - \#length (aliased as #size): -# Returns the count of elements. -# - \#empty?: -# Returns whether the set has no elements. -# - \#include? (aliased as #member? and #===): -# Returns whether a given object is an element in the set. -# - \#subset? (aliased as [<=](#method-i-3C-3D)): -# Returns whether a given object is a subset of the set. -# - \#proper_subset? (aliased as [<](#method-i-3C)): -# Returns whether a given enumerable is a proper subset of the set. -# - \#superset? (aliased as [>=](#method-i-3E-3D])): -# Returns whether a given enumerable is a superset of the set. -# - \#proper_superset? (aliased as [>](#method-i-3E)): -# Returns whether a given enumerable is a proper superset of the set. -# - \#disjoint?: -# Returns +true+ if the set and a given enumerable -# have no common elements, +false+ otherwise. -# - \#intersect?: -# Returns +true+ if the set and a given enumerable: -# have any common elements, +false+ otherwise. -# - \#compare_by_identity?: -# Returns whether the set considers only identity -# when comparing elements. -# -# ### Methods for Assigning -# -# - \#add (aliased as #<<): -# Adds a given object to the set; returns +self+. -# - \#add?: -# If the given object is not an element in the set, -# adds it and returns +self+; otherwise, returns +nil+. -# - \#merge: -# Merges the elements of each given enumerable object to the set; returns +self+. -# - \#replace: -# Replaces the contents of the set with the contents -# of a given enumerable. -# -# ### Methods for Deleting -# -# - \#clear: -# Removes all elements in the set; returns +self+. -# - \#delete: -# Removes a given object from the set; returns +self+. -# - \#delete?: -# If the given object is an element in the set, -# removes it and returns +self+; otherwise, returns +nil+. -# - \#subtract: -# Removes each given object from the set; returns +self+. -# - \#delete_if - Removes elements specified by a given block. -# - \#select! (aliased as #filter!): -# Removes elements not specified by a given block. -# - \#keep_if: -# Removes elements not specified by a given block. -# - \#reject! -# Removes elements specified by a given block. -# -# ### Methods for Converting -# -# - \#classify: -# Returns a hash that classifies the elements, -# as determined by the given block. -# - \#collect! (aliased as #map!): -# Replaces each element with a block return-value. -# - \#divide: -# Returns a hash that classifies the elements, -# as determined by the given block; -# differs from #classify in that the block may accept -# either one or two arguments. -# - \#flatten: -# Returns a new set that is a recursive flattening of +self+. -# \#flatten!: -# Replaces each nested set in +self+ with the elements from that set. -# - \#inspect (aliased as #to_s): -# Returns a string displaying the elements. -# - \#join: -# Returns a string containing all elements, converted to strings -# as needed, and joined by the given record separator. -# - \#to_a: -# Returns an array containing all set elements. -# - \#to_set: -# Returns +self+ if given no arguments and no block; -# with a block given, returns a new set consisting of block -# return values. -# -# ### Methods for Iterating -# -# - \#each: -# Calls the block with each successive element; returns +self+. -# -# ### Other Methods -# -# - \#reset: -# Resets the internal state; useful if an object -# has been modified while an element in the set. -# -class Set - VERSION = "1.1.0" - - include Enumerable - - # Creates a new set containing the given objects. - # - # Set[1, 2] # => #<Set: {1, 2}> - # Set[1, 2, 1] # => #<Set: {1, 2}> - # Set[1, 'c', :s] # => #<Set: {1, "c", :s}> - def self.[](*ary) - new(ary) - end - - # Creates a new set containing the elements of the given enumerable - # object. - # - # If a block is given, the elements of enum are preprocessed by the - # given block. - # - # Set.new([1, 2]) #=> #<Set: {1, 2}> - # Set.new([1, 2, 1]) #=> #<Set: {1, 2}> - # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}> - # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}> - # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}> - def initialize(enum = nil, &block) # :yields: o - @hash ||= Hash.new(false) - - enum.nil? and return - - if block - do_with_enum(enum) { |o| add(block[o]) } - else - merge(enum) - end - end - - # Makes the set compare its elements by their identity and returns - # self. This method may not be supported by all subclasses of Set. - def compare_by_identity - if @hash.respond_to?(:compare_by_identity) - @hash.compare_by_identity - self - else - raise NotImplementedError, "#{self.class.name}\##{__method__} is not implemented" - end - end - - # Returns true if the set will compare its elements by their - # identity. Also see Set#compare_by_identity. - def compare_by_identity? - @hash.respond_to?(:compare_by_identity?) && @hash.compare_by_identity? - end - - def do_with_enum(enum, &block) # :nodoc: - if enum.respond_to?(:each_entry) - enum.each_entry(&block) if block - elsif enum.respond_to?(:each) - enum.each(&block) if block - else - raise ArgumentError, "value must be enumerable" - end - end - private :do_with_enum - - # Dup internal hash. - def initialize_dup(orig) - super - @hash = orig.instance_variable_get(:@hash).dup - end - - # Clone internal hash. - def initialize_clone(orig, **options) - super - @hash = orig.instance_variable_get(:@hash).clone(**options) - end - - def freeze # :nodoc: - @hash.freeze - super - end - - # Returns the number of elements. - def size - @hash.size - end - alias length size - - # Returns true if the set contains no elements. - def empty? - @hash.empty? - end - - # Removes all elements and returns self. - # - # set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}> - # set.clear #=> #<Set: {}> - # set #=> #<Set: {}> - def clear - @hash.clear - self - end - - # Replaces the contents of the set with the contents of the given - # enumerable object and returns self. - # - # set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}> - # set.replace([1, 2]) #=> #<Set: {1, 2}> - # set #=> #<Set: {1, 2}> - def replace(enum) - if enum.instance_of?(self.class) - @hash.replace(enum.instance_variable_get(:@hash)) - self - else - do_with_enum(enum) # make sure enum is enumerable before calling clear - clear - merge(enum) - end - end - - # Returns an array containing all elements in the set. - # - # Set[1, 2].to_a #=> [1, 2] - # Set[1, 'c', :s].to_a #=> [1, "c", :s] - def to_a - @hash.keys - end - - # Returns self if no arguments are given. Otherwise, converts the - # set to another with `klass.new(self, *args, &block)`. - # - # In subclasses, returns `klass.new(self, *args, &block)` unless - # overridden. - def to_set(klass = Set, *args, &block) - return self if instance_of?(Set) && klass == Set && block.nil? && args.empty? - klass.new(self, *args, &block) - end - - def flatten_merge(set, seen = Set.new) # :nodoc: - set.each { |e| - if e.is_a?(Set) - if seen.include?(e_id = e.object_id) - raise ArgumentError, "tried to flatten recursive Set" - end - - seen.add(e_id) - flatten_merge(e, seen) - seen.delete(e_id) - else - add(e) - end - } - - self - end - protected :flatten_merge - - # Returns a new set that is a copy of the set, flattening each - # containing set recursively. - def flatten - self.class.new.flatten_merge(self) - end - - # Equivalent to Set#flatten, but replaces the receiver with the - # result in place. Returns nil if no modifications were made. - def flatten! - replace(flatten()) if any?(Set) - end - - # Returns true if the set contains the given object. - # - # Note that <code>include?</code> and <code>member?</code> do not test member - # equality using <code>==</code> as do other Enumerables. - # - # See also Enumerable#include? - def include?(o) - @hash[o] - end - alias member? include? - - # Returns true if the set is a superset of the given set. - def superset?(set) - case - when set.instance_of?(self.class) && @hash.respond_to?(:>=) - @hash >= set.instance_variable_get(:@hash) - when set.is_a?(Set) - size >= set.size && set.all?(self) - else - raise ArgumentError, "value must be a set" - end - end - alias >= superset? - - # Returns true if the set is a proper superset of the given set. - def proper_superset?(set) - case - when set.instance_of?(self.class) && @hash.respond_to?(:>) - @hash > set.instance_variable_get(:@hash) - when set.is_a?(Set) - size > set.size && set.all?(self) - else - raise ArgumentError, "value must be a set" - end - end - alias > proper_superset? - - # Returns true if the set is a subset of the given set. - def subset?(set) - case - when set.instance_of?(self.class) && @hash.respond_to?(:<=) - @hash <= set.instance_variable_get(:@hash) - when set.is_a?(Set) - size <= set.size && all?(set) - else - raise ArgumentError, "value must be a set" - end - end - alias <= subset? - - # Returns true if the set is a proper subset of the given set. - def proper_subset?(set) - case - when set.instance_of?(self.class) && @hash.respond_to?(:<) - @hash < set.instance_variable_get(:@hash) - when set.is_a?(Set) - size < set.size && all?(set) - else - raise ArgumentError, "value must be a set" - end - end - alias < proper_subset? - - # Returns 0 if the set are equal, - # -1 / +1 if the set is a proper subset / superset of the given set, - # or nil if they both have unique elements. - def <=>(set) - return unless set.is_a?(Set) - - case size <=> set.size - when -1 then -1 if proper_subset?(set) - when +1 then +1 if proper_superset?(set) - else 0 if self.==(set) - end - end - - # Returns true if the set and the given enumerable have at least one - # element in common. - # - # Set[1, 2, 3].intersect? Set[4, 5] #=> false - # Set[1, 2, 3].intersect? Set[3, 4] #=> true - # Set[1, 2, 3].intersect? 4..5 #=> false - # Set[1, 2, 3].intersect? [3, 4] #=> true - def intersect?(set) - case set - when Set - if size < set.size - any?(set) - else - set.any?(self) - end - when Enumerable - set.any?(self) - else - raise ArgumentError, "value must be enumerable" - end - end - - # Returns true if the set and the given enumerable have - # no element in common. This method is the opposite of `intersect?`. - # - # Set[1, 2, 3].disjoint? Set[3, 4] #=> false - # Set[1, 2, 3].disjoint? Set[4, 5] #=> true - # Set[1, 2, 3].disjoint? [3, 4] #=> false - # Set[1, 2, 3].disjoint? 4..5 #=> true - def disjoint?(set) - !intersect?(set) - end - - # Calls the given block once for each element in the set, passing - # the element as parameter. Returns an enumerator if no block is - # given. - def each(&block) - block_given? or return enum_for(__method__) { size } - @hash.each_key(&block) - self - end - - # Adds the given object to the set and returns self. Use `merge` to - # add many elements at once. - # - # Set[1, 2].add(3) #=> #<Set: {1, 2, 3}> - # Set[1, 2].add([3, 4]) #=> #<Set: {1, 2, [3, 4]}> - # Set[1, 2].add(2) #=> #<Set: {1, 2}> - def add(o) - @hash[o] = true - self - end - alias << add - - # Adds the given object to the set and returns self. If the - # object is already in the set, returns nil. - # - # Set[1, 2].add?(3) #=> #<Set: {1, 2, 3}> - # Set[1, 2].add?([3, 4]) #=> #<Set: {1, 2, [3, 4]}> - # Set[1, 2].add?(2) #=> nil - def add?(o) - add(o) unless include?(o) - end - - # Deletes the given object from the set and returns self. Use - # `subtract` to delete many items at once. - def delete(o) - @hash.delete(o) - self - end - - # Deletes the given object from the set and returns self. If the - # object is not in the set, returns nil. - def delete?(o) - delete(o) if include?(o) - end - - # Deletes every element of the set for which block evaluates to - # true, and returns self. Returns an enumerator if no block is - # given. - def delete_if(&block) - block_given? or return enum_for(__method__) { size } - # Instead of directly using @hash.delete_if, perform enumeration - # using self.each that subclasses may override. - select(&block).each { |o| @hash.delete(o) } - self - end - - # Deletes every element of the set for which block evaluates to - # false, and returns self. Returns an enumerator if no block is - # given. - def keep_if(&block) - block_given? or return enum_for(__method__) { size } - # Instead of directly using @hash.keep_if, perform enumeration - # using self.each that subclasses may override. - reject(&block).each { |o| @hash.delete(o) } - self - end - - # Replaces the elements with ones returned by `collect()`. - # Returns an enumerator if no block is given. - def collect! - block_given? or return enum_for(__method__) { size } - set = self.class.new - each { |o| set << yield(o) } - replace(set) - end - alias map! collect! - - # Equivalent to Set#delete_if, but returns nil if no changes were - # made. Returns an enumerator if no block is given. - def reject!(&block) - block_given? or return enum_for(__method__) { size } - n = size - delete_if(&block) - self if size != n - end - - # Equivalent to Set#keep_if, but returns nil if no changes were - # made. Returns an enumerator if no block is given. - def select!(&block) - block_given? or return enum_for(__method__) { size } - n = size - keep_if(&block) - self if size != n - end - - # Equivalent to Set#select! - alias filter! select! - - # Merges the elements of the given enumerable objects to the set and - # returns self. - def merge(*enums, **nil) - enums.each do |enum| - if enum.instance_of?(self.class) - @hash.update(enum.instance_variable_get(:@hash)) - else - do_with_enum(enum) { |o| add(o) } - end - end - - self - end - - # Deletes every element that appears in the given enumerable object - # and returns self. - def subtract(enum) - do_with_enum(enum) { |o| delete(o) } - self - end - - # Returns a new set built by merging the set and the elements of the - # given enumerable object. - # - # Set[1, 2, 3] | Set[2, 4, 5] #=> #<Set: {1, 2, 3, 4, 5}> - # Set[1, 5, 'z'] | (1..6) #=> #<Set: {1, 5, "z", 2, 3, 4, 6}> - def |(enum) - dup.merge(enum) - end - alias + | - alias union | - - # Returns a new set built by duplicating the set, removing every - # element that appears in the given enumerable object. - # - # Set[1, 3, 5] - Set[1, 5] #=> #<Set: {3}> - # Set['a', 'b', 'z'] - ['a', 'c'] #=> #<Set: {"b", "z"}> - def -(enum) - dup.subtract(enum) - end - alias difference - - - # Returns a new set containing elements common to the set and the - # given enumerable object. - # - # Set[1, 3, 5] & Set[3, 2, 1] #=> #<Set: {3, 1}> - # Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> #<Set: {"a", "b"}> - def &(enum) - n = self.class.new - if enum.is_a?(Set) - if enum.size > size - each { |o| n.add(o) if enum.include?(o) } - else - enum.each { |o| n.add(o) if include?(o) } - end - else - do_with_enum(enum) { |o| n.add(o) if include?(o) } - end - n - end - alias intersection & - - # Returns a new set containing elements exclusive between the set - # and the given enumerable object. `(set ^ enum)` is equivalent to - # `((set | enum) - (set & enum))`. - # - # Set[1, 2] ^ Set[2, 3] #=> #<Set: {3, 1}> - # Set[1, 'b', 'c'] ^ ['b', 'd'] #=> #<Set: {"d", 1, "c"}> - def ^(enum) - n = Set.new(enum) - each { |o| n.add(o) unless n.delete?(o) } - n - end - - # Returns true if two sets are equal. The equality of each couple - # of elements is defined according to Object#eql?. - # - # Set[1, 2] == Set[2, 1] #=> true - # Set[1, 3, 5] == Set[1, 5] #=> false - # Set['a', 'b', 'c'] == Set['a', 'c', 'b'] #=> true - # Set['a', 'b', 'c'] == ['a', 'c', 'b'] #=> false - def ==(other) - if self.equal?(other) - true - elsif other.instance_of?(self.class) - @hash == other.instance_variable_get(:@hash) - elsif other.is_a?(Set) && self.size == other.size - other.all? { |o| @hash.include?(o) } - else - false - end - end - - def hash # :nodoc: - @hash.hash - end - - def eql?(o) # :nodoc: - return false unless o.is_a?(Set) - @hash.eql?(o.instance_variable_get(:@hash)) - end - - # Resets the internal state after modification to existing elements - # and returns self. - # - # Elements will be reindexed and deduplicated. - def reset - if @hash.respond_to?(:rehash) - @hash.rehash # This should perform frozenness check. - else - raise FrozenError, "can't modify frozen #{self.class.name}" if frozen? - end - self - end - - # Returns true if the given object is a member of the set, - # and false otherwise. - # - # Used in case statements: - # - # require 'set' - # - # case :apple - # when Set[:potato, :carrot] - # "vegetable" - # when Set[:apple, :banana] - # "fruit" - # end - # # => "fruit" - # - # Or by itself: - # - # Set[1, 2, 3] === 2 #=> true - # Set[1, 2, 3] === 4 #=> false - # - alias === include? - - # Classifies the set by the return value of the given block and - # returns a hash of {value => set of elements} pairs. The block is - # called once for each element of the set, passing the element as - # parameter. - # - # require 'set' - # files = Set.new(Dir.glob("*.rb")) - # hash = files.classify { |f| File.mtime(f).year } - # hash #=> {2000=>#<Set: {"a.rb", "b.rb"}>, - # # 2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>, - # # 2002=>#<Set: {"f.rb"}>} - # - # Returns an enumerator if no block is given. - def classify # :yields: o - block_given? or return enum_for(__method__) { size } - - h = {} - - each { |i| - (h[yield(i)] ||= self.class.new).add(i) - } - - h - end - - # Divides the set into a set of subsets according to the commonality - # defined by the given block. - # - # If the arity of the block is 2, elements o1 and o2 are in common - # if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are - # in common if block.call(o1) == block.call(o2). - # - # require 'set' - # numbers = Set[1, 3, 4, 6, 9, 10, 11] - # set = numbers.divide { |i,j| (i - j).abs == 1 } - # set #=> #<Set: {#<Set: {1}>, - # # #<Set: {11, 9, 10}>, - # # #<Set: {3, 4}>, - # # #<Set: {6}>}> - # - # Returns an enumerator if no block is given. - def divide(&func) - func or return enum_for(__method__) { size } - - if func.arity == 2 - require 'tsort' - - class << dig = {} # :nodoc: - include TSort - - alias tsort_each_node each_key - def tsort_each_child(node, &block) - fetch(node).each(&block) - end - end - - each { |u| - dig[u] = a = [] - each{ |v| func.call(u, v) and a << v } - } - - set = Set.new() - dig.each_strongly_connected_component { |css| - set.add(self.class.new(css)) - } - set - else - Set.new(classify(&func).values) - end - end - - # Returns a string created by converting each element of the set to a string - # See also: Array#join - def join(separator=nil) - to_a.join(separator) - end - - InspectKey = :__inspect_key__ # :nodoc: - - # Returns a string containing a human-readable representation of the - # set ("#<Set: {element1, element2, ...}>"). - def inspect - ids = (Thread.current[InspectKey] ||= []) - - if ids.include?(object_id) - return sprintf('#<%s: {...}>', self.class.name) - end - - ids << object_id - begin - return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) - ensure - ids.pop - end - end - - alias to_s inspect - - def pretty_print(pp) # :nodoc: - pp.group(1, sprintf('#<%s:', self.class.name), '>') { - pp.breakable - pp.group(1, '{', '}') { - pp.seplist(self) { |o| - pp.pp o - } - } - } - end - - def pretty_print_cycle(pp) # :nodoc: - pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') - end -end - -module Enumerable - # Makes a set from the enumerable object with given arguments. - # Needs to `require "set"` to use this method. - def to_set(klass = Set, *args, &block) - klass.new(self, *args, &block) - end unless method_defined?(:to_set) -end - -autoload :SortedSet, "#{__dir__}/set/sorted_set" diff --git a/lib/set/set.gemspec b/lib/set/set.gemspec deleted file mode 100644 index 2ebef6985d..0000000000 --- a/lib/set/set.gemspec +++ /dev/null @@ -1,30 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1, "..").join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Akinori MUSHA"] - spec.email = ["knu@idaemons.org"] - - spec.summary = %q{Provides a class to deal with collections of unordered, unique values} - spec.description = %q{Provides a class to deal with collections of unordered, unique values} - spec.homepage = "https://github.com/ruby/set" - spec.licenses = ["Ruby", "BSD-2-Clause"] - spec.required_ruby_version = Gem::Requirement.new(">= 3.0.0") - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - spec.metadata["changelog_uri"] = "https://github.com/ruby/set/blob/v#{spec.version}/CHANGELOG.md" - - # Specify which files should be added to the gem when it is released. - # The `git ls-files -z` loads the files in the RubyGem that have been added into git. - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.require_paths = ["lib"] -end diff --git a/lib/set/sorted_set.rb b/lib/set/sorted_set.rb deleted file mode 100644 index bc07bc1fb0..0000000000 --- a/lib/set/sorted_set.rb +++ /dev/null @@ -1,6 +0,0 @@ -begin - require 'sorted_set' -rescue ::LoadError - raise "The `SortedSet` class has been extracted from the `set` library. " \ - "You must use the `sorted_set` gem or other alternatives." -end diff --git a/lib/set/subclass_compatible.rb b/lib/set/subclass_compatible.rb new file mode 100644 index 0000000000..f43c34f6a2 --- /dev/null +++ b/lib/set/subclass_compatible.rb @@ -0,0 +1,347 @@ +# frozen_string_literal: true + +# :markup: markdown +# +# set/subclass_compatible.rb - Provides compatibility for set subclasses +# +# Copyright (c) 2002-2024 Akinori MUSHA <knu@iDaemons.org> +# +# Documentation by Akinori MUSHA and Gavin Sinclair. +# +# All rights reserved. You can redistribute and/or modify it under the same +# terms as Ruby. + + +class Set + # This module is automatically included in subclasses of Set, to + # make them backwards compatible with the pure-Ruby set implementation + # used before Ruby 4. Users who want to use Set subclasses without + # this compatibility layer should subclass from Set::CoreSet. + # + # Note that Set subclasses that access `@hash` are not compatible even + # with this support. Such subclasses must be updated to support Ruby 4. + module SubclassCompatible + module ClassMethods + def [](*ary) + new(ary) + end + end + + # Creates a new set containing the elements of the given enumerable + # object. + # + # If a block is given, the elements of enum are preprocessed by the + # given block. + # + # Set.new([1, 2]) #=> #<Set: {1, 2}> + # Set.new([1, 2, 1]) #=> #<Set: {1, 2}> + # Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}> + # Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}> + # Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}> + def initialize(enum = nil, &block) # :yields: o + enum.nil? and return + + if block + do_with_enum(enum) { |o| add(block[o]) } + else + merge(enum) + end + end + + def do_with_enum(enum, &block) # :nodoc: + if enum.respond_to?(:each_entry) + enum.each_entry(&block) if block + elsif enum.respond_to?(:each) + enum.each(&block) if block + else + raise ArgumentError, "value must be enumerable" + end + end + private :do_with_enum + + def replace(enum) + if enum.instance_of?(self.class) + super + else + do_with_enum(enum) # make sure enum is enumerable before calling clear + clear + merge(enum) + end + end + + def to_set(&block) + return self if instance_of?(Set) && block.nil? + Set.new(self, &block) + end + + def flatten_merge(set, seen = {}) # :nodoc: + set.each { |e| + if e.is_a?(Set) + case seen[e_id = e.object_id] + when true + raise ArgumentError, "tried to flatten recursive Set" + when false + next + end + + seen[e_id] = true + flatten_merge(e, seen) + seen[e_id] = false + else + add(e) + end + } + + self + end + protected :flatten_merge + + def flatten + self.class.new.flatten_merge(self) + end + + def flatten! + replace(flatten()) if any?(Set) + end + + def superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size >= set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias >= superset? + + def proper_superset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size > set.size && set.all?(self) + else + raise ArgumentError, "value must be a set" + end + end + alias > proper_superset? + + def subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size <= set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias <= subset? + + def proper_subset?(set) + case + when set.instance_of?(self.class) + super + when set.is_a?(Set) + size < set.size && all?(set) + else + raise ArgumentError, "value must be a set" + end + end + alias < proper_subset? + + def <=>(set) + return unless set.is_a?(Set) + + case size <=> set.size + when -1 then -1 if proper_subset?(set) + when +1 then +1 if proper_superset?(set) + else 0 if self.==(set) + end + end + + def intersect?(set) + case set + when Set + if size < set.size + any?(set) + else + set.any?(self) + end + when Enumerable + set.any?(self) + else + raise ArgumentError, "value must be enumerable" + end + end + + def disjoint?(set) + !intersect?(set) + end + + def add?(o) + add(o) unless include?(o) + end + + def delete?(o) + delete(o) if include?(o) + end + + def delete_if(&block) + block_given? or return enum_for(__method__) { size } + select(&block).each { |o| delete(o) } + self + end + + def keep_if(&block) + block_given? or return enum_for(__method__) { size } + reject(&block).each { |o| delete(o) } + self + end + + def collect! + block_given? or return enum_for(__method__) { size } + set = self.class.new + each { |o| set << yield(o) } + replace(set) + end + alias map! collect! + + def reject!(&block) + block_given? or return enum_for(__method__) { size } + n = size + delete_if(&block) + self if size != n + end + + def select!(&block) + block_given? or return enum_for(__method__) { size } + n = size + keep_if(&block) + self if size != n + end + + alias filter! select! + + def merge(*enums, **nil) + enums.each do |enum| + if enum.instance_of?(self.class) + super(enum) + else + do_with_enum(enum) { |o| add(o) } + end + end + + self + end + + def subtract(enum) + do_with_enum(enum) { |o| delete(o) } + self + end + + def |(enum) + dup.merge(enum) + end + alias + | + alias union | + + def -(enum) + dup.subtract(enum) + end + alias difference - + + def &(enum) + n = self.class.new + if enum.is_a?(Set) + if enum.size > size + each { |o| n.add(o) if enum.include?(o) } + else + enum.each { |o| n.add(o) if include?(o) } + end + else + do_with_enum(enum) { |o| n.add(o) if include?(o) } + end + n + end + alias intersection & + + def ^(enum) + n = self.class.new(enum) + each { |o| n.add(o) unless n.delete?(o) } + n + end + + def ==(other) + if self.equal?(other) + true + elsif other.instance_of?(self.class) + super + elsif other.is_a?(Set) && self.size == other.size + other.all? { |o| include?(o) } + else + false + end + end + + def eql?(o) # :nodoc: + return false unless o.is_a?(Set) + super + end + + def classify + block_given? or return enum_for(__method__) { size } + + h = {} + + each { |i| + (h[yield(i)] ||= self.class.new).add(i) + } + + h + end + + def join(separator=nil) + to_a.join(separator) + end + + InspectKey = :__inspect_key__ # :nodoc: + + # Returns a string containing a human-readable representation of the + # set ("#<Set: {element1, element2, ...}>"). + def inspect + ids = (Thread.current[InspectKey] ||= []) + + if ids.include?(object_id) + return sprintf('#<%s: {...}>', self.class.name) + end + + ids << object_id + begin + return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2]) + ensure + ids.pop + end + end + + alias to_s inspect + + def pretty_print(pp) # :nodoc: + pp.group(1, sprintf('#<%s:', self.class.name), '>') { + pp.breakable + pp.group(1, '{', '}') { + pp.seplist(self) { |o| + pp.pp o + } + } + } + end + + def pretty_print_cycle(pp) # :nodoc: + pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...') + end + end + private_constant :SubclassCompatible +end diff --git a/lib/shellwords.gemspec b/lib/shellwords.gemspec index 8d0c518ca5..b601508f94 100644 --- a/lib/shellwords.gemspec +++ b/lib/shellwords.gemspec @@ -25,7 +25,5 @@ Gem::Specification.new do |spec| spec.files = Dir.chdir(srcdir) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:(?:test|spec|features)/|\.git|Rake)}) || f == gemspec_file} end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] end diff --git a/lib/shellwords.rb b/lib/shellwords.rb index d8243abd61..20a85ed9d2 100644 --- a/lib/shellwords.rb +++ b/lib/shellwords.rb @@ -65,7 +65,7 @@ module Shellwords # The version number string. - VERSION = "0.2.0" + VERSION = "0.2.2" # Splits a string into an array of tokens in the same way the UNIX # Bourne shell does. @@ -73,6 +73,9 @@ module Shellwords # argv = Shellwords.split('here are "two words"') # argv #=> ["here", "are", "two words"] # + # +line+ must not contain NUL characters because of nature of + # +exec+ system call. + # # Note, however, that this is not a command line parser. Shell # metacharacters except for the single and double quotes and # backslash are not treated as such. @@ -87,9 +90,14 @@ module Shellwords def shellsplit(line) words = [] field = String.new - line.scan(/\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m) do + line.scan(/\G\s*(?>([^\0\s\\\'\"]+)|'([^\0\']*)'|"((?:[^\0\"\\]|\\[^\0])*)"|(\\[^\0]?)|(\S))(\s|\z)?/m) do |word, sq, dq, esc, garbage, sep| - raise ArgumentError, "Unmatched quote: #{line.inspect}" if garbage + if garbage + b = $~.begin(0) + line = $~[0] + line = "..." + line if b > 0 + raise ArgumentError, "#{garbage == "\0" ? 'Nul character' : 'Unmatched quote'} at #{b}: #{line}" + end # 2.2.3 Double-Quotes: # # The <backslash> shall retain its special meaning as an @@ -118,6 +126,9 @@ module Shellwords # command line. +str+ can be a non-string object that responds to # +to_s+. # + # +str+ must not contain NUL characters because of nature of +exec+ + # system call. + # # Note that a resulted string should be used unquoted and is not # intended for use in double quotes nor in single quotes. # @@ -150,6 +161,9 @@ module Shellwords # An empty argument will be skipped, so return empty quotes. return "''".dup if str.empty? + # Shellwords cannot contain NUL characters. + raise ArgumentError, "NUL character" if str.index("\0") + str = str.dup # Treat multibyte characters as is. It is the caller's responsibility @@ -175,6 +189,7 @@ module Shellwords # All elements are joined into a single string with fields separated by a # space, where each element is escaped for the Bourne shell and stringified # using +to_s+. + # See also Shellwords.shellescape. # # ary = ["There's", "a", "time", "and", "place", "for", "everything"] # argv = Shellwords.join(ary) diff --git a/lib/singleton.rb b/lib/singleton.rb index 1011335adf..74aec8903c 100644 --- a/lib/singleton.rb +++ b/lib/singleton.rb @@ -92,9 +92,10 @@ # p a.strip # => nil # module Singleton - VERSION = "0.2.0" + # The version string + VERSION = "0.3.0" - module SingletonInstanceMethods + module SingletonInstanceMethods # :nodoc: # Raises a TypeError to prevent cloning. def clone raise TypeError, "can't clone instance of singleton #{self.class}" @@ -143,11 +144,11 @@ module Singleton end end - def self.module_with_class_methods + def self.module_with_class_methods # :nodoc: SingletonClassMethods end - module SingletonClassProperties + module SingletonClassProperties # :nodoc: def self.included(c) # extending an object with Singleton is a bad idea @@ -196,10 +197,10 @@ module Singleton end if defined?(Ractor) - module RactorLocalSingleton + module RactorLocalSingleton # :nodoc: include Singleton::SingletonInstanceMethods - module RactorLocalSingletonClassMethods + module RactorLocalSingletonClassMethods # :nodoc: include Singleton::SingletonClassMethods def instance set_mutex(Thread::Mutex.new) if Ractor.current[mutex_key].nil? diff --git a/lib/syntax_suggest/api.rb b/lib/syntax_suggest/api.rb index 65660ec5e5..0f82d8362a 100644 --- a/lib/syntax_suggest/api.rb +++ b/lib/syntax_suggest/api.rb @@ -146,11 +146,7 @@ module SyntaxSuggest def self.valid_without?(without_lines:, code_lines:) lines = code_lines - Array(without_lines).flatten - if lines.empty? - true - else - valid?(lines) - end + lines.empty? || valid?(lines) end # SyntaxSuggest.invalid? [Private] @@ -227,6 +223,7 @@ require_relative "lex_all" require_relative "code_line" require_relative "code_block" require_relative "block_expand" +require_relative "mini_stringio" require_relative "priority_queue" require_relative "unvisited_lines" require_relative "around_block_scan" diff --git a/lib/syntax_suggest/capture_code_context.rb b/lib/syntax_suggest/capture_code_context.rb index 1f232cfae3..5de9ec09cc 100644 --- a/lib/syntax_suggest/capture_code_context.rb +++ b/lib/syntax_suggest/capture_code_context.rb @@ -15,7 +15,7 @@ module SyntaxSuggest # # 1. Sanitize/format input source # 2. Search for invalid blocks - # 3. Format invalid blocks into something meaninful + # 3. Format invalid blocks into something meaningful # # This class handles the third part. # diff --git a/lib/syntax_suggest/clean_document.rb b/lib/syntax_suggest/clean_document.rb index 2790ccae86..ba307af46e 100644 --- a/lib/syntax_suggest/clean_document.rb +++ b/lib/syntax_suggest/clean_document.rb @@ -10,7 +10,7 @@ module SyntaxSuggest # # 1. Sanitize/format input source # 2. Search for invalid blocks - # 3. Format invalid blocks into something meaninful + # 3. Format invalid blocks into something meaningful # # This class handles the first part. # diff --git a/lib/syntax_suggest/code_frontier.rb b/lib/syntax_suggest/code_frontier.rb index 0f870d0df0..38d5375ef4 100644 --- a/lib/syntax_suggest/code_frontier.rb +++ b/lib/syntax_suggest/code_frontier.rb @@ -8,7 +8,7 @@ module SyntaxSuggest # # 1. Sanitize/format input source # 2. Search for invalid blocks - # 3. Format invalid blocks into something meaninful + # 3. Format invalid blocks into something meaningful # # The Code frontier is a critical part of the second step # diff --git a/lib/syntax_suggest/code_line.rb b/lib/syntax_suggest/code_line.rb index 58197e95d0..76ca892ac3 100644 --- a/lib/syntax_suggest/code_line.rb +++ b/lib/syntax_suggest/code_line.rb @@ -180,18 +180,17 @@ module SyntaxSuggest # EOM # expect(lines.first.trailing_slash?).to eq(true) # - if SyntaxSuggest.use_prism_parser? - def trailing_slash? - last = @lex.last - last&.type == :on_tstring_end - end - else - def trailing_slash? - last = @lex.last - return false unless last - return false unless last.type == :on_sp + def trailing_slash? + last = @lex.last + # Older versions of prism diverged slightly from Ripper in compatibility mode + case last&.type + when :on_sp last.token == TRAILING_SLASH + when :on_tstring_end + true + else + false end end diff --git a/lib/syntax_suggest/core_ext.rb b/lib/syntax_suggest/core_ext.rb index c299627bb7..94f57ba605 100644 --- a/lib/syntax_suggest/core_ext.rb +++ b/lib/syntax_suggest/core_ext.rb @@ -3,24 +3,6 @@ # Ruby 3.2+ has a cleaner way to hook into Ruby that doesn't use `require` if SyntaxError.method_defined?(:detailed_message) module SyntaxSuggest - # Mini String IO [Private] - # - # Acts like a StringIO with reduced API, but without having to require that - # class. - class MiniStringIO - def initialize(isatty: $stderr.isatty) - @string = +"" - @isatty = isatty - end - - attr_reader :isatty - def puts(value = $/, **) - @string << value - end - - attr_reader :string - end - # SyntaxSuggest.module_for_detailed_message [Private] # # Used to monkeypatch SyntaxError via Module.prepend diff --git a/lib/syntax_suggest/mini_stringio.rb b/lib/syntax_suggest/mini_stringio.rb new file mode 100644 index 0000000000..1a82572eeb --- /dev/null +++ b/lib/syntax_suggest/mini_stringio.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module SyntaxSuggest + # Mini String IO [Private] + # + # Acts like a StringIO with reduced API, but without having to require that + # class. + # + # The original codebase emitted directly to $stderr, but now SyntaxError#detailed_message + # needs a string output. To accomplish that we kept the original print infrastructure in place and + # added this class to accumulate the print output into a string. + class MiniStringIO + EMPTY_ARG = Object.new + + def initialize(isatty: $stderr.isatty) + @string = +"" + @isatty = isatty + end + + attr_reader :isatty + def puts(value = EMPTY_ARG, **) + if !value.equal?(EMPTY_ARG) + @string << value + end + @string << $/ + end + + attr_reader :string + end +end diff --git a/lib/syntax_suggest/version.rb b/lib/syntax_suggest/version.rb index d5eff2a17a..db50a1a89a 100644 --- a/lib/syntax_suggest/version.rb +++ b/lib/syntax_suggest/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module SyntaxSuggest - VERSION = "2.0.1" + VERSION = "2.0.3" end diff --git a/lib/tempfile.rb b/lib/tempfile.rb index 2f8a39cfe5..cd512bb1c5 100644 --- a/lib/tempfile.rb +++ b/lib/tempfile.rb @@ -29,7 +29,7 @@ require 'tmpdir' # require 'tempfile' # # # Tempfile.create with a block -# # The filename are choosen automatically. +# # The filename are chosen automatically. # # (You can specify the prefix and suffix of the filename by an optional argument.) # Tempfile.create {|f| # f.puts "foo" @@ -157,7 +157,7 @@ require 'tmpdir' class Tempfile < DelegateClass(File) # The version - VERSION = "0.2.1" + VERSION = "0.3.1" # Creates a file in the underlying file system; # returns a new \Tempfile object based on that file. @@ -550,8 +550,8 @@ end # # Implementation note: # -# The keyword argument +anonymous=true+ is implemented using FILE_SHARE_DELETE on Windows. -# O_TMPFILE is used on Linux. +# The keyword argument <tt>anonymous=true</tt> is implemented using +FILE_SHARE_DELETE+ on Windows. +# +O_TMPFILE+ is used on Linux. # # Related: Tempfile.new. # @@ -564,6 +564,8 @@ def Tempfile.create(basename="", tmpdir=nil, mode: 0, anonymous: false, **option end class << Tempfile +# :stopdoc: + private def create_with_filename(basename="", tmpdir=nil, mode: 0, **options) tmpfile = nil Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts| @@ -593,9 +595,7 @@ private def create_with_filename(basename="", tmpdir=nil, mode: 0, **options) end end -File.open(IO::NULL) do |f| - File.new(f.fileno, autoclose: false, path: "").path -rescue IOError +if RUBY_VERSION < "3.2" module PathAttr # :nodoc: attr_reader :path diff --git a/lib/time.gemspec b/lib/time.gemspec index 4b9f9e1218..73650ab12e 100644 --- a/lib/time.gemspec +++ b/lib/time.gemspec @@ -19,6 +19,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage + spec.metadata["changelog_uri"] = "https://github.com/ruby/time/releases" srcdir, gemspec = File.split(__FILE__) spec.files = Dir.chdir(srcdir) do diff --git a/lib/time.rb b/lib/time.rb index c06e97e74f..e6aab3fa5d 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -24,9 +24,10 @@ require 'date' # :startdoc: +# # class Time - VERSION = "0.4.0" # :nodoc: + VERSION = "0.4.2" # :nodoc: class << Time @@ -79,7 +80,7 @@ class Time # # You must require 'time' to use this method. # - def zone_offset(zone, year=self.now.year) + def zone_offset(zone, year=nil) off = nil zone = zone.upcase if /\A([+-])(\d\d)(:?)(\d\d)(?:\3(\d\d))?\z/ =~ zone @@ -88,10 +89,13 @@ class Time off = zone.to_i * 3600 elsif ZoneOffset.include?(zone) off = ZoneOffset[zone] * 3600 - elsif ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false) - off = t.utc_offset - elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false) - off = t.utc_offset + else + year ||= self.now.year + if ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false) + off = t.utc_offset + elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false) + off = t.utc_offset + end end off end diff --git a/lib/timeout.rb b/lib/timeout.rb index 6448f4f114..e293e3be7c 100644 --- a/lib/timeout.rb +++ b/lib/timeout.rb @@ -20,9 +20,9 @@ module Timeout # The version - VERSION = "0.4.1" + VERSION = "0.6.0" - # Internal error raised to when a timeout is triggered. + # Internal exception raised to when a timeout is triggered. class ExitException < Exception def exception(*) # :nodoc: self @@ -44,12 +44,101 @@ module Timeout end # :stopdoc: - CONDVAR = ConditionVariable.new - QUEUE = Queue.new - QUEUE_MUTEX = Mutex.new - TIMEOUT_THREAD_MUTEX = Mutex.new - @timeout_thread = nil - private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX + + # We keep a private reference so that time mocking libraries won't break Timeout. + GET_TIME = Process.method(:clock_gettime) + if defined?(Ractor.make_shareable) + # Ractor.make_shareable(Method) only works on Ruby 4+ + Ractor.make_shareable(GET_TIME) rescue nil + end + private_constant :GET_TIME + + class State + def initialize + @condvar = ConditionVariable.new + @queue = Queue.new + @queue_mutex = Mutex.new + + @timeout_thread = nil + @timeout_thread_mutex = Mutex.new + end + + if defined?(Ractor.store_if_absent) && defined?(Ractor.shareable?) && Ractor.shareable?(GET_TIME) + # Ractor support if + # 1. Ractor.store_if_absent is available + # 2. Method object can be shareable (4.0~) + def self.instance + Ractor.store_if_absent :timeout_gem_state do + State.new + end + end + else + GLOBAL_STATE = State.new + + def self.instance + GLOBAL_STATE + end + end + + def create_timeout_thread + # Threads unexpectedly inherit the interrupt mask: https://github.com/ruby/timeout/issues/41 + # So reset the interrupt mask to the default one for the timeout thread + Thread.handle_interrupt(Object => :immediate) do + watcher = Thread.new do + requests = [] + while true + until @queue.empty? and !requests.empty? # wait to have at least one request + req = @queue.pop + requests << req unless req.done? + end + closest_deadline = requests.min_by(&:deadline).deadline + + now = 0.0 + @queue_mutex.synchronize do + while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and @queue.empty? + @condvar.wait(@queue_mutex, closest_deadline - now) + end + end + + requests.each do |req| + req.interrupt if req.expired?(now) + end + requests.reject!(&:done?) + end + end + + if !watcher.group.enclosed? && (!defined?(Ractor.main?) || Ractor.main?) + ThreadGroup::Default.add(watcher) + end + + watcher.name = "Timeout stdlib thread" + watcher.thread_variable_set(:"\0__detached_thread__", true) + watcher + end + end + + def ensure_timeout_thread_created + unless @timeout_thread&.alive? + # If the Mutex is already owned we are in a signal handler. + # In that case, just return and let the main thread create the Timeout thread. + return if @timeout_thread_mutex.owned? + + Sync.synchronize @timeout_thread_mutex do + unless @timeout_thread&.alive? + @timeout_thread = create_timeout_thread + end + end + end + end + + def add_request(request) + Sync.synchronize @queue_mutex do + @queue << request + @condvar.signal + end + end + end + private_constant :State class Request attr_reader :deadline @@ -64,6 +153,7 @@ module Timeout @done = false # protected by @mutex end + # Only called by the timeout thread, so does not need Sync.synchronize def done? @mutex.synchronize do @done @@ -74,6 +164,7 @@ module Timeout now >= @deadline end + # Only called by the timeout thread, so does not need Sync.synchronize def interrupt @mutex.synchronize do unless @done @@ -84,87 +175,109 @@ module Timeout end def finished - @mutex.synchronize do + Sync.synchronize @mutex do @done = true end end end private_constant :Request - def self.create_timeout_thread - watcher = Thread.new do - requests = [] - while true - until QUEUE.empty? and !requests.empty? # wait to have at least one request - req = QUEUE.pop - requests << req unless req.done? - end - closest_deadline = requests.min_by(&:deadline).deadline - - now = 0.0 - QUEUE_MUTEX.synchronize do - while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty? - CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now) - end - end - - requests.each do |req| - req.interrupt if req.expired?(now) - end - requests.reject!(&:done?) - end - end - ThreadGroup::Default.add(watcher) unless watcher.group.enclosed? - watcher.name = "Timeout stdlib thread" - watcher.thread_variable_set(:"\0__detached_thread__", true) - watcher - end - private_class_method :create_timeout_thread - - def self.ensure_timeout_thread_created - unless @timeout_thread and @timeout_thread.alive? - TIMEOUT_THREAD_MUTEX.synchronize do - unless @timeout_thread and @timeout_thread.alive? - @timeout_thread = create_timeout_thread - end + module Sync + # Calls mutex.synchronize(&block) but if that fails on CRuby due to being in a trap handler, + # run mutex.synchronize(&block) in a separate Thread instead. + def self.synchronize(mutex, &block) + begin + mutex.synchronize(&block) + rescue ThreadError => e + raise e unless e.message == "can't be called from trap context" + # Workaround CRuby issue https://bugs.ruby-lang.org/issues/19473 + # which raises on Mutex#synchronize in trap handler. + # It's expensive to create a Thread just for this, + # but better than failing. + Thread.new { + mutex.synchronize(&block) + }.join end end end - - # We keep a private reference so that time mocking libraries won't break - # Timeout. - GET_TIME = Process.method(:clock_gettime) - private_constant :GET_TIME + private_constant :Sync # :startdoc: - # Perform an operation in a block, raising an error if it takes longer than + # Perform an operation in a block, raising an exception if it takes longer than # +sec+ seconds to complete. # - # +sec+:: Number of seconds to wait for the block to terminate. Any number - # may be used, including Floats to specify fractional seconds. A + # +sec+:: Number of seconds to wait for the block to terminate. Any non-negative number + # or nil may be used, including Floats to specify fractional seconds. A # value of 0 or +nil+ will execute the block without any timeout. + # Any negative number will raise an ArgumentError. # +klass+:: Exception Class to raise if the block fails to terminate - # in +sec+ seconds. Omitting will use the default, Timeout::Error + # in +sec+ seconds. Omitting will use the default, Timeout::Error. # +message+:: Error message to raise with Exception Class. - # Omitting will use the default, "execution expired" + # Omitting will use the default, <tt>"execution expired"</tt>. # # Returns the result of the block *if* the block completed before - # +sec+ seconds, otherwise throws an exception, based on the value of +klass+. + # +sec+ seconds, otherwise raises an exception, based on the value of +klass+. # - # The exception thrown to terminate the given block cannot be rescued inside - # the block unless +klass+ is given explicitly. However, the block can use - # ensure to prevent the handling of the exception. For that reason, this - # method cannot be relied on to enforce timeouts for untrusted blocks. + # The exception raised to terminate the given block is the given +klass+, or + # Timeout::ExitException if +klass+ is not given. The reason for that behavior + # is that Timeout::Error inherits from RuntimeError and might be caught unexpectedly by +rescue+. + # Timeout::ExitException inherits from Exception so it will only be rescued by <tt>rescue Exception</tt>. + # Note that the Timeout::ExitException is translated to a Timeout::Error once it reaches the Timeout.timeout call, + # so outside that call it will be a Timeout::Error. + # + # In general, be aware that the code block may rescue the exception, and in such a case not respect the timeout. + # Also, the block can use +ensure+ to prevent the handling of the exception. + # For those reasons, this method cannot be relied on to enforce timeouts for untrusted blocks. # # If a scheduler is defined, it will be used to handle the timeout by invoking - # Scheduler#timeout_after. + # Fiber::Scheduler#timeout_after. # # Note that this is both a method of module Timeout, so you can <tt>include # Timeout</tt> into your classes so they have a #timeout method, as well as # a module method, so you can call it directly as Timeout.timeout(). - def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ + # + # ==== Ensuring the exception does not fire inside ensure blocks + # + # When using Timeout.timeout, it can be desirable to ensure the timeout exception does not fire inside an +ensure+ block. + # The simplest and best way to do so is to put the Timeout.timeout call inside the body of the +begin+/+ensure+/+end+: + # + # begin + # Timeout.timeout(sec) { some_long_operation } + # ensure + # cleanup # safe, cannot be interrupted by timeout + # end + # + # If that is not feasible, e.g. if there are +ensure+ blocks inside +some_long_operation+, + # they need to not be interrupted by timeout, and it's not possible to move these ensure blocks outside, + # one can use Thread.handle_interrupt to delay the timeout exception like so: + # + # Thread.handle_interrupt(Timeout::Error => :never) { + # Timeout.timeout(sec, Timeout::Error) do + # setup # timeout cannot happen here, no matter how long it takes + # Thread.handle_interrupt(Timeout::Error => :immediate) { + # some_long_operation # timeout can happen here + # } + # ensure + # cleanup # timeout cannot happen here, no matter how long it takes + # end + # } + # + # An important thing to note is the need to pass an exception +klass+ to Timeout.timeout, + # otherwise it does not work. Specifically, using <tt>Thread.handle_interrupt(Timeout::ExitException => ...)</tt> + # is unsupported and causes subtle errors like raising the wrong exception outside the block, do not use that. + # + # Note that Thread.handle_interrupt is somewhat dangerous because if setup or cleanup hangs + # then the current thread will hang too and the timeout will never fire. + # Also note the block might run for longer than +sec+ seconds: + # e.g. +some_long_operation+ executes for +sec+ seconds + whatever time cleanup takes. + # + # If you want the timeout to only happen on blocking operations, one can use +:on_blocking+ + # instead of +:immediate+. However, that means if the block uses no blocking operations after +sec+ seconds, + # the block will not be interrupted. + def self.timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+ return yield(sec) if sec == nil or sec.zero? + raise ArgumentError, "Timeout sec must be a non-negative number" if 0 > sec message ||= "execution expired" @@ -172,13 +285,12 @@ module Timeout return scheduler.timeout_after(sec, klass || Error, message, &block) end - Timeout.ensure_timeout_thread_created + state = State.instance + state.ensure_timeout_thread_created + perform = Proc.new do |exc| request = Request.new(Thread.current, sec, exc, message) - QUEUE_MUTEX.synchronize do - QUEUE << request - CONDVAR.signal - end + state.add_request(request) begin return yield(sec) ensure @@ -192,5 +304,8 @@ module Timeout Error.handle_timeout(message, &perform) end end - module_function :timeout + + private def timeout(*args, &block) + Timeout.timeout(*args, &block) + end end diff --git a/lib/tmpdir.gemspec b/lib/tmpdir.gemspec index 4c96e5984b..4935d1cb3c 100644 --- a/lib/tmpdir.gemspec +++ b/lib/tmpdir.gemspec @@ -1,6 +1,6 @@ Gem::Specification.new do |spec| spec.name = "tmpdir" - spec.version = "0.2.0" + spec.version = "0.3.1" spec.authors = ["Yukihiro Matsumoto"] spec.email = ["matz@ruby-lang.org"] diff --git a/lib/tmpdir.rb b/lib/tmpdir.rb index a5f10fe9cb..f78fd721b7 100644 --- a/lib/tmpdir.rb +++ b/lib/tmpdir.rb @@ -16,10 +16,6 @@ class Dir # Class variables are inaccessible from non-main Ractor. # And instance variables too, in Ruby 3.0. - # System-wide temporary directory path - SYSTMPDIR = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp') - private_constant :SYSTMPDIR - ## # Returns the operating system's temporary file path. # @@ -27,7 +23,7 @@ class Dir # Dir.tmpdir # => "/tmp" def self.tmpdir - ['TMPDIR', 'TMP', 'TEMP', ['system temporary path', SYSTMPDIR], ['/tmp']*2, ['.']*2].find do |name, dir| + Tmpname::TMPDIR_CANDIDATES.find do |name, dir| unless dir next if !(dir = ENV[name] rescue next) or dir.empty? end @@ -98,13 +94,13 @@ class Dir # FileUtils.remove_entry dir # end # - def self.mktmpdir(prefix_suffix=nil, *rest, **options) + def self.mktmpdir(prefix_suffix=nil, *rest, **options, &block) base = nil path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|path, _, _, d| base = d mkdir(path, 0700) } - if block_given? + if block begin yield path.dup ensure @@ -126,6 +122,18 @@ class Dir module Tmpname # :nodoc: module_function + # System-wide temporary directory path + systmpdir = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp') + + # Temporary directory candidates consisting of environment variable + # names or description and path pairs. + TMPDIR_CANDIDATES = [ + 'TMPDIR', 'TMP', 'TEMP', + ['system temporary path', systmpdir], + %w[/tmp /tmp], + %w[. .], + ].each(&:freeze).freeze + def tmpdir Dir.tmpdir end @@ -149,8 +157,8 @@ class Dir # Generates and yields random names to create a temporary name def create(basename, tmpdir=nil, max_try: nil, **opts) - origdir = tmpdir if tmpdir + origdir = tmpdir = File.path(tmpdir) raise ArgumentError, "empty parent path" if tmpdir.empty? else tmpdir = tmpdir() diff --git a/lib/tsort.gemspec b/lib/tsort.gemspec deleted file mode 100644 index 0e2f110a53..0000000000 --- a/lib/tsort.gemspec +++ /dev/null @@ -1,29 +0,0 @@ -name = File.basename(__FILE__, ".gemspec") -version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir| - break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line| - /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1 - end rescue nil -end - -Gem::Specification.new do |spec| - spec.name = name - spec.version = version - spec.authors = ["Tanaka Akira"] - spec.email = ["akr@fsij.org"] - - spec.summary = %q{Topological sorting using Tarjan's algorithm} - spec.description = %q{Topological sorting using Tarjan's algorithm} - spec.homepage = "https://github.com/ruby/tsort" - spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0") - spec.licenses = ["Ruby", "BSD-2-Clause"] - - spec.metadata["homepage_uri"] = spec.homepage - spec.metadata["source_code_uri"] = spec.homepage - - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } - spec.require_paths = ["lib"] -end diff --git a/lib/tsort.rb b/lib/tsort.rb deleted file mode 100644 index dbaed45415..0000000000 --- a/lib/tsort.rb +++ /dev/null @@ -1,455 +0,0 @@ -# frozen_string_literal: true - -#-- -# tsort.rb - provides a module for topological sorting and strongly connected components. -#++ -# - -# -# TSort implements topological sorting using Tarjan's algorithm for -# strongly connected components. -# -# TSort is designed to be able to be used with any object which can be -# interpreted as a directed graph. -# -# TSort requires two methods to interpret an object as a graph, -# tsort_each_node and tsort_each_child. -# -# * tsort_each_node is used to iterate for all nodes over a graph. -# * tsort_each_child is used to iterate for child nodes of a given node. -# -# The equality of nodes are defined by eql? and hash since -# TSort uses Hash internally. -# -# == A Simple Example -# -# The following example demonstrates how to mix the TSort module into an -# existing class (in this case, Hash). Here, we're treating each key in -# the hash as a node in the graph, and so we simply alias the required -# #tsort_each_node method to Hash's #each_key method. For each key in the -# hash, the associated value is an array of the node's child nodes. This -# choice in turn leads to our implementation of the required #tsort_each_child -# method, which fetches the array of child nodes and then iterates over that -# array using the user-supplied block. -# -# require 'tsort' -# -# class Hash -# include TSort -# alias tsort_each_node each_key -# def tsort_each_child(node, &block) -# fetch(node).each(&block) -# end -# end -# -# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort -# #=> [3, 2, 1, 4] -# -# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components -# #=> [[4], [2, 3], [1]] -# -# == A More Realistic Example -# -# A very simple `make' like tool can be implemented as follows: -# -# require 'tsort' -# -# class Make -# def initialize -# @dep = {} -# @dep.default = [] -# end -# -# def rule(outputs, inputs=[], &block) -# triple = [outputs, inputs, block] -# outputs.each {|f| @dep[f] = [triple]} -# @dep[triple] = inputs -# end -# -# def build(target) -# each_strongly_connected_component_from(target) {|ns| -# if ns.length != 1 -# fs = ns.delete_if {|n| Array === n} -# raise TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") -# end -# n = ns.first -# if Array === n -# outputs, inputs, block = n -# inputs_time = inputs.map {|f| File.mtime f}.max -# begin -# outputs_time = outputs.map {|f| File.mtime f}.min -# rescue Errno::ENOENT -# outputs_time = nil -# end -# if outputs_time == nil || -# inputs_time != nil && outputs_time <= inputs_time -# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i -# block.call -# end -# end -# } -# end -# -# def tsort_each_child(node, &block) -# @dep[node].each(&block) -# end -# include TSort -# end -# -# def command(arg) -# print arg, "\n" -# system arg -# end -# -# m = Make.new -# m.rule(%w[t1]) { command 'date > t1' } -# m.rule(%w[t2]) { command 'date > t2' } -# m.rule(%w[t3]) { command 'date > t3' } -# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } -# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } -# m.build('t5') -# -# == Bugs -# -# * 'tsort.rb' is wrong name because this library uses -# Tarjan's algorithm for strongly connected components. -# Although 'strongly_connected_components.rb' is correct but too long. -# -# == References -# -# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", -# <em>SIAM Journal on Computing</em>, Vol. 1, No. 2, pp. 146-160, June 1972. -# - -module TSort - - VERSION = "0.2.0" - - class Cyclic < StandardError - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.tsort #=> [4, 2, 3, 1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.tsort # raises TSort::Cyclic - # - def tsort - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort(each_node, each_child) - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # If there is a cycle, TSort::Cyclic is raised. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.tsort(each_node, each_child) # raises TSort::Cyclic - # - def self.tsort(each_node, each_child) - tsort_each(each_node, each_child).to_a - end - - # The iterator version of the #tsort method. - # <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #tsort_each returns +nil+. - # If there is a cycle, TSort::Cyclic is raised. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.tsort_each {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def tsort_each(&block) # :yields: node - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.tsort_each(each_node, each_child, &block) - end - - # The iterator version of the TSort.tsort method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.tsort_each(each_node, each_child) {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def self.tsort_each(each_node, each_child) # :yields: node - return to_enum(__method__, each_node, each_child) unless block_given? - - each_strongly_connected_component(each_node, each_child) {|component| - if component.size == 1 - yield component.first - else - raise Cyclic.new("topological sort failed: #{component.inspect}") - end - } - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] - # - def strongly_connected_components - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.strongly_connected_components(each_node, each_child) - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2], [3], [1]] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2, 3], [1]] - # - def self.strongly_connected_components(each_node, each_child) - each_strongly_connected_component(each_node, each_child).to_a - end - - # The iterator version of the #strongly_connected_components method. - # <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to - # <tt><em>obj</em>.strongly_connected_components.each</tt>, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #each_strongly_connected_component returns +nil+. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def each_strongly_connected_component(&block) # :yields: nodes - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - TSort.each_strongly_connected_component(each_node, each_child, &block) - end - - # The iterator version of the TSort.strongly_connected_components method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes - return to_enum(__method__, each_node, each_child) unless block_given? - - id_map = {} - stack = [] - each_node.call {|node| - unless id_map.include? node - each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| - yield c - } - end - } - nil - end - - # Iterates over strongly connected component in the subgraph reachable from - # _node_. - # - # Return value is unspecified. - # - # #each_strongly_connected_component_from doesn't call #tsort_each_node. - # - # class G - # include TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2, 3] - # - def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes - TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) - end - - # Iterates over strongly connected components in a graph. - # The graph is represented by _node_ and _each_child_. - # - # _node_ is the first node. - # _each_child_ should have +call+ method which takes a node argument - # and yields for each child node. - # - # Return value is unspecified. - # - # #TSort.each_strongly_connected_component_from is a class method and - # it doesn't need a class to represent a graph which includes TSort. - # - # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_child = lambda {|n, &b| graph[n].each(&b) } - # TSort.each_strongly_connected_component_from(1, each_child) {|scc| - # p scc - # } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes - return to_enum(__method__, node, each_child, id_map, stack) unless block_given? - - minimum_id = node_id = id_map[node] = id_map.size - stack_length = stack.length - stack << node - - each_child.call(node) {|child| - if id_map.include? child - child_id = id_map[child] - minimum_id = child_id if child_id && child_id < minimum_id - else - sub_minimum_id = - each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| - yield c - } - minimum_id = sub_minimum_id if sub_minimum_id < minimum_id - end - } - - if node_id == minimum_id - component = stack.slice!(stack_length .. -1) - component.each {|n| id_map[n] = nil} - yield component - end - - minimum_id - end - - # Should be implemented by a extended class. - # - # #tsort_each_node is used to iterate for all nodes over a graph. - # - def tsort_each_node # :yields: node - raise NotImplementedError.new - end - - # Should be implemented by a extended class. - # - # #tsort_each_child is used to iterate for child nodes of _node_. - # - def tsort_each_child(node) # :yields: child - raise NotImplementedError.new - end -end diff --git a/lib/unicode_normalize/normalize.rb b/lib/unicode_normalize/normalize.rb index 1caf2cc8c8..0447df8de7 100644 --- a/lib/unicode_normalize/normalize.rb +++ b/lib/unicode_normalize/normalize.rb @@ -82,16 +82,22 @@ module UnicodeNormalize # :nodoc: ## Canonical Ordering def self.canonical_ordering_one(string) - sorting = string.each_char.collect { |c| [c, CLASS_TABLE[c]] } - (sorting.length-2).downto(0) do |i| # almost, but not exactly bubble sort - (0..i).each do |j| - later_class = sorting[j+1].last - if 0<later_class and later_class<sorting[j].last - sorting[j], sorting[j+1] = sorting[j+1], sorting[j] - end + result = '' + unordered = [] + chars = string.chars + n = chars.size + chars.each_with_index do |char, i| + ccc = CLASS_TABLE[char] + if ccc == 0 + unordered.sort!.each { result << chars[it % n] } + unordered.clear + result << char + else + unordered << ccc * n + i end end - return sorting.collect(&:first).join('') + unordered.sort!.each { result << chars[it % n] } + result end ## Normalization Forms for Patterns (not whole Strings) @@ -105,16 +111,22 @@ module UnicodeNormalize # :nodoc: start = nfd_string[0] last_class = CLASS_TABLE[start]-1 accents = '' + result = '' nfd_string[1..-1].each_char do |accent| accent_class = CLASS_TABLE[accent] if last_class<accent_class and composite = COMPOSITION_TABLE[start+accent] start = composite + elsif accent_class == 0 + result << start << accents + start = accent + accents = '' + last_class = -1 else accents << accent last_class = accent_class end end - hangul_comp_one(start+accents) + hangul_comp_one(result+start+accents) end def self.normalize(string, form = :nfc) diff --git a/lib/unicode_normalize/tables.rb b/lib/unicode_normalize/tables.rb index 7448fad13f..dd5d3499b8 100644 --- a/lib/unicode_normalize/tables.rb +++ b/lib/unicode_normalize/tables.rb @@ -1,6 +1,9 @@ # coding: us-ascii # frozen_string_literal: true +Encoding::UNICODE_VERSION == "17.0.0" or + raise "Unicode version mismatch: 17.0.0 expected but #{Encoding::UNICODE_VERSION}" + # automatically generated by template/unicode_norm_gen.tmpl module UnicodeNormalize # :nodoc: @@ -29,7 +32,7 @@ module UnicodeNormalize # :nodoc: "\u0825-\u0827" \ "\u0829-\u082D" \ "\u0859-\u085B" \ - "\u0898-\u089F" \ + "\u0897-\u089F" \ "\u08CA-\u08E1" \ "\u08E3-\u08FF" \ "\u093C" \ @@ -96,7 +99,8 @@ module UnicodeNormalize # :nodoc: "\u1A75-\u1A7C" \ "\u1A7F" \ "\u1AB0-\u1ABD" \ - "\u1ABF-\u1ACE" \ + "\u1ABF-\u1ADD" \ + "\u1AE0-\u1AEB" \ "\u1B34\u1B35" \ "\u1B44" \ "\u1B6B-\u1B73" \ @@ -149,7 +153,9 @@ module UnicodeNormalize # :nodoc: "\u{10A3F}" \ "\u{10AE5}\u{10AE6}" \ "\u{10D24}-\u{10D27}" \ + "\u{10D69}-\u{10D6D}" \ "\u{10EAB}\u{10EAC}" \ + "\u{10EFA}\u{10EFB}" \ "\u{10EFD}-\u{10EFF}" \ "\u{10F46}-\u{10F50}" \ "\u{10F82}-\u{10F85}" \ @@ -171,6 +177,12 @@ module UnicodeNormalize # :nodoc: "\u{11357}" \ "\u{11366}-\u{1136C}" \ "\u{11370}-\u{11374}" \ + "\u{113B8}" \ + "\u{113BB}" \ + "\u{113C2}" \ + "\u{113C5}" \ + "\u{113C7}-\u{113C9}" \ + "\u{113CE}-\u{113D0}" \ "\u{11442}" \ "\u{11446}" \ "\u{1145E}" \ @@ -196,8 +208,11 @@ module UnicodeNormalize # :nodoc: "\u{11D44}\u{11D45}" \ "\u{11D97}" \ "\u{11F41}\u{11F42}" \ + "\u{1611E}-\u{16129}" \ + "\u{1612F}" \ "\u{16AF0}-\u{16AF4}" \ "\u{16B30}-\u{16B36}" \ + "\u{16D67}\u{16D68}" \ "\u{16FF0}\u{16FF1}" \ "\u{1BC9E}" \ "\u{1D165}-\u{1D169}" \ @@ -216,6 +231,11 @@ module UnicodeNormalize # :nodoc: "\u{1E2AE}" \ "\u{1E2EC}-\u{1E2EF}" \ "\u{1E4EC}-\u{1E4EF}" \ + "\u{1E5EE}\u{1E5EF}" \ + "\u{1E6E3}" \ + "\u{1E6E6}" \ + "\u{1E6EE}\u{1E6EF}" \ + "\u{1E6F5}" \ "\u{1E8D0}-\u{1E8D6}" \ "\u{1E944}-\u{1E94A}" \ "]" @@ -441,15 +461,25 @@ module UnicodeNormalize # :nodoc: "\uFB40\uFB41" \ "\uFB43\uFB44" \ "\uFB46-\uFB4E" \ + "\u{105C9}" \ + "\u{105E4}" \ "\u{1109A}" \ "\u{1109C}" \ "\u{110AB}" \ "\u{1112E}\u{1112F}" \ "\u{1134B}\u{1134C}" \ + "\u{11383}" \ + "\u{11385}" \ + "\u{1138E}" \ + "\u{11391}" \ + "\u{113C5}" \ + "\u{113C7}\u{113C8}" \ "\u{114BB}\u{114BC}" \ "\u{114BE}" \ "\u{115BA}\u{115BB}" \ "\u{11938}" \ + "\u{16121}-\u{16128}" \ + "\u{16D68}-\u{16D6A}" \ "\u{1D15E}-\u{1D164}" \ "\u{1D1BB}-\u{1D1C0}" \ "\u{2F800}-\u{2FA1D}" \ @@ -613,14 +643,25 @@ module UnicodeNormalize # :nodoc: "\u30DB" \ "\u30EF-\u30F2" \ "\u30FD" \ + "\u{105D2}" \ + "\u{105DA}" \ "\u{11099}" \ "\u{1109B}" \ "\u{110A5}" \ "\u{11131}\u{11132}" \ "\u{11347}" \ + "\u{11382}" \ + "\u{11384}" \ + "\u{1138B}" \ + "\u{11390}" \ + "\u{113C2}" \ "\u{114B9}" \ "\u{115B8}\u{115B9}" \ "\u{11935}" \ + "\u{1611E}" \ + "\u{16129}" \ + "\u{16D63}" \ + "\u{16D67}" \ "]?#{accents}+" \ "|#{'' # precomposed Hangul syllables }" \ @@ -891,6 +932,10 @@ module UnicodeNormalize # :nodoc: "\u30F4" \ "\u30F7-\u30FA" \ "\u30FD\u30FE" \ + "\u{105C9}" \ + "\u{105D2}" \ + "\u{105DA}" \ + "\u{105E4}" \ "\u{11099}-\u{1109C}" \ "\u{110A5}" \ "\u{110AB}" \ @@ -898,12 +943,23 @@ module UnicodeNormalize # :nodoc: "\u{11131}\u{11132}" \ "\u{11347}" \ "\u{1134B}\u{1134C}" \ + "\u{11382}-\u{11385}" \ + "\u{1138B}" \ + "\u{1138E}" \ + "\u{11390}\u{11391}" \ + "\u{113C2}" \ + "\u{113C5}" \ + "\u{113C7}\u{113C8}" \ "\u{114B9}" \ "\u{114BB}\u{114BC}" \ "\u{114BE}" \ "\u{115B8}-\u{115BB}" \ "\u{11935}" \ "\u{11938}" \ + "\u{1611E}" \ + "\u{16121}-\u{16129}" \ + "\u{16D63}" \ + "\u{16D67}-\u{16D6A}" \ "]?#{accents}+" \ "|#{'' # Hangul syllables with separate trailer }" \ @@ -1410,7 +1466,7 @@ module UnicodeNormalize # :nodoc: "\u3280-\u33FF" \ "\uA69C\uA69D" \ "\uA770" \ - "\uA7F2-\uA7F4" \ + "\uA7F1-\uA7F4" \ "\uA7F8\uA7F9" \ "\uAB5C-\uAB5F" \ "\uAB69" \ @@ -1440,6 +1496,7 @@ module UnicodeNormalize # :nodoc: "\u{10781}-\u{10785}" \ "\u{10787}-\u{107B0}" \ "\u{107B2}-\u{107BA}" \ + "\u{1CCD6}-\u{1CCF9}" \ "\u{1D400}-\u{1D454}" \ "\u{1D456}-\u{1D49C}" \ "\u{1D49E}\u{1D49F}" \ @@ -1789,6 +1846,7 @@ module UnicodeNormalize # :nodoc: "\u0859"=>220, "\u085A"=>220, "\u085B"=>220, + "\u0897"=>230, "\u0898"=>230, "\u0899"=>220, "\u089A"=>220, @@ -1967,6 +2025,33 @@ module UnicodeNormalize # :nodoc: "\u1ACC"=>230, "\u1ACD"=>230, "\u1ACE"=>230, + "\u1ACF"=>230, + "\u1AD0"=>230, + "\u1AD1"=>230, + "\u1AD2"=>230, + "\u1AD3"=>230, + "\u1AD4"=>230, + "\u1AD5"=>230, + "\u1AD6"=>230, + "\u1AD7"=>230, + "\u1AD8"=>230, + "\u1AD9"=>230, + "\u1ADA"=>230, + "\u1ADB"=>230, + "\u1ADC"=>230, + "\u1ADD"=>220, + "\u1AE0"=>230, + "\u1AE1"=>230, + "\u1AE2"=>230, + "\u1AE3"=>230, + "\u1AE4"=>230, + "\u1AE5"=>230, + "\u1AE6"=>220, + "\u1AE7"=>230, + "\u1AE8"=>230, + "\u1AE9"=>230, + "\u1AEA"=>230, + "\u1AEB"=>234, "\u1B34"=>7, "\u1B44"=>9, "\u1B6B"=>230, @@ -2234,8 +2319,15 @@ module UnicodeNormalize # :nodoc: "\u{10D25}"=>230, "\u{10D26}"=>230, "\u{10D27}"=>230, + "\u{10D69}"=>230, + "\u{10D6A}"=>230, + "\u{10D6B}"=>230, + "\u{10D6C}"=>230, + "\u{10D6D}"=>230, "\u{10EAB}"=>230, "\u{10EAC}"=>230, + "\u{10EFA}"=>220, + "\u{10EFB}"=>220, "\u{10EFD}"=>220, "\u{10EFE}"=>220, "\u{10EFF}"=>220, @@ -2286,6 +2378,9 @@ module UnicodeNormalize # :nodoc: "\u{11372}"=>230, "\u{11373}"=>230, "\u{11374}"=>230, + "\u{113CE}"=>9, + "\u{113CF}"=>9, + "\u{113D0}"=>9, "\u{11442}"=>9, "\u{11446}"=>7, "\u{1145E}"=>230, @@ -2313,6 +2408,7 @@ module UnicodeNormalize # :nodoc: "\u{11D97}"=>9, "\u{11F41}"=>9, "\u{11F42}"=>9, + "\u{1612F}"=>9, "\u{16AF0}"=>1, "\u{16AF1}"=>1, "\u{16AF2}"=>1, @@ -2416,6 +2512,13 @@ module UnicodeNormalize # :nodoc: "\u{1E4ED}"=>232, "\u{1E4EE}"=>220, "\u{1E4EF}"=>230, + "\u{1E5EE}"=>230, + "\u{1E5EF}"=>220, + "\u{1E6E3}"=>230, + "\u{1E6E6}"=>230, + "\u{1E6EE}"=>230, + "\u{1E6EF}"=>230, + "\u{1E6F5}"=>230, "\u{1E8D0}"=>220, "\u{1E8D1}"=>220, "\u{1E8D2}"=>220, @@ -3928,6 +4031,8 @@ module UnicodeNormalize # :nodoc: "\uFB4C"=>"\u05D1\u05BF", "\uFB4D"=>"\u05DB\u05BF", "\uFB4E"=>"\u05E4\u05BF", + "\u{105C9}"=>"\u{105D2}\u0307", + "\u{105E4}"=>"\u{105DA}\u0307", "\u{1109A}"=>"\u{11099}\u{110BA}", "\u{1109C}"=>"\u{1109B}\u{110BA}", "\u{110AB}"=>"\u{110A5}\u{110BA}", @@ -3935,12 +4040,30 @@ module UnicodeNormalize # :nodoc: "\u{1112F}"=>"\u{11132}\u{11127}", "\u{1134B}"=>"\u{11347}\u{1133E}", "\u{1134C}"=>"\u{11347}\u{11357}", + "\u{11383}"=>"\u{11382}\u{113C9}", + "\u{11385}"=>"\u{11384}\u{113BB}", + "\u{1138E}"=>"\u{1138B}\u{113C2}", + "\u{11391}"=>"\u{11390}\u{113C9}", + "\u{113C5}"=>"\u{113C2}\u{113C2}", + "\u{113C7}"=>"\u{113C2}\u{113B8}", + "\u{113C8}"=>"\u{113C2}\u{113C9}", "\u{114BB}"=>"\u{114B9}\u{114BA}", "\u{114BC}"=>"\u{114B9}\u{114B0}", "\u{114BE}"=>"\u{114B9}\u{114BD}", "\u{115BA}"=>"\u{115B8}\u{115AF}", "\u{115BB}"=>"\u{115B9}\u{115AF}", "\u{11938}"=>"\u{11935}\u{11930}", + "\u{16121}"=>"\u{1611E}\u{1611E}", + "\u{16122}"=>"\u{1611E}\u{16129}", + "\u{16123}"=>"\u{1611E}\u{1611F}", + "\u{16124}"=>"\u{16129}\u{1611F}", + "\u{16125}"=>"\u{1611E}\u{16120}", + "\u{16126}"=>"\u{1611E}\u{1611E}\u{1611F}", + "\u{16127}"=>"\u{1611E}\u{16129}\u{1611F}", + "\u{16128}"=>"\u{1611E}\u{1611E}\u{16120}", + "\u{16D68}"=>"\u{16D67}\u{16D67}", + "\u{16D69}"=>"\u{16D63}\u{16D67}", + "\u{16D6A}"=>"\u{16D63}\u{16D67}\u{16D67}", "\u{1D15E}"=>"\u{1D157}\u{1D165}", "\u{1D15F}"=>"\u{1D158}\u{1D165}", "\u{1D160}"=>"\u{1D158}\u{1D165}\u{1D16E}", @@ -5839,6 +5962,7 @@ module UnicodeNormalize # :nodoc: "\uA69C"=>"\u044A", "\uA69D"=>"\u044C", "\uA770"=>"\uA76F", + "\uA7F1"=>"S", "\uA7F2"=>"C", "\uA7F3"=>"F", "\uA7F4"=>"Q", @@ -6950,6 +7074,42 @@ module UnicodeNormalize # :nodoc: "\u{107B8}"=>"\u01C2", "\u{107B9}"=>"\u{1DF0A}", "\u{107BA}"=>"\u{1DF1E}", + "\u{1CCD6}"=>"A", + "\u{1CCD7}"=>"B", + "\u{1CCD8}"=>"C", + "\u{1CCD9}"=>"D", + "\u{1CCDA}"=>"E", + "\u{1CCDB}"=>"F", + "\u{1CCDC}"=>"G", + "\u{1CCDD}"=>"H", + "\u{1CCDE}"=>"I", + "\u{1CCDF}"=>"J", + "\u{1CCE0}"=>"K", + "\u{1CCE1}"=>"L", + "\u{1CCE2}"=>"M", + "\u{1CCE3}"=>"N", + "\u{1CCE4}"=>"O", + "\u{1CCE5}"=>"P", + "\u{1CCE6}"=>"Q", + "\u{1CCE7}"=>"R", + "\u{1CCE8}"=>"S", + "\u{1CCE9}"=>"T", + "\u{1CCEA}"=>"U", + "\u{1CCEB}"=>"V", + "\u{1CCEC}"=>"W", + "\u{1CCED}"=>"X", + "\u{1CCEE}"=>"Y", + "\u{1CCEF}"=>"Z", + "\u{1CCF0}"=>"0", + "\u{1CCF1}"=>"1", + "\u{1CCF2}"=>"2", + "\u{1CCF3}"=>"3", + "\u{1CCF4}"=>"4", + "\u{1CCF5}"=>"5", + "\u{1CCF6}"=>"6", + "\u{1CCF7}"=>"7", + "\u{1CCF8}"=>"8", + "\u{1CCF9}"=>"9", "\u{1D400}"=>"A", "\u{1D401}"=>"B", "\u{1D402}"=>"C", @@ -9242,6 +9402,8 @@ module UnicodeNormalize # :nodoc: "\u30F1\u3099"=>"\u30F9", "\u30F2\u3099"=>"\u30FA", "\u30FD\u3099"=>"\u30FE", + "\u{105D2}\u0307"=>"\u{105C9}", + "\u{105DA}\u0307"=>"\u{105E4}", "\u{11099}\u{110BA}"=>"\u{1109A}", "\u{1109B}\u{110BA}"=>"\u{1109C}", "\u{110A5}\u{110BA}"=>"\u{110AB}", @@ -9249,11 +9411,29 @@ module UnicodeNormalize # :nodoc: "\u{11132}\u{11127}"=>"\u{1112F}", "\u{11347}\u{1133E}"=>"\u{1134B}", "\u{11347}\u{11357}"=>"\u{1134C}", + "\u{11382}\u{113C9}"=>"\u{11383}", + "\u{11384}\u{113BB}"=>"\u{11385}", + "\u{1138B}\u{113C2}"=>"\u{1138E}", + "\u{11390}\u{113C9}"=>"\u{11391}", + "\u{113C2}\u{113C2}"=>"\u{113C5}", + "\u{113C2}\u{113B8}"=>"\u{113C7}", + "\u{113C2}\u{113C9}"=>"\u{113C8}", "\u{114B9}\u{114BA}"=>"\u{114BB}", "\u{114B9}\u{114B0}"=>"\u{114BC}", "\u{114B9}\u{114BD}"=>"\u{114BE}", "\u{115B8}\u{115AF}"=>"\u{115BA}", "\u{115B9}\u{115AF}"=>"\u{115BB}", "\u{11935}\u{11930}"=>"\u{11938}", + "\u{1611E}\u{1611E}"=>"\u{16121}", + "\u{1611E}\u{16129}"=>"\u{16122}", + "\u{1611E}\u{1611F}"=>"\u{16123}", + "\u{16129}\u{1611F}"=>"\u{16124}", + "\u{1611E}\u{16120}"=>"\u{16125}", + "\u{16121}\u{1611F}"=>"\u{16126}", + "\u{16122}\u{1611F}"=>"\u{16127}", + "\u{16121}\u{16120}"=>"\u{16128}", + "\u{16D67}\u{16D67}"=>"\u{16D68}", + "\u{16D63}\u{16D67}"=>"\u{16D69}", + "\u{16D69}\u{16D67}"=>"\u{16D6A}", }.freeze end diff --git a/lib/uri/common.rb b/lib/uri/common.rb index 904df10663..0b3bb4f099 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -13,41 +13,49 @@ require_relative "rfc2396_parser" require_relative "rfc3986_parser" module URI + # The default parser instance for RFC 2396. RFC2396_PARSER = RFC2396_Parser.new Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor) + # The default parser instance for RFC 3986. RFC3986_PARSER = RFC3986_Parser.new Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor) + # The default parser instance. DEFAULT_PARSER = RFC3986_PARSER Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor) + # Set the default parser instance. def self.parser=(parser = RFC3986_PARSER) remove_const(:Parser) if defined?(::URI::Parser) const_set("Parser", parser.class) + remove_const(:PARSER) if defined?(::URI::PARSER) + const_set("PARSER", parser) + remove_const(:REGEXP) if defined?(::URI::REGEXP) remove_const(:PATTERN) if defined?(::URI::PATTERN) if Parser == RFC2396_Parser const_set("REGEXP", URI::RFC2396_REGEXP) const_set("PATTERN", URI::RFC2396_REGEXP::PATTERN) - Parser.new.pattern.each_pair do |sym, str| - unless REGEXP::PATTERN.const_defined?(sym) - REGEXP::PATTERN.const_set(sym, str) - end - end end Parser.new.regexp.each_pair do |sym, str| - remove_const(sym) if const_defined?(sym) + remove_const(sym) if const_defined?(sym, false) const_set(sym, str) end end self.parser = RFC3986_PARSER - def self.const_missing(const) - if value = RFC2396_PARSER.regexp[const] - warn "URI::#{const} is obsolete. Use RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + def self.const_missing(const) # :nodoc: + if const == :REGEXP + warn "URI::REGEXP is obsolete. Use URI::RFC2396_REGEXP explicitly.", uplevel: 1 if $VERBOSE + URI::RFC2396_REGEXP + elsif value = RFC2396_PARSER.regexp[const] + warn "URI::#{const} is obsolete. Use URI::RFC2396_PARSER.regexp[#{const.inspect}] explicitly.", uplevel: 1 if $VERBOSE + value + elsif value = RFC2396_Parser.const_get(const) + warn "URI::#{const} is obsolete. Use URI::RFC2396_Parser::#{const} explicitly.", uplevel: 1 if $VERBOSE value else super @@ -86,7 +94,41 @@ module URI module_function :make_components_hash end - module Schemes + module Schemes # :nodoc: + class << self + ReservedChars = ".+-" + EscapedChars = "\u01C0\u01C1\u01C2" + # Use Lo category chars as escaped chars for TruffleRuby, which + # does not allow Symbol categories as identifiers. + + def escape(name) + unless name and name.ascii_only? + return nil + end + name.upcase.tr(ReservedChars, EscapedChars) + end + + def unescape(name) + name.tr(EscapedChars, ReservedChars).encode(Encoding::US_ASCII).upcase + end + + def find(name) + const_get(name, false) if name and const_defined?(name, false) + end + + def register(name, klass) + unless scheme = escape(name) + raise ArgumentError, "invalid character as scheme - #{name}" + end + const_set(scheme, klass) + end + + def list + constants.map { |name| + [unescape(name.to_s), const_get(name)] + }.to_h + end + end end private_constant :Schemes @@ -99,7 +141,7 @@ module URI # Note that after calling String#upcase on +scheme+, it must be a valid # constant name. def self.register_scheme(scheme, klass) - Schemes.const_set(scheme.to_s.upcase, klass) + Schemes.register(scheme, klass) end # Returns a hash of the defined schemes: @@ -117,14 +159,14 @@ module URI # # Related: URI.register_scheme. def self.scheme_list - Schemes.constants.map { |name| - [name.to_s.upcase, Schemes.const_get(name)] - }.to_h + Schemes.list end + # :stopdoc: INITIAL_SCHEMES = scheme_list private_constant :INITIAL_SCHEMES Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor) + # :startdoc: # Returns a new object constructed from the given +scheme+, +arguments+, # and +default+: @@ -143,12 +185,10 @@ module URI # # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # def self.for(scheme, *arguments, default: Generic) - const_name = scheme.to_s.upcase + const_name = Schemes.escape(scheme) uri_class = INITIAL_SCHEMES[const_name] - uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false) - Schemes.const_get(const_name, false) - end + uri_class ||= Schemes.find(const_name) uri_class ||= default return uri_class.new(scheme, *arguments) @@ -190,7 +230,7 @@ module URI # ["fragment", "top"]] # def self.split(uri) - DEFAULT_PARSER.split(uri) + PARSER.split(uri) end # Returns a new \URI object constructed from the given string +uri+: @@ -200,11 +240,11 @@ module URI # URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top') # # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top> # - # It's recommended to first ::escape string +uri+ + # It's recommended to first URI::RFC2396_PARSER.escape string +uri+ # if it may contain invalid URI characters. # def self.parse(uri) - DEFAULT_PARSER.parse(uri) + PARSER.parse(uri) end # Merges the given URI strings +str+ @@ -260,7 +300,7 @@ module URI # def self.extract(str, schemes = nil, &block) # :nodoc: warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.extract(str, schemes, &block) + PARSER.extract(str, schemes, &block) end # @@ -297,14 +337,14 @@ module URI # def self.regexp(schemes = nil)# :nodoc: warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE - DEFAULT_PARSER.make_regexp(schemes) + PARSER.make_regexp(schemes) end TBLENCWWWCOMP_ = {} # :nodoc: 256.times do |i| TBLENCWWWCOMP_[-i.chr] = -('%%%02X' % i) end - TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze + TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze # :nodoc: TBLENCWWWCOMP_[' '] = '+' TBLENCWWWCOMP_.freeze TBLDECWWWCOMP_ = {} # :nodoc: @@ -402,6 +442,8 @@ module URI _decode_uri_component(/%\h\h/, str, enc) end + # Returns a string derived from the given string +str+ with + # URI-encoded characters matching +regexp+ according to +table+. def self._encode_uri_component(regexp, table, str, enc) str = str.to_s.dup if str.encoding != Encoding::ASCII_8BIT @@ -416,6 +458,8 @@ module URI end private_class_method :_encode_uri_component + # Returns a string decoding characters matching +regexp+ from the + # given \URL-encoded string +str+. def self._decode_uri_component(regexp, str, enc) raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc) @@ -854,6 +898,7 @@ module Kernel # Returns a \URI object derived from the given +uri+, # which may be a \URI string or an existing \URI object: # + # require 'uri' # # Returns a new URI. # uri = URI('http://github.com/ruby/ruby') # # => #<URI::HTTP http://github.com/ruby/ruby> @@ -861,6 +906,8 @@ module Kernel # URI(uri) # # => #<URI::HTTP http://github.com/ruby/ruby> # + # You must require 'uri' to use this method. + # def URI(uri) if uri.is_a?(URI::Generic) uri diff --git a/lib/uri/file.rb b/lib/uri/file.rb index 940d361af8..47b5aef067 100644 --- a/lib/uri/file.rb +++ b/lib/uri/file.rb @@ -47,7 +47,7 @@ module URI # :path => '/ruby/src'}) # uri2.to_s # => "file://host.example.com/ruby/src" # - # uri3 = URI::File.build({:path => URI::escape('/path/my file.txt')}) + # uri3 = URI::File.build({:path => URI::RFC2396_PARSER.escape('/path/my file.txt')}) # uri3.to_s # => "file:///path/my%20file.txt" # def self.build(args) diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index d4bfa3b919..6a0f638d76 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -73,7 +73,7 @@ module URI # # At first, tries to create a new URI::Generic instance using # URI::Generic::build. But, if exception URI::InvalidComponentError is raised, - # then it does URI::Escape.escape all URI components and tries again. + # then it does URI::RFC2396_PARSER.escape all URI components and tries again. # def self.build2(args) begin @@ -126,9 +126,9 @@ module URI end end else - component = self.class.component rescue ::URI::Generic::COMPONENT + component = self.component rescue ::URI::Generic::COMPONENT raise ArgumentError, - "expected Array of or Hash of components of #{self.class} (#{component.join(', ')})" + "expected Array of or Hash of components of #{self} (#{component.join(', ')})" end tmp << nil @@ -186,18 +186,18 @@ module URI if arg_check self.scheme = scheme - self.userinfo = userinfo self.hostname = host self.port = port + self.userinfo = userinfo self.path = path self.query = query self.opaque = opaque self.fragment = fragment else self.set_scheme(scheme) - self.set_userinfo(userinfo) self.set_host(host) self.set_port(port) + self.set_userinfo(userinfo) self.set_path(path) self.query = query self.set_opaque(opaque) @@ -284,7 +284,7 @@ module URI # Returns the parser to be used. # - # Unless a URI::Parser is defined, DEFAULT_PARSER is used. + # Unless the +parser+ is defined, DEFAULT_PARSER is used. # def parser if !defined?(@parser) || !@parser @@ -315,7 +315,7 @@ module URI end # - # Checks the scheme +v+ component against the URI::Parser Regexp for :SCHEME. + # Checks the scheme +v+ component against the +parser+ Regexp for :SCHEME. # def check_scheme(v) if v && parser.regexp[:SCHEME] !~ v @@ -385,7 +385,7 @@ module URI # # Checks the user +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -409,7 +409,7 @@ module URI # # Checks the password +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :USERINFO. + # and against the +parser+ Regexp for :USERINFO. # # Can not have a registry or opaque component defined, # with a user component defined. @@ -466,7 +466,7 @@ module URI # # uri = URI.parse("http://john:S3nsit1ve@my.example.com") # uri.user = "sam" - # uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com" + # uri.to_s #=> "http://sam@my.example.com" # def user=(user) check_user(user) @@ -511,7 +511,7 @@ module URI user, password = split_userinfo(user) end @user = user - @password = password if password + @password = password [@user, @password] end @@ -522,7 +522,7 @@ module URI # See also URI::Generic.user=. # def set_user(v) - set_userinfo(v, @password) + set_userinfo(v, nil) v end protected :set_user @@ -574,6 +574,12 @@ module URI @password end + # Returns the authority info (array of user, password, host and + # port), if any is set. Or returns +nil+. + def authority + return @user, @password, @host, @port if @user || @password || @host || @port + end + # Returns the user component after URI decoding. def decoded_user URI.decode_uri_component(@user) if @user @@ -586,7 +592,7 @@ module URI # # Checks the host +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :HOST. + # and against the +parser+ Regexp for :HOST. # # Can not have a registry or opaque component defined, # with a host component defined. @@ -615,6 +621,13 @@ module URI end protected :set_host + # Protected setter for the authority info (+user+, +password+, +host+ + # and +port+). If +port+ is +nil+, +default_port+ will be set. + # + protected def set_authority(user, password, host, port = nil) + @user, @password, @host, @port = user, password, host, port || self.default_port + end + # # == Args # @@ -639,6 +652,7 @@ module URI def host=(v) check_host(v) set_host(v) + set_userinfo(nil) v end @@ -675,7 +689,7 @@ module URI # # Checks the port +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp for :PORT. + # and against the +parser+ Regexp for :PORT. # # Can not have a registry or opaque component defined, # with a port component defined. @@ -729,6 +743,7 @@ module URI def port=(v) check_port(v) set_port(v) + set_userinfo(nil) port end @@ -737,18 +752,18 @@ module URI end private :check_registry - def set_registry(v) #:nodoc: + def set_registry(v) # :nodoc: raise InvalidURIError, "cannot set registry" end protected :set_registry - def registry=(v) + def registry=(v) # :nodoc: raise InvalidURIError, "cannot set registry" end # # Checks the path +v+ component for RFC2396 compliance - # and against the URI::Parser Regexp + # and against the +parser+ Regexp # for :ABS_PATH and :REL_PATH. # # Can not have a opaque component defined, @@ -853,7 +868,7 @@ module URI # # Checks the opaque +v+ component for RFC2396 compliance and - # against the URI::Parser Regexp for :OPAQUE. + # against the +parser+ Regexp for :OPAQUE. # # Can not have a host, port, user, or path component defined, # with an opaque component defined. @@ -905,7 +920,7 @@ module URI end # - # Checks the fragment +v+ component against the URI::Parser Regexp for :FRAGMENT. + # Checks the fragment +v+ component against the +parser+ Regexp for :FRAGMENT. # # # == Args @@ -1121,7 +1136,7 @@ module URI base = self.dup - authority = rel.userinfo || rel.host || rel.port + authority = rel.authority # RFC2396, Section 5.2, 2) if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query @@ -1133,17 +1148,14 @@ module URI base.fragment=(nil) # RFC2396, Section 5.2, 4) - if !authority - base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path - else - # RFC2396, Section 5.2, 4) - base.set_path(rel.path) if rel.path + if authority + base.set_authority(*authority) + base.set_path(rel.path) + elsif base.path && rel.path + base.set_path(merge_path(base.path, rel.path)) end # RFC2396, Section 5.2, 7) - base.set_userinfo(rel.userinfo) if rel.userinfo - base.set_host(rel.host) if rel.host - base.set_port(rel.port) if rel.port base.query = rel.query if rel.query base.fragment=(rel.fragment) if rel.fragment @@ -1392,10 +1404,12 @@ module URI end end + # Returns the hash value. def hash self.component_ary.hash end + # Compares with _oth_ for Hash. def eql?(oth) self.class == oth.class && parser == oth.parser && @@ -1438,7 +1452,7 @@ module URI end end - def inspect + def inspect # :nodoc: "#<#{self.class} #{self}>" end @@ -1526,7 +1540,7 @@ module URI else unless proxy_uri = env[name] if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + warn 'The environment variable HTTP_PROXY is discouraged. Please use http_proxy instead.', uplevel: 1 end end end diff --git a/lib/uri/http.rb b/lib/uri/http.rb index 900b132c8c..3c41cd4e93 100644 --- a/lib/uri/http.rb +++ b/lib/uri/http.rb @@ -61,6 +61,18 @@ module URI super(tmp) end + # Do not allow empty host names, as they are not allowed by RFC 3986. + def check_host(v) + ret = super + + if ret && v.empty? + raise InvalidComponentError, + "bad component(expected host component): #{v}" + end + + ret + end + # # == Description # diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index a56ca34267..cefd126cc6 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -67,7 +67,7 @@ module URI # # == Synopsis # - # URI::Parser.new([opts]) + # URI::RFC2396_Parser.new([opts]) # # == Args # @@ -86,7 +86,7 @@ module URI # # == Examples # - # p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") + # p = URI::RFC2396_Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})") # u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP http://example.jp/%uABCD> # URI.parse(u.to_s) #=> raises URI::InvalidURIError # @@ -108,12 +108,12 @@ module URI # The Hash of patterns. # - # See also URI::Parser.initialize_pattern. + # See also #initialize_pattern. attr_reader :pattern # The Hash of Regexp. # - # See also URI::Parser.initialize_regexp. + # See also #initialize_regexp. attr_reader :regexp # Returns a split URI against +regexp[:ABS_URI]+. @@ -202,8 +202,7 @@ module URI # # == Usage # - # p = URI::Parser.new - # p.parse("ldap://ldap.example.com/dc=example?user=john") + # URI::RFC2396_PARSER.parse("ldap://ldap.example.com/dc=example?user=john") # #=> #<URI::LDAP ldap://ldap.example.com/dc=example?user=john> # def parse(uri) @@ -244,7 +243,7 @@ module URI # If no +block+ given, then returns the result, # else it calls +block+ for each element in result. # - # See also URI::Parser.make_regexp. + # See also #make_regexp. # def extract(str, schemes = nil) if block_given? @@ -263,7 +262,7 @@ module URI unless schemes @regexp[:ABS_URI_REF] else - /(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x + /(?=(?i:#{Regexp.union(*schemes).source}):)#{@pattern[:X_ABS_URI]}/x end end @@ -321,14 +320,14 @@ module URI str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) } end - @@to_s = Kernel.instance_method(:to_s) - if @@to_s.respond_to?(:bind_call) - def inspect - @@to_s.bind_call(self) + TO_S = Kernel.instance_method(:to_s) # :nodoc: + if TO_S.respond_to?(:bind_call) + def inspect # :nodoc: + TO_S.bind_call(self) end else - def inspect - @@to_s.bind(self).call + def inspect # :nodoc: + TO_S.bind(self).call end end @@ -524,6 +523,8 @@ module URI ret end + # Returns +uri+ as-is if it is URI, or convert it to URI if it is + # a String. def convert_to_uri(uri) if uri.is_a?(URI::Generic) uri @@ -536,4 +537,11 @@ module URI end end # class Parser + + # Backward compatibility for URI::REGEXP::PATTERN::* + RFC2396_Parser.new.pattern.each_pair do |sym, str| + unless RFC2396_REGEXP::PATTERN.const_defined?(sym, false) + RFC2396_REGEXP::PATTERN.const_set(sym, str) + end + end end # module URI diff --git a/lib/uri/rfc3986_parser.rb b/lib/uri/rfc3986_parser.rb index 4000f1357f..0b5f0c4488 100644 --- a/lib/uri/rfc3986_parser.rb +++ b/lib/uri/rfc3986_parser.rb @@ -142,25 +142,25 @@ module URI # Compatibility for RFC2396 parser def extract(str, schemes = nil, &block) # :nodoc: - warn "URI::RFC3986_PARSER.extract is obsoleted. Use URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE + warn "URI::RFC3986_PARSER.extract is obsolete. Use URI::RFC2396_PARSER.extract explicitly.", uplevel: 1 if $VERBOSE RFC2396_PARSER.extract(str, schemes, &block) end # Compatibility for RFC2396 parser def make_regexp(schemes = nil) # :nodoc: - warn "URI::RFC3986_PARSER.make_regexp is obsoleted. Use URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE + warn "URI::RFC3986_PARSER.make_regexp is obsolete. Use URI::RFC2396_PARSER.make_regexp explicitly.", uplevel: 1 if $VERBOSE RFC2396_PARSER.make_regexp(schemes) end # Compatibility for RFC2396 parser def escape(str, unsafe = nil) # :nodoc: - warn "URI::RFC3986_PARSER.escape is obsoleted. Use URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE + warn "URI::RFC3986_PARSER.escape is obsolete. Use URI::RFC2396_PARSER.escape explicitly.", uplevel: 1 if $VERBOSE unsafe ? RFC2396_PARSER.escape(str, unsafe) : RFC2396_PARSER.escape(str) end # Compatibility for RFC2396 parser def unescape(str, escaped = nil) # :nodoc: - warn "URI::RFC3986_PARSER.unescape is obsoleted. Use URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE + warn "URI::RFC3986_PARSER.unescape is obsolete. Use URI::RFC2396_PARSER.unescape explicitly.", uplevel: 1 if $VERBOSE escaped ? RFC2396_PARSER.unescape(str, escaped) : RFC2396_PARSER.unescape(str) end diff --git a/lib/uri/uri.gemspec b/lib/uri/uri.gemspec index 9cf0a71196..0d0f897cba 100644 --- a/lib/uri/uri.gemspec +++ b/lib/uri/uri.gemspec @@ -30,8 +30,11 @@ Gem::Specification.new do |spec| # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. + gemspec = File.basename(__FILE__) spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject do |file| + (file == gemspec) || file.start_with?(*%w[bin/ test/ rakelib/ .github/ .gitignore Gemfile Rakefile]) + end end spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } diff --git a/lib/uri/version.rb b/lib/uri/version.rb index bfe3f47670..1f810602eb 100644 --- a/lib/uri/version.rb +++ b/lib/uri/version.rb @@ -1,6 +1,6 @@ module URI # :stopdoc: - VERSION_CODE = '001301'.freeze - VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze + VERSION = '1.1.1'.freeze + VERSION_CODE = VERSION.split('.').map{|s| s.rjust(2, '0')}.join.freeze # :startdoc: end diff --git a/lib/weakref.gemspec b/lib/weakref.gemspec index 03893f77e6..9d5c79851e 100644 --- a/lib/weakref.gemspec +++ b/lib/weakref.gemspec @@ -26,8 +26,6 @@ Gem::Specification.new do |spec| spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z 2>#{IO::NULL}`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end - spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] spec.add_dependency "delegate" diff --git a/lib/weakref.rb b/lib/weakref.rb index a8da39a26a..c7274f9664 100644 --- a/lib/weakref.rb +++ b/lib/weakref.rb @@ -17,7 +17,8 @@ require "delegate" # class WeakRef < Delegator - VERSION = "0.1.3" + # The version string + VERSION = "0.1.4" ## # RefError is raised when a referenced object has been recycled by the @@ -41,7 +42,7 @@ class WeakRef < Delegator super end - def __getobj__ # :nodoc: + def __getobj__(&_block) # :nodoc: @@__map[self] or defined?(@delegate_sd_obj) ? @delegate_sd_obj : Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(2)) end diff --git a/lib/yaml.rb b/lib/yaml.rb index b2669899dd..c6f0f89fd2 100644 --- a/lib/yaml.rb +++ b/lib/yaml.rb @@ -66,5 +66,6 @@ YAML = Psych # :nodoc: # # Syck can also be found on github: https://github.com/ruby/syck module YAML - LOADER_VERSION = "0.3.0" + # The version of YAML wrapper + LOADER_VERSION = "0.4.0" end |
