mirror of
https://github.com/mistergibson/gxg-web-client.git
synced 2026-10-19 07:39:44 -07:00
Initial Commit
This commit is contained in:
parent
356ad3315b
commit
81daf6f387
283 changed files with 130672 additions and 1 deletions
184
gxg/standard/English.rb
Normal file
184
gxg/standard/English.rb
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# frozen_string_literal: true
|
||||
# Include the English library file in a Ruby script, and you can
|
||||
# reference the global variables such as \VAR{\$\_} using less
|
||||
# cryptic names, listed in the following table.% \vref{tab:english}.
|
||||
#
|
||||
# Without 'English':
|
||||
#
|
||||
# $\ = ' -- '
|
||||
# "waterbuffalo" =~ /buff/
|
||||
# print $', $$, "\n"
|
||||
#
|
||||
# With English:
|
||||
#
|
||||
# require "English"
|
||||
#
|
||||
# $OUTPUT_FIELD_SEPARATOR = ' -- '
|
||||
# "waterbuffalo" =~ /buff/
|
||||
# print $POSTMATCH, $PID, "\n"
|
||||
#
|
||||
# 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:: $~
|
||||
# $IGNORECASE:: $=
|
||||
# $ARGV:: $*
|
||||
# $MATCH:: $&
|
||||
# $PREMATCH:: $`
|
||||
# $POSTMATCH:: $'
|
||||
# $LAST_PAREN_MATCH:: $+
|
||||
#
|
||||
module English end if false
|
||||
|
||||
# The exception object passed to +raise+.
|
||||
alias $ERROR_INFO $!
|
||||
|
||||
# The stack backtrace generated by the last
|
||||
# exception. <tt>See Kernel.caller</tt> for details. Thread local.
|
||||
alias $ERROR_POSITION $@
|
||||
|
||||
# The default separator pattern used by <tt>String.split</tt>. May be
|
||||
# set from the command line using the <tt>-F</tt> flag.
|
||||
alias $FS $;
|
||||
|
||||
# The default separator pattern used by <tt>String.split</tt>. May be
|
||||
# set from the command line using the <tt>-F</tt> flag.
|
||||
alias $FIELD_SEPARATOR $;
|
||||
|
||||
# The separator string output between the parameters to methods such
|
||||
# as <tt>Kernel.print</tt> and <tt>Array.join</tt>. Defaults to +nil+,
|
||||
# which adds no text.
|
||||
alias $OFS $,
|
||||
|
||||
# The separator string output between the parameters to methods such
|
||||
# as <tt>Kernel.print</tt> and <tt>Array.join</tt>. Defaults to +nil+,
|
||||
# which adds no text.
|
||||
alias $OUTPUT_FIELD_SEPARATOR $,
|
||||
|
||||
# The input record separator (newline by default). This is the value
|
||||
# that routines such as <tt>Kernel.gets</tt> use to determine record
|
||||
# boundaries. If set to +nil+, +gets+ will read the entire file.
|
||||
alias $RS $/
|
||||
|
||||
# The input record separator (newline by default). This is the value
|
||||
# that routines such as <tt>Kernel.gets</tt> use to determine record
|
||||
# boundaries. If set to +nil+, +gets+ will read the entire file.
|
||||
alias $INPUT_RECORD_SEPARATOR $/
|
||||
|
||||
# The string appended to the output of every call to methods such as
|
||||
# <tt>Kernel.print</tt> and <tt>IO.write</tt>. The default value is
|
||||
# +nil+.
|
||||
alias $ORS $\
|
||||
|
||||
# The string appended to the output of every call to methods such as
|
||||
# <tt>Kernel.print</tt> and <tt>IO.write</tt>. The default value is
|
||||
# +nil+.
|
||||
alias $OUTPUT_RECORD_SEPARATOR $\
|
||||
|
||||
# The number of the last line read from the current input file.
|
||||
alias $INPUT_LINE_NUMBER $.
|
||||
|
||||
# The number of the last line read from the current input file.
|
||||
alias $NR $.
|
||||
|
||||
# The last line read by <tt>Kernel.gets</tt> or
|
||||
# <tt>Kernel.readline</tt>. Many string-related functions in the
|
||||
# +Kernel+ module operate on <tt>$_</tt> by default. The variable is
|
||||
# local to the current scope. Thread local.
|
||||
alias $LAST_READ_LINE $_
|
||||
|
||||
# The destination of output for <tt>Kernel.print</tt>
|
||||
# and <tt>Kernel.printf</tt>. The default value is
|
||||
# <tt>$stdout</tt>.
|
||||
alias $DEFAULT_OUTPUT $>
|
||||
|
||||
# An object that provides access to the concatenation
|
||||
# of the contents of all the files
|
||||
# given as command-line arguments, or <tt>$stdin</tt>
|
||||
# (in the case where there are no
|
||||
# arguments). <tt>$<</tt> supports methods similar to a
|
||||
# +File+ object:
|
||||
# +inmode+, +close+,
|
||||
# <tt>closed?</tt>, +each+,
|
||||
# <tt>each_byte</tt>, <tt>each_line</tt>,
|
||||
# +eof+, <tt>eof?</tt>, +file+,
|
||||
# +filename+, +fileno+,
|
||||
# +getc+, +gets+, +lineno+,
|
||||
# <tt>lineno=</tt>, +path+,
|
||||
# +pos+, <tt>pos=</tt>,
|
||||
# +read+, +readchar+,
|
||||
# +readline+, +readlines+,
|
||||
# +rewind+, +seek+, +skip+,
|
||||
# +tell+, <tt>to_a</tt>, <tt>to_i</tt>,
|
||||
# <tt>to_io</tt>, <tt>to_s</tt>, along with the
|
||||
# methods in +Enumerable+. The method +file+
|
||||
# returns a +File+ object for the file currently
|
||||
# being read. This may change as <tt>$<</tt> reads
|
||||
# through the files on the command line. Read only.
|
||||
alias $DEFAULT_INPUT $<
|
||||
|
||||
# The process number of the program being executed. Read only.
|
||||
alias $PID $$
|
||||
|
||||
# The process number of the program being executed. Read only.
|
||||
alias $PROCESS_ID $$
|
||||
|
||||
# The exit status of the last child process to terminate. Read
|
||||
# only. Thread local.
|
||||
alias $CHILD_STATUS $?
|
||||
|
||||
# A +MatchData+ object that encapsulates the results of a successful
|
||||
# pattern match. The variables <tt>$&</tt>, <tt>$`</tt>, <tt>$'</tt>,
|
||||
# and <tt>$1</tt> to <tt>$9</tt> are all derived from
|
||||
# <tt>$~</tt>. Assigning to <tt>$~</tt> changes the values of these
|
||||
# derived variables. This variable is local to the current
|
||||
# scope.
|
||||
alias $LAST_MATCH_INFO $~
|
||||
|
||||
# This variable is no longer effective. Deprecated.
|
||||
alias $IGNORECASE $=
|
||||
|
||||
# An array of strings containing the command-line
|
||||
# options from the invocation of the program. Options
|
||||
# used by the Ruby interpreter will have been
|
||||
# removed. Read only. Also known simply as +ARGV+.
|
||||
alias $ARGV $*
|
||||
|
||||
# The string matched by the last successful pattern
|
||||
# match. This variable is local to the current
|
||||
# scope. Read only.
|
||||
alias $MATCH $&
|
||||
|
||||
# The string preceding the match in the last
|
||||
# successful pattern match. This variable is local to
|
||||
# the current scope. Read only.
|
||||
alias $PREMATCH $`
|
||||
|
||||
# The string following the match in the last
|
||||
# successful pattern match. This variable is local to
|
||||
# the current scope. Read only.
|
||||
alias $POSTMATCH $'
|
||||
|
||||
# The contents of the highest-numbered group matched in the last
|
||||
# successful pattern match. Thus, in <tt>"cat" =~ /(c|a)(t|z)/</tt>,
|
||||
# <tt>$+</tt> will be set to "t". This variable is local to the
|
||||
# current scope. Read only.
|
||||
alias $LAST_PAREN_MATCH $+
|
||||
132
gxg/standard/abbrev.rb
Normal file
132
gxg/standard/abbrev.rb
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# frozen_string_literal: true
|
||||
#--
|
||||
# Copyright (c) 2001,2003 Akinori MUSHA <knu@iDaemons.org>
|
||||
#
|
||||
# All rights reserved. You can redistribute and/or modify it under
|
||||
# the same terms as Ruby.
|
||||
#
|
||||
# $Idaemons: /home/cvs/rb/abbrev.rb,v 1.2 2001/05/30 09:37:45 knu Exp $
|
||||
# $RoughId: abbrev.rb,v 1.4 2003/10/14 19:45:42 knu Exp $
|
||||
# $Id: abbrev.rb 57585 2017-02-09 16:12:00Z kazu $
|
||||
#++
|
||||
|
||||
##
|
||||
# Calculates the set of unambiguous abbreviations for a given set of strings.
|
||||
#
|
||||
# require 'abbrev'
|
||||
# require 'pp'
|
||||
#
|
||||
# pp Abbrev.abbrev(['ruby'])
|
||||
# #=> {"ruby"=>"ruby", "rub"=>"ruby", "ru"=>"ruby", "r"=>"ruby"}
|
||||
#
|
||||
# pp Abbrev.abbrev(%w{ ruby rules })
|
||||
#
|
||||
# _Generates:_
|
||||
# { "ruby" => "ruby",
|
||||
# "rub" => "ruby",
|
||||
# "rules" => "rules",
|
||||
# "rule" => "rules",
|
||||
# "rul" => "rules" }
|
||||
#
|
||||
# It also provides an array core extension, Array#abbrev.
|
||||
#
|
||||
# pp %w{ summer winter }.abbrev
|
||||
#
|
||||
# _Generates:_
|
||||
# { "summer" => "summer",
|
||||
# "summe" => "summer",
|
||||
# "summ" => "summer",
|
||||
# "sum" => "summer",
|
||||
# "su" => "summer",
|
||||
# "s" => "summer",
|
||||
# "winter" => "winter",
|
||||
# "winte" => "winter",
|
||||
# "wint" => "winter",
|
||||
# "win" => "winter",
|
||||
# "wi" => "winter",
|
||||
# "w" => "winter" }
|
||||
|
||||
module Abbrev
|
||||
|
||||
# Given a set of strings, calculate the set of unambiguous abbreviations for
|
||||
# those strings, and return a hash where the keys are all the possible
|
||||
# abbreviations and the values are the full strings.
|
||||
#
|
||||
# Thus, given +words+ is "car" and "cone", the keys pointing to "car" would
|
||||
# be "ca" and "car", while those pointing to "cone" would be "co", "con", and
|
||||
# "cone".
|
||||
#
|
||||
# require 'abbrev'
|
||||
#
|
||||
# Abbrev.abbrev(%w{ car cone })
|
||||
# #=> {"ca"=>"car", "con"=>"cone", "co"=>"cone", "car"=>"car", "cone"=>"cone"}
|
||||
#
|
||||
# The optional +pattern+ parameter is a pattern or a string. Only input
|
||||
# strings that match the pattern or start with the string are included in the
|
||||
# output hash.
|
||||
#
|
||||
# Abbrev.abbrev(%w{car box cone crab}, /b/)
|
||||
# #=> {"box"=>"box", "bo"=>"box", "b"=>"box", "crab" => "crab"}
|
||||
#
|
||||
# Abbrev.abbrev(%w{car box cone}, 'ca')
|
||||
# #=> {"car"=>"car", "ca"=>"car"}
|
||||
def abbrev(words, pattern = nil)
|
||||
table = {}
|
||||
seen = Hash.new(0)
|
||||
|
||||
if pattern.is_a?(String)
|
||||
pattern = /\A#{Regexp.quote(pattern)}/ # regard as a prefix
|
||||
end
|
||||
|
||||
words.each do |word|
|
||||
next if word.empty?
|
||||
word.size.downto(1) { |len|
|
||||
abbrev = word[0...len]
|
||||
|
||||
next if pattern && pattern !~ abbrev
|
||||
|
||||
case seen[abbrev] += 1
|
||||
when 1
|
||||
table[abbrev] = word
|
||||
when 2
|
||||
table.delete(abbrev)
|
||||
else
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
words.each do |word|
|
||||
next if pattern && pattern !~ word
|
||||
|
||||
table[word] = word
|
||||
end
|
||||
|
||||
table
|
||||
end
|
||||
|
||||
module_function :abbrev
|
||||
end
|
||||
|
||||
class Array
|
||||
# Calculates the set of unambiguous abbreviations for the strings in +self+.
|
||||
#
|
||||
# require 'abbrev'
|
||||
# %w{ car cone }.abbrev
|
||||
# #=> {"car"=>"car", "ca"=>"car", "cone"=>"cone", "con"=>"cone", "co"=>"cone"}
|
||||
#
|
||||
# The optional +pattern+ parameter is a pattern or a string. Only input
|
||||
# strings that match the pattern or start with the string are included in the
|
||||
# output hash.
|
||||
#
|
||||
# %w{ fast boat day }.abbrev(/^.a/)
|
||||
# #=> {"fast"=>"fast", "fas"=>"fast", "fa"=>"fast", "day"=>"day", "da"=>"day"}
|
||||
#
|
||||
# Abbrev.abbrev(%w{car box cone}, "ca")
|
||||
# #=> {"car"=>"car", "ca"=>"car"}
|
||||
#
|
||||
# See also Abbrev.abbrev
|
||||
def abbrev(pattern = nil)
|
||||
Abbrev::abbrev(self, pattern)
|
||||
end
|
||||
end
|
||||
435
gxg/standard/cmath.rb
Normal file
435
gxg/standard/cmath.rb
Normal file
|
|
@ -0,0 +1,435 @@
|
|||
# frozen_string_literal: true
|
||||
##
|
||||
# = Trigonometric and transcendental functions for complex numbers.
|
||||
#
|
||||
# CMath is a library that provides trigonometric and transcendental
|
||||
# functions for complex numbers. The functions in this module accept
|
||||
# integers, floating-point numbers or complex numbers as arguments.
|
||||
#
|
||||
# Note that the selection of functions is similar, but not identical,
|
||||
# to that in module math. The reason for having two modules is that
|
||||
# some users aren't interested in complex numbers, and perhaps don't
|
||||
# even know what they are. They would rather have Math.sqrt(-1) raise
|
||||
# an exception than return a complex number.
|
||||
#
|
||||
# For more information you can see Complex class.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# To start using this library, simply require cmath library:
|
||||
#
|
||||
# require "cmath"
|
||||
|
||||
module CMath
|
||||
|
||||
include Math
|
||||
|
||||
# Backup of Math is needed because mathn.rb replaces Math with CMath.
|
||||
RealMath = Math # :nodoc:
|
||||
private_constant :RealMath
|
||||
|
||||
%w[
|
||||
exp
|
||||
log
|
||||
log2
|
||||
log10
|
||||
sqrt
|
||||
cbrt
|
||||
sin
|
||||
cos
|
||||
tan
|
||||
sinh
|
||||
cosh
|
||||
tanh
|
||||
asin
|
||||
acos
|
||||
atan
|
||||
atan2
|
||||
asinh
|
||||
acosh
|
||||
atanh
|
||||
].each do |meth|
|
||||
define_method(meth + '!') do |*args, &block|
|
||||
warn("CMath##{meth}! is deprecated; use CMath##{meth} or Math##{meth}", uplevel: 1) if $VERBOSE
|
||||
RealMath.send(meth, *args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Math::E raised to the +z+ power
|
||||
#
|
||||
# CMath.exp(1.i * Math::PI) #=> (-1.0+1.2246467991473532e-16i)
|
||||
def exp(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.exp(z)
|
||||
else
|
||||
ere = RealMath.exp(z.real)
|
||||
Complex(ere * RealMath.cos(z.imag),
|
||||
ere * RealMath.sin(z.imag))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the natural logarithm of Complex. If a second argument is given,
|
||||
# it will be the base of logarithm.
|
||||
#
|
||||
# CMath.log(1 + 4i) #=> (1.416606672028108+1.3258176636680326i)
|
||||
# CMath.log(1 + 4i, 10) #=> (0.6152244606891369+0.5757952953408879i)
|
||||
def log(z, b=::Math::E)
|
||||
begin
|
||||
if z.real? && z >= 0 && b >= 0
|
||||
RealMath.log(z, b)
|
||||
else
|
||||
Complex(RealMath.log(z.abs), z.arg) / log(b)
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the base 2 logarithm of +z+
|
||||
#
|
||||
# CMath.log2(-1) => (0.0+4.532360141827194i)
|
||||
def log2(z)
|
||||
begin
|
||||
if z.real? and z >= 0
|
||||
RealMath.log2(z)
|
||||
else
|
||||
log(z) / RealMath.log(2)
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the base 10 logarithm of +z+
|
||||
#
|
||||
# CMath.log10(-1) #=> (0.0+1.3643763538418412i)
|
||||
def log10(z)
|
||||
begin
|
||||
if z.real? and z >= 0
|
||||
RealMath.log10(z)
|
||||
else
|
||||
log(z) / RealMath.log(10)
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the non-negative square root of Complex.
|
||||
#
|
||||
# CMath.sqrt(-1 + 0i) #=> 0.0+1.0i
|
||||
def sqrt(z)
|
||||
begin
|
||||
if z.real?
|
||||
if z < 0
|
||||
Complex(0, RealMath.sqrt(-z))
|
||||
else
|
||||
RealMath.sqrt(z)
|
||||
end
|
||||
else
|
||||
if z.imag < 0 ||
|
||||
(z.imag == 0 && z.imag.to_s[0] == '-')
|
||||
sqrt(z.conjugate).conjugate
|
||||
else
|
||||
r = z.abs
|
||||
x = z.real
|
||||
Complex(RealMath.sqrt((r + x) / 2.0), RealMath.sqrt((r - x) / 2.0))
|
||||
end
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the principal value of the cube root of +z+
|
||||
#
|
||||
# CMath.cbrt(1 + 4i) #=> (1.449461632813119+0.6858152562177092i)
|
||||
def cbrt(z)
|
||||
z ** (1.0/3)
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the sine of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.sin(1 + 1i) #=> (1.2984575814159773+0.6349639147847361i)
|
||||
def sin(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.sin(z)
|
||||
else
|
||||
Complex(RealMath.sin(z.real) * RealMath.cosh(z.imag),
|
||||
RealMath.cos(z.real) * RealMath.sinh(z.imag))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the cosine of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.cos(1 + 1i) #=> (0.8337300251311491-0.9888977057628651i)
|
||||
def cos(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.cos(z)
|
||||
else
|
||||
Complex(RealMath.cos(z.real) * RealMath.cosh(z.imag),
|
||||
-RealMath.sin(z.real) * RealMath.sinh(z.imag))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the tangent of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.tan(1 + 1i) #=> (0.27175258531951174+1.0839233273386943i)
|
||||
def tan(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.tan(z)
|
||||
else
|
||||
sin(z) / cos(z)
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the hyperbolic sine of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.sinh(1 + 1i) #=> (0.6349639147847361+1.2984575814159773i)
|
||||
def sinh(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.sinh(z)
|
||||
else
|
||||
Complex(RealMath.sinh(z.real) * RealMath.cos(z.imag),
|
||||
RealMath.cosh(z.real) * RealMath.sin(z.imag))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the hyperbolic cosine of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.cosh(1 + 1i) #=> (0.8337300251311491+0.9888977057628651i)
|
||||
def cosh(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.cosh(z)
|
||||
else
|
||||
Complex(RealMath.cosh(z.real) * RealMath.cos(z.imag),
|
||||
RealMath.sinh(z.real) * RealMath.sin(z.imag))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the hyperbolic tangent of +z+, where +z+ is given in radians
|
||||
#
|
||||
# CMath.tanh(1 + 1i) #=> (1.0839233273386943+0.27175258531951174i)
|
||||
def tanh(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.tanh(z)
|
||||
else
|
||||
sinh(z) / cosh(z)
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the arc sine of +z+
|
||||
#
|
||||
# CMath.asin(1 + 1i) #=> (0.6662394324925153+1.0612750619050355i)
|
||||
def asin(z)
|
||||
begin
|
||||
if z.real? and z >= -1 and z <= 1
|
||||
RealMath.asin(z)
|
||||
else
|
||||
(-1.0).i * log(1.0.i * z + sqrt(1.0 - z * z))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the arc cosine of +z+
|
||||
#
|
||||
# CMath.acos(1 + 1i) #=> (0.9045568943023813-1.0612750619050357i)
|
||||
def acos(z)
|
||||
begin
|
||||
if z.real? and z >= -1 and z <= 1
|
||||
RealMath.acos(z)
|
||||
else
|
||||
(-1.0).i * log(z + 1.0.i * sqrt(1.0 - z * z))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the arc tangent of +z+
|
||||
#
|
||||
# CMath.atan(1 + 1i) #=> (1.0172219678978514+0.4023594781085251i)
|
||||
def atan(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.atan(z)
|
||||
else
|
||||
1.0.i * log((1.0.i + z) / (1.0.i - z)) / 2.0
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# returns the arc tangent of +y+ divided by +x+ using the signs of +y+ and
|
||||
# +x+ to determine the quadrant
|
||||
#
|
||||
# CMath.atan2(1 + 1i, 0) #=> (1.5707963267948966+0.0i)
|
||||
def atan2(y,x)
|
||||
begin
|
||||
if y.real? and x.real?
|
||||
RealMath.atan2(y,x)
|
||||
else
|
||||
(-1.0).i * log((x + 1.0.i * y) / sqrt(x * x + y * y))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# returns the inverse hyperbolic sine of +z+
|
||||
#
|
||||
# CMath.asinh(1 + 1i) #=> (1.0612750619050357+0.6662394324925153i)
|
||||
def asinh(z)
|
||||
begin
|
||||
if z.real?
|
||||
RealMath.asinh(z)
|
||||
else
|
||||
log(z + sqrt(1.0 + z * z))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# returns the inverse hyperbolic cosine of +z+
|
||||
#
|
||||
# CMath.acosh(1 + 1i) #=> (1.0612750619050357+0.9045568943023813i)
|
||||
def acosh(z)
|
||||
begin
|
||||
if z.real? and z >= 1
|
||||
RealMath.acosh(z)
|
||||
else
|
||||
log(z + sqrt(z * z - 1.0))
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# returns the inverse hyperbolic tangent of +z+
|
||||
#
|
||||
# CMath.atanh(1 + 1i) #=> (0.4023594781085251+1.0172219678978514i)
|
||||
def atanh(z)
|
||||
begin
|
||||
if z.real? and z >= -1 and z <= 1
|
||||
RealMath.atanh(z)
|
||||
else
|
||||
log((1.0 + z) / (1.0 - z)) / 2.0
|
||||
end
|
||||
rescue NoMethodError
|
||||
handle_no_method_error
|
||||
end
|
||||
end
|
||||
|
||||
module_function :exp!
|
||||
module_function :exp
|
||||
module_function :log!
|
||||
module_function :log
|
||||
module_function :log2!
|
||||
module_function :log2
|
||||
module_function :log10!
|
||||
module_function :log10
|
||||
module_function :sqrt!
|
||||
module_function :sqrt
|
||||
module_function :cbrt!
|
||||
module_function :cbrt
|
||||
|
||||
module_function :sin!
|
||||
module_function :sin
|
||||
module_function :cos!
|
||||
module_function :cos
|
||||
module_function :tan!
|
||||
module_function :tan
|
||||
|
||||
module_function :sinh!
|
||||
module_function :sinh
|
||||
module_function :cosh!
|
||||
module_function :cosh
|
||||
module_function :tanh!
|
||||
module_function :tanh
|
||||
|
||||
module_function :asin!
|
||||
module_function :asin
|
||||
module_function :acos!
|
||||
module_function :acos
|
||||
module_function :atan!
|
||||
module_function :atan
|
||||
module_function :atan2!
|
||||
module_function :atan2
|
||||
|
||||
module_function :asinh!
|
||||
module_function :asinh
|
||||
module_function :acosh!
|
||||
module_function :acosh
|
||||
module_function :atanh!
|
||||
module_function :atanh
|
||||
|
||||
module_function :frexp
|
||||
module_function :ldexp
|
||||
module_function :hypot
|
||||
module_function :erf
|
||||
module_function :erfc
|
||||
module_function :gamma
|
||||
module_function :lgamma
|
||||
|
||||
private
|
||||
def handle_no_method_error # :nodoc:
|
||||
if $!.name == :real?
|
||||
raise TypeError, "Numeric Number required"
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
module_function :handle_no_method_error
|
||||
|
||||
end
|
||||
2355
gxg/standard/csv.rb
Normal file
2355
gxg/standard/csv.rb
Normal file
File diff suppressed because it is too large
Load diff
71
gxg/standard/expect.rb
Normal file
71
gxg/standard/expect.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: true
|
||||
$expect_verbose = false
|
||||
|
||||
# Expect library adds the IO instance method #expect, which does similar act to
|
||||
# tcl's expect extension.
|
||||
#
|
||||
# In order to use this method, you must require expect:
|
||||
#
|
||||
# require 'expect'
|
||||
#
|
||||
# Please see #expect for usage.
|
||||
class IO
|
||||
# call-seq:
|
||||
# IO#expect(pattern,timeout=9999999) -> Array
|
||||
# IO#expect(pattern,timeout=9999999) { |result| ... } -> nil
|
||||
#
|
||||
# Reads from the IO until the given +pattern+ matches or the +timeout+ is over.
|
||||
#
|
||||
# It returns an array with the read buffer, followed by the matches.
|
||||
# If a block is given, the result is yielded to the block and returns nil.
|
||||
#
|
||||
# When called without a block, it waits until the input that matches the
|
||||
# given +pattern+ is obtained from the IO or the time specified as the
|
||||
# timeout passes. An array is returned when the pattern is obtained from the
|
||||
# IO. The first element of the array is the entire string obtained from the
|
||||
# IO until the pattern matches, followed by elements indicating which the
|
||||
# pattern which matched to the anchor in the regular expression.
|
||||
#
|
||||
# The optional timeout parameter defines, in seconds, the total time to wait
|
||||
# for the pattern. If the timeout expires or eof is found, nil is returned
|
||||
# or yielded. However, the buffer in a timeout session is kept for the next
|
||||
# expect call. The default timeout is 9999999 seconds.
|
||||
def expect(pat,timeout=9999999)
|
||||
buf = ''.dup
|
||||
case pat
|
||||
when String
|
||||
e_pat = Regexp.new(Regexp.quote(pat))
|
||||
when Regexp
|
||||
e_pat = pat
|
||||
else
|
||||
raise TypeError, "unsupported pattern class: #{pat.class}"
|
||||
end
|
||||
@unusedBuf ||= ''
|
||||
while true
|
||||
if not @unusedBuf.empty?
|
||||
c = @unusedBuf.slice!(0)
|
||||
elsif !IO.select([self],nil,nil,timeout) or eof? then
|
||||
result = nil
|
||||
@unusedBuf = buf
|
||||
break
|
||||
else
|
||||
c = getc
|
||||
end
|
||||
buf << c
|
||||
if $expect_verbose
|
||||
STDOUT.print c
|
||||
STDOUT.flush
|
||||
end
|
||||
if mat=e_pat.match(buf) then
|
||||
result = [buf,*mat.captures]
|
||||
break
|
||||
end
|
||||
end
|
||||
if block_given? then
|
||||
yield result
|
||||
else
|
||||
return result
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
88
gxg/standard/find.rb
Normal file
88
gxg/standard/find.rb
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# find.rb: the Find module for processing all files under a given directory.
|
||||
#
|
||||
|
||||
#
|
||||
# The +Find+ module supports the top-down traversal of a set of file paths.
|
||||
#
|
||||
# For example, to total the size of all files under your home directory,
|
||||
# ignoring anything in a "dot" directory (e.g. $HOME/.ssh):
|
||||
#
|
||||
# require 'find'
|
||||
#
|
||||
# total_size = 0
|
||||
#
|
||||
# Find.find(ENV["HOME"]) do |path|
|
||||
# if FileTest.directory?(path)
|
||||
# if File.basename(path)[0] == ?.
|
||||
# Find.prune # Don't look any further into this directory.
|
||||
# else
|
||||
# next
|
||||
# end
|
||||
# else
|
||||
# total_size += FileTest.size(path)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
module Find
|
||||
|
||||
#
|
||||
# Calls the associated block with the name of every file and directory listed
|
||||
# as arguments, then recursively on their subdirectories, and so on.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
#
|
||||
# See the +Find+ module documentation for an example.
|
||||
#
|
||||
def find(*paths, ignore_error: true) # :yield: path
|
||||
block_given? or return enum_for(__method__, *paths, ignore_error: ignore_error)
|
||||
|
||||
fs_encoding = Encoding.find("filesystem")
|
||||
|
||||
paths.collect!{|d| raise Errno::ENOENT, d unless File.exist?(d); d.dup}.each do |path|
|
||||
path = path.to_path if path.respond_to? :to_path
|
||||
enc = path.encoding == Encoding::US_ASCII ? fs_encoding : path.encoding
|
||||
ps = [path]
|
||||
while file = ps.shift
|
||||
catch(:prune) do
|
||||
yield file.dup.taint
|
||||
begin
|
||||
s = File.lstat(file)
|
||||
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
|
||||
raise unless ignore_error
|
||||
next
|
||||
end
|
||||
if s.directory? then
|
||||
begin
|
||||
fs = Dir.children(file, encoding: enc)
|
||||
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG
|
||||
raise unless ignore_error
|
||||
next
|
||||
end
|
||||
fs.sort!
|
||||
fs.reverse_each {|f|
|
||||
f = File.join(file, f)
|
||||
ps.unshift f.untaint
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
#
|
||||
# Skips the current file or directory, restarting the loop with the next
|
||||
# entry. If the current file is a directory, that directory will not be
|
||||
# recursively entered. Meaningful only within the block associated with
|
||||
# Find::find.
|
||||
#
|
||||
# See the +Find+ module documentation for an example.
|
||||
#
|
||||
def prune
|
||||
throw :prune
|
||||
end
|
||||
|
||||
module_function :find, :prune
|
||||
end
|
||||
613
gxg/standard/getoptlong.rb
Normal file
613
gxg/standard/getoptlong.rb
Normal file
|
|
@ -0,0 +1,613 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# GetoptLong for Ruby
|
||||
#
|
||||
# Copyright (C) 1998, 1999, 2000 Motoyuki Kasahara.
|
||||
#
|
||||
# You may redistribute and/or modify this library under the same license
|
||||
# terms as Ruby.
|
||||
#
|
||||
# See GetoptLong for documentation.
|
||||
#
|
||||
# Additional documents and the latest version of `getoptlong.rb' can be
|
||||
# found at http://www.sra.co.jp/people/m-kasahr/ruby/getoptlong/
|
||||
|
||||
# The GetoptLong class allows you to parse command line options similarly to
|
||||
# the GNU getopt_long() C library call. Note, however, that GetoptLong is a
|
||||
# pure Ruby implementation.
|
||||
#
|
||||
# GetoptLong allows for POSIX-style options like <tt>--file</tt> as well
|
||||
# as single letter options like <tt>-f</tt>
|
||||
#
|
||||
# The empty option <tt>--</tt> (two minus symbols) is used to end option
|
||||
# processing. This can be particularly important if options have optional
|
||||
# arguments.
|
||||
#
|
||||
# Here is a simple example of usage:
|
||||
#
|
||||
# require 'getoptlong'
|
||||
#
|
||||
# opts = GetoptLong.new(
|
||||
# [ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
||||
# [ '--repeat', '-n', GetoptLong::REQUIRED_ARGUMENT ],
|
||||
# [ '--name', GetoptLong::OPTIONAL_ARGUMENT ]
|
||||
# )
|
||||
#
|
||||
# dir = nil
|
||||
# name = nil
|
||||
# repetitions = 1
|
||||
# opts.each do |opt, arg|
|
||||
# case opt
|
||||
# when '--help'
|
||||
# puts <<-EOF
|
||||
# hello [OPTION] ... DIR
|
||||
#
|
||||
# -h, --help:
|
||||
# show help
|
||||
#
|
||||
# --repeat x, -n x:
|
||||
# repeat x times
|
||||
#
|
||||
# --name [name]:
|
||||
# greet user by name, if name not supplied default is John
|
||||
#
|
||||
# DIR: The directory in which to issue the greeting.
|
||||
# EOF
|
||||
# when '--repeat'
|
||||
# repetitions = arg.to_i
|
||||
# when '--name'
|
||||
# if arg == ''
|
||||
# name = 'John'
|
||||
# else
|
||||
# name = arg
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# if ARGV.length != 1
|
||||
# puts "Missing dir argument (try --help)"
|
||||
# exit 0
|
||||
# end
|
||||
#
|
||||
# dir = ARGV.shift
|
||||
#
|
||||
# Dir.chdir(dir)
|
||||
# for i in (1..repetitions)
|
||||
# print "Hello"
|
||||
# if name
|
||||
# print ", #{name}"
|
||||
# end
|
||||
# puts
|
||||
# end
|
||||
#
|
||||
# Example command line:
|
||||
#
|
||||
# hello -n 6 --name -- /tmp
|
||||
#
|
||||
class GetoptLong
|
||||
#
|
||||
# Orderings.
|
||||
#
|
||||
ORDERINGS = [REQUIRE_ORDER = 0, PERMUTE = 1, RETURN_IN_ORDER = 2]
|
||||
|
||||
#
|
||||
# Argument flags.
|
||||
#
|
||||
ARGUMENT_FLAGS = [NO_ARGUMENT = 0, REQUIRED_ARGUMENT = 1,
|
||||
OPTIONAL_ARGUMENT = 2]
|
||||
|
||||
#
|
||||
# Status codes.
|
||||
#
|
||||
STATUS_YET, STATUS_STARTED, STATUS_TERMINATED = 0, 1, 2
|
||||
|
||||
#
|
||||
# Error types.
|
||||
#
|
||||
class Error < StandardError; end
|
||||
class AmbiguousOption < Error; end
|
||||
class NeedlessArgument < Error; end
|
||||
class MissingArgument < Error; end
|
||||
class InvalidOption < Error; end
|
||||
|
||||
#
|
||||
# Set up option processing.
|
||||
#
|
||||
# The options to support are passed to new() as an array of arrays.
|
||||
# Each sub-array contains any number of String option names which carry
|
||||
# the same meaning, and one of the following flags:
|
||||
#
|
||||
# GetoptLong::NO_ARGUMENT :: Option does not take an argument.
|
||||
#
|
||||
# GetoptLong::REQUIRED_ARGUMENT :: Option always takes an argument.
|
||||
#
|
||||
# GetoptLong::OPTIONAL_ARGUMENT :: Option may or may not take an argument.
|
||||
#
|
||||
# The first option name is considered to be the preferred (canonical) name.
|
||||
# Other than that, the elements of each sub-array can be in any order.
|
||||
#
|
||||
def initialize(*arguments)
|
||||
#
|
||||
# Current ordering.
|
||||
#
|
||||
if ENV.include?('POSIXLY_CORRECT')
|
||||
@ordering = REQUIRE_ORDER
|
||||
else
|
||||
@ordering = PERMUTE
|
||||
end
|
||||
|
||||
#
|
||||
# Hash table of option names.
|
||||
# Keys of the table are option names, and their values are canonical
|
||||
# names of the options.
|
||||
#
|
||||
@canonical_names = Hash.new
|
||||
|
||||
#
|
||||
# Hash table of argument flags.
|
||||
# Keys of the table are option names, and their values are argument
|
||||
# flags of the options.
|
||||
#
|
||||
@argument_flags = Hash.new
|
||||
|
||||
#
|
||||
# Whether error messages are output to $stderr.
|
||||
#
|
||||
@quiet = false
|
||||
|
||||
#
|
||||
# Status code.
|
||||
#
|
||||
@status = STATUS_YET
|
||||
|
||||
#
|
||||
# Error code.
|
||||
#
|
||||
@error = nil
|
||||
|
||||
#
|
||||
# Error message.
|
||||
#
|
||||
@error_message = nil
|
||||
|
||||
#
|
||||
# Rest of catenated short options.
|
||||
#
|
||||
@rest_singles = ''
|
||||
|
||||
#
|
||||
# List of non-option-arguments.
|
||||
# Append them to ARGV when option processing is terminated.
|
||||
#
|
||||
@non_option_arguments = Array.new
|
||||
|
||||
if 0 < arguments.length
|
||||
set_options(*arguments)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Set the handling of the ordering of options and arguments.
|
||||
# A RuntimeError is raised if option processing has already started.
|
||||
#
|
||||
# The supplied value must be a member of GetoptLong::ORDERINGS. It alters
|
||||
# the processing of options as follows:
|
||||
#
|
||||
# <b>REQUIRE_ORDER</b> :
|
||||
#
|
||||
# Options are required to occur before non-options.
|
||||
#
|
||||
# Processing of options ends as soon as a word is encountered that has not
|
||||
# been preceded by an appropriate option flag.
|
||||
#
|
||||
# For example, if -a and -b are options which do not take arguments,
|
||||
# parsing command line arguments of '-a one -b two' would result in
|
||||
# 'one', '-b', 'two' being left in ARGV, and only ('-a', '') being
|
||||
# processed as an option/arg pair.
|
||||
#
|
||||
# This is the default ordering, if the environment variable
|
||||
# POSIXLY_CORRECT is set. (This is for compatibility with GNU getopt_long.)
|
||||
#
|
||||
# <b>PERMUTE</b> :
|
||||
#
|
||||
# Options can occur anywhere in the command line parsed. This is the
|
||||
# default behavior.
|
||||
#
|
||||
# Every sequence of words which can be interpreted as an option (with or
|
||||
# without argument) is treated as an option; non-option words are skipped.
|
||||
#
|
||||
# For example, if -a does not require an argument and -b optionally takes
|
||||
# an argument, parsing '-a one -b two three' would result in ('-a','') and
|
||||
# ('-b', 'two') being processed as option/arg pairs, and 'one','three'
|
||||
# being left in ARGV.
|
||||
#
|
||||
# If the ordering is set to PERMUTE but the environment variable
|
||||
# POSIXLY_CORRECT is set, REQUIRE_ORDER is used instead. This is for
|
||||
# compatibility with GNU getopt_long.
|
||||
#
|
||||
# <b>RETURN_IN_ORDER</b> :
|
||||
#
|
||||
# All words on the command line are processed as options. Words not
|
||||
# preceded by a short or long option flag are passed as arguments
|
||||
# with an option of '' (empty string).
|
||||
#
|
||||
# For example, if -a requires an argument but -b does not, a command line
|
||||
# of '-a one -b two three' would result in option/arg pairs of ('-a', 'one')
|
||||
# ('-b', ''), ('', 'two'), ('', 'three') being processed.
|
||||
#
|
||||
def ordering=(ordering)
|
||||
#
|
||||
# The method is failed if option processing has already started.
|
||||
#
|
||||
if @status != STATUS_YET
|
||||
set_error(ArgumentError, "argument error")
|
||||
raise RuntimeError,
|
||||
"invoke ordering=, but option processing has already started"
|
||||
end
|
||||
|
||||
#
|
||||
# Check ordering.
|
||||
#
|
||||
if !ORDERINGS.include?(ordering)
|
||||
raise ArgumentError, "invalid ordering `#{ordering}'"
|
||||
end
|
||||
if ordering == PERMUTE && ENV.include?('POSIXLY_CORRECT')
|
||||
@ordering = REQUIRE_ORDER
|
||||
else
|
||||
@ordering = ordering
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Return ordering.
|
||||
#
|
||||
attr_reader :ordering
|
||||
|
||||
#
|
||||
# Set options. Takes the same argument as GetoptLong.new.
|
||||
#
|
||||
# Raises a RuntimeError if option processing has already started.
|
||||
#
|
||||
def set_options(*arguments)
|
||||
#
|
||||
# The method is failed if option processing has already started.
|
||||
#
|
||||
if @status != STATUS_YET
|
||||
raise RuntimeError,
|
||||
"invoke set_options, but option processing has already started"
|
||||
end
|
||||
|
||||
#
|
||||
# Clear tables of option names and argument flags.
|
||||
#
|
||||
@canonical_names.clear
|
||||
@argument_flags.clear
|
||||
|
||||
arguments.each do |arg|
|
||||
if !arg.is_a?(Array)
|
||||
raise ArgumentError, "the option list contains non-Array argument"
|
||||
end
|
||||
|
||||
#
|
||||
# Find an argument flag and it set to `argument_flag'.
|
||||
#
|
||||
argument_flag = nil
|
||||
arg.each do |i|
|
||||
if ARGUMENT_FLAGS.include?(i)
|
||||
if argument_flag != nil
|
||||
raise ArgumentError, "too many argument-flags"
|
||||
end
|
||||
argument_flag = i
|
||||
end
|
||||
end
|
||||
|
||||
raise ArgumentError, "no argument-flag" if argument_flag == nil
|
||||
|
||||
canonical_name = nil
|
||||
arg.each do |i|
|
||||
#
|
||||
# Check an option name.
|
||||
#
|
||||
next if i == argument_flag
|
||||
begin
|
||||
if !i.is_a?(String) || i !~ /\A-([^-]|-.+)\z/
|
||||
raise ArgumentError, "an invalid option `#{i}'"
|
||||
end
|
||||
if (@canonical_names.include?(i))
|
||||
raise ArgumentError, "option redefined `#{i}'"
|
||||
end
|
||||
rescue
|
||||
@canonical_names.clear
|
||||
@argument_flags.clear
|
||||
raise
|
||||
end
|
||||
|
||||
#
|
||||
# Register the option (`i') to the `@canonical_names' and
|
||||
# `@canonical_names' Hashes.
|
||||
#
|
||||
if canonical_name == nil
|
||||
canonical_name = i
|
||||
end
|
||||
@canonical_names[i] = canonical_name
|
||||
@argument_flags[i] = argument_flag
|
||||
end
|
||||
raise ArgumentError, "no option name" if canonical_name == nil
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
#
|
||||
# Set/Unset `quiet' mode.
|
||||
#
|
||||
attr_writer :quiet
|
||||
|
||||
#
|
||||
# Return the flag of `quiet' mode.
|
||||
#
|
||||
attr_reader :quiet
|
||||
|
||||
#
|
||||
# `quiet?' is an alias of `quiet'.
|
||||
#
|
||||
alias quiet? quiet
|
||||
|
||||
#
|
||||
# Explicitly terminate option processing.
|
||||
#
|
||||
def terminate
|
||||
return nil if @status == STATUS_TERMINATED
|
||||
raise RuntimeError, "an error has occurred" if @error != nil
|
||||
|
||||
@status = STATUS_TERMINATED
|
||||
@non_option_arguments.reverse_each do |argument|
|
||||
ARGV.unshift(argument)
|
||||
end
|
||||
|
||||
@canonical_names = nil
|
||||
@argument_flags = nil
|
||||
@rest_singles = nil
|
||||
@non_option_arguments = nil
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if option processing has terminated, false otherwise.
|
||||
#
|
||||
def terminated?
|
||||
return @status == STATUS_TERMINATED
|
||||
end
|
||||
|
||||
#
|
||||
# Set an error (a protected method).
|
||||
#
|
||||
def set_error(type, message)
|
||||
$stderr.print("#{$0}: #{message}\n") if !@quiet
|
||||
|
||||
@error = type
|
||||
@error_message = message
|
||||
@canonical_names = nil
|
||||
@argument_flags = nil
|
||||
@rest_singles = nil
|
||||
@non_option_arguments = nil
|
||||
|
||||
raise type, message
|
||||
end
|
||||
protected :set_error
|
||||
|
||||
#
|
||||
# Examine whether an option processing is failed.
|
||||
#
|
||||
attr_reader :error
|
||||
|
||||
#
|
||||
# `error?' is an alias of `error'.
|
||||
#
|
||||
alias error? error
|
||||
|
||||
# Return the appropriate error message in POSIX-defined format.
|
||||
# If no error has occurred, returns nil.
|
||||
#
|
||||
def error_message
|
||||
return @error_message
|
||||
end
|
||||
|
||||
#
|
||||
# Get next option name and its argument, as an Array of two elements.
|
||||
#
|
||||
# The option name is always converted to the first (preferred)
|
||||
# name given in the original options to GetoptLong.new.
|
||||
#
|
||||
# Example: ['--option', 'value']
|
||||
#
|
||||
# Returns nil if the processing is complete (as determined by
|
||||
# STATUS_TERMINATED).
|
||||
#
|
||||
def get
|
||||
option_name, option_argument = nil, ''
|
||||
|
||||
#
|
||||
# Check status.
|
||||
#
|
||||
return nil if @error != nil
|
||||
case @status
|
||||
when STATUS_YET
|
||||
@status = STATUS_STARTED
|
||||
when STATUS_TERMINATED
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Get next option argument.
|
||||
#
|
||||
if 0 < @rest_singles.length
|
||||
argument = '-' + @rest_singles
|
||||
elsif (ARGV.length == 0)
|
||||
terminate
|
||||
return nil
|
||||
elsif @ordering == PERMUTE
|
||||
while 0 < ARGV.length && ARGV[0] !~ /\A-./
|
||||
@non_option_arguments.push(ARGV.shift)
|
||||
end
|
||||
if ARGV.length == 0
|
||||
terminate
|
||||
return nil
|
||||
end
|
||||
argument = ARGV.shift
|
||||
elsif @ordering == REQUIRE_ORDER
|
||||
if (ARGV[0] !~ /\A-./)
|
||||
terminate
|
||||
return nil
|
||||
end
|
||||
argument = ARGV.shift
|
||||
else
|
||||
argument = ARGV.shift
|
||||
end
|
||||
|
||||
#
|
||||
# Check the special argument `--'.
|
||||
# `--' indicates the end of the option list.
|
||||
#
|
||||
if argument == '--' && @rest_singles.length == 0
|
||||
terminate
|
||||
return nil
|
||||
end
|
||||
|
||||
#
|
||||
# Check for long and short options.
|
||||
#
|
||||
if argument =~ /\A(--[^=]+)/ && @rest_singles.length == 0
|
||||
#
|
||||
# This is a long style option, which start with `--'.
|
||||
#
|
||||
pattern = $1
|
||||
if @canonical_names.include?(pattern)
|
||||
option_name = pattern
|
||||
else
|
||||
#
|
||||
# The option `option_name' is not registered in `@canonical_names'.
|
||||
# It may be an abbreviated.
|
||||
#
|
||||
matches = []
|
||||
@canonical_names.each_key do |key|
|
||||
if key.index(pattern) == 0
|
||||
option_name = key
|
||||
matches << key
|
||||
end
|
||||
end
|
||||
if 2 <= matches.length
|
||||
set_error(AmbiguousOption, "option `#{argument}' is ambiguous between #{matches.join(', ')}")
|
||||
elsif matches.length == 0
|
||||
set_error(InvalidOption, "unrecognized option `#{argument}'")
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Check an argument to the option.
|
||||
#
|
||||
if @argument_flags[option_name] == REQUIRED_ARGUMENT
|
||||
if argument =~ /=(.*)/m
|
||||
option_argument = $1
|
||||
elsif 0 < ARGV.length
|
||||
option_argument = ARGV.shift
|
||||
else
|
||||
set_error(MissingArgument,
|
||||
"option `#{argument}' requires an argument")
|
||||
end
|
||||
elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
|
||||
if argument =~ /=(.*)/m
|
||||
option_argument = $1
|
||||
elsif 0 < ARGV.length && ARGV[0] !~ /\A-./
|
||||
option_argument = ARGV.shift
|
||||
else
|
||||
option_argument = ''
|
||||
end
|
||||
elsif argument =~ /=(.*)/m
|
||||
set_error(NeedlessArgument,
|
||||
"option `#{option_name}' doesn't allow an argument")
|
||||
end
|
||||
|
||||
elsif argument =~ /\A(-(.))(.*)/m
|
||||
#
|
||||
# This is a short style option, which start with `-' (not `--').
|
||||
# Short options may be catenated (e.g. `-l -g' is equivalent to
|
||||
# `-lg').
|
||||
#
|
||||
option_name, ch, @rest_singles = $1, $2, $3
|
||||
|
||||
if @canonical_names.include?(option_name)
|
||||
#
|
||||
# The option `option_name' is found in `@canonical_names'.
|
||||
# Check its argument.
|
||||
#
|
||||
if @argument_flags[option_name] == REQUIRED_ARGUMENT
|
||||
if 0 < @rest_singles.length
|
||||
option_argument = @rest_singles
|
||||
@rest_singles = ''
|
||||
elsif 0 < ARGV.length
|
||||
option_argument = ARGV.shift
|
||||
else
|
||||
# 1003.2 specifies the format of this message.
|
||||
set_error(MissingArgument, "option requires an argument -- #{ch}")
|
||||
end
|
||||
elsif @argument_flags[option_name] == OPTIONAL_ARGUMENT
|
||||
if 0 < @rest_singles.length
|
||||
option_argument = @rest_singles
|
||||
@rest_singles = ''
|
||||
elsif 0 < ARGV.length && ARGV[0] !~ /\A-./
|
||||
option_argument = ARGV.shift
|
||||
else
|
||||
option_argument = ''
|
||||
end
|
||||
end
|
||||
else
|
||||
#
|
||||
# This is an invalid option.
|
||||
# 1003.2 specifies the format of this message.
|
||||
#
|
||||
if ENV.include?('POSIXLY_CORRECT')
|
||||
set_error(InvalidOption, "invalid option -- #{ch}")
|
||||
else
|
||||
set_error(InvalidOption, "invalid option -- #{ch}")
|
||||
end
|
||||
end
|
||||
else
|
||||
#
|
||||
# This is a non-option argument.
|
||||
# Only RETURN_IN_ORDER fell into here.
|
||||
#
|
||||
return '', argument
|
||||
end
|
||||
|
||||
return @canonical_names[option_name], option_argument
|
||||
end
|
||||
|
||||
#
|
||||
# `get_option' is an alias of `get'.
|
||||
#
|
||||
alias get_option get
|
||||
|
||||
# Iterator version of `get'.
|
||||
#
|
||||
# The block is called repeatedly with two arguments:
|
||||
# The first is the option name.
|
||||
# The second is the argument which followed it (if any).
|
||||
# Example: ('--opt', 'value')
|
||||
#
|
||||
# The option name is always converted to the first (preferred)
|
||||
# name given in the original options to GetoptLong.new.
|
||||
#
|
||||
def each
|
||||
loop do
|
||||
option_name, option_argument = get_option
|
||||
break if option_name == nil
|
||||
yield option_name, option_argument
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# `each_option' is an alias of `each'.
|
||||
#
|
||||
alias each_option each
|
||||
end
|
||||
23
gxg/standard/io/console/size.rb
Normal file
23
gxg/standard/io/console/size.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: false
|
||||
# fallback to console window size
|
||||
def IO.default_console_size
|
||||
[
|
||||
ENV["LINES"].to_i.nonzero? || 25,
|
||||
ENV["COLUMNS"].to_i.nonzero? || 80,
|
||||
]
|
||||
end
|
||||
|
||||
begin
|
||||
require 'io/console'
|
||||
rescue LoadError
|
||||
class << IO
|
||||
alias console_size default_console_size
|
||||
end
|
||||
else
|
||||
# returns console window size
|
||||
def IO.console_size
|
||||
console.winsize
|
||||
rescue NoMethodError
|
||||
default_console_size
|
||||
end
|
||||
end
|
||||
315
gxg/standard/monitor.rb
Normal file
315
gxg/standard/monitor.rb
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
# frozen_string_literal: false
|
||||
# = monitor.rb
|
||||
#
|
||||
# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
|
||||
#
|
||||
# This library is distributed under the terms of the Ruby license.
|
||||
# You can freely distribute/modify this library.
|
||||
#
|
||||
|
||||
#
|
||||
# In concurrent programming, a monitor is an object or module intended to be
|
||||
# used safely by more than one thread. The defining characteristic of a
|
||||
# monitor is that its methods are executed with mutual exclusion. That is, at
|
||||
# each point in time, at most one thread may be executing any of its methods.
|
||||
# This mutual exclusion greatly simplifies reasoning about the implementation
|
||||
# of monitors compared to reasoning about parallel code that updates a data
|
||||
# structure.
|
||||
#
|
||||
# You can read more about the general principles on the Wikipedia page for
|
||||
# Monitors[http://en.wikipedia.org/wiki/Monitor_%28synchronization%29]
|
||||
#
|
||||
# == Examples
|
||||
#
|
||||
# === Simple object.extend
|
||||
#
|
||||
# require 'monitor.rb'
|
||||
#
|
||||
# buf = []
|
||||
# buf.extend(MonitorMixin)
|
||||
# empty_cond = buf.new_cond
|
||||
#
|
||||
# # consumer
|
||||
# Thread.start do
|
||||
# loop do
|
||||
# buf.synchronize do
|
||||
# empty_cond.wait_while { buf.empty? }
|
||||
# print buf.shift
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # producer
|
||||
# while line = ARGF.gets
|
||||
# buf.synchronize do
|
||||
# buf.push(line)
|
||||
# empty_cond.signal
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# The consumer thread waits for the producer thread to push a line to buf
|
||||
# while <tt>buf.empty?</tt>. The producer thread (main thread) reads a
|
||||
# line from ARGF and pushes it into buf then calls <tt>empty_cond.signal</tt>
|
||||
# to notify the consumer thread of new data.
|
||||
#
|
||||
# === Simple Class include
|
||||
#
|
||||
# require 'monitor'
|
||||
#
|
||||
# class SynchronizedArray < Array
|
||||
#
|
||||
# include MonitorMixin
|
||||
#
|
||||
# def initialize(*args)
|
||||
# super(*args)
|
||||
# end
|
||||
#
|
||||
# alias :old_shift :shift
|
||||
# alias :old_unshift :unshift
|
||||
#
|
||||
# def shift(n=1)
|
||||
# self.synchronize do
|
||||
# self.old_shift(n)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def unshift(item)
|
||||
# self.synchronize do
|
||||
# self.old_unshift(item)
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# # other methods ...
|
||||
# end
|
||||
#
|
||||
# +SynchronizedArray+ implements an Array with synchronized access to items.
|
||||
# This Class is implemented as subclass of Array which includes the
|
||||
# MonitorMixin module.
|
||||
#
|
||||
module MonitorMixin
|
||||
#
|
||||
# FIXME: This isn't documented in Nutshell.
|
||||
#
|
||||
# Since MonitorMixin.new_cond returns a ConditionVariable, and the example
|
||||
# above calls while_wait and signal, this class should be documented.
|
||||
#
|
||||
class ConditionVariable
|
||||
class Timeout < Exception; end
|
||||
|
||||
#
|
||||
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
|
||||
#
|
||||
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
|
||||
# even if no other thread doesn't signal.
|
||||
#
|
||||
def wait(timeout = nil)
|
||||
@monitor.__send__(:mon_check_owner)
|
||||
count = @monitor.__send__(:mon_exit_for_cond)
|
||||
begin
|
||||
@cond.wait(@monitor.instance_variable_get(:@mon_mutex), timeout)
|
||||
return true
|
||||
ensure
|
||||
@monitor.__send__(:mon_enter_for_cond, count)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly while the given block yields a truthy value.
|
||||
#
|
||||
def wait_while
|
||||
while yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Calls wait repeatedly until the given block yields a truthy value.
|
||||
#
|
||||
def wait_until
|
||||
until yield
|
||||
wait
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up the first thread in line waiting for this lock.
|
||||
#
|
||||
def signal
|
||||
@monitor.__send__(:mon_check_owner)
|
||||
@cond.signal
|
||||
end
|
||||
|
||||
#
|
||||
# Wakes up all threads waiting for this lock.
|
||||
#
|
||||
def broadcast
|
||||
@monitor.__send__(:mon_check_owner)
|
||||
@cond.broadcast
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize(monitor)
|
||||
@monitor = monitor
|
||||
@cond = Thread::ConditionVariable.new
|
||||
end
|
||||
end
|
||||
|
||||
def self.extend_object(obj)
|
||||
super(obj)
|
||||
obj.__send__(:mon_initialize)
|
||||
end
|
||||
|
||||
#
|
||||
# Attempts to enter exclusive section. Returns +false+ if lock fails.
|
||||
#
|
||||
def mon_try_enter
|
||||
if @mon_owner != Thread.current
|
||||
unless @mon_mutex.try_lock
|
||||
return false
|
||||
end
|
||||
@mon_owner = Thread.current
|
||||
@mon_count = 0
|
||||
end
|
||||
@mon_count += 1
|
||||
return true
|
||||
end
|
||||
# For backward compatibility
|
||||
alias try_mon_enter mon_try_enter
|
||||
|
||||
#
|
||||
# Enters exclusive section.
|
||||
#
|
||||
def mon_enter
|
||||
if @mon_owner != Thread.current
|
||||
@mon_mutex.lock
|
||||
@mon_owner = Thread.current
|
||||
@mon_count = 0
|
||||
end
|
||||
@mon_count += 1
|
||||
end
|
||||
|
||||
#
|
||||
# Leaves exclusive section.
|
||||
#
|
||||
def mon_exit
|
||||
mon_check_owner
|
||||
@mon_count -=1
|
||||
if @mon_count == 0
|
||||
@mon_owner = nil
|
||||
@mon_mutex.unlock
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if this monitor is locked by any thread
|
||||
#
|
||||
def mon_locked?
|
||||
@mon_mutex.locked?
|
||||
end
|
||||
|
||||
#
|
||||
# Returns true if this monitor is locked by current thread.
|
||||
#
|
||||
def mon_owned?
|
||||
@mon_mutex.locked? && @mon_owner == Thread.current
|
||||
end
|
||||
|
||||
#
|
||||
# Enters exclusive section and executes the block. Leaves the exclusive
|
||||
# section automatically when the block exits. See example under
|
||||
# +MonitorMixin+.
|
||||
#
|
||||
def mon_synchronize
|
||||
mon_enter
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
mon_exit
|
||||
end
|
||||
end
|
||||
alias synchronize mon_synchronize
|
||||
|
||||
#
|
||||
# Creates a new MonitorMixin::ConditionVariable associated with the
|
||||
# receiver.
|
||||
#
|
||||
def new_cond
|
||||
return ConditionVariable.new(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
|
||||
# of this constructor. Have look at the examples above to understand how to
|
||||
# use this module.
|
||||
def initialize(*args)
|
||||
super
|
||||
mon_initialize
|
||||
end
|
||||
|
||||
# Initializes the MonitorMixin after being included in a class or when an
|
||||
# object has been extended with the MonitorMixin
|
||||
def mon_initialize
|
||||
@mon_owner = nil
|
||||
@mon_count = 0
|
||||
@mon_mutex = Thread::Mutex.new
|
||||
end
|
||||
|
||||
def mon_check_owner
|
||||
if @mon_owner != Thread.current
|
||||
raise ThreadError, "current thread not owner"
|
||||
end
|
||||
end
|
||||
|
||||
def mon_enter_for_cond(count)
|
||||
@mon_owner = Thread.current
|
||||
@mon_count = count
|
||||
end
|
||||
|
||||
def mon_exit_for_cond
|
||||
count = @mon_count
|
||||
@mon_owner = nil
|
||||
@mon_count = 0
|
||||
return count
|
||||
end
|
||||
end
|
||||
|
||||
# Use the Monitor class when you want to have a lock object for blocks with
|
||||
# mutual exclusion.
|
||||
#
|
||||
# require 'monitor'
|
||||
#
|
||||
# lock = Monitor.new
|
||||
# lock.synchronize do
|
||||
# # exclusive access
|
||||
# end
|
||||
#
|
||||
class Monitor
|
||||
include MonitorMixin
|
||||
alias try_enter try_mon_enter
|
||||
alias enter mon_enter
|
||||
alias exit mon_exit
|
||||
end
|
||||
|
||||
|
||||
# Documentation comments:
|
||||
# - All documentation comes from Nutshell.
|
||||
# - MonitorMixin.new_cond appears in the example, but is not documented in
|
||||
# Nutshell.
|
||||
# - All the internals (internal modules Accessible and Initializable, class
|
||||
# ConditionVariable) appear in RDoc. It might be good to hide them, by
|
||||
# making them private, or marking them :nodoc:, etc.
|
||||
# - RDoc doesn't recognise aliases, so we have mon_synchronize documented, but
|
||||
# not synchronize.
|
||||
# - mon_owner is in Nutshell, but appears as an accessor in a separate module
|
||||
# here, so is hard/impossible to RDoc. Some other useful accessors
|
||||
# (mon_count and some queue stuff) are also in this module, and don't appear
|
||||
# directly in the RDoc output.
|
||||
# - in short, it may be worth changing the code layout in this file to make the
|
||||
# documentation easier
|
||||
|
||||
# Local variables:
|
||||
# mode: Ruby
|
||||
# tab-width: 8
|
||||
# End:
|
||||
109
gxg/standard/mutex_m.rb
Normal file
109
gxg/standard/mutex_m.rb
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# mutex_m.rb -
|
||||
# $Release Version: 3.0$
|
||||
# $Revision: 1.7 $
|
||||
# Original from mutex.rb
|
||||
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
|
||||
# modified by matz
|
||||
# patched by akira yamada
|
||||
#
|
||||
# --
|
||||
|
||||
# = mutex_m.rb
|
||||
#
|
||||
# When 'mutex_m' is required, any object that extends or includes Mutex_m will
|
||||
# be treated like a Mutex.
|
||||
#
|
||||
# Start by requiring the standard library Mutex_m:
|
||||
#
|
||||
# require "mutex_m.rb"
|
||||
#
|
||||
# From here you can extend an object with Mutex instance methods:
|
||||
#
|
||||
# obj = Object.new
|
||||
# obj.extend Mutex_m
|
||||
#
|
||||
# Or mixin Mutex_m into your module to your class inherit Mutex instance
|
||||
# methods.
|
||||
#
|
||||
# class Foo
|
||||
# include Mutex_m
|
||||
# # ...
|
||||
# end
|
||||
# obj = Foo.new
|
||||
# # this obj can be handled like Mutex
|
||||
#
|
||||
module Mutex_m
|
||||
def Mutex_m.define_aliases(cl) # :nodoc:
|
||||
cl.module_eval %q{
|
||||
alias locked? mu_locked?
|
||||
alias lock mu_lock
|
||||
alias unlock mu_unlock
|
||||
alias try_lock mu_try_lock
|
||||
alias synchronize mu_synchronize
|
||||
}
|
||||
end
|
||||
|
||||
def Mutex_m.append_features(cl) # :nodoc:
|
||||
super
|
||||
define_aliases(cl) unless cl.instance_of?(Module)
|
||||
end
|
||||
|
||||
def Mutex_m.extend_object(obj) # :nodoc:
|
||||
super
|
||||
obj.mu_extended
|
||||
end
|
||||
|
||||
def mu_extended # :nodoc:
|
||||
unless (defined? locked? and
|
||||
defined? lock and
|
||||
defined? unlock and
|
||||
defined? try_lock and
|
||||
defined? synchronize)
|
||||
Mutex_m.define_aliases(singleton_class)
|
||||
end
|
||||
mu_initialize
|
||||
end
|
||||
|
||||
# See Mutex#synchronize
|
||||
def mu_synchronize(&block)
|
||||
@_mutex.synchronize(&block)
|
||||
end
|
||||
|
||||
# See Mutex#locked?
|
||||
def mu_locked?
|
||||
@_mutex.locked?
|
||||
end
|
||||
|
||||
# See Mutex#try_lock
|
||||
def mu_try_lock
|
||||
@_mutex.try_lock
|
||||
end
|
||||
|
||||
# See Mutex#lock
|
||||
def mu_lock
|
||||
@_mutex.lock
|
||||
end
|
||||
|
||||
# See Mutex#unlock
|
||||
def mu_unlock
|
||||
@_mutex.unlock
|
||||
end
|
||||
|
||||
# See Mutex#sleep
|
||||
def sleep(timeout = nil)
|
||||
@_mutex.sleep(timeout)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def mu_initialize # :nodoc:
|
||||
@_mutex = Thread::Mutex.new
|
||||
end
|
||||
|
||||
def initialize(*args) # :nodoc:
|
||||
mu_initialize
|
||||
super
|
||||
end
|
||||
end
|
||||
1493
gxg/standard/net/ftp.rb
Normal file
1493
gxg/standard/net/ftp.rb
Normal file
File diff suppressed because it is too large
Load diff
1646
gxg/standard/net/http.rb
Normal file
1646
gxg/standard/net/http.rb
Normal file
File diff suppressed because it is too large
Load diff
26
gxg/standard/net/http/backward.rb
Normal file
26
gxg/standard/net/http/backward.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: false
|
||||
# for backward compatibility
|
||||
|
||||
# :enddoc:
|
||||
|
||||
class Net::HTTP
|
||||
ProxyMod = ProxyDelta
|
||||
end
|
||||
|
||||
module Net
|
||||
HTTPSession = Net::HTTP
|
||||
end
|
||||
|
||||
module Net::NetPrivate
|
||||
HTTPRequest = ::Net::HTTPRequest
|
||||
end
|
||||
|
||||
Net::HTTPInformationCode = Net::HTTPInformation
|
||||
Net::HTTPSuccessCode = Net::HTTPSuccess
|
||||
Net::HTTPRedirectionCode = Net::HTTPRedirection
|
||||
Net::HTTPRetriableCode = Net::HTTPRedirection
|
||||
Net::HTTPClientErrorCode = Net::HTTPClientError
|
||||
Net::HTTPFatalErrorCode = Net::HTTPClientError
|
||||
Net::HTTPServerErrorCode = Net::HTTPServerError
|
||||
Net::HTTPResponceReceiver = Net::HTTPResponse
|
||||
|
||||
26
gxg/standard/net/http/exceptions.rb
Normal file
26
gxg/standard/net/http/exceptions.rb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: false
|
||||
# Net::HTTP exception class.
|
||||
# You cannot use Net::HTTPExceptions directly; instead, you must use
|
||||
# its subclasses.
|
||||
module Net::HTTPExceptions
|
||||
def initialize(msg, res) #:nodoc:
|
||||
super msg
|
||||
@response = res
|
||||
end
|
||||
attr_reader :response
|
||||
alias data response #:nodoc: obsolete
|
||||
end
|
||||
class Net::HTTPError < Net::ProtocolError
|
||||
include Net::HTTPExceptions
|
||||
end
|
||||
class Net::HTTPRetriableError < Net::ProtoRetriableError
|
||||
include Net::HTTPExceptions
|
||||
end
|
||||
class Net::HTTPServerException < Net::ProtoServerError
|
||||
# We cannot use the name "HTTPServerError", it is the name of the response.
|
||||
include Net::HTTPExceptions
|
||||
end
|
||||
class Net::HTTPFatalError < Net::ProtoFatalError
|
||||
include Net::HTTPExceptions
|
||||
end
|
||||
|
||||
338
gxg/standard/net/http/generic_request.rb
Normal file
338
gxg/standard/net/http/generic_request.rb
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
# frozen_string_literal: false
|
||||
# HTTPGenericRequest is the parent of the HTTPRequest class.
|
||||
# Do not use this directly; use a subclass of HTTPRequest.
|
||||
#
|
||||
# Mixes in the HTTPHeader module to provide easier access to HTTP headers.
|
||||
#
|
||||
class Net::HTTPGenericRequest
|
||||
|
||||
include Net::HTTPHeader
|
||||
|
||||
def initialize(m, reqbody, resbody, uri_or_path, initheader = nil)
|
||||
@method = m
|
||||
@request_has_body = reqbody
|
||||
@response_has_body = resbody
|
||||
|
||||
if URI === uri_or_path then
|
||||
@uri = uri_or_path.dup
|
||||
host = @uri.hostname.dup
|
||||
host << ":".freeze << @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
|
||||
end
|
||||
|
||||
@decode_content = false
|
||||
|
||||
if @response_has_body and Net::HTTP::HAVE_ZLIB then
|
||||
if !initheader ||
|
||||
!initheader.keys.any? { |k|
|
||||
%w[accept-encoding range].include? k.downcase
|
||||
} then
|
||||
@decode_content = true
|
||||
initheader = initheader ? initheader.dup : {}
|
||||
initheader["accept-encoding"] =
|
||||
"gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
|
||||
end
|
||||
end
|
||||
|
||||
initialize_http_header initheader
|
||||
self['Accept'] ||= '*/*'
|
||||
self['User-Agent'] ||= 'Ruby'
|
||||
self['Host'] ||= host if host
|
||||
@body = nil
|
||||
@body_stream = nil
|
||||
@body_data = nil
|
||||
end
|
||||
|
||||
attr_reader :method
|
||||
attr_reader :path
|
||||
attr_reader :uri
|
||||
|
||||
# Automatically set to false if the user sets the Accept-Encoding header.
|
||||
# This indicates they wish to handle Content-encoding in responses
|
||||
# themselves.
|
||||
attr_reader :decode_content
|
||||
|
||||
def inspect
|
||||
"\#<#{self.class} #{@method}>"
|
||||
end
|
||||
|
||||
##
|
||||
# Don't automatically decode response content-encoding if the user indicates
|
||||
# they want to handle it.
|
||||
|
||||
def []=(key, val) # :nodoc:
|
||||
@decode_content = false if key.downcase == 'accept-encoding'
|
||||
|
||||
super key, val
|
||||
end
|
||||
|
||||
def request_body_permitted?
|
||||
@request_has_body
|
||||
end
|
||||
|
||||
def response_body_permitted?
|
||||
@response_has_body
|
||||
end
|
||||
|
||||
def body_exist?
|
||||
warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE
|
||||
response_body_permitted?
|
||||
end
|
||||
|
||||
attr_reader :body
|
||||
|
||||
def body=(str)
|
||||
@body = str
|
||||
@body_stream = nil
|
||||
@body_data = nil
|
||||
str
|
||||
end
|
||||
|
||||
attr_reader :body_stream
|
||||
|
||||
def body_stream=(input)
|
||||
@body = nil
|
||||
@body_stream = input
|
||||
@body_data = nil
|
||||
input
|
||||
end
|
||||
|
||||
def set_body_internal(str) #:nodoc: internal use only
|
||||
raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream)
|
||||
self.body = str if str
|
||||
if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted?
|
||||
self.body = ''
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# write
|
||||
#
|
||||
|
||||
def exec(sock, ver, path) #:nodoc: internal use only
|
||||
if @body
|
||||
send_request_with_body sock, ver, path, @body
|
||||
elsif @body_stream
|
||||
send_request_with_body_stream sock, ver, path, @body_stream
|
||||
elsif @body_data
|
||||
send_request_with_body_data sock, ver, path, @body_data
|
||||
else
|
||||
write_header sock, ver, path
|
||||
end
|
||||
end
|
||||
|
||||
def update_uri(addr, port, ssl) # :nodoc: internal use only
|
||||
# reflect the connection and @path to @uri
|
||||
return unless @uri
|
||||
|
||||
if ssl
|
||||
scheme = 'https'.freeze
|
||||
klass = URI::HTTPS
|
||||
else
|
||||
scheme = 'http'.freeze
|
||||
klass = URI::HTTP
|
||||
end
|
||||
|
||||
if host = self['host']
|
||||
host.sub!(/:.*/s, ''.freeze)
|
||||
elsif host = @uri.host
|
||||
else
|
||||
host = addr
|
||||
end
|
||||
# convert the class of the URI
|
||||
if @uri.is_a?(klass)
|
||||
@uri.host = host
|
||||
@uri.port = port
|
||||
else
|
||||
@uri = klass.new(
|
||||
scheme, @uri.userinfo,
|
||||
host, port, nil,
|
||||
@uri.path, nil, @uri.query, nil)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
class Chunker #:nodoc:
|
||||
def initialize(sock)
|
||||
@sock = sock
|
||||
@prev = nil
|
||||
end
|
||||
|
||||
def write(buf)
|
||||
# avoid memcpy() of buf, buf can huge and eat memory bandwidth
|
||||
@sock.write("#{buf.bytesize.to_s(16)}\r\n")
|
||||
rv = @sock.write(buf)
|
||||
@sock.write("\r\n")
|
||||
rv
|
||||
end
|
||||
|
||||
def finish
|
||||
@sock.write("0\r\n\r\n")
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def send_request_with_body_stream(sock, ver, path, f)
|
||||
unless content_length() or chunked?
|
||||
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?
|
||||
chunker = Chunker.new(sock)
|
||||
IO.copy_stream(f, chunker)
|
||||
chunker.finish
|
||||
else
|
||||
# copy_stream can sendfile() to sock.io unless we use SSL.
|
||||
# If sock.io is an SSLSocket, copy_stream will hit SSL_write()
|
||||
IO.copy_stream(f, sock.io)
|
||||
end
|
||||
end
|
||||
|
||||
def send_request_with_body_data(sock, ver, path, params)
|
||||
if /\Amultipart\/form-data\z/i !~ self.content_type
|
||||
self.content_type = 'application/x-www-form-urlencoded'
|
||||
return send_request_with_body(sock, ver, path, URI.encode_www_form(params))
|
||||
end
|
||||
|
||||
opt = @form_option.dup
|
||||
require 'securerandom' unless defined?(SecureRandom)
|
||||
opt[:boundary] ||= SecureRandom.urlsafe_base64(40)
|
||||
self.set_content_type(self.content_type, boundary: opt[:boundary])
|
||||
if chunked?
|
||||
write_header sock, ver, path
|
||||
encode_multipart_form_data(sock, params, opt)
|
||||
else
|
||||
require 'tempfile'
|
||||
file = Tempfile.new('multipart')
|
||||
file.binmode
|
||||
encode_multipart_form_data(file, params, opt)
|
||||
file.rewind
|
||||
self.content_length = file.size
|
||||
write_header sock, ver, path
|
||||
IO.copy_stream(file, sock)
|
||||
file.close(true)
|
||||
end
|
||||
end
|
||||
|
||||
def encode_multipart_form_data(out, params, opt)
|
||||
charset = opt[:charset]
|
||||
boundary = opt[:boundary]
|
||||
require 'securerandom' unless defined?(SecureRandom)
|
||||
boundary ||= SecureRandom.urlsafe_base64(40)
|
||||
chunked_p = chunked?
|
||||
|
||||
buf = ''
|
||||
params.each do |key, value, h={}|
|
||||
key = quote_string(key, charset)
|
||||
filename =
|
||||
h.key?(:filename) ? h[:filename] :
|
||||
value.respond_to?(:to_path) ? File.basename(value.to_path) :
|
||||
nil
|
||||
|
||||
buf << "--#{boundary}\r\n"
|
||||
if filename
|
||||
filename = quote_string(filename, charset)
|
||||
type = h[:content_type] || 'application/octet-stream'
|
||||
buf << "Content-Disposition: form-data; " \
|
||||
"name=\"#{key}\"; filename=\"#{filename}\"\r\n" \
|
||||
"Content-Type: #{type}\r\n\r\n"
|
||||
if !out.respond_to?(:write) || !value.respond_to?(:read)
|
||||
# if +out+ is not an IO or +value+ is not an IO
|
||||
buf << (value.respond_to?(:read) ? value.read : value)
|
||||
elsif value.respond_to?(:size) && chunked_p
|
||||
# if +out+ is an IO and +value+ is a File, use IO.copy_stream
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
out << "%x\r\n" % value.size if chunked_p
|
||||
IO.copy_stream(value, out)
|
||||
out << "\r\n" if chunked_p
|
||||
else
|
||||
# +out+ is an IO, and +value+ is not a File but an IO
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
1 while flush_buffer(out, value.read(4096), chunked_p)
|
||||
end
|
||||
else
|
||||
# non-file field:
|
||||
# HTML5 says, "The parts of the generated multipart/form-data
|
||||
# resource that correspond to non-file fields must not have a
|
||||
# Content-Type header specified."
|
||||
buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n"
|
||||
buf << (value.respond_to?(:read) ? value.read : value)
|
||||
end
|
||||
buf << "\r\n"
|
||||
end
|
||||
buf << "--#{boundary}--\r\n"
|
||||
flush_buffer(out, buf, chunked_p)
|
||||
out << "0\r\n\r\n" if chunked_p
|
||||
end
|
||||
|
||||
def quote_string(str, charset)
|
||||
str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset
|
||||
str.gsub(/[\\"]/, '\\\\\&')
|
||||
end
|
||||
|
||||
def flush_buffer(out, buf, chunked_p)
|
||||
return unless buf
|
||||
out << "%x\r\n"%buf.bytesize if chunked_p
|
||||
out << buf
|
||||
out << "\r\n" if chunked_p
|
||||
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.
|
||||
|
||||
def wait_for_continue(sock, ver)
|
||||
if ver >= '1.1' and @header['expect'] and
|
||||
@header['expect'].include?('100-continue')
|
||||
if sock.io.to_io.wait_readable(sock.continue_timeout)
|
||||
res = Net::HTTPResponse.read_new(sock)
|
||||
unless res.kind_of?(Net::HTTPContinue)
|
||||
res.decode_content = @decode_content
|
||||
throw :response, res
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def write_header(sock, ver, path)
|
||||
reqline = "#{@method} #{path} HTTP/#{ver}"
|
||||
if /[\r\n]/ =~ reqline
|
||||
raise ArgumentError, "A Request-Line must not contain CR or LF"
|
||||
end
|
||||
buf = ""
|
||||
buf << reqline << "\r\n"
|
||||
each_capitalized do |k,v|
|
||||
buf << "#{k}: #{v}\r\n"
|
||||
end
|
||||
buf << "\r\n"
|
||||
sock.write buf
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
494
gxg/standard/net/http/header.rb
Normal file
494
gxg/standard/net/http/header.rb
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
# frozen_string_literal: false
|
||||
# The HTTPHeader module defines methods for reading and writing
|
||||
# HTTP headers.
|
||||
#
|
||||
# It is used as a mixin by other classes, to provide hash-like
|
||||
# access to HTTP header values. Unlike raw hash access, HTTPHeader
|
||||
# provides access via case-insensitive keys. It also provides
|
||||
# methods for accessing commonly-used HTTP header values in more
|
||||
# convenient formats.
|
||||
#
|
||||
module Net::HTTPHeader
|
||||
|
||||
def initialize_http_header(initheader)
|
||||
@header = {}
|
||||
return unless initheader
|
||||
initheader.each do |key, value|
|
||||
warn "net/http: duplicated HTTP header: #{key}", uplevel: 1 if key?(key) and $VERBOSE
|
||||
if value.nil?
|
||||
warn "net/http: nil HTTP header: #{key}", uplevel: 1 if $VERBOSE
|
||||
else
|
||||
value = value.strip # raise error for invalid byte sequences
|
||||
if value.count("\r\n") > 0
|
||||
raise ArgumentError, 'header field value cannot include CR/LF'
|
||||
end
|
||||
@header[key.downcase] = [value]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def size #:nodoc: obsolete
|
||||
@header.size
|
||||
end
|
||||
|
||||
alias length size #:nodoc: obsolete
|
||||
|
||||
# Returns the header field corresponding to the case-insensitive key.
|
||||
# For example, a key of "Content-Type" might return "text/html"
|
||||
def [](key)
|
||||
a = @header[key.downcase] or return nil
|
||||
a.join(', ')
|
||||
end
|
||||
|
||||
# Sets the header field corresponding to the case-insensitive key.
|
||||
def []=(key, val)
|
||||
unless val
|
||||
@header.delete key.downcase
|
||||
return val
|
||||
end
|
||||
set_field(key, val)
|
||||
end
|
||||
|
||||
# [Ruby 1.8.3]
|
||||
# Adds a value to a named header field, instead of replacing its value.
|
||||
# Second argument +val+ must be a String.
|
||||
# See also #[]=, #[] and #get_fields.
|
||||
#
|
||||
# request.add_field 'X-My-Header', 'a'
|
||||
# p request['X-My-Header'] #=> "a"
|
||||
# p request.get_fields('X-My-Header') #=> ["a"]
|
||||
# request.add_field 'X-My-Header', 'b'
|
||||
# p request['X-My-Header'] #=> "a, b"
|
||||
# p request.get_fields('X-My-Header') #=> ["a", "b"]
|
||||
# request.add_field 'X-My-Header', 'c'
|
||||
# p request['X-My-Header'] #=> "a, b, c"
|
||||
# p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
|
||||
#
|
||||
def add_field(key, val)
|
||||
if @header.key?(key.downcase)
|
||||
append_field_value(@header[key.downcase], val)
|
||||
else
|
||||
set_field(key, val)
|
||||
end
|
||||
end
|
||||
|
||||
private def set_field(key, val)
|
||||
case val
|
||||
when Enumerable
|
||||
ary = []
|
||||
append_field_value(ary, val)
|
||||
@header[key.downcase] = ary
|
||||
else
|
||||
val = val.to_s # for compatibility use to_s instead of to_str
|
||||
if val.b.count("\r\n") > 0
|
||||
raise ArgumentError, 'header field value cannot include CR/LF'
|
||||
end
|
||||
@header[key.downcase] = [val]
|
||||
end
|
||||
end
|
||||
|
||||
private def append_field_value(ary, val)
|
||||
case val
|
||||
when Enumerable
|
||||
val.each{|x| append_field_value(ary, x)}
|
||||
else
|
||||
val = val.to_s
|
||||
if /[\r\n]/n.match?(val.b)
|
||||
raise ArgumentError, 'header field value cannot include CR/LF'
|
||||
end
|
||||
ary.push val
|
||||
end
|
||||
end
|
||||
|
||||
# [Ruby 1.8.3]
|
||||
# Returns an array of header field strings corresponding to the
|
||||
# case-insensitive +key+. This method allows you to get duplicated
|
||||
# header fields without any processing. See also #[].
|
||||
#
|
||||
# p response.get_fields('Set-Cookie')
|
||||
# #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23",
|
||||
# "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"]
|
||||
# p response['Set-Cookie']
|
||||
# #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"
|
||||
#
|
||||
def get_fields(key)
|
||||
return nil unless @header[key.downcase]
|
||||
@header[key.downcase].dup
|
||||
end
|
||||
|
||||
# Returns the header field corresponding to the case-insensitive key.
|
||||
# Returns the default value +args+, or the result of the block, or
|
||||
# raises an IndexError if there's no header field named +key+
|
||||
# See Hash#fetch
|
||||
def fetch(key, *args, &block) #:yield: +key+
|
||||
a = @header.fetch(key.downcase, *args, &block)
|
||||
a.kind_of?(Array) ? a.join(', ') : a
|
||||
end
|
||||
|
||||
# Iterates through the header names and values, passing in the name
|
||||
# and value to the code block supplied.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# response.header.each_header {|key,value| puts "#{key} = #{value}" }
|
||||
#
|
||||
def each_header #:yield: +key+, +value+
|
||||
block_given? or return enum_for(__method__) { @header.size }
|
||||
@header.each do |k,va|
|
||||
yield k, va.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
alias each each_header
|
||||
|
||||
# Iterates through the header names in the header, passing
|
||||
# each header name to the code block.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
def each_name(&block) #:yield: +key+
|
||||
block_given? or return enum_for(__method__) { @header.size }
|
||||
@header.each_key(&block)
|
||||
end
|
||||
|
||||
alias each_key each_name
|
||||
|
||||
# Iterates through the header names in the header, passing
|
||||
# capitalized header names to the code block.
|
||||
#
|
||||
# Note that header names are capitalized systematically;
|
||||
# capitalization may not match that used by the remote HTTP
|
||||
# server in its response.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
def each_capitalized_name #:yield: +key+
|
||||
block_given? or return enum_for(__method__) { @header.size }
|
||||
@header.each_key do |k|
|
||||
yield capitalize(k)
|
||||
end
|
||||
end
|
||||
|
||||
# Iterates through header values, passing each value to the
|
||||
# code block.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
def each_value #:yield: +value+
|
||||
block_given? or return enum_for(__method__) { @header.size }
|
||||
@header.each_value do |va|
|
||||
yield va.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
# Removes a header field, specified by case-insensitive key.
|
||||
def delete(key)
|
||||
@header.delete(key.downcase)
|
||||
end
|
||||
|
||||
# true if +key+ header exists.
|
||||
def key?(key)
|
||||
@header.key?(key.downcase)
|
||||
end
|
||||
|
||||
# Returns a Hash consisting of header names and array of values.
|
||||
# e.g.
|
||||
# {"cache-control" => ["private"],
|
||||
# "content-type" => ["text/html"],
|
||||
# "date" => ["Wed, 22 Jun 2005 22:11:50 GMT"]}
|
||||
def to_hash
|
||||
@header.dup
|
||||
end
|
||||
|
||||
# As for #each_header, except the keys are provided in capitalized form.
|
||||
#
|
||||
# Note that header names are capitalized systematically;
|
||||
# capitalization may not match that used by the remote HTTP
|
||||
# server in its response.
|
||||
#
|
||||
# Returns an enumerator if no block is given.
|
||||
def each_capitalized
|
||||
block_given? or return enum_for(__method__) { @header.size }
|
||||
@header.each do |k,v|
|
||||
yield capitalize(k), v.join(', ')
|
||||
end
|
||||
end
|
||||
|
||||
alias canonical_each each_capitalized
|
||||
|
||||
def capitalize(name)
|
||||
name.to_s.split(/-/).map {|s| s.capitalize }.join('-')
|
||||
end
|
||||
private :capitalize
|
||||
|
||||
# Returns an Array of Range objects which represent the Range:
|
||||
# HTTP header field, or +nil+ if there is no such header.
|
||||
def range
|
||||
return nil unless @header['range']
|
||||
|
||||
value = self['Range']
|
||||
# byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec )
|
||||
# *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] )
|
||||
# corrected collected ABNF
|
||||
# http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1
|
||||
# http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C
|
||||
# http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5
|
||||
unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value
|
||||
raise Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'"
|
||||
end
|
||||
|
||||
byte_range_set = $1
|
||||
result = byte_range_set.split(/,/).map {|spec|
|
||||
m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or
|
||||
raise Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'"
|
||||
d1 = m[1].to_i
|
||||
d2 = m[2].to_i
|
||||
if m[1] and m[2]
|
||||
if d1 > d2
|
||||
raise Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'"
|
||||
end
|
||||
d1..d2
|
||||
elsif m[1]
|
||||
d1..-1
|
||||
elsif m[2]
|
||||
-d2..-1
|
||||
else
|
||||
raise Net::HTTPHeaderSyntaxError, 'range is not specified'
|
||||
end
|
||||
}
|
||||
# if result.empty?
|
||||
# byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec
|
||||
# but above regexp already denies it.
|
||||
if result.size == 1 && result[0].begin == 0 && result[0].end == -1
|
||||
raise Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length'
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Sets the HTTP Range: header.
|
||||
# Accepts either a Range object as a single argument,
|
||||
# or a beginning index and a length from that index.
|
||||
# Example:
|
||||
#
|
||||
# req.range = (0..1023)
|
||||
# req.set_range 0, 1023
|
||||
#
|
||||
def set_range(r, e = nil)
|
||||
unless r
|
||||
@header.delete 'range'
|
||||
return r
|
||||
end
|
||||
r = (r...r+e) if e
|
||||
case r
|
||||
when Numeric
|
||||
n = r.to_i
|
||||
rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
|
||||
when Range
|
||||
first = r.first
|
||||
last = r.end
|
||||
last -= 1 if r.exclude_end?
|
||||
if last == -1
|
||||
rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
|
||||
else
|
||||
raise Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
|
||||
raise Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
|
||||
raise Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last
|
||||
rangestr = "#{first}-#{last}"
|
||||
end
|
||||
else
|
||||
raise TypeError, 'Range/Integer is required'
|
||||
end
|
||||
@header['range'] = ["bytes=#{rangestr}"]
|
||||
r
|
||||
end
|
||||
|
||||
alias range= set_range
|
||||
|
||||
# Returns an Integer object which represents the HTTP Content-Length:
|
||||
# header field, or +nil+ if that field was not provided.
|
||||
def content_length
|
||||
return nil unless key?('Content-Length')
|
||||
len = self['Content-Length'].slice(/\d+/) or
|
||||
raise Net::HTTPHeaderSyntaxError, 'wrong Content-Length format'
|
||||
len.to_i
|
||||
end
|
||||
|
||||
def content_length=(len)
|
||||
unless len
|
||||
@header.delete 'content-length'
|
||||
return nil
|
||||
end
|
||||
@header['content-length'] = [len.to_i.to_s]
|
||||
end
|
||||
|
||||
# Returns "true" if the "transfer-encoding" header is present and
|
||||
# set to "chunked". This is an HTTP/1.1 feature, allowing the
|
||||
# the content to be sent in "chunks" without at the outset
|
||||
# stating the entire content length.
|
||||
def chunked?
|
||||
return false unless @header['transfer-encoding']
|
||||
field = self['Transfer-Encoding']
|
||||
(/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false
|
||||
end
|
||||
|
||||
# Returns a Range object which represents the value of the Content-Range:
|
||||
# header field.
|
||||
# For a partial entity body, this indicates where this fragment
|
||||
# fits inside the full entity body, as range of byte offsets.
|
||||
def content_range
|
||||
return nil unless @header['content-range']
|
||||
m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or
|
||||
raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format'
|
||||
m[1].to_i .. m[2].to_i
|
||||
end
|
||||
|
||||
# The length of the range represented in Content-Range: header.
|
||||
def range_length
|
||||
r = content_range() or return nil
|
||||
r.end - r.begin + 1
|
||||
end
|
||||
|
||||
# Returns a content type string such as "text/html".
|
||||
# This method returns nil if Content-Type: header field does not exist.
|
||||
def content_type
|
||||
return nil unless main_type()
|
||||
if sub_type()
|
||||
then "#{main_type()}/#{sub_type()}"
|
||||
else main_type()
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a content type string such as "text".
|
||||
# This method returns nil if Content-Type: header field does not exist.
|
||||
def main_type
|
||||
return nil unless @header['content-type']
|
||||
self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
|
||||
end
|
||||
|
||||
# Returns a content type string such as "html".
|
||||
# This method returns nil if Content-Type: header field does not exist
|
||||
# or sub-type is not given (e.g. "Content-Type: text").
|
||||
def sub_type
|
||||
return nil unless @header['content-type']
|
||||
_, sub = *self['Content-Type'].split(';').first.to_s.split('/')
|
||||
return nil unless sub
|
||||
sub.strip
|
||||
end
|
||||
|
||||
# Any parameters specified for the content type, returned as a Hash.
|
||||
# For example, a header of Content-Type: text/html; charset=EUC-JP
|
||||
# would result in type_params returning {'charset' => 'EUC-JP'}
|
||||
def type_params
|
||||
result = {}
|
||||
list = self['Content-Type'].to_s.split(';')
|
||||
list.shift
|
||||
list.each do |param|
|
||||
k, v = *param.split('=', 2)
|
||||
result[k.strip] = v.strip
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
# Sets the content type in an HTTP header.
|
||||
# The +type+ should be a full HTTP content type, e.g. "text/html".
|
||||
# The +params+ are an optional Hash of parameters to add after the
|
||||
# content type, e.g. {'charset' => 'iso-8859-1'}
|
||||
def set_content_type(type, params = {})
|
||||
@header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
|
||||
end
|
||||
|
||||
alias content_type= set_content_type
|
||||
|
||||
# Set header fields and a body from HTML form data.
|
||||
# +params+ should be an Array of Arrays or
|
||||
# a Hash containing HTML form data.
|
||||
# Optional argument +sep+ means data record separator.
|
||||
#
|
||||
# Values are URL encoded as necessary and the content-type is set to
|
||||
# application/x-www-form-urlencoded
|
||||
#
|
||||
# Example:
|
||||
# http.form_data = {"q" => "ruby", "lang" => "en"}
|
||||
# http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"}
|
||||
# http.set_form_data({"q" => "ruby", "lang" => "en"}, ';')
|
||||
#
|
||||
def set_form_data(params, sep = '&')
|
||||
query = URI.encode_www_form(params)
|
||||
query.gsub!(/&/, sep) if sep != '&'
|
||||
self.body = query
|
||||
self.content_type = 'application/x-www-form-urlencoded'
|
||||
end
|
||||
|
||||
alias form_data= set_form_data
|
||||
|
||||
# Set an HTML form data set.
|
||||
# +params+ is the form data set; it is an Array of Arrays or a Hash
|
||||
# +enctype is the type to encode the form data set.
|
||||
# It is application/x-www-form-urlencoded or multipart/form-data.
|
||||
# +formopt+ is an optional hash to specify the detail.
|
||||
#
|
||||
# boundary:: the boundary of the multipart message
|
||||
# charset:: the charset of the message. All names and the values of
|
||||
# non-file fields are encoded as the charset.
|
||||
#
|
||||
# Each item of params is an array and contains following items:
|
||||
# +name+:: the name of the field
|
||||
# +value+:: the value of the field, it should be a String or a File
|
||||
# +opt+:: an optional hash to specify additional information
|
||||
#
|
||||
# Each item is a file field or a normal field.
|
||||
# If +value+ is a File object or the +opt+ have a filename key,
|
||||
# the item is treated as a file field.
|
||||
#
|
||||
# If Transfer-Encoding is set as chunked, this send the request in
|
||||
# chunked encoding. Because chunked encoding is HTTP/1.1 feature,
|
||||
# you must confirm the server to support HTTP/1.1 before sending it.
|
||||
#
|
||||
# Example:
|
||||
# http.set_form([["q", "ruby"], ["lang", "en"]])
|
||||
#
|
||||
# See also RFC 2388, RFC 2616, HTML 4.01, and HTML5
|
||||
#
|
||||
def set_form(params, enctype='application/x-www-form-urlencoded', formopt={})
|
||||
@body_data = params
|
||||
@body = nil
|
||||
@body_stream = nil
|
||||
@form_option = formopt
|
||||
case enctype
|
||||
when /\Aapplication\/x-www-form-urlencoded\z/i,
|
||||
/\Amultipart\/form-data\z/i
|
||||
self.content_type = enctype
|
||||
else
|
||||
raise ArgumentError, "invalid enctype: #{enctype}"
|
||||
end
|
||||
end
|
||||
|
||||
# Set the Authorization: header for "Basic" authorization.
|
||||
def basic_auth(account, password)
|
||||
@header['authorization'] = [basic_encode(account, password)]
|
||||
end
|
||||
|
||||
# Set Proxy-Authorization: header for "Basic" authorization.
|
||||
def proxy_basic_auth(account, password)
|
||||
@header['proxy-authorization'] = [basic_encode(account, password)]
|
||||
end
|
||||
|
||||
def basic_encode(account, password)
|
||||
'Basic ' + ["#{account}:#{password}"].pack('m0')
|
||||
end
|
||||
private :basic_encode
|
||||
|
||||
def connection_close?
|
||||
token = /(?:\A|,)\s*close\s*(?:\z|,)/i
|
||||
@header['connection']&.grep(token) {return true}
|
||||
@header['proxy-connection']&.grep(token) {return true}
|
||||
false
|
||||
end
|
||||
|
||||
def connection_keep_alive?
|
||||
token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
|
||||
@header['connection']&.grep(token) {return true}
|
||||
@header['proxy-connection']&.grep(token) {return true}
|
||||
false
|
||||
end
|
||||
|
||||
end
|
||||
17
gxg/standard/net/http/proxy_delta.rb
Normal file
17
gxg/standard/net/http/proxy_delta.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: false
|
||||
module Net::HTTP::ProxyDelta #:nodoc: internal use only
|
||||
private
|
||||
|
||||
def conn_address
|
||||
proxy_address()
|
||||
end
|
||||
|
||||
def conn_port
|
||||
proxy_port()
|
||||
end
|
||||
|
||||
def edit_path(path)
|
||||
use_ssl? ? path : "http://#{addr_port()}#{path}"
|
||||
end
|
||||
end
|
||||
|
||||
21
gxg/standard/net/http/request.rb
Normal file
21
gxg/standard/net/http/request.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: false
|
||||
# HTTP request class.
|
||||
# This class wraps together the request header and the request path.
|
||||
# You cannot use this class directly. Instead, you should use one of its
|
||||
# subclasses: Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Head.
|
||||
#
|
||||
class Net::HTTPRequest < Net::HTTPGenericRequest
|
||||
# Creates an HTTP request object for +path+.
|
||||
#
|
||||
# +initheader+ are the default headers to use. Net::HTTP adds
|
||||
# Accept-Encoding to enable compression of the response body unless
|
||||
# Accept-Encoding or Range are supplied in +initheader+.
|
||||
|
||||
def initialize(path, initheader = nil)
|
||||
super self.class::METHOD,
|
||||
self.class::REQUEST_HAS_BODY,
|
||||
self.class::RESPONSE_HAS_BODY,
|
||||
path, initheader
|
||||
end
|
||||
end
|
||||
|
||||
123
gxg/standard/net/http/requests.rb
Normal file
123
gxg/standard/net/http/requests.rb
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# HTTP/1.1 methods --- RFC2616
|
||||
#
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
# See Net::HTTP for usage examples.
|
||||
class Net::HTTP::Get < Net::HTTPRequest
|
||||
METHOD = 'GET'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
# See Net::HTTP for usage examples.
|
||||
class Net::HTTP::Head < Net::HTTPRequest
|
||||
METHOD = 'HEAD'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = false
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
# See Net::HTTP for usage examples.
|
||||
class Net::HTTP::Post < Net::HTTPRequest
|
||||
METHOD = 'POST'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
# See Net::HTTP for usage examples.
|
||||
class Net::HTTP::Put < Net::HTTPRequest
|
||||
METHOD = 'PUT'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
# See Net::HTTP for usage examples.
|
||||
class Net::HTTP::Delete < Net::HTTPRequest
|
||||
METHOD = 'DELETE'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Options < Net::HTTPRequest
|
||||
METHOD = 'OPTIONS'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Trace < Net::HTTPRequest
|
||||
METHOD = 'TRACE'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
#
|
||||
# PATCH method --- RFC5789
|
||||
#
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Patch < Net::HTTPRequest
|
||||
METHOD = 'PATCH'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
#
|
||||
# WebDAV methods --- RFC2518
|
||||
#
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Propfind < Net::HTTPRequest
|
||||
METHOD = 'PROPFIND'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Proppatch < Net::HTTPRequest
|
||||
METHOD = 'PROPPATCH'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Mkcol < Net::HTTPRequest
|
||||
METHOD = 'MKCOL'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Copy < Net::HTTPRequest
|
||||
METHOD = 'COPY'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Move < Net::HTTPRequest
|
||||
METHOD = 'MOVE'
|
||||
REQUEST_HAS_BODY = false
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Lock < Net::HTTPRequest
|
||||
METHOD = 'LOCK'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
# See Net::HTTPGenericRequest for attributes and methods.
|
||||
class Net::HTTP::Unlock < Net::HTTPRequest
|
||||
METHOD = 'UNLOCK'
|
||||
REQUEST_HAS_BODY = true
|
||||
RESPONSE_HAS_BODY = true
|
||||
end
|
||||
|
||||
419
gxg/standard/net/http/response.rb
Normal file
419
gxg/standard/net/http/response.rb
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
# frozen_string_literal: false
|
||||
# HTTP response class.
|
||||
#
|
||||
# This class wraps together the response header and the response body (the
|
||||
# entity requested).
|
||||
#
|
||||
# It mixes in the HTTPHeader module, which provides access to response
|
||||
# header values both via hash-like methods and via individual readers.
|
||||
#
|
||||
# Note that each possible HTTP response code defines its own
|
||||
# HTTPResponse subclass. These are listed below.
|
||||
#
|
||||
# All classes are defined under the Net module. Indentation indicates
|
||||
# inheritance. For a list of the classes see Net::HTTP.
|
||||
#
|
||||
#
|
||||
class Net::HTTPResponse
|
||||
class << self
|
||||
# true if the response has a body.
|
||||
def body_permitted?
|
||||
self::HAS_BODY
|
||||
end
|
||||
|
||||
def exception_type # :nodoc: internal use only
|
||||
self::EXCEPTION_TYPE
|
||||
end
|
||||
|
||||
def read_new(sock) #:nodoc: internal use only
|
||||
httpv, code, msg = read_status_line(sock)
|
||||
res = response_class(code).new(httpv, code, msg)
|
||||
each_response_header(sock) do |k,v|
|
||||
res.add_field k, v
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def read_status_line(sock)
|
||||
str = sock.readline
|
||||
m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or
|
||||
raise Net::HTTPBadResponse, "wrong status line: #{str.dump}"
|
||||
m.captures
|
||||
end
|
||||
|
||||
def response_class(code)
|
||||
CODE_TO_OBJ[code] or
|
||||
CODE_CLASS_TO_OBJ[code[0,1]] or
|
||||
Net::HTTPUnknownResponse
|
||||
end
|
||||
|
||||
def each_response_header(sock)
|
||||
key = value = nil
|
||||
while true
|
||||
line = sock.readuntil("\n", true).sub(/\s+\z/, '')
|
||||
break if line.empty?
|
||||
if line[0] == ?\s or line[0] == ?\t and value
|
||||
value << ' ' unless value.empty?
|
||||
value << line.strip
|
||||
else
|
||||
yield key, value if key
|
||||
key, value = line.strip.split(/\s*:\s*/, 2)
|
||||
raise Net::HTTPBadResponse, 'wrong header line format' if value.nil?
|
||||
end
|
||||
end
|
||||
yield key, value if key
|
||||
end
|
||||
end
|
||||
|
||||
# next is to fix bug in RDoc, where the private inside class << self
|
||||
# spills out.
|
||||
public
|
||||
|
||||
include Net::HTTPHeader
|
||||
|
||||
def initialize(httpv, code, msg) #:nodoc: internal use only
|
||||
@http_version = httpv
|
||||
@code = code
|
||||
@message = msg
|
||||
initialize_http_header nil
|
||||
@body = nil
|
||||
@read = false
|
||||
@uri = nil
|
||||
@decode_content = false
|
||||
end
|
||||
|
||||
# The HTTP version supported by the server.
|
||||
attr_reader :http_version
|
||||
|
||||
# The HTTP result code string. For example, '302'. You can also
|
||||
# determine the response type by examining which response subclass
|
||||
# the response object is an instance of.
|
||||
attr_reader :code
|
||||
|
||||
# The HTTP result message sent by the server. For example, 'Not Found'.
|
||||
attr_reader :message
|
||||
alias msg message # :nodoc: obsolete
|
||||
|
||||
# The URI used to fetch this response. The response URI is only available
|
||||
# if a URI was used to create the request.
|
||||
attr_reader :uri
|
||||
|
||||
# Set to true automatically when the request did not contain an
|
||||
# Accept-Encoding header from the user.
|
||||
attr_accessor :decode_content
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
|
||||
end
|
||||
|
||||
#
|
||||
# response <-> exception relationship
|
||||
#
|
||||
|
||||
def code_type #:nodoc:
|
||||
self.class
|
||||
end
|
||||
|
||||
def error! #:nodoc:
|
||||
message = @code
|
||||
message += ' ' + @message.dump if @message
|
||||
raise error_type().new(message, self)
|
||||
end
|
||||
|
||||
def error_type #:nodoc:
|
||||
self.class::EXCEPTION_TYPE
|
||||
end
|
||||
|
||||
# Raises an HTTP error if the response is not 2xx (success).
|
||||
def value
|
||||
error! unless self.kind_of?(Net::HTTPSuccess)
|
||||
end
|
||||
|
||||
def uri= uri # :nodoc:
|
||||
@uri = uri.dup if uri
|
||||
end
|
||||
|
||||
#
|
||||
# header (for backward compatibility only; DO NOT USE)
|
||||
#
|
||||
|
||||
def response #:nodoc:
|
||||
warn "Net::HTTPResponse#response is obsolete", uplevel: 1 if $VERBOSE
|
||||
self
|
||||
end
|
||||
|
||||
def header #:nodoc:
|
||||
warn "Net::HTTPResponse#header is obsolete", uplevel: 1 if $VERBOSE
|
||||
self
|
||||
end
|
||||
|
||||
def read_header #:nodoc:
|
||||
warn "Net::HTTPResponse#read_header is obsolete", uplevel: 1 if $VERBOSE
|
||||
self
|
||||
end
|
||||
|
||||
#
|
||||
# body
|
||||
#
|
||||
|
||||
def reading_body(sock, reqmethodallowbody) #:nodoc: internal use only
|
||||
@socket = sock
|
||||
@body_exist = reqmethodallowbody && self.class.body_permitted?
|
||||
begin
|
||||
yield
|
||||
self.body # ensure to read body
|
||||
ensure
|
||||
@socket = nil
|
||||
end
|
||||
end
|
||||
|
||||
# Gets the entity body returned by the remote HTTP server.
|
||||
#
|
||||
# If a block is given, the body is passed to the block, and
|
||||
# the body is provided in fragments, as it is read in from the socket.
|
||||
#
|
||||
# Calling this method a second or subsequent time for the same
|
||||
# HTTPResponse object will return the value already read.
|
||||
#
|
||||
# http.request_get('/index.html') {|res|
|
||||
# puts res.read_body
|
||||
# }
|
||||
#
|
||||
# http.request_get('/index.html') {|res|
|
||||
# p res.read_body.object_id # 538149362
|
||||
# p res.read_body.object_id # 538149362
|
||||
# }
|
||||
#
|
||||
# # using iterator
|
||||
# http.request_get('/index.html') {|res|
|
||||
# res.read_body do |segment|
|
||||
# print segment
|
||||
# end
|
||||
# }
|
||||
#
|
||||
def read_body(dest = nil, &block)
|
||||
if @read
|
||||
raise IOError, "#{self.class}\#read_body called twice" if dest or block
|
||||
return @body
|
||||
end
|
||||
to = procdest(dest, block)
|
||||
stream_check
|
||||
if @body_exist
|
||||
read_body_0 to
|
||||
@body = to
|
||||
else
|
||||
@body = nil
|
||||
end
|
||||
@read = true
|
||||
|
||||
@body
|
||||
end
|
||||
|
||||
# Returns the full entity body.
|
||||
#
|
||||
# Calling this method a second or subsequent time will return the
|
||||
# string already read.
|
||||
#
|
||||
# http.request_get('/index.html') {|res|
|
||||
# puts res.body
|
||||
# }
|
||||
#
|
||||
# http.request_get('/index.html') {|res|
|
||||
# p res.body.object_id # 538149362
|
||||
# p res.body.object_id # 538149362
|
||||
# }
|
||||
#
|
||||
def body
|
||||
read_body()
|
||||
end
|
||||
|
||||
# Because it may be necessary to modify the body, Eg, decompression
|
||||
# this method facilitates that.
|
||||
def body=(value)
|
||||
@body = value
|
||||
end
|
||||
|
||||
alias entity body #:nodoc: obsolete
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Checks for a supported Content-Encoding header and yields an Inflate
|
||||
# wrapper for this response's socket when zlib is present. If the
|
||||
# Content-Encoding is not supported or zlib is missing, the plain socket is
|
||||
# yielded.
|
||||
#
|
||||
# If a Content-Range header is present, a plain socket is yielded as the
|
||||
# bytes in the range may not be a complete deflate block.
|
||||
|
||||
def inflater # :nodoc:
|
||||
return yield @socket unless Net::HTTP::HAVE_ZLIB
|
||||
return yield @socket unless @decode_content
|
||||
return yield @socket if self['content-range']
|
||||
|
||||
v = self['content-encoding']
|
||||
case v&.downcase
|
||||
when 'deflate', 'gzip', 'x-gzip' then
|
||||
self.delete 'content-encoding'
|
||||
|
||||
inflate_body_io = Inflater.new(@socket)
|
||||
|
||||
begin
|
||||
yield inflate_body_io
|
||||
ensure
|
||||
orig_err = $!
|
||||
begin
|
||||
inflate_body_io.finish
|
||||
rescue => err
|
||||
raise orig_err || err
|
||||
end
|
||||
end
|
||||
when 'none', 'identity' then
|
||||
self.delete 'content-encoding'
|
||||
|
||||
yield @socket
|
||||
else
|
||||
yield @socket
|
||||
end
|
||||
end
|
||||
|
||||
def read_body_0(dest)
|
||||
inflater do |inflate_body_io|
|
||||
if chunked?
|
||||
read_chunked dest, inflate_body_io
|
||||
return
|
||||
end
|
||||
|
||||
@socket = inflate_body_io
|
||||
|
||||
clen = content_length()
|
||||
if clen
|
||||
@socket.read clen, dest, true # ignore EOF
|
||||
return
|
||||
end
|
||||
clen = range_length()
|
||||
if clen
|
||||
@socket.read clen, dest
|
||||
return
|
||||
end
|
||||
@socket.read_all dest
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF,
|
||||
# etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip
|
||||
# encoded.
|
||||
#
|
||||
# See RFC 2616 section 3.6.1 for definitions
|
||||
|
||||
def read_chunked(dest, chunk_data_io) # :nodoc:
|
||||
total = 0
|
||||
while true
|
||||
line = @socket.readline
|
||||
hexlen = line.slice(/[0-9a-fA-F]+/) or
|
||||
raise Net::HTTPBadResponse, "wrong chunk size line: #{line}"
|
||||
len = hexlen.hex
|
||||
break if len == 0
|
||||
begin
|
||||
chunk_data_io.read len, dest
|
||||
ensure
|
||||
total += len
|
||||
@socket.read 2 # \r\n
|
||||
end
|
||||
end
|
||||
until @socket.readline.empty?
|
||||
# none
|
||||
end
|
||||
end
|
||||
|
||||
def stream_check
|
||||
raise IOError, 'attempt to read body out of block' if @socket.closed?
|
||||
end
|
||||
|
||||
def procdest(dest, block)
|
||||
raise ArgumentError, 'both arg and block given for HTTP method' if
|
||||
dest and block
|
||||
if block
|
||||
Net::ReadAdapter.new(block)
|
||||
else
|
||||
dest || ''
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Inflater is a wrapper around Net::BufferedIO that transparently inflates
|
||||
# zlib and gzip streams.
|
||||
|
||||
class Inflater # :nodoc:
|
||||
|
||||
##
|
||||
# Creates a new Inflater wrapping +socket+
|
||||
|
||||
def initialize socket
|
||||
@socket = socket
|
||||
# zlib with automatic gzip detection
|
||||
@inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS)
|
||||
end
|
||||
|
||||
##
|
||||
# Finishes the inflate stream.
|
||||
|
||||
def finish
|
||||
return if @inflate.total_in == 0
|
||||
@inflate.finish
|
||||
end
|
||||
|
||||
##
|
||||
# Returns a Net::ReadAdapter that inflates each read chunk into +dest+.
|
||||
#
|
||||
# This allows a large response body to be inflated without storing the
|
||||
# entire body in memory.
|
||||
|
||||
def inflate_adapter(dest)
|
||||
if dest.respond_to?(:set_encoding)
|
||||
dest.set_encoding(Encoding::ASCII_8BIT)
|
||||
elsif dest.respond_to?(:force_encoding)
|
||||
dest.force_encoding(Encoding::ASCII_8BIT)
|
||||
end
|
||||
block = proc do |compressed_chunk|
|
||||
@inflate.inflate(compressed_chunk) do |chunk|
|
||||
dest << chunk
|
||||
end
|
||||
end
|
||||
|
||||
Net::ReadAdapter.new(block)
|
||||
end
|
||||
|
||||
##
|
||||
# Reads +clen+ bytes from the socket, inflates them, then writes them to
|
||||
# +dest+. +ignore_eof+ is passed down to Net::BufferedIO#read
|
||||
#
|
||||
# Unlike Net::BufferedIO#read, this method returns more than +clen+ bytes.
|
||||
# At this time there is no way for a user of Net::HTTPResponse to read a
|
||||
# specific number of bytes from the HTTP response body, so this internal
|
||||
# API does not return the same number of bytes as were requested.
|
||||
#
|
||||
# See https://bugs.ruby-lang.org/issues/6492 for further discussion.
|
||||
|
||||
def read clen, dest, ignore_eof = false
|
||||
temp_dest = inflate_adapter(dest)
|
||||
|
||||
@socket.read clen, temp_dest, ignore_eof
|
||||
end
|
||||
|
||||
##
|
||||
# Reads the rest of the socket, inflates it, then writes it to +dest+.
|
||||
|
||||
def read_all dest
|
||||
temp_dest = inflate_adapter(dest)
|
||||
|
||||
@socket.read_all temp_dest
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
299
gxg/standard/net/http/responses.rb
Normal file
299
gxg/standard/net/http/responses.rb
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
# frozen_string_literal: false
|
||||
# :stopdoc:
|
||||
# https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
|
||||
class Net::HTTPUnknownResponse < Net::HTTPResponse
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = Net::HTTPError
|
||||
end
|
||||
class Net::HTTPInformation < Net::HTTPResponse # 1xx
|
||||
HAS_BODY = false
|
||||
EXCEPTION_TYPE = Net::HTTPError
|
||||
end
|
||||
class Net::HTTPSuccess < Net::HTTPResponse # 2xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = Net::HTTPError
|
||||
end
|
||||
class Net::HTTPRedirection < Net::HTTPResponse # 3xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = Net::HTTPRetriableError
|
||||
end
|
||||
class Net::HTTPClientError < Net::HTTPResponse # 4xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = Net::HTTPServerException # for backward compatibility
|
||||
end
|
||||
class Net::HTTPServerError < Net::HTTPResponse # 5xx
|
||||
HAS_BODY = true
|
||||
EXCEPTION_TYPE = Net::HTTPFatalError # for backward compatibility
|
||||
end
|
||||
|
||||
class Net::HTTPContinue < Net::HTTPInformation # 100
|
||||
HAS_BODY = false
|
||||
end
|
||||
class Net::HTTPSwitchProtocol < Net::HTTPInformation # 101
|
||||
HAS_BODY = false
|
||||
end
|
||||
class Net::HTTPProcessing < Net::HTTPInformation # 102
|
||||
HAS_BODY = false
|
||||
end
|
||||
|
||||
class Net::HTTPOK < Net::HTTPSuccess # 200
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPCreated < Net::HTTPSuccess # 201
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPAccepted < Net::HTTPSuccess # 202
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNonAuthoritativeInformation < Net::HTTPSuccess # 203
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNoContent < Net::HTTPSuccess # 204
|
||||
HAS_BODY = false
|
||||
end
|
||||
class Net::HTTPResetContent < Net::HTTPSuccess # 205
|
||||
HAS_BODY = false
|
||||
end
|
||||
class Net::HTTPPartialContent < Net::HTTPSuccess # 206
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPMultiStatus < Net::HTTPSuccess # 207 - RFC 4918
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPAlreadyReported < Net::HTTPSuccess # 208 - RFC 5842
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPIMUsed < Net::HTTPSuccess # 226 - RFC 3229
|
||||
HAS_BODY = true
|
||||
end
|
||||
|
||||
class Net::HTTPMultipleChoices < Net::HTTPRedirection # 300
|
||||
HAS_BODY = true
|
||||
end
|
||||
Net::HTTPMultipleChoice = Net::HTTPMultipleChoices
|
||||
class Net::HTTPMovedPermanently < Net::HTTPRedirection # 301
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPFound < Net::HTTPRedirection # 302
|
||||
HAS_BODY = true
|
||||
end
|
||||
Net::HTTPMovedTemporarily = Net::HTTPFound
|
||||
class Net::HTTPSeeOther < Net::HTTPRedirection # 303
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNotModified < Net::HTTPRedirection # 304
|
||||
HAS_BODY = false
|
||||
end
|
||||
class Net::HTTPUseProxy < Net::HTTPRedirection # 305
|
||||
HAS_BODY = false
|
||||
end
|
||||
# 306 Switch Proxy - no longer unused
|
||||
class Net::HTTPTemporaryRedirect < Net::HTTPRedirection # 307
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPPermanentRedirect < Net::HTTPRedirection # 308
|
||||
HAS_BODY = true
|
||||
end
|
||||
|
||||
class Net::HTTPBadRequest < Net::HTTPClientError # 400
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPUnauthorized < Net::HTTPClientError # 401
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPPaymentRequired < Net::HTTPClientError # 402
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPForbidden < Net::HTTPClientError # 403
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNotFound < Net::HTTPClientError # 404
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPMethodNotAllowed < Net::HTTPClientError # 405
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNotAcceptable < Net::HTTPClientError # 406
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPProxyAuthenticationRequired < Net::HTTPClientError # 407
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPRequestTimeOut < Net::HTTPClientError # 408
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPConflict < Net::HTTPClientError # 409
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPGone < Net::HTTPClientError # 410
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPLengthRequired < Net::HTTPClientError # 411
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPPreconditionFailed < Net::HTTPClientError # 412
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPRequestEntityTooLarge < Net::HTTPClientError # 413
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPRequestURITooLong < Net::HTTPClientError # 414
|
||||
HAS_BODY = true
|
||||
end
|
||||
Net::HTTPRequestURITooLarge = Net::HTTPRequestURITooLong
|
||||
class Net::HTTPUnsupportedMediaType < Net::HTTPClientError # 415
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPRequestedRangeNotSatisfiable < Net::HTTPClientError # 416
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPExpectationFailed < Net::HTTPClientError # 417
|
||||
HAS_BODY = true
|
||||
end
|
||||
# 418 I'm a teapot - RFC 2324; a joke RFC
|
||||
# 420 Enhance Your Calm - Twitter
|
||||
class Net::HTTPMisdirectedRequest < Net::HTTPClientError # 421 - RFC 7540
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPUnprocessableEntity < Net::HTTPClientError # 422 - RFC 4918
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPLocked < Net::HTTPClientError # 423 - RFC 4918
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPFailedDependency < Net::HTTPClientError # 424 - RFC 4918
|
||||
HAS_BODY = true
|
||||
end
|
||||
# 425 Unordered Collection - existed only in draft
|
||||
class Net::HTTPUpgradeRequired < Net::HTTPClientError # 426 - RFC 2817
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPPreconditionRequired < Net::HTTPClientError # 428 - RFC 6585
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPTooManyRequests < Net::HTTPClientError # 429 - RFC 6585
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPRequestHeaderFieldsTooLarge < Net::HTTPClientError # 431 - RFC 6585
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPUnavailableForLegalReasons < Net::HTTPClientError # 451 - RFC 7725
|
||||
HAS_BODY = true
|
||||
end
|
||||
# 444 No Response - Nginx
|
||||
# 449 Retry With - Microsoft
|
||||
# 450 Blocked by Windows Parental Controls - Microsoft
|
||||
# 499 Client Closed Request - Nginx
|
||||
|
||||
class Net::HTTPInternalServerError < Net::HTTPServerError # 500
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNotImplemented < Net::HTTPServerError # 501
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPBadGateway < Net::HTTPServerError # 502
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPServiceUnavailable < Net::HTTPServerError # 503
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPGatewayTimeOut < Net::HTTPServerError # 504
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPVersionNotSupported < Net::HTTPServerError # 505
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPVariantAlsoNegotiates < Net::HTTPServerError # 506
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPInsufficientStorage < Net::HTTPServerError # 507 - RFC 4918
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPLoopDetected < Net::HTTPServerError # 508 - RFC 5842
|
||||
HAS_BODY = true
|
||||
end
|
||||
# 509 Bandwidth Limit Exceeded - Apache bw/limited extension
|
||||
class Net::HTTPNotExtended < Net::HTTPServerError # 510 - RFC 2774
|
||||
HAS_BODY = true
|
||||
end
|
||||
class Net::HTTPNetworkAuthenticationRequired < Net::HTTPServerError # 511 - RFC 6585
|
||||
HAS_BODY = true
|
||||
end
|
||||
|
||||
class Net::HTTPResponse
|
||||
CODE_CLASS_TO_OBJ = {
|
||||
'1' => Net::HTTPInformation,
|
||||
'2' => Net::HTTPSuccess,
|
||||
'3' => Net::HTTPRedirection,
|
||||
'4' => Net::HTTPClientError,
|
||||
'5' => Net::HTTPServerError
|
||||
}
|
||||
CODE_TO_OBJ = {
|
||||
'100' => Net::HTTPContinue,
|
||||
'101' => Net::HTTPSwitchProtocol,
|
||||
'102' => Net::HTTPProcessing,
|
||||
|
||||
'200' => Net::HTTPOK,
|
||||
'201' => Net::HTTPCreated,
|
||||
'202' => Net::HTTPAccepted,
|
||||
'203' => Net::HTTPNonAuthoritativeInformation,
|
||||
'204' => Net::HTTPNoContent,
|
||||
'205' => Net::HTTPResetContent,
|
||||
'206' => Net::HTTPPartialContent,
|
||||
'207' => Net::HTTPMultiStatus,
|
||||
'208' => Net::HTTPAlreadyReported,
|
||||
'226' => Net::HTTPIMUsed,
|
||||
|
||||
'300' => Net::HTTPMultipleChoices,
|
||||
'301' => Net::HTTPMovedPermanently,
|
||||
'302' => Net::HTTPFound,
|
||||
'303' => Net::HTTPSeeOther,
|
||||
'304' => Net::HTTPNotModified,
|
||||
'305' => Net::HTTPUseProxy,
|
||||
'307' => Net::HTTPTemporaryRedirect,
|
||||
'308' => Net::HTTPPermanentRedirect,
|
||||
|
||||
'400' => Net::HTTPBadRequest,
|
||||
'401' => Net::HTTPUnauthorized,
|
||||
'402' => Net::HTTPPaymentRequired,
|
||||
'403' => Net::HTTPForbidden,
|
||||
'404' => Net::HTTPNotFound,
|
||||
'405' => Net::HTTPMethodNotAllowed,
|
||||
'406' => Net::HTTPNotAcceptable,
|
||||
'407' => Net::HTTPProxyAuthenticationRequired,
|
||||
'408' => Net::HTTPRequestTimeOut,
|
||||
'409' => Net::HTTPConflict,
|
||||
'410' => Net::HTTPGone,
|
||||
'411' => Net::HTTPLengthRequired,
|
||||
'412' => Net::HTTPPreconditionFailed,
|
||||
'413' => Net::HTTPRequestEntityTooLarge,
|
||||
'414' => Net::HTTPRequestURITooLong,
|
||||
'415' => Net::HTTPUnsupportedMediaType,
|
||||
'416' => Net::HTTPRequestedRangeNotSatisfiable,
|
||||
'417' => Net::HTTPExpectationFailed,
|
||||
'421' => Net::HTTPMisdirectedRequest,
|
||||
'422' => Net::HTTPUnprocessableEntity,
|
||||
'423' => Net::HTTPLocked,
|
||||
'424' => Net::HTTPFailedDependency,
|
||||
'426' => Net::HTTPUpgradeRequired,
|
||||
'428' => Net::HTTPPreconditionRequired,
|
||||
'429' => Net::HTTPTooManyRequests,
|
||||
'431' => Net::HTTPRequestHeaderFieldsTooLarge,
|
||||
'451' => Net::HTTPUnavailableForLegalReasons,
|
||||
|
||||
'500' => Net::HTTPInternalServerError,
|
||||
'501' => Net::HTTPNotImplemented,
|
||||
'502' => Net::HTTPBadGateway,
|
||||
'503' => Net::HTTPServiceUnavailable,
|
||||
'504' => Net::HTTPGatewayTimeOut,
|
||||
'505' => Net::HTTPVersionNotSupported,
|
||||
'506' => Net::HTTPVariantAlsoNegotiates,
|
||||
'507' => Net::HTTPInsufficientStorage,
|
||||
'508' => Net::HTTPLoopDetected,
|
||||
'510' => Net::HTTPNotExtended,
|
||||
'511' => Net::HTTPNetworkAuthenticationRequired,
|
||||
}
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
|
||||
83
gxg/standard/net/http/status.rb
Normal file
83
gxg/standard/net/http/status.rb
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env ruby
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'net/http'
|
||||
|
||||
if $0 == __FILE__
|
||||
require 'open-uri'
|
||||
IO.foreach(__FILE__) do |line|
|
||||
puts line
|
||||
break if line.start_with?('end')
|
||||
end
|
||||
puts
|
||||
puts "Net::HTTP::STATUS_CODES = {"
|
||||
url = "https://www.iana.org/assignments/http-status-codes/http-status-codes-1.csv"
|
||||
URI(url).read.each_line do |line|
|
||||
code, mes, = line.split(',')
|
||||
next if ['(Unused)', 'Unassigned', 'Description'].include?(mes)
|
||||
puts " #{code} => '#{mes}',"
|
||||
end
|
||||
puts "}"
|
||||
end
|
||||
|
||||
Net::HTTP::STATUS_CODES = {
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
208 => 'Already Reported',
|
||||
226 => 'IM Used',
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
307 => 'Temporary Redirect',
|
||||
308 => 'Permanent Redirect',
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
409 => 'Conflict',
|
||||
410 => 'Gone',
|
||||
411 => 'Length Required',
|
||||
412 => 'Precondition Failed',
|
||||
413 => 'Payload Too Large',
|
||||
414 => 'URI Too Long',
|
||||
415 => 'Unsupported Media Type',
|
||||
416 => 'Range Not Satisfiable',
|
||||
417 => 'Expectation Failed',
|
||||
421 => 'Misdirected Request',
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
426 => 'Upgrade Required',
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
431 => 'Request Header Fields Too Large',
|
||||
451 => 'Unavailable For Legal Reasons',
|
||||
500 => 'Internal Server Error',
|
||||
501 => 'Not Implemented',
|
||||
502 => 'Bad Gateway',
|
||||
503 => 'Service Unavailable',
|
||||
504 => 'Gateway Timeout',
|
||||
505 => 'HTTP Version Not Supported',
|
||||
506 => 'Variant Also Negotiates',
|
||||
507 => 'Insufficient Storage',
|
||||
508 => 'Loop Detected',
|
||||
510 => 'Not Extended',
|
||||
511 => 'Network Authentication Required',
|
||||
}
|
||||
23
gxg/standard/net/https.rb
Normal file
23
gxg/standard/net/https.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: false
|
||||
=begin
|
||||
|
||||
= net/https -- SSL/TLS enhancement for Net::HTTP.
|
||||
|
||||
This file has been merged with net/http. There is no longer any need to
|
||||
require 'net/https' to use HTTPS.
|
||||
|
||||
See Net::HTTP for details on how to make HTTPS connections.
|
||||
|
||||
== Info
|
||||
'OpenSSL for Ruby 2' project
|
||||
Copyright (C) 2001 GOTOU Yuuzou <gotoyuzo@notwork.org>
|
||||
All rights reserved.
|
||||
|
||||
== Licence
|
||||
This program is licensed under the same licence as Ruby.
|
||||
(See the file 'LICENCE'.)
|
||||
|
||||
=end
|
||||
|
||||
require 'net/http'
|
||||
require 'openssl'
|
||||
3724
gxg/standard/net/imap.rb
Normal file
3724
gxg/standard/net/imap.rb
Normal file
File diff suppressed because it is too large
Load diff
1022
gxg/standard/net/pop.rb
Normal file
1022
gxg/standard/net/pop.rb
Normal file
File diff suppressed because it is too large
Load diff
442
gxg/standard/net/protocol.rb
Normal file
442
gxg/standard/net/protocol.rb
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# = net/protocol.rb
|
||||
#
|
||||
#--
|
||||
# Copyright (c) 1999-2004 Yukihiro Matsumoto
|
||||
# Copyright (c) 1999-2004 Minero Aoki
|
||||
#
|
||||
# written and maintained by Minero Aoki <aamine@loveruby.net>
|
||||
#
|
||||
# This program is free software. You can re-distribute and/or
|
||||
# modify this program under the same terms as Ruby itself,
|
||||
# Ruby Distribute License or GNU General Public License.
|
||||
#
|
||||
# $Id: protocol.rb 58846 2017-05-22 07:36:00Z normal $
|
||||
#++
|
||||
#
|
||||
# WARNING: This file is going to remove.
|
||||
# Do not rely on the implementation written in this file.
|
||||
#
|
||||
|
||||
require 'socket'
|
||||
require 'timeout'
|
||||
require 'io/wait'
|
||||
|
||||
module Net # :nodoc:
|
||||
|
||||
class Protocol #:nodoc: internal use only
|
||||
private
|
||||
def Protocol.protocol_param(name, val)
|
||||
module_eval(<<-End, __FILE__, __LINE__ + 1)
|
||||
def #{name}
|
||||
#{val}
|
||||
end
|
||||
End
|
||||
end
|
||||
|
||||
def ssl_socket_connect(s, timeout)
|
||||
if timeout
|
||||
while true
|
||||
raise Net::OpenTimeout if timeout <= 0
|
||||
start = Process.clock_gettime Process::CLOCK_MONOTONIC
|
||||
# to_io is required because SSLSocket doesn't have wait_readable yet
|
||||
case s.connect_nonblock(exception: false)
|
||||
when :wait_readable; s.to_io.wait_readable(timeout)
|
||||
when :wait_writable; s.to_io.wait_writable(timeout)
|
||||
else; break
|
||||
end
|
||||
timeout -= Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
|
||||
end
|
||||
else
|
||||
s.connect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ProtocolError < StandardError; end
|
||||
class ProtoSyntaxError < ProtocolError; end
|
||||
class ProtoFatalError < ProtocolError; end
|
||||
class ProtoUnknownError < ProtocolError; end
|
||||
class ProtoServerError < ProtocolError; end
|
||||
class ProtoAuthError < ProtocolError; end
|
||||
class ProtoCommandError < ProtocolError; end
|
||||
class ProtoRetriableError < ProtocolError; end
|
||||
ProtocRetryError = ProtoRetriableError
|
||||
|
||||
##
|
||||
# OpenTimeout, a subclass of Timeout::Error, is raised if a connection cannot
|
||||
# be created within the open_timeout.
|
||||
|
||||
class OpenTimeout < Timeout::Error; end
|
||||
|
||||
##
|
||||
# ReadTimeout, a subclass of Timeout::Error, is raised if a chunk of the
|
||||
# response cannot be read within the read_timeout.
|
||||
|
||||
class ReadTimeout < Timeout::Error; end
|
||||
|
||||
|
||||
class BufferedIO #:nodoc: internal use only
|
||||
def initialize(io, read_timeout: 60, continue_timeout: nil, debug_output: nil)
|
||||
@io = io
|
||||
@read_timeout = read_timeout
|
||||
@continue_timeout = continue_timeout
|
||||
@debug_output = debug_output
|
||||
@rbuf = ''.dup
|
||||
end
|
||||
|
||||
attr_reader :io
|
||||
attr_accessor :read_timeout
|
||||
attr_accessor :continue_timeout
|
||||
attr_accessor :debug_output
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} io=#{@io}>"
|
||||
end
|
||||
|
||||
def eof?
|
||||
@io.eof?
|
||||
end
|
||||
|
||||
def closed?
|
||||
@io.closed?
|
||||
end
|
||||
|
||||
def close
|
||||
@io.close
|
||||
end
|
||||
|
||||
#
|
||||
# Read
|
||||
#
|
||||
|
||||
public
|
||||
|
||||
def read(len, dest = ''.dup, ignore_eof = false)
|
||||
LOG "reading #{len} bytes..."
|
||||
read_bytes = 0
|
||||
begin
|
||||
while read_bytes + @rbuf.size < len
|
||||
s = rbuf_consume(@rbuf.size)
|
||||
read_bytes += s.size
|
||||
dest << s
|
||||
rbuf_fill
|
||||
end
|
||||
s = rbuf_consume(len - read_bytes)
|
||||
read_bytes += s.size
|
||||
dest << s
|
||||
rescue EOFError
|
||||
raise unless ignore_eof
|
||||
end
|
||||
LOG "read #{read_bytes} bytes"
|
||||
dest
|
||||
end
|
||||
|
||||
def read_all(dest = ''.dup)
|
||||
LOG 'reading all...'
|
||||
read_bytes = 0
|
||||
begin
|
||||
while true
|
||||
s = rbuf_consume(@rbuf.size)
|
||||
read_bytes += s.size
|
||||
dest << s
|
||||
rbuf_fill
|
||||
end
|
||||
rescue EOFError
|
||||
;
|
||||
end
|
||||
LOG "read #{read_bytes} bytes"
|
||||
dest
|
||||
end
|
||||
|
||||
def readuntil(terminator, ignore_eof = false)
|
||||
begin
|
||||
until idx = @rbuf.index(terminator)
|
||||
rbuf_fill
|
||||
end
|
||||
return rbuf_consume(idx + terminator.size)
|
||||
rescue EOFError
|
||||
raise unless ignore_eof
|
||||
return rbuf_consume(@rbuf.size)
|
||||
end
|
||||
end
|
||||
|
||||
def readline
|
||||
readuntil("\n").chop
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
BUFSIZE = 1024 * 16
|
||||
|
||||
def rbuf_fill
|
||||
case rv = @io.read_nonblock(BUFSIZE, exception: false)
|
||||
when String
|
||||
@rbuf << rv
|
||||
rv.clear
|
||||
return
|
||||
when :wait_readable
|
||||
@io.to_io.wait_readable(@read_timeout) or raise Net::ReadTimeout
|
||||
# continue looping
|
||||
when :wait_writable
|
||||
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
|
||||
# http://www.openssl.org/support/faq.html#PROG10
|
||||
@io.to_io.wait_writable(@read_timeout) or raise Net::ReadTimeout
|
||||
# continue looping
|
||||
when nil
|
||||
raise EOFError, 'end of file reached'
|
||||
end while true
|
||||
end
|
||||
|
||||
def rbuf_consume(len)
|
||||
s = @rbuf.slice!(0, len)
|
||||
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
|
||||
s
|
||||
end
|
||||
|
||||
#
|
||||
# Write
|
||||
#
|
||||
|
||||
public
|
||||
|
||||
def write(str)
|
||||
writing {
|
||||
write0 str
|
||||
}
|
||||
end
|
||||
|
||||
alias << write
|
||||
|
||||
def writeline(str)
|
||||
writing {
|
||||
write0 str + "\r\n"
|
||||
}
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def writing
|
||||
@written_bytes = 0
|
||||
@debug_output << '<- ' if @debug_output
|
||||
yield
|
||||
@debug_output << "\n" if @debug_output
|
||||
bytes = @written_bytes
|
||||
@written_bytes = nil
|
||||
bytes
|
||||
end
|
||||
|
||||
def write0(str)
|
||||
@debug_output << str.dump if @debug_output
|
||||
len = @io.write(str)
|
||||
@written_bytes += len
|
||||
len
|
||||
end
|
||||
|
||||
#
|
||||
# Logging
|
||||
#
|
||||
|
||||
private
|
||||
|
||||
def LOG_off
|
||||
@save_debug_out = @debug_output
|
||||
@debug_output = nil
|
||||
end
|
||||
|
||||
def LOG_on
|
||||
@debug_output = @save_debug_out
|
||||
end
|
||||
|
||||
def LOG(msg)
|
||||
return unless @debug_output
|
||||
@debug_output << msg + "\n"
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class InternetMessageIO < BufferedIO #:nodoc: internal use only
|
||||
def initialize(*)
|
||||
super
|
||||
@wbuf = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Read
|
||||
#
|
||||
|
||||
def each_message_chunk
|
||||
LOG 'reading message...'
|
||||
LOG_off()
|
||||
read_bytes = 0
|
||||
while (line = readuntil("\r\n")) != ".\r\n"
|
||||
read_bytes += line.size
|
||||
yield line.sub(/\A\./, '')
|
||||
end
|
||||
LOG_on()
|
||||
LOG "read message (#{read_bytes} bytes)"
|
||||
end
|
||||
|
||||
# *library private* (cannot handle 'break')
|
||||
def each_list_item
|
||||
while (str = readuntil("\r\n")) != ".\r\n"
|
||||
yield str.chop
|
||||
end
|
||||
end
|
||||
|
||||
def write_message_0(src)
|
||||
prev = @written_bytes
|
||||
each_crlf_line(src) do |line|
|
||||
write0 dot_stuff(line)
|
||||
end
|
||||
@written_bytes - prev
|
||||
end
|
||||
|
||||
#
|
||||
# Write
|
||||
#
|
||||
|
||||
def write_message(src)
|
||||
LOG "writing message from #{src.class}"
|
||||
LOG_off()
|
||||
len = writing {
|
||||
using_each_crlf_line {
|
||||
write_message_0 src
|
||||
}
|
||||
}
|
||||
LOG_on()
|
||||
LOG "wrote #{len} bytes"
|
||||
len
|
||||
end
|
||||
|
||||
def write_message_by_block(&block)
|
||||
LOG 'writing message from block'
|
||||
LOG_off()
|
||||
len = writing {
|
||||
using_each_crlf_line {
|
||||
begin
|
||||
block.call(WriteAdapter.new(self, :write_message_0))
|
||||
rescue LocalJumpError
|
||||
# allow `break' from writer block
|
||||
end
|
||||
}
|
||||
}
|
||||
LOG_on()
|
||||
LOG "wrote #{len} bytes"
|
||||
len
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def dot_stuff(s)
|
||||
s.sub(/\A\./, '..')
|
||||
end
|
||||
|
||||
def using_each_crlf_line
|
||||
@wbuf = ''.dup
|
||||
yield
|
||||
if not @wbuf.empty? # unterminated last line
|
||||
write0 dot_stuff(@wbuf.chomp) + "\r\n"
|
||||
elsif @written_bytes == 0 # empty src
|
||||
write0 "\r\n"
|
||||
end
|
||||
write0 ".\r\n"
|
||||
@wbuf = nil
|
||||
end
|
||||
|
||||
def each_crlf_line(src)
|
||||
buffer_filling(@wbuf, src) do
|
||||
while line = @wbuf.slice!(/\A[^\r\n]*(?:\n|\r(?:\n|(?!\z)))/)
|
||||
yield line.chomp("\n") + "\r\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def buffer_filling(buf, src)
|
||||
case src
|
||||
when String # for speeding up.
|
||||
0.step(src.size - 1, 1024) do |i|
|
||||
buf << src[i, 1024]
|
||||
yield
|
||||
end
|
||||
when File # for speeding up.
|
||||
while s = src.read(1024)
|
||||
buf << s
|
||||
yield
|
||||
end
|
||||
else # generic reader
|
||||
src.each do |str|
|
||||
buf << str
|
||||
yield if buf.size > 1024
|
||||
end
|
||||
yield unless buf.empty?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# The writer adapter class
|
||||
#
|
||||
class WriteAdapter
|
||||
def initialize(socket, method)
|
||||
@socket = socket
|
||||
@method_id = method
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class} socket=#{@socket.inspect}>"
|
||||
end
|
||||
|
||||
def write(str)
|
||||
@socket.__send__(@method_id, str)
|
||||
end
|
||||
|
||||
alias print write
|
||||
|
||||
def <<(str)
|
||||
write str
|
||||
self
|
||||
end
|
||||
|
||||
def puts(str = '')
|
||||
write str.chomp("\n") + "\n"
|
||||
end
|
||||
|
||||
def printf(*args)
|
||||
write sprintf(*args)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
class ReadAdapter #:nodoc: internal use only
|
||||
def initialize(block)
|
||||
@block = block
|
||||
end
|
||||
|
||||
def inspect
|
||||
"#<#{self.class}>"
|
||||
end
|
||||
|
||||
def <<(str)
|
||||
call_block(str, &@block) if @block
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method is needed because @block must be called by yield,
|
||||
# not Proc#call. You can see difference when using `break' in
|
||||
# the block.
|
||||
def call_block(str)
|
||||
yield str
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
module NetPrivate #:nodoc: obsolete
|
||||
Socket = ::Net::InternetMessageIO
|
||||
end
|
||||
|
||||
end # module Net
|
||||
1078
gxg/standard/net/smtp.rb
Normal file
1078
gxg/standard/net/smtp.rb
Normal file
File diff suppressed because it is too large
Load diff
40
gxg/standard/openssl/bn.rb
Normal file
40
gxg/standard/openssl/bn.rb
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
#
|
||||
# = Ruby-space definitions that completes C-space funcs for BN
|
||||
#
|
||||
# = Info
|
||||
# 'OpenSSL for Ruby 2' project
|
||||
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
|
||||
# All rights reserved.
|
||||
#
|
||||
# = Licence
|
||||
# This program is licensed under the same licence as Ruby.
|
||||
# (See the file 'LICENCE'.)
|
||||
#++
|
||||
|
||||
module OpenSSL
|
||||
class BN
|
||||
include Comparable
|
||||
|
||||
def pretty_print(q)
|
||||
q.object_group(self) {
|
||||
q.text ' '
|
||||
q.text to_i.to_s
|
||||
}
|
||||
end
|
||||
end # BN
|
||||
end # OpenSSL
|
||||
|
||||
##
|
||||
#--
|
||||
# Add double dispatch to Integer
|
||||
#++
|
||||
class Integer
|
||||
# Casts an Integer as an OpenSSL::BN
|
||||
#
|
||||
# See `man bn` for more info.
|
||||
def to_bn
|
||||
OpenSSL::BN::new(self)
|
||||
end
|
||||
end # Integer
|
||||
455
gxg/standard/openssl/buffering.rb
Normal file
455
gxg/standard/openssl/buffering.rb
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
# coding: binary
|
||||
# frozen_string_literal: false
|
||||
#--
|
||||
#= Info
|
||||
# 'OpenSSL for Ruby 2' project
|
||||
# Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
|
||||
# All rights reserved.
|
||||
#
|
||||
#= Licence
|
||||
# This program is licensed under the same licence as Ruby.
|
||||
# (See the file 'LICENCE'.)
|
||||
#++
|
||||
|
||||
##
|
||||
# OpenSSL IO buffering mix-in module.
|
||||
#
|
||||
# This module allows an OpenSSL::SSL::SSLSocket to behave like an IO.
|
||||
#
|
||||
# You typically won't use this module directly, you can see it implemented in
|
||||
# OpenSSL::SSL::SSLSocket.
|
||||
|
||||
module OpenSSL::Buffering
|
||||
include Enumerable
|
||||
|
||||
##
|
||||
# The "sync mode" of the SSLSocket.
|
||||
#
|
||||
# See IO#sync for full details.
|
||||
|
||||
attr_accessor :sync
|
||||
|
||||
##
|
||||
# Default size to read from or write to the SSLSocket for buffer operations.
|
||||
|
||||
BLOCK_SIZE = 1024*16
|
||||
|
||||
##
|
||||
# Creates an instance of OpenSSL's buffering IO module.
|
||||
|
||||
def initialize(*)
|
||||
super
|
||||
@eof = false
|
||||
@rbuffer = ""
|
||||
@sync = @io.sync
|
||||
end
|
||||
|
||||
#
|
||||
# for reading.
|
||||
#
|
||||
private
|
||||
|
||||
##
|
||||
# Fills the buffer from the underlying SSLSocket
|
||||
|
||||
def fill_rbuff
|
||||
begin
|
||||
@rbuffer << self.sysread(BLOCK_SIZE)
|
||||
rescue Errno::EAGAIN
|
||||
retry
|
||||
rescue EOFError
|
||||
@eof = true
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Consumes _size_ bytes from the buffer
|
||||
|
||||
def consume_rbuff(size=nil)
|
||||
if @rbuffer.empty?
|
||||
nil
|
||||
else
|
||||
size = @rbuffer.size unless size
|
||||
ret = @rbuffer[0, size]
|
||||
@rbuffer[0, size] = ""
|
||||
ret
|
||||
end
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
##
|
||||
# Reads _size_ bytes from the stream. If _buf_ is provided it must
|
||||
# reference a string which will receive the data.
|
||||
#
|
||||
# See IO#read for full details.
|
||||
|
||||
def read(size=nil, buf=nil)
|
||||
if size == 0
|
||||
if buf
|
||||
buf.clear
|
||||
return buf
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
until @eof
|
||||
break if size && size <= @rbuffer.size
|
||||
fill_rbuff
|
||||
end
|
||||
ret = consume_rbuff(size) || ""
|
||||
if buf
|
||||
buf.replace(ret)
|
||||
ret = buf
|
||||
end
|
||||
(size && ret.empty?) ? nil : ret
|
||||
end
|
||||
|
||||
##
|
||||
# Reads at most _maxlen_ bytes from the stream. If _buf_ is provided it
|
||||
# must reference a string which will receive the data.
|
||||
#
|
||||
# See IO#readpartial for full details.
|
||||
|
||||
def readpartial(maxlen, buf=nil)
|
||||
if maxlen == 0
|
||||
if buf
|
||||
buf.clear
|
||||
return buf
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
if @rbuffer.empty?
|
||||
begin
|
||||
return sysread(maxlen, buf)
|
||||
rescue Errno::EAGAIN
|
||||
retry
|
||||
end
|
||||
end
|
||||
ret = consume_rbuff(maxlen)
|
||||
if buf
|
||||
buf.replace(ret)
|
||||
ret = buf
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
##
|
||||
# Reads at most _maxlen_ bytes in the non-blocking manner.
|
||||
#
|
||||
# When no data can be read without blocking it raises
|
||||
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
|
||||
#
|
||||
# IO::WaitReadable means SSL needs to read internally so read_nonblock
|
||||
# should be called again when the underlying IO is readable.
|
||||
#
|
||||
# IO::WaitWritable means SSL needs to write internally so read_nonblock
|
||||
# should be called again after the underlying IO is writable.
|
||||
#
|
||||
# OpenSSL::Buffering#read_nonblock needs two rescue clause as follows:
|
||||
#
|
||||
# # emulates blocking read (readpartial).
|
||||
# begin
|
||||
# result = ssl.read_nonblock(maxlen)
|
||||
# rescue IO::WaitReadable
|
||||
# IO.select([io])
|
||||
# retry
|
||||
# rescue IO::WaitWritable
|
||||
# IO.select(nil, [io])
|
||||
# retry
|
||||
# end
|
||||
#
|
||||
# Note that one reason that read_nonblock writes to the underlying IO is
|
||||
# when the peer requests a new TLS/SSL handshake. See openssl the FAQ for
|
||||
# more details. http://www.openssl.org/support/faq.html
|
||||
#
|
||||
# By specifying a keyword argument _exception_ to +false+, you can indicate
|
||||
# that read_nonblock should not raise an IO::Wait*able exception, but
|
||||
# return the symbol +:wait_writable+ or +:wait_readable+ instead. At EOF,
|
||||
# it will return +nil+ instead of raising EOFError.
|
||||
|
||||
def read_nonblock(maxlen, buf=nil, exception: true)
|
||||
if maxlen == 0
|
||||
if buf
|
||||
buf.clear
|
||||
return buf
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
if @rbuffer.empty?
|
||||
return sysread_nonblock(maxlen, buf, exception: exception)
|
||||
end
|
||||
ret = consume_rbuff(maxlen)
|
||||
if buf
|
||||
buf.replace(ret)
|
||||
ret = buf
|
||||
end
|
||||
ret
|
||||
end
|
||||
|
||||
##
|
||||
# Reads the next "line" from the stream. Lines are separated by _eol_. If
|
||||
# _limit_ is provided the result will not be longer than the given number of
|
||||
# bytes.
|
||||
#
|
||||
# _eol_ may be a String or Regexp.
|
||||
#
|
||||
# Unlike IO#gets the line read will not be assigned to +$_+.
|
||||
#
|
||||
# Unlike IO#gets the separator must be provided if a limit is provided.
|
||||
|
||||
def gets(eol=$/, limit=nil)
|
||||
idx = @rbuffer.index(eol)
|
||||
until @eof
|
||||
break if idx
|
||||
fill_rbuff
|
||||
idx = @rbuffer.index(eol)
|
||||
end
|
||||
if eol.is_a?(Regexp)
|
||||
size = idx ? idx+$&.size : nil
|
||||
else
|
||||
size = idx ? idx+eol.size : nil
|
||||
end
|
||||
if size && limit && limit >= 0
|
||||
size = [size, limit].min
|
||||
end
|
||||
consume_rbuff(size)
|
||||
end
|
||||
|
||||
##
|
||||
# Executes the block for every line in the stream where lines are separated
|
||||
# by _eol_.
|
||||
#
|
||||
# See also #gets
|
||||
|
||||
def each(eol=$/)
|
||||
while line = self.gets(eol)
|
||||
yield line
|
||||
end
|
||||
end
|
||||
alias each_line each
|
||||
|
||||
##
|
||||
# Reads lines from the stream which are separated by _eol_.
|
||||
#
|
||||
# See also #gets
|
||||
|
||||
def readlines(eol=$/)
|
||||
ary = []
|
||||
while line = self.gets(eol)
|
||||
ary << line
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
##
|
||||
# Reads a line from the stream which is separated by _eol_.
|
||||
#
|
||||
# Raises EOFError if at end of file.
|
||||
|
||||
def readline(eol=$/)
|
||||
raise EOFError if eof?
|
||||
gets(eol)
|
||||
end
|
||||
|
||||
##
|
||||
# Reads one character from the stream. Returns nil if called at end of
|
||||
# file.
|
||||
|
||||
def getc
|
||||
read(1)
|
||||
end
|
||||
|
||||
##
|
||||
# Calls the given block once for each byte in the stream.
|
||||
|
||||
def each_byte # :yields: byte
|
||||
while c = getc
|
||||
yield(c.ord)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Reads a one-character string from the stream. Raises an EOFError at end
|
||||
# of file.
|
||||
|
||||
def readchar
|
||||
raise EOFError if eof?
|
||||
getc
|
||||
end
|
||||
|
||||
##
|
||||
# Pushes character _c_ back onto the stream such that a subsequent buffered
|
||||
# character read will return it.
|
||||
#
|
||||
# Unlike IO#getc multiple bytes may be pushed back onto the stream.
|
||||
#
|
||||
# Has no effect on unbuffered reads (such as #sysread).
|
||||
|
||||
def ungetc(c)
|
||||
@rbuffer[0,0] = c.chr
|
||||
end
|
||||
|
||||
##
|
||||
# Returns true if the stream is at file which means there is no more data to
|
||||
# be read.
|
||||
|
||||
def eof?
|
||||
fill_rbuff if !@eof && @rbuffer.empty?
|
||||
@eof && @rbuffer.empty?
|
||||
end
|
||||
alias eof eof?
|
||||
|
||||
#
|
||||
# for writing.
|
||||
#
|
||||
private
|
||||
|
||||
##
|
||||
# Writes _s_ to the buffer. When the buffer is full or #sync is true the
|
||||
# buffer is flushed to the underlying socket.
|
||||
|
||||
def do_write(s)
|
||||
@wbuffer = "" unless defined? @wbuffer
|
||||
@wbuffer << s
|
||||
@wbuffer.force_encoding(Encoding::BINARY)
|
||||
@sync ||= false
|
||||
if @sync or @wbuffer.size > BLOCK_SIZE or @wbuffer.rindex("\n")
|
||||
until @wbuffer.empty?
|
||||
begin
|
||||
nwrote = syswrite(@wbuffer)
|
||||
rescue Errno::EAGAIN
|
||||
retry
|
||||
end
|
||||
@wbuffer[0, nwrote] = ""
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
##
|
||||
# Writes _s_ to the stream. If the argument is not a String it will be
|
||||
# converted using +.to_s+ method. Returns the number of bytes written.
|
||||
|
||||
def write(*s)
|
||||
s.inject(0) do |written, str|
|
||||
do_write(str)
|
||||
written + str.bytesize
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Writes _s_ in the non-blocking manner.
|
||||
#
|
||||
# If there is buffered data, it is flushed first. This may block.
|
||||
#
|
||||
# write_nonblock returns number of bytes written to the SSL connection.
|
||||
#
|
||||
# When no data can be written without blocking it raises
|
||||
# OpenSSL::SSL::SSLError extended by IO::WaitReadable or IO::WaitWritable.
|
||||
#
|
||||
# IO::WaitReadable means SSL needs to read internally so write_nonblock
|
||||
# should be called again after the underlying IO is readable.
|
||||
#
|
||||
# IO::WaitWritable means SSL needs to write internally so write_nonblock
|
||||
# should be called again after underlying IO is writable.
|
||||
#
|
||||
# So OpenSSL::Buffering#write_nonblock needs two rescue clause as follows.
|
||||
#
|
||||
# # emulates blocking write.
|
||||
# begin
|
||||
# result = ssl.write_nonblock(str)
|
||||
# rescue IO::WaitReadable
|
||||
# IO.select([io])
|
||||
# retry
|
||||
# rescue IO::WaitWritable
|
||||
# IO.select(nil, [io])
|
||||
# retry
|
||||
# end
|
||||
#
|
||||
# Note that one reason that write_nonblock reads from the underlying IO
|
||||
# is when the peer requests a new TLS/SSL handshake. See the openssl FAQ
|
||||
# for more details. http://www.openssl.org/support/faq.html
|
||||
#
|
||||
# By specifying a keyword argument _exception_ to +false+, you can indicate
|
||||
# that write_nonblock should not raise an IO::Wait*able exception, but
|
||||
# return the symbol +:wait_writable+ or +:wait_readable+ instead.
|
||||
|
||||
def write_nonblock(s, exception: true)
|
||||
flush
|
||||
syswrite_nonblock(s, exception: exception)
|
||||
end
|
||||
|
||||
##
|
||||
# Writes _s_ to the stream. _s_ will be converted to a String using
|
||||
# +.to_s+ method.
|
||||
|
||||
def <<(s)
|
||||
do_write(s)
|
||||
self
|
||||
end
|
||||
|
||||
##
|
||||
# Writes _args_ to the stream along with a record separator.
|
||||
#
|
||||
# See IO#puts for full details.
|
||||
|
||||
def puts(*args)
|
||||
s = ""
|
||||
if args.empty?
|
||||
s << "\n"
|
||||
end
|
||||
args.each{|arg|
|
||||
s << arg.to_s
|
||||
s.sub!(/(?<!\n)\z/, "\n")
|
||||
}
|
||||
do_write(s)
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Writes _args_ to the stream.
|
||||
#
|
||||
# See IO#print for full details.
|
||||
|
||||
def print(*args)
|
||||
s = ""
|
||||
args.each{ |arg| s << arg.to_s }
|
||||
do_write(s)
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Formats and writes to the stream converting parameters under control of
|
||||
# the format string.
|
||||
#
|
||||
# See Kernel#sprintf for format string details.
|
||||
|
||||
def printf(s, *args)
|
||||
do_write(s % args)
|
||||
nil
|
||||
end
|
||||
|
||||
##
|
||||
# Flushes buffered data to the SSLSocket.
|
||||
|
||||
def flush
|
||||
osync = @sync
|
||||
@sync = true
|
||||
do_write ""
|
||||
return self
|
||||
ensure
|
||||
@sync = osync
|
||||
end
|
||||
|
||||
##
|
||||
# Closes the SSLSocket and flushes any unwritten data.
|
||||
|
||||
def close
|
||||
flush rescue nil
|
||||
sysclose
|
||||
end
|
||||
end
|
||||
67
gxg/standard/openssl/cipher.rb
Normal file
67
gxg/standard/openssl/cipher.rb
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# = Ruby-space predefined Cipher subclasses
|
||||
#
|
||||
# = Info
|
||||
# 'OpenSSL for Ruby 2' project
|
||||
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
|
||||
# All rights reserved.
|
||||
#
|
||||
# = Licence
|
||||
# This program is licensed under the same licence as Ruby.
|
||||
# (See the file 'LICENCE'.)
|
||||
#++
|
||||
|
||||
module OpenSSL
|
||||
class Cipher
|
||||
%w(AES CAST5 BF DES IDEA RC2 RC4 RC5).each{|name|
|
||||
klass = Class.new(Cipher){
|
||||
define_method(:initialize){|*args|
|
||||
cipher_name = args.inject(name){|n, arg| "#{n}-#{arg}" }
|
||||
super(cipher_name.downcase)
|
||||
}
|
||||
}
|
||||
const_set(name, klass)
|
||||
}
|
||||
|
||||
%w(128 192 256).each{|keylen|
|
||||
klass = Class.new(Cipher){
|
||||
define_method(:initialize){|mode = "CBC"|
|
||||
super("aes-#{keylen}-#{mode}".downcase)
|
||||
}
|
||||
}
|
||||
const_set("AES#{keylen}", klass)
|
||||
}
|
||||
|
||||
# call-seq:
|
||||
# cipher.random_key -> key
|
||||
#
|
||||
# Generate a random key with OpenSSL::Random.random_bytes and sets it to
|
||||
# the cipher, and returns it.
|
||||
#
|
||||
# You must call #encrypt or #decrypt before calling this method.
|
||||
def random_key
|
||||
str = OpenSSL::Random.random_bytes(self.key_len)
|
||||
self.key = str
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# cipher.random_iv -> iv
|
||||
#
|
||||
# Generate a random IV with OpenSSL::Random.random_bytes and sets it to the
|
||||
# cipher, and returns it.
|
||||
#
|
||||
# You must call #encrypt or #decrypt before calling this method.
|
||||
def random_iv
|
||||
str = OpenSSL::Random.random_bytes(self.iv_len)
|
||||
self.iv = str
|
||||
end
|
||||
|
||||
# Deprecated.
|
||||
#
|
||||
# This class is only provided for backwards compatibility.
|
||||
# Use OpenSSL::Cipher.
|
||||
class Cipher < Cipher; end
|
||||
deprecate_constant :Cipher
|
||||
end # Cipher
|
||||
end # OpenSSL
|
||||
474
gxg/standard/openssl/config.rb
Normal file
474
gxg/standard/openssl/config.rb
Normal file
|
|
@ -0,0 +1,474 @@
|
|||
# frozen_string_literal: false
|
||||
=begin
|
||||
= Ruby-space definitions that completes C-space funcs for Config
|
||||
|
||||
= Info
|
||||
Copyright (C) 2010 Hiroshi Nakamura <nahi@ruby-lang.org>
|
||||
|
||||
= Licence
|
||||
This program is licensed under the same licence as Ruby.
|
||||
(See the file 'LICENCE'.)
|
||||
|
||||
=end
|
||||
|
||||
require 'stringio'
|
||||
|
||||
module OpenSSL
|
||||
##
|
||||
# = OpenSSL::Config
|
||||
#
|
||||
# Configuration for the openssl library.
|
||||
#
|
||||
# Many system's installation of openssl library will depend on your system
|
||||
# configuration. See the value of OpenSSL::Config::DEFAULT_CONFIG_FILE for
|
||||
# the location of the file for your host.
|
||||
#
|
||||
# See also http://www.openssl.org/docs/apps/config.html
|
||||
class Config
|
||||
include Enumerable
|
||||
|
||||
class << self
|
||||
|
||||
##
|
||||
# Parses a given _string_ as a blob that contains configuration for
|
||||
# OpenSSL.
|
||||
#
|
||||
# If the source of the IO is a file, then consider using #parse_config.
|
||||
def parse(string)
|
||||
c = new()
|
||||
parse_config(StringIO.new(string)).each do |section, hash|
|
||||
c[section] = hash
|
||||
end
|
||||
c
|
||||
end
|
||||
|
||||
##
|
||||
# load is an alias to ::new
|
||||
alias load new
|
||||
|
||||
##
|
||||
# Parses the configuration data read from _io_, see also #parse.
|
||||
#
|
||||
# Raises a ConfigError on invalid configuration data.
|
||||
def parse_config(io)
|
||||
begin
|
||||
parse_config_lines(io)
|
||||
rescue ConfigError => e
|
||||
e.message.replace("error in line #{io.lineno}: " + e.message)
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def get_key_string(data, section, key) # :nodoc:
|
||||
if v = data[section] && data[section][key]
|
||||
return v
|
||||
elsif section == 'ENV'
|
||||
if v = ENV[key]
|
||||
return v
|
||||
end
|
||||
end
|
||||
if v = data['default'] && data['default'][key]
|
||||
return v
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parse_config_lines(io)
|
||||
section = 'default'
|
||||
data = {section => {}}
|
||||
while definition = get_definition(io)
|
||||
definition = clear_comments(definition)
|
||||
next if definition.empty?
|
||||
if definition[0] == ?[
|
||||
if /\[([^\]]*)\]/ =~ definition
|
||||
section = $1.strip
|
||||
data[section] ||= {}
|
||||
else
|
||||
raise ConfigError, "missing close square bracket"
|
||||
end
|
||||
else
|
||||
if /\A([^:\s]*)(?:::([^:\s]*))?\s*=(.*)\z/ =~ definition
|
||||
if $2
|
||||
section = $1
|
||||
key = $2
|
||||
else
|
||||
key = $1
|
||||
end
|
||||
value = unescape_value(data, section, $3)
|
||||
(data[section] ||= {})[key] = value.strip
|
||||
else
|
||||
raise ConfigError, "missing equal sign"
|
||||
end
|
||||
end
|
||||
end
|
||||
data
|
||||
end
|
||||
|
||||
# escape with backslash
|
||||
QUOTE_REGEXP_SQ = /\A([^'\\]*(?:\\.[^'\\]*)*)'/
|
||||
# escape with backslash and doubled dq
|
||||
QUOTE_REGEXP_DQ = /\A([^"\\]*(?:""[^"\\]*|\\.[^"\\]*)*)"/
|
||||
# escaped char map
|
||||
ESCAPE_MAP = {
|
||||
"r" => "\r",
|
||||
"n" => "\n",
|
||||
"b" => "\b",
|
||||
"t" => "\t",
|
||||
}
|
||||
|
||||
def unescape_value(data, section, value)
|
||||
scanned = []
|
||||
while m = value.match(/['"\\$]/)
|
||||
scanned << m.pre_match
|
||||
c = m[0]
|
||||
value = m.post_match
|
||||
case c
|
||||
when "'"
|
||||
if m = value.match(QUOTE_REGEXP_SQ)
|
||||
scanned << m[1].gsub(/\\(.)/, '\\1')
|
||||
value = m.post_match
|
||||
else
|
||||
break
|
||||
end
|
||||
when '"'
|
||||
if m = value.match(QUOTE_REGEXP_DQ)
|
||||
scanned << m[1].gsub(/""/, '').gsub(/\\(.)/, '\\1')
|
||||
value = m.post_match
|
||||
else
|
||||
break
|
||||
end
|
||||
when "\\"
|
||||
c = value.slice!(0, 1)
|
||||
scanned << (ESCAPE_MAP[c] || c)
|
||||
when "$"
|
||||
ref, value = extract_reference(value)
|
||||
refsec = section
|
||||
if ref.index('::')
|
||||
refsec, ref = ref.split('::', 2)
|
||||
end
|
||||
if v = get_key_string(data, refsec, ref)
|
||||
scanned << v
|
||||
else
|
||||
raise ConfigError, "variable has no value"
|
||||
end
|
||||
else
|
||||
raise 'must not reaced'
|
||||
end
|
||||
end
|
||||
scanned << value
|
||||
scanned.join
|
||||
end
|
||||
|
||||
def extract_reference(value)
|
||||
rest = ''
|
||||
if m = value.match(/\(([^)]*)\)|\{([^}]*)\}/)
|
||||
value = m[1] || m[2]
|
||||
rest = m.post_match
|
||||
elsif [?(, ?{].include?(value[0])
|
||||
raise ConfigError, "no close brace"
|
||||
end
|
||||
if m = value.match(/[a-zA-Z0-9_]*(?:::[a-zA-Z0-9_]*)?/)
|
||||
return m[0], m.post_match + rest
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
|
||||
def clear_comments(line)
|
||||
# FCOMMENT
|
||||
if m = line.match(/\A([\t\n\f ]*);.*\z/)
|
||||
return m[1]
|
||||
end
|
||||
# COMMENT
|
||||
scanned = []
|
||||
while m = line.match(/[#'"\\]/)
|
||||
scanned << m.pre_match
|
||||
c = m[0]
|
||||
line = m.post_match
|
||||
case c
|
||||
when '#'
|
||||
line = nil
|
||||
break
|
||||
when "'", '"'
|
||||
regexp = (c == "'") ? QUOTE_REGEXP_SQ : QUOTE_REGEXP_DQ
|
||||
scanned << c
|
||||
if m = line.match(regexp)
|
||||
scanned << m[0]
|
||||
line = m.post_match
|
||||
else
|
||||
scanned << line
|
||||
line = nil
|
||||
break
|
||||
end
|
||||
when "\\"
|
||||
scanned << c
|
||||
scanned << line.slice!(0, 1)
|
||||
else
|
||||
raise 'must not reaced'
|
||||
end
|
||||
end
|
||||
scanned << line
|
||||
scanned.join
|
||||
end
|
||||
|
||||
def get_definition(io)
|
||||
if line = get_line(io)
|
||||
while /[^\\]\\\z/ =~ line
|
||||
if extra = get_line(io)
|
||||
line += extra
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return line.strip
|
||||
end
|
||||
end
|
||||
|
||||
def get_line(io)
|
||||
if line = io.gets
|
||||
line.gsub(/[\r\n]*/, '')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates an instance of OpenSSL's configuration class.
|
||||
#
|
||||
# This can be used in contexts like OpenSSL::X509::ExtensionFactory.config=
|
||||
#
|
||||
# If the optional _filename_ parameter is provided, then it is read in and
|
||||
# parsed via #parse_config.
|
||||
#
|
||||
# This can raise IO exceptions based on the access, or availability of the
|
||||
# file. A ConfigError exception may be raised depending on the validity of
|
||||
# the data being configured.
|
||||
#
|
||||
def initialize(filename = nil)
|
||||
@data = {}
|
||||
if filename
|
||||
File.open(filename.to_s) do |file|
|
||||
Config.parse_config(file).each do |section, hash|
|
||||
self[section] = hash
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Gets the value of _key_ from the given _section_
|
||||
#
|
||||
# Given the following configurating file being loaded:
|
||||
#
|
||||
# config = OpenSSL::Config.load('foo.cnf')
|
||||
# #=> #<OpenSSL::Config sections=["default"]>
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=bar
|
||||
#
|
||||
# You can get a specific value from the config if you know the _section_
|
||||
# and _key_ like so:
|
||||
#
|
||||
# config.get_value('default','foo')
|
||||
# #=> "bar"
|
||||
#
|
||||
def get_value(section, key)
|
||||
if section.nil?
|
||||
raise TypeError.new('nil not allowed')
|
||||
end
|
||||
section = 'default' if section.empty?
|
||||
get_key_string(section, key)
|
||||
end
|
||||
|
||||
##
|
||||
#
|
||||
# *Deprecated*
|
||||
#
|
||||
# Use #get_value instead
|
||||
def value(arg1, arg2 = nil) # :nodoc:
|
||||
warn('Config#value is deprecated; use Config#get_value')
|
||||
if arg2.nil?
|
||||
section, key = 'default', arg1
|
||||
else
|
||||
section, key = arg1, arg2
|
||||
end
|
||||
section ||= 'default'
|
||||
section = 'default' if section.empty?
|
||||
get_key_string(section, key)
|
||||
end
|
||||
|
||||
##
|
||||
# Set the target _key_ with a given _value_ under a specific _section_.
|
||||
#
|
||||
# Given the following configurating file being loaded:
|
||||
#
|
||||
# config = OpenSSL::Config.load('foo.cnf')
|
||||
# #=> #<OpenSSL::Config sections=["default"]>
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=bar
|
||||
#
|
||||
# You can set the value of _foo_ under the _default_ section to a new
|
||||
# value:
|
||||
#
|
||||
# config.add_value('default', 'foo', 'buzz')
|
||||
# #=> "buzz"
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=buzz
|
||||
#
|
||||
def add_value(section, key, value)
|
||||
check_modify
|
||||
(@data[section] ||= {})[key] = value
|
||||
end
|
||||
|
||||
##
|
||||
# Get a specific _section_ from the current configuration
|
||||
#
|
||||
# Given the following configurating file being loaded:
|
||||
#
|
||||
# config = OpenSSL::Config.load('foo.cnf')
|
||||
# #=> #<OpenSSL::Config sections=["default"]>
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=bar
|
||||
#
|
||||
# You can get a hash of the specific section like so:
|
||||
#
|
||||
# config['default']
|
||||
# #=> {"foo"=>"bar"}
|
||||
#
|
||||
def [](section)
|
||||
@data[section] || {}
|
||||
end
|
||||
|
||||
##
|
||||
# Deprecated
|
||||
#
|
||||
# Use #[] instead
|
||||
def section(name) # :nodoc:
|
||||
warn('Config#section is deprecated; use Config#[]')
|
||||
@data[name] || {}
|
||||
end
|
||||
|
||||
##
|
||||
# Sets a specific _section_ name with a Hash _pairs_.
|
||||
#
|
||||
# Given the following configuration being created:
|
||||
#
|
||||
# config = OpenSSL::Config.new
|
||||
# #=> #<OpenSSL::Config sections=[]>
|
||||
# config['default'] = {"foo"=>"bar","baz"=>"buz"}
|
||||
# #=> {"foo"=>"bar", "baz"=>"buz"}
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=bar
|
||||
# # baz=buz
|
||||
#
|
||||
# It's important to note that this will essentially merge any of the keys
|
||||
# in _pairs_ with the existing _section_. For example:
|
||||
#
|
||||
# config['default']
|
||||
# #=> {"foo"=>"bar", "baz"=>"buz"}
|
||||
# config['default'] = {"foo" => "changed"}
|
||||
# #=> {"foo"=>"changed"}
|
||||
# config['default']
|
||||
# #=> {"foo"=>"changed", "baz"=>"buz"}
|
||||
#
|
||||
def []=(section, pairs)
|
||||
check_modify
|
||||
@data[section] ||= {}
|
||||
pairs.each do |key, value|
|
||||
self.add_value(section, key, value)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Get the names of all sections in the current configuration
|
||||
def sections
|
||||
@data.keys
|
||||
end
|
||||
|
||||
##
|
||||
# Get the parsable form of the current configuration
|
||||
#
|
||||
# Given the following configuration being created:
|
||||
#
|
||||
# config = OpenSSL::Config.new
|
||||
# #=> #<OpenSSL::Config sections=[]>
|
||||
# config['default'] = {"foo"=>"bar","baz"=>"buz"}
|
||||
# #=> {"foo"=>"bar", "baz"=>"buz"}
|
||||
# puts config.to_s
|
||||
# #=> [ default ]
|
||||
# # foo=bar
|
||||
# # baz=buz
|
||||
#
|
||||
# You can parse get the serialized configuration using #to_s and then parse
|
||||
# it later:
|
||||
#
|
||||
# serialized_config = config.to_s
|
||||
# # much later...
|
||||
# new_config = OpenSSL::Config.parse(serialized_config)
|
||||
# #=> #<OpenSSL::Config sections=["default"]>
|
||||
# puts new_config
|
||||
# #=> [ default ]
|
||||
# foo=bar
|
||||
# baz=buz
|
||||
#
|
||||
def to_s
|
||||
ary = []
|
||||
@data.keys.sort.each do |section|
|
||||
ary << "[ #{section} ]\n"
|
||||
@data[section].keys.each do |key|
|
||||
ary << "#{key}=#{@data[section][key]}\n"
|
||||
end
|
||||
ary << "\n"
|
||||
end
|
||||
ary.join
|
||||
end
|
||||
|
||||
##
|
||||
# For a block.
|
||||
#
|
||||
# Receive the section and its pairs for the current configuration.
|
||||
#
|
||||
# config.each do |section, key, value|
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
def each
|
||||
@data.each do |section, hash|
|
||||
hash.each do |key, value|
|
||||
yield [section, key, value]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# String representation of this configuration object, including the class
|
||||
# name and its sections.
|
||||
def inspect
|
||||
"#<#{self.class.name} sections=#{sections.inspect}>"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def data # :nodoc:
|
||||
@data
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def initialize_copy(other)
|
||||
@data = other.data.dup
|
||||
end
|
||||
|
||||
def check_modify
|
||||
raise TypeError.new("Insecure: can't modify OpenSSL config") if frozen?
|
||||
end
|
||||
|
||||
def get_key_string(section, key)
|
||||
Config.get_key_string(@data, section, key)
|
||||
end
|
||||
end
|
||||
end
|
||||
75
gxg/standard/openssl/digest.rb
Normal file
75
gxg/standard/openssl/digest.rb
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# = Ruby-space predefined Digest subclasses
|
||||
#
|
||||
# = Info
|
||||
# 'OpenSSL for Ruby 2' project
|
||||
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
|
||||
# All rights reserved.
|
||||
#
|
||||
# = Licence
|
||||
# This program is licensed under the same licence as Ruby.
|
||||
# (See the file 'LICENCE'.)
|
||||
#++
|
||||
|
||||
module OpenSSL
|
||||
class Digest
|
||||
|
||||
alg = %w(MD2 MD4 MD5 MDC2 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512)
|
||||
if OPENSSL_VERSION_NUMBER < 0x10100000
|
||||
alg += %w(DSS DSS1 SHA)
|
||||
end
|
||||
|
||||
# Return the hash value computed with _name_ Digest. _name_ is either the
|
||||
# long name or short name of a supported digest algorithm.
|
||||
#
|
||||
# === Examples
|
||||
#
|
||||
# OpenSSL::Digest.digest("SHA256", "abc")
|
||||
#
|
||||
# which is equivalent to:
|
||||
#
|
||||
# OpenSSL::Digest::SHA256.digest("abc")
|
||||
|
||||
def self.digest(name, data)
|
||||
super(data, name)
|
||||
end
|
||||
|
||||
alg.each{|name|
|
||||
klass = Class.new(self) {
|
||||
define_method(:initialize, ->(data = nil) {super(name, data)})
|
||||
}
|
||||
singleton = (class << klass; self; end)
|
||||
singleton.class_eval{
|
||||
define_method(:digest){|data| new.digest(data) }
|
||||
define_method(:hexdigest){|data| new.hexdigest(data) }
|
||||
}
|
||||
const_set(name, klass)
|
||||
}
|
||||
|
||||
# Deprecated.
|
||||
#
|
||||
# This class is only provided for backwards compatibility.
|
||||
# Use OpenSSL::Digest instead.
|
||||
class Digest < Digest; end # :nodoc:
|
||||
deprecate_constant :Digest
|
||||
|
||||
end # Digest
|
||||
|
||||
# Returns a Digest subclass by _name_
|
||||
#
|
||||
# require 'openssl'
|
||||
#
|
||||
# OpenSSL::Digest("MD5")
|
||||
# # => OpenSSL::Digest::MD5
|
||||
#
|
||||
# Digest("Foo")
|
||||
# # => NameError: wrong constant name Foo
|
||||
|
||||
def Digest(name)
|
||||
OpenSSL::Digest.const_get(name)
|
||||
end
|
||||
|
||||
module_function :Digest
|
||||
|
||||
end # OpenSSL
|
||||
22
gxg/standard/openssl/pkcs5.rb
Normal file
22
gxg/standard/openssl/pkcs5.rb
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# Ruby/OpenSSL Project
|
||||
# Copyright (C) 2017 Ruby/OpenSSL Project Authors
|
||||
#++
|
||||
|
||||
module OpenSSL
|
||||
module PKCS5
|
||||
module_function
|
||||
|
||||
# OpenSSL::PKCS5.pbkdf2_hmac has been renamed to OpenSSL::KDF.pbkdf2_hmac.
|
||||
# This method is provided for backwards compatibility.
|
||||
def pbkdf2_hmac(pass, salt, iter, keylen, digest)
|
||||
OpenSSL::KDF.pbkdf2_hmac(pass, salt: salt, iterations: iter,
|
||||
length: keylen, hash: digest)
|
||||
end
|
||||
|
||||
def pbkdf2_hmac_sha1(pass, salt, iter, keylen)
|
||||
pbkdf2_hmac(pass, salt, iter, keylen, "sha1")
|
||||
end
|
||||
end
|
||||
end
|
||||
25
gxg/standard/openssl/pkey.rb
Normal file
25
gxg/standard/openssl/pkey.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# Ruby/OpenSSL Project
|
||||
# Copyright (C) 2017 Ruby/OpenSSL Project Authors
|
||||
#++
|
||||
|
||||
module OpenSSL::PKey
|
||||
if defined?(EC)
|
||||
class EC::Point
|
||||
# :call-seq:
|
||||
# point.to_bn([conversion_form]) -> OpenSSL::BN
|
||||
#
|
||||
# Returns the octet string representation of the EC point as an instance of
|
||||
# OpenSSL::BN.
|
||||
#
|
||||
# If _conversion_form_ is not given, the _point_conversion_form_ attribute
|
||||
# set to the group is used.
|
||||
#
|
||||
# See #to_octet_string for more information.
|
||||
def to_bn(conversion_form = group.point_conversion_form)
|
||||
OpenSSL::BN.new(to_octet_string(conversion_form), 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
504
gxg/standard/openssl/ssl.rb
Normal file
504
gxg/standard/openssl/ssl.rb
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
# frozen_string_literal: false
|
||||
=begin
|
||||
= Info
|
||||
'OpenSSL for Ruby 2' project
|
||||
Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
|
||||
All rights reserved.
|
||||
|
||||
= Licence
|
||||
This program is licensed under the same licence as Ruby.
|
||||
(See the file 'LICENCE'.)
|
||||
=end
|
||||
|
||||
require "openssl/buffering"
|
||||
require "io/nonblock"
|
||||
require "ipaddr"
|
||||
|
||||
module OpenSSL
|
||||
module SSL
|
||||
class SSLContext
|
||||
DEFAULT_PARAMS = { # :nodoc:
|
||||
:min_version => OpenSSL::SSL::TLS1_VERSION,
|
||||
:verify_mode => OpenSSL::SSL::VERIFY_PEER,
|
||||
:verify_hostname => true,
|
||||
:options => -> {
|
||||
opts = OpenSSL::SSL::OP_ALL
|
||||
opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
|
||||
opts |= OpenSSL::SSL::OP_NO_COMPRESSION
|
||||
opts
|
||||
}.call
|
||||
}
|
||||
|
||||
if defined?(OpenSSL::PKey::DH)
|
||||
DEFAULT_2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_
|
||||
-----BEGIN DH PARAMETERS-----
|
||||
MIIBCAKCAQEA7E6kBrYiyvmKAMzQ7i8WvwVk9Y/+f8S7sCTN712KkK3cqd1jhJDY
|
||||
JbrYeNV3kUIKhPxWHhObHKpD1R84UpL+s2b55+iMd6GmL7OYmNIT/FccKhTcveab
|
||||
VBmZT86BZKYyf45hUF9FOuUM9xPzuK3Vd8oJQvfYMCd7LPC0taAEljQLR4Edf8E6
|
||||
YoaOffgTf5qxiwkjnlVZQc3whgnEt9FpVMvQ9eknyeGB5KHfayAc3+hUAvI3/Cr3
|
||||
1bNveX5wInh5GDx1FGhKBZ+s1H+aedudCm7sCgRwv8lKWYGiHzObSma8A86KG+MD
|
||||
7Lo5JquQ3DlBodj3IDyPrxIv96lvRPFtAwIBAg==
|
||||
-----END DH PARAMETERS-----
|
||||
_end_of_pem_
|
||||
private_constant :DEFAULT_2048
|
||||
|
||||
DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc:
|
||||
warn "using default DH parameters." if $VERBOSE
|
||||
DEFAULT_2048
|
||||
}
|
||||
end
|
||||
|
||||
if !(OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") &&
|
||||
OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000)
|
||||
DEFAULT_PARAMS.merge!(
|
||||
ciphers: %w{
|
||||
ECDHE-ECDSA-AES128-GCM-SHA256
|
||||
ECDHE-RSA-AES128-GCM-SHA256
|
||||
ECDHE-ECDSA-AES256-GCM-SHA384
|
||||
ECDHE-RSA-AES256-GCM-SHA384
|
||||
DHE-RSA-AES128-GCM-SHA256
|
||||
DHE-DSS-AES128-GCM-SHA256
|
||||
DHE-RSA-AES256-GCM-SHA384
|
||||
DHE-DSS-AES256-GCM-SHA384
|
||||
ECDHE-ECDSA-AES128-SHA256
|
||||
ECDHE-RSA-AES128-SHA256
|
||||
ECDHE-ECDSA-AES128-SHA
|
||||
ECDHE-RSA-AES128-SHA
|
||||
ECDHE-ECDSA-AES256-SHA384
|
||||
ECDHE-RSA-AES256-SHA384
|
||||
ECDHE-ECDSA-AES256-SHA
|
||||
ECDHE-RSA-AES256-SHA
|
||||
DHE-RSA-AES128-SHA256
|
||||
DHE-RSA-AES256-SHA256
|
||||
DHE-RSA-AES128-SHA
|
||||
DHE-RSA-AES256-SHA
|
||||
DHE-DSS-AES128-SHA256
|
||||
DHE-DSS-AES256-SHA256
|
||||
DHE-DSS-AES128-SHA
|
||||
DHE-DSS-AES256-SHA
|
||||
AES128-GCM-SHA256
|
||||
AES256-GCM-SHA384
|
||||
AES128-SHA256
|
||||
AES256-SHA256
|
||||
AES128-SHA
|
||||
AES256-SHA
|
||||
}.join(":"),
|
||||
)
|
||||
end
|
||||
|
||||
DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc:
|
||||
DEFAULT_CERT_STORE.set_default_paths
|
||||
DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
|
||||
|
||||
# A callback invoked when DH parameters are required.
|
||||
#
|
||||
# The callback is invoked with the Session for the key exchange, an
|
||||
# flag indicating the use of an export cipher and the keylength
|
||||
# required.
|
||||
#
|
||||
# The callback must return an OpenSSL::PKey::DH instance of the correct
|
||||
# key length.
|
||||
|
||||
attr_accessor :tmp_dh_callback
|
||||
|
||||
# A callback invoked at connect time to distinguish between multiple
|
||||
# server names.
|
||||
#
|
||||
# The callback is invoked with an SSLSocket and a server name. The
|
||||
# callback must return an SSLContext for the server name or nil.
|
||||
attr_accessor :servername_cb
|
||||
|
||||
# call-seq:
|
||||
# SSLContext.new -> ctx
|
||||
# SSLContext.new(:TLSv1) -> ctx
|
||||
# SSLContext.new("SSLv23") -> ctx
|
||||
#
|
||||
# Creates a new SSL context.
|
||||
#
|
||||
# If an argument is given, #ssl_version= is called with the value. Note
|
||||
# that this form is deprecated. New applications should use #min_version=
|
||||
# and #max_version= as necessary.
|
||||
def initialize(version = nil)
|
||||
self.options |= OpenSSL::SSL::OP_ALL
|
||||
self.ssl_version = version if version
|
||||
end
|
||||
|
||||
##
|
||||
# call-seq:
|
||||
# ctx.set_params(params = {}) -> params
|
||||
#
|
||||
# Sets saner defaults optimized for the use with HTTP-like protocols.
|
||||
#
|
||||
# If a Hash _params_ is given, the parameters are overridden with it.
|
||||
# The keys in _params_ must be assignment methods on SSLContext.
|
||||
#
|
||||
# If the verify_mode is not VERIFY_NONE and ca_file, ca_path and
|
||||
# cert_store are not set then the system default certificate store is
|
||||
# used.
|
||||
def set_params(params={})
|
||||
params = DEFAULT_PARAMS.merge(params)
|
||||
self.options = params.delete(:options) # set before min_version/max_version
|
||||
params.each{|name, value| self.__send__("#{name}=", value) }
|
||||
if self.verify_mode != OpenSSL::SSL::VERIFY_NONE
|
||||
unless self.ca_file or self.ca_path or self.cert_store
|
||||
self.cert_store = DEFAULT_CERT_STORE
|
||||
end
|
||||
end
|
||||
return params
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ctx.min_version = OpenSSL::SSL::TLS1_2_VERSION
|
||||
# ctx.min_version = :TLS1_2
|
||||
# ctx.min_version = nil
|
||||
#
|
||||
# Sets the lower bound on the supported SSL/TLS protocol version. The
|
||||
# version may be specified by an integer constant named
|
||||
# OpenSSL::SSL::*_VERSION, a Symbol, or +nil+ which means "any version".
|
||||
#
|
||||
# Be careful that you don't overwrite OpenSSL::SSL::OP_NO_{SSL,TLS}v*
|
||||
# options by #options= once you have called #min_version= or
|
||||
# #max_version=.
|
||||
#
|
||||
# === Example
|
||||
# ctx = OpenSSL::SSL::SSLContext.new
|
||||
# ctx.min_version = OpenSSL::SSL::TLS1_1_VERSION
|
||||
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
|
||||
#
|
||||
# sock = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx)
|
||||
# sock.connect # Initiates a connection using either TLS 1.1 or TLS 1.2
|
||||
def min_version=(version)
|
||||
set_minmax_proto_version(version, @max_proto_version ||= nil)
|
||||
@min_proto_version = version
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
|
||||
# ctx.max_version = :TLS1_2
|
||||
# ctx.max_version = nil
|
||||
#
|
||||
# Sets the upper bound of the supported SSL/TLS protocol version. See
|
||||
# #min_version= for the possible values.
|
||||
def max_version=(version)
|
||||
set_minmax_proto_version(@min_proto_version ||= nil, version)
|
||||
@max_proto_version = version
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ctx.ssl_version = :TLSv1
|
||||
# ctx.ssl_version = "SSLv23"
|
||||
#
|
||||
# Sets the SSL/TLS protocol version for the context. This forces
|
||||
# connections to use only the specified protocol version. This is
|
||||
# deprecated and only provided for backwards compatibility. Use
|
||||
# #min_version= and #max_version= instead.
|
||||
#
|
||||
# === History
|
||||
# As the name hints, this used to call the SSL_CTX_set_ssl_version()
|
||||
# function which sets the SSL method used for connections created from
|
||||
# the context. As of Ruby/OpenSSL 2.1, this accessor method is
|
||||
# implemented to call #min_version= and #max_version= instead.
|
||||
def ssl_version=(meth)
|
||||
meth = meth.to_s if meth.is_a?(Symbol)
|
||||
if /(?<type>_client|_server)\z/ =~ meth
|
||||
meth = $`
|
||||
if $VERBOSE
|
||||
warn "#{caller(1, 1)[0]}: method type #{type.inspect} is ignored"
|
||||
end
|
||||
end
|
||||
version = METHODS_MAP[meth.intern] or
|
||||
raise ArgumentError, "unknown SSL method `%s'" % meth
|
||||
set_minmax_proto_version(version, version)
|
||||
@min_proto_version = @max_proto_version = version
|
||||
end
|
||||
|
||||
METHODS_MAP = {
|
||||
SSLv23: 0,
|
||||
SSLv2: OpenSSL::SSL::SSL2_VERSION,
|
||||
SSLv3: OpenSSL::SSL::SSL3_VERSION,
|
||||
TLSv1: OpenSSL::SSL::TLS1_VERSION,
|
||||
TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION,
|
||||
TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION,
|
||||
}.freeze
|
||||
private_constant :METHODS_MAP
|
||||
|
||||
# The list of available SSL/TLS methods. This constant is only provided
|
||||
# for backwards compatibility.
|
||||
METHODS = METHODS_MAP.flat_map { |name,|
|
||||
[name, :"#{name}_client", :"#{name}_server"]
|
||||
}.freeze
|
||||
deprecate_constant :METHODS
|
||||
end
|
||||
|
||||
module SocketForwarder
|
||||
def addr
|
||||
to_io.addr
|
||||
end
|
||||
|
||||
def peeraddr
|
||||
to_io.peeraddr
|
||||
end
|
||||
|
||||
def setsockopt(level, optname, optval)
|
||||
to_io.setsockopt(level, optname, optval)
|
||||
end
|
||||
|
||||
def getsockopt(level, optname)
|
||||
to_io.getsockopt(level, optname)
|
||||
end
|
||||
|
||||
def fcntl(*args)
|
||||
to_io.fcntl(*args)
|
||||
end
|
||||
|
||||
def closed?
|
||||
to_io.closed?
|
||||
end
|
||||
|
||||
def do_not_reverse_lookup=(flag)
|
||||
to_io.do_not_reverse_lookup = flag
|
||||
end
|
||||
end
|
||||
|
||||
def verify_certificate_identity(cert, hostname)
|
||||
should_verify_common_name = true
|
||||
cert.extensions.each{|ext|
|
||||
next if ext.oid != "subjectAltName"
|
||||
ostr = OpenSSL::ASN1.decode(ext.to_der).value.last
|
||||
sequence = OpenSSL::ASN1.decode(ostr.value)
|
||||
sequence.value.each{|san|
|
||||
case san.tag
|
||||
when 2 # dNSName in GeneralName (RFC5280)
|
||||
should_verify_common_name = false
|
||||
return true if verify_hostname(hostname, san.value)
|
||||
when 7 # iPAddress in GeneralName (RFC5280)
|
||||
should_verify_common_name = false
|
||||
if san.value.size == 4 || san.value.size == 16
|
||||
begin
|
||||
return true if san.value == IPAddr.new(hostname).hton
|
||||
rescue IPAddr::InvalidAddressError
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
}
|
||||
if should_verify_common_name
|
||||
cert.subject.to_a.each{|oid, value|
|
||||
if oid == "CN"
|
||||
return true if verify_hostname(hostname, value)
|
||||
end
|
||||
}
|
||||
end
|
||||
return false
|
||||
end
|
||||
module_function :verify_certificate_identity
|
||||
|
||||
def verify_hostname(hostname, san) # :nodoc:
|
||||
# RFC 5280, IA5String is limited to the set of ASCII characters
|
||||
return false unless san.ascii_only?
|
||||
return false unless hostname.ascii_only?
|
||||
|
||||
# See RFC 6125, section 6.4.1
|
||||
# Matching is case-insensitive.
|
||||
san_parts = san.downcase.split(".")
|
||||
|
||||
# TODO: this behavior should probably be more strict
|
||||
return san == hostname if san_parts.size < 2
|
||||
|
||||
# Matching is case-insensitive.
|
||||
host_parts = hostname.downcase.split(".")
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 2.
|
||||
# If the wildcard character is the only character of the left-most
|
||||
# label in the presented identifier, the client SHOULD NOT compare
|
||||
# against anything but the left-most label of the reference
|
||||
# identifier (e.g., *.example.com would match foo.example.com but
|
||||
# not bar.foo.example.com or example.com).
|
||||
return false unless san_parts.size == host_parts.size
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 1.
|
||||
# The client SHOULD NOT attempt to match a presented identifier in
|
||||
# which the wildcard character comprises a label other than the
|
||||
# left-most label (e.g., do not match bar.*.example.net).
|
||||
return false unless verify_wildcard(host_parts.shift, san_parts.shift)
|
||||
|
||||
san_parts.join(".") == host_parts.join(".")
|
||||
end
|
||||
module_function :verify_hostname
|
||||
|
||||
def verify_wildcard(domain_component, san_component) # :nodoc:
|
||||
parts = san_component.split("*", -1)
|
||||
|
||||
return false if parts.size > 2
|
||||
return san_component == domain_component if parts.size == 1
|
||||
|
||||
# RFC 6125, section 6.4.3, subitem 3.
|
||||
# The client SHOULD NOT attempt to match a presented identifier
|
||||
# where the wildcard character is embedded within an A-label or
|
||||
# U-label of an internationalized domain name.
|
||||
return false if domain_component.start_with?("xn--") && san_component != "*"
|
||||
|
||||
parts[0].length + parts[1].length < domain_component.length &&
|
||||
domain_component.start_with?(parts[0]) &&
|
||||
domain_component.end_with?(parts[1])
|
||||
end
|
||||
module_function :verify_wildcard
|
||||
|
||||
class SSLSocket
|
||||
include Buffering
|
||||
include SocketForwarder
|
||||
|
||||
attr_reader :hostname
|
||||
|
||||
# The underlying IO object.
|
||||
attr_reader :io
|
||||
alias :to_io :io
|
||||
|
||||
# The SSLContext object used in this connection.
|
||||
attr_reader :context
|
||||
|
||||
# Whether to close the underlying socket as well, when the SSL/TLS
|
||||
# connection is shut down. This defaults to +false+.
|
||||
attr_accessor :sync_close
|
||||
|
||||
# call-seq:
|
||||
# ssl.sysclose => nil
|
||||
#
|
||||
# Sends "close notify" to the peer and tries to shut down the SSL
|
||||
# connection gracefully.
|
||||
#
|
||||
# If sync_close is set to +true+, the underlying IO is also closed.
|
||||
def sysclose
|
||||
return if closed?
|
||||
stop
|
||||
io.close if sync_close
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ssl.post_connection_check(hostname) -> true
|
||||
#
|
||||
# Perform hostname verification following RFC 6125.
|
||||
#
|
||||
# This method MUST be called after calling #connect to ensure that the
|
||||
# hostname of a remote peer has been verified.
|
||||
def post_connection_check(hostname)
|
||||
if peer_cert.nil?
|
||||
msg = "Peer verification enabled, but no certificate received."
|
||||
if using_anon_cipher?
|
||||
msg += " Anonymous cipher suite #{cipher[0]} was negotiated. " \
|
||||
"Anonymous suites must be disabled to use peer verification."
|
||||
end
|
||||
raise SSLError, msg
|
||||
end
|
||||
|
||||
unless OpenSSL::SSL.verify_certificate_identity(peer_cert, hostname)
|
||||
raise SSLError, "hostname \"#{hostname}\" does not match the server certificate"
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# ssl.session -> aSession
|
||||
#
|
||||
# Returns the SSLSession object currently used, or nil if the session is
|
||||
# not established.
|
||||
def session
|
||||
SSL::Session.new(self)
|
||||
rescue SSL::Session::SessionError
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def using_anon_cipher?
|
||||
ctx = OpenSSL::SSL::SSLContext.new
|
||||
ctx.ciphers = "aNULL"
|
||||
ctx.ciphers.include?(cipher)
|
||||
end
|
||||
|
||||
def client_cert_cb
|
||||
@context.client_cert_cb
|
||||
end
|
||||
|
||||
def tmp_dh_callback
|
||||
@context.tmp_dh_callback || OpenSSL::SSL::SSLContext::DEFAULT_TMP_DH_CALLBACK
|
||||
end
|
||||
|
||||
def tmp_ecdh_callback
|
||||
@context.tmp_ecdh_callback
|
||||
end
|
||||
|
||||
def session_new_cb
|
||||
@context.session_new_cb
|
||||
end
|
||||
|
||||
def session_get_cb
|
||||
@context.session_get_cb
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# SSLServer represents a TCP/IP server socket with Secure Sockets Layer.
|
||||
class SSLServer
|
||||
include SocketForwarder
|
||||
# When true then #accept works exactly the same as TCPServer#accept
|
||||
attr_accessor :start_immediately
|
||||
|
||||
# Creates a new instance of SSLServer.
|
||||
# * _srv_ is an instance of TCPServer.
|
||||
# * _ctx_ is an instance of OpenSSL::SSL::SSLContext.
|
||||
def initialize(svr, ctx)
|
||||
@svr = svr
|
||||
@ctx = ctx
|
||||
unless ctx.session_id_context
|
||||
# see #6137 - session id may not exceed 32 bytes
|
||||
prng = ::Random.new($0.hash)
|
||||
session_id = prng.bytes(16).unpack('H*')[0]
|
||||
@ctx.session_id_context = session_id
|
||||
end
|
||||
@start_immediately = true
|
||||
end
|
||||
|
||||
# Returns the TCPServer passed to the SSLServer when initialized.
|
||||
def to_io
|
||||
@svr
|
||||
end
|
||||
|
||||
# See TCPServer#listen for details.
|
||||
def listen(backlog=5)
|
||||
@svr.listen(backlog)
|
||||
end
|
||||
|
||||
# See BasicSocket#shutdown for details.
|
||||
def shutdown(how=Socket::SHUT_RDWR)
|
||||
@svr.shutdown(how)
|
||||
end
|
||||
|
||||
# Works similar to TCPServer#accept.
|
||||
def accept
|
||||
# Socket#accept returns [socket, addrinfo].
|
||||
# TCPServer#accept returns a socket.
|
||||
# The following comma strips addrinfo.
|
||||
sock, = @svr.accept
|
||||
begin
|
||||
ssl = OpenSSL::SSL::SSLSocket.new(sock, @ctx)
|
||||
ssl.sync_close = true
|
||||
ssl.accept if @start_immediately
|
||||
ssl
|
||||
rescue Exception => ex
|
||||
if ssl
|
||||
ssl.close
|
||||
else
|
||||
sock.close
|
||||
end
|
||||
raise ex
|
||||
end
|
||||
end
|
||||
|
||||
# See IO#close for details.
|
||||
def close
|
||||
@svr.close
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
215
gxg/standard/openssl/x509.rb
Normal file
215
gxg/standard/openssl/x509.rb
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# = Ruby-space definitions that completes C-space funcs for X509 and subclasses
|
||||
#
|
||||
# = Info
|
||||
# 'OpenSSL for Ruby 2' project
|
||||
# Copyright (C) 2002 Michal Rokos <m.rokos@sh.cvut.cz>
|
||||
# All rights reserved.
|
||||
#
|
||||
# = Licence
|
||||
# This program is licensed under the same licence as Ruby.
|
||||
# (See the file 'LICENCE'.)
|
||||
#++
|
||||
|
||||
module OpenSSL
|
||||
module X509
|
||||
class ExtensionFactory
|
||||
def create_extension(*arg)
|
||||
if arg.size > 1
|
||||
create_ext(*arg)
|
||||
else
|
||||
send("create_ext_from_"+arg[0].class.name.downcase, arg[0])
|
||||
end
|
||||
end
|
||||
|
||||
def create_ext_from_array(ary)
|
||||
raise ExtensionError, "unexpected array form" if ary.size > 3
|
||||
create_ext(ary[0], ary[1], ary[2])
|
||||
end
|
||||
|
||||
def create_ext_from_string(str) # "oid = critical, value"
|
||||
oid, value = str.split(/=/, 2)
|
||||
oid.strip!
|
||||
value.strip!
|
||||
create_ext(oid, value)
|
||||
end
|
||||
|
||||
def create_ext_from_hash(hash)
|
||||
create_ext(hash["oid"], hash["value"], hash["critical"])
|
||||
end
|
||||
end
|
||||
|
||||
class Extension
|
||||
def ==(other)
|
||||
return false unless Extension === other
|
||||
to_der == other.to_der
|
||||
end
|
||||
|
||||
def to_s # "oid = critical, value"
|
||||
str = self.oid
|
||||
str << " = "
|
||||
str << "critical, " if self.critical?
|
||||
str << self.value.gsub(/\n/, ", ")
|
||||
end
|
||||
|
||||
def to_h # {"oid"=>sn|ln, "value"=>value, "critical"=>true|false}
|
||||
{"oid"=>self.oid,"value"=>self.value,"critical"=>self.critical?}
|
||||
end
|
||||
|
||||
def to_a
|
||||
[ self.oid, self.value, self.critical? ]
|
||||
end
|
||||
end
|
||||
|
||||
class Name
|
||||
module RFC2253DN
|
||||
Special = ',=+<>#;'
|
||||
HexChar = /[0-9a-fA-F]/
|
||||
HexPair = /#{HexChar}#{HexChar}/
|
||||
HexString = /#{HexPair}+/
|
||||
Pair = /\\(?:[#{Special}]|\\|"|#{HexPair})/
|
||||
StringChar = /[^\\"#{Special}]/
|
||||
QuoteChar = /[^\\"]/
|
||||
AttributeType = /[a-zA-Z][0-9a-zA-Z]*|[0-9]+(?:\.[0-9]+)*/
|
||||
AttributeValue = /
|
||||
(?!["#])((?:#{StringChar}|#{Pair})*)|
|
||||
\#(#{HexString})|
|
||||
"((?:#{QuoteChar}|#{Pair})*)"
|
||||
/x
|
||||
TypeAndValue = /\A(#{AttributeType})=#{AttributeValue}/
|
||||
|
||||
module_function
|
||||
|
||||
def expand_pair(str)
|
||||
return nil unless str
|
||||
return str.gsub(Pair){
|
||||
pair = $&
|
||||
case pair.size
|
||||
when 2 then pair[1,1]
|
||||
when 3 then Integer("0x#{pair[1,2]}").chr
|
||||
else raise OpenSSL::X509::NameError, "invalid pair: #{str}"
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
def expand_hexstring(str)
|
||||
return nil unless str
|
||||
der = str.gsub(HexPair){$&.to_i(16).chr }
|
||||
a1 = OpenSSL::ASN1.decode(der)
|
||||
return a1.value, a1.tag
|
||||
end
|
||||
|
||||
def expand_value(str1, str2, str3)
|
||||
value = expand_pair(str1)
|
||||
value, tag = expand_hexstring(str2) unless value
|
||||
value = expand_pair(str3) unless value
|
||||
return value, tag
|
||||
end
|
||||
|
||||
def scan(dn)
|
||||
str = dn
|
||||
ary = []
|
||||
while true
|
||||
if md = TypeAndValue.match(str)
|
||||
remain = md.post_match
|
||||
type = md[1]
|
||||
value, tag = expand_value(md[2], md[3], md[4]) rescue nil
|
||||
if value
|
||||
type_and_value = [type, value]
|
||||
type_and_value.push(tag) if tag
|
||||
ary.unshift(type_and_value)
|
||||
if remain.length > 2 && remain[0] == ?,
|
||||
str = remain[1..-1]
|
||||
next
|
||||
elsif remain.length > 2 && remain[0] == ?+
|
||||
raise OpenSSL::X509::NameError,
|
||||
"multi-valued RDN is not supported: #{dn}"
|
||||
elsif remain.empty?
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
msg_dn = dn[0, dn.length - str.length] + " =>" + str
|
||||
raise OpenSSL::X509::NameError, "malformed RDN: #{msg_dn}"
|
||||
end
|
||||
return ary
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE)
|
||||
ary = OpenSSL::X509::Name::RFC2253DN.scan(str)
|
||||
self.new(ary, template)
|
||||
end
|
||||
|
||||
def parse_openssl(str, template=OBJECT_TYPE_TEMPLATE)
|
||||
if str.start_with?("/")
|
||||
# /A=B/C=D format
|
||||
ary = str[1..-1].split("/").map { |i| i.split("=", 2) }
|
||||
else
|
||||
# Comma-separated
|
||||
ary = str.split(",").map { |i| i.strip.split("=", 2) }
|
||||
end
|
||||
self.new(ary, template)
|
||||
end
|
||||
|
||||
alias parse parse_openssl
|
||||
end
|
||||
|
||||
def pretty_print(q)
|
||||
q.object_group(self) {
|
||||
q.text ' '
|
||||
q.text to_s(OpenSSL::X509::Name::RFC2253)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class Attribute
|
||||
def ==(other)
|
||||
return false unless Attribute === other
|
||||
to_der == other.to_der
|
||||
end
|
||||
end
|
||||
|
||||
class StoreContext
|
||||
def cleanup
|
||||
warn "(#{caller.first}) OpenSSL::X509::StoreContext#cleanup is deprecated with no replacement" if $VERBOSE
|
||||
end
|
||||
end
|
||||
|
||||
class Certificate
|
||||
def pretty_print(q)
|
||||
q.object_group(self) {
|
||||
q.breakable
|
||||
q.text 'subject='; q.pp self.subject; q.text ','; q.breakable
|
||||
q.text 'issuer='; q.pp self.issuer; q.text ','; q.breakable
|
||||
q.text 'serial='; q.pp self.serial; q.text ','; q.breakable
|
||||
q.text 'not_before='; q.pp self.not_before; q.text ','; q.breakable
|
||||
q.text 'not_after='; q.pp self.not_after
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
class CRL
|
||||
def ==(other)
|
||||
return false unless CRL === other
|
||||
to_der == other.to_der
|
||||
end
|
||||
end
|
||||
|
||||
class Revoked
|
||||
def ==(other)
|
||||
return false unless Revoked === other
|
||||
to_der == other.to_der
|
||||
end
|
||||
end
|
||||
|
||||
class Request
|
||||
def ==(other)
|
||||
return false unless Request === other
|
||||
to_der == other.to_der
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
2
gxg/standard/optionparser.rb
Normal file
2
gxg/standard/optionparser.rb
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# frozen_string_literal: false
|
||||
require_relative 'optparse'
|
||||
2161
gxg/standard/optparse.rb
Normal file
2161
gxg/standard/optparse.rb
Normal file
File diff suppressed because it is too large
Load diff
51
gxg/standard/optparse/ac.rb
Normal file
51
gxg/standard/optparse/ac.rb
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# frozen_string_literal: false
|
||||
require 'optparse'
|
||||
|
||||
class OptionParser::AC < OptionParser
|
||||
private
|
||||
|
||||
def _check_ac_args(name, block)
|
||||
unless /\A\w[-\w]*\z/ =~ name
|
||||
raise ArgumentError, name
|
||||
end
|
||||
unless block
|
||||
raise ArgumentError, "no block given", ParseError.filter_backtrace(caller)
|
||||
end
|
||||
end
|
||||
|
||||
def _ac_arg_enable(prefix, name, help_string, block)
|
||||
_check_ac_args(name, block)
|
||||
|
||||
sdesc = []
|
||||
ldesc = ["--#{prefix}-#{name}"]
|
||||
desc = [help_string]
|
||||
q = name.downcase
|
||||
enable = Switch::NoArgument.new(nil, proc {true}, sdesc, ldesc, nil, desc, block)
|
||||
disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, block)
|
||||
top.append(enable, [], ["enable-" + q], disable, ['disable-' + q])
|
||||
enable
|
||||
end
|
||||
|
||||
public
|
||||
|
||||
def ac_arg_enable(name, help_string, &block)
|
||||
_ac_arg_enable("enable", name, help_string, block)
|
||||
end
|
||||
|
||||
def ac_arg_disable(name, help_string, &block)
|
||||
_ac_arg_enable("disable", name, help_string, block)
|
||||
end
|
||||
|
||||
def ac_arg_with(name, help_string, &block)
|
||||
_check_ac_args(name, block)
|
||||
|
||||
sdesc = []
|
||||
ldesc = ["--with-#{name}"]
|
||||
desc = [help_string]
|
||||
q = name.downcase
|
||||
with = Switch::PlacedArgument.new(*search(:atype, String), sdesc, ldesc, nil, desc, block)
|
||||
without = Switch::NoArgument.new(nil, proc {}, sdesc, ldesc, nil, desc, block)
|
||||
top.append(with, [], ["with-" + q], without, ['without-' + q])
|
||||
with
|
||||
end
|
||||
end
|
||||
18
gxg/standard/optparse/date.rb
Normal file
18
gxg/standard/optparse/date.rb
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: false
|
||||
require 'optparse'
|
||||
require 'date'
|
||||
|
||||
OptionParser.accept(DateTime) do |s,|
|
||||
begin
|
||||
DateTime.parse(s) if s
|
||||
rescue ArgumentError
|
||||
raise OptionParser::InvalidArgument, s
|
||||
end
|
||||
end
|
||||
OptionParser.accept(Date) do |s,|
|
||||
begin
|
||||
Date.parse(s) if s
|
||||
rescue ArgumentError
|
||||
raise OptionParser::InvalidArgument, s
|
||||
end
|
||||
end
|
||||
17
gxg/standard/optparse/kwargs.rb
Normal file
17
gxg/standard/optparse/kwargs.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
require 'optparse'
|
||||
|
||||
class OptionParser
|
||||
def define_by_keywords(options, meth, **opts)
|
||||
meth.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|
|
||||
options[name] = o
|
||||
end
|
||||
end
|
||||
end
|
||||
options
|
||||
end
|
||||
end
|
||||
7
gxg/standard/optparse/shellwords.rb
Normal file
7
gxg/standard/optparse/shellwords.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: false
|
||||
# -*- ruby -*-
|
||||
|
||||
require 'shellwords'
|
||||
require 'optparse'
|
||||
|
||||
OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)}
|
||||
11
gxg/standard/optparse/time.rb
Normal file
11
gxg/standard/optparse/time.rb
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# frozen_string_literal: false
|
||||
require 'optparse'
|
||||
require 'time'
|
||||
|
||||
OptionParser.accept(Time) do |s,|
|
||||
begin
|
||||
(Time.httpdate(s) rescue Time.parse(s)) if s
|
||||
rescue
|
||||
raise OptionParser::InvalidArgument, s
|
||||
end
|
||||
end
|
||||
7
gxg/standard/optparse/uri.rb
Normal file
7
gxg/standard/optparse/uri.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: false
|
||||
# -*- ruby -*-
|
||||
|
||||
require 'optparse'
|
||||
require 'uri'
|
||||
|
||||
OptionParser.accept(URI) {|s,| URI.parse(s) if s}
|
||||
71
gxg/standard/optparse/version.rb
Normal file
71
gxg/standard/optparse/version.rb
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# frozen_string_literal: false
|
||||
# OptionParser internal utility
|
||||
|
||||
class << OptionParser
|
||||
def show_version(*pkgs)
|
||||
progname = ARGV.options.program_name
|
||||
result = false
|
||||
show = proc do |klass, cname, version|
|
||||
str = "#{progname}"
|
||||
unless klass == ::Object and cname == :VERSION
|
||||
version = version.join(".") if Array === version
|
||||
str << ": #{klass}" unless klass == Object
|
||||
str << " version #{version}"
|
||||
end
|
||||
[:Release, :RELEASE].find do |rel|
|
||||
if klass.const_defined?(rel)
|
||||
str << " (#{klass.const_get(rel)})"
|
||||
end
|
||||
end
|
||||
puts str
|
||||
result = true
|
||||
end
|
||||
if pkgs.size == 1 and pkgs[0] == "all"
|
||||
self.search_const(::Object, /\AV(?:ERSION|ersion)\z/) do |klass, cname, version|
|
||||
unless cname[1] == ?e and klass.const_defined?(:Version)
|
||||
show.call(klass, cname.intern, version)
|
||||
end
|
||||
end
|
||||
else
|
||||
pkgs.each do |pkg|
|
||||
begin
|
||||
pkg = pkg.split(/::|\//).inject(::Object) {|m, c| m.const_get(c)}
|
||||
v = case
|
||||
when pkg.const_defined?(:Version)
|
||||
pkg.const_get(n = :Version)
|
||||
when pkg.const_defined?(:VERSION)
|
||||
pkg.const_get(n = :VERSION)
|
||||
else
|
||||
n = nil
|
||||
"unknown"
|
||||
end
|
||||
show.call(pkg, n, v)
|
||||
rescue NameError
|
||||
end
|
||||
end
|
||||
end
|
||||
result
|
||||
end
|
||||
|
||||
def each_const(path, base = ::Object)
|
||||
path.split(/::|\//).inject(base) do |klass, name|
|
||||
raise NameError, path unless Module === klass
|
||||
klass.constants.grep(/#{name}/i) do |c|
|
||||
klass.const_defined?(c) or next
|
||||
klass.const_get(c)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def search_const(klass, name)
|
||||
klasses = [klass]
|
||||
while klass = klasses.shift
|
||||
klass.constants.each do |cname|
|
||||
klass.const_defined?(cname) or next
|
||||
const = klass.const_get(cname)
|
||||
yield klass, cname, const if name === cname
|
||||
klasses << const if Module === const and const != ::Object
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
556
gxg/standard/prettyprint.rb
Normal file
556
gxg/standard/prettyprint.rb
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
# frozen_string_literal: true
|
||||
#
|
||||
# This class implements a pretty printing algorithm. It finds line breaks and
|
||||
# nice indentations for grouped structure.
|
||||
#
|
||||
# By default, the class assumes that primitive elements are strings and each
|
||||
# byte in the strings have single column in width. But it can be used for
|
||||
# other situations by giving suitable arguments for some methods:
|
||||
# * newline object and space generation block for PrettyPrint.new
|
||||
# * optional width argument for PrettyPrint#text
|
||||
# * PrettyPrint#breakable
|
||||
#
|
||||
# There are several candidate uses:
|
||||
# * text formatting using proportional fonts
|
||||
# * multibyte characters which has columns different to number of bytes
|
||||
# * non-string formatting
|
||||
#
|
||||
# == Bugs
|
||||
# * Box based formatting?
|
||||
# * Other (better) model/algorithm?
|
||||
#
|
||||
# Report any bugs at http://bugs.ruby-lang.org
|
||||
#
|
||||
# == References
|
||||
# Christian Lindig, Strictly Pretty, March 2000,
|
||||
# http://www.st.cs.uni-sb.de/~lindig/papers/#pretty
|
||||
#
|
||||
# Philip Wadler, A prettier printer, March 1998,
|
||||
# http://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
|
||||
#
|
||||
# == Author
|
||||
# Tanaka Akira <akr@fsij.org>
|
||||
#
|
||||
class PrettyPrint
|
||||
|
||||
# This is a convenience method which is same as follows:
|
||||
#
|
||||
# begin
|
||||
# q = PrettyPrint.new(output, maxwidth, newline, &genspace)
|
||||
# ...
|
||||
# q.flush
|
||||
# output
|
||||
# end
|
||||
#
|
||||
def PrettyPrint.format(output=''.dup, maxwidth=79, newline="\n", genspace=lambda {|n| ' ' * n})
|
||||
q = PrettyPrint.new(output, maxwidth, newline, &genspace)
|
||||
yield q
|
||||
q.flush
|
||||
output
|
||||
end
|
||||
|
||||
# This is similar to PrettyPrint::format but the result has no breaks.
|
||||
#
|
||||
# +maxwidth+, +newline+ and +genspace+ are ignored.
|
||||
#
|
||||
# The invocation of +breakable+ in the block doesn't break a line and is
|
||||
# treated as just an invocation of +text+.
|
||||
#
|
||||
def PrettyPrint.singleline_format(output=''.dup, maxwidth=nil, newline=nil, genspace=nil)
|
||||
q = SingleLine.new(output)
|
||||
yield q
|
||||
output
|
||||
end
|
||||
|
||||
# Creates a buffer for pretty printing.
|
||||
#
|
||||
# +output+ is an output target. If it is not specified, '' is assumed. It
|
||||
# should have a << method which accepts the first argument +obj+ of
|
||||
# PrettyPrint#text, the first argument +sep+ of PrettyPrint#breakable, the
|
||||
# first argument +newline+ of PrettyPrint.new, and the result of a given
|
||||
# block for PrettyPrint.new.
|
||||
#
|
||||
# +maxwidth+ specifies maximum line length. If it is not specified, 79 is
|
||||
# assumed. However actual outputs may overflow +maxwidth+ if long
|
||||
# non-breakable texts are provided.
|
||||
#
|
||||
# +newline+ is used for line breaks. "\n" is used if it is not specified.
|
||||
#
|
||||
# The block is used to generate spaces. {|width| ' ' * width} is used if it
|
||||
# is not given.
|
||||
#
|
||||
def initialize(output=''.dup, maxwidth=79, newline="\n", &genspace)
|
||||
@output = output
|
||||
@maxwidth = maxwidth
|
||||
@newline = newline
|
||||
@genspace = genspace || lambda {|n| ' ' * n}
|
||||
|
||||
@output_width = 0
|
||||
@buffer_width = 0
|
||||
@buffer = []
|
||||
|
||||
root_group = Group.new(0)
|
||||
@group_stack = [root_group]
|
||||
@group_queue = GroupQueue.new(root_group)
|
||||
@indent = 0
|
||||
end
|
||||
|
||||
# The output object.
|
||||
#
|
||||
# This defaults to '', and should accept the << method
|
||||
attr_reader :output
|
||||
|
||||
# The maximum width of a line, before it is separated in to a newline
|
||||
#
|
||||
# This defaults to 79, and should be a Fixnum
|
||||
attr_reader :maxwidth
|
||||
|
||||
# The value that is appended to +output+ to add a new line.
|
||||
#
|
||||
# This defaults to "\n", and should be String
|
||||
attr_reader :newline
|
||||
|
||||
# A lambda or Proc, that takes one argument, of a Fixnum, and returns
|
||||
# the corresponding number of spaces.
|
||||
#
|
||||
# By default this is:
|
||||
# lambda {|n| ' ' * n}
|
||||
attr_reader :genspace
|
||||
|
||||
# The number of spaces to be indented
|
||||
attr_reader :indent
|
||||
|
||||
# The PrettyPrint::GroupQueue of groups in stack to be pretty printed
|
||||
attr_reader :group_queue
|
||||
|
||||
# Returns the group most recently added to the stack.
|
||||
#
|
||||
# Contrived example:
|
||||
# out = ""
|
||||
# => ""
|
||||
# q = PrettyPrint.new(out)
|
||||
# => #<PrettyPrint:0x82f85c0 @output="", @maxwidth=79, @newline="\n", @genspace=#<Proc:0x82f8368@/home/vbatts/.rvm/rubies/ruby-head/lib/ruby/2.0.0/prettyprint.rb:82 (lambda)>, @output_width=0, @buffer_width=0, @buffer=[], @group_stack=[#<PrettyPrint::Group:0x82f8138 @depth=0, @breakables=[], @break=false>], @group_queue=#<PrettyPrint::GroupQueue:0x82fb7c0 @queue=[[#<PrettyPrint::Group:0x82f8138 @depth=0, @breakables=[], @break=false>]]>, @indent=0>
|
||||
# q.group {
|
||||
# q.text q.current_group.inspect
|
||||
# q.text q.newline
|
||||
# q.group(q.current_group.depth + 1) {
|
||||
# q.text q.current_group.inspect
|
||||
# q.text q.newline
|
||||
# q.group(q.current_group.depth + 1) {
|
||||
# q.text q.current_group.inspect
|
||||
# q.text q.newline
|
||||
# q.group(q.current_group.depth + 1) {
|
||||
# q.text q.current_group.inspect
|
||||
# q.text q.newline
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# }
|
||||
# => 284
|
||||
# puts out
|
||||
# #<PrettyPrint::Group:0x8354758 @depth=1, @breakables=[], @break=false>
|
||||
# #<PrettyPrint::Group:0x8354550 @depth=2, @breakables=[], @break=false>
|
||||
# #<PrettyPrint::Group:0x83541cc @depth=3, @breakables=[], @break=false>
|
||||
# #<PrettyPrint::Group:0x8347e54 @depth=4, @breakables=[], @break=false>
|
||||
def current_group
|
||||
@group_stack.last
|
||||
end
|
||||
|
||||
# Breaks the buffer into lines that are shorter than #maxwidth
|
||||
def break_outmost_groups
|
||||
while @maxwidth < @output_width + @buffer_width
|
||||
return unless group = @group_queue.deq
|
||||
until group.breakables.empty?
|
||||
data = @buffer.shift
|
||||
@output_width = data.output(@output, @output_width)
|
||||
@buffer_width -= data.width
|
||||
end
|
||||
while !@buffer.empty? && Text === @buffer.first
|
||||
text = @buffer.shift
|
||||
@output_width = text.output(@output, @output_width)
|
||||
@buffer_width -= text.width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# This adds +obj+ as a text of +width+ columns in width.
|
||||
#
|
||||
# If +width+ is not specified, obj.length is used.
|
||||
#
|
||||
def text(obj, width=obj.length)
|
||||
if @buffer.empty?
|
||||
@output << obj
|
||||
@output_width += width
|
||||
else
|
||||
text = @buffer.last
|
||||
unless Text === text
|
||||
text = Text.new
|
||||
@buffer << text
|
||||
end
|
||||
text.add(obj, width)
|
||||
@buffer_width += width
|
||||
break_outmost_groups
|
||||
end
|
||||
end
|
||||
|
||||
# This is similar to #breakable except
|
||||
# the decision to break or not is determined individually.
|
||||
#
|
||||
# Two #fill_breakable under a group may cause 4 results:
|
||||
# (break,break), (break,non-break), (non-break,break), (non-break,non-break).
|
||||
# This is different to #breakable because two #breakable under a group
|
||||
# may cause 2 results:
|
||||
# (break,break), (non-break,non-break).
|
||||
#
|
||||
# The text +sep+ is inserted if a line is not broken at this point.
|
||||
#
|
||||
# If +sep+ is not specified, " " is used.
|
||||
#
|
||||
# If +width+ is not specified, +sep.length+ is used. You will have to
|
||||
# specify this when +sep+ is a multibyte character, for example.
|
||||
#
|
||||
def fill_breakable(sep=' ', width=sep.length)
|
||||
group { breakable sep, width }
|
||||
end
|
||||
|
||||
# This says "you can break a line here if necessary", and a +width+\-column
|
||||
# text +sep+ is inserted if a line is not broken at the point.
|
||||
#
|
||||
# If +sep+ is not specified, " " is used.
|
||||
#
|
||||
# If +width+ is not specified, +sep.length+ is used. You will have to
|
||||
# specify this when +sep+ is a multibyte character, for example.
|
||||
#
|
||||
def breakable(sep=' ', width=sep.length)
|
||||
group = @group_stack.last
|
||||
if group.break?
|
||||
flush
|
||||
@output << @newline
|
||||
@output << @genspace.call(@indent)
|
||||
@output_width = @indent
|
||||
@buffer_width = 0
|
||||
else
|
||||
@buffer << Breakable.new(sep, width, self)
|
||||
@buffer_width += width
|
||||
break_outmost_groups
|
||||
end
|
||||
end
|
||||
|
||||
# Groups line break hints added in the block. The line break hints are all
|
||||
# to be used or not.
|
||||
#
|
||||
# If +indent+ is specified, the method call is regarded as nested by
|
||||
# nest(indent) { ... }.
|
||||
#
|
||||
# If +open_obj+ is specified, <tt>text open_obj, open_width</tt> is called
|
||||
# before grouping. If +close_obj+ is specified, <tt>text close_obj,
|
||||
# close_width</tt> is called after grouping.
|
||||
#
|
||||
def group(indent=0, open_obj='', close_obj='', open_width=open_obj.length, close_width=close_obj.length)
|
||||
text open_obj, open_width
|
||||
group_sub {
|
||||
nest(indent) {
|
||||
yield
|
||||
}
|
||||
}
|
||||
text close_obj, close_width
|
||||
end
|
||||
|
||||
# Takes a block and queues a new group that is indented 1 level further.
|
||||
def group_sub
|
||||
group = Group.new(@group_stack.last.depth + 1)
|
||||
@group_stack.push group
|
||||
@group_queue.enq group
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
@group_stack.pop
|
||||
if group.breakables.empty?
|
||||
@group_queue.delete group
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Increases left margin after newline with +indent+ for line breaks added in
|
||||
# the block.
|
||||
#
|
||||
def nest(indent)
|
||||
@indent += indent
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
@indent -= indent
|
||||
end
|
||||
end
|
||||
|
||||
# outputs buffered data.
|
||||
#
|
||||
def flush
|
||||
@buffer.each {|data|
|
||||
@output_width = data.output(@output, @output_width)
|
||||
}
|
||||
@buffer.clear
|
||||
@buffer_width = 0
|
||||
end
|
||||
|
||||
# The Text class is the means by which to collect strings from objects.
|
||||
#
|
||||
# This class is intended for internal use of the PrettyPrint buffers.
|
||||
class Text # :nodoc:
|
||||
|
||||
# Creates a new text object.
|
||||
#
|
||||
# This constructor takes no arguments.
|
||||
#
|
||||
# The workflow is to append a PrettyPrint::Text object to the buffer, and
|
||||
# being able to call the buffer.last() to reference it.
|
||||
#
|
||||
# As there are objects, use PrettyPrint::Text#add to include the objects
|
||||
# and the width to utilized by the String version of this object.
|
||||
def initialize
|
||||
@objs = []
|
||||
@width = 0
|
||||
end
|
||||
|
||||
# The total width of the objects included in this Text object.
|
||||
attr_reader :width
|
||||
|
||||
# Render the String text of the objects that have been added to this Text object.
|
||||
#
|
||||
# Output the text to +out+, and increment the width to +output_width+
|
||||
def output(out, output_width)
|
||||
@objs.each {|obj| out << obj}
|
||||
output_width + @width
|
||||
end
|
||||
|
||||
# Include +obj+ in the objects to be pretty printed, and increment
|
||||
# this Text object's total width by +width+
|
||||
def add(obj, width)
|
||||
@objs << obj
|
||||
@width += width
|
||||
end
|
||||
end
|
||||
|
||||
# The Breakable class is used for breaking up object information
|
||||
#
|
||||
# This class is intended for internal use of the PrettyPrint buffers.
|
||||
class Breakable # :nodoc:
|
||||
|
||||
# Create a new Breakable object.
|
||||
#
|
||||
# Arguments:
|
||||
# * +sep+ String of the separator
|
||||
# * +width+ Fixnum width of the +sep+
|
||||
# * +q+ parent PrettyPrint object, to base from
|
||||
def initialize(sep, width, q)
|
||||
@obj = sep
|
||||
@width = width
|
||||
@pp = q
|
||||
@indent = q.indent
|
||||
@group = q.current_group
|
||||
@group.breakables.push self
|
||||
end
|
||||
|
||||
# Holds the separator String
|
||||
#
|
||||
# The +sep+ argument from ::new
|
||||
attr_reader :obj
|
||||
|
||||
# The width of +obj+ / +sep+
|
||||
attr_reader :width
|
||||
|
||||
# The number of spaces to indent.
|
||||
#
|
||||
# This is inferred from +q+ within PrettyPrint, passed in ::new
|
||||
attr_reader :indent
|
||||
|
||||
# Render the String text of the objects that have been added to this
|
||||
# Breakable object.
|
||||
#
|
||||
# Output the text to +out+, and increment the width to +output_width+
|
||||
def output(out, output_width)
|
||||
@group.breakables.shift
|
||||
if @group.break?
|
||||
out << @pp.newline
|
||||
out << @pp.genspace.call(@indent)
|
||||
@indent
|
||||
else
|
||||
@pp.group_queue.delete @group if @group.breakables.empty?
|
||||
out << @obj
|
||||
output_width + @width
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The Group class is used for making indentation easier.
|
||||
#
|
||||
# While this class does neither the breaking into newlines nor indentation,
|
||||
# it is used in a stack (as well as a queue) within PrettyPrint, to group
|
||||
# objects.
|
||||
#
|
||||
# For information on using groups, see PrettyPrint#group
|
||||
#
|
||||
# This class is intended for internal use of the PrettyPrint buffers.
|
||||
class Group # :nodoc:
|
||||
# Create a Group object
|
||||
#
|
||||
# Arguments:
|
||||
# * +depth+ - this group's relation to previous groups
|
||||
def initialize(depth)
|
||||
@depth = depth
|
||||
@breakables = []
|
||||
@break = false
|
||||
end
|
||||
|
||||
# This group's relation to previous groups
|
||||
attr_reader :depth
|
||||
|
||||
# Array to hold the Breakable objects for this Group
|
||||
attr_reader :breakables
|
||||
|
||||
# Makes a break for this Group, and returns true
|
||||
def break
|
||||
@break = true
|
||||
end
|
||||
|
||||
# Boolean of whether this Group has made a break
|
||||
def break?
|
||||
@break
|
||||
end
|
||||
|
||||
# Boolean of whether this Group has been queried for being first
|
||||
#
|
||||
# This is used as a predicate, and ought to be called first.
|
||||
def first?
|
||||
if defined? @first
|
||||
false
|
||||
else
|
||||
@first = false
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# The GroupQueue class is used for managing the queue of Group to be pretty
|
||||
# printed.
|
||||
#
|
||||
# This queue groups the Group objects, based on their depth.
|
||||
#
|
||||
# This class is intended for internal use of the PrettyPrint buffers.
|
||||
class GroupQueue # :nodoc:
|
||||
# Create a GroupQueue object
|
||||
#
|
||||
# Arguments:
|
||||
# * +groups+ - one or more PrettyPrint::Group objects
|
||||
def initialize(*groups)
|
||||
@queue = []
|
||||
groups.each {|g| enq g}
|
||||
end
|
||||
|
||||
# Enqueue +group+
|
||||
#
|
||||
# This does not strictly append the group to the end of the queue,
|
||||
# but instead adds it in line, base on the +group.depth+
|
||||
def enq(group)
|
||||
depth = group.depth
|
||||
@queue << [] until depth < @queue.length
|
||||
@queue[depth] << group
|
||||
end
|
||||
|
||||
# Returns the outer group of the queue
|
||||
def deq
|
||||
@queue.each {|gs|
|
||||
(gs.length-1).downto(0) {|i|
|
||||
unless gs[i].breakables.empty?
|
||||
group = gs.slice!(i, 1).first
|
||||
group.break
|
||||
return group
|
||||
end
|
||||
}
|
||||
gs.each {|group| group.break}
|
||||
gs.clear
|
||||
}
|
||||
return nil
|
||||
end
|
||||
|
||||
# Remote +group+ from this queue
|
||||
def delete(group)
|
||||
@queue[group.depth].delete(group)
|
||||
end
|
||||
end
|
||||
|
||||
# PrettyPrint::SingleLine is used by PrettyPrint.singleline_format
|
||||
#
|
||||
# It is passed to be similar to a PrettyPrint object itself, by responding to:
|
||||
# * #text
|
||||
# * #breakable
|
||||
# * #nest
|
||||
# * #group
|
||||
# * #flush
|
||||
# * #first?
|
||||
#
|
||||
# but instead, the output has no line breaks
|
||||
#
|
||||
class SingleLine
|
||||
# Create a PrettyPrint::SingleLine object
|
||||
#
|
||||
# Arguments:
|
||||
# * +output+ - String (or similar) to store rendered text. Needs to respond to '<<'
|
||||
# * +maxwidth+ - Argument position expected to be here for compatibility.
|
||||
# This argument is a noop.
|
||||
# * +newline+ - Argument position expected to be here for compatibility.
|
||||
# This argument is a noop.
|
||||
def initialize(output, maxwidth=nil, newline=nil)
|
||||
@output = output
|
||||
@first = [true]
|
||||
end
|
||||
|
||||
# Add +obj+ to the text to be output.
|
||||
#
|
||||
# +width+ argument is here for compatibility. It is a noop argument.
|
||||
def text(obj, width=nil)
|
||||
@output << obj
|
||||
end
|
||||
|
||||
# Appends +sep+ to the text to be output. By default +sep+ is ' '
|
||||
#
|
||||
# +width+ argument is here for compatibility. It is a noop argument.
|
||||
def breakable(sep=' ', width=nil)
|
||||
@output << sep
|
||||
end
|
||||
|
||||
# Takes +indent+ arg, but does nothing with it.
|
||||
#
|
||||
# Yields to a block.
|
||||
def nest(indent) # :nodoc:
|
||||
yield
|
||||
end
|
||||
|
||||
# Opens a block for grouping objects to be pretty printed.
|
||||
#
|
||||
# Arguments:
|
||||
# * +indent+ - noop argument. Present for compatibility.
|
||||
# * +open_obj+ - text appended before the &blok. Default is ''
|
||||
# * +close_obj+ - text appended after the &blok. Default is ''
|
||||
# * +open_width+ - noop argument. Present for compatibility.
|
||||
# * +close_width+ - noop argument. Present for compatibility.
|
||||
def group(indent=nil, open_obj='', close_obj='', open_width=nil, close_width=nil)
|
||||
@first.push true
|
||||
@output << open_obj
|
||||
yield
|
||||
@output << close_obj
|
||||
@first.pop
|
||||
end
|
||||
|
||||
# Method present for compatibility, but is a noop
|
||||
def flush # :nodoc:
|
||||
end
|
||||
|
||||
# This is used as a predicate, and ought to be called first.
|
||||
def first?
|
||||
result = @first[-1]
|
||||
@first[-1] = false
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
467
gxg/standard/prime.rb
Normal file
467
gxg/standard/prime.rb
Normal file
|
|
@ -0,0 +1,467 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# = prime.rb
|
||||
#
|
||||
# Prime numbers and factorization library.
|
||||
#
|
||||
# Copyright::
|
||||
# Copyright (c) 1998-2008 Keiju ISHITSUKA(SHL Japan Inc.)
|
||||
# Copyright (c) 2008 Yuki Sonoda (Yugui) <yugui@yugui.jp>
|
||||
#
|
||||
# Documentation::
|
||||
# Yuki Sonoda
|
||||
#
|
||||
|
||||
require "singleton"
|
||||
require "forwardable"
|
||||
|
||||
class Integer
|
||||
# Re-composes a prime factorization and returns the product.
|
||||
#
|
||||
# See Prime#int_from_prime_division for more details.
|
||||
def Integer.from_prime_division(pd)
|
||||
Prime.int_from_prime_division(pd)
|
||||
end
|
||||
|
||||
# Returns the factorization of +self+.
|
||||
#
|
||||
# See Prime#prime_division for more details.
|
||||
def prime_division(generator = Prime::Generator23.new)
|
||||
Prime.prime_division(self, generator)
|
||||
end
|
||||
|
||||
# Returns true if +self+ is a prime number, else returns false.
|
||||
def prime?
|
||||
return self >= 2 if self <= 3
|
||||
return true if self == 5
|
||||
return false unless 30.gcd(self) == 1
|
||||
(7..Integer.sqrt(self)).step(30) do |p|
|
||||
return false if
|
||||
self%(p) == 0 || self%(p+4) == 0 || self%(p+6) == 0 || self%(p+10) == 0 ||
|
||||
self%(p+12) == 0 || self%(p+16) == 0 || self%(p+22) == 0 || self%(p+24) == 0
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
# Iterates the given block over all prime numbers.
|
||||
#
|
||||
# See +Prime+#each for more details.
|
||||
def Integer.each_prime(ubound, &block) # :yields: prime
|
||||
Prime.each(ubound, &block)
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# The set of all prime numbers.
|
||||
#
|
||||
# == Example
|
||||
#
|
||||
# Prime.each(100) do |prime|
|
||||
# p prime #=> 2, 3, 5, 7, 11, ...., 97
|
||||
# end
|
||||
#
|
||||
# Prime is Enumerable:
|
||||
#
|
||||
# Prime.first 5 # => [2, 3, 5, 7, 11]
|
||||
#
|
||||
# == Retrieving the instance
|
||||
#
|
||||
# For convenience, each instance method of +Prime+.instance can be accessed
|
||||
# as a class method of +Prime+.
|
||||
#
|
||||
# e.g.
|
||||
# Prime.instance.prime?(2) #=> true
|
||||
# Prime.prime?(2) #=> true
|
||||
#
|
||||
# == Generators
|
||||
#
|
||||
# A "generator" provides an implementation of enumerating pseudo-prime
|
||||
# numbers and it remembers the position of enumeration and upper bound.
|
||||
# Furthermore, it is an external iterator of prime enumeration which is
|
||||
# compatible with an Enumerator.
|
||||
#
|
||||
# +Prime+::+PseudoPrimeGenerator+ is the base class for generators.
|
||||
# There are few implementations of generator.
|
||||
#
|
||||
# [+Prime+::+EratosthenesGenerator+]
|
||||
# Uses eratosthenes' sieve.
|
||||
# [+Prime+::+TrialDivisionGenerator+]
|
||||
# Uses the trial division method.
|
||||
# [+Prime+::+Generator23+]
|
||||
# Generates all positive integers which are not divisible by either 2 or 3.
|
||||
# This sequence is very bad as a pseudo-prime sequence. But this
|
||||
# is faster and uses much less memory than the other generators. So,
|
||||
# it is suitable for factorizing an integer which is not large but
|
||||
# has many prime factors. e.g. for Prime#prime? .
|
||||
|
||||
class Prime
|
||||
include Enumerable
|
||||
include Singleton
|
||||
|
||||
class << self
|
||||
extend Forwardable
|
||||
include Enumerable
|
||||
|
||||
def method_added(method) # :nodoc:
|
||||
(class<< self;self;end).def_delegator :instance, method
|
||||
end
|
||||
end
|
||||
|
||||
# Iterates the given block over all prime numbers.
|
||||
#
|
||||
# == Parameters
|
||||
#
|
||||
# +ubound+::
|
||||
# Optional. An arbitrary positive number.
|
||||
# The upper bound of enumeration. The method enumerates
|
||||
# prime numbers infinitely if +ubound+ is nil.
|
||||
# +generator+::
|
||||
# Optional. An implementation of pseudo-prime generator.
|
||||
#
|
||||
# == Return value
|
||||
#
|
||||
# An evaluated value of the given block at the last time.
|
||||
# Or an enumerator which is compatible to an +Enumerator+
|
||||
# if no block given.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Calls +block+ once for each prime number, passing the prime as
|
||||
# a parameter.
|
||||
#
|
||||
# +ubound+::
|
||||
# Upper bound of prime numbers. The iterator stops after it
|
||||
# yields all prime numbers p <= +ubound+.
|
||||
#
|
||||
def each(ubound = nil, generator = EratosthenesGenerator.new, &block)
|
||||
generator.upper_bound = ubound
|
||||
generator.each(&block)
|
||||
end
|
||||
|
||||
|
||||
# Returns true if +value+ is a prime number, else returns false.
|
||||
#
|
||||
# == Parameters
|
||||
#
|
||||
# +value+:: an arbitrary integer to be checked.
|
||||
# +generator+:: optional. A pseudo-prime generator.
|
||||
def prime?(value, generator = Prime::Generator23.new)
|
||||
raise ArgumentError, "Expected a prime generator, got #{generator}" unless generator.respond_to? :each
|
||||
raise ArgumentError, "Expected an integer, got #{value}" unless value.respond_to?(:integer?) && value.integer?
|
||||
return false if value < 2
|
||||
generator.each do |num|
|
||||
q,r = value.divmod num
|
||||
return true if q < num
|
||||
return false if r == 0
|
||||
end
|
||||
end
|
||||
|
||||
# Re-composes a prime factorization and returns the product.
|
||||
#
|
||||
# == Parameters
|
||||
# +pd+:: Array of pairs of integers. The each internal
|
||||
# pair consists of a prime number -- a prime factor --
|
||||
# and a natural number -- an exponent.
|
||||
#
|
||||
# == Example
|
||||
# For <tt>[[p_1, e_1], [p_2, e_2], ...., [p_n, e_n]]</tt>, it returns:
|
||||
#
|
||||
# p_1**e_1 * p_2**e_2 * .... * p_n**e_n.
|
||||
#
|
||||
# Prime.int_from_prime_division([[2,2], [3,1]]) #=> 12
|
||||
def int_from_prime_division(pd)
|
||||
pd.inject(1){|value, (prime, index)|
|
||||
value * prime**index
|
||||
}
|
||||
end
|
||||
|
||||
# Returns the factorization of +value+.
|
||||
#
|
||||
# == Parameters
|
||||
# +value+:: An arbitrary integer.
|
||||
# +generator+:: Optional. A pseudo-prime generator.
|
||||
# +generator+.succ must return the next
|
||||
# pseudo-prime number in the ascending
|
||||
# order. It must generate all prime numbers,
|
||||
# but may also generate non prime numbers too.
|
||||
#
|
||||
# === Exceptions
|
||||
# +ZeroDivisionError+:: when +value+ is zero.
|
||||
#
|
||||
# == Example
|
||||
# For an arbitrary integer:
|
||||
#
|
||||
# n = p_1**e_1 * p_2**e_2 * .... * p_n**e_n,
|
||||
#
|
||||
# prime_division(n) returns:
|
||||
#
|
||||
# [[p_1, e_1], [p_2, e_2], ...., [p_n, e_n]].
|
||||
#
|
||||
# Prime.prime_division(12) #=> [[2,2], [3,1]]
|
||||
#
|
||||
def prime_division(value, generator = Prime::Generator23.new)
|
||||
raise ZeroDivisionError if value == 0
|
||||
if value < 0
|
||||
value = -value
|
||||
pv = [[-1, 1]]
|
||||
else
|
||||
pv = []
|
||||
end
|
||||
generator.each do |prime|
|
||||
count = 0
|
||||
while (value1, mod = value.divmod(prime)
|
||||
mod) == 0
|
||||
value = value1
|
||||
count += 1
|
||||
end
|
||||
if count != 0
|
||||
pv.push [prime, count]
|
||||
end
|
||||
break if value1 <= prime
|
||||
end
|
||||
if value > 1
|
||||
pv.push [value, 1]
|
||||
end
|
||||
pv
|
||||
end
|
||||
|
||||
# An abstract class for enumerating pseudo-prime numbers.
|
||||
#
|
||||
# Concrete subclasses should override succ, next, rewind.
|
||||
class PseudoPrimeGenerator
|
||||
include Enumerable
|
||||
|
||||
def initialize(ubound = nil)
|
||||
@ubound = ubound
|
||||
end
|
||||
|
||||
def upper_bound=(ubound)
|
||||
@ubound = ubound
|
||||
end
|
||||
def upper_bound
|
||||
@ubound
|
||||
end
|
||||
|
||||
# returns the next pseudo-prime number, and move the internal
|
||||
# position forward.
|
||||
#
|
||||
# +PseudoPrimeGenerator+#succ raises +NotImplementedError+.
|
||||
def succ
|
||||
raise NotImplementedError, "need to define `succ'"
|
||||
end
|
||||
|
||||
# alias of +succ+.
|
||||
def next
|
||||
raise NotImplementedError, "need to define `next'"
|
||||
end
|
||||
|
||||
# Rewinds the internal position for enumeration.
|
||||
#
|
||||
# See +Enumerator+#rewind.
|
||||
def rewind
|
||||
raise NotImplementedError, "need to define `rewind'"
|
||||
end
|
||||
|
||||
# Iterates the given block for each prime number.
|
||||
def each
|
||||
return self.dup unless block_given?
|
||||
if @ubound
|
||||
last_value = nil
|
||||
loop do
|
||||
prime = succ
|
||||
break last_value if prime > @ubound
|
||||
last_value = yield prime
|
||||
end
|
||||
else
|
||||
loop do
|
||||
yield succ
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# see +Enumerator+#with_index.
|
||||
def with_index(offset = 0)
|
||||
return enum_for(:with_index, offset) { Float::INFINITY } unless block_given?
|
||||
return each_with_index(&proc) if offset == 0
|
||||
|
||||
each do |prime|
|
||||
yield prime, offset
|
||||
offset += 1
|
||||
end
|
||||
end
|
||||
|
||||
# see +Enumerator+#with_object.
|
||||
def with_object(obj)
|
||||
return enum_for(:with_object, obj) { Float::INFINITY } unless block_given?
|
||||
each do |prime|
|
||||
yield prime, obj
|
||||
end
|
||||
end
|
||||
|
||||
def size
|
||||
Float::INFINITY
|
||||
end
|
||||
end
|
||||
|
||||
# An implementation of +PseudoPrimeGenerator+.
|
||||
#
|
||||
# Uses +EratosthenesSieve+.
|
||||
class EratosthenesGenerator < PseudoPrimeGenerator
|
||||
def initialize
|
||||
@last_prime_index = -1
|
||||
super
|
||||
end
|
||||
|
||||
def succ
|
||||
@last_prime_index += 1
|
||||
EratosthenesSieve.instance.get_nth_prime(@last_prime_index)
|
||||
end
|
||||
def rewind
|
||||
initialize
|
||||
end
|
||||
alias next succ
|
||||
end
|
||||
|
||||
# An implementation of +PseudoPrimeGenerator+ which uses
|
||||
# a prime table generated by trial division.
|
||||
class TrialDivisionGenerator < PseudoPrimeGenerator
|
||||
def initialize
|
||||
@index = -1
|
||||
super
|
||||
end
|
||||
|
||||
def succ
|
||||
TrialDivision.instance[@index += 1]
|
||||
end
|
||||
def rewind
|
||||
initialize
|
||||
end
|
||||
alias next succ
|
||||
end
|
||||
|
||||
# Generates all integers which are greater than 2 and
|
||||
# are not divisible by either 2 or 3.
|
||||
#
|
||||
# This is a pseudo-prime generator, suitable on
|
||||
# checking primality of an integer by brute force
|
||||
# method.
|
||||
class Generator23 < PseudoPrimeGenerator
|
||||
def initialize
|
||||
@prime = 1
|
||||
@step = nil
|
||||
super
|
||||
end
|
||||
|
||||
def succ
|
||||
if (@step)
|
||||
@prime += @step
|
||||
@step = 6 - @step
|
||||
else
|
||||
case @prime
|
||||
when 1; @prime = 2
|
||||
when 2; @prime = 3
|
||||
when 3; @prime = 5; @step = 2
|
||||
end
|
||||
end
|
||||
@prime
|
||||
end
|
||||
alias next succ
|
||||
def rewind
|
||||
initialize
|
||||
end
|
||||
end
|
||||
|
||||
# Internal use. An implementation of prime table by trial division method.
|
||||
class TrialDivision
|
||||
include Singleton
|
||||
|
||||
def initialize # :nodoc:
|
||||
# These are included as class variables to cache them for later uses. If memory
|
||||
# usage is a problem, they can be put in Prime#initialize as instance variables.
|
||||
|
||||
# There must be no primes between @primes[-1] and @next_to_check.
|
||||
@primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101]
|
||||
# @next_to_check % 6 must be 1.
|
||||
@next_to_check = 103 # @primes[-1] - @primes[-1] % 6 + 7
|
||||
@ulticheck_index = 3 # @primes.index(@primes.reverse.find {|n|
|
||||
# n < Math.sqrt(@@next_to_check) })
|
||||
@ulticheck_next_squared = 121 # @primes[@ulticheck_index + 1] ** 2
|
||||
end
|
||||
|
||||
# Returns the cached prime numbers.
|
||||
def cache
|
||||
@primes
|
||||
end
|
||||
alias primes cache
|
||||
alias primes_so_far cache
|
||||
|
||||
# Returns the +index+th prime number.
|
||||
#
|
||||
# +index+ is a 0-based index.
|
||||
def [](index)
|
||||
while index >= @primes.length
|
||||
# Only check for prime factors up to the square root of the potential primes,
|
||||
# but without the performance hit of an actual square root calculation.
|
||||
if @next_to_check + 4 > @ulticheck_next_squared
|
||||
@ulticheck_index += 1
|
||||
@ulticheck_next_squared = @primes.at(@ulticheck_index + 1) ** 2
|
||||
end
|
||||
# Only check numbers congruent to one and five, modulo six. All others
|
||||
|
||||
# are divisible by two or three. This also allows us to skip checking against
|
||||
# two and three.
|
||||
@primes.push @next_to_check if @primes[2..@ulticheck_index].find {|prime| @next_to_check % prime == 0 }.nil?
|
||||
@next_to_check += 4
|
||||
@primes.push @next_to_check if @primes[2..@ulticheck_index].find {|prime| @next_to_check % prime == 0 }.nil?
|
||||
@next_to_check += 2
|
||||
end
|
||||
@primes[index]
|
||||
end
|
||||
end
|
||||
|
||||
# Internal use. An implementation of Eratosthenes' sieve
|
||||
class EratosthenesSieve
|
||||
include Singleton
|
||||
|
||||
def initialize
|
||||
@primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101]
|
||||
# @max_checked must be an even number
|
||||
@max_checked = @primes.last + 1
|
||||
end
|
||||
|
||||
def get_nth_prime(n)
|
||||
compute_primes while @primes.size <= n
|
||||
@primes[n]
|
||||
end
|
||||
|
||||
private
|
||||
def compute_primes
|
||||
# max_segment_size must be an even number
|
||||
max_segment_size = 1e6.to_i
|
||||
max_cached_prime = @primes.last
|
||||
# do not double count primes if #compute_primes is interrupted
|
||||
# by Timeout.timeout
|
||||
@max_checked = max_cached_prime + 1 if max_cached_prime > @max_checked
|
||||
|
||||
segment_min = @max_checked
|
||||
segment_max = [segment_min + max_segment_size, max_cached_prime * 2].min
|
||||
root = Integer.sqrt(segment_max)
|
||||
|
||||
segment = ((segment_min + 1) .. segment_max).step(2).to_a
|
||||
|
||||
(1..Float::INFINITY).each do |sieving|
|
||||
prime = @primes[sieving]
|
||||
break if prime > root
|
||||
composite_index = (-(segment_min + 1 + prime) / 2) % prime
|
||||
while composite_index < segment.size do
|
||||
segment[composite_index] = nil
|
||||
composite_index += prime
|
||||
end
|
||||
end
|
||||
|
||||
@primes.concat(segment.compact!)
|
||||
|
||||
@max_checked = segment_max
|
||||
end
|
||||
end
|
||||
end
|
||||
149
gxg/standard/profiler.rb
Normal file
149
gxg/standard/profiler.rb
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# frozen_string_literal: true
|
||||
# Profile provides a way to Profile your Ruby application.
|
||||
#
|
||||
# Profiling your program is a way of determining which methods are called and
|
||||
# how long each method takes to complete. This way you can detect which
|
||||
# methods are possible bottlenecks.
|
||||
#
|
||||
# Profiling your program will slow down your execution time considerably,
|
||||
# so activate it only when you need it. Don't confuse benchmarking with
|
||||
# profiling.
|
||||
#
|
||||
# There are two ways to activate Profiling:
|
||||
#
|
||||
# == Command line
|
||||
#
|
||||
# Run your Ruby script with <code>-rprofile</code>:
|
||||
#
|
||||
# ruby -rprofile example.rb
|
||||
#
|
||||
# If you're profiling an executable in your <code>$PATH</code> you can use
|
||||
# <code>ruby -S</code>:
|
||||
#
|
||||
# ruby -rprofile -S some_executable
|
||||
#
|
||||
# == From code
|
||||
#
|
||||
# Just require 'profile':
|
||||
#
|
||||
# require 'profile'
|
||||
#
|
||||
# def slow_method
|
||||
# 5000.times do
|
||||
# 9999999999999999*999999999
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# def fast_method
|
||||
# 5000.times do
|
||||
# 9999999999999999+999999999
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# slow_method
|
||||
# fast_method
|
||||
#
|
||||
# The output in both cases is a report when the execution is over:
|
||||
#
|
||||
# ruby -rprofile example.rb
|
||||
#
|
||||
# % cumulative self self total
|
||||
# time seconds seconds calls ms/call ms/call name
|
||||
# 68.42 0.13 0.13 2 65.00 95.00 Integer#times
|
||||
# 15.79 0.16 0.03 5000 0.01 0.01 Fixnum#*
|
||||
# 15.79 0.19 0.03 5000 0.01 0.01 Fixnum#+
|
||||
# 0.00 0.19 0.00 2 0.00 0.00 IO#set_encoding
|
||||
# 0.00 0.19 0.00 1 0.00 100.00 Object#slow_method
|
||||
# 0.00 0.19 0.00 2 0.00 0.00 Module#method_added
|
||||
# 0.00 0.19 0.00 1 0.00 90.00 Object#fast_method
|
||||
# 0.00 0.19 0.00 1 0.00 190.00 #toplevel
|
||||
|
||||
module Profiler__
|
||||
class Wrapper < Struct.new(:defined_class, :method_id, :hash) # :nodoc:
|
||||
private :defined_class=, :method_id=, :hash=
|
||||
|
||||
def initialize(klass, mid)
|
||||
super(klass, mid, nil)
|
||||
self.hash = Struct.instance_method(:hash).bind(self).call
|
||||
end
|
||||
|
||||
def to_s
|
||||
"#{defined_class.inspect}#".sub(/\A\#<Class:(.*)>#\z/, '\1.') << method_id.to_s
|
||||
end
|
||||
alias inspect to_s
|
||||
end
|
||||
|
||||
# internal values
|
||||
@@start = nil # the start time that profiling began
|
||||
@@stacks = nil # the map of stacks keyed by thread
|
||||
@@maps = nil # the map of call data keyed by thread, class and id. Call data contains the call count, total time,
|
||||
PROFILE_CALL_PROC = TracePoint.new(*%i[call c_call b_call]) {|tp| # :nodoc:
|
||||
now = Process.times[0]
|
||||
stack = (@@stacks[Thread.current] ||= [])
|
||||
stack.push [now, 0.0]
|
||||
}
|
||||
PROFILE_RETURN_PROC = TracePoint.new(*%i[return c_return b_return]) {|tp| # :nodoc:
|
||||
now = Process.times[0]
|
||||
key = Wrapper.new(tp.defined_class, tp.method_id)
|
||||
stack = (@@stacks[Thread.current] ||= [])
|
||||
if tick = stack.pop
|
||||
threadmap = (@@maps[Thread.current] ||= {})
|
||||
data = (threadmap[key] ||= [0, 0.0, 0.0, key])
|
||||
data[0] += 1
|
||||
cost = now - tick[0]
|
||||
data[1] += cost
|
||||
data[2] += cost - tick[1]
|
||||
stack[-1][1] += cost if stack[-1]
|
||||
end
|
||||
}
|
||||
module_function
|
||||
# Starts the profiler.
|
||||
#
|
||||
# See Profiler__ for more information.
|
||||
def start_profile
|
||||
@@start = Process.times[0]
|
||||
@@stacks = {}
|
||||
@@maps = {}
|
||||
PROFILE_CALL_PROC.enable
|
||||
PROFILE_RETURN_PROC.enable
|
||||
end
|
||||
# Stops the profiler.
|
||||
#
|
||||
# See Profiler__ for more information.
|
||||
def stop_profile
|
||||
PROFILE_CALL_PROC.disable
|
||||
PROFILE_RETURN_PROC.disable
|
||||
end
|
||||
# Outputs the results from the profiler.
|
||||
#
|
||||
# See Profiler__ for more information.
|
||||
def print_profile(f)
|
||||
stop_profile
|
||||
total = Process.times[0] - @@start
|
||||
if total == 0 then total = 0.01 end
|
||||
totals = {}
|
||||
@@maps.values.each do |threadmap|
|
||||
threadmap.each do |key, data|
|
||||
total_data = (totals[key] ||= [0, 0.0, 0.0, key])
|
||||
total_data[0] += data[0]
|
||||
total_data[1] += data[1]
|
||||
total_data[2] += data[2]
|
||||
end
|
||||
end
|
||||
|
||||
# Maybe we should show a per thread output and a totals view?
|
||||
|
||||
data = totals.values
|
||||
data = data.sort_by{|x| -x[2]}
|
||||
sum = 0
|
||||
f.printf " %% cumulative self self total\n"
|
||||
f.printf " time seconds seconds calls ms/call ms/call name\n"
|
||||
for d in data
|
||||
sum += d[2]
|
||||
f.printf "%6.2f %8.2f %8.2f %8d ", d[2]/total*100, sum, d[2], d[0]
|
||||
f.printf "%8.2f %8.2f %s\n", d[2]*1000/d[0], d[1]*1000/d[0], d[3]
|
||||
end
|
||||
f.printf "%6.2f %8.2f %8.2f %8d ", 0.0, total, 0.0, 1 # ???
|
||||
f.printf "%8.2f %8.2f %s\n", 0.0, total*1000, "#toplevel" # ???
|
||||
end
|
||||
end
|
||||
623
gxg/standard/racc/parser.rb
Normal file
623
gxg/standard/racc/parser.rb
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# $originalId: parser.rb,v 1.8 2006/07/06 11:42:07 aamine Exp $
|
||||
#
|
||||
# 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.
|
||||
#++
|
||||
|
||||
module Racc
|
||||
class ParseError < StandardError; end
|
||||
end
|
||||
unless defined?(::ParseError)
|
||||
ParseError = Racc::ParseError
|
||||
end
|
||||
|
||||
# Racc is a 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>] [--embedded=<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>
|
||||
#
|
||||
# [+filename+]
|
||||
# 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).
|
||||
# [-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 LGPL, 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 = '1.4.6'
|
||||
Racc_Runtime_Revision = %w$originalRevision: 1.8 $[1]
|
||||
|
||||
Racc_Runtime_Core_Version_R = '1.4.6'
|
||||
Racc_Runtime_Core_Revision_R = %w$originalRevision: 1.8 $[1]
|
||||
begin
|
||||
require 'racc/cparse'
|
||||
# Racc_Runtime_Core_Version_C = (defined in extension)
|
||||
Racc_Runtime_Core_Revision_C = Racc_Runtime_Core_Id_C.split[2]
|
||||
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_Core_Revision = Racc_Runtime_Core_Revision_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_Core_Revision = Racc_Runtime_Core_Revision_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
|
||||
def do_parse
|
||||
__send__(Racc_Main_Parsing_Routine, _racc_setup(), false)
|
||||
end
|
||||
|
||||
# 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].
|
||||
def yyparse(recv, mid)
|
||||
__send__(Racc_YY_Parse_Method, recv, mid, _racc_setup(), true)
|
||||
end
|
||||
|
||||
def _racc_yyparse_rb(recv, mid, arg, c_debug)
|
||||
action_table, action_check, action_default, action_pointer,
|
||||
_, _, _, _,
|
||||
_, _, token_table, _,
|
||||
_, _, * = arg
|
||||
|
||||
_racc_init_sysvars
|
||||
act = nil
|
||||
i = nil
|
||||
|
||||
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 not(i = action_pointer[@racc_state[-1]]) or
|
||||
not @racc_read_next or
|
||||
@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
|
||||
|
||||
if act > 0 and act < shift_n
|
||||
#
|
||||
# shift
|
||||
#
|
||||
if @racc_error_status > 0
|
||||
@racc_error_status -= 1 unless @racc_t == 1 # error token
|
||||
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
|
||||
on_error @racc_t, @racc_val, @racc_vstack
|
||||
end
|
||||
when 3
|
||||
if @racc_t == 0 # is $
|
||||
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("\nparse 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
|
||||
76
gxg/standard/resolv-replace.rb
Normal file
76
gxg/standard/resolv-replace.rb
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'socket'
|
||||
require 'resolv'
|
||||
|
||||
class << IPSocket
|
||||
# :stopdoc:
|
||||
alias original_resolv_getaddress getaddress
|
||||
# :startdoc:
|
||||
def getaddress(host)
|
||||
begin
|
||||
return Resolv.getaddress(host).to_s
|
||||
rescue Resolv::ResolvError
|
||||
raise SocketError, "Hostname not known: #{host}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class TCPSocket < IPSocket
|
||||
# :stopdoc:
|
||||
alias original_resolv_initialize initialize
|
||||
# :startdoc:
|
||||
def initialize(host, serv, *rest)
|
||||
rest[0] = IPSocket.getaddress(rest[0]) if rest[0]
|
||||
original_resolv_initialize(IPSocket.getaddress(host), serv, *rest)
|
||||
end
|
||||
end
|
||||
|
||||
class UDPSocket < IPSocket
|
||||
# :stopdoc:
|
||||
alias original_resolv_bind bind
|
||||
# :startdoc:
|
||||
def bind(host, port)
|
||||
host = IPSocket.getaddress(host) if host != ""
|
||||
original_resolv_bind(host, port)
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
alias original_resolv_connect connect
|
||||
# :startdoc:
|
||||
def connect(host, port)
|
||||
original_resolv_connect(IPSocket.getaddress(host), port)
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
alias original_resolv_send send
|
||||
# :startdoc:
|
||||
def send(mesg, flags, *rest)
|
||||
if rest.length == 2
|
||||
host, port = rest
|
||||
begin
|
||||
addrs = Resolv.getaddresses(host)
|
||||
rescue Resolv::ResolvError
|
||||
raise SocketError, "Hostname not known: #{host}"
|
||||
end
|
||||
addrs[0...-1].each {|addr|
|
||||
begin
|
||||
return original_resolv_send(mesg, flags, addr, port)
|
||||
rescue SystemCallError
|
||||
end
|
||||
}
|
||||
original_resolv_send(mesg, flags, addrs[-1], port)
|
||||
else
|
||||
original_resolv_send(mesg, flags, *rest)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class SOCKSSocket < TCPSocket
|
||||
# :stopdoc:
|
||||
alias original_resolv_initialize initialize
|
||||
# :startdoc:
|
||||
def initialize(host, serv)
|
||||
original_resolv_initialize(IPSocket.getaddress(host), port)
|
||||
end
|
||||
end if defined? SOCKSSocket
|
||||
2856
gxg/standard/resolv.rb
Normal file
2856
gxg/standard/resolv.rb
Normal file
File diff suppressed because it is too large
Load diff
327
gxg/standard/rinda/rinda.rb
Normal file
327
gxg/standard/rinda/rinda.rb
Normal file
|
|
@ -0,0 +1,327 @@
|
|||
# frozen_string_literal: false
|
||||
require 'drb/drb'
|
||||
|
||||
##
|
||||
# A module to implement the Linda distributed computing paradigm in Ruby.
|
||||
#
|
||||
# Rinda is part of DRb (dRuby).
|
||||
#
|
||||
# == Example(s)
|
||||
#
|
||||
# See the sample/drb/ directory in the Ruby distribution, from 1.8.2 onwards.
|
||||
#
|
||||
#--
|
||||
# TODO
|
||||
# == Introduction to Linda/rinda?
|
||||
#
|
||||
# == Why is this library separate from DRb?
|
||||
|
||||
module Rinda
|
||||
|
||||
##
|
||||
# Rinda error base class
|
||||
|
||||
class RindaError < RuntimeError; end
|
||||
|
||||
##
|
||||
# Raised when a hash-based tuple has an invalid key.
|
||||
|
||||
class InvalidHashTupleKey < RindaError; end
|
||||
|
||||
##
|
||||
# Raised when trying to use a canceled tuple.
|
||||
|
||||
class RequestCanceledError < ThreadError; end
|
||||
|
||||
##
|
||||
# Raised when trying to use an expired tuple.
|
||||
|
||||
class RequestExpiredError < ThreadError; end
|
||||
|
||||
##
|
||||
# A tuple is the elementary object in Rinda programming.
|
||||
# Tuples may be matched against templates if the tuple and
|
||||
# the template are the same size.
|
||||
|
||||
class Tuple
|
||||
|
||||
##
|
||||
# Creates a new Tuple from +ary_or_hash+ which must be an Array or Hash.
|
||||
|
||||
def initialize(ary_or_hash)
|
||||
if hash?(ary_or_hash)
|
||||
init_with_hash(ary_or_hash)
|
||||
else
|
||||
init_with_ary(ary_or_hash)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The number of elements in the tuple.
|
||||
|
||||
def size
|
||||
@tuple.size
|
||||
end
|
||||
|
||||
##
|
||||
# Accessor method for elements of the tuple.
|
||||
|
||||
def [](k)
|
||||
@tuple[k]
|
||||
end
|
||||
|
||||
##
|
||||
# Fetches item +k+ from the tuple.
|
||||
|
||||
def fetch(k)
|
||||
@tuple.fetch(k)
|
||||
end
|
||||
|
||||
##
|
||||
# Iterate through the tuple, yielding the index or key, and the
|
||||
# value, thus ensuring arrays are iterated similarly to hashes.
|
||||
|
||||
def each # FIXME
|
||||
if Hash === @tuple
|
||||
@tuple.each { |k, v| yield(k, v) }
|
||||
else
|
||||
@tuple.each_with_index { |v, k| yield(k, v) }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Return the tuple itself
|
||||
def value
|
||||
@tuple
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hash?(ary_or_hash)
|
||||
ary_or_hash.respond_to?(:keys)
|
||||
end
|
||||
|
||||
##
|
||||
# Munges +ary+ into a valid Tuple.
|
||||
|
||||
def init_with_ary(ary)
|
||||
@tuple = Array.new(ary.size)
|
||||
@tuple.size.times do |i|
|
||||
@tuple[i] = ary[i]
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Ensures +hash+ is a valid Tuple.
|
||||
|
||||
def init_with_hash(hash)
|
||||
@tuple = Hash.new
|
||||
hash.each do |k, v|
|
||||
raise InvalidHashTupleKey unless String === k
|
||||
@tuple[k] = v
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# Templates are used to match tuples in Rinda.
|
||||
|
||||
class Template < Tuple
|
||||
|
||||
##
|
||||
# Matches this template against +tuple+. The +tuple+ must be the same
|
||||
# size as the template. An element with a +nil+ value in a template acts
|
||||
# as a wildcard, matching any value in the corresponding position in the
|
||||
# tuple. Elements of the template match the +tuple+ if the are #== or
|
||||
# #===.
|
||||
#
|
||||
# Template.new([:foo, 5]).match Tuple.new([:foo, 5]) # => true
|
||||
# Template.new([:foo, nil]).match Tuple.new([:foo, 5]) # => true
|
||||
# Template.new([String]).match Tuple.new(['hello']) # => true
|
||||
#
|
||||
# Template.new([:foo]).match Tuple.new([:foo, 5]) # => false
|
||||
# Template.new([:foo, 6]).match Tuple.new([:foo, 5]) # => false
|
||||
# Template.new([:foo, nil]).match Tuple.new([:foo]) # => false
|
||||
# Template.new([:foo, 6]).match Tuple.new([:foo]) # => false
|
||||
|
||||
def match(tuple)
|
||||
return false unless tuple.respond_to?(:size)
|
||||
return false unless tuple.respond_to?(:fetch)
|
||||
return false unless self.size == tuple.size
|
||||
each do |k, v|
|
||||
begin
|
||||
it = tuple.fetch(k)
|
||||
rescue
|
||||
return false
|
||||
end
|
||||
next if v.nil?
|
||||
next if v == it
|
||||
next if v === it
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
##
|
||||
# Alias for #match.
|
||||
|
||||
def ===(tuple)
|
||||
match(tuple)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# <i>Documentation?</i>
|
||||
|
||||
class DRbObjectTemplate
|
||||
|
||||
##
|
||||
# Creates a new DRbObjectTemplate that will match against +uri+ and +ref+.
|
||||
|
||||
def initialize(uri=nil, ref=nil)
|
||||
@drb_uri = uri
|
||||
@drb_ref = ref
|
||||
end
|
||||
|
||||
##
|
||||
# This DRbObjectTemplate matches +ro+ if the remote object's drburi and
|
||||
# drbref are the same. +nil+ is used as a wildcard.
|
||||
|
||||
def ===(ro)
|
||||
return true if super(ro)
|
||||
unless @drb_uri.nil?
|
||||
return false unless (@drb_uri === ro.__drburi rescue false)
|
||||
end
|
||||
unless @drb_ref.nil?
|
||||
return false unless (@drb_ref === ro.__drbref rescue false)
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# TupleSpaceProxy allows a remote Tuplespace to appear as local.
|
||||
|
||||
class TupleSpaceProxy
|
||||
##
|
||||
# A Port ensures that a moved tuple arrives properly at its destination
|
||||
# and does not get lost.
|
||||
#
|
||||
# See https://bugs.ruby-lang.org/issues/8125
|
||||
|
||||
class Port # :nodoc:
|
||||
attr_reader :value
|
||||
|
||||
def self.deliver
|
||||
port = new
|
||||
|
||||
begin
|
||||
yield(port)
|
||||
ensure
|
||||
port.close
|
||||
end
|
||||
|
||||
port.value
|
||||
end
|
||||
|
||||
def initialize
|
||||
@open = true
|
||||
@value = nil
|
||||
end
|
||||
|
||||
##
|
||||
# Don't let the DRb thread push to it when remote sends tuple
|
||||
|
||||
def close
|
||||
@open = false
|
||||
end
|
||||
|
||||
##
|
||||
# Stores +value+ and ensure it does not get marshaled multiple times.
|
||||
|
||||
def push value
|
||||
raise 'port closed' unless @open
|
||||
|
||||
@value = value
|
||||
|
||||
nil # avoid Marshal
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a new TupleSpaceProxy to wrap +ts+.
|
||||
|
||||
def initialize(ts)
|
||||
@ts = ts
|
||||
end
|
||||
|
||||
##
|
||||
# Adds +tuple+ to the proxied TupleSpace. See TupleSpace#write.
|
||||
|
||||
def write(tuple, sec=nil)
|
||||
@ts.write(tuple, sec)
|
||||
end
|
||||
|
||||
##
|
||||
# Takes +tuple+ from the proxied TupleSpace. See TupleSpace#take.
|
||||
|
||||
def take(tuple, sec=nil, &block)
|
||||
Port.deliver do |port|
|
||||
@ts.move(DRbObject.new(port), tuple, sec, &block)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Reads +tuple+ from the proxied TupleSpace. See TupleSpace#read.
|
||||
|
||||
def read(tuple, sec=nil, &block)
|
||||
@ts.read(tuple, sec, &block)
|
||||
end
|
||||
|
||||
##
|
||||
# Reads all tuples matching +tuple+ from the proxied TupleSpace. See
|
||||
# TupleSpace#read_all.
|
||||
|
||||
def read_all(tuple)
|
||||
@ts.read_all(tuple)
|
||||
end
|
||||
|
||||
##
|
||||
# Registers for notifications of event +ev+ on the proxied TupleSpace.
|
||||
# See TupleSpace#notify
|
||||
|
||||
def notify(ev, tuple, sec=nil)
|
||||
@ts.notify(ev, tuple, sec)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# An SimpleRenewer allows a TupleSpace to check if a TupleEntry is still
|
||||
# alive.
|
||||
|
||||
class SimpleRenewer
|
||||
|
||||
include DRbUndumped
|
||||
|
||||
##
|
||||
# Creates a new SimpleRenewer that keeps an object alive for another +sec+
|
||||
# seconds.
|
||||
|
||||
def initialize(sec=180)
|
||||
@sec = sec
|
||||
end
|
||||
|
||||
##
|
||||
# Called by the TupleSpace to check if the object is still alive.
|
||||
|
||||
def renew
|
||||
@sec
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
484
gxg/standard/rinda/ring.rb
Normal file
484
gxg/standard/rinda/ring.rb
Normal file
|
|
@ -0,0 +1,484 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# Note: Rinda::Ring API is unstable.
|
||||
#
|
||||
require 'drb/drb'
|
||||
require 'rinda/rinda'
|
||||
require 'ipaddr'
|
||||
|
||||
module Rinda
|
||||
|
||||
##
|
||||
# The default port Ring discovery will use.
|
||||
|
||||
Ring_PORT = 7647
|
||||
|
||||
##
|
||||
# A RingServer allows a Rinda::TupleSpace to be located via UDP broadcasts.
|
||||
# Default service location uses the following steps:
|
||||
#
|
||||
# 1. A RingServer begins listening on the network broadcast UDP address.
|
||||
# 2. A RingFinger sends a UDP packet containing the DRb URI where it will
|
||||
# listen for a reply.
|
||||
# 3. The RingServer receives the UDP packet and connects back to the
|
||||
# provided DRb URI with the DRb service.
|
||||
#
|
||||
# A RingServer requires a TupleSpace:
|
||||
#
|
||||
# ts = Rinda::TupleSpace.new
|
||||
# rs = Rinda::RingServer.new
|
||||
#
|
||||
# RingServer can also listen on multicast addresses for announcements. This
|
||||
# allows multiple RingServers to run on the same host. To use network
|
||||
# broadcast and multicast:
|
||||
#
|
||||
# ts = Rinda::TupleSpace.new
|
||||
# rs = Rinda::RingServer.new ts, %w[Socket::INADDR_ANY, 239.0.0.1 ff02::1]
|
||||
|
||||
class RingServer
|
||||
|
||||
include DRbUndumped
|
||||
|
||||
##
|
||||
# Special renewer for the RingServer to allow shutdown
|
||||
|
||||
class Renewer # :nodoc:
|
||||
include DRbUndumped
|
||||
|
||||
##
|
||||
# Set to false to shutdown future requests using this Renewer
|
||||
|
||||
attr_writer :renew
|
||||
|
||||
def initialize # :nodoc:
|
||||
@renew = true
|
||||
end
|
||||
|
||||
def renew # :nodoc:
|
||||
@renew ? 1 : true
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Advertises +ts+ on the given +addresses+ at +port+.
|
||||
#
|
||||
# If +addresses+ is omitted only the UDP broadcast address is used.
|
||||
#
|
||||
# +addresses+ can contain multiple addresses. If a multicast address is
|
||||
# given in +addresses+ then the RingServer will listen for multicast
|
||||
# queries.
|
||||
#
|
||||
# If you use IPv4 multicast you may need to set an address of the inbound
|
||||
# interface which joins a multicast group.
|
||||
#
|
||||
# ts = Rinda::TupleSpace.new
|
||||
# rs = Rinda::RingServer.new(ts, [['239.0.0.1', '9.5.1.1']])
|
||||
#
|
||||
# You can set addresses as an Array Object. The first element of the
|
||||
# Array is a multicast address and the second is an inbound interface
|
||||
# address. If the second is omitted then '0.0.0.0' is used.
|
||||
#
|
||||
# If you use IPv6 multicast you may need to set both the local interface
|
||||
# address and the inbound interface index:
|
||||
#
|
||||
# rs = Rinda::RingServer.new(ts, [['ff02::1', '::1', 1]])
|
||||
#
|
||||
# The first element is a multicast address and the second is an inbound
|
||||
# interface address. The third is an inbound interface index.
|
||||
#
|
||||
# At this time there is no easy way to get an interface index by name.
|
||||
#
|
||||
# If the second is omitted then '::1' is used.
|
||||
# If the third is omitted then 0 (default interface) is used.
|
||||
|
||||
def initialize(ts, addresses=[Socket::INADDR_ANY], port=Ring_PORT)
|
||||
@port = port
|
||||
|
||||
if Integer === addresses then
|
||||
addresses, @port = [Socket::INADDR_ANY], addresses
|
||||
end
|
||||
|
||||
@renewer = Renewer.new
|
||||
|
||||
@ts = ts
|
||||
@sockets = []
|
||||
addresses.each do |address|
|
||||
if Array === address
|
||||
make_socket(*address)
|
||||
else
|
||||
make_socket(address)
|
||||
end
|
||||
end
|
||||
|
||||
@w_services = write_services
|
||||
@r_service = reply_service
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a socket at +address+
|
||||
#
|
||||
# If +address+ is multicast address then +interface_address+ and
|
||||
# +multicast_interface+ can be set as optional.
|
||||
#
|
||||
# A created socket is bound to +interface_address+. If you use IPv4
|
||||
# multicast then the interface of +interface_address+ is used as the
|
||||
# inbound interface. If +interface_address+ is omitted or nil then
|
||||
# '0.0.0.0' or '::1' is used.
|
||||
#
|
||||
# If you use IPv6 multicast then +multicast_interface+ is used as the
|
||||
# inbound interface. +multicast_interface+ is a network interface index.
|
||||
# If +multicast_interface+ is omitted then 0 (default interface) is used.
|
||||
|
||||
def make_socket(address, interface_address=nil, multicast_interface=0)
|
||||
addrinfo = Addrinfo.udp(address, @port)
|
||||
|
||||
socket = Socket.new(addrinfo.pfamily, addrinfo.socktype,
|
||||
addrinfo.protocol)
|
||||
|
||||
if addrinfo.ipv4_multicast? or addrinfo.ipv6_multicast? then
|
||||
if Socket.const_defined?(:SO_REUSEPORT) then
|
||||
socket.setsockopt(:SOCKET, :SO_REUSEPORT, true)
|
||||
else
|
||||
socket.setsockopt(:SOCKET, :SO_REUSEADDR, true)
|
||||
end
|
||||
|
||||
if addrinfo.ipv4_multicast? then
|
||||
interface_address = '0.0.0.0' if interface_address.nil?
|
||||
socket.bind(Addrinfo.udp(interface_address, @port))
|
||||
|
||||
mreq = IPAddr.new(addrinfo.ip_address).hton +
|
||||
IPAddr.new(interface_address).hton
|
||||
|
||||
socket.setsockopt(:IPPROTO_IP, :IP_ADD_MEMBERSHIP, mreq)
|
||||
else
|
||||
interface_address = '::1' if interface_address.nil?
|
||||
socket.bind(Addrinfo.udp(interface_address, @port))
|
||||
|
||||
mreq = IPAddr.new(addrinfo.ip_address).hton +
|
||||
[multicast_interface].pack('I')
|
||||
|
||||
socket.setsockopt(:IPPROTO_IPV6, :IPV6_JOIN_GROUP, mreq)
|
||||
end
|
||||
else
|
||||
socket.bind(addrinfo)
|
||||
end
|
||||
|
||||
socket
|
||||
rescue
|
||||
socket = socket.close if socket
|
||||
raise
|
||||
ensure
|
||||
@sockets << socket if socket
|
||||
end
|
||||
|
||||
##
|
||||
# Creates threads that pick up UDP packets and passes them to do_write for
|
||||
# decoding.
|
||||
|
||||
def write_services
|
||||
@sockets.map do |s|
|
||||
Thread.new(s) do |socket|
|
||||
loop do
|
||||
msg = socket.recv(1024)
|
||||
do_write(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Extracts the response URI from +msg+ and adds it to TupleSpace where it
|
||||
# will be picked up by +reply_service+ for notification.
|
||||
|
||||
def do_write(msg)
|
||||
Thread.new do
|
||||
begin
|
||||
tuple, sec = Marshal.load(msg)
|
||||
@ts.write(tuple, sec)
|
||||
rescue
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a thread that notifies waiting clients from the TupleSpace.
|
||||
|
||||
def reply_service
|
||||
Thread.new do
|
||||
loop do
|
||||
do_reply
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Pulls lookup tuples out of the TupleSpace and sends their DRb object the
|
||||
# address of the local TupleSpace.
|
||||
|
||||
def do_reply
|
||||
tuple = @ts.take([:lookup_ring, nil], @renewer)
|
||||
Thread.new { tuple[1].call(@ts) rescue nil}
|
||||
rescue
|
||||
end
|
||||
|
||||
##
|
||||
# Shuts down the RingServer
|
||||
|
||||
def shutdown
|
||||
@renewer.renew = false
|
||||
|
||||
@w_services.each do |thread|
|
||||
thread.kill
|
||||
thread.join
|
||||
end
|
||||
|
||||
@sockets.each do |socket|
|
||||
socket.close
|
||||
end
|
||||
|
||||
@r_service.kill
|
||||
@r_service.join
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# RingFinger is used by RingServer clients to discover the RingServer's
|
||||
# TupleSpace. Typically, all a client needs to do is call
|
||||
# RingFinger.primary to retrieve the remote TupleSpace, which it can then
|
||||
# begin using.
|
||||
#
|
||||
# To find the first available remote TupleSpace:
|
||||
#
|
||||
# Rinda::RingFinger.primary
|
||||
#
|
||||
# To create a RingFinger that broadcasts to a custom list:
|
||||
#
|
||||
# rf = Rinda::RingFinger.new ['localhost', '192.0.2.1']
|
||||
# rf.primary
|
||||
#
|
||||
# Rinda::RingFinger also understands multicast addresses and sets them up
|
||||
# properly. This allows you to run multiple RingServers on the same host:
|
||||
#
|
||||
# rf = Rinda::RingFinger.new ['239.0.0.1']
|
||||
# rf.primary
|
||||
#
|
||||
# You can set the hop count (or TTL) for multicast searches using
|
||||
# #multicast_hops.
|
||||
#
|
||||
# If you use IPv6 multicast you may need to set both an address and the
|
||||
# outbound interface index:
|
||||
#
|
||||
# rf = Rinda::RingFinger.new ['ff02::1']
|
||||
# rf.multicast_interface = 1
|
||||
# rf.primary
|
||||
#
|
||||
# At this time there is no easy way to get an interface index by name.
|
||||
|
||||
class RingFinger
|
||||
|
||||
@@broadcast_list = ['<broadcast>', 'localhost']
|
||||
|
||||
@@finger = nil
|
||||
|
||||
##
|
||||
# Creates a singleton RingFinger and looks for a RingServer. Returns the
|
||||
# created RingFinger.
|
||||
|
||||
def self.finger
|
||||
unless @@finger
|
||||
@@finger = self.new
|
||||
@@finger.lookup_ring_any
|
||||
end
|
||||
@@finger
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the first advertised TupleSpace.
|
||||
|
||||
def self.primary
|
||||
finger.primary
|
||||
end
|
||||
|
||||
##
|
||||
# Contains all discovered TupleSpaces except for the primary.
|
||||
|
||||
def self.to_a
|
||||
finger.to_a
|
||||
end
|
||||
|
||||
##
|
||||
# The list of addresses where RingFinger will send query packets.
|
||||
|
||||
attr_accessor :broadcast_list
|
||||
|
||||
##
|
||||
# Maximum number of hops for sent multicast packets (if using a multicast
|
||||
# address in the broadcast list). The default is 1 (same as UDP
|
||||
# broadcast).
|
||||
|
||||
attr_accessor :multicast_hops
|
||||
|
||||
##
|
||||
# The interface index to send IPv6 multicast packets from.
|
||||
|
||||
attr_accessor :multicast_interface
|
||||
|
||||
##
|
||||
# The port that RingFinger will send query packets to.
|
||||
|
||||
attr_accessor :port
|
||||
|
||||
##
|
||||
# Contain the first advertised TupleSpace after lookup_ring_any is called.
|
||||
|
||||
attr_accessor :primary
|
||||
|
||||
##
|
||||
# Creates a new RingFinger that will look for RingServers at +port+ on
|
||||
# the addresses in +broadcast_list+.
|
||||
#
|
||||
# If +broadcast_list+ contains a multicast address then multicast queries
|
||||
# will be made using the given multicast_hops and multicast_interface.
|
||||
|
||||
def initialize(broadcast_list=@@broadcast_list, port=Ring_PORT)
|
||||
@broadcast_list = broadcast_list || ['localhost']
|
||||
@port = port
|
||||
@primary = nil
|
||||
@rings = []
|
||||
|
||||
@multicast_hops = 1
|
||||
@multicast_interface = 0
|
||||
end
|
||||
|
||||
##
|
||||
# Contains all discovered TupleSpaces except for the primary.
|
||||
|
||||
def to_a
|
||||
@rings
|
||||
end
|
||||
|
||||
##
|
||||
# Iterates over all discovered TupleSpaces starting with the primary.
|
||||
|
||||
def each
|
||||
lookup_ring_any unless @primary
|
||||
return unless @primary
|
||||
yield(@primary)
|
||||
@rings.each { |x| yield(x) }
|
||||
end
|
||||
|
||||
##
|
||||
# Looks up RingServers waiting +timeout+ seconds. RingServers will be
|
||||
# given +block+ as a callback, which will be called with the remote
|
||||
# TupleSpace.
|
||||
|
||||
def lookup_ring(timeout=5, &block)
|
||||
return lookup_ring_any(timeout) unless block_given?
|
||||
|
||||
msg = Marshal.dump([[:lookup_ring, DRbObject.new(block)], timeout])
|
||||
@broadcast_list.each do |it|
|
||||
send_message(it, msg)
|
||||
end
|
||||
sleep(timeout)
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the first found remote TupleSpace. Any further recovered
|
||||
# TupleSpaces can be found by calling +to_a+.
|
||||
|
||||
def lookup_ring_any(timeout=5)
|
||||
queue = Thread::Queue.new
|
||||
|
||||
Thread.new do
|
||||
self.lookup_ring(timeout) do |ts|
|
||||
queue.push(ts)
|
||||
end
|
||||
queue.push(nil)
|
||||
end
|
||||
|
||||
@primary = queue.pop
|
||||
raise('RingNotFound') if @primary.nil?
|
||||
|
||||
Thread.new do
|
||||
while it = queue.pop
|
||||
@rings.push(it)
|
||||
end
|
||||
end
|
||||
|
||||
@primary
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a socket for +address+ with the appropriate multicast options
|
||||
# for multicast addresses.
|
||||
|
||||
def make_socket(address) # :nodoc:
|
||||
addrinfo = Addrinfo.udp(address, @port)
|
||||
|
||||
soc = Socket.new(addrinfo.pfamily, addrinfo.socktype, addrinfo.protocol)
|
||||
begin
|
||||
if addrinfo.ipv4_multicast? then
|
||||
soc.setsockopt(Socket::Option.ipv4_multicast_loop(1))
|
||||
soc.setsockopt(Socket::Option.ipv4_multicast_ttl(@multicast_hops))
|
||||
elsif addrinfo.ipv6_multicast? then
|
||||
soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_LOOP, true)
|
||||
soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_HOPS,
|
||||
[@multicast_hops].pack('I'))
|
||||
soc.setsockopt(:IPPROTO_IPV6, :IPV6_MULTICAST_IF,
|
||||
[@multicast_interface].pack('I'))
|
||||
else
|
||||
soc.setsockopt(:SOL_SOCKET, :SO_BROADCAST, true)
|
||||
end
|
||||
|
||||
soc.connect(addrinfo)
|
||||
rescue Exception
|
||||
soc.close
|
||||
raise
|
||||
end
|
||||
|
||||
soc
|
||||
end
|
||||
|
||||
def send_message(address, message) # :nodoc:
|
||||
soc = make_socket(address)
|
||||
|
||||
soc.send(message, 0)
|
||||
rescue
|
||||
nil
|
||||
ensure
|
||||
soc.close if soc
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# RingProvider uses a RingServer advertised TupleSpace as a name service.
|
||||
# TupleSpace clients can register themselves with the remote TupleSpace and
|
||||
# look up other provided services via the remote TupleSpace.
|
||||
#
|
||||
# Services are registered with a tuple of the format [:name, klass,
|
||||
# DRbObject, description].
|
||||
|
||||
class RingProvider
|
||||
|
||||
##
|
||||
# Creates a RingProvider that will provide a +klass+ service running on
|
||||
# +front+, with a +description+. +renewer+ is optional.
|
||||
|
||||
def initialize(klass, front, desc, renewer = nil)
|
||||
@tuple = [:name, klass, front, desc]
|
||||
@renewer = renewer || Rinda::SimpleRenewer.new
|
||||
end
|
||||
|
||||
##
|
||||
# Advertises this service on the primary remote TupleSpace.
|
||||
|
||||
def provide
|
||||
ts = Rinda::RingFinger.primary
|
||||
ts.write(@tuple, @renewer)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
641
gxg/standard/rinda/tuplespace.rb
Normal file
641
gxg/standard/rinda/tuplespace.rb
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
# frozen_string_literal: false
|
||||
require 'monitor'
|
||||
require 'drb/drb'
|
||||
require 'rinda/rinda'
|
||||
require 'forwardable'
|
||||
|
||||
module Rinda
|
||||
|
||||
##
|
||||
# A TupleEntry is a Tuple (i.e. a possible entry in some Tuplespace)
|
||||
# together with expiry and cancellation data.
|
||||
|
||||
class TupleEntry
|
||||
|
||||
include DRbUndumped
|
||||
|
||||
attr_accessor :expires
|
||||
|
||||
##
|
||||
# Creates a TupleEntry based on +ary+ with an optional renewer or expiry
|
||||
# time +sec+.
|
||||
#
|
||||
# A renewer must implement the +renew+ method which returns a Numeric,
|
||||
# nil, or true to indicate when the tuple has expired.
|
||||
|
||||
def initialize(ary, sec=nil)
|
||||
@cancel = false
|
||||
@expires = nil
|
||||
@tuple = make_tuple(ary)
|
||||
@renewer = nil
|
||||
renew(sec)
|
||||
end
|
||||
|
||||
##
|
||||
# Marks this TupleEntry as canceled.
|
||||
|
||||
def cancel
|
||||
@cancel = true
|
||||
end
|
||||
|
||||
##
|
||||
# A TupleEntry is dead when it is canceled or expired.
|
||||
|
||||
def alive?
|
||||
!canceled? && !expired?
|
||||
end
|
||||
|
||||
##
|
||||
# Return the object which makes up the tuple itself: the Array
|
||||
# or Hash.
|
||||
|
||||
def value; @tuple.value; end
|
||||
|
||||
##
|
||||
# Returns the canceled status.
|
||||
|
||||
def canceled?; @cancel; end
|
||||
|
||||
##
|
||||
# Has this tuple expired? (true/false).
|
||||
#
|
||||
# A tuple has expired when its expiry timer based on the +sec+ argument to
|
||||
# #initialize runs out.
|
||||
|
||||
def expired?
|
||||
return true unless @expires
|
||||
return false if @expires > Time.now
|
||||
return true if @renewer.nil?
|
||||
renew(@renewer)
|
||||
return true unless @expires
|
||||
return @expires < Time.now
|
||||
end
|
||||
|
||||
##
|
||||
# Reset the expiry time according to +sec_or_renewer+.
|
||||
#
|
||||
# +nil+:: it is set to expire in the far future.
|
||||
# +true+:: it has expired.
|
||||
# Numeric:: it will expire in that many seconds.
|
||||
#
|
||||
# Otherwise the argument refers to some kind of renewer object
|
||||
# which will reset its expiry time.
|
||||
|
||||
def renew(sec_or_renewer)
|
||||
sec, @renewer = get_renewer(sec_or_renewer)
|
||||
@expires = make_expires(sec)
|
||||
end
|
||||
|
||||
##
|
||||
# Returns an expiry Time based on +sec+ which can be one of:
|
||||
# Numeric:: +sec+ seconds into the future
|
||||
# +true+:: the expiry time is the start of 1970 (i.e. expired)
|
||||
# +nil+:: it is Tue Jan 19 03:14:07 GMT Standard Time 2038 (i.e. when
|
||||
# UNIX clocks will die)
|
||||
|
||||
def make_expires(sec=nil)
|
||||
case sec
|
||||
when Numeric
|
||||
Time.now + sec
|
||||
when true
|
||||
Time.at(1)
|
||||
when nil
|
||||
Time.at(2**31-1)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieves +key+ from the tuple.
|
||||
|
||||
def [](key)
|
||||
@tuple[key]
|
||||
end
|
||||
|
||||
##
|
||||
# Fetches +key+ from the tuple.
|
||||
|
||||
def fetch(key)
|
||||
@tuple.fetch(key)
|
||||
end
|
||||
|
||||
##
|
||||
# The size of the tuple.
|
||||
|
||||
def size
|
||||
@tuple.size
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a Rinda::Tuple for +ary+.
|
||||
|
||||
def make_tuple(ary)
|
||||
Rinda::Tuple.new(ary)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Returns a valid argument to make_expires and the renewer or nil.
|
||||
#
|
||||
# Given +true+, +nil+, or Numeric, returns that value and +nil+ (no actual
|
||||
# renewer). Otherwise it returns an expiry value from calling +it.renew+
|
||||
# and the renewer.
|
||||
|
||||
def get_renewer(it)
|
||||
case it
|
||||
when Numeric, true, nil
|
||||
return it, nil
|
||||
else
|
||||
begin
|
||||
return it.renew, it
|
||||
rescue Exception
|
||||
return it, nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# A TemplateEntry is a Template together with expiry and cancellation data.
|
||||
|
||||
class TemplateEntry < TupleEntry
|
||||
##
|
||||
# Matches this TemplateEntry against +tuple+. See Template#match for
|
||||
# details on how a Template matches a Tuple.
|
||||
|
||||
def match(tuple)
|
||||
@tuple.match(tuple)
|
||||
end
|
||||
|
||||
alias === match
|
||||
|
||||
def make_tuple(ary) # :nodoc:
|
||||
Rinda::Template.new(ary)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# <i>Documentation?</i>
|
||||
|
||||
class WaitTemplateEntry < TemplateEntry
|
||||
|
||||
attr_reader :found
|
||||
|
||||
def initialize(place, ary, expires=nil)
|
||||
super(ary, expires)
|
||||
@place = place
|
||||
@cond = place.new_cond
|
||||
@found = nil
|
||||
end
|
||||
|
||||
def cancel
|
||||
super
|
||||
signal
|
||||
end
|
||||
|
||||
def wait
|
||||
@cond.wait
|
||||
end
|
||||
|
||||
def read(tuple)
|
||||
@found = tuple
|
||||
signal
|
||||
end
|
||||
|
||||
def signal
|
||||
@place.synchronize do
|
||||
@cond.signal
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# A NotifyTemplateEntry is returned by TupleSpace#notify and is notified of
|
||||
# TupleSpace changes. You may receive either your subscribed event or the
|
||||
# 'close' event when iterating over notifications.
|
||||
#
|
||||
# See TupleSpace#notify_event for valid notification types.
|
||||
#
|
||||
# == Example
|
||||
#
|
||||
# ts = Rinda::TupleSpace.new
|
||||
# observer = ts.notify 'write', [nil]
|
||||
#
|
||||
# Thread.start do
|
||||
# observer.each { |t| p t }
|
||||
# end
|
||||
#
|
||||
# 3.times { |i| ts.write [i] }
|
||||
#
|
||||
# Outputs:
|
||||
#
|
||||
# ['write', [0]]
|
||||
# ['write', [1]]
|
||||
# ['write', [2]]
|
||||
|
||||
class NotifyTemplateEntry < TemplateEntry
|
||||
|
||||
##
|
||||
# Creates a new NotifyTemplateEntry that watches +place+ for +event+s that
|
||||
# match +tuple+.
|
||||
|
||||
def initialize(place, event, tuple, expires=nil)
|
||||
ary = [event, Rinda::Template.new(tuple)]
|
||||
super(ary, expires)
|
||||
@queue = Thread::Queue.new
|
||||
@done = false
|
||||
end
|
||||
|
||||
##
|
||||
# Called by TupleSpace to notify this NotifyTemplateEntry of a new event.
|
||||
|
||||
def notify(ev)
|
||||
@queue.push(ev)
|
||||
end
|
||||
|
||||
##
|
||||
# Retrieves a notification. Raises RequestExpiredError when this
|
||||
# NotifyTemplateEntry expires.
|
||||
|
||||
def pop
|
||||
raise RequestExpiredError if @done
|
||||
it = @queue.pop
|
||||
@done = true if it[0] == 'close'
|
||||
return it
|
||||
end
|
||||
|
||||
##
|
||||
# Yields event/tuple pairs until this NotifyTemplateEntry expires.
|
||||
|
||||
def each # :yields: event, tuple
|
||||
while !@done
|
||||
it = pop
|
||||
yield(it)
|
||||
end
|
||||
rescue
|
||||
ensure
|
||||
cancel
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
##
|
||||
# TupleBag is an unordered collection of tuples. It is the basis
|
||||
# of Tuplespace.
|
||||
|
||||
class TupleBag
|
||||
class TupleBin
|
||||
extend Forwardable
|
||||
def_delegators '@bin', :find_all, :delete_if, :each, :empty?
|
||||
|
||||
def initialize
|
||||
@bin = []
|
||||
end
|
||||
|
||||
def add(tuple)
|
||||
@bin.push(tuple)
|
||||
end
|
||||
|
||||
def delete(tuple)
|
||||
idx = @bin.rindex(tuple)
|
||||
@bin.delete_at(idx) if idx
|
||||
end
|
||||
|
||||
def find
|
||||
@bin.reverse_each do |x|
|
||||
return x if yield(x)
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def initialize # :nodoc:
|
||||
@hash = {}
|
||||
@enum = enum_for(:each_entry)
|
||||
end
|
||||
|
||||
##
|
||||
# +true+ if the TupleBag to see if it has any expired entries.
|
||||
|
||||
def has_expires?
|
||||
@enum.find do |tuple|
|
||||
tuple.expires
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Add +tuple+ to the TupleBag.
|
||||
|
||||
def push(tuple)
|
||||
key = bin_key(tuple)
|
||||
@hash[key] ||= TupleBin.new
|
||||
@hash[key].add(tuple)
|
||||
end
|
||||
|
||||
##
|
||||
# Removes +tuple+ from the TupleBag.
|
||||
|
||||
def delete(tuple)
|
||||
key = bin_key(tuple)
|
||||
bin = @hash[key]
|
||||
return nil unless bin
|
||||
bin.delete(tuple)
|
||||
@hash.delete(key) if bin.empty?
|
||||
tuple
|
||||
end
|
||||
|
||||
##
|
||||
# Finds all live tuples that match +template+.
|
||||
def find_all(template)
|
||||
bin_for_find(template).find_all do |tuple|
|
||||
tuple.alive? && template.match(tuple)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Finds a live tuple that matches +template+.
|
||||
|
||||
def find(template)
|
||||
bin_for_find(template).find do |tuple|
|
||||
tuple.alive? && template.match(tuple)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Finds all tuples in the TupleBag which when treated as templates, match
|
||||
# +tuple+ and are alive.
|
||||
|
||||
def find_all_template(tuple)
|
||||
@enum.find_all do |template|
|
||||
template.alive? && template.match(tuple)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Delete tuples which dead tuples from the TupleBag, returning the deleted
|
||||
# tuples.
|
||||
|
||||
def delete_unless_alive
|
||||
deleted = []
|
||||
@hash.each do |key, bin|
|
||||
bin.delete_if do |tuple|
|
||||
if tuple.alive?
|
||||
false
|
||||
else
|
||||
deleted.push(tuple)
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
deleted
|
||||
end
|
||||
|
||||
private
|
||||
def each_entry(&blk)
|
||||
@hash.each do |k, v|
|
||||
v.each(&blk)
|
||||
end
|
||||
end
|
||||
|
||||
def bin_key(tuple)
|
||||
head = tuple[0]
|
||||
if head.class == Symbol
|
||||
return head
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def bin_for_find(template)
|
||||
key = bin_key(template)
|
||||
key ? @hash.fetch(key, []) : @enum
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The Tuplespace manages access to the tuples it contains,
|
||||
# ensuring mutual exclusion requirements are met.
|
||||
#
|
||||
# The +sec+ option for the write, take, move, read and notify methods may
|
||||
# either be a number of seconds or a Renewer object.
|
||||
|
||||
class TupleSpace
|
||||
|
||||
include DRbUndumped
|
||||
include MonitorMixin
|
||||
|
||||
##
|
||||
# Creates a new TupleSpace. +period+ is used to control how often to look
|
||||
# for dead tuples after modifications to the TupleSpace.
|
||||
#
|
||||
# If no dead tuples are found +period+ seconds after the last
|
||||
# modification, the TupleSpace will stop looking for dead tuples.
|
||||
|
||||
def initialize(period=60)
|
||||
super()
|
||||
@bag = TupleBag.new
|
||||
@read_waiter = TupleBag.new
|
||||
@take_waiter = TupleBag.new
|
||||
@notify_waiter = TupleBag.new
|
||||
@period = period
|
||||
@keeper = nil
|
||||
end
|
||||
|
||||
##
|
||||
# Adds +tuple+
|
||||
|
||||
def write(tuple, sec=nil)
|
||||
entry = create_entry(tuple, sec)
|
||||
synchronize do
|
||||
if entry.expired?
|
||||
@read_waiter.find_all_template(entry).each do |template|
|
||||
template.read(tuple)
|
||||
end
|
||||
notify_event('write', entry.value)
|
||||
notify_event('delete', entry.value)
|
||||
else
|
||||
@bag.push(entry)
|
||||
start_keeper if entry.expires
|
||||
@read_waiter.find_all_template(entry).each do |template|
|
||||
template.read(tuple)
|
||||
end
|
||||
@take_waiter.find_all_template(entry).each do |template|
|
||||
template.signal
|
||||
end
|
||||
notify_event('write', entry.value)
|
||||
end
|
||||
end
|
||||
entry
|
||||
end
|
||||
|
||||
##
|
||||
# Removes +tuple+
|
||||
|
||||
def take(tuple, sec=nil, &block)
|
||||
move(nil, tuple, sec, &block)
|
||||
end
|
||||
|
||||
##
|
||||
# Moves +tuple+ to +port+.
|
||||
|
||||
def move(port, tuple, sec=nil)
|
||||
template = WaitTemplateEntry.new(self, tuple, sec)
|
||||
yield(template) if block_given?
|
||||
synchronize do
|
||||
entry = @bag.find(template)
|
||||
if entry
|
||||
port.push(entry.value) if port
|
||||
@bag.delete(entry)
|
||||
notify_event('take', entry.value)
|
||||
return port ? nil : entry.value
|
||||
end
|
||||
raise RequestExpiredError if template.expired?
|
||||
|
||||
begin
|
||||
@take_waiter.push(template)
|
||||
start_keeper if template.expires
|
||||
while true
|
||||
raise RequestCanceledError if template.canceled?
|
||||
raise RequestExpiredError if template.expired?
|
||||
entry = @bag.find(template)
|
||||
if entry
|
||||
port.push(entry.value) if port
|
||||
@bag.delete(entry)
|
||||
notify_event('take', entry.value)
|
||||
return port ? nil : entry.value
|
||||
end
|
||||
template.wait
|
||||
end
|
||||
ensure
|
||||
@take_waiter.delete(template)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Reads +tuple+, but does not remove it.
|
||||
|
||||
def read(tuple, sec=nil)
|
||||
template = WaitTemplateEntry.new(self, tuple, sec)
|
||||
yield(template) if block_given?
|
||||
synchronize do
|
||||
entry = @bag.find(template)
|
||||
return entry.value if entry
|
||||
raise RequestExpiredError if template.expired?
|
||||
|
||||
begin
|
||||
@read_waiter.push(template)
|
||||
start_keeper if template.expires
|
||||
template.wait
|
||||
raise RequestCanceledError if template.canceled?
|
||||
raise RequestExpiredError if template.expired?
|
||||
return template.found
|
||||
ensure
|
||||
@read_waiter.delete(template)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns all tuples matching +tuple+. Does not remove the found tuples.
|
||||
|
||||
def read_all(tuple)
|
||||
template = WaitTemplateEntry.new(self, tuple, nil)
|
||||
synchronize do
|
||||
entry = @bag.find_all(template)
|
||||
entry.collect do |e|
|
||||
e.value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Registers for notifications of +event+. Returns a NotifyTemplateEntry.
|
||||
# See NotifyTemplateEntry for examples of how to listen for notifications.
|
||||
#
|
||||
# +event+ can be:
|
||||
# 'write':: A tuple was added
|
||||
# 'take':: A tuple was taken or moved
|
||||
# 'delete':: A tuple was lost after being overwritten or expiring
|
||||
#
|
||||
# The TupleSpace will also notify you of the 'close' event when the
|
||||
# NotifyTemplateEntry has expired.
|
||||
|
||||
def notify(event, tuple, sec=nil)
|
||||
template = NotifyTemplateEntry.new(self, event, tuple, sec)
|
||||
synchronize do
|
||||
@notify_waiter.push(template)
|
||||
end
|
||||
template
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_entry(tuple, sec)
|
||||
TupleEntry.new(tuple, sec)
|
||||
end
|
||||
|
||||
##
|
||||
# Removes dead tuples.
|
||||
|
||||
def keep_clean
|
||||
synchronize do
|
||||
@read_waiter.delete_unless_alive.each do |e|
|
||||
e.signal
|
||||
end
|
||||
@take_waiter.delete_unless_alive.each do |e|
|
||||
e.signal
|
||||
end
|
||||
@notify_waiter.delete_unless_alive.each do |e|
|
||||
e.notify(['close'])
|
||||
end
|
||||
@bag.delete_unless_alive.each do |e|
|
||||
notify_event('delete', e.value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Notifies all registered listeners for +event+ of a status change of
|
||||
# +tuple+.
|
||||
|
||||
def notify_event(event, tuple)
|
||||
ev = [event, tuple]
|
||||
@notify_waiter.find_all_template(ev).each do |template|
|
||||
template.notify(ev)
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Creates a thread that scans the tuplespace for expired tuples.
|
||||
|
||||
def start_keeper
|
||||
return if @keeper && @keeper.alive?
|
||||
@keeper = Thread.new do
|
||||
while true
|
||||
sleep(@period)
|
||||
synchronize do
|
||||
break unless need_keeper?
|
||||
keep_clean
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Checks the tuplespace to see if it needs cleaning.
|
||||
|
||||
def need_keeper?
|
||||
return true if @bag.has_expires?
|
||||
return true if @read_waiter.has_expires?
|
||||
return true if @take_waiter.has_expires?
|
||||
return true if @notify_waiter.has_expires?
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
776
gxg/standard/scanf.rb
Normal file
776
gxg/standard/scanf.rb
Normal file
|
|
@ -0,0 +1,776 @@
|
|||
# frozen_string_literal: false
|
||||
# scanf for Ruby
|
||||
#
|
||||
#--
|
||||
# $Release Version: 1.1.2 $
|
||||
# $Revision: 60261 $
|
||||
# $Id: scanf.rb 60261 2017-10-21 12:34:13Z nobu $
|
||||
# $Author: nobu $
|
||||
#++
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# scanf is an implementation of the C function scanf(3), modified as necessary
|
||||
# for Ruby compatibility.
|
||||
#
|
||||
# the methods provided are String#scanf, IO#scanf, and
|
||||
# Kernel#scanf. Kernel#scanf is a wrapper around STDIN.scanf. IO#scanf
|
||||
# can be used on any IO stream, including file handles and sockets.
|
||||
# scanf can be called either with or without a block.
|
||||
#
|
||||
# Scanf scans an input string or stream according to a <b>format</b>, as
|
||||
# described below in Conversions, and returns an array of matches between
|
||||
# the format and the input. The format is defined in a string, and is
|
||||
# similar (though not identical) to the formats used in Kernel#printf and
|
||||
# Kernel#sprintf.
|
||||
#
|
||||
# The format may contain <b>conversion specifiers</b>, which tell scanf
|
||||
# what form (type) each particular matched substring should be converted
|
||||
# to (e.g., decimal integer, floating point number, literal string,
|
||||
# etc.) The matches and conversions take place from left to right, and
|
||||
# the conversions themselves are returned as an array.
|
||||
#
|
||||
# The format string may also contain characters other than those in the
|
||||
# conversion specifiers. White space (blanks, tabs, or newlines) in the
|
||||
# format string matches any amount of white space, including none, in
|
||||
# the input. Everything else matches only itself.
|
||||
#
|
||||
# Scanning stops, and scanf returns, when any input character fails to
|
||||
# match the specifications in the format string, or when input is
|
||||
# exhausted, or when everything in the format string has been
|
||||
# matched. All matches found up to the stopping point are returned in
|
||||
# the return array (or yielded to the block, if a block was given).
|
||||
#
|
||||
#
|
||||
# == Basic usage
|
||||
#
|
||||
# require 'scanf'
|
||||
#
|
||||
# # String#scanf and IO#scanf take a single argument, the format string
|
||||
# array = a_string.scanf("%d%s")
|
||||
# array = an_io.scanf("%d%s")
|
||||
#
|
||||
# # Kernel#scanf reads from STDIN
|
||||
# array = scanf("%d%s")
|
||||
#
|
||||
# == Block usage
|
||||
#
|
||||
# When called with a block, scanf keeps scanning the input, cycling back
|
||||
# to the beginning of the format string, and yields a new array of
|
||||
# conversions to the block every time the format string is matched
|
||||
# (including partial matches, but not including complete failures). The
|
||||
# actual return value of scanf when called with a block is an array
|
||||
# containing the results of all the executions of the block.
|
||||
#
|
||||
# str = "123 abc 456 def 789 ghi"
|
||||
# str.scanf("%d%s") { |num,str| [ num * 2, str.upcase ] }
|
||||
# # => [[246, "ABC"], [912, "DEF"], [1578, "GHI"]]
|
||||
#
|
||||
# == Conversions
|
||||
#
|
||||
# The single argument to scanf is a format string, which generally
|
||||
# includes one or more conversion specifiers. Conversion specifiers
|
||||
# begin with the percent character ('%') and include information about
|
||||
# what scanf should next scan for (string, decimal number, single
|
||||
# character, etc.).
|
||||
#
|
||||
# There may be an optional maximum field width, expressed as a decimal
|
||||
# integer, between the % and the conversion. If no width is given, a
|
||||
# default of `infinity' is used (with the exception of the %c specifier;
|
||||
# see below). Otherwise, given a field width of <em>n</em> for a given
|
||||
# conversion, at most <em>n</em> characters are scanned in processing
|
||||
# that conversion. Before conversion begins, most conversions skip
|
||||
# white space in the input string; this white space is not counted
|
||||
# against the field width.
|
||||
#
|
||||
# The following conversions are available.
|
||||
#
|
||||
# [%]
|
||||
# Matches a literal `%'. That is, `%%' in the format string matches a
|
||||
# single input `%' character. No conversion is done, and the resulting
|
||||
# '%' is not included in the return array.
|
||||
#
|
||||
# [d]
|
||||
# Matches an optionally signed decimal integer.
|
||||
#
|
||||
# [u]
|
||||
# Same as d.
|
||||
#
|
||||
# [i]
|
||||
# Matches an optionally signed integer. The integer is read in base
|
||||
# 16 if it begins with `0x' or `0X', in base 8 if it begins with `0',
|
||||
# and in base 10 other- wise. Only characters that correspond to the
|
||||
# base are recognized.
|
||||
#
|
||||
# [o]
|
||||
# Matches an optionally signed octal integer.
|
||||
#
|
||||
# [x, X]
|
||||
# Matches an optionally signed hexadecimal integer,
|
||||
#
|
||||
# [a, e, f, g, A, E, F, G]
|
||||
# Matches an optionally signed floating-point number.
|
||||
#
|
||||
# [s]
|
||||
# Matches a sequence of non-white-space character. The input string stops at
|
||||
# white space or at the maximum field width, whichever occurs first.
|
||||
#
|
||||
# [c]
|
||||
# Matches a single character, or a sequence of <em>n</em> characters if a
|
||||
# field width of <em>n</em> is specified. The usual skip of leading white
|
||||
# space is suppressed. To skip white space first, use an explicit space in
|
||||
# the format.
|
||||
#
|
||||
# [[]
|
||||
# Matches a nonempty sequence of characters from the specified set
|
||||
# of accepted characters. The usual skip of leading white space is
|
||||
# suppressed. This bracketed sub-expression is interpreted exactly like a
|
||||
# character class in a Ruby regular expression. (In fact, it is placed as-is
|
||||
# in a regular expression.) The matching against the input string ends with
|
||||
# the appearance of a character not in (or, with a circumflex, in) the set,
|
||||
# or when the field width runs out, whichever comes first.
|
||||
#
|
||||
# === Assignment suppression
|
||||
#
|
||||
# To require that a particular match occur, but without including the result
|
||||
# in the return array, place the <b>assignment suppression flag</b>, which is
|
||||
# the star character ('*'), immediately after the leading '%' of a format
|
||||
# specifier (just before the field width, if any).
|
||||
#
|
||||
# == scanf for Ruby compared with scanf in C
|
||||
#
|
||||
# scanf for Ruby is based on the C function scanf(3), but with modifications,
|
||||
# dictated mainly by the underlying differences between the languages.
|
||||
#
|
||||
# === Unimplemented flags and specifiers
|
||||
#
|
||||
# * The only flag implemented in scanf for Ruby is '<tt>*</tt>' (ignore
|
||||
# upcoming conversion). Many of the flags available in C versions of
|
||||
# scanf(3) have to do with the type of upcoming pointer arguments, and are
|
||||
# meaningless in Ruby.
|
||||
#
|
||||
# * The <tt>n</tt> specifier (store number of characters consumed so far in
|
||||
# next pointer) is not implemented.
|
||||
#
|
||||
# * The <tt>p</tt> specifier (match a pointer value) is not implemented.
|
||||
#
|
||||
# === Altered specifiers
|
||||
#
|
||||
# [o, u, x, X]
|
||||
# In scanf for Ruby, all of these specifiers scan for an optionally signed
|
||||
# integer, rather than for an unsigned integer like their C counterparts.
|
||||
#
|
||||
# === Return values
|
||||
#
|
||||
# scanf for Ruby returns an array of successful conversions, whereas
|
||||
# scanf(3) returns the number of conversions successfully
|
||||
# completed. (See below for more details on scanf for Ruby's return
|
||||
# values.)
|
||||
#
|
||||
# == Return values
|
||||
#
|
||||
# Without a block, scanf returns an array containing all the conversions
|
||||
# it has found. If none are found, scanf will return an empty array. An
|
||||
# unsuccessful match is never ignored, but rather always signals the end
|
||||
# of the scanning operation. If the first unsuccessful match takes place
|
||||
# after one or more successful matches have already taken place, the
|
||||
# returned array will contain the results of those successful matches.
|
||||
#
|
||||
# With a block scanf returns a 'map'-like array of transformations from
|
||||
# the block -- that is, an array reflecting what the block did with each
|
||||
# yielded result from the iterative scanf operation. (See "Block
|
||||
# usage", above.)
|
||||
#
|
||||
# == Current limitations and bugs
|
||||
#
|
||||
# When using IO#scanf under Windows, make sure you open your files in
|
||||
# binary mode:
|
||||
#
|
||||
# File.open("filename", "rb")
|
||||
#
|
||||
# so that scanf can keep track of characters correctly.
|
||||
#
|
||||
# Support for character classes is reasonably complete (since it
|
||||
# essentially piggy-backs on Ruby's regular expression handling of
|
||||
# character classes), but users are advised that character class testing
|
||||
# has not been exhaustive, and that they should exercise some caution
|
||||
# in using any of the more complex and/or arcane character class
|
||||
# idioms.
|
||||
#
|
||||
# == License and copyright
|
||||
#
|
||||
# Copyright:: (c) 2002-2003 David Alan Black
|
||||
# License:: Distributed on the same licensing terms as Ruby itself
|
||||
#
|
||||
# == Warranty disclaimer
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# == Credits and acknowledgements
|
||||
#
|
||||
# scanf was developed as the major activity of the Austin Ruby Codefest
|
||||
# (Austin, Texas, August 2002).
|
||||
#
|
||||
# Principal author:: David Alan Black (mailto:dblack@superlink.net)
|
||||
# Co-author:: Hal Fulton (mailto:hal9000@hypermetrics.com)
|
||||
# Project contributors:: Nolan Darilek, Jason Johnston
|
||||
#
|
||||
# Thanks to Hal Fulton for hosting the Codefest.
|
||||
#
|
||||
# Thanks to Matz for suggestions about the class design.
|
||||
#
|
||||
# Thanks to Gavin Sinclair for some feedback on the documentation.
|
||||
#
|
||||
# The text for parts of this document, especially the Description and
|
||||
# Conversions sections, above, were adapted from the Linux Programmer's
|
||||
# Manual manpage for scanf(3), dated 1995-11-01.
|
||||
#
|
||||
# == Bugs and bug reports
|
||||
#
|
||||
# scanf for Ruby is based on something of an amalgam of C scanf
|
||||
# implementations and documentation, rather than on a single canonical
|
||||
# description. Suggestions for features and behaviors which appear in
|
||||
# other scanfs, and would be meaningful in Ruby, are welcome, as are
|
||||
# reports of suspicious behaviors and/or bugs. (Please see "Credits and
|
||||
# acknowledgements", above, for email addresses.)
|
||||
|
||||
module Scanf
|
||||
# :stopdoc:
|
||||
|
||||
# ==Technical notes
|
||||
#
|
||||
# ===Rationale behind scanf for Ruby
|
||||
#
|
||||
# The impetus for a scanf implementation in Ruby comes chiefly from the fact
|
||||
# that existing pattern matching operations, such as Regexp#match and
|
||||
# String#scan, return all results as strings, which have to be converted to
|
||||
# integers or floats explicitly in cases where what's ultimately wanted are
|
||||
# integer or float values.
|
||||
#
|
||||
# ===Design of scanf for Ruby
|
||||
#
|
||||
# scanf for Ruby is essentially a <format string>-to-<regular
|
||||
# expression> converter.
|
||||
#
|
||||
# When scanf is called, a FormatString object is generated from the
|
||||
# format string ("%d%s...") argument. The FormatString object breaks the
|
||||
# format string down into atoms ("%d", "%5f", "blah", etc.), and from
|
||||
# each atom it creates a FormatSpecifier object, which it
|
||||
# saves.
|
||||
#
|
||||
# Each FormatSpecifier has a regular expression fragment and a "handler"
|
||||
# associated with it. For example, the regular expression fragment
|
||||
# associated with the format "%d" is "([-+]?\d+)", and the handler
|
||||
# associated with it is a wrapper around String#to_i. scanf itself calls
|
||||
# FormatString#match, passing in the input string. FormatString#match
|
||||
# iterates through its FormatSpecifiers; for each one, it matches the
|
||||
# corresponding regular expression fragment against the string. If
|
||||
# there's a match, it sends the matched string to the handler associated
|
||||
# with the FormatSpecifier.
|
||||
#
|
||||
# Thus, to follow up the "%d" example: if "123" occurs in the input
|
||||
# string when a FormatSpecifier consisting of "%d" is reached, the "123"
|
||||
# will be matched against "([-+]?\d+)", and the matched string will be
|
||||
# rendered into an integer by a call to to_i.
|
||||
#
|
||||
# The rendered match is then saved to an accumulator array, and the
|
||||
# input string is reduced to the post-match substring. Thus the string
|
||||
# is "eaten" from the left as the FormatSpecifiers are applied in
|
||||
# sequence. (This is done to a duplicate string; the original string is
|
||||
# not altered.)
|
||||
#
|
||||
# As soon as a regular expression fragment fails to match the string, or
|
||||
# when the FormatString object runs out of FormatSpecifiers, scanning
|
||||
# stops and results accumulated so far are returned in an array.
|
||||
|
||||
class FormatSpecifier
|
||||
|
||||
attr_reader :re_string, :matched_string, :conversion, :matched
|
||||
|
||||
private
|
||||
|
||||
def skip; /^\s*%\*/.match(@spec_string); end
|
||||
|
||||
def extract_float(s)
|
||||
return nil unless s &&! skip
|
||||
if /\A(?<sign>[-+]?)0[xX](?<frac>\.\h+|\h+(?:\.\h*)?)[pP](?<exp>[-+]?\d+)/ =~ s
|
||||
f1, f2 = frac.split('.')
|
||||
f = f1.hex
|
||||
if f2
|
||||
len = f2.length
|
||||
if len > 0
|
||||
f += f2.hex / (16.0 ** len)
|
||||
end
|
||||
end
|
||||
(sign == ?- ? -1 : 1) * Math.ldexp(f, exp.to_i)
|
||||
elsif /\A([-+]?\d+)\.([eE][-+]\d+)/ =~ s
|
||||
($1 << $2).to_f
|
||||
else
|
||||
s.to_f
|
||||
end
|
||||
end
|
||||
def extract_decimal(s); s.to_i if s &&! skip; end
|
||||
def extract_hex(s); s.hex if s &&! skip; end
|
||||
def extract_octal(s); s.oct if s &&! skip; end
|
||||
def extract_integer(s); Integer(s) if s &&! skip; end
|
||||
def extract_plain(s); s unless skip; end
|
||||
|
||||
def nil_proc(s); nil; end
|
||||
|
||||
public
|
||||
|
||||
def to_s
|
||||
@spec_string
|
||||
end
|
||||
|
||||
def count_space?
|
||||
/(?:\A|\S)%\*?\d*c|%\d*\[/.match(@spec_string)
|
||||
end
|
||||
|
||||
def initialize(str)
|
||||
@spec_string = str
|
||||
h = '[A-Fa-f0-9]'
|
||||
|
||||
@re_string, @handler =
|
||||
case @spec_string
|
||||
|
||||
# %[[:...:]]
|
||||
when /%\*?(\[\[:[a-z]+:\]\])/
|
||||
[ "(#{$1}+)", :extract_plain ]
|
||||
|
||||
# %5[[:...:]]
|
||||
when /%\*?(\d+)(\[\[:[a-z]+:\]\])/
|
||||
[ "(#{$2}{1,#{$1}})", :extract_plain ]
|
||||
|
||||
# %[...]
|
||||
when /%\*?\[([^\]]*)\]/
|
||||
yes = $1
|
||||
if /^\^/.match(yes) then no = yes[1..-1] else no = '^' + yes end
|
||||
[ "([#{yes}]+)(?=[#{no}]|\\z)", :extract_plain ]
|
||||
|
||||
# %5[...]
|
||||
when /%\*?(\d+)\[([^\]]*)\]/
|
||||
yes = $2
|
||||
w = $1
|
||||
[ "([#{yes}]{1,#{w}})", :extract_plain ]
|
||||
|
||||
# %i
|
||||
when /%\*?i/
|
||||
[ "([-+]?(?:(?:0[0-7]+)|(?:0[Xx]#{h}+)|(?:[1-9]\\d*)))", :extract_integer ]
|
||||
|
||||
# %5i
|
||||
when /%\*?(\d+)i/
|
||||
n = $1.to_i
|
||||
s = "("
|
||||
if n > 1 then s += "[1-9]\\d{1,#{n-1}}|" end
|
||||
if n > 1 then s += "0[0-7]{1,#{n-1}}|" end
|
||||
if n > 2 then s += "[-+]0[0-7]{1,#{n-2}}|" end
|
||||
if n > 2 then s += "[-+][1-9]\\d{1,#{n-2}}|" end
|
||||
if n > 2 then s += "0[Xx]#{h}{1,#{n-2}}|" end
|
||||
if n > 3 then s += "[-+]0[Xx]#{h}{1,#{n-3}}|" end
|
||||
s += "\\d"
|
||||
s += ")"
|
||||
[ s, :extract_integer ]
|
||||
|
||||
# %d, %u
|
||||
when /%\*?[du]/
|
||||
[ '([-+]?\d+)', :extract_decimal ]
|
||||
|
||||
# %5d, %5u
|
||||
when /%\*?(\d+)[du]/
|
||||
n = $1.to_i
|
||||
s = "("
|
||||
if n > 1 then s += "[-+]\\d{1,#{n-1}}|" end
|
||||
s += "\\d{1,#{$1}})"
|
||||
[ s, :extract_decimal ]
|
||||
|
||||
# %x
|
||||
when /%\*?[Xx]/
|
||||
[ "([-+]?(?:0[Xx])?#{h}+)", :extract_hex ]
|
||||
|
||||
# %5x
|
||||
when /%\*?(\d+)[Xx]/
|
||||
n = $1.to_i
|
||||
s = "("
|
||||
if n > 3 then s += "[-+]0[Xx]#{h}{1,#{n-3}}|" end
|
||||
if n > 2 then s += "0[Xx]#{h}{1,#{n-2}}|" end
|
||||
if n > 1 then s += "[-+]#{h}{1,#{n-1}}|" end
|
||||
s += "#{h}{1,#{n}}"
|
||||
s += ")"
|
||||
[ s, :extract_hex ]
|
||||
|
||||
# %o
|
||||
when /%\*?o/
|
||||
[ '([-+]?[0-7]+)', :extract_octal ]
|
||||
|
||||
# %5o
|
||||
when /%\*?(\d+)o/
|
||||
[ "([-+][0-7]{1,#{$1.to_i-1}}|[0-7]{1,#{$1}})", :extract_octal ]
|
||||
|
||||
# %f
|
||||
when /%\*?[aefgAEFG]/
|
||||
[ '([-+]?(?:0[xX](?:\.\h+|\h+(?:\.\h*)?)[pP][-+]?\d+|\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))', :extract_float ]
|
||||
|
||||
# %5f
|
||||
when /%\*?(\d+)[aefgAEFG]/
|
||||
[ '(?=[-+]?(?:0[xX](?:\.\h+|\h+(?:\.\h*)?)[pP][-+]?\d+|\d+(?![\d.])|\d*\.\d*(?:[eE][-+]?\d+)?))' +
|
||||
"(\\S{1,#{$1}})", :extract_float ]
|
||||
|
||||
# %5s
|
||||
when /%\*?(\d+)s/
|
||||
[ "(\\S{1,#{$1}})", :extract_plain ]
|
||||
|
||||
# %s
|
||||
when /%\*?s/
|
||||
[ '(\S+)', :extract_plain ]
|
||||
|
||||
# %c
|
||||
when /\s%\*?c/
|
||||
[ "\\s*(.)", :extract_plain ]
|
||||
|
||||
# %c
|
||||
when /%\*?c/
|
||||
[ "(.)", :extract_plain ]
|
||||
|
||||
# %5c (whitespace issues are handled by the count_*_space? methods)
|
||||
when /%\*?(\d+)c/
|
||||
[ "(.{1,#{$1}})", :extract_plain ]
|
||||
|
||||
# %%
|
||||
when /%%/
|
||||
[ '(\s*%)', :nil_proc ]
|
||||
|
||||
# literal characters
|
||||
else
|
||||
[ "(#{Regexp.escape(@spec_string)})", :nil_proc ]
|
||||
end
|
||||
|
||||
@re_string = '\A' + @re_string
|
||||
end
|
||||
|
||||
def to_re
|
||||
Regexp.new(@re_string,Regexp::MULTILINE)
|
||||
end
|
||||
|
||||
def match(str)
|
||||
@matched = false
|
||||
s = str.dup
|
||||
s.sub!(/\A\s+/,'') unless count_space?
|
||||
res = to_re.match(s)
|
||||
if res
|
||||
@conversion = send(@handler, res[1])
|
||||
@matched_string = @conversion.to_s
|
||||
@matched = true
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
def letter
|
||||
@spec_string[/%\*?\d*([a-z\[])/, 1]
|
||||
end
|
||||
|
||||
def width
|
||||
@spec_string[/%\*?(\d+)/, 1]&.to_i
|
||||
end
|
||||
|
||||
def mid_match?
|
||||
return false unless @matched
|
||||
cc_no_width = letter == '[' &&! width
|
||||
c_or_cc_width = (letter == 'c' || letter == '[') && width
|
||||
width_left = c_or_cc_width && (matched_string.size < width)
|
||||
|
||||
return width_left || cc_no_width
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class FormatString
|
||||
|
||||
attr_reader :string_left, :last_spec_tried,
|
||||
:last_match_tried, :matched_count, :space
|
||||
|
||||
SPECIFIERS = 'diuXxofFeEgGscaA'
|
||||
REGEX = /
|
||||
# possible space, followed by...
|
||||
(?:\s*
|
||||
# percent sign, followed by...
|
||||
%
|
||||
# another percent sign, or...
|
||||
(?:%|
|
||||
# optional assignment suppression flag
|
||||
\*?
|
||||
# optional maximum field width
|
||||
\d*
|
||||
# named character class, ...
|
||||
(?:\[\[:\w+:\]\]|
|
||||
# traditional character class, or...
|
||||
\[[^\]]*\]|
|
||||
# specifier letter.
|
||||
[#{SPECIFIERS}])))|
|
||||
# or miscellaneous characters
|
||||
[^%\s]+/ix
|
||||
|
||||
def initialize(str)
|
||||
@specs = []
|
||||
@i = 1
|
||||
s = str.to_s
|
||||
return unless /\S/.match(s)
|
||||
@space = true if /\s\z/.match(s)
|
||||
@specs.replace s.scan(REGEX).map {|spec| FormatSpecifier.new(spec) }
|
||||
end
|
||||
|
||||
def to_s
|
||||
@specs.join('')
|
||||
end
|
||||
|
||||
def prune(n=matched_count)
|
||||
n.times { @specs.shift }
|
||||
end
|
||||
|
||||
def spec_count
|
||||
@specs.size
|
||||
end
|
||||
|
||||
def last_spec
|
||||
@i == spec_count - 1
|
||||
end
|
||||
|
||||
def match(str)
|
||||
accum = []
|
||||
@string_left = str
|
||||
@matched_count = 0
|
||||
|
||||
@specs.each_with_index do |spec,i|
|
||||
@i=i
|
||||
@last_spec_tried = spec
|
||||
@last_match_tried = spec.match(@string_left)
|
||||
break unless @last_match_tried
|
||||
@matched_count += 1
|
||||
|
||||
accum << spec.conversion
|
||||
|
||||
@string_left = @last_match_tried.post_match
|
||||
break if @string_left.empty?
|
||||
end
|
||||
return accum.compact
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
end
|
||||
|
||||
class IO
|
||||
|
||||
#:stopdoc:
|
||||
# The trick here is doing a match where you grab one *line*
|
||||
# of input at a time. The linebreak may or may not occur
|
||||
# at the boundary where the string matches a format specifier.
|
||||
# And if it does, some rule about whitespace may or may not
|
||||
# be in effect...
|
||||
#
|
||||
# That's why this is much more elaborate than the string
|
||||
# version.
|
||||
#
|
||||
# For each line:
|
||||
#
|
||||
# Match succeeds (non-emptily)
|
||||
# and the last attempted spec/string sub-match succeeded:
|
||||
#
|
||||
# could the last spec keep matching?
|
||||
# yes: save interim results and continue (next line)
|
||||
#
|
||||
# The last attempted spec/string did not match:
|
||||
#
|
||||
# are we on the next-to-last spec in the string?
|
||||
# yes:
|
||||
# is fmt_string.string_left all spaces?
|
||||
# yes: does current spec care about input space?
|
||||
# yes: fatal failure
|
||||
# no: save interim results and continue
|
||||
# no: continue [this state could be analyzed further]
|
||||
#
|
||||
#:startdoc:
|
||||
|
||||
# Scans the current string until the match is exhausted,
|
||||
# yielding each match as it is encountered in the string.
|
||||
# A block is not necessary though, as the results will simply
|
||||
# be aggregated into the final array.
|
||||
#
|
||||
# "123 456".block_scanf("%d")
|
||||
# # => [123, 456]
|
||||
#
|
||||
# If a block is given, the value from that is returned from
|
||||
# the yield is added to an output array.
|
||||
#
|
||||
# "123 456".block_scanf("%d") do |digit,| # the ',' unpacks the Array
|
||||
# digit + 100
|
||||
# end
|
||||
# # => [223, 556]
|
||||
#
|
||||
# See Scanf for details on creating a format string.
|
||||
#
|
||||
# You will need to require 'scanf' to use IO#scanf.
|
||||
def scanf(str,&b) #:yield: current_match
|
||||
return block_scanf(str,&b) if b
|
||||
return [] unless str.size > 0
|
||||
|
||||
start_position = pos rescue 0
|
||||
matched_so_far = 0
|
||||
source_buffer = ""
|
||||
result_buffer = []
|
||||
final_result = []
|
||||
|
||||
fstr = Scanf::FormatString.new(str)
|
||||
|
||||
loop do
|
||||
if eof || (tty? &&! fstr.match(source_buffer))
|
||||
final_result.concat(result_buffer)
|
||||
break
|
||||
end
|
||||
|
||||
source_buffer << gets
|
||||
|
||||
current_match = fstr.match(source_buffer)
|
||||
|
||||
spec = fstr.last_spec_tried
|
||||
|
||||
if spec.matched
|
||||
if spec.mid_match?
|
||||
result_buffer.replace(current_match)
|
||||
next
|
||||
end
|
||||
|
||||
elsif (fstr.matched_count == fstr.spec_count - 1)
|
||||
if /\A\s*\z/.match(fstr.string_left)
|
||||
break if spec.count_space?
|
||||
result_buffer.replace(current_match)
|
||||
next
|
||||
end
|
||||
end
|
||||
|
||||
final_result.concat(current_match)
|
||||
|
||||
matched_so_far += source_buffer.size
|
||||
source_buffer.replace(fstr.string_left)
|
||||
matched_so_far -= source_buffer.size
|
||||
break if fstr.last_spec
|
||||
fstr.prune
|
||||
end
|
||||
|
||||
begin
|
||||
seek(start_position + matched_so_far, IO::SEEK_SET)
|
||||
rescue Errno::ESPIPE
|
||||
end
|
||||
|
||||
soak_up_spaces if fstr.last_spec && fstr.space
|
||||
|
||||
return final_result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def soak_up_spaces
|
||||
c = getc
|
||||
ungetc(c) if c
|
||||
until eof ||! c || /\S/.match(c.chr)
|
||||
c = getc
|
||||
end
|
||||
ungetc(c) if (c && /\S/.match(c.chr))
|
||||
end
|
||||
|
||||
def block_scanf(str)
|
||||
final = []
|
||||
# Sub-ideal, since another FS gets created in scanf.
|
||||
# But used here to determine the number of specifiers.
|
||||
fstr = Scanf::FormatString.new(str)
|
||||
last_spec = fstr.last_spec
|
||||
begin
|
||||
current = scanf(str)
|
||||
break if current.empty?
|
||||
final.push(yield(current))
|
||||
end until eof || fstr.last_spec_tried == last_spec
|
||||
return final
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
|
||||
# :section: scanf
|
||||
#
|
||||
# You will need to require 'scanf' to use these methods
|
||||
|
||||
# Scans the current string. If a block is given, it
|
||||
# functions exactly like block_scanf.
|
||||
#
|
||||
# arr = "123 456".scanf("%d%d")
|
||||
# # => [123, 456]
|
||||
#
|
||||
# require 'pp'
|
||||
#
|
||||
# "this 123 read that 456 other".scanf("%s%d%s") {|m| pp m}
|
||||
#
|
||||
# # ["this", 123, "read"]
|
||||
# # ["that", 456, "other"]
|
||||
# # => [["this", 123, "read"], ["that", 456, "other"]]
|
||||
#
|
||||
# See Scanf for details on creating a format string.
|
||||
#
|
||||
# You will need to require 'scanf' to use String#scanf
|
||||
def scanf(fstr,&b) #:yield: current_match
|
||||
if b
|
||||
block_scanf(fstr,&b)
|
||||
else
|
||||
fs =
|
||||
if fstr.is_a? Scanf::FormatString
|
||||
fstr
|
||||
else
|
||||
Scanf::FormatString.new(fstr)
|
||||
end
|
||||
fs.match(self)
|
||||
end
|
||||
end
|
||||
|
||||
# Scans the current string until the match is exhausted
|
||||
# yielding each match as it is encountered in the string.
|
||||
# A block is not necessary as the results will simply
|
||||
# be aggregated into the final array.
|
||||
#
|
||||
# "123 456".block_scanf("%d")
|
||||
# # => [123, 456]
|
||||
#
|
||||
# If a block is given, the value from that is returned from
|
||||
# the yield is added to an output array.
|
||||
#
|
||||
# "123 456".block_scanf("%d) do |digit,| # the ',' unpacks the Array
|
||||
# digit + 100
|
||||
# end
|
||||
# # => [223, 556]
|
||||
#
|
||||
# See Scanf for details on creating a format string.
|
||||
#
|
||||
# You will need to require 'scanf' to use String#block_scanf
|
||||
def block_scanf(fstr) #:yield: current_match
|
||||
fs = Scanf::FormatString.new(fstr)
|
||||
str = self.dup
|
||||
final = []
|
||||
begin
|
||||
current = str.scanf(fs)
|
||||
final.push(yield(current)) unless current.empty?
|
||||
str = fs.string_left
|
||||
end until current.empty? || str.empty?
|
||||
return final
|
||||
end
|
||||
end
|
||||
|
||||
module Kernel
|
||||
private
|
||||
# Scans STDIN for data matching +format+. See IO#scanf for details.
|
||||
#
|
||||
# See Scanf for details on creating a format string.
|
||||
#
|
||||
# You will need to require 'scanf' to use Kernel#scanf.
|
||||
def scanf(format, &b) #:doc:
|
||||
STDIN.scanf(format ,&b)
|
||||
end
|
||||
end
|
||||
231
gxg/standard/shellwords.rb
Normal file
231
gxg/standard/shellwords.rb
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# frozen-string-literal: true
|
||||
##
|
||||
# == Manipulates strings like the UNIX Bourne shell
|
||||
#
|
||||
# This module manipulates strings according to the word parsing rules
|
||||
# of the UNIX Bourne shell.
|
||||
#
|
||||
# The shellwords() function was originally a port of shellwords.pl,
|
||||
# but modified to conform to the Shell & Utilities volume of the IEEE
|
||||
# Std 1003.1-2008, 2016 Edition [1].
|
||||
#
|
||||
# === Usage
|
||||
#
|
||||
# You can use Shellwords to parse a string into a Bourne shell friendly Array.
|
||||
#
|
||||
# require 'shellwords'
|
||||
#
|
||||
# argv = Shellwords.split('three blind "mice"')
|
||||
# argv #=> ["three", "blind", "mice"]
|
||||
#
|
||||
# Once you've required Shellwords, you can use the #split alias
|
||||
# String#shellsplit.
|
||||
#
|
||||
# argv = "see how they run".shellsplit
|
||||
# argv #=> ["see", "how", "they", "run"]
|
||||
#
|
||||
# Be careful you don't leave a quote unmatched.
|
||||
#
|
||||
# argv = "they all ran after the farmer's wife".shellsplit
|
||||
# #=> ArgumentError: Unmatched double quote: ...
|
||||
#
|
||||
# In this case, you might want to use Shellwords.escape, or its alias
|
||||
# String#shellescape.
|
||||
#
|
||||
# This method will escape the String for you to safely use with a Bourne shell.
|
||||
#
|
||||
# argv = Shellwords.escape("special's.txt")
|
||||
# argv #=> "special\\'s.txt"
|
||||
# system("cat " + argv)
|
||||
#
|
||||
# Shellwords also comes with a core extension for Array, Array#shelljoin.
|
||||
#
|
||||
# argv = %w{ls -lta lib}
|
||||
# system(argv.shelljoin)
|
||||
#
|
||||
# You can use this method to create an escaped string out of an array of tokens
|
||||
# separated by a space. In this example we used the literal shortcut for
|
||||
# Array.new.
|
||||
#
|
||||
# === Authors
|
||||
# * Wakou Aoyama
|
||||
# * Akinori MUSHA <knu@iDaemons.org>
|
||||
#
|
||||
# === Contact
|
||||
# * Akinori MUSHA <knu@iDaemons.org> (current maintainer)
|
||||
#
|
||||
# === Resources
|
||||
#
|
||||
# 1: {IEEE Std 1003.1-2008, 2016 Edition, the Shell & Utilities volume}[http://pubs.opengroup.org/onlinepubs/9699919799/utilities/contents.html]
|
||||
|
||||
module Shellwords
|
||||
# Splits a string into an array of tokens in the same way the UNIX
|
||||
# Bourne shell does.
|
||||
#
|
||||
# argv = Shellwords.split('here are "two words"')
|
||||
# argv #=> ["here", "are", "two words"]
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# argv = Shellwords.split('ruby my_prog.rb | less')
|
||||
# argv #=> ["ruby", "my_prog.rb", "|", "less"]
|
||||
#
|
||||
# String#shellsplit is a shortcut for this function.
|
||||
#
|
||||
# argv = 'here are "two words"'.shellsplit
|
||||
# argv #=> ["here", "are", "two words"]
|
||||
def shellsplit(line)
|
||||
words = []
|
||||
field = String.new
|
||||
line.scan(/\G\s*(?>([^\s\\\'\"]+)|'([^\']*)'|"((?:[^\"\\]|\\.)*)"|(\\.?)|(\S))(\s|\z)?/m) do
|
||||
|word, sq, dq, esc, garbage, sep|
|
||||
raise ArgumentError, "Unmatched double quote: #{line.inspect}" if garbage
|
||||
# 2.2.3 Double-Quotes:
|
||||
#
|
||||
# The <backslash> shall retain its special meaning as an
|
||||
# escape character only when followed by one of the following
|
||||
# characters when considered special:
|
||||
#
|
||||
# $ ` " \ <newline>
|
||||
field << (word || sq || (dq && dq.gsub(/\\([$`"\\\n])/, '\\1')) || esc.gsub(/\\(.)/, '\\1'))
|
||||
if sep
|
||||
words << field
|
||||
field = String.new
|
||||
end
|
||||
end
|
||||
words
|
||||
end
|
||||
|
||||
alias shellwords shellsplit
|
||||
|
||||
module_function :shellsplit, :shellwords
|
||||
|
||||
class << self
|
||||
alias split shellsplit
|
||||
end
|
||||
|
||||
# Escapes a string so that it can be safely used in a Bourne shell
|
||||
# command line. +str+ can be a non-string object that responds to
|
||||
# +to_s+.
|
||||
#
|
||||
# Note that a resulted string should be used unquoted and is not
|
||||
# intended for use in double quotes nor in single quotes.
|
||||
#
|
||||
# argv = Shellwords.escape("It's better to give than to receive")
|
||||
# argv #=> "It\\'s\\ better\\ to\\ give\\ than\\ to\\ receive"
|
||||
#
|
||||
# String#shellescape is a shorthand for this function.
|
||||
#
|
||||
# argv = "It's better to give than to receive".shellescape
|
||||
# argv #=> "It\\'s\\ better\\ to\\ give\\ than\\ to\\ receive"
|
||||
#
|
||||
# # Search files in lib for method definitions
|
||||
# pattern = "^[ \t]*def "
|
||||
# open("| grep -Ern #{pattern.shellescape} lib") { |grep|
|
||||
# grep.each_line { |line|
|
||||
# file, lineno, matched_line = line.split(':', 3)
|
||||
# # ...
|
||||
# }
|
||||
# }
|
||||
#
|
||||
# It is the caller's responsibility to encode the string in the right
|
||||
# encoding for the shell environment where this string is used.
|
||||
#
|
||||
# Multibyte characters are treated as multibyte characters, not as bytes.
|
||||
#
|
||||
# Returns an empty quoted String if +str+ has a length of zero.
|
||||
def shellescape(str)
|
||||
str = str.to_s
|
||||
|
||||
# An empty argument will be skipped, so return empty quotes.
|
||||
return "''".dup if str.empty?
|
||||
|
||||
str = str.dup
|
||||
|
||||
# Treat multibyte characters as is. It is the caller's responsibility
|
||||
# to encode the string in the right encoding for the shell
|
||||
# environment.
|
||||
str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
|
||||
|
||||
# A LF cannot be escaped with a backslash because a backslash + LF
|
||||
# combo is regarded as a line continuation and simply ignored.
|
||||
str.gsub!(/\n/, "'\n'")
|
||||
|
||||
return str
|
||||
end
|
||||
|
||||
module_function :shellescape
|
||||
|
||||
class << self
|
||||
alias escape shellescape
|
||||
end
|
||||
|
||||
# Builds a command line string from an argument list, +array+.
|
||||
#
|
||||
# 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+.
|
||||
#
|
||||
# ary = ["There's", "a", "time", "and", "place", "for", "everything"]
|
||||
# argv = Shellwords.join(ary)
|
||||
# argv #=> "There\\'s a time and place for everything"
|
||||
#
|
||||
# Array#shelljoin is a shortcut for this function.
|
||||
#
|
||||
# ary = ["Don't", "rock", "the", "boat"]
|
||||
# argv = ary.shelljoin
|
||||
# argv #=> "Don\\'t rock the boat"
|
||||
#
|
||||
# You can also mix non-string objects in the elements as allowed in Array#join.
|
||||
#
|
||||
# output = `#{['ps', '-p', $$].shelljoin}`
|
||||
#
|
||||
def shelljoin(array)
|
||||
array.map { |arg| shellescape(arg) }.join(' ')
|
||||
end
|
||||
|
||||
module_function :shelljoin
|
||||
|
||||
class << self
|
||||
alias join shelljoin
|
||||
end
|
||||
end
|
||||
|
||||
class String
|
||||
# call-seq:
|
||||
# str.shellsplit => array
|
||||
#
|
||||
# Splits +str+ into an array of tokens in the same way the UNIX
|
||||
# Bourne shell does.
|
||||
#
|
||||
# See Shellwords.shellsplit for details.
|
||||
def shellsplit
|
||||
Shellwords.split(self)
|
||||
end
|
||||
|
||||
# call-seq:
|
||||
# str.shellescape => string
|
||||
#
|
||||
# Escapes +str+ so that it can be safely used in a Bourne shell
|
||||
# command line.
|
||||
#
|
||||
# See Shellwords.shellescape for details.
|
||||
def shellescape
|
||||
Shellwords.escape(self)
|
||||
end
|
||||
end
|
||||
|
||||
class Array
|
||||
# call-seq:
|
||||
# array.shelljoin => string
|
||||
#
|
||||
# Builds a command line string from an argument list +array+ joining
|
||||
# all elements escaped for the Bourne shell and separated by a space.
|
||||
#
|
||||
# See Shellwords.shelljoin for details.
|
||||
def shelljoin
|
||||
Shellwords.join(self)
|
||||
end
|
||||
end
|
||||
329
gxg/standard/sync.rb
Normal file
329
gxg/standard/sync.rb
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# sync.rb - 2 phase lock with counter
|
||||
# $Release Version: 1.0$
|
||||
# $Revision: 56183 $
|
||||
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
|
||||
#
|
||||
# --
|
||||
# Sync_m, Synchronizer_m
|
||||
# Usage:
|
||||
# obj.extend(Sync_m)
|
||||
# or
|
||||
# class Foo
|
||||
# include Sync_m
|
||||
# :
|
||||
# end
|
||||
#
|
||||
# Sync_m#sync_mode
|
||||
# Sync_m#sync_locked?, locked?
|
||||
# Sync_m#sync_shared?, shared?
|
||||
# Sync_m#sync_exclusive?, sync_exclusive?
|
||||
# Sync_m#sync_try_lock, try_lock
|
||||
# Sync_m#sync_lock, lock
|
||||
# Sync_m#sync_unlock, unlock
|
||||
#
|
||||
# Sync, Synchronizer:
|
||||
# Usage:
|
||||
# sync = Sync.new
|
||||
#
|
||||
# Sync#mode
|
||||
# Sync#locked?
|
||||
# Sync#shared?
|
||||
# Sync#exclusive?
|
||||
# Sync#try_lock(mode) -- mode = :EX, :SH, :UN
|
||||
# Sync#lock(mode) -- mode = :EX, :SH, :UN
|
||||
# Sync#unlock
|
||||
# Sync#synchronize(mode) {...}
|
||||
#
|
||||
#
|
||||
|
||||
unless defined? Thread
|
||||
raise "Thread not available for this ruby interpreter"
|
||||
end
|
||||
|
||||
##
|
||||
# A module that provides a two-phase lock with a counter.
|
||||
|
||||
module Sync_m
|
||||
# lock mode
|
||||
UN = :UN
|
||||
SH = :SH
|
||||
EX = :EX
|
||||
|
||||
# exceptions
|
||||
class Err < StandardError
|
||||
def Err.Fail(*opt)
|
||||
fail self, sprintf(self::Message, *opt)
|
||||
end
|
||||
|
||||
class UnknownLocker < Err
|
||||
Message = "Thread(%s) not locked."
|
||||
def UnknownLocker.Fail(th)
|
||||
super(th.inspect)
|
||||
end
|
||||
end
|
||||
|
||||
class LockModeFailer < Err
|
||||
Message = "Unknown lock mode(%s)"
|
||||
def LockModeFailer.Fail(mode)
|
||||
if mode.id2name
|
||||
mode = mode.id2name
|
||||
end
|
||||
super(mode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def Sync_m.define_aliases(cl)
|
||||
cl.module_eval %q{
|
||||
alias locked? sync_locked?
|
||||
alias shared? sync_shared?
|
||||
alias exclusive? sync_exclusive?
|
||||
alias lock sync_lock
|
||||
alias unlock sync_unlock
|
||||
alias try_lock sync_try_lock
|
||||
alias synchronize sync_synchronize
|
||||
}
|
||||
end
|
||||
|
||||
def Sync_m.append_features(cl)
|
||||
super
|
||||
# do nothing for Modules
|
||||
# make aliases for Classes.
|
||||
define_aliases(cl) unless cl.instance_of?(Module)
|
||||
self
|
||||
end
|
||||
|
||||
def Sync_m.extend_object(obj)
|
||||
super
|
||||
obj.sync_extend
|
||||
end
|
||||
|
||||
def sync_extend
|
||||
unless (defined? locked? and
|
||||
defined? shared? and
|
||||
defined? exclusive? and
|
||||
defined? lock and
|
||||
defined? unlock and
|
||||
defined? try_lock and
|
||||
defined? synchronize)
|
||||
Sync_m.define_aliases(singleton_class)
|
||||
end
|
||||
sync_initialize
|
||||
end
|
||||
|
||||
# accessing
|
||||
def sync_locked?
|
||||
sync_mode != UN
|
||||
end
|
||||
|
||||
def sync_shared?
|
||||
sync_mode == SH
|
||||
end
|
||||
|
||||
def sync_exclusive?
|
||||
sync_mode == EX
|
||||
end
|
||||
|
||||
# locking methods.
|
||||
def sync_try_lock(mode = EX)
|
||||
return unlock if mode == UN
|
||||
@sync_mutex.synchronize do
|
||||
sync_try_lock_sub(mode)
|
||||
end
|
||||
end
|
||||
|
||||
def sync_lock(m = EX)
|
||||
return unlock if m == UN
|
||||
Thread.handle_interrupt(StandardError => :on_blocking) do
|
||||
while true
|
||||
@sync_mutex.synchronize do
|
||||
begin
|
||||
if sync_try_lock_sub(m)
|
||||
return self
|
||||
else
|
||||
if sync_sh_locker[Thread.current]
|
||||
sync_upgrade_waiting.push [Thread.current, sync_sh_locker[Thread.current]]
|
||||
sync_sh_locker.delete(Thread.current)
|
||||
else
|
||||
unless sync_waiting.include?(Thread.current) || sync_upgrade_waiting.reverse_each.any?{|w| w.first == Thread.current }
|
||||
sync_waiting.push Thread.current
|
||||
end
|
||||
end
|
||||
@sync_mutex.sleep
|
||||
end
|
||||
ensure
|
||||
sync_waiting.delete(Thread.current)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def sync_unlock(m = EX)
|
||||
wakeup_threads = []
|
||||
@sync_mutex.synchronize do
|
||||
if sync_mode == UN
|
||||
Err::UnknownLocker.Fail(Thread.current)
|
||||
end
|
||||
|
||||
m = sync_mode if m == EX and sync_mode == SH
|
||||
|
||||
runnable = false
|
||||
case m
|
||||
when UN
|
||||
Err::UnknownLocker.Fail(Thread.current)
|
||||
|
||||
when EX
|
||||
if sync_ex_locker == Thread.current
|
||||
if (self.sync_ex_count = sync_ex_count - 1) == 0
|
||||
self.sync_ex_locker = nil
|
||||
if sync_sh_locker.include?(Thread.current)
|
||||
self.sync_mode = SH
|
||||
else
|
||||
self.sync_mode = UN
|
||||
end
|
||||
runnable = true
|
||||
end
|
||||
else
|
||||
Err::UnknownLocker.Fail(Thread.current)
|
||||
end
|
||||
|
||||
when SH
|
||||
if (count = sync_sh_locker[Thread.current]).nil?
|
||||
Err::UnknownLocker.Fail(Thread.current)
|
||||
else
|
||||
if (sync_sh_locker[Thread.current] = count - 1) == 0
|
||||
sync_sh_locker.delete(Thread.current)
|
||||
if sync_sh_locker.empty? and sync_ex_count == 0
|
||||
self.sync_mode = UN
|
||||
runnable = true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if runnable
|
||||
if sync_upgrade_waiting.size > 0
|
||||
th, count = sync_upgrade_waiting.shift
|
||||
sync_sh_locker[th] = count
|
||||
th.wakeup
|
||||
wakeup_threads.push th
|
||||
else
|
||||
wait = sync_waiting
|
||||
self.sync_waiting = []
|
||||
for th in wait
|
||||
th.wakeup
|
||||
wakeup_threads.push th
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
for th in wakeup_threads
|
||||
th.run
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def sync_synchronize(mode = EX)
|
||||
Thread.handle_interrupt(StandardError => :on_blocking) do
|
||||
sync_lock(mode)
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
sync_unlock
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :sync_mode
|
||||
|
||||
attr_accessor :sync_waiting
|
||||
attr_accessor :sync_upgrade_waiting
|
||||
attr_accessor :sync_sh_locker
|
||||
attr_accessor :sync_ex_locker
|
||||
attr_accessor :sync_ex_count
|
||||
|
||||
def sync_inspect
|
||||
sync_iv = instance_variables.select{|iv| /^@sync_/ =~ iv.id2name}.collect{|iv| iv.id2name + '=' + instance_eval(iv.id2name).inspect}.join(",")
|
||||
print "<#{self.class}.extend Sync_m: #{inspect}, <Sync_m: #{sync_iv}>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sync_initialize
|
||||
@sync_mode = UN
|
||||
@sync_waiting = []
|
||||
@sync_upgrade_waiting = []
|
||||
@sync_sh_locker = Hash.new
|
||||
@sync_ex_locker = nil
|
||||
@sync_ex_count = 0
|
||||
|
||||
@sync_mutex = Thread::Mutex.new
|
||||
end
|
||||
|
||||
def initialize(*args)
|
||||
super
|
||||
sync_initialize
|
||||
end
|
||||
|
||||
def sync_try_lock_sub(m)
|
||||
case m
|
||||
when SH
|
||||
case sync_mode
|
||||
when UN
|
||||
self.sync_mode = m
|
||||
sync_sh_locker[Thread.current] = 1
|
||||
ret = true
|
||||
when SH
|
||||
count = 0 unless count = sync_sh_locker[Thread.current]
|
||||
sync_sh_locker[Thread.current] = count + 1
|
||||
ret = true
|
||||
when EX
|
||||
# in EX mode, lock will upgrade to EX lock
|
||||
if sync_ex_locker == Thread.current
|
||||
self.sync_ex_count = sync_ex_count + 1
|
||||
ret = true
|
||||
else
|
||||
ret = false
|
||||
end
|
||||
end
|
||||
when EX
|
||||
if sync_mode == UN or
|
||||
sync_mode == SH && sync_sh_locker.size == 1 && sync_sh_locker.include?(Thread.current)
|
||||
self.sync_mode = m
|
||||
self.sync_ex_locker = Thread.current
|
||||
self.sync_ex_count = 1
|
||||
ret = true
|
||||
elsif sync_mode == EX && sync_ex_locker == Thread.current
|
||||
self.sync_ex_count = sync_ex_count + 1
|
||||
ret = true
|
||||
else
|
||||
ret = false
|
||||
end
|
||||
else
|
||||
Err::LockModeFailer.Fail m
|
||||
end
|
||||
return ret
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# An alias for Sync_m from sync.rb
|
||||
|
||||
Synchronizer_m = Sync_m
|
||||
|
||||
##
|
||||
# A class that provides two-phase lock with a counter. See Sync_m for
|
||||
# details.
|
||||
|
||||
class Sync
|
||||
include Sync_m
|
||||
end
|
||||
|
||||
##
|
||||
# An alias for Sync from sync.rb. See Sync_m for details.
|
||||
|
||||
Synchronizer = Sync
|
||||
209
gxg/standard/syslog/logger.rb
Normal file
209
gxg/standard/syslog/logger.rb
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
# frozen_string_literal: false
|
||||
require 'syslog'
|
||||
require 'logger'
|
||||
|
||||
##
|
||||
# Syslog::Logger is a Logger work-alike that logs via syslog instead of to a
|
||||
# file. You can use Syslog::Logger to aggregate logs between multiple
|
||||
# machines.
|
||||
#
|
||||
# By default, Syslog::Logger uses the program name 'ruby', but this can be
|
||||
# changed via the first argument to Syslog::Logger.new.
|
||||
#
|
||||
# NOTE! You can only set the Syslog::Logger program name when you initialize
|
||||
# Syslog::Logger for the first time. This is a limitation of the way
|
||||
# Syslog::Logger uses syslog (and in some ways, a limitation of the way
|
||||
# syslog(3) works). Attempts to change Syslog::Logger's program name after
|
||||
# the first initialization will be ignored.
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# The following will log to syslogd on your local machine:
|
||||
#
|
||||
# require 'syslog/logger'
|
||||
#
|
||||
# log = Syslog::Logger.new 'my_program'
|
||||
# log.info 'this line will be logged via syslog(3)'
|
||||
#
|
||||
# Also the facility may be set to specify the facility level which will be used:
|
||||
#
|
||||
# log.info 'this line will be logged using Syslog default facility level'
|
||||
#
|
||||
# log_local1 = Syslog::Logger.new 'my_program', Syslog::LOG_LOCAL1
|
||||
# log_local1.info 'this line will be logged using local1 facility level'
|
||||
#
|
||||
#
|
||||
# You may need to perform some syslog.conf setup first. For a BSD machine add
|
||||
# the following lines to /etc/syslog.conf:
|
||||
#
|
||||
# !my_program
|
||||
# *.* /var/log/my_program.log
|
||||
#
|
||||
# Then touch /var/log/my_program.log and signal syslogd with a HUP
|
||||
# (killall -HUP syslogd, on FreeBSD).
|
||||
#
|
||||
# If you wish to have logs automatically roll over and archive, see the
|
||||
# newsyslog.conf(5) and newsyslog(8) man pages.
|
||||
|
||||
class Syslog::Logger
|
||||
# Default formatter for log messages.
|
||||
class Formatter
|
||||
def call severity, time, progname, msg
|
||||
clean msg
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
##
|
||||
# Clean up messages so they're nice and pretty.
|
||||
|
||||
def clean message
|
||||
message = message.to_s.strip
|
||||
message.gsub!(/\e\[[0-9;]*m/, '') # remove useless ansi color codes
|
||||
return message
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# The version of Syslog::Logger you are using.
|
||||
|
||||
VERSION = '2.1.0'
|
||||
|
||||
##
|
||||
# Maps Logger warning types to syslog(3) warning types.
|
||||
#
|
||||
# Messages from Ruby applications are not considered as critical as messages
|
||||
# from other system daemons using syslog(3), so most messages are reduced by
|
||||
# one level. For example, a fatal message for Ruby's Logger is considered
|
||||
# an error for syslog(3).
|
||||
|
||||
LEVEL_MAP = {
|
||||
::Logger::UNKNOWN => Syslog::LOG_ALERT,
|
||||
::Logger::FATAL => Syslog::LOG_ERR,
|
||||
::Logger::ERROR => Syslog::LOG_WARNING,
|
||||
::Logger::WARN => Syslog::LOG_NOTICE,
|
||||
::Logger::INFO => Syslog::LOG_INFO,
|
||||
::Logger::DEBUG => Syslog::LOG_DEBUG,
|
||||
}
|
||||
|
||||
##
|
||||
# Returns the internal Syslog object that is initialized when the
|
||||
# first instance is created.
|
||||
|
||||
def self.syslog
|
||||
@@syslog
|
||||
end
|
||||
|
||||
##
|
||||
# Specifies the internal Syslog object to be used.
|
||||
|
||||
def self.syslog= syslog
|
||||
@@syslog = syslog
|
||||
end
|
||||
|
||||
##
|
||||
# Builds a methods for level +meth+.
|
||||
|
||||
def self.make_methods meth
|
||||
level = ::Logger.const_get(meth.upcase)
|
||||
eval <<-EOM, nil, __FILE__, __LINE__ + 1
|
||||
def #{meth}(message = nil, &block)
|
||||
add(#{level}, message, &block)
|
||||
end
|
||||
|
||||
def #{meth}?
|
||||
@level <= #{level}
|
||||
end
|
||||
EOM
|
||||
end
|
||||
|
||||
##
|
||||
# :method: unknown
|
||||
#
|
||||
# Logs a +message+ at the unknown (syslog alert) log level, or logs the
|
||||
# message returned from the block.
|
||||
|
||||
##
|
||||
# :method: fatal
|
||||
#
|
||||
# Logs a +message+ at the fatal (syslog err) log level, or logs the message
|
||||
# returned from the block.
|
||||
|
||||
##
|
||||
# :method: error
|
||||
#
|
||||
# Logs a +message+ at the error (syslog warning) log level, or logs the
|
||||
# message returned from the block.
|
||||
|
||||
##
|
||||
# :method: warn
|
||||
#
|
||||
# Logs a +message+ at the warn (syslog notice) log level, or logs the
|
||||
# message returned from the block.
|
||||
|
||||
##
|
||||
# :method: info
|
||||
#
|
||||
# Logs a +message+ at the info (syslog info) log level, or logs the message
|
||||
# returned from the block.
|
||||
|
||||
##
|
||||
# :method: debug
|
||||
#
|
||||
# Logs a +message+ at the debug (syslog debug) log level, or logs the
|
||||
# message returned from the block.
|
||||
|
||||
Logger::Severity::constants.each do |severity|
|
||||
make_methods severity.downcase
|
||||
end
|
||||
|
||||
##
|
||||
# Log level for Logger compatibility.
|
||||
|
||||
attr_accessor :level
|
||||
|
||||
# Logging formatter, as a +Proc+ that will take four arguments and
|
||||
# return the formatted message. The arguments are:
|
||||
#
|
||||
# +severity+:: The Severity of the log message.
|
||||
# +time+:: A Time instance representing when the message was logged.
|
||||
# +progname+:: The #progname configured, or passed to the logger method.
|
||||
# +msg+:: The _Object_ the user passed to the log message; not necessarily a
|
||||
# String.
|
||||
#
|
||||
# The block should return an Object that can be written to the logging
|
||||
# device via +write+. The default formatter is used when no formatter is
|
||||
# set.
|
||||
attr_accessor :formatter
|
||||
|
||||
##
|
||||
# The facility argument is used to specify what type of program is logging the message.
|
||||
|
||||
attr_accessor :facility
|
||||
|
||||
##
|
||||
# Fills in variables for Logger compatibility. If this is the first
|
||||
# instance of Syslog::Logger, +program_name+ may be set to change the logged
|
||||
# program name. The +facility+ may be set to specify the facility level which will be used.
|
||||
#
|
||||
# Due to the way syslog works, only one program name may be chosen.
|
||||
|
||||
def initialize program_name = 'ruby', facility = nil
|
||||
@level = ::Logger::DEBUG
|
||||
@formatter = Formatter.new
|
||||
|
||||
@@syslog ||= Syslog.open(program_name)
|
||||
|
||||
@facility = (facility || @@syslog.facility)
|
||||
end
|
||||
|
||||
##
|
||||
# Almost duplicates Logger#add. +progname+ is ignored.
|
||||
|
||||
def add severity, message = nil, progname = nil, &block
|
||||
severity ||= ::Logger::UNKNOWN
|
||||
@level <= severity and
|
||||
@@syslog.log( (LEVEL_MAP[severity] | @facility), '%s', formatter.call(severity, Time.now, progname, (message || block.call)) )
|
||||
true
|
||||
end
|
||||
end
|
||||
140
gxg/standard/thwait.rb
Normal file
140
gxg/standard/thwait.rb
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
# frozen_string_literal: false
|
||||
#
|
||||
# thwait.rb - thread synchronization class
|
||||
# $Release Version: 0.9 $
|
||||
# $Revision: 1.3 $
|
||||
# by Keiju ISHITSUKA(Nihon Rational Software Co.,Ltd.)
|
||||
|
||||
require "e2mmap.rb"
|
||||
|
||||
#
|
||||
# This class watches for termination of multiple threads. Basic functionality
|
||||
# (wait until specified threads have terminated) can be accessed through the
|
||||
# class method ThreadsWait::all_waits. Finer control can be gained using
|
||||
# instance methods.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# ThreadsWait.all_waits(thr1, thr2, ...) do |t|
|
||||
# STDERR.puts "Thread #{t} has terminated."
|
||||
# end
|
||||
#
|
||||
#
|
||||
# th = ThreadsWait.new(thread1,...)
|
||||
# th.next_wait # next one to be done
|
||||
#
|
||||
#
|
||||
class ThreadsWait
|
||||
extend Exception2MessageMapper
|
||||
def_exception("ErrNoWaitingThread", "No threads for waiting.")
|
||||
def_exception("ErrNoFinishedThread", "No finished threads.")
|
||||
|
||||
#
|
||||
# Waits until all specified threads have terminated. If a block is provided,
|
||||
# it is executed for each thread as they terminate.
|
||||
#
|
||||
def ThreadsWait.all_waits(*threads) # :yield: thread
|
||||
tw = ThreadsWait.new(*threads)
|
||||
if block_given?
|
||||
tw.all_waits do |th|
|
||||
yield th
|
||||
end
|
||||
else
|
||||
tw.all_waits
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Creates a ThreadsWait object, specifying the threads to wait on.
|
||||
# Non-blocking.
|
||||
#
|
||||
def initialize(*threads)
|
||||
@threads = []
|
||||
@wait_queue = Thread::Queue.new
|
||||
join_nowait(*threads) unless threads.empty?
|
||||
end
|
||||
|
||||
# Returns the array of threads that have not terminated yet.
|
||||
attr_reader :threads
|
||||
|
||||
#
|
||||
# Returns +true+ if there are no threads in the pool still running.
|
||||
#
|
||||
def empty?
|
||||
@threads.empty?
|
||||
end
|
||||
|
||||
#
|
||||
# Returns +true+ if any thread has terminated and is ready to be collected.
|
||||
#
|
||||
def finished?
|
||||
!@wait_queue.empty?
|
||||
end
|
||||
|
||||
#
|
||||
# Waits for specified threads to terminate, and returns when one of
|
||||
# the threads terminated.
|
||||
#
|
||||
def join(*threads)
|
||||
join_nowait(*threads)
|
||||
next_wait
|
||||
end
|
||||
|
||||
#
|
||||
# Specifies the threads that this object will wait for, but does not actually
|
||||
# wait.
|
||||
#
|
||||
def join_nowait(*threads)
|
||||
threads.flatten!
|
||||
@threads.concat threads
|
||||
for th in threads
|
||||
Thread.start(th) do |t|
|
||||
begin
|
||||
t.join
|
||||
ensure
|
||||
@wait_queue.push t
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Waits until any of the specified threads has terminated, and returns the one
|
||||
# that does.
|
||||
#
|
||||
# If there is no thread to wait, raises +ErrNoWaitingThread+. If +nonblock+
|
||||
# is true, and there is no terminated thread, raises +ErrNoFinishedThread+.
|
||||
#
|
||||
def next_wait(nonblock = nil)
|
||||
ThreadsWait.fail ErrNoWaitingThread if @threads.empty?
|
||||
begin
|
||||
@threads.delete(th = @wait_queue.pop(nonblock))
|
||||
th
|
||||
rescue ThreadError
|
||||
ThreadsWait.fail ErrNoFinishedThread
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Waits until all of the specified threads are terminated. If a block is
|
||||
# supplied for the method, it is executed for each thread termination.
|
||||
#
|
||||
# Raises exceptions in the same manner as +next_wait+.
|
||||
#
|
||||
def all_waits
|
||||
until @threads.empty?
|
||||
th = next_wait
|
||||
yield th if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# An alias for ThreadsWait from thwait.rb
|
||||
|
||||
ThWait = ThreadsWait
|
||||
|
||||
# Documentation comments:
|
||||
# - Source of documentation is evenly split between Nutshell, existing
|
||||
# comments, and my own rephrasing.
|
||||
# - I'm not particularly confident that the comments are all exactly correct.
|
||||
130
gxg/standard/timeout.rb
Normal file
130
gxg/standard/timeout.rb
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
# frozen_string_literal: false
|
||||
# Timeout long-running blocks
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# require 'timeout'
|
||||
# status = Timeout::timeout(5) {
|
||||
# # Something that should be interrupted if it takes more than 5 seconds...
|
||||
# }
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# 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 Timeout.timeout instead.
|
||||
#
|
||||
# == Copyright
|
||||
#
|
||||
# Copyright:: (C) 2000 Network Applied Communication Laboratory, Inc.
|
||||
# Copyright:: (C) 2000 Information-technology Promotion Agency, Japan
|
||||
|
||||
module Timeout
|
||||
# Raised by Timeout.timeout when the block times out.
|
||||
class Error < RuntimeError
|
||||
attr_reader :thread
|
||||
|
||||
def self.catch(*args)
|
||||
exc = new(*args)
|
||||
exc.instance_variable_set(:@thread, Thread.current)
|
||||
::Kernel.catch(exc) {yield exc}
|
||||
end
|
||||
|
||||
def exception(*)
|
||||
# TODO: use Fiber.current to see if self can be thrown
|
||||
if self.thread == Thread.current
|
||||
bt = caller
|
||||
begin
|
||||
throw(self, bt)
|
||||
rescue UncaughtThrowError
|
||||
end
|
||||
end
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
THIS_FILE = /\A#{Regexp.quote(__FILE__)}:/o
|
||||
CALLER_OFFSET = ((c = caller[0]) && THIS_FILE =~ c) ? 1 : 0
|
||||
private_constant :THIS_FILE, :CALLER_OFFSET
|
||||
# :startdoc:
|
||||
|
||||
# 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
|
||||
# value of 0 or +nil+ will execute the block without any timeout.
|
||||
# +klass+:: Exception Class to raise if the block fails to terminate
|
||||
# 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"
|
||||
#
|
||||
# Returns the result of the block *if* the block completed before
|
||||
# +sec+ seconds, otherwise throws 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.
|
||||
#
|
||||
# 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) #:yield: +sec+
|
||||
return yield(sec) if sec == nil or sec.zero?
|
||||
message ||= "execution expired".freeze
|
||||
from = "from #{caller_locations(1, 1)[0]}" if $DEBUG
|
||||
e = Error
|
||||
bl = proc do |exception|
|
||||
begin
|
||||
x = Thread.current
|
||||
y = Thread.start {
|
||||
Thread.current.name = from
|
||||
begin
|
||||
sleep sec
|
||||
rescue => e
|
||||
x.raise e
|
||||
else
|
||||
x.raise exception, message
|
||||
end
|
||||
}
|
||||
return yield(sec)
|
||||
ensure
|
||||
if y
|
||||
y.kill
|
||||
y.join # make sure y is dead.
|
||||
end
|
||||
end
|
||||
end
|
||||
if klass
|
||||
begin
|
||||
bl.call(klass)
|
||||
rescue klass => e
|
||||
bt = e.backtrace
|
||||
end
|
||||
else
|
||||
bt = Error.catch(message, &bl)
|
||||
end
|
||||
level = -caller(CALLER_OFFSET).size-2
|
||||
while THIS_FILE =~ bt[level]
|
||||
bt.delete_at(level)
|
||||
end
|
||||
raise(e, message, bt)
|
||||
end
|
||||
|
||||
module_function :timeout
|
||||
end
|
||||
|
||||
def timeout(*args, &block)
|
||||
warn "Object##{__method__} is deprecated, use Timeout.timeout instead.", uplevel: 1
|
||||
Timeout.timeout(*args, &block)
|
||||
end
|
||||
|
||||
# Another name for Timeout::Error, defined for backwards compatibility with
|
||||
# earlier versions of timeout.rb.
|
||||
TimeoutError = Timeout::Error
|
||||
class Object
|
||||
deprecate_constant :TimeoutError
|
||||
end
|
||||
286
gxg/standard/tracer.rb
Normal file
286
gxg/standard/tracer.rb
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# $Release Version: 0.3$
|
||||
# $Revision: 1.12 $
|
||||
|
||||
##
|
||||
# Outputs a source level execution trace of a Ruby program.
|
||||
#
|
||||
# It does this by registering an event handler with Kernel#set_trace_func for
|
||||
# processing incoming events. It also provides methods for filtering unwanted
|
||||
# trace output (see Tracer.add_filter, Tracer.on, and Tracer.off).
|
||||
#
|
||||
# == Example
|
||||
#
|
||||
# Consider the following Ruby script
|
||||
#
|
||||
# class A
|
||||
# def square(a)
|
||||
# return a*a
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# a = A.new
|
||||
# a.square(5)
|
||||
#
|
||||
# Running the above script using <code>ruby -r tracer example.rb</code> will
|
||||
# output the following trace to STDOUT (Note you can also explicitly
|
||||
# <code>require 'tracer'</code>)
|
||||
#
|
||||
# #0:<internal:lib/rubygems/custom_require>:38:Kernel:<: -
|
||||
# #0:example.rb:3::-: class A
|
||||
# #0:example.rb:3::C: class A
|
||||
# #0:example.rb:4::-: def square(a)
|
||||
# #0:example.rb:7::E: end
|
||||
# #0:example.rb:9::-: a = A.new
|
||||
# #0:example.rb:10::-: a.square(5)
|
||||
# #0:example.rb:4:A:>: def square(a)
|
||||
# #0:example.rb:5:A:-: return a*a
|
||||
# #0:example.rb:6:A:<: end
|
||||
# | | | | |
|
||||
# | | | | ---------------------+ event
|
||||
# | | | ------------------------+ class
|
||||
# | | --------------------------+ line
|
||||
# | ------------------------------------+ filename
|
||||
# ---------------------------------------+ thread
|
||||
#
|
||||
# Symbol table used for displaying incoming events:
|
||||
#
|
||||
# +}+:: call a C-language routine
|
||||
# +{+:: return from a C-language routine
|
||||
# +>+:: call a Ruby method
|
||||
# +C+:: start a class or module definition
|
||||
# +E+:: finish a class or module definition
|
||||
# +-+:: execute code on a new line
|
||||
# +^+:: raise an exception
|
||||
# +<+:: return from a Ruby method
|
||||
#
|
||||
# == Copyright
|
||||
#
|
||||
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
|
||||
#
|
||||
class Tracer
|
||||
class << self
|
||||
# display additional debug information (defaults to false)
|
||||
attr_accessor :verbose
|
||||
alias verbose? verbose
|
||||
|
||||
# output stream used to output trace (defaults to STDOUT)
|
||||
attr_accessor :stdout
|
||||
|
||||
# mutex lock used by tracer for displaying trace output
|
||||
attr_reader :stdout_mutex
|
||||
|
||||
# display process id in trace output (defaults to false)
|
||||
attr_accessor :display_process_id
|
||||
alias display_process_id? display_process_id
|
||||
|
||||
# display thread id in trace output (defaults to true)
|
||||
attr_accessor :display_thread_id
|
||||
alias display_thread_id? display_thread_id
|
||||
|
||||
# display C-routine calls in trace output (defaults to false)
|
||||
attr_accessor :display_c_call
|
||||
alias display_c_call? display_c_call
|
||||
end
|
||||
|
||||
Tracer::stdout = STDOUT
|
||||
Tracer::verbose = false
|
||||
Tracer::display_process_id = false
|
||||
Tracer::display_thread_id = true
|
||||
Tracer::display_c_call = false
|
||||
|
||||
@stdout_mutex = Thread::Mutex.new
|
||||
|
||||
# Symbol table used for displaying trace information
|
||||
EVENT_SYMBOL = {
|
||||
"line" => "-",
|
||||
"call" => ">",
|
||||
"return" => "<",
|
||||
"class" => "C",
|
||||
"end" => "E",
|
||||
"raise" => "^",
|
||||
"c-call" => "}",
|
||||
"c-return" => "{",
|
||||
"unknown" => "?"
|
||||
}
|
||||
|
||||
def initialize # :nodoc:
|
||||
@threads = Hash.new
|
||||
if defined? Thread.main
|
||||
@threads[Thread.main.object_id] = 0
|
||||
else
|
||||
@threads[Thread.current.object_id] = 0
|
||||
end
|
||||
|
||||
@get_line_procs = {}
|
||||
|
||||
@filters = []
|
||||
end
|
||||
|
||||
def stdout # :nodoc:
|
||||
Tracer.stdout
|
||||
end
|
||||
|
||||
def on # :nodoc:
|
||||
if block_given?
|
||||
on
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
off
|
||||
end
|
||||
else
|
||||
set_trace_func method(:trace_func).to_proc
|
||||
stdout.print "Trace on\n" if Tracer.verbose?
|
||||
end
|
||||
end
|
||||
|
||||
def off # :nodoc:
|
||||
set_trace_func nil
|
||||
stdout.print "Trace off\n" if Tracer.verbose?
|
||||
end
|
||||
|
||||
def add_filter(p = proc) # :nodoc:
|
||||
@filters.push p
|
||||
end
|
||||
|
||||
def set_get_line_procs(file, p = proc) # :nodoc:
|
||||
@get_line_procs[file] = p
|
||||
end
|
||||
|
||||
def get_line(file, line) # :nodoc:
|
||||
if p = @get_line_procs[file]
|
||||
return p.call(line)
|
||||
end
|
||||
|
||||
unless list = SCRIPT_LINES__[file]
|
||||
list = File.readlines(file) rescue []
|
||||
SCRIPT_LINES__[file] = list
|
||||
end
|
||||
|
||||
if l = list[line - 1]
|
||||
l
|
||||
else
|
||||
"-\n"
|
||||
end
|
||||
end
|
||||
|
||||
def get_thread_no # :nodoc:
|
||||
if no = @threads[Thread.current.object_id]
|
||||
no
|
||||
else
|
||||
@threads[Thread.current.object_id] = @threads.size
|
||||
end
|
||||
end
|
||||
|
||||
def trace_func(event, file, line, id, binding, klass, *) # :nodoc:
|
||||
return if file == __FILE__
|
||||
|
||||
for p in @filters
|
||||
return unless p.call event, file, line, id, binding, klass
|
||||
end
|
||||
|
||||
return unless Tracer::display_c_call? or
|
||||
event != "c-call" && event != "c-return"
|
||||
|
||||
Tracer::stdout_mutex.synchronize do
|
||||
if EVENT_SYMBOL[event]
|
||||
stdout.printf("<%d>", $$) if Tracer::display_process_id?
|
||||
stdout.printf("#%d:", get_thread_no) if Tracer::display_thread_id?
|
||||
if line == 0
|
||||
source = "?\n"
|
||||
else
|
||||
source = get_line(file, line)
|
||||
end
|
||||
stdout.printf("%s:%d:%s:%s: %s",
|
||||
file,
|
||||
line,
|
||||
klass || '',
|
||||
EVENT_SYMBOL[event],
|
||||
source)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# Reference to singleton instance of Tracer
|
||||
Single = new
|
||||
|
||||
##
|
||||
# Start tracing
|
||||
#
|
||||
# === Example
|
||||
#
|
||||
# Tracer.on
|
||||
# # code to trace here
|
||||
# Tracer.off
|
||||
#
|
||||
# You can also pass a block:
|
||||
#
|
||||
# Tracer.on {
|
||||
# # trace everything in this block
|
||||
# }
|
||||
|
||||
def Tracer.on
|
||||
if block_given?
|
||||
Single.on{yield}
|
||||
else
|
||||
Single.on
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Disable tracing
|
||||
|
||||
def Tracer.off
|
||||
Single.off
|
||||
end
|
||||
|
||||
##
|
||||
# Register an event handler <code>p</code> which is called everytime a line
|
||||
# in +file_name+ is executed.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# Tracer.set_get_line_procs("example.rb", lambda { |line|
|
||||
# puts "line number executed is #{line}"
|
||||
# })
|
||||
|
||||
def Tracer.set_get_line_procs(file_name, p = proc)
|
||||
Single.set_get_line_procs(file_name, p)
|
||||
end
|
||||
|
||||
##
|
||||
# Used to filter unwanted trace output
|
||||
#
|
||||
# Example which only outputs lines of code executed within the Kernel class:
|
||||
#
|
||||
# Tracer.add_filter do |event, file, line, id, binding, klass, *rest|
|
||||
# "Kernel" == klass.to_s
|
||||
# end
|
||||
|
||||
def Tracer.add_filter(p = proc)
|
||||
Single.add_filter(p)
|
||||
end
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
SCRIPT_LINES__ = {} unless defined? SCRIPT_LINES__
|
||||
|
||||
if $0 == __FILE__
|
||||
# direct call
|
||||
|
||||
$0 = ARGV[0]
|
||||
ARGV.shift
|
||||
Tracer.on
|
||||
require $0
|
||||
else
|
||||
# call Tracer.on only if required by -r command-line option
|
||||
count = caller.count {|bt| %r%/rubygems/core_ext/kernel_require\.rb:% !~ bt}
|
||||
if (defined?(Gem) and count == 0) or
|
||||
(!defined?(Gem) and count <= 1)
|
||||
Tracer.on
|
||||
end
|
||||
end
|
||||
# :startdoc:
|
||||
452
gxg/standard/tsort.rb
Normal file
452
gxg/standard/tsort.rb
Normal file
|
|
@ -0,0 +1,452 @@
|
|||
# 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
|
||||
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 TSort.tsort(each_node, each_child)
|
||||
TSort.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 TSort.tsort_each(each_node, each_child) # :yields: node
|
||||
return to_enum(__method__, each_node, each_child) unless block_given?
|
||||
|
||||
TSort.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 TSort.strongly_connected_components(each_node, each_child)
|
||||
TSort.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 TSort.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
|
||||
TSort.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 TSort.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 =
|
||||
TSort.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
|
||||
175
gxg/standard/unicode_normalize/normalize.rb
Normal file
175
gxg/standard/unicode_normalize/normalize.rb
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
# coding: utf-8
|
||||
# frozen_string_literal: false
|
||||
|
||||
# Copyright Ayumu Nojima (野島 歩) and Martin J. Dürst (duerst@it.aoyama.ac.jp)
|
||||
|
||||
# This file, the companion file tables.rb (autogenerated), and the module,
|
||||
# constants, and method defined herein are part of the implementation of the
|
||||
# built-in String class, not part of the standard library. They should
|
||||
# therefore never be gemified. They implement the methods
|
||||
# String#unicode_normalize, String#unicode_normalize!, and String#unicode_normalized?.
|
||||
#
|
||||
# They are placed here because they are written in Ruby. They are loaded on
|
||||
# demand when any of the three methods mentioned above is executed for the
|
||||
# first time. This reduces the memory footprint and startup time for scripts
|
||||
# and applications that do not use those methods.
|
||||
#
|
||||
# The name and even the existence of the module UnicodeNormalize and all of its
|
||||
# content are purely an implementation detail, and should not be exposed in
|
||||
# any test or spec or otherwise.
|
||||
|
||||
require 'unicode_normalize/tables.rb'
|
||||
|
||||
|
||||
module UnicodeNormalize # :nodoc:
|
||||
## Constant for max hash capacity to avoid DoS attack
|
||||
MAX_HASH_LENGTH = 18000 # enough for all test cases, otherwise tests get slow
|
||||
|
||||
## Regular Expressions and Hash Constants
|
||||
REGEXP_D = Regexp.compile(REGEXP_D_STRING, Regexp::EXTENDED)
|
||||
REGEXP_C = Regexp.compile(REGEXP_C_STRING, Regexp::EXTENDED)
|
||||
REGEXP_K = Regexp.compile(REGEXP_K_STRING, Regexp::EXTENDED)
|
||||
NF_HASH_D = Hash.new do |hash, key|
|
||||
hash.shift if hash.length>MAX_HASH_LENGTH # prevent DoS attack
|
||||
hash[key] = nfd_one(key)
|
||||
end
|
||||
NF_HASH_C = Hash.new do |hash, key|
|
||||
hash.shift if hash.length>MAX_HASH_LENGTH # prevent DoS attack
|
||||
hash[key] = nfc_one(key)
|
||||
end
|
||||
|
||||
## Constants For Hangul
|
||||
# for details such as the meaning of the identifiers below, please see
|
||||
# http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf, pp. 144/145
|
||||
SBASE = 0xAC00
|
||||
LBASE = 0x1100
|
||||
VBASE = 0x1161
|
||||
TBASE = 0x11A7
|
||||
LCOUNT = 19
|
||||
VCOUNT = 21
|
||||
TCOUNT = 28
|
||||
NCOUNT = VCOUNT * TCOUNT
|
||||
SCOUNT = LCOUNT * NCOUNT
|
||||
|
||||
# Unicode-based encodings (except UTF-8)
|
||||
UNICODE_ENCODINGS = [Encoding::UTF_16BE, Encoding::UTF_16LE, Encoding::UTF_32BE, Encoding::UTF_32LE,
|
||||
Encoding::GB18030, Encoding::UCS_2BE, Encoding::UCS_4BE]
|
||||
|
||||
## Hangul Algorithm
|
||||
def self.hangul_decomp_one(target)
|
||||
syllable_index = target.ord - SBASE
|
||||
return target if syllable_index < 0 || syllable_index >= SCOUNT
|
||||
l = LBASE + syllable_index / NCOUNT
|
||||
v = VBASE + (syllable_index % NCOUNT) / TCOUNT
|
||||
t = TBASE + syllable_index % TCOUNT
|
||||
(t==TBASE ? [l, v] : [l, v, t]).pack('U*') + target[1..-1]
|
||||
end
|
||||
|
||||
def self.hangul_comp_one(string)
|
||||
length = string.length
|
||||
if length>1 and 0 <= (lead =string[0].ord-LBASE) and lead < LCOUNT and
|
||||
0 <= (vowel=string[1].ord-VBASE) and vowel < VCOUNT
|
||||
lead_vowel = SBASE + (lead * VCOUNT + vowel) * TCOUNT
|
||||
if length>2 and 0 <= (trail=string[2].ord-TBASE) and trail < TCOUNT
|
||||
(lead_vowel + trail).chr(Encoding::UTF_8) + string[3..-1]
|
||||
else
|
||||
lead_vowel.chr(Encoding::UTF_8) + string[2..-1]
|
||||
end
|
||||
else
|
||||
string
|
||||
end
|
||||
end
|
||||
|
||||
## 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
|
||||
end
|
||||
end
|
||||
return sorting.collect(&:first).join('')
|
||||
end
|
||||
|
||||
## Normalization Forms for Patterns (not whole Strings)
|
||||
def self.nfd_one(string)
|
||||
string = string.chars.map! {|c| DECOMPOSITION_TABLE[c] || c}.join('')
|
||||
canonical_ordering_one(hangul_decomp_one(string))
|
||||
end
|
||||
|
||||
def self.nfc_one(string)
|
||||
nfd_string = nfd_one string
|
||||
start = nfd_string[0]
|
||||
last_class = CLASS_TABLE[start]-1
|
||||
accents = ''
|
||||
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
|
||||
else
|
||||
accents << accent
|
||||
last_class = accent_class
|
||||
end
|
||||
end
|
||||
hangul_comp_one(start+accents)
|
||||
end
|
||||
|
||||
def self.normalize(string, form = :nfc)
|
||||
encoding = string.encoding
|
||||
case encoding
|
||||
when Encoding::UTF_8
|
||||
case form
|
||||
when :nfc then
|
||||
string.gsub REGEXP_C, NF_HASH_C
|
||||
when :nfd then
|
||||
string.gsub REGEXP_D, NF_HASH_D
|
||||
when :nfkc then
|
||||
string.gsub(REGEXP_K, KOMPATIBLE_TABLE).gsub(REGEXP_C, NF_HASH_C)
|
||||
when :nfkd then
|
||||
string.gsub(REGEXP_K, KOMPATIBLE_TABLE).gsub(REGEXP_D, NF_HASH_D)
|
||||
else
|
||||
raise ArgumentError, "Invalid normalization form #{form}."
|
||||
end
|
||||
when Encoding::US_ASCII
|
||||
string
|
||||
when *UNICODE_ENCODINGS
|
||||
normalize(string.encode(Encoding::UTF_8), form).encode(encoding)
|
||||
else
|
||||
raise Encoding::CompatibilityError, "Unicode Normalization not appropriate for #{encoding}"
|
||||
end
|
||||
end
|
||||
|
||||
def self.normalized?(string, form = :nfc)
|
||||
encoding = string.encoding
|
||||
case encoding
|
||||
when Encoding::UTF_8
|
||||
case form
|
||||
when :nfc then
|
||||
string.scan REGEXP_C do |match|
|
||||
return false if NF_HASH_C[match] != match
|
||||
end
|
||||
true
|
||||
when :nfd then
|
||||
string.scan REGEXP_D do |match|
|
||||
return false if NF_HASH_D[match] != match
|
||||
end
|
||||
true
|
||||
when :nfkc then
|
||||
normalized?(string, :nfc) and string !~ REGEXP_K
|
||||
when :nfkd then
|
||||
normalized?(string, :nfd) and string !~ REGEXP_K
|
||||
else
|
||||
raise ArgumentError, "Invalid normalization form #{form}."
|
||||
end
|
||||
when Encoding::US_ASCII
|
||||
true
|
||||
when *UNICODE_ENCODINGS
|
||||
normalized? string.encode(Encoding::UTF_8), form
|
||||
else
|
||||
raise Encoding::CompatibilityError, "Unicode Normalization not appropriate for #{encoding}"
|
||||
end
|
||||
end
|
||||
end # module
|
||||
1170
gxg/standard/unicode_normalize/tables.rb
Normal file
1170
gxg/standard/unicode_normalize/tables.rb
Normal file
File diff suppressed because it is too large
Load diff
750
gxg/standard/uri/common.rb
Normal file
750
gxg/standard/uri/common.rb
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# = uri/common.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# Revision:: $Id: common.rb 61155 2017-12-12 11:56:25Z shyouhei $
|
||||
# License::
|
||||
# You can redistribute it and/or modify it under the same term as Ruby.
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require "uri/rfc2396_parser"
|
||||
require "uri/rfc3986_parser"
|
||||
|
||||
module URI
|
||||
REGEXP = RFC2396_REGEXP
|
||||
Parser = RFC2396_Parser
|
||||
RFC3986_PARSER = RFC3986_Parser.new
|
||||
|
||||
# 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)
|
||||
end
|
||||
end
|
||||
DEFAULT_PARSER.regexp.each_pair do |sym, str|
|
||||
const_set(sym, str)
|
||||
end
|
||||
|
||||
module Util # :nodoc:
|
||||
def make_components_hash(klass, array_hash)
|
||||
tmp = {}
|
||||
if array_hash.kind_of?(Array) &&
|
||||
array_hash.size == klass.component.size - 1
|
||||
klass.component[1..-1].each_index do |i|
|
||||
begin
|
||||
tmp[klass.component[i + 1]] = array_hash[i].clone
|
||||
rescue TypeError
|
||||
tmp[klass.component[i + 1]] = array_hash[i]
|
||||
end
|
||||
end
|
||||
|
||||
elsif array_hash.kind_of?(Hash)
|
||||
array_hash.each do |key, value|
|
||||
begin
|
||||
tmp[key] = value.clone
|
||||
rescue TypeError
|
||||
tmp[key] = value
|
||||
end
|
||||
end
|
||||
else
|
||||
raise ArgumentError,
|
||||
"expected Array of or Hash of components of #{klass} (#{klass.component[1..-1].join(', ')})"
|
||||
end
|
||||
tmp[:scheme] = klass.to_s.sub(/\A.*::/, '').downcase
|
||||
|
||||
return tmp
|
||||
end
|
||||
module_function :make_components_hash
|
||||
end
|
||||
|
||||
# module for escaping unsafe characters with codes.
|
||||
module Escape
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI.escape(str [, unsafe])
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String to replaces in.
|
||||
# +unsafe+::
|
||||
# Regexp that matches all symbols that must be replaced with codes.
|
||||
# By default uses <tt>UNSAFE</tt>.
|
||||
# When this argument is a String, it represents a character set.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Escapes the string, replacing all unsafe characters with codes.
|
||||
#
|
||||
# This method is obsolete and should not be used. Instead, use
|
||||
# CGI.escape, URI.encode_www_form or URI.encode_www_form_component
|
||||
# depending on your specific use case.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# enc_uri = URI.escape("http://example.com/?a=\11\15")
|
||||
# p enc_uri
|
||||
# # => "http://example.com/?a=%09%0D"
|
||||
#
|
||||
# p URI.unescape(enc_uri)
|
||||
# # => "http://example.com/?a=\t\r"
|
||||
#
|
||||
# p URI.escape("@?@!", "!?")
|
||||
# # => "@%3F@%21"
|
||||
#
|
||||
def escape(*arg)
|
||||
warn "URI.escape is obsolete", uplevel: 1 if $VERBOSE
|
||||
DEFAULT_PARSER.escape(*arg)
|
||||
end
|
||||
alias encode escape
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI.unescape(str)
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# Unescapes the string.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# This method is obsolete and should not be used. Instead, use
|
||||
# CGI.unescape, URI.decode_www_form or URI.decode_www_form_component
|
||||
# depending on your specific use case.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# enc_uri = URI.escape("http://example.com/?a=\11\15")
|
||||
# p enc_uri
|
||||
# # => "http://example.com/?a=%09%0D"
|
||||
#
|
||||
# p URI.unescape(enc_uri)
|
||||
# # => "http://example.com/?a=\t\r"
|
||||
#
|
||||
def unescape(*arg)
|
||||
warn "URI.unescape is obsolete", uplevel: 1 if $VERBOSE
|
||||
DEFAULT_PARSER.unescape(*arg)
|
||||
end
|
||||
alias decode unescape
|
||||
end # module Escape
|
||||
|
||||
extend Escape
|
||||
include REGEXP
|
||||
|
||||
@@schemes = {}
|
||||
# Returns a Hash of the defined schemes
|
||||
def self.scheme_list
|
||||
@@schemes
|
||||
end
|
||||
|
||||
#
|
||||
# Base class for all URI exceptions.
|
||||
#
|
||||
class Error < StandardError; end
|
||||
#
|
||||
# Not a URI.
|
||||
#
|
||||
class InvalidURIError < Error; end
|
||||
#
|
||||
# Not a URI component.
|
||||
#
|
||||
class InvalidComponentError < Error; end
|
||||
#
|
||||
# URI is valid, bad usage is not.
|
||||
#
|
||||
class BadURIError < Error; end
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::split(uri)
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +uri+::
|
||||
# String with URI.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Splits the string on following parts and returns array with result:
|
||||
#
|
||||
# * Scheme
|
||||
# * Userinfo
|
||||
# * Host
|
||||
# * Port
|
||||
# * Registry
|
||||
# * Path
|
||||
# * Opaque
|
||||
# * Query
|
||||
# * Fragment
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# p URI.split("http://www.ruby-lang.org/")
|
||||
# # => ["http", nil, "www.ruby-lang.org", nil, nil, "/", nil, nil, nil]
|
||||
#
|
||||
def self.split(uri)
|
||||
RFC3986_PARSER.split(uri)
|
||||
end
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::parse(uri_str)
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +uri_str+::
|
||||
# String with URI.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Creates one of the URI's subclasses instance from the string.
|
||||
#
|
||||
# == Raises
|
||||
#
|
||||
# URI::InvalidURIError
|
||||
# Raised if URI given is not a correct one.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# uri = URI.parse("http://www.ruby-lang.org/")
|
||||
# p uri
|
||||
# # => #<URI::HTTP:0x202281be URL:http://www.ruby-lang.org/>
|
||||
# p uri.scheme
|
||||
# # => "http"
|
||||
# p uri.host
|
||||
# # => "www.ruby-lang.org"
|
||||
#
|
||||
# It's recommended to first ::escape the provided +uri_str+ if there are any
|
||||
# invalid URI characters.
|
||||
#
|
||||
def self.parse(uri)
|
||||
RFC3986_PARSER.parse(uri)
|
||||
end
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::join(str[, str, ...])
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String(s) to work with, will be converted to RFC3986 URIs before merging.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Joins URIs.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# p URI.join("http://example.com/","main.rbx")
|
||||
# # => #<URI::HTTP:0x2022ac02 URL:http://example.com/main.rbx>
|
||||
#
|
||||
# p URI.join('http://example.com', 'foo')
|
||||
# # => #<URI::HTTP:0x01ab80a0 URL:http://example.com/foo>
|
||||
#
|
||||
# p URI.join('http://example.com', '/foo', '/bar')
|
||||
# # => #<URI::HTTP:0x01aaf0b0 URL:http://example.com/bar>
|
||||
#
|
||||
# p URI.join('http://example.com', '/foo', 'bar')
|
||||
# # => #<URI::HTTP:0x801a92af0 URL:http://example.com/bar>
|
||||
#
|
||||
# p URI.join('http://example.com', '/foo/', 'bar')
|
||||
# # => #<URI::HTTP:0x80135a3a0 URL:http://example.com/foo/bar>
|
||||
#
|
||||
#
|
||||
def self.join(*str)
|
||||
RFC3986_PARSER.join(*str)
|
||||
end
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::extract(str[, schemes][,&blk])
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String to extract URIs from.
|
||||
# +schemes+::
|
||||
# Limit URI matching to a specific schemes.
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Extracts URIs from a string. If block given, iterates through all matched URIs.
|
||||
# Returns nil if block given or array with matches.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require "uri"
|
||||
#
|
||||
# URI.extract("text here http://foo.example.org/bla and here mailto:test@example.com and here also.")
|
||||
# # => ["http://foo.example.com/bla", "mailto:test@example.com"]
|
||||
#
|
||||
def self.extract(str, schemes = nil, &block)
|
||||
warn "URI.extract is obsolete", uplevel: 1 if $VERBOSE
|
||||
DEFAULT_PARSER.extract(str, schemes, &block)
|
||||
end
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::regexp([match_schemes])
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +match_schemes+::
|
||||
# Array of schemes. If given, resulting regexp matches to URIs
|
||||
# whose scheme is one of the match_schemes.
|
||||
#
|
||||
# == Description
|
||||
# Returns a Regexp object which matches to URI-like strings.
|
||||
# The Regexp object returned by this method includes arbitrary
|
||||
# number of capture group (parentheses). Never rely on it's number.
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# # extract first URI from html_string
|
||||
# html_string.slice(URI.regexp)
|
||||
#
|
||||
# # remove ftp URIs
|
||||
# html_string.sub(URI.regexp(['ftp'])
|
||||
#
|
||||
# # You should not rely on the number of parentheses
|
||||
# html_string.scan(URI.regexp) do |*matches|
|
||||
# p $&
|
||||
# end
|
||||
#
|
||||
def self.regexp(schemes = nil)
|
||||
warn "URI.regexp is obsolete", uplevel: 1 if $VERBOSE
|
||||
DEFAULT_PARSER.make_regexp(schemes)
|
||||
end
|
||||
|
||||
TBLENCWWWCOMP_ = {} # :nodoc:
|
||||
256.times do |i|
|
||||
TBLENCWWWCOMP_[i.chr] = '%%%02X' % i
|
||||
end
|
||||
TBLENCWWWCOMP_[' '] = '+'
|
||||
TBLENCWWWCOMP_.freeze
|
||||
TBLDECWWWCOMP_ = {} # :nodoc:
|
||||
256.times do |i|
|
||||
h, l = i>>4, i&15
|
||||
TBLDECWWWCOMP_['%%%X%X' % [h, l]] = i.chr
|
||||
TBLDECWWWCOMP_['%%%x%X' % [h, l]] = i.chr
|
||||
TBLDECWWWCOMP_['%%%X%x' % [h, l]] = i.chr
|
||||
TBLDECWWWCOMP_['%%%x%x' % [h, l]] = i.chr
|
||||
end
|
||||
TBLDECWWWCOMP_['+'] = ' '
|
||||
TBLDECWWWCOMP_.freeze
|
||||
|
||||
HTML5ASCIIINCOMPAT = defined? Encoding::UTF_7 ? [Encoding::UTF_7, Encoding::UTF_16BE, Encoding::UTF_16LE,
|
||||
Encoding::UTF_32BE, Encoding::UTF_32LE] : [] # :nodoc:
|
||||
|
||||
# Encode given +str+ to URL-encoded form data.
|
||||
#
|
||||
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
||||
# (ASCII space) to + and converts others to %XX.
|
||||
#
|
||||
# If +enc+ is given, convert +str+ to the encoding before percent encoding.
|
||||
#
|
||||
# This is an implementation of
|
||||
# http://www.w3.org/TR/2013/CR-html5-20130806/forms.html#url-encoded-form-data
|
||||
#
|
||||
# See URI.decode_www_form_component, URI.encode_www_form
|
||||
def self.encode_www_form_component(str, enc=nil)
|
||||
str = str.to_s.dup
|
||||
if str.encoding != Encoding::ASCII_8BIT
|
||||
if enc && enc != Encoding::ASCII_8BIT
|
||||
str.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
||||
str.encode!(enc, fallback: ->(x){"&#{x.ord};"})
|
||||
end
|
||||
str.force_encoding(Encoding::ASCII_8BIT)
|
||||
end
|
||||
str.gsub!(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_)
|
||||
str.force_encoding(Encoding::US_ASCII)
|
||||
end
|
||||
|
||||
# Decode given +str+ of URL-encoded form data.
|
||||
#
|
||||
# This decodes + to SP.
|
||||
#
|
||||
# See URI.encode_www_form_component, URI.decode_www_form
|
||||
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
|
||||
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str
|
||||
str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc)
|
||||
end
|
||||
|
||||
# Generate URL-encoded form data from given +enum+.
|
||||
#
|
||||
# This generates application/x-www-form-urlencoded data defined in HTML5
|
||||
# from given an Enumerable object.
|
||||
#
|
||||
# This internally uses URI.encode_www_form_component(str).
|
||||
#
|
||||
# This method doesn't convert the encoding of given items, so convert them
|
||||
# before call this method if you want to send data as other than original
|
||||
# encoding or mixed encoding data. (Strings which are encoded in an HTML5
|
||||
# ASCII incompatible encoding are converted to UTF-8.)
|
||||
#
|
||||
# This method doesn't handle files. When you send a file, use
|
||||
# multipart/form-data.
|
||||
#
|
||||
# This refers http://url.spec.whatwg.org/#concept-urlencoded-serializer
|
||||
#
|
||||
# URI.encode_www_form([["q", "ruby"], ["lang", "en"]])
|
||||
# #=> "q=ruby&lang=en"
|
||||
# URI.encode_www_form("q" => "ruby", "lang" => "en")
|
||||
# #=> "q=ruby&lang=en"
|
||||
# URI.encode_www_form("q" => ["ruby", "perl"], "lang" => "en")
|
||||
# #=> "q=ruby&q=perl&lang=en"
|
||||
# URI.encode_www_form([["q", "ruby"], ["q", "perl"], ["lang", "en"]])
|
||||
# #=> "q=ruby&q=perl&lang=en"
|
||||
#
|
||||
# See URI.encode_www_form_component, URI.decode_www_form
|
||||
def self.encode_www_form(enum, enc=nil)
|
||||
enum.map do |k,v|
|
||||
if v.nil?
|
||||
encode_www_form_component(k, enc)
|
||||
elsif v.respond_to?(:to_ary)
|
||||
v.to_ary.map do |w|
|
||||
str = encode_www_form_component(k, enc)
|
||||
unless w.nil?
|
||||
str << '='
|
||||
str << encode_www_form_component(w, enc)
|
||||
end
|
||||
end.join('&')
|
||||
else
|
||||
str = encode_www_form_component(k, enc)
|
||||
str << '='
|
||||
str << encode_www_form_component(v, enc)
|
||||
end
|
||||
end.join('&')
|
||||
end
|
||||
|
||||
# Decode URL-encoded form data from given +str+.
|
||||
#
|
||||
# This decodes application/x-www-form-urlencoded data
|
||||
# and returns array of key-value array.
|
||||
#
|
||||
# This refers http://url.spec.whatwg.org/#concept-urlencoded-parser ,
|
||||
# so this supports only &-separator, don't support ;-separator.
|
||||
#
|
||||
# ary = URI.decode_www_form("a=1&a=2&b=3")
|
||||
# p ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
|
||||
# p ary.assoc('a').last #=> '1'
|
||||
# p ary.assoc('b').last #=> '3'
|
||||
# p ary.rassoc('a').last #=> '2'
|
||||
# p Hash[ary] # => {"a"=>"2", "b"=>"3"}
|
||||
#
|
||||
# See URI.decode_www_form_component, URI.encode_www_form
|
||||
def self.decode_www_form(str, enc=Encoding::UTF_8, separator: '&', use__charset_: false, isindex: false)
|
||||
raise ArgumentError, "the input of #{self.name}.#{__method__} must be ASCII only string" unless str.ascii_only?
|
||||
ary = []
|
||||
return ary if str.empty?
|
||||
enc = Encoding.find(enc)
|
||||
str.b.each_line(separator) do |string|
|
||||
string.chomp!(separator)
|
||||
key, sep, val = string.partition('=')
|
||||
if isindex
|
||||
if sep.empty?
|
||||
val = key
|
||||
key = ''
|
||||
end
|
||||
isindex = false
|
||||
end
|
||||
|
||||
if use__charset_ and key == '_charset_' and e = get_encoding(val)
|
||||
enc = e
|
||||
use__charset_ = false
|
||||
end
|
||||
|
||||
key.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
|
||||
if val
|
||||
val.gsub!(/\+|%\h\h/, TBLDECWWWCOMP_)
|
||||
else
|
||||
val = ''
|
||||
end
|
||||
|
||||
ary << [key, val]
|
||||
end
|
||||
ary.each do |k, v|
|
||||
k.force_encoding(enc)
|
||||
k.scrub!
|
||||
v.force_encoding(enc)
|
||||
v.scrub!
|
||||
end
|
||||
ary
|
||||
end
|
||||
|
||||
private
|
||||
=begin command for WEB_ENCODINGS_
|
||||
curl https://encoding.spec.whatwg.org/encodings.json|
|
||||
ruby -rjson -e 'H={}
|
||||
h={
|
||||
"shift_jis"=>"Windows-31J",
|
||||
"euc-jp"=>"cp51932",
|
||||
"iso-2022-jp"=>"cp50221",
|
||||
"x-mac-cyrillic"=>"macCyrillic",
|
||||
}
|
||||
JSON($<.read).map{|x|x["encodings"]}.flatten.each{|x|
|
||||
Encoding.find(n=h.fetch(n=x["name"].downcase,n))rescue next
|
||||
x["labels"].each{|y|H[y]=n}
|
||||
}
|
||||
puts "{"
|
||||
H.each{|k,v|puts %[ #{k.dump}=>#{v.dump},]}
|
||||
puts "}"
|
||||
'
|
||||
=end
|
||||
WEB_ENCODINGS_ = {
|
||||
"unicode-1-1-utf-8"=>"utf-8",
|
||||
"utf-8"=>"utf-8",
|
||||
"utf8"=>"utf-8",
|
||||
"866"=>"ibm866",
|
||||
"cp866"=>"ibm866",
|
||||
"csibm866"=>"ibm866",
|
||||
"ibm866"=>"ibm866",
|
||||
"csisolatin2"=>"iso-8859-2",
|
||||
"iso-8859-2"=>"iso-8859-2",
|
||||
"iso-ir-101"=>"iso-8859-2",
|
||||
"iso8859-2"=>"iso-8859-2",
|
||||
"iso88592"=>"iso-8859-2",
|
||||
"iso_8859-2"=>"iso-8859-2",
|
||||
"iso_8859-2:1987"=>"iso-8859-2",
|
||||
"l2"=>"iso-8859-2",
|
||||
"latin2"=>"iso-8859-2",
|
||||
"csisolatin3"=>"iso-8859-3",
|
||||
"iso-8859-3"=>"iso-8859-3",
|
||||
"iso-ir-109"=>"iso-8859-3",
|
||||
"iso8859-3"=>"iso-8859-3",
|
||||
"iso88593"=>"iso-8859-3",
|
||||
"iso_8859-3"=>"iso-8859-3",
|
||||
"iso_8859-3:1988"=>"iso-8859-3",
|
||||
"l3"=>"iso-8859-3",
|
||||
"latin3"=>"iso-8859-3",
|
||||
"csisolatin4"=>"iso-8859-4",
|
||||
"iso-8859-4"=>"iso-8859-4",
|
||||
"iso-ir-110"=>"iso-8859-4",
|
||||
"iso8859-4"=>"iso-8859-4",
|
||||
"iso88594"=>"iso-8859-4",
|
||||
"iso_8859-4"=>"iso-8859-4",
|
||||
"iso_8859-4:1988"=>"iso-8859-4",
|
||||
"l4"=>"iso-8859-4",
|
||||
"latin4"=>"iso-8859-4",
|
||||
"csisolatincyrillic"=>"iso-8859-5",
|
||||
"cyrillic"=>"iso-8859-5",
|
||||
"iso-8859-5"=>"iso-8859-5",
|
||||
"iso-ir-144"=>"iso-8859-5",
|
||||
"iso8859-5"=>"iso-8859-5",
|
||||
"iso88595"=>"iso-8859-5",
|
||||
"iso_8859-5"=>"iso-8859-5",
|
||||
"iso_8859-5:1988"=>"iso-8859-5",
|
||||
"arabic"=>"iso-8859-6",
|
||||
"asmo-708"=>"iso-8859-6",
|
||||
"csiso88596e"=>"iso-8859-6",
|
||||
"csiso88596i"=>"iso-8859-6",
|
||||
"csisolatinarabic"=>"iso-8859-6",
|
||||
"ecma-114"=>"iso-8859-6",
|
||||
"iso-8859-6"=>"iso-8859-6",
|
||||
"iso-8859-6-e"=>"iso-8859-6",
|
||||
"iso-8859-6-i"=>"iso-8859-6",
|
||||
"iso-ir-127"=>"iso-8859-6",
|
||||
"iso8859-6"=>"iso-8859-6",
|
||||
"iso88596"=>"iso-8859-6",
|
||||
"iso_8859-6"=>"iso-8859-6",
|
||||
"iso_8859-6:1987"=>"iso-8859-6",
|
||||
"csisolatingreek"=>"iso-8859-7",
|
||||
"ecma-118"=>"iso-8859-7",
|
||||
"elot_928"=>"iso-8859-7",
|
||||
"greek"=>"iso-8859-7",
|
||||
"greek8"=>"iso-8859-7",
|
||||
"iso-8859-7"=>"iso-8859-7",
|
||||
"iso-ir-126"=>"iso-8859-7",
|
||||
"iso8859-7"=>"iso-8859-7",
|
||||
"iso88597"=>"iso-8859-7",
|
||||
"iso_8859-7"=>"iso-8859-7",
|
||||
"iso_8859-7:1987"=>"iso-8859-7",
|
||||
"sun_eu_greek"=>"iso-8859-7",
|
||||
"csiso88598e"=>"iso-8859-8",
|
||||
"csisolatinhebrew"=>"iso-8859-8",
|
||||
"hebrew"=>"iso-8859-8",
|
||||
"iso-8859-8"=>"iso-8859-8",
|
||||
"iso-8859-8-e"=>"iso-8859-8",
|
||||
"iso-ir-138"=>"iso-8859-8",
|
||||
"iso8859-8"=>"iso-8859-8",
|
||||
"iso88598"=>"iso-8859-8",
|
||||
"iso_8859-8"=>"iso-8859-8",
|
||||
"iso_8859-8:1988"=>"iso-8859-8",
|
||||
"visual"=>"iso-8859-8",
|
||||
"csisolatin6"=>"iso-8859-10",
|
||||
"iso-8859-10"=>"iso-8859-10",
|
||||
"iso-ir-157"=>"iso-8859-10",
|
||||
"iso8859-10"=>"iso-8859-10",
|
||||
"iso885910"=>"iso-8859-10",
|
||||
"l6"=>"iso-8859-10",
|
||||
"latin6"=>"iso-8859-10",
|
||||
"iso-8859-13"=>"iso-8859-13",
|
||||
"iso8859-13"=>"iso-8859-13",
|
||||
"iso885913"=>"iso-8859-13",
|
||||
"iso-8859-14"=>"iso-8859-14",
|
||||
"iso8859-14"=>"iso-8859-14",
|
||||
"iso885914"=>"iso-8859-14",
|
||||
"csisolatin9"=>"iso-8859-15",
|
||||
"iso-8859-15"=>"iso-8859-15",
|
||||
"iso8859-15"=>"iso-8859-15",
|
||||
"iso885915"=>"iso-8859-15",
|
||||
"iso_8859-15"=>"iso-8859-15",
|
||||
"l9"=>"iso-8859-15",
|
||||
"iso-8859-16"=>"iso-8859-16",
|
||||
"cskoi8r"=>"koi8-r",
|
||||
"koi"=>"koi8-r",
|
||||
"koi8"=>"koi8-r",
|
||||
"koi8-r"=>"koi8-r",
|
||||
"koi8_r"=>"koi8-r",
|
||||
"koi8-ru"=>"koi8-u",
|
||||
"koi8-u"=>"koi8-u",
|
||||
"dos-874"=>"windows-874",
|
||||
"iso-8859-11"=>"windows-874",
|
||||
"iso8859-11"=>"windows-874",
|
||||
"iso885911"=>"windows-874",
|
||||
"tis-620"=>"windows-874",
|
||||
"windows-874"=>"windows-874",
|
||||
"cp1250"=>"windows-1250",
|
||||
"windows-1250"=>"windows-1250",
|
||||
"x-cp1250"=>"windows-1250",
|
||||
"cp1251"=>"windows-1251",
|
||||
"windows-1251"=>"windows-1251",
|
||||
"x-cp1251"=>"windows-1251",
|
||||
"ansi_x3.4-1968"=>"windows-1252",
|
||||
"ascii"=>"windows-1252",
|
||||
"cp1252"=>"windows-1252",
|
||||
"cp819"=>"windows-1252",
|
||||
"csisolatin1"=>"windows-1252",
|
||||
"ibm819"=>"windows-1252",
|
||||
"iso-8859-1"=>"windows-1252",
|
||||
"iso-ir-100"=>"windows-1252",
|
||||
"iso8859-1"=>"windows-1252",
|
||||
"iso88591"=>"windows-1252",
|
||||
"iso_8859-1"=>"windows-1252",
|
||||
"iso_8859-1:1987"=>"windows-1252",
|
||||
"l1"=>"windows-1252",
|
||||
"latin1"=>"windows-1252",
|
||||
"us-ascii"=>"windows-1252",
|
||||
"windows-1252"=>"windows-1252",
|
||||
"x-cp1252"=>"windows-1252",
|
||||
"cp1253"=>"windows-1253",
|
||||
"windows-1253"=>"windows-1253",
|
||||
"x-cp1253"=>"windows-1253",
|
||||
"cp1254"=>"windows-1254",
|
||||
"csisolatin5"=>"windows-1254",
|
||||
"iso-8859-9"=>"windows-1254",
|
||||
"iso-ir-148"=>"windows-1254",
|
||||
"iso8859-9"=>"windows-1254",
|
||||
"iso88599"=>"windows-1254",
|
||||
"iso_8859-9"=>"windows-1254",
|
||||
"iso_8859-9:1989"=>"windows-1254",
|
||||
"l5"=>"windows-1254",
|
||||
"latin5"=>"windows-1254",
|
||||
"windows-1254"=>"windows-1254",
|
||||
"x-cp1254"=>"windows-1254",
|
||||
"cp1255"=>"windows-1255",
|
||||
"windows-1255"=>"windows-1255",
|
||||
"x-cp1255"=>"windows-1255",
|
||||
"cp1256"=>"windows-1256",
|
||||
"windows-1256"=>"windows-1256",
|
||||
"x-cp1256"=>"windows-1256",
|
||||
"cp1257"=>"windows-1257",
|
||||
"windows-1257"=>"windows-1257",
|
||||
"x-cp1257"=>"windows-1257",
|
||||
"cp1258"=>"windows-1258",
|
||||
"windows-1258"=>"windows-1258",
|
||||
"x-cp1258"=>"windows-1258",
|
||||
"x-mac-cyrillic"=>"macCyrillic",
|
||||
"x-mac-ukrainian"=>"macCyrillic",
|
||||
"chinese"=>"gbk",
|
||||
"csgb2312"=>"gbk",
|
||||
"csiso58gb231280"=>"gbk",
|
||||
"gb2312"=>"gbk",
|
||||
"gb_2312"=>"gbk",
|
||||
"gb_2312-80"=>"gbk",
|
||||
"gbk"=>"gbk",
|
||||
"iso-ir-58"=>"gbk",
|
||||
"x-gbk"=>"gbk",
|
||||
"gb18030"=>"gb18030",
|
||||
"big5"=>"big5",
|
||||
"big5-hkscs"=>"big5",
|
||||
"cn-big5"=>"big5",
|
||||
"csbig5"=>"big5",
|
||||
"x-x-big5"=>"big5",
|
||||
"cseucpkdfmtjapanese"=>"cp51932",
|
||||
"euc-jp"=>"cp51932",
|
||||
"x-euc-jp"=>"cp51932",
|
||||
"csiso2022jp"=>"cp50221",
|
||||
"iso-2022-jp"=>"cp50221",
|
||||
"csshiftjis"=>"Windows-31J",
|
||||
"ms932"=>"Windows-31J",
|
||||
"ms_kanji"=>"Windows-31J",
|
||||
"shift-jis"=>"Windows-31J",
|
||||
"shift_jis"=>"Windows-31J",
|
||||
"sjis"=>"Windows-31J",
|
||||
"windows-31j"=>"Windows-31J",
|
||||
"x-sjis"=>"Windows-31J",
|
||||
"cseuckr"=>"euc-kr",
|
||||
"csksc56011987"=>"euc-kr",
|
||||
"euc-kr"=>"euc-kr",
|
||||
"iso-ir-149"=>"euc-kr",
|
||||
"korean"=>"euc-kr",
|
||||
"ks_c_5601-1987"=>"euc-kr",
|
||||
"ks_c_5601-1989"=>"euc-kr",
|
||||
"ksc5601"=>"euc-kr",
|
||||
"ksc_5601"=>"euc-kr",
|
||||
"windows-949"=>"euc-kr",
|
||||
"utf-16be"=>"utf-16be",
|
||||
"utf-16"=>"utf-16le",
|
||||
"utf-16le"=>"utf-16le",
|
||||
} # :nodoc:
|
||||
|
||||
# :nodoc:
|
||||
# return encoding or nil
|
||||
# http://encoding.spec.whatwg.org/#concept-encoding-get
|
||||
def self.get_encoding(label)
|
||||
Encoding.find(WEB_ENCODINGS_[label.to_str.strip.downcase]) rescue nil
|
||||
end
|
||||
end # module URI
|
||||
|
||||
module Kernel
|
||||
|
||||
#
|
||||
# Returns +uri+ converted to a URI object.
|
||||
#
|
||||
def URI(uri)
|
||||
if uri.is_a?(URI::Generic)
|
||||
uri
|
||||
elsif uri = String.try_convert(uri)
|
||||
URI.parse(uri)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"bad argument (expected URI object or URI string)"
|
||||
end
|
||||
end
|
||||
module_function :URI
|
||||
end
|
||||
267
gxg/standard/uri/ftp.rb
Normal file
267
gxg/standard/uri/ftp.rb
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/ftp.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
||||
# Revision:: $Id: ftp.rb 59598 2017-08-15 01:05:50Z nobu $
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/generic'
|
||||
|
||||
module URI
|
||||
|
||||
#
|
||||
# FTP URI syntax is defined by RFC1738 section 3.2.
|
||||
#
|
||||
# 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
|
||||
#
|
||||
class FTP < Generic
|
||||
# A Default port of 21 for URI::FTP
|
||||
DEFAULT_PORT = 21
|
||||
|
||||
#
|
||||
# An Array of the available components for URI::FTP
|
||||
#
|
||||
COMPONENT = [
|
||||
:scheme,
|
||||
:userinfo, :host, :port,
|
||||
:path, :typecode
|
||||
].freeze
|
||||
|
||||
#
|
||||
# Typecode is "a", "i" or "d".
|
||||
#
|
||||
# * "a" indicates a text file (the FTP command was ASCII)
|
||||
# * "i" indicates a binary file (FTP command IMAGE)
|
||||
# * "d" indicates the contents of a directory should be displayed
|
||||
#
|
||||
TYPECODE = ['a', 'i', 'd'].freeze
|
||||
|
||||
# Typecode prefix
|
||||
# ';type='
|
||||
TYPECODE_PREFIX = ';type='.freeze
|
||||
|
||||
def self.new2(user, password, host, port, path,
|
||||
typecode = nil, arg_check = true) # :nodoc:
|
||||
# Do not use this method! Not tested. [Bug #7301]
|
||||
# This methods remains just for compatibility,
|
||||
# Keep it undocumented until the active maintainer is assigned.
|
||||
typecode = nil if typecode.size == 0
|
||||
if typecode && !TYPECODE.include?(typecode)
|
||||
raise ArgumentError,
|
||||
"bad typecode is specified: #{typecode}"
|
||||
end
|
||||
|
||||
# do escape
|
||||
|
||||
self.new('ftp',
|
||||
[user, password],
|
||||
host, port, nil,
|
||||
typecode ? path + TYPECODE_PREFIX + typecode : path,
|
||||
nil, nil, nil, arg_check)
|
||||
end
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Creates a new URI::FTP object from components, with syntax checking.
|
||||
#
|
||||
# The components accepted are +userinfo+, +host+, +port+, +path+ and
|
||||
# +typecode+.
|
||||
#
|
||||
# The components should be provided either as an Array, or as a Hash
|
||||
# with keys formed by preceding the component names with a colon.
|
||||
#
|
||||
# If an Array is used, the components must be passed in the order
|
||||
# [userinfo, host, port, path, typecode]
|
||||
#
|
||||
# If the path supplied is absolute, it will be escaped in order to
|
||||
# make it absolute in the URI. Examples:
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# uri = URI::FTP.build(['user:password', 'ftp.example.com', nil,
|
||||
# '/path/file.zip', 'i'])
|
||||
# puts uri.to_s -> ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i
|
||||
#
|
||||
# uri2 = URI::FTP.build({:host => 'ftp.example.com',
|
||||
# :path => 'ruby/src'})
|
||||
# puts uri2.to_s -> ftp://ftp.example.com/ruby/src
|
||||
#
|
||||
def self.build(args)
|
||||
|
||||
# Fix the incoming path to be generic URL syntax
|
||||
# FTP path -> URL path
|
||||
# foo/bar /foo/bar
|
||||
# /foo/bar /%2Ffoo/bar
|
||||
#
|
||||
if args.kind_of?(Array)
|
||||
args[3] = '/' + args[3].sub(/^\//, '%2F')
|
||||
else
|
||||
args[:path] = '/' + args[:path].sub(/^\//, '%2F')
|
||||
end
|
||||
|
||||
tmp = Util::make_components_hash(self, args)
|
||||
|
||||
if tmp[:typecode]
|
||||
if tmp[:typecode].size == 1
|
||||
tmp[:typecode] = TYPECODE_PREFIX + tmp[:typecode]
|
||||
end
|
||||
tmp[:path] << tmp[:typecode]
|
||||
end
|
||||
|
||||
return super(tmp)
|
||||
end
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Creates a new URI::FTP object from generic URL components with no
|
||||
# syntax checking.
|
||||
#
|
||||
# Unlike build(), this method does not escape the path component as
|
||||
# required by RFC1738; instead it is treated as per RFC2396.
|
||||
#
|
||||
# Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
|
||||
# +opaque+, +query+ and +fragment+, in that order.
|
||||
#
|
||||
def initialize(scheme,
|
||||
userinfo, host, port, registry,
|
||||
path, opaque,
|
||||
query,
|
||||
fragment,
|
||||
parser = nil,
|
||||
arg_check = false)
|
||||
raise InvalidURIError unless path
|
||||
path = path.sub(/^\//,'')
|
||||
path.sub!(/^%2F/,'/')
|
||||
super(scheme, userinfo, host, port, registry, path, opaque,
|
||||
query, fragment, parser, arg_check)
|
||||
@typecode = nil
|
||||
if tmp = @path.index(TYPECODE_PREFIX)
|
||||
typecode = @path[tmp + TYPECODE_PREFIX.size..-1]
|
||||
@path = @path[0..tmp - 1]
|
||||
|
||||
if arg_check
|
||||
self.typecode = typecode
|
||||
else
|
||||
self.set_typecode(typecode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# typecode accessor
|
||||
#
|
||||
# see URI::FTP::COMPONENT
|
||||
attr_reader :typecode
|
||||
|
||||
# validates typecode +v+,
|
||||
# returns a +true+ or +false+ boolean
|
||||
#
|
||||
def check_typecode(v)
|
||||
if TYPECODE.include?(v)
|
||||
return true
|
||||
else
|
||||
raise InvalidComponentError,
|
||||
"bad typecode(expected #{TYPECODE.join(', ')}): #{v}"
|
||||
end
|
||||
end
|
||||
private :check_typecode
|
||||
|
||||
# Private setter for the typecode +v+
|
||||
#
|
||||
# see also URI::FTP.typecode=
|
||||
#
|
||||
def set_typecode(v)
|
||||
@typecode = v
|
||||
end
|
||||
protected :set_typecode
|
||||
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +v+::
|
||||
# String
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# public setter for the typecode +v+.
|
||||
# (with validation)
|
||||
#
|
||||
# see also URI::FTP.check_typecode
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# uri = URI.parse("ftp://john@ftp.example.com/my_file.img")
|
||||
# #=> #<URI::FTP:0x00000000923650 URL:ftp://john@ftp.example.com/my_file.img>
|
||||
# uri.typecode = "i"
|
||||
# # => "i"
|
||||
# uri
|
||||
# #=> #<URI::FTP:0x00000000923650 URL:ftp://john@ftp.example.com/my_file.img;type=i>
|
||||
#
|
||||
def typecode=(typecode)
|
||||
check_typecode(typecode)
|
||||
set_typecode(typecode)
|
||||
typecode
|
||||
end
|
||||
|
||||
def merge(oth) # :nodoc:
|
||||
tmp = super(oth)
|
||||
if self != tmp
|
||||
tmp.set_typecode(oth.typecode)
|
||||
end
|
||||
|
||||
return tmp
|
||||
end
|
||||
|
||||
# Returns the path from an FTP URI.
|
||||
#
|
||||
# RFC 1738 specifically states that the path for an FTP URI does not
|
||||
# include the / which separates the URI path from the URI host. Example:
|
||||
#
|
||||
# +ftp://ftp.example.com/pub/ruby+
|
||||
#
|
||||
# The above URI indicates that the client should connect to
|
||||
# ftp.example.com then cd pub/ruby from the initial login directory.
|
||||
#
|
||||
# If you want to cd to an absolute directory, you must include an
|
||||
# escaped / (%2F) in the path. Example:
|
||||
#
|
||||
# +ftp://ftp.example.com/%2Fpub/ruby+
|
||||
#
|
||||
# This method will then return "/pub/ruby"
|
||||
#
|
||||
def path
|
||||
return @path.sub(/^\//,'').sub(/^%2F/,'/')
|
||||
end
|
||||
|
||||
# Private setter for the path of the URI::FTP
|
||||
def set_path(v)
|
||||
super("/" + v.sub(/^\//, "%2F"))
|
||||
end
|
||||
protected :set_path
|
||||
|
||||
# Returns a String representation of the URI::FTP
|
||||
def to_s
|
||||
save_path = nil
|
||||
if @typecode
|
||||
save_path = @path
|
||||
@path = @path + TYPECODE_PREFIX + @typecode
|
||||
end
|
||||
str = super
|
||||
if @typecode
|
||||
@path = save_path
|
||||
end
|
||||
|
||||
return str
|
||||
end
|
||||
end
|
||||
@@schemes['FTP'] = FTP
|
||||
end
|
||||
1563
gxg/standard/uri/generic.rb
Normal file
1563
gxg/standard/uri/generic.rb
Normal file
File diff suppressed because it is too large
Load diff
88
gxg/standard/uri/http.rb
Normal file
88
gxg/standard/uri/http.rb
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/http.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
||||
# Revision:: $Id: http.rb 59836 2017-09-11 06:12:40Z hsbt $
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/generic'
|
||||
|
||||
module URI
|
||||
|
||||
#
|
||||
# The syntax of HTTP URIs is defined in RFC1738 section 3.3.
|
||||
#
|
||||
# Note that the Ruby URI library allows HTTP URLs containing usernames and
|
||||
# passwords. This is not legal as per the RFC, but used to be
|
||||
# supported in Internet Explorer 5 and 6, before the MS04-004 security
|
||||
# update. See <URL:http://support.microsoft.com/kb/834489>.
|
||||
#
|
||||
class HTTP < Generic
|
||||
# A Default port of 80 for URI::HTTP
|
||||
DEFAULT_PORT = 80
|
||||
|
||||
# An Array of the available components for URI::HTTP
|
||||
COMPONENT = %i[
|
||||
scheme
|
||||
userinfo host port
|
||||
path
|
||||
query
|
||||
fragment
|
||||
].freeze
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Create a new URI::HTTP object from components, with syntax checking.
|
||||
#
|
||||
# The components accepted are userinfo, host, port, path, query and
|
||||
# fragment.
|
||||
#
|
||||
# The components should be provided either as an Array, or as a Hash
|
||||
# with keys formed by preceding the component names with a colon.
|
||||
#
|
||||
# If an Array is used, the components must be passed in the order
|
||||
# [userinfo, host, port, path, query, fragment].
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# newuri = URI::HTTP.build(host: 'www.example.com', path: '/foo/bar')
|
||||
#
|
||||
# newuri = URI::HTTP.build([nil, "www.example.com", nil, "/path",
|
||||
# "query", 'fragment'])
|
||||
#
|
||||
# Currently, if passed userinfo components this method generates
|
||||
# invalid HTTP URIs as per RFC 1738.
|
||||
#
|
||||
def self.build(args)
|
||||
tmp = Util.make_components_hash(self, args)
|
||||
super(tmp)
|
||||
end
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Returns the full path for an HTTP request, as required by Net::HTTP::Get.
|
||||
#
|
||||
# If the URI contains a query, the full path is URI#path + '?' + URI#query.
|
||||
# Otherwise, the path is simply URI#path.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# newuri = URI::HTTP.build(path: '/foo/bar', query: 'test=true')
|
||||
# newuri.request_uri # => "/foo/bar?test=true"
|
||||
#
|
||||
def request_uri
|
||||
return unless @path
|
||||
|
||||
url = @query ? "#@path?#@query" : @path.dup
|
||||
url.start_with?(?/.freeze) ? url : ?/ + url
|
||||
end
|
||||
end
|
||||
|
||||
@@schemes['HTTP'] = HTTP
|
||||
|
||||
end
|
||||
23
gxg/standard/uri/https.rb
Normal file
23
gxg/standard/uri/https.rb
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/https.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
||||
# Revision:: $Id: https.rb 53141 2015-12-16 05:07:31Z naruse $
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/http'
|
||||
|
||||
module URI
|
||||
|
||||
# The default port for HTTPS URIs is 443, and the scheme is 'https:' rather
|
||||
# than 'http:'. Other than that, HTTPS URIs are identical to HTTP URIs;
|
||||
# see URI::HTTP.
|
||||
class HTTPS < HTTP
|
||||
# A Default port of 443 for URI::HTTPS
|
||||
DEFAULT_PORT = 443
|
||||
end
|
||||
@@schemes['HTTPS'] = HTTPS
|
||||
end
|
||||
261
gxg/standard/uri/ldap.rb
Normal file
261
gxg/standard/uri/ldap.rb
Normal file
|
|
@ -0,0 +1,261 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/ldap.rb
|
||||
#
|
||||
# Author::
|
||||
# Takaaki Tateishi <ttate@jaist.ac.jp>
|
||||
# Akira Yamada <akira@ruby-lang.org>
|
||||
# License::
|
||||
# URI::LDAP is copyrighted free software by Takaaki Tateishi and Akira Yamada.
|
||||
# You can redistribute it and/or modify it under the same term as Ruby.
|
||||
# Revision:: $Id: ldap.rb 53141 2015-12-16 05:07:31Z naruse $
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/generic'
|
||||
|
||||
module URI
|
||||
|
||||
#
|
||||
# LDAP URI SCHEMA (described in RFC2255)
|
||||
# ldap://<host>/<dn>[?<attrs>[?<scope>[?<filter>[?<extensions>]]]]
|
||||
#
|
||||
class LDAP < Generic
|
||||
|
||||
# A Default port of 389 for URI::LDAP
|
||||
DEFAULT_PORT = 389
|
||||
|
||||
# An Array of the available components for URI::LDAP
|
||||
COMPONENT = [
|
||||
:scheme,
|
||||
:host, :port,
|
||||
:dn,
|
||||
:attributes,
|
||||
:scope,
|
||||
:filter,
|
||||
:extensions,
|
||||
].freeze
|
||||
|
||||
# Scopes available for the starting point.
|
||||
#
|
||||
# * SCOPE_BASE - the Base DN
|
||||
# * SCOPE_ONE - one level under the Base DN, not including the base DN and
|
||||
# not including any entries under this.
|
||||
# * SCOPE_SUB - subtress, all entries at all levels
|
||||
#
|
||||
SCOPE = [
|
||||
SCOPE_ONE = 'one',
|
||||
SCOPE_SUB = 'sub',
|
||||
SCOPE_BASE = 'base',
|
||||
].freeze
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Create a new URI::LDAP object from components, with syntax checking.
|
||||
#
|
||||
# The components accepted are host, port, dn, attributes,
|
||||
# scope, filter, and extensions.
|
||||
#
|
||||
# The components should be provided either as an Array, or as a Hash
|
||||
# with keys formed by preceding the component names with a colon.
|
||||
#
|
||||
# If an Array is used, the components must be passed in the order
|
||||
# [host, port, dn, attributes, scope, filter, extensions].
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# newuri = URI::LDAP.build({:host => 'ldap.example.com',
|
||||
# :dn> => '/dc=example'})
|
||||
#
|
||||
# newuri = URI::LDAP.build(["ldap.example.com", nil,
|
||||
# "/dc=example;dc=com", "query", nil, nil, nil])
|
||||
#
|
||||
def self.build(args)
|
||||
tmp = Util::make_components_hash(self, args)
|
||||
|
||||
if tmp[:dn]
|
||||
tmp[:path] = tmp[:dn]
|
||||
end
|
||||
|
||||
query = []
|
||||
[:extensions, :filter, :scope, :attributes].collect do |x|
|
||||
next if !tmp[x] && query.size == 0
|
||||
query.unshift(tmp[x])
|
||||
end
|
||||
|
||||
tmp[:query] = query.join('?')
|
||||
|
||||
return super(tmp)
|
||||
end
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Create a new URI::LDAP object from generic URI components as per
|
||||
# RFC 2396. No LDAP-specific syntax checking is performed.
|
||||
#
|
||||
# Arguments are +scheme+, +userinfo+, +host+, +port+, +registry+, +path+,
|
||||
# +opaque+, +query+ and +fragment+, in that order.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# uri = URI::LDAP.new("ldap", nil, "ldap.example.com", nil,
|
||||
# "/dc=example;dc=com", "query", nil, nil, nil, nil)
|
||||
#
|
||||
#
|
||||
# See also URI::Generic.new
|
||||
#
|
||||
def initialize(*arg)
|
||||
super(*arg)
|
||||
|
||||
if @fragment
|
||||
raise InvalidURIError, 'bad LDAP URL'
|
||||
end
|
||||
|
||||
parse_dn
|
||||
parse_query
|
||||
end
|
||||
|
||||
# private method to cleanup +dn+ from using the +path+ component attribute
|
||||
def parse_dn
|
||||
@dn = @path[1..-1]
|
||||
end
|
||||
private :parse_dn
|
||||
|
||||
# private method to cleanup +attributes+, +scope+, +filter+ and +extensions+,
|
||||
# from using the +query+ component attribute
|
||||
def parse_query
|
||||
@attributes = nil
|
||||
@scope = nil
|
||||
@filter = nil
|
||||
@extensions = nil
|
||||
|
||||
if @query
|
||||
attrs, scope, filter, extensions = @query.split('?')
|
||||
|
||||
@attributes = attrs if attrs && attrs.size > 0
|
||||
@scope = scope if scope && scope.size > 0
|
||||
@filter = filter if filter && filter.size > 0
|
||||
@extensions = extensions if extensions && extensions.size > 0
|
||||
end
|
||||
end
|
||||
private :parse_query
|
||||
|
||||
# private method to assemble +query+ from +attributes+, +scope+, +filter+ and +extensions+.
|
||||
def build_path_query
|
||||
@path = '/' + @dn
|
||||
|
||||
query = []
|
||||
[@extensions, @filter, @scope, @attributes].each do |x|
|
||||
next if !x && query.size == 0
|
||||
query.unshift(x)
|
||||
end
|
||||
@query = query.join('?')
|
||||
end
|
||||
private :build_path_query
|
||||
|
||||
# returns dn.
|
||||
def dn
|
||||
@dn
|
||||
end
|
||||
|
||||
# private setter for dn +val+
|
||||
def set_dn(val)
|
||||
@dn = val
|
||||
build_path_query
|
||||
@dn
|
||||
end
|
||||
protected :set_dn
|
||||
|
||||
# setter for dn +val+
|
||||
def dn=(val)
|
||||
set_dn(val)
|
||||
val
|
||||
end
|
||||
|
||||
# returns attributes.
|
||||
def attributes
|
||||
@attributes
|
||||
end
|
||||
|
||||
# private setter for attributes +val+
|
||||
def set_attributes(val)
|
||||
@attributes = val
|
||||
build_path_query
|
||||
@attributes
|
||||
end
|
||||
protected :set_attributes
|
||||
|
||||
# setter for attributes +val+
|
||||
def attributes=(val)
|
||||
set_attributes(val)
|
||||
val
|
||||
end
|
||||
|
||||
# returns scope.
|
||||
def scope
|
||||
@scope
|
||||
end
|
||||
|
||||
# private setter for scope +val+
|
||||
def set_scope(val)
|
||||
@scope = val
|
||||
build_path_query
|
||||
@scope
|
||||
end
|
||||
protected :set_scope
|
||||
|
||||
# setter for scope +val+
|
||||
def scope=(val)
|
||||
set_scope(val)
|
||||
val
|
||||
end
|
||||
|
||||
# returns filter.
|
||||
def filter
|
||||
@filter
|
||||
end
|
||||
|
||||
# private setter for filter +val+
|
||||
def set_filter(val)
|
||||
@filter = val
|
||||
build_path_query
|
||||
@filter
|
||||
end
|
||||
protected :set_filter
|
||||
|
||||
# setter for filter +val+
|
||||
def filter=(val)
|
||||
set_filter(val)
|
||||
val
|
||||
end
|
||||
|
||||
# returns extensions.
|
||||
def extensions
|
||||
@extensions
|
||||
end
|
||||
|
||||
# private setter for extensions +val+
|
||||
def set_extensions(val)
|
||||
@extensions = val
|
||||
build_path_query
|
||||
@extensions
|
||||
end
|
||||
protected :set_extensions
|
||||
|
||||
# setter for extensions +val+
|
||||
def extensions=(val)
|
||||
set_extensions(val)
|
||||
val
|
||||
end
|
||||
|
||||
# Checks if URI has a path
|
||||
# For URI::LDAP this will return +false+
|
||||
def hierarchical?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@schemes['LDAP'] = LDAP
|
||||
end
|
||||
21
gxg/standard/uri/ldaps.rb
Normal file
21
gxg/standard/uri/ldaps.rb
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/ldap.rb
|
||||
#
|
||||
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/ldap'
|
||||
|
||||
module URI
|
||||
|
||||
# The default port for LDAPS URIs is 636, and the scheme is 'ldaps:' rather
|
||||
# than 'ldap:'. Other than that, LDAPS URIs are identical to LDAP URIs;
|
||||
# see URI::LDAP.
|
||||
class LDAPS < LDAP
|
||||
# A Default port of 636 for URI::LDAPS
|
||||
DEFAULT_PORT = 636
|
||||
end
|
||||
@@schemes['LDAPS'] = LDAPS
|
||||
end
|
||||
294
gxg/standard/uri/mailto.rb
Normal file
294
gxg/standard/uri/mailto.rb
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
# frozen_string_literal: false
|
||||
# = uri/mailto.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
||||
# Revision:: $Id: mailto.rb 57066 2016-12-12 15:24:28Z naruse $
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
require 'uri/generic'
|
||||
|
||||
module URI
|
||||
|
||||
#
|
||||
# RFC6068, The mailto URL scheme
|
||||
#
|
||||
class MailTo < Generic
|
||||
include REGEXP
|
||||
|
||||
# A Default port of nil for URI::MailTo
|
||||
DEFAULT_PORT = nil
|
||||
|
||||
# An Array of the available components for URI::MailTo
|
||||
COMPONENT = [ :scheme, :to, :headers ].freeze
|
||||
|
||||
# :stopdoc:
|
||||
# "hname" and "hvalue" are encodings of an RFC 822 header name and
|
||||
# value, respectively. As with "to", all URL reserved characters must
|
||||
# be encoded.
|
||||
#
|
||||
# "#mailbox" is as specified in RFC 822 [RFC822]. This means that it
|
||||
# consists of zero or more comma-separated mail addresses, possibly
|
||||
# including "phrase" and "comment" components. Note that all URL
|
||||
# reserved characters in "to" must be encoded: in particular,
|
||||
# parentheses, commas, and the percent sign ("%"), which commonly occur
|
||||
# in the "mailbox" syntax.
|
||||
#
|
||||
# Within mailto URLs, the characters "?", "=", "&" are reserved.
|
||||
|
||||
# ; RFC 6068
|
||||
# hfields = "?" hfield *( "&" hfield )
|
||||
# hfield = hfname "=" hfvalue
|
||||
# hfname = *qchar
|
||||
# hfvalue = *qchar
|
||||
# qchar = unreserved / pct-encoded / some-delims
|
||||
# some-delims = "!" / "$" / "'" / "(" / ")" / "*"
|
||||
# / "+" / "," / ";" / ":" / "@"
|
||||
#
|
||||
# ; RFC3986
|
||||
# unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
|
||||
# pct-encoded = "%" HEXDIG HEXDIG
|
||||
HEADER_REGEXP = /\A(?<hfield>(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g<hfield>)*\z/
|
||||
# practical regexp for email address
|
||||
# http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
|
||||
EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
|
||||
# :startdoc:
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Creates a new URI::MailTo object from components, with syntax checking.
|
||||
#
|
||||
# Components can be provided as an Array or Hash. If an Array is used,
|
||||
# the components must be supplied as [to, headers].
|
||||
#
|
||||
# If a Hash is used, the keys are the component names preceded by colons.
|
||||
#
|
||||
# The headers can be supplied as a pre-encoded string, such as
|
||||
# "subject=subscribe&cc=address", or as an Array of Arrays like
|
||||
# [['subject', 'subscribe'], ['cc', 'address']]
|
||||
#
|
||||
# Examples:
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
|
||||
# puts m1.to_s -> mailto:joe@example.com?subject=Ruby
|
||||
#
|
||||
# m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
|
||||
# puts m2.to_s -> mailto:john@example.com?Subject=Ruby&Cc=jack@example.com
|
||||
#
|
||||
# m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
|
||||
# puts m3.to_s -> mailto:listman@example.com?subject=subscribe
|
||||
#
|
||||
def self.build(args)
|
||||
tmp = Util.make_components_hash(self, args)
|
||||
|
||||
case tmp[:to]
|
||||
when Array
|
||||
tmp[:opaque] = tmp[:to].join(',')
|
||||
when String
|
||||
tmp[:opaque] = tmp[:to].dup
|
||||
else
|
||||
tmp[:opaque] = ''
|
||||
end
|
||||
|
||||
if tmp[:headers]
|
||||
query =
|
||||
case tmp[:headers]
|
||||
when Array
|
||||
tmp[:headers].collect { |x|
|
||||
if x.kind_of?(Array)
|
||||
x[0] + '=' + x[1..-1].join
|
||||
else
|
||||
x.to_s
|
||||
end
|
||||
}.join('&')
|
||||
when Hash
|
||||
tmp[:headers].collect { |h,v|
|
||||
h + '=' + v
|
||||
}.join('&')
|
||||
else
|
||||
tmp[:headers].to_s
|
||||
end
|
||||
unless query.empty?
|
||||
tmp[:opaque] << '?' << query
|
||||
end
|
||||
end
|
||||
|
||||
super(tmp)
|
||||
end
|
||||
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Creates a new URI::MailTo object from generic URL components with
|
||||
# no syntax checking.
|
||||
#
|
||||
# This method is usually called from URI::parse, which checks
|
||||
# the validity of each component.
|
||||
#
|
||||
def initialize(*arg)
|
||||
super(*arg)
|
||||
|
||||
@to = nil
|
||||
@headers = []
|
||||
|
||||
# The RFC3986 parser does not normally populate opaque
|
||||
@opaque = "?#{@query}" if @query && !@opaque
|
||||
|
||||
unless @opaque
|
||||
raise InvalidComponentError,
|
||||
"missing opaque part for mailto URL"
|
||||
end
|
||||
to, header = @opaque.split('?', 2)
|
||||
# allow semicolon as a addr-spec separator
|
||||
# http://support.microsoft.com/kb/820868
|
||||
unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to
|
||||
raise InvalidComponentError,
|
||||
"unrecognised opaque part for mailtoURL: #{@opaque}"
|
||||
end
|
||||
|
||||
if arg[10] # arg_check
|
||||
self.to = to
|
||||
self.headers = header
|
||||
else
|
||||
set_to(to)
|
||||
set_headers(header)
|
||||
end
|
||||
end
|
||||
|
||||
# The primary e-mail address of the URL, as a String
|
||||
attr_reader :to
|
||||
|
||||
# E-mail headers set by the URL, as an Array of Arrays
|
||||
attr_reader :headers
|
||||
|
||||
# check the to +v+ component
|
||||
def check_to(v)
|
||||
return true unless v
|
||||
return true if v.size == 0
|
||||
|
||||
v.split(/[,;]/).each do |addr|
|
||||
# check url safety as path-rootless
|
||||
if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr
|
||||
raise InvalidComponentError,
|
||||
"an address in 'to' is invalid as URI #{addr.dump}"
|
||||
end
|
||||
|
||||
# check addr-spec
|
||||
# don't s/\+/ /g
|
||||
addr.gsub!(/%\h\h/, URI::TBLDECWWWCOMP_)
|
||||
if EMAIL_REGEXP !~ addr
|
||||
raise InvalidComponentError,
|
||||
"an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}"
|
||||
end
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
private :check_to
|
||||
|
||||
# private setter for to +v+
|
||||
def set_to(v)
|
||||
@to = v
|
||||
end
|
||||
protected :set_to
|
||||
|
||||
# setter for to +v+
|
||||
def to=(v)
|
||||
check_to(v)
|
||||
set_to(v)
|
||||
v
|
||||
end
|
||||
|
||||
# check the headers +v+ component against either
|
||||
# * HEADER_REGEXP
|
||||
def check_headers(v)
|
||||
return true unless v
|
||||
return true if v.size == 0
|
||||
if HEADER_REGEXP !~ v
|
||||
raise InvalidComponentError,
|
||||
"bad component(expected opaque component): #{v}"
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
private :check_headers
|
||||
|
||||
# private setter for headers +v+
|
||||
def set_headers(v)
|
||||
@headers = []
|
||||
if v
|
||||
v.split('&').each do |x|
|
||||
@headers << x.split(/=/, 2)
|
||||
end
|
||||
end
|
||||
end
|
||||
protected :set_headers
|
||||
|
||||
# setter for headers +v+
|
||||
def headers=(v)
|
||||
check_headers(v)
|
||||
set_headers(v)
|
||||
v
|
||||
end
|
||||
|
||||
# Constructs String from URI
|
||||
def to_s
|
||||
@scheme + ':' +
|
||||
if @to
|
||||
@to
|
||||
else
|
||||
''
|
||||
end +
|
||||
if @headers.size > 0
|
||||
'?' + @headers.collect{|x| x.join('=')}.join('&')
|
||||
else
|
||||
''
|
||||
end +
|
||||
if @fragment
|
||||
'#' + @fragment
|
||||
else
|
||||
''
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the RFC822 e-mail text equivalent of the URL, as a String.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# require 'uri'
|
||||
#
|
||||
# uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr")
|
||||
# uri.to_mailtext
|
||||
# # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n"
|
||||
#
|
||||
def to_mailtext
|
||||
to = URI.decode_www_form_component(@to)
|
||||
head = ''
|
||||
body = ''
|
||||
@headers.each do |x|
|
||||
case x[0]
|
||||
when 'body'
|
||||
body = URI.decode_www_form_component(x[1])
|
||||
when 'to'
|
||||
to << ', ' + URI.decode_www_form_component(x[1])
|
||||
else
|
||||
head << URI.decode_www_form_component(x[0]).capitalize + ': ' +
|
||||
URI.decode_www_form_component(x[1]) + "\n"
|
||||
end
|
||||
end
|
||||
|
||||
"To: #{to}
|
||||
#{head}
|
||||
#{body}
|
||||
"
|
||||
end
|
||||
alias to_rfc822text to_mailtext
|
||||
end
|
||||
|
||||
@@schemes['MAILTO'] = MailTo
|
||||
end
|
||||
544
gxg/standard/uri/rfc2396_parser.rb
Normal file
544
gxg/standard/uri/rfc2396_parser.rb
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
# frozen_string_literal: false
|
||||
#--
|
||||
# = uri/common.rb
|
||||
#
|
||||
# Author:: Akira Yamada <akira@ruby-lang.org>
|
||||
# Revision:: $Id$
|
||||
# License::
|
||||
# You can redistribute it and/or modify it under the same term as Ruby.
|
||||
#
|
||||
# See URI for general documentation
|
||||
#
|
||||
|
||||
module URI
|
||||
#
|
||||
# Includes URI::REGEXP::PATTERN
|
||||
#
|
||||
module RFC2396_REGEXP
|
||||
#
|
||||
# Patterns used to parse URI's
|
||||
#
|
||||
module PATTERN
|
||||
# :stopdoc:
|
||||
|
||||
# RFC 2396 (URI Generic Syntax)
|
||||
# RFC 2732 (IPv6 Literal Addresses in URL's)
|
||||
# RFC 2373 (IPv6 Addressing Architecture)
|
||||
|
||||
# alpha = lowalpha | upalpha
|
||||
ALPHA = "a-zA-Z"
|
||||
# alphanum = alpha | digit
|
||||
ALNUM = "#{ALPHA}\\d"
|
||||
|
||||
# hex = digit | "A" | "B" | "C" | "D" | "E" | "F" |
|
||||
# "a" | "b" | "c" | "d" | "e" | "f"
|
||||
HEX = "a-fA-F\\d"
|
||||
# escaped = "%" hex hex
|
||||
ESCAPED = "%[#{HEX}]{2}"
|
||||
# mark = "-" | "_" | "." | "!" | "~" | "*" | "'" |
|
||||
# "(" | ")"
|
||||
# unreserved = alphanum | mark
|
||||
UNRESERVED = "\\-_.!~*'()#{ALNUM}"
|
||||
# reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
|
||||
# "$" | ","
|
||||
# reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" |
|
||||
# "$" | "," | "[" | "]" (RFC 2732)
|
||||
RESERVED = ";/?:@&=+$,\\[\\]"
|
||||
|
||||
# domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
|
||||
DOMLABEL = "(?:[#{ALNUM}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
|
||||
# toplabel = alpha | alpha *( alphanum | "-" ) alphanum
|
||||
TOPLABEL = "(?:[#{ALPHA}](?:[-#{ALNUM}]*[#{ALNUM}])?)"
|
||||
# hostname = *( domainlabel "." ) toplabel [ "." ]
|
||||
HOSTNAME = "(?:#{DOMLABEL}\\.)*#{TOPLABEL}\\.?"
|
||||
|
||||
# :startdoc:
|
||||
end # PATTERN
|
||||
|
||||
# :startdoc:
|
||||
end # REGEXP
|
||||
|
||||
# class that Parses String's into URI's
|
||||
#
|
||||
# It contains a Hash set of patterns and Regexp's that match and validate.
|
||||
#
|
||||
class RFC2396_Parser
|
||||
include RFC2396_REGEXP
|
||||
|
||||
#
|
||||
# == Synopsis
|
||||
#
|
||||
# URI::Parser.new([opts])
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# The constructor accepts a hash as options for parser.
|
||||
# Keys of options are pattern names of URI components
|
||||
# and values of options are pattern strings.
|
||||
# The constructor generates set of regexps for parsing URIs.
|
||||
#
|
||||
# You can use the following keys:
|
||||
#
|
||||
# * :ESCAPED (URI::PATTERN::ESCAPED in default)
|
||||
# * :UNRESERVED (URI::PATTERN::UNRESERVED in default)
|
||||
# * :DOMLABEL (URI::PATTERN::DOMLABEL in default)
|
||||
# * :TOPLABEL (URI::PATTERN::TOPLABEL in default)
|
||||
# * :HOSTNAME (URI::PATTERN::HOSTNAME in default)
|
||||
#
|
||||
# == Examples
|
||||
#
|
||||
# p = URI::Parser.new(:ESCAPED => "(?:%[a-fA-F0-9]{2}|%u[a-fA-F0-9]{4})")
|
||||
# u = p.parse("http://example.jp/%uABCD") #=> #<URI::HTTP:0xb78cf4f8 URL:http://example.jp/%uABCD>
|
||||
# URI.parse(u.to_s) #=> raises URI::InvalidURIError
|
||||
#
|
||||
# s = "http://example.com/ABCD"
|
||||
# u1 = p.parse(s) #=> #<URI::HTTP:0xb78c3220 URL:http://example.com/ABCD>
|
||||
# u2 = URI.parse(s) #=> #<URI::HTTP:0xb78b6d54 URL:http://example.com/ABCD>
|
||||
# u1 == u2 #=> true
|
||||
# u1.eql?(u2) #=> false
|
||||
#
|
||||
def initialize(opts = {})
|
||||
@pattern = initialize_pattern(opts)
|
||||
@pattern.each_value(&:freeze)
|
||||
@pattern.freeze
|
||||
|
||||
@regexp = initialize_regexp(@pattern)
|
||||
@regexp.each_value(&:freeze)
|
||||
@regexp.freeze
|
||||
end
|
||||
|
||||
# The Hash of patterns.
|
||||
#
|
||||
# see also URI::Parser.initialize_pattern
|
||||
attr_reader :pattern
|
||||
|
||||
# The Hash of Regexp
|
||||
#
|
||||
# see also URI::Parser.initialize_regexp
|
||||
attr_reader :regexp
|
||||
|
||||
# Returns a split URI against regexp[:ABS_URI]
|
||||
def split(uri)
|
||||
case uri
|
||||
when ''
|
||||
# null uri
|
||||
|
||||
when @regexp[:ABS_URI]
|
||||
scheme, opaque, userinfo, host, port,
|
||||
registry, path, query, fragment = $~[1..-1]
|
||||
|
||||
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
|
||||
|
||||
# absoluteURI = scheme ":" ( hier_part | opaque_part )
|
||||
# hier_part = ( net_path | abs_path ) [ "?" query ]
|
||||
# opaque_part = uric_no_slash *uric
|
||||
|
||||
# abs_path = "/" path_segments
|
||||
# net_path = "//" authority [ abs_path ]
|
||||
|
||||
# authority = server | reg_name
|
||||
# server = [ [ userinfo "@" ] hostport ]
|
||||
|
||||
if !scheme
|
||||
raise InvalidURIError,
|
||||
"bad URI(absolute but no scheme): #{uri}"
|
||||
end
|
||||
if !opaque && (!path && (!host && !registry))
|
||||
raise InvalidURIError,
|
||||
"bad URI(absolute but no path): #{uri}"
|
||||
end
|
||||
|
||||
when @regexp[:REL_URI]
|
||||
scheme = nil
|
||||
opaque = nil
|
||||
|
||||
userinfo, host, port, registry,
|
||||
rel_segment, abs_path, query, fragment = $~[1..-1]
|
||||
if rel_segment && abs_path
|
||||
path = rel_segment + abs_path
|
||||
elsif rel_segment
|
||||
path = rel_segment
|
||||
elsif abs_path
|
||||
path = abs_path
|
||||
end
|
||||
|
||||
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
|
||||
|
||||
# relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
|
||||
|
||||
# net_path = "//" authority [ abs_path ]
|
||||
# abs_path = "/" path_segments
|
||||
# rel_path = rel_segment [ abs_path ]
|
||||
|
||||
# authority = server | reg_name
|
||||
# server = [ [ userinfo "@" ] hostport ]
|
||||
|
||||
else
|
||||
raise InvalidURIError, "bad URI(is not URI?): #{uri}"
|
||||
end
|
||||
|
||||
path = '' if !path && !opaque # (see RFC2396 Section 5.2)
|
||||
ret = [
|
||||
scheme,
|
||||
userinfo, host, port, # X
|
||||
registry, # X
|
||||
path, # Y
|
||||
opaque, # Y
|
||||
query,
|
||||
fragment
|
||||
]
|
||||
return ret
|
||||
end
|
||||
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +uri+::
|
||||
# String
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# parses +uri+ and constructs either matching URI scheme object
|
||||
# (FTP, HTTP, HTTPS, LDAP, LDAPS, or MailTo) or URI::Generic
|
||||
#
|
||||
# == Usage
|
||||
#
|
||||
# p = URI::Parser.new
|
||||
# p.parse("ldap://ldap.example.com/dc=example?user=john")
|
||||
# #=> #<URI::LDAP:0x00000000b9e7e8 URL:ldap://ldap.example.com/dc=example?user=john>
|
||||
#
|
||||
def parse(uri)
|
||||
scheme, userinfo, host, port,
|
||||
registry, path, opaque, query, fragment = self.split(uri)
|
||||
|
||||
if scheme && URI.scheme_list.include?(scheme.upcase)
|
||||
URI.scheme_list[scheme.upcase].new(scheme, userinfo, host, port,
|
||||
registry, path, opaque, query,
|
||||
fragment, self)
|
||||
else
|
||||
Generic.new(scheme, userinfo, host, port,
|
||||
registry, path, opaque, query,
|
||||
fragment, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +uris+::
|
||||
# an Array of Strings
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Attempts to parse and merge a set of URIs
|
||||
#
|
||||
def join(*uris)
|
||||
uris[0] = convert_to_uri(uris[0])
|
||||
uris.inject :merge
|
||||
end
|
||||
|
||||
#
|
||||
# :call-seq:
|
||||
# extract( str )
|
||||
# extract( str, schemes )
|
||||
# extract( str, schemes ) {|item| block }
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String to search
|
||||
# +schemes+::
|
||||
# Patterns to apply to +str+
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Attempts to parse and merge a set of URIs
|
||||
# If no +block+ given , then returns the result,
|
||||
# else it calls +block+ for each element in result.
|
||||
#
|
||||
# see also URI::Parser.make_regexp
|
||||
#
|
||||
def extract(str, schemes = nil)
|
||||
if block_given?
|
||||
str.scan(make_regexp(schemes)) { yield $& }
|
||||
nil
|
||||
else
|
||||
result = []
|
||||
str.scan(make_regexp(schemes)) { result.push $& }
|
||||
result
|
||||
end
|
||||
end
|
||||
|
||||
# returns Regexp that is default self.regexp[:ABS_URI_REF],
|
||||
# unless +schemes+ is provided. Then it is a Regexp.union with self.pattern[:X_ABS_URI]
|
||||
def make_regexp(schemes = nil)
|
||||
unless schemes
|
||||
@regexp[:ABS_URI_REF]
|
||||
else
|
||||
/(?=#{Regexp.union(*schemes)}:)#{@pattern[:X_ABS_URI]}/x
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# :call-seq:
|
||||
# escape( str )
|
||||
# escape( str, unsafe )
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String to make safe
|
||||
# +unsafe+::
|
||||
# Regexp to apply. Defaults to self.regexp[:UNSAFE]
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# constructs a safe String from +str+, removing unsafe characters,
|
||||
# replacing them with codes.
|
||||
#
|
||||
def escape(str, unsafe = @regexp[:UNSAFE])
|
||||
unless unsafe.kind_of?(Regexp)
|
||||
# perhaps unsafe is String object
|
||||
unsafe = Regexp.new("[#{Regexp.quote(unsafe)}]", false)
|
||||
end
|
||||
str.gsub(unsafe) do
|
||||
us = $&
|
||||
tmp = ''
|
||||
us.each_byte do |uc|
|
||||
tmp << sprintf('%%%02X', uc)
|
||||
end
|
||||
tmp
|
||||
end.force_encoding(Encoding::US_ASCII)
|
||||
end
|
||||
|
||||
#
|
||||
# :call-seq:
|
||||
# unescape( str )
|
||||
# unescape( str, unsafe )
|
||||
#
|
||||
# == Args
|
||||
#
|
||||
# +str+::
|
||||
# String to remove escapes from
|
||||
# +unsafe+::
|
||||
# Regexp to apply. Defaults to self.regexp[:ESCAPED]
|
||||
#
|
||||
# == Description
|
||||
#
|
||||
# Removes escapes from +str+
|
||||
#
|
||||
def unescape(str, escaped = @regexp[:ESCAPED])
|
||||
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(str.encoding)
|
||||
end
|
||||
|
||||
@@to_s = Kernel.instance_method(:to_s)
|
||||
def inspect
|
||||
@@to_s.bind(self).call
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Constructs the default Hash of patterns
|
||||
def initialize_pattern(opts = {})
|
||||
ret = {}
|
||||
ret[:ESCAPED] = escaped = (opts.delete(:ESCAPED) || PATTERN::ESCAPED)
|
||||
ret[:UNRESERVED] = unreserved = opts.delete(:UNRESERVED) || PATTERN::UNRESERVED
|
||||
ret[:RESERVED] = reserved = opts.delete(:RESERVED) || PATTERN::RESERVED
|
||||
ret[:DOMLABEL] = opts.delete(:DOMLABEL) || PATTERN::DOMLABEL
|
||||
ret[:TOPLABEL] = opts.delete(:TOPLABEL) || PATTERN::TOPLABEL
|
||||
ret[:HOSTNAME] = hostname = opts.delete(:HOSTNAME)
|
||||
|
||||
# RFC 2396 (URI Generic Syntax)
|
||||
# RFC 2732 (IPv6 Literal Addresses in URL's)
|
||||
# RFC 2373 (IPv6 Addressing Architecture)
|
||||
|
||||
# uric = reserved | unreserved | escaped
|
||||
ret[:URIC] = uric = "(?:[#{unreserved}#{reserved}]|#{escaped})"
|
||||
# uric_no_slash = unreserved | escaped | ";" | "?" | ":" | "@" |
|
||||
# "&" | "=" | "+" | "$" | ","
|
||||
ret[:URIC_NO_SLASH] = uric_no_slash = "(?:[#{unreserved};?:@&=+$,]|#{escaped})"
|
||||
# query = *uric
|
||||
ret[:QUERY] = query = "#{uric}*"
|
||||
# fragment = *uric
|
||||
ret[:FRAGMENT] = fragment = "#{uric}*"
|
||||
|
||||
# hostname = *( domainlabel "." ) toplabel [ "." ]
|
||||
# reg-name = *( unreserved / pct-encoded / sub-delims ) # RFC3986
|
||||
unless hostname
|
||||
ret[:HOSTNAME] = hostname = "(?:[a-zA-Z0-9\\-.]|%\\h\\h)+"
|
||||
end
|
||||
|
||||
# RFC 2373, APPENDIX B:
|
||||
# IPv6address = hexpart [ ":" IPv4address ]
|
||||
# IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
|
||||
# hexpart = hexseq | hexseq "::" [ hexseq ] | "::" [ hexseq ]
|
||||
# hexseq = hex4 *( ":" hex4)
|
||||
# hex4 = 1*4HEXDIG
|
||||
#
|
||||
# XXX: This definition has a flaw. "::" + IPv4address must be
|
||||
# allowed too. Here is a replacement.
|
||||
#
|
||||
# IPv4address = 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT "." 1*3DIGIT
|
||||
ret[:IPV4ADDR] = ipv4addr = "\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}"
|
||||
# hex4 = 1*4HEXDIG
|
||||
hex4 = "[#{PATTERN::HEX}]{1,4}"
|
||||
# lastpart = hex4 | IPv4address
|
||||
lastpart = "(?:#{hex4}|#{ipv4addr})"
|
||||
# hexseq1 = *( hex4 ":" ) hex4
|
||||
hexseq1 = "(?:#{hex4}:)*#{hex4}"
|
||||
# hexseq2 = *( hex4 ":" ) lastpart
|
||||
hexseq2 = "(?:#{hex4}:)*#{lastpart}"
|
||||
# IPv6address = hexseq2 | [ hexseq1 ] "::" [ hexseq2 ]
|
||||
ret[:IPV6ADDR] = ipv6addr = "(?:#{hexseq2}|(?:#{hexseq1})?::(?:#{hexseq2})?)"
|
||||
|
||||
# IPv6prefix = ( hexseq1 | [ hexseq1 ] "::" [ hexseq1 ] ) "/" 1*2DIGIT
|
||||
# unused
|
||||
|
||||
# ipv6reference = "[" IPv6address "]" (RFC 2732)
|
||||
ret[:IPV6REF] = ipv6ref = "\\[#{ipv6addr}\\]"
|
||||
|
||||
# host = hostname | IPv4address
|
||||
# host = hostname | IPv4address | IPv6reference (RFC 2732)
|
||||
ret[:HOST] = host = "(?:#{hostname}|#{ipv4addr}|#{ipv6ref})"
|
||||
# port = *digit
|
||||
ret[:PORT] = port = '\d*'
|
||||
# hostport = host [ ":" port ]
|
||||
ret[:HOSTPORT] = hostport = "#{host}(?::#{port})?"
|
||||
|
||||
# userinfo = *( unreserved | escaped |
|
||||
# ";" | ":" | "&" | "=" | "+" | "$" | "," )
|
||||
ret[:USERINFO] = userinfo = "(?:[#{unreserved};:&=+$,]|#{escaped})*"
|
||||
|
||||
# pchar = unreserved | escaped |
|
||||
# ":" | "@" | "&" | "=" | "+" | "$" | ","
|
||||
pchar = "(?:[#{unreserved}:@&=+$,]|#{escaped})"
|
||||
# param = *pchar
|
||||
param = "#{pchar}*"
|
||||
# segment = *pchar *( ";" param )
|
||||
segment = "#{pchar}*(?:;#{param})*"
|
||||
# path_segments = segment *( "/" segment )
|
||||
ret[:PATH_SEGMENTS] = path_segments = "#{segment}(?:/#{segment})*"
|
||||
|
||||
# server = [ [ userinfo "@" ] hostport ]
|
||||
server = "(?:#{userinfo}@)?#{hostport}"
|
||||
# reg_name = 1*( unreserved | escaped | "$" | "," |
|
||||
# ";" | ":" | "@" | "&" | "=" | "+" )
|
||||
ret[:REG_NAME] = reg_name = "(?:[#{unreserved}$,;:@&=+]|#{escaped})+"
|
||||
# authority = server | reg_name
|
||||
authority = "(?:#{server}|#{reg_name})"
|
||||
|
||||
# rel_segment = 1*( unreserved | escaped |
|
||||
# ";" | "@" | "&" | "=" | "+" | "$" | "," )
|
||||
ret[:REL_SEGMENT] = rel_segment = "(?:[#{unreserved};@&=+$,]|#{escaped})+"
|
||||
|
||||
# scheme = alpha *( alpha | digit | "+" | "-" | "." )
|
||||
ret[:SCHEME] = scheme = "[#{PATTERN::ALPHA}][\\-+.#{PATTERN::ALPHA}\\d]*"
|
||||
|
||||
# abs_path = "/" path_segments
|
||||
ret[:ABS_PATH] = abs_path = "/#{path_segments}"
|
||||
# rel_path = rel_segment [ abs_path ]
|
||||
ret[:REL_PATH] = rel_path = "#{rel_segment}(?:#{abs_path})?"
|
||||
# net_path = "//" authority [ abs_path ]
|
||||
ret[:NET_PATH] = net_path = "//#{authority}(?:#{abs_path})?"
|
||||
|
||||
# hier_part = ( net_path | abs_path ) [ "?" query ]
|
||||
ret[:HIER_PART] = hier_part = "(?:#{net_path}|#{abs_path})(?:\\?(?:#{query}))?"
|
||||
# opaque_part = uric_no_slash *uric
|
||||
ret[:OPAQUE_PART] = opaque_part = "#{uric_no_slash}#{uric}*"
|
||||
|
||||
# absoluteURI = scheme ":" ( hier_part | opaque_part )
|
||||
ret[:ABS_URI] = abs_uri = "#{scheme}:(?:#{hier_part}|#{opaque_part})"
|
||||
# relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]
|
||||
ret[:REL_URI] = rel_uri = "(?:#{net_path}|#{abs_path}|#{rel_path})(?:\\?#{query})?"
|
||||
|
||||
# URI-reference = [ absoluteURI | relativeURI ] [ "#" fragment ]
|
||||
ret[:URI_REF] = "(?:#{abs_uri}|#{rel_uri})?(?:##{fragment})?"
|
||||
|
||||
ret[:X_ABS_URI] = "
|
||||
(#{scheme}): (?# 1: scheme)
|
||||
(?:
|
||||
(#{opaque_part}) (?# 2: opaque)
|
||||
|
|
||||
(?:(?:
|
||||
//(?:
|
||||
(?:(?:(#{userinfo})@)? (?# 3: userinfo)
|
||||
(?:(#{host})(?::(\\d*))?))? (?# 4: host, 5: port)
|
||||
|
|
||||
(#{reg_name}) (?# 6: registry)
|
||||
)
|
||||
|
|
||||
(?!//)) (?# XXX: '//' is the mark for hostport)
|
||||
(#{abs_path})? (?# 7: path)
|
||||
)(?:\\?(#{query}))? (?# 8: query)
|
||||
)
|
||||
(?:\\#(#{fragment}))? (?# 9: fragment)
|
||||
"
|
||||
|
||||
ret[:X_REL_URI] = "
|
||||
(?:
|
||||
(?:
|
||||
//
|
||||
(?:
|
||||
(?:(#{userinfo})@)? (?# 1: userinfo)
|
||||
(#{host})?(?::(\\d*))? (?# 2: host, 3: port)
|
||||
|
|
||||
(#{reg_name}) (?# 4: registry)
|
||||
)
|
||||
)
|
||||
|
|
||||
(#{rel_segment}) (?# 5: rel_segment)
|
||||
)?
|
||||
(#{abs_path})? (?# 6: abs_path)
|
||||
(?:\\?(#{query}))? (?# 7: query)
|
||||
(?:\\#(#{fragment}))? (?# 8: fragment)
|
||||
"
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
# Constructs the default Hash of Regexp's
|
||||
def initialize_regexp(pattern)
|
||||
ret = {}
|
||||
|
||||
# for URI::split
|
||||
ret[:ABS_URI] = Regexp.new('\A\s*' + pattern[:X_ABS_URI] + '\s*\z', Regexp::EXTENDED)
|
||||
ret[:REL_URI] = Regexp.new('\A\s*' + pattern[:X_REL_URI] + '\s*\z', Regexp::EXTENDED)
|
||||
|
||||
# for URI::extract
|
||||
ret[:URI_REF] = Regexp.new(pattern[:URI_REF])
|
||||
ret[:ABS_URI_REF] = Regexp.new(pattern[:X_ABS_URI], Regexp::EXTENDED)
|
||||
ret[:REL_URI_REF] = Regexp.new(pattern[:X_REL_URI], Regexp::EXTENDED)
|
||||
|
||||
# for URI::escape/unescape
|
||||
ret[:ESCAPED] = Regexp.new(pattern[:ESCAPED])
|
||||
ret[:UNSAFE] = Regexp.new("[^#{pattern[:UNRESERVED]}#{pattern[:RESERVED]}]")
|
||||
|
||||
# for Generic#initialize
|
||||
ret[:SCHEME] = Regexp.new("\\A#{pattern[:SCHEME]}\\z")
|
||||
ret[:USERINFO] = Regexp.new("\\A#{pattern[:USERINFO]}\\z")
|
||||
ret[:HOST] = Regexp.new("\\A#{pattern[:HOST]}\\z")
|
||||
ret[:PORT] = Regexp.new("\\A#{pattern[:PORT]}\\z")
|
||||
ret[:OPAQUE] = Regexp.new("\\A#{pattern[:OPAQUE_PART]}\\z")
|
||||
ret[:REGISTRY] = Regexp.new("\\A#{pattern[:REG_NAME]}\\z")
|
||||
ret[:ABS_PATH] = Regexp.new("\\A#{pattern[:ABS_PATH]}\\z")
|
||||
ret[:REL_PATH] = Regexp.new("\\A#{pattern[:REL_PATH]}\\z")
|
||||
ret[:QUERY] = Regexp.new("\\A#{pattern[:QUERY]}\\z")
|
||||
ret[:FRAGMENT] = Regexp.new("\\A#{pattern[:FRAGMENT]}\\z")
|
||||
|
||||
ret
|
||||
end
|
||||
|
||||
def convert_to_uri(uri)
|
||||
if uri.is_a?(URI::Generic)
|
||||
uri
|
||||
elsif uri = String.try_convert(uri)
|
||||
parse(uri)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"bad argument (expected URI object or URI string)"
|
||||
end
|
||||
end
|
||||
|
||||
end # class Parser
|
||||
end # module URI
|
||||
125
gxg/standard/uri/rfc3986_parser.rb
Normal file
125
gxg/standard/uri/rfc3986_parser.rb
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
# frozen_string_literal: false
|
||||
module URI
|
||||
class RFC3986_Parser # :nodoc:
|
||||
# URI defined in RFC3986
|
||||
# this regexp is modified not to host is not empty string
|
||||
RFC3986_URI = /\A(?<URI>(?<scheme>[A-Za-z][+\-.0-9A-Za-z]*):(?<hier-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-rootless>\g<segment-nz>(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/
|
||||
RFC3986_relative_ref = /\A(?<relative-ref>(?<relative-part>\/\/(?<authority>(?:(?<userinfo>(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*)@)?(?<host>(?<IP-literal>\[(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{1,4}?::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:){,1}\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+)\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])+))?(?::(?<port>\d*))?)(?<path-abempty>(?:\/(?<segment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*))*)|(?<path-absolute>\/(?:(?<segment-nz>(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+)(?:\/\g<segment>)*)?)|(?<path-noscheme>(?<segment-nz-nc>(?:%\h\h|[!$&-.0-9;=@-Z_a-z~])+)(?:\/\g<segment>)*)|(?<path-empty>))(?:\?(?<query>[^#]*))?(?:\#(?<fragment>(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*))?)\z/
|
||||
attr_reader :regexp
|
||||
|
||||
def initialize
|
||||
@regexp = default_regexp.each_value(&:freeze).freeze
|
||||
end
|
||||
|
||||
def split(uri) #:nodoc:
|
||||
begin
|
||||
uri = uri.to_str
|
||||
rescue NoMethodError
|
||||
raise InvalidURIError, "bad URI(is not URI?): #{uri}"
|
||||
end
|
||||
uri.ascii_only? or
|
||||
raise InvalidURIError, "URI must be ascii only #{uri.dump}"
|
||||
if m = RFC3986_URI.match(uri)
|
||||
query = m["query".freeze]
|
||||
scheme = m["scheme".freeze]
|
||||
opaque = m["path-rootless".freeze]
|
||||
if opaque
|
||||
opaque << "?#{query}" if query
|
||||
[ scheme,
|
||||
nil, # userinfo
|
||||
nil, # host
|
||||
nil, # port
|
||||
nil, # registry
|
||||
nil, # path
|
||||
opaque,
|
||||
nil, # query
|
||||
m["fragment".freeze]
|
||||
]
|
||||
else # normal
|
||||
[ scheme,
|
||||
m["userinfo".freeze],
|
||||
m["host".freeze],
|
||||
m["port".freeze],
|
||||
nil, # registry
|
||||
(m["path-abempty".freeze] ||
|
||||
m["path-absolute".freeze] ||
|
||||
m["path-empty".freeze]),
|
||||
nil, # opaque
|
||||
query,
|
||||
m["fragment".freeze]
|
||||
]
|
||||
end
|
||||
elsif m = RFC3986_relative_ref.match(uri)
|
||||
[ nil, # scheme
|
||||
m["userinfo".freeze],
|
||||
m["host".freeze],
|
||||
m["port".freeze],
|
||||
nil, # registry,
|
||||
(m["path-abempty".freeze] ||
|
||||
m["path-absolute".freeze] ||
|
||||
m["path-noscheme".freeze] ||
|
||||
m["path-empty".freeze]),
|
||||
nil, # opaque
|
||||
m["query".freeze],
|
||||
m["fragment".freeze]
|
||||
]
|
||||
else
|
||||
raise InvalidURIError, "bad URI(is not URI?): #{uri}"
|
||||
end
|
||||
end
|
||||
|
||||
def parse(uri) # :nodoc:
|
||||
scheme, userinfo, host, port,
|
||||
registry, path, opaque, query, fragment = self.split(uri)
|
||||
scheme_list = URI.scheme_list
|
||||
if scheme && scheme_list.include?(uc = scheme.upcase)
|
||||
scheme_list[uc].new(scheme, userinfo, host, port,
|
||||
registry, path, opaque, query,
|
||||
fragment, self)
|
||||
else
|
||||
Generic.new(scheme, userinfo, host, port,
|
||||
registry, path, opaque, query,
|
||||
fragment, self)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def join(*uris) # :nodoc:
|
||||
uris[0] = convert_to_uri(uris[0])
|
||||
uris.inject :merge
|
||||
end
|
||||
|
||||
@@to_s = Kernel.instance_method(:to_s)
|
||||
def inspect
|
||||
@@to_s.bind(self).call
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_regexp # :nodoc:
|
||||
{
|
||||
SCHEME: /\A[A-Za-z][A-Za-z0-9+\-.]*\z/,
|
||||
USERINFO: /\A(?:%\h\h|[!$&-.0-;=A-Z_a-z~])*\z/,
|
||||
HOST: /\A(?:(?<IP-literal>\[(?:(?<IPv6address>(?:\h{1,4}:){6}(?<ls32>\h{1,4}:\h{1,4}|(?<IPv4address>(?<dec-octet>[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]|\d)\.\g<dec-octet>\.\g<dec-octet>\.\g<dec-octet>))|::(?:\h{1,4}:){5}\g<ls32>|\h{,4}::(?:\h{1,4}:){4}\g<ls32>|(?:(?:\h{1,4}:)?\h{1,4})?::(?:\h{1,4}:){3}\g<ls32>|(?:(?:\h{1,4}:){,2}\h{1,4})?::(?:\h{1,4}:){2}\g<ls32>|(?:(?:\h{1,4}:){,3}\h{1,4})?::\h{1,4}:\g<ls32>|(?:(?:\h{1,4}:){,4}\h{1,4})?::\g<ls32>|(?:(?:\h{1,4}:){,5}\h{1,4})?::\h{1,4}|(?:(?:\h{1,4}:){,6}\h{1,4})?::)|(?<IPvFuture>v\h+\.[!$&-.0-;=A-Z_a-z~]+))\])|\g<IPv4address>|(?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*))\z/,
|
||||
ABS_PATH: /\A\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/,
|
||||
REL_PATH: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])+(?:\/(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*)*\z/,
|
||||
QUERY: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/,
|
||||
FRAGMENT: /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~\/?])*\z/,
|
||||
OPAQUE: /\A(?:[^\/].*)?\z/,
|
||||
PORT: /\A[\x09\x0a\x0c\x0d ]*\d*[\x09\x0a\x0c\x0d ]*\z/,
|
||||
}
|
||||
end
|
||||
|
||||
def convert_to_uri(uri)
|
||||
if uri.is_a?(URI::Generic)
|
||||
uri
|
||||
elsif uri = String.try_convert(uri)
|
||||
parse(uri)
|
||||
else
|
||||
raise ArgumentError,
|
||||
"bad argument (expected URI object or URI string)"
|
||||
end
|
||||
end
|
||||
|
||||
end # class Parser
|
||||
end # module URI
|
||||
Loading…
Reference in a new issue