/home/lnzliplg/public_html/ruby32.tar
share/man/man5/gemfile.5 0000644 00000055321 15173547333 0011000 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "GEMFILE" "5" "August 2023" "" ""
.
.SH "NAME"
\fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
.
.SH "SYNOPSIS"
A \fBGemfile\fR describes the gem dependencies required to execute associated Ruby code\.
.
.P
Place the \fBGemfile\fR in the root of the directory containing the associated code\. For instance, in a Rails application, place the \fBGemfile\fR in the same directory as the \fBRakefile\fR\.
.
.SH "SYNTAX"
A \fBGemfile\fR is evaluated as Ruby code, in a context which makes available a number of methods used to describe the gem requirements\.
.
.SH "GLOBAL SOURCE"
At the top of the \fBGemfile\fR, add a single line for the \fBRubyGems\fR source that contains the gems listed in the \fBGemfile\fR\.
.
.IP "" 4
.
.nf
source "https://rubygems\.org"
.
.fi
.
.IP "" 0
.
.P
You can add only one global source\. In Bundler 1\.13, adding multiple global sources was deprecated\. The \fBsource\fR \fBMUST\fR be a valid RubyGems repository\.
.
.P
To use more than one source of RubyGems, you should use \fI\fBsource\fR block\fR\.
.
.P
A source is checked for gems following the heuristics described in \fISOURCE PRIORITY\fR\.
.
.P
\fBNote about a behavior of the feature deprecated in Bundler 1\.13\fR: If a gem is found in more than one global source, Bundler will print a warning after installing the gem indicating which source was used, and listing the other sources where the gem is available\. A specific source can be selected for gems that need to use a non\-standard repository, suppressing this warning, by using the \fI\fB:source\fR option\fR or \fBsource\fR block\.
.
.SS "CREDENTIALS"
Some gem sources require a username and password\. Use bundle config(1) \fIbundle\-config\.1\.html\fR to set the username and password for any of the sources that need it\. The command must be run once on each computer that will install the Gemfile, but this keeps the credentials from being stored in plain text in version control\.
.
.IP "" 4
.
.nf
bundle config gems\.example\.com user:password
.
.fi
.
.IP "" 0
.
.P
For some sources, like a company Gemfury account, it may be easier to include the credentials in the Gemfile as part of the source URL\.
.
.IP "" 4
.
.nf
source "https://user:password@gems\.example\.com"
.
.fi
.
.IP "" 0
.
.P
Credentials in the source URL will take precedence over credentials set using \fBconfig\fR\.
.
.SH "RUBY"
If your application requires a specific Ruby version or engine, specify your requirements using the \fBruby\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\.
.
.SS "VERSION (required)"
The version of Ruby that your application requires\. If your application requires an alternate Ruby engine, such as JRuby, TruffleRuby, etc\., this should be the Ruby version that the engine is compatible with\.
.
.IP "" 4
.
.nf
ruby "3\.1\.2"
.
.fi
.
.IP "" 0
.
.P
If you wish to derive your Ruby version from a version file (ie \.ruby\-version), you can use the \fBfile\fR option instead\.
.
.IP "" 4
.
.nf
ruby file: "\.ruby\-version"
.
.fi
.
.IP "" 0
.
.SS "ENGINE"
Each application \fImay\fR specify a Ruby engine\. If an engine is specified, an engine version \fImust\fR also be specified\.
.
.P
What exactly is an Engine? \- A Ruby engine is an implementation of the Ruby language\.
.
.IP "\(bu" 4
For background: the reference or original implementation of the Ruby programming language is called Matz\'s Ruby Interpreter \fIhttps://en\.wikipedia\.org/wiki/Ruby_MRI\fR, or MRI for short\. This is named after Ruby creator Yukihiro Matsumoto, also known as Matz\. MRI is also known as CRuby, because it is written in C\. MRI is the most widely used Ruby engine\.
.
.IP "\(bu" 4
Other implementations \fIhttps://www\.ruby\-lang\.org/en/about/\fR of Ruby exist\. Some of the more well\-known implementations include JRuby \fIhttp://jruby\.org/\fR and TruffleRuby \fIhttps://www\.graalvm\.org/ruby/\fR\. Rubinius is an alternative implementation of Ruby written in Ruby\. JRuby is an implementation of Ruby on the JVM, short for Java Virtual Machine\. TruffleRuby is a Ruby implementation on the GraalVM, a language toolkit built on the JVM\.
.
.IP "" 0
.
.SS "ENGINE VERSION"
Each application \fImay\fR specify a Ruby engine version\. If an engine version is specified, an engine \fImust\fR also be specified\. If the engine is "ruby" the engine version specified \fImust\fR match the Ruby version\.
.
.IP "" 4
.
.nf
ruby "2\.6\.8", engine: "jruby", engine_version: "9\.3\.8\.0"
.
.fi
.
.IP "" 0
.
.SS "PATCHLEVEL"
Each application \fImay\fR specify a Ruby patchlevel\. Specifying the patchlevel has been meaningless since Ruby 2\.1\.0 was released as the patchlevel is now uniquely determined by a combination of major, minor, and teeny version numbers\.
.
.P
This option was implemented in Bundler 1\.4\.0 for Ruby 2\.0 or earlier\.
.
.IP "" 4
.
.nf
ruby "3\.1\.2", patchlevel: "20"
.
.fi
.
.IP "" 0
.
.SH "GEMS"
Specify gem requirements using the \fBgem\fR method, with the following arguments\. All parameters are \fBOPTIONAL\fR unless otherwise specified\.
.
.SS "NAME (required)"
For each gem requirement, list a single \fIgem\fR line\.
.
.IP "" 4
.
.nf
gem "nokogiri"
.
.fi
.
.IP "" 0
.
.SS "VERSION"
Each \fIgem\fR \fBMAY\fR have one or more version specifiers\.
.
.IP "" 4
.
.nf
gem "nokogiri", ">= 1\.4\.2"
gem "RedCloth", ">= 4\.1\.0", "< 4\.2\.0"
.
.fi
.
.IP "" 0
.
.SS "REQUIRE AS"
Each \fIgem\fR \fBMAY\fR specify files that should be used when autorequiring via \fBBundler\.require\fR\. You may pass an array with multiple files or \fBtrue\fR if the file you want \fBrequired\fR has the same name as \fIgem\fR or \fBfalse\fR to prevent any file from being autorequired\.
.
.IP "" 4
.
.nf
gem "redis", require: ["redis/connection/hiredis", "redis"]
gem "webmock", require: false
gem "byebug", require: true
.
.fi
.
.IP "" 0
.
.P
The argument defaults to the name of the gem\. For example, these are identical:
.
.IP "" 4
.
.nf
gem "nokogiri"
gem "nokogiri", require: "nokogiri"
gem "nokogiri", require: true
.
.fi
.
.IP "" 0
.
.SS "GROUPS"
Each \fIgem\fR \fBMAY\fR specify membership in one or more groups\. Any \fIgem\fR that does not specify membership in any group is placed in the \fBdefault\fR group\.
.
.IP "" 4
.
.nf
gem "rspec", group: :test
gem "wirble", groups: [:development, :test]
.
.fi
.
.IP "" 0
.
.P
The Bundler runtime allows its two main methods, \fBBundler\.setup\fR and \fBBundler\.require\fR, to limit their impact to particular groups\.
.
.IP "" 4
.
.nf
# setup adds gems to Ruby\'s load path
Bundler\.setup # defaults to all groups
require "bundler/setup" # same as Bundler\.setup
Bundler\.setup(:default) # only set up the _default_ group
Bundler\.setup(:test) # only set up the _test_ group (but `not` _default_)
Bundler\.setup(:default, :test) # set up the _default_ and _test_ groups, but no others
# require requires all of the gems in the specified groups
Bundler\.require # defaults to the _default_ group
Bundler\.require(:default) # identical
Bundler\.require(:default, :test) # requires the _default_ and _test_ groups
Bundler\.require(:test) # requires the _test_ group
.
.fi
.
.IP "" 0
.
.P
The Bundler CLI allows you to specify a list of groups whose gems \fBbundle install\fR should not install with the \fBwithout\fR configuration\.
.
.P
To specify multiple groups to ignore, specify a list of groups separated by spaces\.
.
.IP "" 4
.
.nf
bundle config set \-\-local without test
bundle config set \-\-local without development test
.
.fi
.
.IP "" 0
.
.P
Also, calling \fBBundler\.setup\fR with no parameters, or calling \fBrequire "bundler/setup"\fR will setup all groups except for the ones you excluded via \fB\-\-without\fR (since they are not available)\.
.
.P
Note that on \fBbundle install\fR, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies\. This means that you cannot list different versions of the same gems in different groups\. For more details, see Understanding Bundler \fIhttps://bundler\.io/rationale\.html\fR\.
.
.SS "PLATFORMS"
If a gem should only be used in a particular platform or set of platforms, you can specify them\. Platforms are essentially identical to groups, except that you do not need to use the \fB\-\-without\fR install\-time flag to exclude groups of gems for other platforms\.
.
.P
There are a number of \fBGemfile\fR platforms:
.
.TP
\fBruby\fR
C Ruby (MRI), Rubinius, or TruffleRuby, but not Windows
.
.TP
\fBmri\fR
C Ruby (MRI) only, but not Windows
.
.TP
\fBwindows\fR
Windows C Ruby (MRI), including RubyInstaller 32\-bit and 64\-bit versions
.
.TP
\fBmswin\fR
Windows C Ruby (MRI), including RubyInstaller 32\-bit versions
.
.TP
\fBmswin64\fR
Windows C Ruby (MRI), including RubyInstaller 64\-bit versions
.
.TP
\fBrbx\fR
Rubinius
.
.TP
\fBjruby\fR
JRuby
.
.TP
\fBtruffleruby\fR
TruffleRuby
.
.P
On platforms \fBruby\fR, \fBmri\fR, \fBmswin\fR, \fBmswin64\fR, and \fBwindows\fR, you may additionally specify a version by appending the major and minor version numbers without a delimiter\. For example, to specify that a gem should only be used on platform \fBruby\fR version 3\.1, use:
.
.IP "" 4
.
.nf
ruby_31
.
.fi
.
.IP "" 0
.
.P
As with groups (above), you may specify one or more platforms:
.
.IP "" 4
.
.nf
gem "weakling", platforms: :jruby
gem "ruby\-debug", platforms: :mri_31
gem "nokogiri", platforms: [:windows_31, :jruby]
.
.fi
.
.IP "" 0
.
.P
All operations involving groups (\fBbundle install\fR \fIbundle\-install\.1\.html\fR, \fBBundler\.setup\fR, \fBBundler\.require\fR) behave exactly the same as if any groups not matching the current platform were explicitly excluded\.
.
.SS "FORCE_RUBY_PLATFORM"
If you always want the pure ruby variant of a gem to be chosen over platform specific variants, you can use the \fBforce_ruby_platform\fR option:
.
.IP "" 4
.
.nf
gem "ffi", force_ruby_platform: true
.
.fi
.
.IP "" 0
.
.P
This can be handy (assuming the pure ruby variant works fine) when:
.
.IP "\(bu" 4
You\'re having issues with the platform specific variant\.
.
.IP "\(bu" 4
The platform specific variant does not yet support a newer ruby (and thus has a \fBrequired_ruby_version\fR upper bound), but you still want your Gemfile{\.lock} files to resolve under that ruby\.
.
.IP "" 0
.
.SS "SOURCE"
You can select an alternate RubyGems repository for a gem using the \':source\' option\.
.
.IP "" 4
.
.nf
gem "some_internal_gem", source: "https://gems\.example\.com"
.
.fi
.
.IP "" 0
.
.P
This forces the gem to be loaded from this source and ignores the global source declared at the top level of the file\. If the gem does not exist in this source, it will not be installed\.
.
.P
Bundler will search for child dependencies of this gem by first looking in the source selected for the parent, but if they are not found there, it will fall back on the global source\.
.
.P
\fBNote about a behavior of the feature deprecated in Bundler 1\.13\fR: Selecting a specific source repository this way also suppresses the ambiguous gem warning described above in \fIGLOBAL SOURCE\fR\.
.
.P
Using the \fB:source\fR option for an individual gem will also make that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when adding gems with explicit sources, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources\.
.
.SS "GIT"
If necessary, you can specify that a gem is located at a particular git repository using the \fB:git\fR parameter\. The repository can be accessed via several protocols:
.
.TP
\fBHTTP(S)\fR
gem "rails", git: "https://github\.com/rails/rails\.git"
.
.TP
\fBSSH\fR
gem "rails", git: "git@github\.com:rails/rails\.git"
.
.TP
\fBgit\fR
gem "rails", git: "git://github\.com/rails/rails\.git"
.
.P
If using SSH, the user that you use to run \fBbundle install\fR \fBMUST\fR have the appropriate keys available in their \fB$HOME/\.ssh\fR\.
.
.P
\fBNOTE\fR: \fBhttp://\fR and \fBgit://\fR URLs should be avoided if at all possible\. These protocols are unauthenticated, so a man\-in\-the\-middle attacker can deliver malicious code and compromise your system\. HTTPS and SSH are strongly preferred\.
.
.P
The \fBgroup\fR, \fBplatforms\fR, and \fBrequire\fR options are available and behave exactly the same as they would for a normal gem\.
.
.P
A git repository \fBSHOULD\fR have at least one file, at the root of the directory containing the gem, with the extension \fB\.gemspec\fR\. This file \fBMUST\fR contain a valid gem specification, as expected by the \fBgem build\fR command\.
.
.P
If a git repository does not have a \fB\.gemspec\fR, bundler will attempt to create one, but it will not contain any dependencies, executables, or C extension compilation instructions\. As a result, it may fail to properly integrate into your application\.
.
.P
If a git repository does have a \fB\.gemspec\fR for the gem you attached it to, a version specifier, if provided, means that the git repository is only valid if the \fB\.gemspec\fR specifies a version matching the version specifier\. If not, bundler will print a warning\.
.
.IP "" 4
.
.nf
gem "rails", "2\.3\.8", git: "https://github\.com/rails/rails\.git"
# bundle install will fail, because the \.gemspec in the rails
# repository\'s master branch specifies version 3\.0\.0
.
.fi
.
.IP "" 0
.
.P
If a git repository does \fBnot\fR have a \fB\.gemspec\fR for the gem you attached it to, a version specifier \fBMUST\fR be provided\. Bundler will use this version in the simple \fB\.gemspec\fR it creates\.
.
.P
Git repositories support a number of additional options\.
.
.TP
\fBbranch\fR, \fBtag\fR, and \fBref\fR
You \fBMUST\fR only specify at most one of these options\. The default is \fBbranch: "master"\fR\. For example:
.
.IP
gem "rails", git: "https://github\.com/rails/rails\.git", branch: "5\-0\-stable"
.
.IP
gem "rails", git: "https://github\.com/rails/rails\.git", tag: "v5\.0\.0"
.
.IP
gem "rails", git: "https://github\.com/rails/rails\.git", ref: "4aded"
.
.TP
\fBsubmodules\fR
For reference, a git submodule \fIhttps://git\-scm\.com/book/en/v2/Git\-Tools\-Submodules\fR lets you have another git repository within a subfolder of your repository\. Specify \fBsubmodules: true\fR to cause bundler to expand any submodules included in the git repository
.
.P
If a git repository contains multiple \fB\.gemspecs\fR, each \fB\.gemspec\fR represents a gem located at the same place in the file system as the \fB\.gemspec\fR\.
.
.IP "" 4
.
.nf
|~rails [git root]
| |\-rails\.gemspec [rails gem located here]
|~actionpack
| |\-actionpack\.gemspec [actionpack gem located here]
|~activesupport
| |\-activesupport\.gemspec [activesupport gem located here]
|\.\.\.
.
.fi
.
.IP "" 0
.
.P
To install a gem located in a git repository, bundler changes to the directory containing the gemspec, runs \fBgem build name\.gemspec\fR and then installs the resulting gem\. The \fBgem build\fR command, which comes standard with Rubygems, evaluates the \fB\.gemspec\fR in the context of the directory in which it is located\.
.
.SS "GIT SOURCE"
A custom git source can be defined via the \fBgit_source\fR method\. Provide the source\'s name as an argument, and a block which receives a single argument and interpolates it into a string to return the full repo address:
.
.IP "" 4
.
.nf
git_source(:stash){ |repo_name| "https://stash\.corp\.acme\.pl/#{repo_name}\.git" }
gem \'rails\', stash: \'forks/rails\'
.
.fi
.
.IP "" 0
.
.P
In addition, if you wish to choose a specific branch:
.
.IP "" 4
.
.nf
gem "rails", stash: "forks/rails", branch: "branch_name"
.
.fi
.
.IP "" 0
.
.SS "GITHUB"
\fBNOTE\fR: This shorthand should be avoided until Bundler 2\.0, since it currently expands to an insecure \fBgit://\fR URL\. This allows a man\-in\-the\-middle attacker to compromise your system\.
.
.P
If the git repository you want to use is hosted on GitHub and is public, you can use the :github shorthand to specify the github username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\.
.
.IP "" 4
.
.nf
gem "rails", github: "rails/rails"
gem "rails", github: "rails"
.
.fi
.
.IP "" 0
.
.P
Are both equivalent to
.
.IP "" 4
.
.nf
gem "rails", git: "https://github\.com/rails/rails\.git"
.
.fi
.
.IP "" 0
.
.P
Since the \fBgithub\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
.
.P
You can also directly pass a pull request URL:
.
.IP "" 4
.
.nf
gem "rails", github: "https://github\.com/rails/rails/pull/43753"
.
.fi
.
.IP "" 0
.
.P
Which is equivalent to:
.
.IP "" 4
.
.nf
gem "rails", github: "rails/rails", branch: "refs/pull/43753/head"
.
.fi
.
.IP "" 0
.
.SS "GIST"
If the git repository you want to use is hosted as a GitHub Gist and is public, you can use the :gist shorthand to specify the gist identifier (without the trailing "\.git")\.
.
.IP "" 4
.
.nf
gem "the_hatch", gist: "4815162342"
.
.fi
.
.IP "" 0
.
.P
Is equivalent to:
.
.IP "" 4
.
.nf
gem "the_hatch", git: "https://gist\.github\.com/4815162342\.git"
.
.fi
.
.IP "" 0
.
.P
Since the \fBgist\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
.
.SS "BITBUCKET"
If the git repository you want to use is hosted on Bitbucket and is public, you can use the :bitbucket shorthand to specify the bitbucket username and repository name (without the trailing "\.git"), separated by a slash\. If both the username and repository name are the same, you can omit one\.
.
.IP "" 4
.
.nf
gem "rails", bitbucket: "rails/rails"
gem "rails", bitbucket: "rails"
.
.fi
.
.IP "" 0
.
.P
Are both equivalent to
.
.IP "" 4
.
.nf
gem "rails", git: "https://rails@bitbucket\.org/rails/rails\.git"
.
.fi
.
.IP "" 0
.
.P
Since the \fBbitbucket\fR method is a specialization of \fBgit_source\fR, it accepts a \fB:branch\fR named argument\.
.
.SS "PATH"
You can specify that a gem is located in a particular location on the file system\. Relative paths are resolved relative to the directory containing the \fBGemfile\fR\.
.
.P
Similar to the semantics of the \fB:git\fR option, the \fB:path\fR option requires that the directory in question either contains a \fB\.gemspec\fR for the gem, or that you specify an explicit version that bundler should use\.
.
.P
Unlike \fB:git\fR, bundler does not compile C extensions for gems specified as paths\.
.
.IP "" 4
.
.nf
gem "rails", path: "vendor/rails"
.
.fi
.
.IP "" 0
.
.P
If you would like to use multiple local gems directly from the filesystem, you can set a global \fBpath\fR option to the path containing the gem\'s files\. This will automatically load gemspec files from subdirectories\.
.
.IP "" 4
.
.nf
path \'components\' do
gem \'admin_ui\'
gem \'public_ui\'
end
.
.fi
.
.IP "" 0
.
.SH "BLOCK FORM OF SOURCE, GIT, PATH, GROUP and PLATFORMS"
The \fB:source\fR, \fB:git\fR, \fB:path\fR, \fB:group\fR, and \fB:platforms\fR options may be applied to a group of gems by using block form\.
.
.IP "" 4
.
.nf
source "https://gems\.example\.com" do
gem "some_internal_gem"
gem "another_internal_gem"
end
git "https://github\.com/rails/rails\.git" do
gem "activesupport"
gem "actionpack"
end
platforms :ruby do
gem "ruby\-debug"
gem "sqlite3"
end
group :development, optional: true do
gem "wirble"
gem "faker"
end
.
.fi
.
.IP "" 0
.
.P
In the case of the group block form the :optional option can be given to prevent a group from being installed unless listed in the \fB\-\-with\fR option given to the \fBbundle install\fR command\.
.
.P
In the case of the \fBgit\fR block form, the \fB:ref\fR, \fB:branch\fR, \fB:tag\fR, and \fB:submodules\fR options may be passed to the \fBgit\fR method, and all gems in the block will inherit those options\.
.
.P
The presence of a \fBsource\fR block in a Gemfile also makes that source available as a possible global source for any other gems which do not specify explicit sources\. Thus, when defining source blocks, it is recommended that you also ensure all other gems in the Gemfile are using explicit sources, either via source blocks or \fB:source\fR directives on individual gems\.
.
.SH "INSTALL_IF"
The \fBinstall_if\fR method allows gems to be installed based on a proc or lambda\. This is especially useful for optional gems that can only be used if certain software is installed or some other conditions are met\.
.
.IP "" 4
.
.nf
install_if \-> { RUBY_PLATFORM =~ /darwin/ } do
gem "pasteboard"
end
.
.fi
.
.IP "" 0
.
.SH "GEMSPEC"
The \fB\.gemspec\fR \fIhttp://guides\.rubygems\.org/specification\-reference/\fR file is where you provide metadata about your gem to Rubygems\. Some required Gemspec attributes include the name, description, and homepage of your gem\. This is also where you specify the dependencies your gem needs to run\.
.
.P
If you wish to use Bundler to help install dependencies for a gem while it is being developed, use the \fBgemspec\fR method to pull in the dependencies listed in the \fB\.gemspec\fR file\.
.
.P
The \fBgemspec\fR method adds any runtime dependencies as gem requirements in the default group\. It also adds development dependencies as gem requirements in the \fBdevelopment\fR group\. Finally, it adds a gem requirement on your project (\fBpath: \'\.\'\fR)\. In conjunction with \fBBundler\.setup\fR, this allows you to require project files in your test code as you would if the project were installed as a gem; you need not manipulate the load path manually or require project files via relative paths\.
.
.P
The \fBgemspec\fR method supports optional \fB:path\fR, \fB:glob\fR, \fB:name\fR, and \fB:development_group\fR options, which control where bundler looks for the \fB\.gemspec\fR, the glob it uses to look for the gemspec (defaults to: \fB{,*,*/*}\.gemspec\fR), what named \fB\.gemspec\fR it uses (if more than one is present), and which group development dependencies are included in\.
.
.P
When a \fBgemspec\fR dependency encounters version conflicts during resolution, the local version under development will always be selected \-\- even if there are remote versions that better match other requirements for the \fBgemspec\fR gem\.
.
.SH "SOURCE PRIORITY"
When attempting to locate a gem to satisfy a gem requirement, bundler uses the following priority order:
.
.IP "1." 4
The source explicitly attached to the gem (using \fB:source\fR, \fB:path\fR, or \fB:git\fR)
.
.IP "2." 4
For implicit gems (dependencies of explicit gems), any source, git, or path repository declared on the parent\. This results in bundler prioritizing the ActiveSupport gem from the Rails git repository over ones from \fBrubygems\.org\fR
.
.IP "3." 4
If neither of the above conditions are met, the global source will be used\. If multiple global sources are specified, they will be prioritized from last to first, but this is deprecated since Bundler 1\.13, so Bundler prints a warning and will abort with an error in the future\.
.
.IP "" 0
share/man/man1/bundle-list.1 0000644 00000001703 15173547333 0011575 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-LIST" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-list\fR \- List all the gems in the bundle
.
.SH "SYNOPSIS"
\fBbundle list\fR [\-\-name\-only] [\-\-paths] [\-\-without\-group=GROUP[ GROUP\.\.\.]] [\-\-only\-group=GROUP[ GROUP\.\.\.]]
.
.SH "DESCRIPTION"
Prints a list of all the gems in the bundle including their version\.
.
.P
Example:
.
.P
bundle list \-\-name\-only
.
.P
bundle list \-\-paths
.
.P
bundle list \-\-without\-group test
.
.P
bundle list \-\-only\-group dev
.
.P
bundle list \-\-only\-group dev test \-\-paths
.
.SH "OPTIONS"
.
.TP
\fB\-\-name\-only\fR
Print only the name of each gem\.
.
.TP
\fB\-\-paths\fR
Print the path to each gem in the bundle\.
.
.TP
\fB\-\-without\-group=<list>\fR
A space\-separated list of groups of gems to skip during printing\.
.
.TP
\fB\-\-only\-group=<list>\fR
A space\-separated list of groups of gems to print\.
share/man/man1/bundle-lock.1 0000644 00000006165 15173547333 0011561 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-LOCK" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
.
.SH "SYNOPSIS"
\fBbundle lock\fR [\-\-update] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-add\-platform] [\-\-remove\-platform] [\-\-patch] [\-\-minor] [\-\-major] [\-\-strict] [\-\-conservative]
.
.SH "DESCRIPTION"
Lock the gems specified in Gemfile\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-update=<*gems>\fR
Ignores the existing lockfile\. Resolve then updates lockfile\. Taking a list of gems or updating all gems if no list is given\.
.
.TP
\fB\-\-local\fR
Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if a appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.
.
.TP
\fB\-\-print\fR
Prints the lockfile to STDOUT instead of writing to the file system\.
.
.TP
\fB\-\-lockfile=<path>\fR
The path where the lockfile should be written to\.
.
.TP
\fB\-\-full\-index\fR
Fall back to using the single\-file index of all gems\.
.
.TP
\fB\-\-add\-platform\fR
Add a new platform to the lockfile, re\-resolving for the addition of that platform\.
.
.TP
\fB\-\-remove\-platform\fR
Remove a platform from the lockfile\.
.
.TP
\fB\-\-patch\fR
If updating, prefer updating only to next patch version\.
.
.TP
\fB\-\-minor\fR
If updating, prefer updating only to next minor version\.
.
.TP
\fB\-\-major\fR
If updating, prefer updating to next major version (default)\.
.
.TP
\fB\-\-strict\fR
If updating, do not allow any gem to be updated past latest \-\-patch | \-\-minor | \-\-major\.
.
.TP
\fB\-\-conservative\fR
If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated\.
.
.SH "UPDATING ALL GEMS"
If you run \fBbundle lock\fR with \fB\-\-update\fR option without list of gems, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
.
.SH "UPDATING A LIST OF GEMS"
Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
.
.P
For instance, you only want to update \fBnokogiri\fR, run \fBbundle lock \-\-update nokogiri\fR\.
.
.P
Bundler will update \fBnokogiri\fR and any of its dependencies, but leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
.
.SH "SUPPORTING OTHER PLATFORMS"
If you want your bundle to support platforms other than the one you\'re running locally, you can run \fBbundle lock \-\-add\-platform PLATFORM\fR to add PLATFORM to the lockfile, force bundler to re\-resolve and consider the new platform when picking gems, all without needing to have a machine that matches PLATFORM handy to install those platform\-specific gems on\.
.
.P
For a full explanation of gem platforms, see \fBgem help platform\fR\.
.
.SH "PATCH LEVEL OPTIONS"
See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
share/man/man1/bundle-update.1 0000644 00000033124 15173547333 0012106 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-UPDATE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-update\fR \- Update your gems to the latest available versions
.
.SH "SYNOPSIS"
\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-jobs=JOBS] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-redownload] [\-\-strict] [\-\-conservative]
.
.SH "DESCRIPTION"
Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\.
.
.P
You would use \fBbundle update\fR to explicitly update the version of a gem\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-all\fR
Update all gems specified in Gemfile\.
.
.TP
\fB\-\-group=<name>\fR, \fB\-g=[<name>]\fR
Only update the gems in the specified group\. For instance, you can update all gems in the development group with \fBbundle update \-\-group development\fR\. You can also call \fBbundle update rails \-\-group test\fR to update the rails gem and all gems in the test group, for example\.
.
.TP
\fB\-\-source=<name>\fR
The name of a \fB:git\fR or \fB:path\fR source used in the Gemfile(5)\. For instance, with a \fB:git\fR source of \fBhttp://github\.com/rails/rails\.git\fR, you would call \fBbundle update \-\-source rails\fR
.
.TP
\fB\-\-local\fR
Do not attempt to fetch gems remotely and use the gem cache instead\.
.
.TP
\fB\-\-ruby\fR
Update the locked version of Ruby to the current version of Ruby\.
.
.TP
\fB\-\-bundler\fR
Update the locked version of bundler to the invoked bundler version\.
.
.TP
\fB\-\-full\-index\fR
Fall back to using the single\-file index of all gems\.
.
.TP
\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR
Specify the number of jobs to run in parallel\. The default is the number of available processors\.
.
.TP
\fB\-\-retry=[<number>]\fR
Retry failed network or git requests for \fInumber\fR times\.
.
.TP
\fB\-\-quiet\fR
Only output warnings and errors\.
.
.TP
\fB\-\-redownload\fR
Force downloading every gem\.
.
.TP
\fB\-\-patch\fR
Prefer updating only to next patch version\.
.
.TP
\fB\-\-minor\fR
Prefer updating only to next minor version\.
.
.TP
\fB\-\-major\fR
Prefer updating to next major version (default)\.
.
.TP
\fB\-\-strict\fR
Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\.
.
.TP
\fB\-\-conservative\fR
Use bundle install conservative update behavior and do not allow indirect dependencies to be updated\.
.
.SH "UPDATING ALL GEMS"
If you run \fBbundle update \-\-all\fR, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
.
.P
Consider the following Gemfile(5):
.
.IP "" 4
.
.nf
source "https://rubygems\.org"
gem "rails", "3\.0\.0\.rc"
gem "nokogiri"
.
.fi
.
.IP "" 0
.
.P
When you run bundle install(1) \fIbundle\-install\.1\.html\fR the first time, bundler will resolve all of the dependencies, all the way down, and install what you need:
.
.IP "" 4
.
.nf
Fetching gem metadata from https://rubygems\.org/\.\.\.\.\.\.\.\.\.
Resolving dependencies\.\.\.
Installing builder 2\.1\.2
Installing abstract 1\.0\.0
Installing rack 1\.2\.8
Using bundler 1\.7\.6
Installing rake 10\.4\.0
Installing polyglot 0\.3\.5
Installing mime\-types 1\.25\.1
Installing i18n 0\.4\.2
Installing mini_portile 0\.6\.1
Installing tzinfo 0\.3\.42
Installing rack\-mount 0\.6\.14
Installing rack\-test 0\.5\.7
Installing treetop 1\.4\.15
Installing thor 0\.14\.6
Installing activesupport 3\.0\.0\.rc
Installing erubis 2\.6\.6
Installing activemodel 3\.0\.0\.rc
Installing arel 0\.4\.0
Installing mail 2\.2\.20
Installing activeresource 3\.0\.0\.rc
Installing actionpack 3\.0\.0\.rc
Installing activerecord 3\.0\.0\.rc
Installing actionmailer 3\.0\.0\.rc
Installing railties 3\.0\.0\.rc
Installing rails 3\.0\.0\.rc
Installing nokogiri 1\.6\.5
Bundle complete! 2 Gemfile dependencies, 26 gems total\.
Use `bundle show [gemname]` to see where a bundled gem is installed\.
.
.fi
.
.IP "" 0
.
.P
As you can see, even though you have two gems in the Gemfile(5), your application needs 26 different gems in order to run\. Bundler remembers the exact versions it installed in \fBGemfile\.lock\fR\. The next time you run bundle install(1) \fIbundle\-install\.1\.html\fR, bundler skips the dependency resolution and installs the same gems as it installed last time\.
.
.P
After checking in the \fBGemfile\.lock\fR into version control and cloning it on another machine, running bundle install(1) \fIbundle\-install\.1\.html\fR will \fIstill\fR install the gems that you installed last time\. You don\'t need to worry that a new release of \fBerubis\fR or \fBmail\fR changes the gems you use\.
.
.P
However, from time to time, you might want to update the gems you are using to the newest versions that still match the gems in your Gemfile(5)\.
.
.P
To do this, run \fBbundle update \-\-all\fR, which will ignore the \fBGemfile\.lock\fR, and resolve all the dependencies again\. Keep in mind that this process can result in a significantly different set of the 25 gems, based on the requirements of new gems that the gem authors released since the last time you ran \fBbundle update \-\-all\fR\.
.
.SH "UPDATING A LIST OF GEMS"
Sometimes, you want to update a single gem in the Gemfile(5), and leave the rest of the gems that you specified locked to the versions in the \fBGemfile\.lock\fR\.
.
.P
For instance, in the scenario above, imagine that \fBnokogiri\fR releases version \fB1\.4\.4\fR, and you want to update it \fIwithout\fR updating Rails and all of its dependencies\. To do this, run \fBbundle update nokogiri\fR\.
.
.P
Bundler will update \fBnokogiri\fR and any of its dependencies, but leave alone Rails and its dependencies\.
.
.SH "OVERLAPPING DEPENDENCIES"
Sometimes, multiple gems declared in your Gemfile(5) are satisfied by the same second\-level dependency\. For instance, consider the case of \fBthin\fR and \fBrack\-perftools\-profiler\fR\.
.
.IP "" 4
.
.nf
source "https://rubygems\.org"
gem "thin"
gem "rack\-perftools\-profiler"
.
.fi
.
.IP "" 0
.
.P
The \fBthin\fR gem depends on \fBrack >= 1\.0\fR, while \fBrack\-perftools\-profiler\fR depends on \fBrack ~> 1\.0\fR\. If you run bundle install, you get:
.
.IP "" 4
.
.nf
Fetching source index for https://rubygems\.org/
Installing daemons (1\.1\.0)
Installing eventmachine (0\.12\.10) with native extensions
Installing open4 (1\.0\.1)
Installing perftools\.rb (0\.4\.7) with native extensions
Installing rack (1\.2\.1)
Installing rack\-perftools_profiler (0\.0\.2)
Installing thin (1\.2\.7) with native extensions
Using bundler (1\.0\.0\.rc\.3)
.
.fi
.
.IP "" 0
.
.P
In this case, the two gems have their own set of dependencies, but they share \fBrack\fR in common\. If you run \fBbundle update thin\fR, bundler will update \fBdaemons\fR, \fBeventmachine\fR and \fBrack\fR, which are dependencies of \fBthin\fR, but not \fBopen4\fR or \fBperftools\.rb\fR, which are dependencies of \fBrack\-perftools_profiler\fR\. Note that \fBbundle update thin\fR will update \fBrack\fR even though it\'s \fIalso\fR a dependency of \fBrack\-perftools_profiler\fR\.
.
.P
In short, by default, when you update a gem using \fBbundle update\fR, bundler will update all dependencies of that gem, including those that are also dependencies of another gem\.
.
.P
To prevent updating indirect dependencies, prior to version 1\.14 the only option was the \fBCONSERVATIVE UPDATING\fR behavior in bundle install(1) \fIbundle\-install\.1\.html\fR:
.
.P
In this scenario, updating the \fBthin\fR version manually in the Gemfile(5), and then running bundle install(1) \fIbundle\-install\.1\.html\fR will only update \fBdaemons\fR and \fBeventmachine\fR, but not \fBrack\fR\. For more information, see the \fBCONSERVATIVE UPDATING\fR section of bundle install(1) \fIbundle\-install\.1\.html\fR\.
.
.P
Starting with 1\.14, specifying the \fB\-\-conservative\fR option will also prevent indirect dependencies from being updated\.
.
.SH "PATCH LEVEL OPTIONS"
Version 1\.14 introduced 4 patch\-level options that will influence how gem versions are resolved\. One of the following options can be used: \fB\-\-patch\fR, \fB\-\-minor\fR or \fB\-\-major\fR\. \fB\-\-strict\fR can be added to further influence resolution\.
.
.TP
\fB\-\-patch\fR
Prefer updating only to next patch version\.
.
.TP
\fB\-\-minor\fR
Prefer updating only to next minor version\.
.
.TP
\fB\-\-major\fR
Prefer updating to next major version (default)\.
.
.TP
\fB\-\-strict\fR
Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR | \fB\-\-major\fR\.
.
.P
When Bundler is resolving what versions to use to satisfy declared requirements in the Gemfile or in parent gems, it looks up all available versions, filters out any versions that don\'t satisfy the requirement, and then, by default, sorts them from newest to oldest, considering them in that order\.
.
.P
Providing one of the patch level options (e\.g\. \fB\-\-patch\fR) changes the sort order of the satisfying versions, causing Bundler to consider the latest \fB\-\-patch\fR or \fB\-\-minor\fR version available before other versions\. Note that versions outside the stated patch level could still be resolved to if necessary to find a suitable dependency graph\.
.
.P
For example, if gem \'foo\' is locked at 1\.0\.2, with no gem requirement defined in the Gemfile, and versions 1\.0\.3, 1\.0\.4, 1\.1\.0, 1\.1\.1, 2\.0\.0 all exist, the default order of preference by default (\fB\-\-major\fR) will be "2\.0\.0, 1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\.
.
.P
If the \fB\-\-patch\fR option is used, the order of preference will change to "1\.0\.4, 1\.0\.3, 1\.0\.2, 1\.1\.1, 1\.1\.0, 2\.0\.0"\.
.
.P
If the \fB\-\-minor\fR option is used, the order of preference will change to "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2, 2\.0\.0"\.
.
.P
Combining the \fB\-\-strict\fR option with any of the patch level options will remove any versions beyond the scope of the patch level option, to ensure that no gem is updated that far\.
.
.P
To continue the previous example, if both \fB\-\-patch\fR and \fB\-\-strict\fR options are used, the available versions for resolution would be "1\.0\.4, 1\.0\.3, 1\.0\.2"\. If \fB\-\-minor\fR and \fB\-\-strict\fR are used, it would be "1\.1\.1, 1\.1\.0, 1\.0\.4, 1\.0\.3, 1\.0\.2"\.
.
.P
Gem requirements as defined in the Gemfile will still be the first determining factor for what versions are available\. If the gem requirement for \fBfoo\fR in the Gemfile is \'~> 1\.0\', that will accomplish the same thing as providing the \fB\-\-minor\fR and \fB\-\-strict\fR options\.
.
.SH "PATCH LEVEL EXAMPLES"
Given the following gem specifications:
.
.IP "" 4
.
.nf
foo 1\.4\.3, requires: ~> bar 2\.0
foo 1\.4\.4, requires: ~> bar 2\.0
foo 1\.4\.5, requires: ~> bar 2\.1
foo 1\.5\.0, requires: ~> bar 2\.1
foo 1\.5\.1, requires: ~> bar 3\.0
bar with versions 2\.0\.3, 2\.0\.4, 2\.1\.0, 2\.1\.1, 3\.0\.0
.
.fi
.
.IP "" 0
.
.P
Gemfile:
.
.IP "" 4
.
.nf
gem \'foo\'
.
.fi
.
.IP "" 0
.
.P
Gemfile\.lock:
.
.IP "" 4
.
.nf
foo (1\.4\.3)
bar (~> 2\.0)
bar (2\.0\.3)
.
.fi
.
.IP "" 0
.
.P
Cases:
.
.IP "" 4
.
.nf
# Command Line Result
\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-
1 bundle update \-\-patch \'foo 1\.4\.5\', \'bar 2\.1\.1\'
2 bundle update \-\-patch foo \'foo 1\.4\.5\', \'bar 2\.1\.1\'
3 bundle update \-\-minor \'foo 1\.5\.1\', \'bar 3\.0\.0\'
4 bundle update \-\-minor \-\-strict \'foo 1\.5\.0\', \'bar 2\.1\.1\'
5 bundle update \-\-patch \-\-strict \'foo 1\.4\.4\', \'bar 2\.0\.4\'
.
.fi
.
.IP "" 0
.
.P
In case 1, bar is upgraded to 2\.1\.1, a minor version increase, because the dependency from foo 1\.4\.5 required it\.
.
.P
In case 2, only foo is requested to be unlocked, but bar is also allowed to move because it\'s not a declared dependency in the Gemfile\.
.
.P
In case 3, bar goes up a whole major release, because a minor increase is preferred now for foo, and when it goes to 1\.5\.1, it requires 3\.0\.0 of bar\.
.
.P
In case 4, foo is preferred up to a minor version, but 1\.5\.1 won\'t work because the \-\-strict flag removes bar 3\.0\.0 from consideration since it\'s a major increment\.
.
.P
In case 5, both foo and bar have any minor or major increments removed from consideration because of the \-\-strict flag, so the most they can move is up to 1\.4\.4 and 2\.0\.4\.
.
.SH "RECOMMENDED WORKFLOW"
In general, when working with an application managed with bundler, you should use the following workflow:
.
.IP "\(bu" 4
After you create your Gemfile(5) for the first time, run
.
.IP
$ bundle install
.
.IP "\(bu" 4
Check the resulting \fBGemfile\.lock\fR into version control
.
.IP
$ git add Gemfile\.lock
.
.IP "\(bu" 4
When checking out this repository on another development machine, run
.
.IP
$ bundle install
.
.IP "\(bu" 4
When checking out this repository on a deployment machine, run
.
.IP
$ bundle install \-\-deployment
.
.IP "\(bu" 4
After changing the Gemfile(5) to reflect a new or update dependency, run
.
.IP
$ bundle install
.
.IP "\(bu" 4
Make sure to check the updated \fBGemfile\.lock\fR into version control
.
.IP
$ git add Gemfile\.lock
.
.IP "\(bu" 4
If bundle install(1) \fIbundle\-install\.1\.html\fR reports a conflict, manually update the specific gems that you changed in the Gemfile(5)
.
.IP
$ bundle update rails thin
.
.IP "\(bu" 4
If you want to update all the gems to the latest possible versions that still match the gems listed in the Gemfile(5), run
.
.IP
$ bundle update \-\-all
.
.IP "" 0
share/man/man1/bundle-clean.1 0000644 00000001341 15173547333 0011702 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-CLEAN" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
.
.SH "SYNOPSIS"
\fBbundle clean\fR [\-\-dry\-run] [\-\-force]
.
.SH "DESCRIPTION"
This command will remove all unused gems in your bundler directory\. This is useful when you have made many changes to your gem dependencies\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-dry\-run\fR
Print the changes, but do not clean the unused gems\.
.
.TP
\fB\-\-force\fR
Forces cleaning up unused gems even if Bundler is configured to use globally installed gems\. As a consequence, removes all system gems except for the ones in the current application\.
share/man/man1/bundle-plugin.1 0000644 00000004425 15173547333 0012124 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-PLUGIN" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-plugin\fR \- Manage Bundler plugins
.
.SH "SYNOPSIS"
\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git|\-\-local_git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR]
.
.br
\fBbundle plugin\fR uninstall PLUGINS
.
.br
\fBbundle plugin\fR list
.
.br
\fBbundle plugin\fR help [COMMAND]
.
.SH "DESCRIPTION"
You can install, uninstall, and list plugin(s) with this command to extend functionalities of Bundler\.
.
.SH "SUB\-COMMANDS"
.
.SS "install"
Install the given plugin(s)\.
.
.IP "\(bu" 4
\fBbundle plugin install bundler\-graph\fR: Install bundler\-graph gem from RubyGems\.org\. The global source, specified in source in Gemfile is ignored\.
.
.IP "\(bu" 4
\fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR: Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\.
.
.IP "\(bu" 4
\fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR: You can specify the version of the gem via \fB\-\-version\fR\.
.
.IP "\(bu" 4
\fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR: Install bundler\-graph gem from Git repository\. \fB\-\-git\fR can be replaced with \fB\-\-local\-git\fR\. You cannot use both \fB\-\-git\fR and \fB\-\-local\-git\fR\. You can use standard Git URLs like:
.
.IP "\(bu" 4
\fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR
.
.IP "\(bu" 4
\fBhttp[s]://host\.xz[:port]/path/to/repo\.git\fR
.
.IP "\(bu" 4
\fB/path/to/repo\fR
.
.IP "\(bu" 4
\fBfile:///path/to/repo\fR
.
.IP "" 0
.
.IP
When you specify \fB\-\-git\fR/\fB\-\-local\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\. When you specify both, only the latter is used\.
.
.IP "" 0
.
.SS "uninstall"
Uninstall the plugin(s) specified in PLUGINS\.
.
.SS "list"
List the installed plugins and available commands\.
.
.P
No options\.
.
.SS "help"
Describe subcommands or one specific subcommand\.
.
.P
No options\.
.
.SH "SEE ALSO"
.
.IP "\(bu" 4
How to write a Bundler plugin \fIhttps://bundler\.io/guides/bundler_plugins\.html\fR
.
.IP "" 0
share/man/man1/erb.1 0000644 00000006376 15173547333 0010136 0 ustar 00 .\"Ruby is copyrighted by Yukihiro Matsumoto <matz@netlab.jp>.
.Dd December 16, 2018
.Dt ERB \&1 "Ruby Programmer's Reference Guide"
.Os UNIX
.Sh NAME
.Nm erb
.Nd Ruby Templating
.Sh SYNOPSIS
.Nm
.Op Fl -version
.Op Fl UPdnvx
.Op Fl E Ar ext Ns Op Ns : Ns int
.Op Fl S Ar level
.Op Fl T Ar mode
.Op Fl r Ar library
.Op Fl -
.Op file ...
.Pp
.Sh DESCRIPTION
.Nm
is a command line front-end for
.Li "ERB"
library, which is an implementation of eRuby.
.Pp
ERB provides an easy to use but powerful templating system for Ruby.
Using ERB, actual Ruby code can be added to any plain text document for the
purposes of generating document information details and/or flow control.
.Pp
.Nm
is a part of
.Nm Ruby .
.Pp
.Sh OPTIONS
.Bl -tag -width "1234567890123" -compact
.Pp
.It Fl -version
Prints the version of
.Nm .
.Pp
.It Fl E Ar external Ns Op : Ns Ar internal
.It Fl -encoding Ar external Ns Op : Ns Ar internal
Specifies the default value(s) for external encodings and internal encoding. Values should be separated with colon (:).
.Pp
You can omit the one for internal encodings, then the value
.Pf ( Li "Encoding.default_internal" ) will be nil.
.Pp
.It Fl P
Disables ruby code evaluation for lines beginning with
.Li "%" .
.Pp
.It Fl S Ar level
Specifies the safe level in which eRuby script will run.
.Pp
.It Fl T Ar mode
Specifies trim mode (default 0).
.Ar mode
can be one of
.Bl -hang -offset indent
.It Sy 0
EOL remains after the embedded ruby script is evaluated.
.Pp
.It Sy 1
EOL is removed if the line ends with
.Li "%>" .
.Pp
.It Sy 2
EOL is removed if the line starts with
.Li "<%"
and ends with
.Li "%>" .
.Pp
.It Sy -
EOL is removed if the line ends with
.Li "-%>" .
And leading whitespaces are removed if the erb directive starts with
.Li "<%-" .
.Pp
.El
.It Fl r
Load a library
.Pp
.It Fl U
can be one of
Sets the default value for internal encodings
.Pf ( Li "Encoding.default_internal" ) to UTF-8.
.Pp
.It Fl d
.It Fl -debug
Turns on debug mode.
.Li "$DEBUG"
will be set to true.
.Pp
.It Fl h
.It Fl -help
Prints a summary of the options.
.Pp
.It Fl n
Used with
.Fl x .
Prepends the line number to each line in the output.
.Pp
.It Fl v
Enables verbose mode.
.Li "$VERBOSE"
will be set to true.
.Pp
.It Fl x
Converts the eRuby script into Ruby script and prints it without line numbers.
.Pp
.El
.Pp
.Sh EXAMPLES
Here is an eRuby script
.Bd -literal -offset indent
<?xml version="1.0" ?>
<% require 'prime' -%>
<erb-example>
<calc><%= 1+1 %></calc>
<var><%= __FILE__ %></var>
<library><%= Prime.each(10).to_a.join(", ") %></library>
</erb-example>
.Ed
.Pp
Command
.Dl "% erb -T - example.erb"
prints
.Bd -literal -offset indent
<?xml version="1.0" ?>
<erb-example>
<calc>2</calc>
<var>example.erb</var>
<library>2, 3, 5, 7</library>
</erb-example>
.Ed
.Pp
.Sh SEE ALSO
.Xr ruby 1 .
.Pp
And see
.Xr ri 1
documentation for
.Li "ERB"
class.
.Pp
.Sh REPORTING BUGS
.Bl -bullet
.It
Security vulnerabilities should be reported via an email to
.Mt security@ruby-lang.org .
Reported problems will be published after being fixed.
.Pp
.It
Other bugs and feature requests can be reported via the
Ruby Issue Tracking System
.Pq Lk https://bugs.ruby-lang.org/ .
Do not report security vulnerabilities
via this system because it publishes the vulnerabilities immediately.
.El
.Sh AUTHORS
Written by Masatoshi SEKI.
share/man/man1/bundle-info.1 0000644 00000000737 15173547333 0011563 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-INFO" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-info\fR \- Show information for the given gem in your bundle
.
.SH "SYNOPSIS"
\fBbundle info\fR [GEM_NAME] [\-\-path]
.
.SH "DESCRIPTION"
Given a gem name present in your bundle, print the basic information about it such as homepage, version, path and summary\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-path\fR
Print the path of the given gem
share/man/man1/bundle-open.1 0000644 00000001503 15173547333 0011561 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-OPEN" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
.
.SH "SYNOPSIS"
\fBbundle open\fR [GEM] [\-\-path=PATH]
.
.SH "DESCRIPTION"
Opens the source directory of the provided GEM in your editor\.
.
.P
For this to work the \fBEDITOR\fR or \fBBUNDLER_EDITOR\fR environment variable has to be set\.
.
.P
Example:
.
.IP "" 4
.
.nf
bundle open \'rack\'
.
.fi
.
.IP "" 0
.
.P
Will open the source directory for the \'rack\' gem in your bundle\.
.
.IP "" 4
.
.nf
bundle open \'rack\' \-\-path \'README\.md\'
.
.fi
.
.IP "" 0
.
.P
Will open the README\.md file of the \'rack\' gem source in your bundle\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-path\fR
Specify GEM source relative path to open\.
share/man/man1/bundle-inject.1 0000644 00000001514 15173547333 0012076 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-INJECT" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
.
.SH "SYNOPSIS"
\fBbundle inject\fR [GEM] [VERSION]
.
.SH "DESCRIPTION"
Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\.
.
.P
This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn\'t listed yet\.
.
.P
Example:
.
.IP "" 4
.
.nf
bundle install
bundle inject \'rack\' \'> 0\'
.
.fi
.
.IP "" 0
.
.P
This will inject the \'rack\' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\.
.
.P
The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\.
share/man/man1/ruby.1 0000644 00000045644 15173547333 0010350 0 ustar 00 .\"Ruby is copyrighted by Yukihiro Matsumoto <matz@netlab.jp>.
.Dd April 14, 2018
.Dt RUBY \&1 "Ruby Programmer's Reference Guide"
.Os UNIX
.Sh NAME
.Nm ruby
.Nd Interpreted object-oriented scripting language
.Sh SYNOPSIS
.Nm
.Op Fl -copyright
.Op Fl -version
.Op Fl SUacdlnpswvy
.Op Fl 0 Ns Op Ar octal
.Op Fl C Ar directory
.Op Fl E Ar external Ns Op : Ns Ar internal
.Op Fl F Ns Op Ar pattern
.Op Fl I Ar directory
.Op Fl K Ns Op Ar c
.Op Fl T Ns Op Ar level
.Op Fl W Ns Op Ar level
.Op Fl e Ar command
.Op Fl i Ns Op Ar extension
.Op Fl r Ar library
.Op Fl x Ns Op Ar directory
.Op Fl - Ns Bro Cm enable Ns | Ns Cm disable Brc Ns - Ns Ar FEATURE
.Op Fl -dump Ns = Ns Ar target
.Op Fl -verbose
.Op Fl -
.Op Ar program_file
.Op Ar argument ...
.Sh DESCRIPTION
Ruby is an interpreted scripting language for quick and easy
object-oriented programming. It has many features to process text
files and to do system management tasks (like in Perl). It is simple,
straight-forward, and extensible.
.Pp
If you want a language for easy object-oriented programming, or you
don't like the Perl ugliness, or you do like the concept of LISP, but
don't like too many parentheses, Ruby might be your language of
choice.
.Sh FEATURES
Ruby's features are as follows:
.Bl -tag -width 6n
.It Sy "Interpretive"
Ruby is an interpreted language, so you don't have to recompile
programs written in Ruby to execute them.
.Pp
.It Sy "Variables have no type (dynamic typing)"
Variables in Ruby can contain data of any type. You don't have to
worry about variable typing. Consequently, it has a weaker compile
time check.
.Pp
.It Sy "No declaration needed"
You can use variables in your Ruby programs without any declarations.
Variable names denote their scope - global, class, instance, or local.
.Pp
.It Sy "Simple syntax"
Ruby has a simple syntax influenced slightly from Eiffel.
.Pp
.It Sy "No user-level memory management"
Ruby has automatic memory management. Objects no longer referenced
from anywhere are automatically collected by the garbage collector
built into the interpreter.
.Pp
.It Sy "Everything is an object"
Ruby is a purely object-oriented language, and was so since its
creation. Even such basic data as integers are seen as objects.
.Pp
.It Sy "Class, inheritance, and methods"
Being an object-oriented language, Ruby naturally has basic
features like classes, inheritance, and methods.
.Pp
.It Sy "Singleton methods"
Ruby has the ability to define methods for certain objects. For
example, you can define a press-button action for certain widget by
defining a singleton method for the button. Or, you can make up your
own prototype based object system using singleton methods, if you want
to.
.Pp
.It Sy "Mix-in by modules"
Ruby intentionally does not have the multiple inheritance as it is a
source of confusion. Instead, Ruby has the ability to share
implementations across the inheritance tree. This is often called a
.Sq Mix-in .
.Pp
.It Sy "Iterators"
Ruby has iterators for loop abstraction.
.Pp
.It Sy "Closures"
In Ruby, you can objectify the procedure.
.Pp
.It Sy "Text processing and regular expressions"
Ruby has a bunch of text processing features like in Perl.
.Pp
.It Sy "M17N, character set independent"
Ruby supports multilingualized programming. Easy to process texts
written in many different natural languages and encoded in many
different character encodings, without dependence on Unicode.
.Pp
.It Sy "Bignums"
With built-in bignums, you can for example calculate factorial(400).
.Pp
.It Sy "Reflection and domain specific languages"
Class is also an instance of the Class class. Definition of classes and methods
is an expression just as 1+1 is. So your programs can even write and modify programs.
Thus you can write your application in your own programming language on top of Ruby.
.Pp
.It Sy "Exception handling"
As in Java(tm).
.Pp
.It Sy "Direct access to the OS"
Ruby can use most
.Ux
system calls, often used in system programming.
.Pp
.It Sy "Dynamic loading"
On most
.Ux
systems, you can load object files into the Ruby interpreter
on-the-fly.
.It Sy "Rich libraries"
In addition to the
.Dq builtin libraries
and
.Dq standard libraries
that are bundled with Ruby, a vast amount of third-party libraries
.Pq Dq gems
are available via the package management system called
.Sq RubyGems ,
namely the
.Xr gem 1
command. Visit RubyGems.org
.Pq Lk https://rubygems.org/
to find the gems you need, and explore GitHub
.Pq Lk https://github.com/
to see how they are being developed and used.
.El
.Pp
.Sh OPTIONS
The Ruby interpreter accepts the following command-line options (switches).
They are quite similar to those of
.Xr perl 1 .
.Bl -tag -width "1234567890123" -compact
.Pp
.It Fl -copyright
Prints the copyright notice, and quits immediately without running any
script.
.Pp
.It Fl -version
Prints the version of the Ruby interpreter, and quits immediately without
running any script.
.Pp
.It Fl 0 Ns Op Ar octal
(The digit
.Dq zero . )
Specifies the input record separator
.Pf ( Li "$/" )
as an octal number. If no digit is given, the null character is taken
as the separator. Other switches may follow the digits.
.Fl 00
turns Ruby into paragraph mode.
.Fl 0777
makes Ruby read whole file at once as a single string since there is
no legal character with that value.
.Pp
.It Fl C Ar directory
.It Fl X Ar directory
Causes Ruby to switch to the directory.
.Pp
.It Fl E Ar external Ns Op : Ns Ar internal
.It Fl -encoding Ar external Ns Op : Ns Ar internal
Specifies the default value(s) for external encodings and internal encoding. Values should be separated with colon (:).
.Pp
You can omit the one for internal encodings, then the value
.Pf ( Li "Encoding.default_internal" ) will be nil.
.Pp
.It Fl -external-encoding Ns = Ns Ar encoding
.It Fl -internal-encoding Ns = Ns Ar encoding
Specify the default external or internal character encoding
.Pp
.It Fl F Ar pattern
Specifies input field separator
.Pf ( Li "$;" ) .
.Pp
.It Fl I Ar directory
Used to tell Ruby where to load the library scripts. Directory path
will be added to the load-path variable
.Pf ( Li "$:" ) .
.Pp
.It Fl K Ar kcode
Specifies KANJI (Japanese) encoding. The default value for script encodings
.Pf ( Li "__ENCODING__" ) and external encodings ( Li "Encoding.default_external" ) will be the specified one.
.Ar kcode
can be one of
.Bl -hang -offset indent
.It Sy e
EUC-JP
.Pp
.It Sy s
Windows-31J (CP932)
.Pp
.It Sy u
UTF-8
.Pp
.It Sy n
ASCII-8BIT (BINARY)
.El
.Pp
.It Fl S
Makes Ruby use the
.Ev PATH
environment variable to search for script, unless its name begins
with a slash. This is used to emulate
.Li #!
on machines that don't support it, in the following manner:
.Bd -literal -offset indent
#! /usr/local/bin/ruby
# This line makes the next one a comment in Ruby \e
exec /usr/local/bin/ruby -S $0 $*
.Ed
.Pp
On some systems
.Li "$0"
does not always contain the full pathname, so you need the
.Fl S
switch to tell Ruby to search for the script if necessary (to handle embedded
spaces and such). A better construct than
.Li "$*"
would be
.Li ${1+"$@"} ,
but it does not work if the script is being interpreted by
.Xr csh 1 .
.Pp
.It Fl T Ns Op Ar level=1
Turns on taint checks at the specified level (default 1).
.Pp
.It Fl U
Sets the default value for internal encodings
.Pf ( Li "Encoding.default_internal" ) to UTF-8.
.Pp
.It Fl W Ns Op Ar level=2
Turns on verbose mode at the specified level without printing the version
message at the beginning. The level can be;
.Bl -hang -offset indent
.It Sy 0
Verbose mode is "silence". It sets the
.Li "$VERBOSE"
to nil.
.Pp
.It Sy 1
Verbose mode is "medium". It sets the
.Li "$VERBOSE"
to false.
.Pp
.It Sy 2 (default)
Verbose mode is "verbose". It sets the
.Li "$VERBOSE"
to true.
.Fl W Ns
2 is the same as
.Fl w
.
.El
.Pp
.It Fl a
Turns on auto-split mode when used with
.Fl n
or
.Fl p .
In auto-split mode, Ruby executes
.Dl $F = $_.split
at beginning of each loop.
.Pp
.It Fl c
Causes Ruby to check the syntax of the script and exit without
executing. If there are no syntax errors, Ruby will print
.Dq Syntax OK
to the standard output.
.Pp
.It Fl d
.It Fl -debug
Turns on debug mode.
.Li "$DEBUG"
will be set to true.
.Pp
.It Fl e Ar command
Specifies script from command-line while telling Ruby not to search
the rest of the arguments for a script file name.
.Pp
.It Fl h
.It Fl -help
Prints a summary of the options.
.Pp
.It Fl i Ar extension
Specifies in-place-edit mode. The extension, if specified, is added
to old file name to make a backup copy. For example:
.Bd -literal -offset indent
% echo matz > /tmp/junk
% cat /tmp/junk
matz
% ruby -p -i.bak -e '$_.upcase!' /tmp/junk
% cat /tmp/junk
MATZ
% cat /tmp/junk.bak
matz
.Ed
.Pp
.It Fl l
(The lowercase letter
.Dq ell . )
Enables automatic line-ending processing, which means to firstly set
.Li "$\e"
to the value of
.Li "$/" ,
and secondly chops every line read using
.Li chomp! .
.Pp
.It Fl n
Causes Ruby to assume the following loop around your script, which
makes it iterate over file name arguments somewhat like
.Nm sed
.Fl n
or
.Nm awk .
.Bd -literal -offset indent
while gets
...
end
.Ed
.Pp
.It Fl p
Acts mostly same as -n switch, but print the value of variable
.Li "$_"
at the each end of the loop. For example:
.Bd -literal -offset indent
% echo matz | ruby -p -e '$_.tr! "a-z", "A-Z"'
MATZ
.Ed
.Pp
.It Fl r Ar library
Causes Ruby to load the library using require. It is useful when using
.Fl n
or
.Fl p .
.Pp
.It Fl s
Enables some switch parsing for switches after script name but before
any file name arguments (or before a
.Fl - ) .
Any switches found there are removed from
.Li ARGV
and set the corresponding variable in the script. For example:
.Bd -literal -offset indent
#! /usr/local/bin/ruby -s
# prints "true" if invoked with `-xyz' switch.
print "true\en" if $xyz
.Ed
.Pp
.It Fl v
Enables verbose mode. Ruby will print its version at the beginning
and set the variable
.Li "$VERBOSE"
to true. Some methods print extra messages if this variable is true.
If this switch is given, and no other switches are present, Ruby quits
after printing its version.
.Pp
.It Fl w
Enables verbose mode without printing version message at the
beginning. It sets the
.Li "$VERBOSE"
variable to true.
.Pp
.It Fl x Ns Op Ar directory
Tells Ruby that the script is embedded in a message. Leading garbage
will be discarded until the first line that starts with
.Dq #!
and contains the string,
.Dq ruby .
Any meaningful switches on that line will be applied. The end of the script
must be specified with either
.Li EOF ,
.Li "^D" ( Li "control-D" ) ,
.Li "^Z" ( Li "control-Z" ) ,
or the reserved word
.Li __END__ .
If the directory name is specified, Ruby will switch to that directory
before executing script.
.Pp
.It Fl y
.It Fl -yydebug
DO NOT USE.
.Pp
Turns on compiler debug mode. Ruby will print a bunch of internal
state messages during compilation. Only specify this switch you are going to
debug the Ruby interpreter.
.Pp
.It Fl -disable- Ns Ar FEATURE
.It Fl -enable- Ns Ar FEATURE
Disables (or enables) the specified
.Ar FEATURE .
.Bl -tag -width "--disable-rubyopt" -compact
.It Fl -disable-gems
.It Fl -enable-gems
Disables (or enables) RubyGems libraries. By default, Ruby will load the latest
version of each installed gem. The
.Li Gem
constant is true if RubyGems is enabled, false if otherwise.
.Pp
.It Fl -disable-rubyopt
.It Fl -enable-rubyopt
Ignores (or considers) the
.Ev RUBYOPT
environment variable. By default, Ruby considers the variable.
.Pp
.It Fl -disable-all
.It Fl -enable-all
Disables (or enables) all features.
.Pp
.El
.Pp
.It Fl -dump Ns = Ns Ar target
Dump some information.
.Pp
Prints the specified target.
.Ar target
can be one of:
.Bl -hang -offset indent
.It Sy version
Print version description (same as
.Fl -version).
.It Sy usage
Print a brief usage message (same as
.Fl h).
.It Sy help
Show long help message (same as
.Fl -help).
.It Sy syntax
Check syntax (same as
.Fl c
.Fl -yydebug).
.Pp
.El
.Pp
Or one of the following, which are intended for debugging the interpreter:
.Bl -hang -offset indent -tag -width "parsetree_with_comment"
.It Sy yydebug
Enable compiler debug mode (same as
.Fl -yydebug).
.It Sy parsetree
Print a textual representation of the Ruby AST for the program.
.It Sy parsetree_with_comment
Print a textual representation of the Ruby AST for the program, but with each node annoted with the associated Ruby source code.
.It Sy insns
Print a list of disassembled bytecode instructions.
.It Sy insns_without_opt
Print the list of disassembled bytecode instructions before various optimizations have been applied.
.El
.Pp
.It Fl -verbose
Enables verbose mode without printing version message at the
beginning. It sets the
.Li "$VERBOSE"
variable to true.
If this switch is given, and no script arguments (script file or
.Fl e
options) are present, Ruby quits immediately.
.El
.Pp
.Sh ENVIRONMENT
.Bl -tag -width "RUBYSHELL" -compact
.It Ev RUBYLIB
A colon-separated list of directories that are added to Ruby's
library load path
.Pf ( Li "$:" ) . Directories from this environment variable are searched
before the standard load path is searched.
.Pp
e.g.:
.Dl RUBYLIB="$HOME/lib/ruby:$HOME/lib/rubyext"
.Pp
.It Ev RUBYOPT
Additional Ruby options.
.Pp
e.g.
.Dl RUBYOPT="-w -Ke"
.Pp
Note that RUBYOPT can contain only
.Fl d , Fl E , Fl I , Fl K , Fl r , Fl T , Fl U , Fl v , Fl w , Fl W, Fl -debug ,
.Fl -disable- Ns Ar FEATURE
and
.Fl -enable- Ns Ar FEATURE .
.Pp
.It Ev RUBYPATH
A colon-separated list of directories that Ruby searches for
Ruby programs when the
.Fl S
flag is specified. This variable precedes the
.Ev PATH
environment variable.
.Pp
.It Ev RUBYSHELL
The path to the system shell command. This environment variable is
enabled for only mswin32, mingw32, and OS/2 platforms. If this
variable is not defined, Ruby refers to
.Ev COMSPEC .
.Pp
.It Ev PATH
Ruby refers to the
.Ev PATH
environment variable on calling Kernel#system.
.El
.Pp
And Ruby depends on some RubyGems related environment variables unless RubyGems is disabled.
See the help of
.Xr gem 1
as below.
.Bd -literal -offset indent
% gem help
.Ed
.Pp
.Sh GC ENVIRONMENT
The Ruby garbage collector (GC) tracks objects in fixed-sized slots,
but each object may have auxiliary memory allocations handled by the
malloc family of C standard library calls (
.Xr malloc 3 ,
.Xr calloc 3 ,
and
.Xr realloc 3 ) .
In this documentatation, the "heap" refers to the Ruby object heap
of fixed-sized slots, while "malloc" refers to auxiliary
allocations commonly referred to as the "process heap".
Thus there are at least two possible ways to trigger GC:
.Bl -hang -offset indent
.It Sy 1
Reaching the object limit.
.It Sy 2
Reaching the malloc limit.
.Pp
.El
In Ruby 2.1, the generational GC was introduced and the limits are divided
into young and old generations, providing two additional ways to trigger
a GC:
.Bl -hang -offset indent
.It Sy 3
Reaching the old object limit.
.It Sy 4
Reaching the old malloc limit.
.El
.Pp
There are currently 4 possible areas where the GC may be tuned by
the following 11 environment variables:
.Bl -hang -compact -width "RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR"
.It Ev RUBY_GC_HEAP_INIT_SLOTS
Initial allocation slots. Introduced in Ruby 2.1, default: 10000.
.Pp
.It Ev RUBY_GC_HEAP_FREE_SLOTS
Prepare at least this amount of slots after GC.
Allocate this number slots if there are not enough slots.
Introduced in Ruby 2.1, default: 4096
.Pp
.It Ev RUBY_GC_HEAP_GROWTH_FACTOR
Increase allocation rate of heap slots by this factor.
Introduced in Ruby 2.1, default: 1.8, minimum: 1.0 (no growth)
.Pp
.It Ev RUBY_GC_HEAP_GROWTH_MAX_SLOTS
Allocation rate is limited to this number of slots,
preventing excessive allocation due to RUBY_GC_HEAP_GROWTH_FACTOR.
Introduced in Ruby 2.1, default: 0 (no limit)
.Pp
.It Ev RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR
Perform a full GC when the number of old objects is more than R * N,
where R is this factor and N is the number of old objects after the
last full GC.
Introduced in Ruby 2.1.1, default: 2.0
.Pp
.It Ev RUBY_GC_MALLOC_LIMIT
The initial limit of young generation allocation from the malloc-family.
GC will start when this limit is reached.
Default: 16MB
.Pp
.It Ev RUBY_GC_MALLOC_LIMIT_MAX
The maximum limit of young generation allocation from malloc before GC starts.
Prevents excessive malloc growth due to RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR.
Introduced in Ruby 2.1, default: 32MB.
.Pp
.It Ev RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR
Increases the limit of young generation malloc calls, reducing
GC frequency but increasing malloc growth until RUBY_GC_MALLOC_LIMIT_MAX
is reached.
Introduced in Ruby 2.1, default: 1.4, minimum: 1.0 (no growth)
.Pp
.It Ev RUBY_GC_OLDMALLOC_LIMIT
The initial limit of old generation allocation from malloc,
a full GC will start when this limit is reached.
Introduced in Ruby 2.1, default: 16MB
.Pp
.It Ev RUBY_GC_OLDMALLOC_LIMIT_MAX
The maximum limit of old generation allocation from malloc before a
full GC starts.
Prevents excessive malloc growth due to RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR.
Introduced in Ruby 2.1, default: 128MB
.Pp
.It Ev RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR
Increases the limit of old generation malloc allocation, reducing full
GC frequency but increasing malloc growth until RUBY_GC_OLDMALLOC_LIMIT_MAX
is reached.
Introduced in Ruby 2.1, default: 1.2, minimum: 1.0 (no growth)
.Pp
.El
.Sh STACK SIZE ENVIRONMENT
Stack size environment variables are implementation-dependent and
subject to change with different versions of Ruby. The VM stack is used
for pure-Ruby code and managed by the virtual machine. Machine stack is
used by the operating system and its usage is dependent on C extensions
as well as C compiler options. Using lower values for these may allow
applications to keep more Fibers or Threads running; but increases the
chance of SystemStackError exceptions and segmentation faults (SIGSEGV).
These environment variables are available since Ruby 2.0.0.
All values are specified in bytes.
.Pp
.Bl -hang -compact -width "RUBY_THREAD_MACHINE_STACK_SIZE"
.It Ev RUBY_THREAD_VM_STACK_SIZE
VM stack size used at thread creation.
default: 524288 (32-bit CPU) or 1048575 (64-bit)
.Pp
.It Ev RUBY_THREAD_MACHINE_STACK_SIZE
Machine stack size used at thread creation.
default: 524288 or 1048575
.Pp
.It Ev RUBY_FIBER_VM_STACK_SIZE
VM stack size used at fiber creation.
default: 65536 or 131072
.Pp
.It Ev RUBY_FIBER_MACHINE_STACK_SIZE
Machine stack size used at fiber creation.
default: 262144 or 524288
.Pp
.El
.Sh SEE ALSO
.Bl -hang -compact -width "https://www.ruby-toolbox.com/"
.It Lk https://www.ruby-lang.org/
The official web site.
.It Lk https://www.ruby-toolbox.com/
Comprehensive catalog of Ruby libraries.
.El
.Pp
.Sh REPORTING BUGS
.Bl -bullet
.It
Security vulnerabilities should be reported via an email to
.Mt security@ruby-lang.org .
Reported problems will be published after being fixed.
.Pp
.It
Other bugs and feature requests can be reported via the
Ruby Issue Tracking System
.Pq Lk https://bugs.ruby-lang.org/ .
Do not report security vulnerabilities
via this system because it publishes the vulnerabilities immediately.
.El
.Sh AUTHORS
Ruby is designed and implemented by
.An Yukihiro Matsumoto Aq matz@netlab.jp .
.Pp
See
.Aq Lk https://bugs.ruby-lang.org/projects/ruby/wiki/Contributors
for contributors to Ruby.
share/man/man1/bundle-viz.1 0000644 00000002335 15173547333 0011434 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-VIZ" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
.
.SH "SYNOPSIS"
\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP]
.
.SH "DESCRIPTION"
\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\.
.
.P
The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\.
.
.P
\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-file\fR, \fB\-f\fR
The name to use for the generated file\. See \fB\-\-format\fR option
.
.TP
\fB\-\-format\fR, \fB\-F\fR
This is output format option\. Supported format is png, jpg, svg, dot \.\.\.
.
.TP
\fB\-\-requirements\fR, \fB\-R\fR
Set to show the version of each required dependency\.
.
.TP
\fB\-\-version\fR, \fB\-v\fR
Set to show each gem version\.
.
.TP
\fB\-\-without\fR, \fB\-W\fR
Exclude gems that are part of the specified named group\.
share/man/man1/bundle-console.1 0000644 00000003221 15173547333 0012261 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-CONSOLE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-console\fR \- Deprecated way to open an IRB session with the bundle pre\-loaded
.
.SH "SYNOPSIS"
\fBbundle console\fR [GROUP]
.
.SH "DESCRIPTION"
Starts an interactive Ruby console session in the context of the current bundle\.
.
.P
If no \fBGROUP\fR is specified, all gems in the \fBdefault\fR group in the Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR are preliminarily loaded\.
.
.P
If \fBGROUP\fR is specified, all gems in the given group in the Gemfile in addition to the gems in \fBdefault\fR group are loaded\. Even if the given group does not exist in the Gemfile, IRB console starts without any warning or error\.
.
.P
The environment variable \fBBUNDLE_CONSOLE\fR or \fBbundle config set console\fR can be used to change the shell from the following:
.
.IP "\(bu" 4
\fBirb\fR (default)
.
.IP "\(bu" 4
\fBpry\fR (https://github\.com/pry/pry)
.
.IP "\(bu" 4
\fBripl\fR (https://github\.com/cldwalker/ripl)
.
.IP "" 0
.
.P
\fBbundle console\fR uses irb by default\. An alternative Pry or Ripl can be used with \fBbundle console\fR by adjusting the \fBconsole\fR Bundler setting\. Also make sure that \fBpry\fR or \fBripl\fR is in your Gemfile\.
.
.SH "EXAMPLE"
.
.nf
$ bundle config set console pry
$ bundle console
Resolving dependencies\.\.\.
[1] pry(main)>
.
.fi
.
.SH "NOTES"
This command was deprecated in Bundler 2\.1 and will be removed in 3\.0\. Use \fBbin/console\fR script, which can be generated by \fBbundle gem <NAME>\fR\.
.
.SH "SEE ALSO"
Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR
share/man/man1/ri.1 0000644 00000012343 15173547333 0007767 0 ustar 00 .\"Ruby is copyrighted by Yukihiro Matsumoto <matz@netlab.jp>.
.Dd April 20, 2017
.Dt RI \&1 "Ruby Programmer's Reference Guide"
.Os UNIX
.Sh NAME
.Nm ri
.Nd Ruby API reference front end
.Sh SYNOPSIS
.Nm
.Op Fl ahilTv
.Op Fl d Ar DIRNAME
.Op Fl f Ar FORMAT
.Op Fl w Ar WIDTH
.Op Fl - Ns Oo Cm no- Oc Ns Cm pager
.Op Fl -server Ns Oo = Ns Ar PORT Oc
.Op Fl - Ns Oo Cm no- Oc Ns Cm list-doc-dirs
.Op Fl -no-standard-docs
.Op Fl - Ns Oo Cm no- Oc Ns Bro Cm system Ns | Ns Cm site Ns | Ns Cm gems Ns | Ns Cm home Brc
.Op Fl - Ns Oo Cm no- Oc Ns Cm profile
.Op Fl -dump Ns = Ns Ar CACHE
.Op Ar name ...
.Sh DESCRIPTION
.Nm
is a command-line front end for the Ruby API reference.
You can search and read the API reference for classes and methods with
.Nm .
.Pp
.Nm
is a part of Ruby.
.Pp
.Ar name
can be:
.Bl -diag -offset indent
.It Class | Module | Module::Class
.Pp
.It Class::method | Class#method | Class.method | method
.Pp
.It gem_name: | gem_name:README | gem_name:History
.El
.Pp
All class names may be abbreviated to their minimum unambiguous form.
If a name is ambiguous, all valid options will be listed.
.Pp
A
.Ql \&.
matches either class or instance methods, while #method
matches only instance and ::method matches only class methods.
.Pp
README and other files may be displayed by prefixing them with the gem name
they're contained in. If the gem name is followed by a
.Ql \&:
all files in the gem will be shown.
The file name extension may be omitted where it is unambiguous.
.Pp
For example:
.Bd -literal -offset indent
ri Fil
ri File
ri File.new
ri zip
ri rdoc:README
.Ed
.Pp
Note that shell quoting or escaping may be required for method names
containing punctuation:
.Bd -literal -offset indent
ri 'Array.[]'
ri compact\e!
.Ed
.Pp
To see the default directories
.Nm
will search, run:
.Bd -literal -offset indent
ri --list-doc-dirs
.Ed
.Pp
Specifying the
.Fl -system , Fl -site , Fl -home , Fl -gems ,
or
.Fl -doc-dir
options will limit
.Nm
to searching only the specified directories.
.Pp
.Nm
options may be set in the
.Ev RI
environment variable.
.Pp
The
.Nm
pager can be set with the
.Ev RI_PAGER
environment variable or the
.Ev PAGER
environment variable.
.Pp
.Sh OPTIONS
.Bl -tag -width "1234567890123" -compact
.Pp
.It Fl i
.It Fl - Ns Oo Cm no- Oc Ns Cm interactive
In interactive mode you can repeatedly
look up methods with autocomplete.
.Pp
.It Fl a
.It Fl - Ns Oo Cm no- Oc Ns Cm all
Show all documentation for a class or module.
.Pp
.It Fl l
.It Fl - Ns Oo Cm no- Oc Ns Cm list
List classes
.Nm
knows about.
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm pager
Send output to a pager,
rather than directly to stdout.
.Pp
.It Fl T
Synonym for
.Fl -no-pager .
.Pp
.It Fl w Ar WIDTH
.It Fl -width Ns = Ns Ar WIDTH
Set the width of the output.
.Pp
.It Fl -server Ns Oo = Ns Ar PORT Oc
Run RDoc server on the given port.
The default port is\~8214.
.Pp
.It Fl f Ar FORMAT
.It Fl -format Ns = Ns Ar FORMAT
Use the selected formatter.
The default formatter is
.Li bs
for paged output and
.Li ansi
otherwise.
Valid formatters are:
.Li ansi , Li bs , Li markdown , Li rdoc .
.Pp
.It Fl h
.It Fl -help
Show help and exit.
.Pp
.It Fl v
.It Fl -version
Output version information and exit.
.El
.Pp
Data source options:
.Bl -tag -width "1234567890123" -compact
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm list-doc-dirs
List the directories from which
.Nm
will source documentation on stdout and exit.
.Pp
.It Fl d Ar DIRNAME
.It Fl -doc-dir Ns = Ns Ar DIRNAME
List of directories from which to source
documentation in addition to the standard
directories. May be repeated.
.Pp
.It Fl -no-standard-docs
Do not include documentation from the Ruby standard library,
.Pa site_lib ,
installed gems, or
.Pa ~/.rdoc .
Use with
.Fl -doc-dir .
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm system
Include documentation from Ruby's standard library. Defaults to true.
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm site
Include documentation from libraries installed in
.Pa site_lib .
Defaults to true.
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm gems
Include documentation from RubyGems. Defaults to true.
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm home
Include documentation stored in
.Pa ~/.rdoc .
Defaults to true.
.El
.Pp
Debug options:
.Bl -tag -width "1234567890123" -compact
.Pp
.It Fl - Ns Oo Cm no- Oc Ns Cm profile
Run with the Ruby profiler.
.Pp
.It Fl -dump Ns = Ns Ar CACHE
Dump data from an ri cache or data file.
.El
.Pp
.Sh ENVIRONMENT
.Bl -tag -width "USERPROFILE" -compact
.Pp
.It Ev RI
Options to prepend to those specified on the command-line.
.Pp
.It Ev RI_PAGER
.It Ev PAGER
Pager program to use for displaying.
.Pp
.It Ev HOME
.It Ev USERPROFILE
.It Ev HOMEPATH
Path to the user's home directory.
.El
.Pp
.Sh FILES
.Bl -tag -width "USERPROFILE" -compact
.Pp
.It Pa ~/.rdoc
Path for ri data in the user's home directory.
.Pp
.El
.Pp
.Sh SEE ALSO
.Xr ruby 1 ,
.Xr rdoc 1 ,
.Xr gem 1
.Pp
.Sh REPORTING BUGS
.Bl -bullet
.It
Security vulnerabilities should be reported via an email to
.Mt security@ruby-lang.org .
Reported problems will be published after being fixed.
.Pp
.It
Other bugs and feature requests can be reported via the
Ruby Issue Tracking System
.Pq Lk https://bugs.ruby-lang.org/ .
Do not report security vulnerabilities
via this system because it publishes the vulnerabilities immediately.
.El
.Sh AUTHORS
Written by
.An Dave Thomas Aq dave@pragmaticprogrammer.com .
share/man/man1/bundle-version.1 0000644 00000001200 15173547333 0012277 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-VERSION" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-version\fR \- Prints Bundler version information
.
.SH "SYNOPSIS"
\fBbundle version\fR
.
.SH "DESCRIPTION"
Prints Bundler version information\.
.
.SH "OPTIONS"
No options\.
.
.SH "EXAMPLE"
Print the version of Bundler with build date and commit hash of the in the Git source\.
.
.IP "" 4
.
.nf
bundle version
.
.fi
.
.IP "" 0
.
.P
shows \fBBundler version 2\.3\.21 (2022\-08\-24 commit d54be5fdd8)\fR for example\.
.
.P
cf\. \fBbundle \-\-version\fR shows \fBBundler version 2\.3\.21\fR\.
share/man/man1/bundle-check.1 0000644 00000001704 15173547333 0011700 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-CHECK" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
.
.SH "SYNOPSIS"
\fBbundle check\fR [\-\-dry\-run] [\-\-gemfile=FILE] [\-\-path=PATH]
.
.SH "DESCRIPTION"
\fBcheck\fR searches the local machine for each of the gems requested in the Gemfile\. If all gems are found, Bundler prints a success message and exits with a status of 0\.
.
.P
If not, the first missing gem is listed and Bundler exits status 1\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-dry\-run\fR
Locks the [\fBGemfile(5)\fR][Gemfile(5)] before running the command\.
.
.TP
\fB\-\-gemfile\fR
Use the specified gemfile instead of the [\fBGemfile(5)\fR][Gemfile(5)]\.
.
.TP
\fB\-\-path\fR
Specify a different path than the system default (\fB$BUNDLE_PATH\fR or \fB$GEM_HOME\fR)\. Bundler will remember this value for future installs on this machine\.
share/man/man1/bundle-doctor.1 0000644 00000002213 15173547333 0012111 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-DOCTOR" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-doctor\fR \- Checks the bundle for common problems
.
.SH "SYNOPSIS"
\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE]
.
.SH "DESCRIPTION"
Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\.
.
.P
Examples of common problems caught by bundle\-doctor include:
.
.IP "\(bu" 4
Invalid Bundler settings
.
.IP "\(bu" 4
Mismatched Ruby versions
.
.IP "\(bu" 4
Mismatched platforms
.
.IP "\(bu" 4
Uninstalled gems
.
.IP "\(bu" 4
Missing dependencies
.
.IP "" 0
.
.SH "OPTIONS"
.
.TP
\fB\-\-quiet\fR
Only output warnings and errors\.
.
.TP
\fB\-\-gemfile=<gemfile>\fR
The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
share/man/man1/rake.1 0000644 00000007215 15173547333 0010301 0 ustar 00 .Dd June 12, 2016
.Dt RAKE 1
.Os rake 11.2.2
.Sh NAME
.Nm rake
.Nd make-like build utility for Ruby
.Sh SYNOPSIS
.Nm
.Op Fl f Ar rakefile
.Op Ar options
.Ar targets ...
.Sh DESCRIPTION
.Nm
is a
.Xr make 1 Ns -like
build utility for Ruby.
Tasks and dependencies are specified in standard Ruby syntax.
.Sh OPTIONS
.Bl -tag -width Ds
.It Fl m , Fl -multitask
Treat all tasks as multitasks.
.It Fl B , Fl -build-all
Build all prerequisites, including those which are up\-to\-date.
.It Fl j , Fl -jobs Ar num_jobs
Specifies the maximum number of tasks to execute in parallel (default is number of CPU cores + 4).
.El
.Ss Modules
.Bl -tag -width Ds
.It Fl I , Fl -libdir Ar libdir
Include
.Ar libdir
in the search path for required modules.
.It Fl r , Fl -require Ar module
Require
.Ar module
before executing
.Pa rakefile .
.El
.Ss Rakefile location
.Bl -tag -width Ds
.It Fl f , Fl -rakefile Ar filename
Use
.Ar filename
as the rakefile to search for.
.It Fl N , Fl -no-search , Fl -nosearch
Do not search parent directories for the Rakefile.
.It Fl G , Fl -no-system , Fl -nosystem
Use standard project Rakefile search paths, ignore system wide rakefiles.
.It Fl R , Fl -rakelib Ar rakelibdir , Fl -rakelibdir Ar rakelibdir
Auto-import any .rake files in
.Ar rakelibdir
(default is
.Sq rakelib )
.It Fl g , Fl -system
Use system-wide (global) rakefiles (usually
.Pa ~/.rake/*.rake ) .
.El
.Ss Debugging
.Bl -tag -width Ds
.It Fl -backtrace Ns = Ns Ar out
Enable full backtrace.
.Ar out
can be
.Dv stderr
(default) or
.Dv stdout .
.It Fl t , Fl -trace Ns = Ns Ar out
Turn on invoke/execute tracing, enable full backtrace.
.Ar out
can be
.Dv stderr
(default) or
.Dv stdout .
.It Fl -suppress-backtrace Ar pattern
Suppress backtrace lines matching regexp
.Ar pattern .
Ignored if
.Fl -trace
is on.
.It Fl -rules
Trace the rules resolution.
.It Fl n , Fl -dry-run
Do a dry run without executing actions.
.It Fl T , Fl -tasks Op Ar pattern
Display the tasks (matching optional
.Ar pattern )
with descriptions, then exit.
.It Fl D , Fl -describe Op Ar pattern
Describe the tasks (matching optional
.Ar pattern ) ,
then exit.
.It Fl W , Fl -where Op Ar pattern
Describe the tasks (matching optional
.Ar pattern ) ,
then exit.
.It Fl P , Fl -prereqs
Display the tasks and dependencies, then exit.
.It Fl e , Fl -execute Ar code
Execute some Ruby code and exit.
.It Fl p , Fl -execute-print Ar code
Execute some Ruby code, print the result, then exit.
.It Fl E , Fl -execute-continue Ar code
Execute some Ruby code, then continue with normal task processing.
.El
.Ss Information
.Bl -tag -width Ds
.It Fl v , Fl -verbose
Log message to standard output.
.It Fl q , Fl -quiet
Do not log messages to standard output.
.It Fl s , Fl -silent
Like
.Fl -quiet ,
but also suppresses the
.Sq in directory
announcement.
.It Fl X , Fl -no-deprecation-warnings
Disable the deprecation warnings.
.It Fl -comments
Show commented tasks only
.It Fl A , Fl -all
Show all tasks, even uncommented ones (in combination with
.Fl T
or
.Fl D )
.It Fl -job-stats Op Ar level
Display job statistics.
If
.Ar level
is
.Sq history ,
displays a complete job list.
.It Fl V , Fl -version
Display the program version.
.It Fl h , Fl H , Fl -help
Display a help message.
.El
.Sh SEE ALSO
The complete documentation for
.Nm rake
has been installed at
.Pa /usr/share/doc/rake-doc/html/index.html .
It is also available online at
.Lk https://ruby.github.io/rake .
.Sh AUTHORS
.An -nosplit
.Nm
was written by
.An Jim Weirich Aq Mt jim@weirichhouse.org .
.Pp
This manual was created by
.An Caitlin Matos Aq Mt caitlin.matos@zoho.com
for the Debian project (but may be used by others).
It was inspired by the manual by
.An Jani Monoses Aq Mt jani@iv.ro
for the Ubuntu project.
share/man/man1/bundle-platform.1 0000644 00000002664 15173547333 0012455 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-PLATFORM" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-platform\fR \- Displays platform compatibility information
.
.SH "SYNOPSIS"
\fBbundle platform\fR [\-\-ruby]
.
.SH "DESCRIPTION"
\fBplatform\fR displays information from your Gemfile, Gemfile\.lock, and Ruby VM about your platform\.
.
.P
For instance, using this Gemfile(5):
.
.IP "" 4
.
.nf
source "https://rubygems\.org"
ruby "3\.1\.2"
gem "rack"
.
.fi
.
.IP "" 0
.
.P
If you run \fBbundle platform\fR on Ruby 3\.1\.2, it displays the following output:
.
.IP "" 4
.
.nf
Your platform is: x86_64\-linux
Your app has gems that work on these platforms:
* arm64\-darwin\-21
* ruby
* x64\-mingw\-ucrt
* x86_64\-linux
Your Gemfile specifies a Ruby version requirement:
* ruby 3\.1\.2
Your current platform satisfies the Ruby version requirement\.
.
.fi
.
.IP "" 0
.
.P
\fBplatform\fR lists all the platforms in your \fBGemfile\.lock\fR as well as the \fBruby\fR directive if applicable from your Gemfile(5)\. It also lets you know if the \fBruby\fR directive requirement has been met\. If \fBruby\fR directive doesn\'t match the running Ruby VM, it tells you what part does not\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-ruby\fR
It will display the ruby directive information, so you don\'t have to parse it from the Gemfile(5)\.
.
.SH "SEE ALSO"
.
.IP "\(bu" 4
bundle\-lock(1) \fIbundle\-lock\.1\.html\fR
.
.IP "" 0
share/man/man1/bundle-config.1 0000644 00000052432 15173547333 0012074 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-CONFIG" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-config\fR \- Set bundler configuration options
.
.SH "SYNOPSIS"
\fBbundle config\fR list
.
.br
\fBbundle config\fR [get] NAME
.
.br
\fBbundle config\fR [set] NAME VALUE
.
.br
\fBbundle config\fR unset NAME
.
.SH "DESCRIPTION"
This command allows you to interact with Bundler\'s configuration system\.
.
.P
Bundler loads configuration settings in this order:
.
.IP "1." 4
Local config (\fB<project_root>/\.bundle/config\fR or \fB$BUNDLE_APP_CONFIG/config\fR)
.
.IP "2." 4
Environmental variables (\fBENV\fR)
.
.IP "3." 4
Global config (\fB~/\.bundle/config\fR)
.
.IP "4." 4
Bundler default config
.
.IP "" 0
.
.P
Executing \fBbundle config list\fR will print a list of all bundler configuration for the current bundle, and where that configuration was set\.
.
.P
Executing \fBbundle config get <name>\fR will print the value of that configuration setting, and where it was set\.
.
.P
Executing \fBbundle config set <name> <value>\fR defaults to setting \fBlocal\fR configuration if executing from within a local application, otherwise it will set \fBglobal\fR configuration\. See \fB\-\-local\fR and \fB\-\-global\fR options below\.
.
.P
Executing \fBbundle config set \-\-local <name> <value>\fR will set that configuration in the directory for the local application\. The configuration will be stored in \fB<project_root>/\.bundle/config\fR\. If \fBBUNDLE_APP_CONFIG\fR is set, the configuration will be stored in \fB$BUNDLE_APP_CONFIG/config\fR\.
.
.P
Executing \fBbundle config set \-\-global <name> <value>\fR will set that configuration to the value specified for all bundles executed as the current user\. The configuration will be stored in \fB~/\.bundle/config\fR\. If \fIname\fR already is set, \fIname\fR will be overridden and user will be warned\.
.
.P
Executing \fBbundle config unset <name>\fR will delete the configuration in both local and global sources\.
.
.P
Executing \fBbundle config unset \-\-global <name>\fR will delete the configuration only from the user configuration\.
.
.P
Executing \fBbundle config unset \-\-local <name>\fR will delete the configuration only from the local application\.
.
.P
Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
.
.SH "REMEMBERING OPTIONS"
Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\.
.
.P
However, this will be changed in bundler 3, so it\'s better not to rely on this behavior\. If these options must be remembered, it\'s better to set them using \fBbundle config\fR (e\.g\., \fBbundle config set \-\-local path foo\fR)\.
.
.P
The options that can be configured are:
.
.TP
\fBbin\fR
Creates a directory (defaults to \fB~/bin\fR) and place any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
.
.TP
\fBdeployment\fR
In deployment mode, Bundler will \'roll\-out\' the bundle for \fBproduction\fR use\. Please check carefully if you want to have this option enabled in \fBdevelopment\fR or \fBtest\fR environments\.
.
.TP
\fBonly\fR
A space\-separated list of groups to install only gems of the specified groups\.
.
.TP
\fBpath\fR
The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
.
.TP
\fBwithout\fR
A space\-separated list of groups referencing gems to skip during installation\.
.
.TP
\fBwith\fR
A space\-separated list of \fBoptional\fR groups referencing gems to include during installation\.
.
.SH "BUILD OPTIONS"
You can use \fBbundle config\fR to give Bundler the flags to pass to the gem installer every time bundler tries to install a particular gem\.
.
.P
A very common example, the \fBmysql\fR gem, requires Snow Leopard users to pass configuration flags to \fBgem install\fR to specify where to find the \fBmysql_config\fR executable\.
.
.IP "" 4
.
.nf
gem install mysql \-\- \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
.
.fi
.
.IP "" 0
.
.P
Since the specific location of that executable can change from machine to machine, you can specify these flags on a per\-machine basis\.
.
.IP "" 4
.
.nf
bundle config set \-\-global build\.mysql \-\-with\-mysql\-config=/usr/local/mysql/bin/mysql_config
.
.fi
.
.IP "" 0
.
.P
After running this command, every time bundler needs to install the \fBmysql\fR gem, it will pass along the flags you specified\.
.
.SH "CONFIGURATION KEYS"
Configuration keys in bundler have two forms: the canonical form and the environment variable form\.
.
.P
For instance, passing the \fB\-\-without\fR flag to bundle install(1) \fIbundle\-install\.1\.html\fR prevents Bundler from installing certain groups specified in the Gemfile(5)\. Bundler persists this value in \fBapp/\.bundle/config\fR so that calls to \fBBundler\.setup\fR do not try to find gems from the \fBGemfile\fR that you didn\'t install\. Additionally, subsequent calls to bundle install(1) \fIbundle\-install\.1\.html\fR remember this setting and skip those groups\.
.
.P
The canonical form of this configuration is \fB"without"\fR\. To convert the canonical form to the environment variable form, capitalize it, and prepend \fBBUNDLE_\fR\. The environment variable form of \fB"without"\fR is \fBBUNDLE_WITHOUT\fR\.
.
.P
Any periods in the configuration keys must be replaced with two underscores when setting it via environment variables\. The configuration key \fBlocal\.rack\fR becomes the environment variable \fBBUNDLE_LOCAL__RACK\fR\.
.
.SH "LIST OF AVAILABLE KEYS"
The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\.
.
.IP "\(bu" 4
\fBallow_deployment_source_credential_changes\fR (\fBBUNDLE_ALLOW_DEPLOYMENT_SOURCE_CREDENTIAL_CHANGES\fR): When in deployment mode, allow changing the credentials to a gem\'s source\. Ex: \fBhttps://some\.host\.com/gems/path/\fR \-> \fBhttps://user_name:password@some\.host\.com/gems/path\fR
.
.IP "\(bu" 4
\fBallow_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\.
.
.IP "\(bu" 4
\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
.
.IP "\(bu" 4
\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
.
.IP "\(bu" 4
\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
.
.IP "\(bu" 4
\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\.
.
.IP "\(bu" 4
\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
.
.IP "\(bu" 4
\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
.
.IP "\(bu" 4
\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
.
.IP "\(bu" 4
\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
.
.IP "\(bu" 4
\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
.
.IP "\(bu" 4
\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\.
.
.IP "\(bu" 4
\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
.
.IP "\(bu" 4
\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
.
.IP "\(bu" 4
\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
.
.IP "\(bu" 4
\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
.
.IP "\(bu" 4
\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
.
.IP "\(bu" 4
\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
.
.IP "\(bu" 4
\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine\'s platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
.
.IP "\(bu" 4
\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\.
.
.IP "\(bu" 4
\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
.
.IP "\(bu" 4
\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
.
.IP "\(bu" 4
\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
.
.IP "\(bu" 4
\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
.
.IP "\(bu" 4
\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\.
.
.IP "\(bu" 4
\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
.
.IP "\(bu" 4
\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
.
.IP "\(bu" 4
\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
.
.IP "\(bu" 4
\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
.
.IP "\(bu" 4
\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
.
.IP "\(bu" 4
\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\.
.
.IP "\(bu" 4
\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\.
.
.IP "\(bu" 4
\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
.
.IP "\(bu" 4
\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
.
.IP "\(bu" 4
\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler\'s experimental plugin system\.
.
.IP "\(bu" 4
\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
.
.IP "\(bu" 4
\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.
.
.IP "\(bu" 4
\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
.
.IP "\(bu" 4
\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
.
.IP "\(bu" 4
\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
.
.IP "\(bu" 4
\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
.
.IP "\(bu" 4
\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
.
.IP "\(bu" 4
\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
.
.IP "\(bu" 4
\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
.
.IP "\(bu" 4
\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
.
.IP "\(bu" 4
\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
.
.IP "\(bu" 4
\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
.
.IP "\(bu" 4
\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
.
.IP "\(bu" 4
\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
.
.IP "\(bu" 4
\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
.
.IP "\(bu" 4
\fBwith\fR (\fBBUNDLE_WITH\fR): A \fB:\fR\-separated list of groups whose gems bundler should install\.
.
.IP "\(bu" 4
\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A \fB:\fR\-separated list of groups whose gems bundler should not install\.
.
.IP "" 0
.
.P
In general, you should set these settings per\-application by using the applicable flag to the bundle install(1) \fIbundle\-install\.1\.html\fR or bundle cache(1) \fIbundle\-cache\.1\.html\fR command\.
.
.P
You can set them globally either via environment variables or \fBbundle config\fR, whichever is preferable for your setup\. If you use both, environment variables will take preference over global settings\.
.
.SH "LOCAL GIT REPOS"
Bundler also allows you to work against a git repository locally instead of using the remote version\. This can be achieved by setting up a local override:
.
.IP "" 4
.
.nf
bundle config set \-\-local local\.GEM_NAME /path/to/local/git/repository
.
.fi
.
.IP "" 0
.
.P
For example, in order to use a local Rack repository, a developer could call:
.
.IP "" 4
.
.nf
bundle config set \-\-local local\.rack ~/Work/git/rack
.
.fi
.
.IP "" 0
.
.P
Now instead of checking out the remote git repository, the local override will be used\. Similar to a path source, every time the local git repository change, changes will be automatically picked up by Bundler\. This means a commit in the local git repo will update the revision in the \fBGemfile\.lock\fR to the local git repo revision\. This requires the same attention as git submodules\. Before pushing to the remote, you need to ensure the local override was pushed, otherwise you may point to a commit that only exists in your local machine\. You\'ll also need to CGI escape your usernames and passwords as well\.
.
.P
Bundler does many checks to ensure a developer won\'t work with invalid references\. Particularly, we force a developer to specify a branch in the \fBGemfile\fR in order to use this feature\. If the branch specified in the \fBGemfile\fR and the current branch in the local git repository do not match, Bundler will abort\. This ensures that a developer is always working against the correct branches, and prevents accidental locking to a different branch\.
.
.P
Finally, Bundler also ensures that the current revision in the \fBGemfile\.lock\fR exists in the local git repository\. By doing this, Bundler forces you to fetch the latest changes in the remotes\.
.
.SH "MIRRORS OF GEM SOURCES"
Bundler supports overriding gem sources with mirrors\. This allows you to configure rubygems\.org as the gem source in your Gemfile while still using your mirror to fetch gems\.
.
.IP "" 4
.
.nf
bundle config set \-\-global mirror\.SOURCE_URL MIRROR_URL
.
.fi
.
.IP "" 0
.
.P
For example, to use a mirror of https://rubygems\.org hosted at https://example\.org:
.
.IP "" 4
.
.nf
bundle config set \-\-global mirror\.https://rubygems\.org https://example\.org
.
.fi
.
.IP "" 0
.
.P
Each mirror also provides a fallback timeout setting\. If the mirror does not respond within the fallback timeout, Bundler will try to use the original server instead of the mirror\.
.
.IP "" 4
.
.nf
bundle config set \-\-global mirror\.SOURCE_URL\.fallback_timeout TIMEOUT
.
.fi
.
.IP "" 0
.
.P
For example, to fall back to rubygems\.org after 3 seconds:
.
.IP "" 4
.
.nf
bundle config set \-\-global mirror\.https://rubygems\.org\.fallback_timeout 3
.
.fi
.
.IP "" 0
.
.P
The default fallback timeout is 0\.1 seconds, but the setting can currently only accept whole seconds (for example, 1, 15, or 30)\.
.
.SH "CREDENTIALS FOR GEM SOURCES"
Bundler allows you to configure credentials for any gem source, which allows you to avoid putting secrets into your Gemfile\.
.
.IP "" 4
.
.nf
bundle config set \-\-global SOURCE_HOSTNAME USERNAME:PASSWORD
.
.fi
.
.IP "" 0
.
.P
For example, to save the credentials of user \fBclaudette\fR for the gem source at \fBgems\.longerous\.com\fR, you would run:
.
.IP "" 4
.
.nf
bundle config set \-\-global gems\.longerous\.com claudette:s00pers3krit
.
.fi
.
.IP "" 0
.
.P
Or you can set the credentials as an environment variable like this:
.
.IP "" 4
.
.nf
export BUNDLE_GEMS__LONGEROUS__COM="claudette:s00pers3krit"
.
.fi
.
.IP "" 0
.
.P
For gems with a git source with HTTP(S) URL you can specify credentials like so:
.
.IP "" 4
.
.nf
bundle config set \-\-global https://github\.com/rubygems/rubygems\.git username:password
.
.fi
.
.IP "" 0
.
.P
Or you can set the credentials as an environment variable like so:
.
.IP "" 4
.
.nf
export BUNDLE_GITHUB__COM=username:password
.
.fi
.
.IP "" 0
.
.P
This is especially useful for private repositories on hosts such as GitHub, where you can use personal OAuth tokens:
.
.IP "" 4
.
.nf
export BUNDLE_GITHUB__COM=abcd0123generatedtoken:x\-oauth\-basic
.
.fi
.
.IP "" 0
.
.P
Note that any configured credentials will be redacted by informative commands such as \fBbundle config list\fR or \fBbundle config get\fR, unless you use the \fB\-\-parseable\fR flag\. This is to avoid unintentionally leaking credentials when copy\-pasting bundler output\.
.
.P
Also note that to guarantee a sane mapping between valid environment variable names and valid host names, bundler makes the following transformations:
.
.IP "\(bu" 4
Any \fB\-\fR characters in a host name are mapped to a triple dash (\fB___\fR) in the corresponding environment variable\.
.
.IP "\(bu" 4
Any \fB\.\fR characters in a host name are mapped to a double dash (\fB__\fR) in the corresponding environment variable\.
.
.IP "" 0
.
.P
This means that if you have a gem server named \fBmy\.gem\-host\.com\fR, you\'ll need to use the \fBBUNDLE_MY__GEM___HOST__COM\fR variable to configure credentials for it through ENV\.
.
.SH "CONFIGURE BUNDLER DIRECTORIES"
Bundler\'s home, config, cache and plugin directories are able to be configured through environment variables\. The default location for Bundler\'s home directory is \fB~/\.bundle\fR, which all directories inherit from by default\. The following outlines the available environment variables and their default values
.
.IP "" 4
.
.nf
BUNDLE_USER_HOME : $HOME/\.bundle
BUNDLE_USER_CACHE : $BUNDLE_USER_HOME/cache
BUNDLE_USER_CONFIG : $BUNDLE_USER_HOME/config
BUNDLE_USER_PLUGIN : $BUNDLE_USER_HOME/plugin
.
.fi
.
.IP "" 0
share/man/man1/bundle-outdated.1 0000644 00000006425 15173547333 0012441 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-OUTDATED" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-outdated\fR \- List installed gems with newer versions available
.
.SH "SYNOPSIS"
\fBbundle outdated\fR [GEM] [\-\-local] [\-\-pre] [\-\-source] [\-\-strict] [\-\-parseable | \-\-porcelain] [\-\-group=GROUP] [\-\-groups] [\-\-patch|\-\-minor|\-\-major] [\-\-filter\-major] [\-\-filter\-minor] [\-\-filter\-patch] [\-\-only\-explicit]
.
.SH "DESCRIPTION"
Outdated lists the names and versions of gems that have a newer version available in the given source\. Calling outdated with [GEM [GEM]] will only check for newer versions of the given gems\. Prerelease gems are ignored by default\. If your gems are up to date, Bundler will exit with a status of 0\. Otherwise, it will exit 1\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-local\fR
Do not attempt to fetch gems remotely and use the gem cache instead\.
.
.TP
\fB\-\-pre\fR
Check for newer pre\-release gems\.
.
.TP
\fB\-\-source\fR
Check against a specific source\.
.
.TP
\fB\-\-strict\fR
Only list newer versions allowed by your Gemfile requirements, also respecting conservative update flags (\-\-patch, \-\-minor, \-\-major)\.
.
.TP
\fB\-\-parseable\fR, \fB\-\-porcelain\fR
Use minimal formatting for more parseable output\.
.
.TP
\fB\-\-group\fR
List gems from a specific group\.
.
.TP
\fB\-\-groups\fR
List gems organized by groups\.
.
.TP
\fB\-\-minor\fR
Prefer updating only to next minor version\.
.
.TP
\fB\-\-major\fR
Prefer updating to next major version (default)\.
.
.TP
\fB\-\-patch\fR
Prefer updating only to next patch version\.
.
.TP
\fB\-\-filter\-major\fR
Only list major newer versions\.
.
.TP
\fB\-\-filter\-minor\fR
Only list minor newer versions\.
.
.TP
\fB\-\-filter\-patch\fR
Only list patch newer versions\.
.
.TP
\fB\-\-only\-explicit\fR
Only list gems specified in your Gemfile, not their dependencies\.
.
.SH "PATCH LEVEL OPTIONS"
See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
.
.SH "FILTERING OUTPUT"
The 3 filtering options do not affect the resolution of versions, merely what versions are shown in the output\.
.
.P
If the regular output shows the following:
.
.IP "" 4
.
.nf
* Gem Current Latest Requested Groups
* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test
* hashie 1\.2\.0 3\.4\.6 = 1\.2\.0 default
* headless 2\.2\.3 2\.3\.1 = 2\.2\.3 test
.
.fi
.
.IP "" 0
.
.P
\fB\-\-filter\-major\fR would only show:
.
.IP "" 4
.
.nf
* Gem Current Latest Requested Groups
* hashie 1\.2\.0 3\.4\.6 = 1\.2\.0 default
.
.fi
.
.IP "" 0
.
.P
\fB\-\-filter\-minor\fR would only show:
.
.IP "" 4
.
.nf
* Gem Current Latest Requested Groups
* headless 2\.2\.3 2\.3\.1 = 2\.2\.3 test
.
.fi
.
.IP "" 0
.
.P
\fB\-\-filter\-patch\fR would only show:
.
.IP "" 4
.
.nf
* Gem Current Latest Requested Groups
* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test
.
.fi
.
.IP "" 0
.
.P
Filter options can be combined\. \fB\-\-filter\-minor\fR and \fB\-\-filter\-patch\fR would show:
.
.IP "" 4
.
.nf
* Gem Current Latest Requested Groups
* faker 1\.6\.5 1\.6\.6 ~> 1\.4 development, test
.
.fi
.
.IP "" 0
.
.P
Combining all three \fBfilter\fR options would be the same result as providing none of them\.
share/man/man1/bundle-pristine.1 0000644 00000003214 15173547333 0012456 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-PRISTINE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
.
.SH "SYNOPSIS"
\fBbundle pristine\fR
.
.SH "DESCRIPTION"
\fBpristine\fR restores the installed gems in the bundle to their pristine condition using the local gem cache from RubyGems\. For git gems, a forced checkout will be performed\.
.
.P
For further explanation, \fBbundle pristine\fR ignores unpacked files on disk\. In other words, this command utilizes the local \fB\.gem\fR cache or the gem\'s git repository as if one were installing from scratch\.
.
.P
Note: the Bundler gem cannot be restored to its original state with \fBpristine\fR\. One also cannot use \fBbundle pristine\fR on gems with a \'path\' option in the Gemfile, because bundler has no original copy it can restore from\.
.
.P
When is it practical to use \fBbundle pristine\fR?
.
.P
It comes in handy when a developer is debugging a gem\. \fBbundle pristine\fR is a great way to get rid of experimental changes to a gem that one may not want\.
.
.P
Why use \fBbundle pristine\fR over \fBgem pristine \-\-all\fR?
.
.P
Both commands are very similar\. For context: \fBbundle pristine\fR, without arguments, cleans all gems from the lockfile\. Meanwhile, \fBgem pristine \-\-all\fR cleans all installed gems for that Ruby version\.
.
.P
If a developer forgets which gems in their project they might have been debugging, the Rubygems \fBgem pristine [GEMNAME]\fR command may be inconvenient\. One can avoid waiting for \fBgem pristine \-\-all\fR, and instead run \fBbundle pristine\fR\.
share/man/man1/bundle-show.1 0000644 00000001260 15173547333 0011600 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-SHOW" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
.
.SH "SYNOPSIS"
\fBbundle show\fR [GEM] [\-\-paths]
.
.SH "DESCRIPTION"
Without the [GEM] option, \fBshow\fR will print a list of the names and versions of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by name\.
.
.P
Calling show with [GEM] will list the exact location of that gem on your machine\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-paths\fR
List the paths of all gems that are required by your [\fBGemfile(5)\fR][Gemfile(5)], sorted by gem name\.
share/man/man1/bundle-install.1 0000644 00000041153 15173547333 0012273 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-INSTALL" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
.
.SH "SYNOPSIS"
\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang] [\-\-standalone[=GROUP[ GROUP\.\.\.]]] [\-\-system] [\-\-trust\-policy=POLICY] [\-\-with=GROUP[ GROUP\.\.\.]] [\-\-without=GROUP[ GROUP\.\.\.]]
.
.SH "DESCRIPTION"
Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\.
.
.P
If a \fBGemfile\.lock\fR does exist, and you have not updated your Gemfile(5), Bundler will fetch all remote sources, but use the dependencies specified in the \fBGemfile\.lock\fR instead of resolving dependencies\.
.
.P
If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundler will use the dependencies in the \fBGemfile\.lock\fR for all gems that you did not update, but will re\-resolve the dependencies of gems that you did update\. You can find more information about this update process below under \fICONSERVATIVE UPDATING\fR\.
.
.SH "OPTIONS"
The \fB\-\-clean\fR, \fB\-\-deployment\fR, \fB\-\-frozen\fR, \fB\-\-no\-prune\fR, \fB\-\-path\fR, \fB\-\-shebang\fR, \fB\-\-system\fR, \fB\-\-without\fR and \fB\-\-with\fR options are deprecated because they only make sense if they are applied to every subsequent \fBbundle install\fR run automatically and that requires \fBbundler\fR to silently remember them\. Since \fBbundler\fR will no longer remember CLI flags in future versions, \fBbundle config\fR (see bundle\-config(1)) should be used to apply them permanently\.
.
.TP
\fB\-\-binstubs[=<directory>]\fR
Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it in \fBbin/\fR\. This lets you link the binstub inside of an application to the exact gem version the application needs\.
.
.IP
Creates a directory (defaults to \fB~/bin\fR) and places any executables from the gem there\. These executables run in Bundler\'s context\. If used, you might add this directory to your environment\'s \fBPATH\fR variable\. For instance, if the \fBrails\fR gem comes with a \fBrails\fR executable, this flag will create a \fBbin/rails\fR executable that ensures that all referred dependencies will be resolved using the bundled gems\.
.
.TP
\fB\-\-clean\fR
On finishing the installation Bundler is going to remove any gems not present in the current Gemfile(5)\. Don\'t worry, gems currently in use will not be removed\.
.
.IP
This option is deprecated in favor of the \fBclean\fR setting\.
.
.TP
\fB\-\-deployment\fR
In \fIdeployment mode\fR, Bundler will \'roll\-out\' the bundle for production or CI use\. Please check carefully if you want to have this option enabled in your development environment\.
.
.IP
This option is deprecated in favor of the \fBdeployment\fR setting\.
.
.TP
\fB\-\-redownload\fR
Force download every gem, even if the required versions are already available locally\.
.
.TP
\fB\-\-frozen\fR
Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\.
.
.IP
This option is deprecated in favor of the \fBfrozen\fR setting\.
.
.TP
\fB\-\-full\-index\fR
Bundler will not call Rubygems\' API endpoint (default) but download and cache a (currently big) index file of all gems\. Performance can be improved for large bundles that seldom change by enabling this option\.
.
.TP
\fB\-\-gemfile=<gemfile>\fR
The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project\'s root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\.
.
.TP
\fB\-\-jobs=[<number>]\fR, \fB\-j[<number>]\fR
The maximum number of parallel download and install jobs\. The default is the number of available processors\.
.
.TP
\fB\-\-local\fR
Do not attempt to connect to \fBrubygems\.org\fR\. Instead, Bundler will use the gems already present in Rubygems\' cache or in \fBvendor/cache\fR\. Note that if an appropriate platform\-specific gem exists on \fBrubygems\.org\fR it will not be found\.
.
.TP
\fB\-\-prefer\-local\fR
Force using locally installed gems, or gems already present in Rubygems\' cache or in \fBvendor/cache\fR, when resolving, even if newer versions are available remotely\. Only attempt to connect to \fBrubygems\.org\fR for gems that are not present locally\.
.
.TP
\fB\-\-no\-cache\fR
Do not update the cache in \fBvendor/cache\fR with the newly bundled gems\. This does not remove any gems in the cache but keeps the newly bundled gems from being cached during the install\.
.
.TP
\fB\-\-no\-prune\fR
Don\'t remove stale gems from the cache when the installation finishes\.
.
.IP
This option is deprecated in favor of the \fBno_prune\fR setting\.
.
.TP
\fB\-\-path=<path>\fR
The location to install the specified gems to\. This defaults to Rubygems\' setting\. Bundler shares this location with Rubygems, \fBgem install \.\.\.\fR will have gem installed there, too\. Therefore, gems installed without a \fB\-\-path \.\.\.\fR setting will show up by calling \fBgem list\fR\. Accordingly, gems installed to other locations will not get listed\.
.
.IP
This option is deprecated in favor of the \fBpath\fR setting\.
.
.TP
\fB\-\-quiet\fR
Do not print progress information to the standard output\. Instead, Bundler will exit using a status code (\fB$?\fR)\.
.
.TP
\fB\-\-retry=[<number>]\fR
Retry failed network or git requests for \fInumber\fR times\.
.
.TP
\fB\-\-shebang=<ruby\-executable>\fR
Uses the specified ruby executable (usually \fBruby\fR) to execute the scripts created with \fB\-\-binstubs\fR\. In addition, if you use \fB\-\-binstubs\fR together with \fB\-\-shebang jruby\fR these executables will be changed to execute \fBjruby\fR instead\.
.
.IP
This option is deprecated in favor of the \fBshebang\fR setting\.
.
.TP
\fB\-\-standalone[=<list>]\fR
Makes a bundle that can work without depending on Rubygems or Bundler at runtime\. A space separated list of groups to install has to be specified\. Bundler creates a directory named \fBbundle\fR and installs the bundle there\. It also generates a \fBbundle/bundler/setup\.rb\fR file to replace Bundler\'s own setup in the manner required\. Using this option implicitly sets \fBpath\fR, which is a [remembered option][REMEMBERED OPTIONS]\.
.
.TP
\fB\-\-system\fR
Installs the gems specified in the bundle to the system\'s Rubygems location\. This overrides any previous configuration of \fB\-\-path\fR\.
.
.IP
This option is deprecated in favor of the \fBsystem\fR setting\.
.
.TP
\fB\-\-trust\-policy=[<policy>]\fR
Apply the Rubygems security policy \fIpolicy\fR, where policy is one of \fBHighSecurity\fR, \fBMediumSecurity\fR, \fBLowSecurity\fR, \fBAlmostNoSecurity\fR, or \fBNoSecurity\fR\. For more details, please see the Rubygems signing documentation linked below in \fISEE ALSO\fR\.
.
.TP
\fB\-\-with=<list>\fR
A space\-separated list of groups referencing gems to install\. If an optional group is given it is installed\. If a group is given that is in the remembered list of groups given to \-\-without, it is removed from that list\.
.
.IP
This option is deprecated in favor of the \fBwith\fR setting\.
.
.TP
\fB\-\-without=<list>\fR
A space\-separated list of groups referencing gems to skip during installation\. If a group is given that is in the remembered list of groups given to \-\-with, it is removed from that list\.
.
.IP
This option is deprecated in favor of the \fBwithout\fR setting\.
.
.SH "DEPLOYMENT MODE"
Bundler\'s defaults are optimized for development\. To switch to defaults optimized for deployment and for CI, use the \fB\-\-deployment\fR flag\. Do not activate deployment mode on development machines, as it will cause an error when the Gemfile(5) is modified\.
.
.IP "1." 4
A \fBGemfile\.lock\fR is required\.
.
.IP
To ensure that the same versions of the gems you developed with and tested with are also used in deployments, a \fBGemfile\.lock\fR is required\.
.
.IP
This is mainly to ensure that you remember to check your \fBGemfile\.lock\fR into version control\.
.
.IP "2." 4
The \fBGemfile\.lock\fR must be up to date
.
.IP
In development, you can modify your Gemfile(5) and re\-run \fBbundle install\fR to \fIconservatively update\fR your \fBGemfile\.lock\fR snapshot\.
.
.IP
In deployment, your \fBGemfile\.lock\fR should be up\-to\-date with changes made in your Gemfile(5)\.
.
.IP "3." 4
Gems are installed to \fBvendor/bundle\fR not your default system location
.
.IP
In development, it\'s convenient to share the gems used in your application with other applications and other scripts that run on the system\.
.
.IP
In deployment, isolation is a more important default\. In addition, the user deploying the application may not have permission to install gems to the system, or the web server may not have permission to read them\.
.
.IP
As a result, \fBbundle install \-\-deployment\fR installs gems to the \fBvendor/bundle\fR directory in the application\. This may be overridden using the \fB\-\-path\fR option\.
.
.IP "" 0
.
.SH "INSTALLING GROUPS"
By default, \fBbundle install\fR will install all gems in all groups in your Gemfile(5), except those declared for a different platform\.
.
.P
However, you can explicitly tell Bundler to skip installing certain groups with the \fB\-\-without\fR option\. This option takes a space\-separated list of groups\.
.
.P
While the \fB\-\-without\fR option will skip \fIinstalling\fR the gems in the specified groups, it will still \fIdownload\fR those gems and use them to resolve the dependencies of every gem in your Gemfile(5)\.
.
.P
This is so that installing a different set of groups on another machine (such as a production server) will not change the gems and versions that you have already developed and tested against\.
.
.P
\fBBundler offers a rock\-solid guarantee that the third\-party code you are running in development and testing is also the third\-party code you are running in production\. You can choose to exclude some of that code in different environments, but you will never be caught flat\-footed by different versions of third\-party code being used in different environments\.\fR
.
.P
For a simple illustration, consider the following Gemfile(5):
.
.IP "" 4
.
.nf
source \'https://rubygems\.org\'
gem \'sinatra\'
group :production do
gem \'rack\-perftools\-profiler\'
end
.
.fi
.
.IP "" 0
.
.P
In this case, \fBsinatra\fR depends on any version of Rack (\fB>= 1\.0\fR), while \fBrack\-perftools\-profiler\fR depends on 1\.x (\fB~> 1\.0\fR)\.
.
.P
When you run \fBbundle install \-\-without production\fR in development, we look at the dependencies of \fBrack\-perftools\-profiler\fR as well\. That way, you do not spend all your time developing against Rack 2\.0, using new APIs unavailable in Rack 1\.x, only to have Bundler switch to Rack 1\.2 when the \fBproduction\fR group \fIis\fR used\.
.
.P
This should not cause any problems in practice, because we do not attempt to \fBinstall\fR the gems in the excluded groups, and only evaluate as part of the dependency resolution process\.
.
.P
This also means that you cannot include different versions of the same gem in different groups, because doing so would result in different sets of dependencies used in development and production\. Because of the vagaries of the dependency resolution process, this usually affects more than the gems you list in your Gemfile(5), and can (surprisingly) radically change the gems you are using\.
.
.SH "THE GEMFILE\.LOCK"
When you run \fBbundle install\fR, Bundler will persist the full names and versions of all gems that you used (including dependencies of the gems specified in the Gemfile(5)) into a file called \fBGemfile\.lock\fR\.
.
.P
Bundler uses this file in all subsequent calls to \fBbundle install\fR, which guarantees that you always use the same exact code, even as your application moves across machines\.
.
.P
Because of the way dependency resolution works, even a seemingly small change (for instance, an update to a point\-release of a dependency of a gem in your Gemfile(5)) can result in radically different gems being needed to satisfy all dependencies\.
.
.P
As a result, you \fBSHOULD\fR check your \fBGemfile\.lock\fR into version control, in both applications and gems\. If you do not, every machine that checks out your repository (including your production server) will resolve all dependencies again, which will result in different versions of third\-party code being used if \fBany\fR of the gems in the Gemfile(5) or any of their dependencies have been updated\.
.
.P
When Bundler first shipped, the \fBGemfile\.lock\fR was included in the \fB\.gitignore\fR file included with generated gems\. Over time, however, it became clear that this practice forces the pain of broken dependencies onto new contributors, while leaving existing contributors potentially unaware of the problem\. Since \fBbundle install\fR is usually the first step towards a contribution, the pain of broken dependencies would discourage new contributors from contributing\. As a result, we have revised our guidance for gem authors to now recommend checking in the lock for gems\.
.
.SH "CONSERVATIVE UPDATING"
When you make a change to the Gemfile(5) and then run \fBbundle install\fR, Bundler will update only the gems that you modified\.
.
.P
In other words, if a gem that you \fBdid not modify\fR worked before you called \fBbundle install\fR, it will continue to use the exact same versions of all dependencies as it used before the update\.
.
.P
Let\'s take a look at an example\. Here\'s your original Gemfile(5):
.
.IP "" 4
.
.nf
source \'https://rubygems\.org\'
gem \'actionpack\', \'2\.3\.8\'
gem \'activemerchant\'
.
.fi
.
.IP "" 0
.
.P
In this case, both \fBactionpack\fR and \fBactivemerchant\fR depend on \fBactivesupport\fR\. The \fBactionpack\fR gem depends on \fBactivesupport 2\.3\.8\fR and \fBrack ~> 1\.1\.0\fR, while the \fBactivemerchant\fR gem depends on \fBactivesupport >= 2\.3\.2\fR, \fBbraintree >= 2\.0\.0\fR, and \fBbuilder >= 2\.0\.0\fR\.
.
.P
When the dependencies are first resolved, Bundler will select \fBactivesupport 2\.3\.8\fR, which satisfies the requirements of both gems in your Gemfile(5)\.
.
.P
Next, you modify your Gemfile(5) to:
.
.IP "" 4
.
.nf
source \'https://rubygems\.org\'
gem \'actionpack\', \'3\.0\.0\.rc\'
gem \'activemerchant\'
.
.fi
.
.IP "" 0
.
.P
The \fBactionpack 3\.0\.0\.rc\fR gem has a number of new dependencies, and updates the \fBactivesupport\fR dependency to \fB= 3\.0\.0\.rc\fR and the \fBrack\fR dependency to \fB~> 1\.2\.1\fR\.
.
.P
When you run \fBbundle install\fR, Bundler notices that you changed the \fBactionpack\fR gem, but not the \fBactivemerchant\fR gem\. It evaluates the gems currently being used to satisfy its requirements:
.
.TP
\fBactivesupport 2\.3\.8\fR
also used to satisfy a dependency in \fBactivemerchant\fR, which is not being updated
.
.TP
\fBrack ~> 1\.1\.0\fR
not currently being used to satisfy another dependency
.
.P
Because you did not explicitly ask to update \fBactivemerchant\fR, you would not expect it to suddenly stop working after updating \fBactionpack\fR\. However, satisfying the new \fBactivesupport 3\.0\.0\.rc\fR dependency of actionpack requires updating one of its dependencies\.
.
.P
Even though \fBactivemerchant\fR declares a very loose dependency that theoretically matches \fBactivesupport 3\.0\.0\.rc\fR, Bundler treats gems in your Gemfile(5) that have not changed as an atomic unit together with their dependencies\. In this case, the \fBactivemerchant\fR dependency is treated as \fBactivemerchant 1\.7\.1 + activesupport 2\.3\.8\fR, so \fBbundle install\fR will report that it cannot update \fBactionpack\fR\.
.
.P
To explicitly update \fBactionpack\fR, including its dependencies which other gems in the Gemfile(5) still depend on, run \fBbundle update actionpack\fR (see \fBbundle update(1)\fR)\.
.
.P
\fBSummary\fR: In general, after making a change to the Gemfile(5) , you should first try to run \fBbundle install\fR, which will guarantee that no other gem in the Gemfile(5) is impacted by the change\. If that does not work, run bundle update(1) \fIbundle\-update\.1\.html\fR\.
.
.SH "SEE ALSO"
.
.IP "\(bu" 4
Gem install docs \fIhttp://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR
.
.IP "\(bu" 4
Rubygems signing docs \fIhttp://guides\.rubygems\.org/security/\fR
.
.IP "" 0
share/man/man1/bundle-cache.1 0000644 00000006632 15173547333 0011673 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-CACHE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
.
.SH "SYNOPSIS"
\fBbundle cache\fR
.
.P
alias: \fBpackage\fR, \fBpack\fR
.
.SH "DESCRIPTION"
Copy all of the \fB\.gem\fR files needed to run the application into the \fBvendor/cache\fR directory\. In the future, when running \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR, use the gems in the cache in preference to the ones on \fBrubygems\.org\fR\.
.
.SH "GIT AND PATH GEMS"
The \fBbundle cache\fR command can also package \fB:git\fR and \fB:path\fR dependencies besides \.gem files\. This needs to be explicitly enabled via the \fB\-\-all\fR option\. Once used, the \fB\-\-all\fR option will be remembered\.
.
.SH "SUPPORT FOR MULTIPLE PLATFORMS"
When using gems that have different packages for different platforms, Bundler supports caching of gems for other platforms where the Gemfile has been resolved (i\.e\. present in the lockfile) in \fBvendor/cache\fR\. This needs to be enabled via the \fB\-\-all\-platforms\fR option\. This setting will be remembered in your local bundler configuration\.
.
.SH "REMOTE FETCHING"
By default, if you run \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR after running bundle cache(1) \fIbundle\-cache\.1\.html\fR, bundler will still connect to \fBrubygems\.org\fR to check whether a platform\-specific gem exists for any of the gems in \fBvendor/cache\fR\.
.
.P
For instance, consider this Gemfile(5):
.
.IP "" 4
.
.nf
source "https://rubygems\.org"
gem "nokogiri"
.
.fi
.
.IP "" 0
.
.P
If you run \fBbundle cache\fR under C Ruby, bundler will retrieve the version of \fBnokogiri\fR for the \fB"ruby"\fR platform\. If you deploy to JRuby and run \fBbundle install\fR, bundler is forced to check to see whether a \fB"java"\fR platformed \fBnokogiri\fR exists\.
.
.P
Even though the \fBnokogiri\fR gem for the Ruby platform is \fItechnically\fR acceptable on JRuby, it has a C extension that does not run on JRuby\. As a result, bundler will, by default, still connect to \fBrubygems\.org\fR to check whether it has a version of one of your gems more specific to your platform\.
.
.P
This problem is also not limited to the \fB"java"\fR platform\. A similar (common) problem can happen when developing on Windows and deploying to Linux, or even when developing on OSX and deploying to Linux\.
.
.P
If you know for sure that the gems packaged in \fBvendor/cache\fR are appropriate for the platform you are on, you can run \fBbundle install \-\-local\fR to skip checking for more appropriate gems, and use the ones in \fBvendor/cache\fR\.
.
.P
One way to be sure that you have the right platformed versions of all your gems is to run \fBbundle cache\fR on an identical machine and check in the gems\. For instance, you can run \fBbundle cache\fR on an identical staging box during your staging process, and check in the \fBvendor/cache\fR before deploying to production\.
.
.P
By default, bundle cache(1) \fIbundle\-cache\.1\.html\fR fetches and also installs the gems to the default location\. To package the dependencies to \fBvendor/cache\fR without installing them to the local install location, you can run \fBbundle cache \-\-no\-install\fR\.
.
.SH "HISTORY"
In Bundler 2\.1, \fBcache\fR took in the functionalities of \fBpackage\fR and now \fBpackage\fR and \fBpack\fR are aliases of \fBcache\fR\.
share/man/man1/bundle-add.1 0000644 00000003460 15173547333 0011354 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-ADD" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
.
.SH "SYNOPSIS"
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT] [\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-skip\-install] [\-\-strict] [\-\-optimistic]
.
.SH "DESCRIPTION"
Adds the named gem to the Gemfile and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
.
.P
Example:
.
.P
bundle add rails
.
.P
bundle add rails \-\-version "< 3\.0, > 1\.1"
.
.P
bundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"
.
.P
bundle add rails \-\-skip\-install
.
.P
bundle add rails \-\-group "development, test"
.
.SH "OPTIONS"
.
.TP
\fB\-\-version\fR, \fB\-v\fR
Specify version requirements(s) for the added gem\.
.
.TP
\fB\-\-group\fR, \fB\-g\fR
Specify the group(s) for the added gem\. Multiple groups should be separated by commas\.
.
.TP
\fB\-\-source\fR, \fB\-s\fR
Specify the source for the added gem\.
.
.TP
\fB\-\-require\fR, \fB\-r\fR
Adds require path to gem\. Provide false, or a path as a string\.
.
.TP
\fB\-\-path\fR
Specify the file system path for the added gem\.
.
.TP
\fB\-\-git\fR
Specify the git source for the added gem\.
.
.TP
\fB\-\-github\fR
Specify the github source for the added gem\.
.
.TP
\fB\-\-branch\fR
Specify the git branch for the added gem\.
.
.TP
\fB\-\-ref\fR
Specify the git ref for the added gem\.
.
.TP
\fB\-\-skip\-install\fR
Adds the gem to the Gemfile but does not install it\.
.
.TP
\fB\-\-optimistic\fR
Adds optimistic declaration of version\.
.
.TP
\fB\-\-strict\fR
Adds strict declaration of version\.
share/man/man1/bundle-remove.1 0000644 00000001517 15173547333 0012122 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-REMOVE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-remove\fR \- Removes gems from the Gemfile
.
.SH "SYNOPSIS"
\fBbundle remove [GEM [GEM \.\.\.]] [\-\-install]\fR
.
.SH "DESCRIPTION"
Removes the given gems from the Gemfile while ensuring that the resulting Gemfile is still valid\. If a gem cannot be removed, a warning is printed\. If a gem is already absent from the Gemfile, and error is raised\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-install\fR
Runs \fBbundle install\fR after the given gems have been removed from the Gemfile, which ensures that both the lockfile and the installed gems on disk are also updated to remove the given gem(s)\.
.
.P
Example:
.
.P
bundle remove rails
.
.P
bundle remove rails rack
.
.P
bundle remove rails rack \-\-install
share/man/man1/bundle-help.1 0000644 00000000705 15173547333 0011553 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-HELP" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-help\fR \- Displays detailed help for each subcommand
.
.SH "SYNOPSIS"
\fBbundle help\fR [COMMAND]
.
.SH "DESCRIPTION"
Displays detailed help for the given subcommand\. You can specify a single \fBCOMMAND\fR at the same time\. When \fBCOMMAND\fR is omitted, help for \fBhelp\fR command will be displayed\.
share/man/man1/bundle-binstubs.1 0000644 00000003116 15173547333 0012453 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-BINSTUBS" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
.
.SH "SYNOPSIS"
\fBbundle binstubs\fR \fIGEM_NAME\fR [\-\-force] [\-\-path PATH] [\-\-standalone]
.
.SH "DESCRIPTION"
Binstubs are scripts that wrap around executables\. Bundler creates a small Ruby file (a binstub) that loads Bundler, runs the command, and puts it into \fBbin/\fR\. Binstubs are a shortcut\-or alternative\- to always using \fBbundle exec\fR\. This gives you a file that can be run directly, and one that will always run the correct gem version used by the application\.
.
.P
For example, if you run \fBbundle binstubs rspec\-core\fR, Bundler will create the file \fBbin/rspec\fR\. That file will contain enough code to load Bundler, tell it to load the bundled gems, and then run rspec\.
.
.P
This command generates binstubs for executables in \fBGEM_NAME\fR\. Binstubs are put into \fBbin\fR, or the \fB\-\-path\fR directory if one has been set\. Calling binstubs with [GEM [GEM]] will create binstubs for all given gems\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-force\fR
Overwrite existing binstubs if they exist\.
.
.TP
\fB\-\-path\fR
The location to install the specified binstubs to\. This defaults to \fBbin\fR\.
.
.TP
\fB\-\-standalone\fR
Makes binstubs that can work without depending on Rubygems or Bundler at runtime\.
.
.TP
\fB\-\-shebang\fR
Specify a different shebang executable name than the default (default \'ruby\')
.
.TP
\fB\-\-all\fR
Create binstubs for all gems in the bundle\.
share/man/man1/bundle-exec.1 0000644 00000015160 15173547333 0011550 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-EXEC" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-exec\fR \- Execute a command in the context of the bundle
.
.SH "SYNOPSIS"
\fBbundle exec\fR [\-\-keep\-file\-descriptors] \fIcommand\fR
.
.SH "DESCRIPTION"
This command executes the command, making all gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] available to \fBrequire\fR in Ruby programs\.
.
.P
Essentially, if you would normally have run something like \fBrspec spec/my_spec\.rb\fR, and you want to use the gems specified in the [\fBGemfile(5)\fR][Gemfile(5)] and installed via bundle install(1) \fIbundle\-install\.1\.html\fR, you should run \fBbundle exec rspec spec/my_spec\.rb\fR\.
.
.P
Note that \fBbundle exec\fR does not require that an executable is available on your shell\'s \fB$PATH\fR\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-keep\-file\-descriptors\fR
Exec in Ruby 2\.0 began discarding non\-standard file descriptors\. When this flag is passed, exec will revert to the 1\.9 behaviour of passing all file descriptors to the new process\.
.
.SH "BUNDLE INSTALL \-\-BINSTUBS"
If you use the \fB\-\-binstubs\fR flag in bundle install(1) \fIbundle\-install\.1\.html\fR, Bundler will automatically create a directory (which defaults to \fBapp_root/bin\fR) containing all of the executables available from gems in the bundle\.
.
.P
After using \fB\-\-binstubs\fR, \fBbin/rspec spec/my_spec\.rb\fR is identical to \fBbundle exec rspec spec/my_spec\.rb\fR\.
.
.SH "ENVIRONMENT MODIFICATIONS"
\fBbundle exec\fR makes a number of changes to the shell environment, then executes the command you specify in full\.
.
.IP "\(bu" 4
make sure that it\'s still possible to shell out to \fBbundle\fR from inside a command invoked by \fBbundle exec\fR (using \fB$BUNDLE_BIN_PATH\fR)
.
.IP "\(bu" 4
put the directory containing executables (like \fBrails\fR, \fBrspec\fR, \fBrackup\fR) for your bundle on \fB$PATH\fR
.
.IP "\(bu" 4
make sure that if bundler is invoked in the subshell, it uses the same \fBGemfile\fR (by setting \fBBUNDLE_GEMFILE\fR)
.
.IP "\(bu" 4
add \fB\-rbundler/setup\fR to \fB$RUBYOPT\fR, which makes sure that Ruby programs invoked in the subshell can see the gems in the bundle
.
.IP "" 0
.
.P
It also modifies Rubygems:
.
.IP "\(bu" 4
disallow loading additional gems not in the bundle
.
.IP "\(bu" 4
modify the \fBgem\fR method to be a no\-op if a gem matching the requirements is in the bundle, and to raise a \fBGem::LoadError\fR if it\'s not
.
.IP "\(bu" 4
Define \fBGem\.refresh\fR to be a no\-op, since the source index is always frozen when using bundler, and to prevent gems from the system leaking into the environment
.
.IP "\(bu" 4
Override \fBGem\.bin_path\fR to use the gems in the bundle, making system executables work
.
.IP "\(bu" 4
Add all gems in the bundle into Gem\.loaded_specs
.
.IP "" 0
.
.P
Finally, \fBbundle exec\fR also implicitly modifies \fBGemfile\.lock\fR if the lockfile and the Gemfile do not match\. Bundler needs the Gemfile to determine things such as a gem\'s groups, \fBautorequire\fR, and platforms, etc\., and that information isn\'t stored in the lockfile\. The Gemfile and lockfile must be synced in order to \fBbundle exec\fR successfully, so \fBbundle exec\fR updates the lockfile beforehand\.
.
.SS "Loading"
By default, when attempting to \fBbundle exec\fR to a file with a ruby shebang, Bundler will \fBKernel\.load\fR that file instead of using \fBKernel\.exec\fR\. For the vast majority of cases, this is a performance improvement\. In a rare few cases, this could cause some subtle side\-effects (such as dependence on the exact contents of \fB$0\fR or \fB__FILE__\fR) and the optimization can be disabled by enabling the \fBdisable_exec_load\fR setting\.
.
.SS "Shelling out"
Any Ruby code that opens a subshell (like \fBsystem\fR, backticks, or \fB%x{}\fR) will automatically use the current Bundler environment\. If you need to shell out to a Ruby command that is not part of your current bundle, use the \fBwith_unbundled_env\fR method with a block\. Any subshells created inside the block will be given the environment present before Bundler was activated\. For example, Homebrew commands run Ruby, but don\'t work inside a bundle:
.
.IP "" 4
.
.nf
Bundler\.with_unbundled_env do
`brew install wget`
end
.
.fi
.
.IP "" 0
.
.P
Using \fBwith_unbundled_env\fR is also necessary if you are shelling out to a different bundle\. Any Bundler commands run in a subshell will inherit the current Gemfile, so commands that need to run in the context of a different bundle also need to use \fBwith_unbundled_env\fR\.
.
.IP "" 4
.
.nf
Bundler\.with_unbundled_env do
Dir\.chdir "/other/bundler/project" do
`bundle exec \./script`
end
end
.
.fi
.
.IP "" 0
.
.P
Bundler provides convenience helpers that wrap \fBsystem\fR and \fBexec\fR, and they can be used like this:
.
.IP "" 4
.
.nf
Bundler\.clean_system(\'brew install wget\')
Bundler\.clean_exec(\'brew install wget\')
.
.fi
.
.IP "" 0
.
.SH "RUBYGEMS PLUGINS"
At present, the Rubygems plugin system requires all files named \fBrubygems_plugin\.rb\fR on the load path of \fIany\fR installed gem when any Ruby code requires \fBrubygems\.rb\fR\. This includes executables installed into the system, like \fBrails\fR, \fBrackup\fR, and \fBrspec\fR\.
.
.P
Since Rubygems plugins can contain arbitrary Ruby code, they commonly end up activating themselves or their dependencies\.
.
.P
For instance, the \fBgemcutter 0\.5\fR gem depended on \fBjson_pure\fR\. If you had that version of gemcutter installed (even if you \fIalso\fR had a newer version without this problem), Rubygems would activate \fBgemcutter 0\.5\fR and \fBjson_pure <latest>\fR\.
.
.P
If your Gemfile(5) also contained \fBjson_pure\fR (or a gem with a dependency on \fBjson_pure\fR), the latest version on your system might conflict with the version in your Gemfile(5), or the snapshot version in your \fBGemfile\.lock\fR\.
.
.P
If this happens, bundler will say:
.
.IP "" 4
.
.nf
You have already activated json_pure 1\.4\.6 but your Gemfile
requires json_pure 1\.4\.3\. Consider using bundle exec\.
.
.fi
.
.IP "" 0
.
.P
In this situation, you almost certainly want to remove the underlying gem with the problematic gem plugin\. In general, the authors of these plugins (in this case, the \fBgemcutter\fR gem) have released newer versions that are more careful in their plugins\.
.
.P
You can find a list of all the gems containing gem plugins by running
.
.IP "" 4
.
.nf
ruby \-e "puts Gem\.find_files(\'rubygems_plugin\.rb\')"
.
.fi
.
.IP "" 0
.
.P
At the very least, you should remove all but the newest version of each gem plugin, and also remove all gem plugins that you aren\'t using (\fBgem uninstall gem_name\fR)\.
share/man/man1/bundle-gem.1 0000644 00000012304 15173547333 0011371 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-GEM" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
.
.SH "SYNOPSIS"
\fBbundle gem\fR \fIGEM_NAME\fR \fIOPTIONS\fR
.
.SH "DESCRIPTION"
Generates a directory named \fBGEM_NAME\fR with a \fBRakefile\fR, \fBGEM_NAME\.gemspec\fR, and other supporting files and directories that can be used to develop a rubygem with that name\.
.
.P
Run \fBrake \-T\fR in the resulting project for a list of Rake tasks that can be used to test and publish the gem to rubygems\.org\.
.
.P
The generated project skeleton can be customized with OPTIONS, as explained below\. Note that these options can also be specified via Bundler\'s global configuration file using the following names:
.
.IP "\(bu" 4
\fBgem\.coc\fR
.
.IP "\(bu" 4
\fBgem\.mit\fR
.
.IP "\(bu" 4
\fBgem\.test\fR
.
.IP "" 0
.
.SH "OPTIONS"
.
.IP "\(bu" 4
\fB\-\-exe\fR or \fB\-b\fR or \fB\-\-bin\fR: Specify that Bundler should create a binary executable (as \fBexe/GEM_NAME\fR) in the generated rubygem project\. This binary will also be added to the \fBGEM_NAME\.gemspec\fR manifest\. This behavior is disabled by default\.
.
.IP "\(bu" 4
\fB\-\-no\-exe\fR: Do not create a binary (overrides \fB\-\-exe\fR specified in the global config)\.
.
.IP "\(bu" 4
\fB\-\-coc\fR: Add a \fBCODE_OF_CONDUCT\.md\fR file to the root of the generated project\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
.
.IP "\(bu" 4
\fB\-\-no\-coc\fR: Do not create a \fBCODE_OF_CONDUCT\.md\fR (overrides \fB\-\-coc\fR specified in the global config)\.
.
.IP "\(bu" 4
\fB\-\-ext=c\fR, \fB\-\-ext=rust\fR Add boilerplate for C or Rust (currently magnus \fIhttps://docs\.rs/magnus\fR based) extension code to the generated project\. This behavior is disabled by default\.
.
.IP "\(bu" 4
\fB\-\-no\-ext\fR: Do not add extension code (overrides \fB\-\-ext\fR specified in the global config)\.
.
.IP "\(bu" 4
\fB\-\-mit\fR: Add an MIT license to a \fBLICENSE\.txt\fR file in the root of the generated project\. Your name from the global git config is used for the copyright statement\. If this option is unspecified, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
.
.IP "\(bu" 4
\fB\-\-no\-mit\fR: Do not create a \fBLICENSE\.txt\fR (overrides \fB\-\-mit\fR specified in the global config)\.
.
.IP "\(bu" 4
\fB\-t\fR, \fB\-\-test=minitest\fR, \fB\-\-test=rspec\fR, \fB\-\-test=test\-unit\fR: Specify the test framework that Bundler should use when generating the project\. Acceptable values are \fBminitest\fR, \fBrspec\fR and \fBtest\-unit\fR\. The \fBGEM_NAME\.gemspec\fR will be configured and a skeleton test/spec directory will be created based on this option\. Given no option is specified:
.
.IP
When Bundler is configured to generate tests, this defaults to Bundler\'s global config setting \fBgem\.test\fR\.
.
.IP
When Bundler is configured to not generate tests, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
.
.IP "\(bu" 4
\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.
.IP
When Bundler is configured to generate CI files, this defaults to Bundler\'s global config setting \fBgem\.ci\fR\.
.
.IP
When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
.
.IP "\(bu" 4
\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project\'s development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.
.IP
When Bundler is configured to add a linter, this defaults to Bundler\'s global config setting \fBgem\.linter\fR\.
.
.IP
When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler\'s global config for future \fBbundle gem\fR use\.
.
.IP "\(bu" 4
\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR: Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
.
.IP "" 0
.
.SH "SEE ALSO"
.
.IP "\(bu" 4
bundle config(1) \fIbundle\-config\.1\.html\fR
.
.IP "" 0
share/man/man1/bundle.1 0000644 00000007375 15173547333 0010637 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\fR \- Ruby Dependency Management
.
.SH "SYNOPSIS"
\fBbundle\fR COMMAND [\-\-no\-color] [\-\-verbose] [ARGS]
.
.SH "DESCRIPTION"
Bundler manages an \fBapplication\'s dependencies\fR through its entire life across many machines systematically and repeatably\.
.
.P
See the bundler website \fIhttps://bundler\.io\fR for information on getting started, and Gemfile(5) for more information on the \fBGemfile\fR format\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-no\-color\fR
Print all output without color
.
.TP
\fB\-\-retry\fR, \fB\-r\fR
Specify the number of times you wish to attempt network commands
.
.TP
\fB\-\-verbose\fR, \fB\-V\fR
Print out additional logging information
.
.SH "BUNDLE COMMANDS"
We divide \fBbundle\fR subcommands into primary commands and utilities:
.
.SH "PRIMARY COMMANDS"
.
.TP
\fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR
Install the gems specified by the \fBGemfile\fR or \fBGemfile\.lock\fR
.
.TP
\fBbundle update(1)\fR \fIbundle\-update\.1\.html\fR
Update dependencies to their latest versions
.
.TP
\fBbundle cache(1)\fR \fIbundle\-cache\.1\.html\fR
Package the \.gem files required by your application into the \fBvendor/cache\fR directory (aliases: \fBbundle package\fR, \fBbundle pack\fR)
.
.TP
\fBbundle exec(1)\fR \fIbundle\-exec\.1\.html\fR
Execute a script in the current bundle
.
.TP
\fBbundle config(1)\fR \fIbundle\-config\.1\.html\fR
Specify and read configuration options for Bundler
.
.TP
\fBbundle help(1)\fR \fIbundle\-help\.1\.html\fR
Display detailed help for each subcommand
.
.SH "UTILITIES"
.
.TP
\fBbundle add(1)\fR \fIbundle\-add\.1\.html\fR
Add the named gem to the Gemfile and run \fBbundle install\fR
.
.TP
\fBbundle binstubs(1)\fR \fIbundle\-binstubs\.1\.html\fR
Generate binstubs for executables in a gem
.
.TP
\fBbundle check(1)\fR \fIbundle\-check\.1\.html\fR
Determine whether the requirements for your application are installed and available to Bundler
.
.TP
\fBbundle show(1)\fR \fIbundle\-show\.1\.html\fR
Show the source location of a particular gem in the bundle
.
.TP
\fBbundle outdated(1)\fR \fIbundle\-outdated\.1\.html\fR
Show all of the outdated gems in the current bundle
.
.TP
\fBbundle console(1)\fR (deprecated)
Start an IRB session in the current bundle
.
.TP
\fBbundle open(1)\fR \fIbundle\-open\.1\.html\fR
Open an installed gem in the editor
.
.TP
\fBbundle lock(1)\fR \fIbundle\-lock\.1\.html\fR
Generate a lockfile for your dependencies
.
.TP
\fBbundle viz(1)\fR \fIbundle\-viz\.1\.html\fR (deprecated)
Generate a visual representation of your dependencies
.
.TP
\fBbundle init(1)\fR \fIbundle\-init\.1\.html\fR
Generate a simple \fBGemfile\fR, placed in the current directory
.
.TP
\fBbundle gem(1)\fR \fIbundle\-gem\.1\.html\fR
Create a simple gem, suitable for development with Bundler
.
.TP
\fBbundle platform(1)\fR \fIbundle\-platform\.1\.html\fR
Display platform compatibility information
.
.TP
\fBbundle clean(1)\fR \fIbundle\-clean\.1\.html\fR
Clean up unused gems in your Bundler directory
.
.TP
\fBbundle doctor(1)\fR \fIbundle\-doctor\.1\.html\fR
Display warnings about common problems
.
.TP
\fBbundle remove(1)\fR \fIbundle\-remove\.1\.html\fR
Removes gems from the Gemfile
.
.TP
\fBbundle plugin(1)\fR \fIbundle\-plugin\.1\.html\fR
Manage Bundler plugins
.
.TP
\fBbundle version(1)\fR \fIbundle\-version\.1\.html\fR
Prints Bundler version information
.
.SH "PLUGINS"
When running a command that isn\'t listed in PRIMARY COMMANDS or UTILITIES, Bundler will try to find an executable on your path named \fBbundler\-<command>\fR and execute it, passing down any extra arguments to it\.
.
.SH "OBSOLETE"
These commands are obsolete and should no longer be used:
.
.IP "\(bu" 4
\fBbundle inject(1)\fR
.
.IP "" 0
share/man/man1/bundle-init.1 0000644 00000002215 15173547333 0011564 0 ustar 00 .\" generated with Ronn/v0.7.3
.\" http://github.com/rtomayko/ronn/tree/0.7.3
.
.TH "BUNDLE\-INIT" "1" "August 2023" "" ""
.
.SH "NAME"
\fBbundle\-init\fR \- Generates a Gemfile into the current working directory
.
.SH "SYNOPSIS"
\fBbundle init\fR [\-\-gemspec=FILE]
.
.SH "DESCRIPTION"
Init generates a default [\fBGemfile(5)\fR][Gemfile(5)] in the current working directory\. When adding a [\fBGemfile(5)\fR][Gemfile(5)] to a gem with a gemspec, the \fB\-\-gemspec\fR option will automatically add each dependency listed in the gemspec file to the newly created [\fBGemfile(5)\fR][Gemfile(5)]\.
.
.SH "OPTIONS"
.
.TP
\fB\-\-gemspec\fR
Use the specified \.gemspec to create the [\fBGemfile(5)\fR][Gemfile(5)]
.
.TP
\fB\-\-gemfile\fR
Use the specified name for the gemfile instead of \fBGemfile\fR
.
.SH "FILES"
Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\.
.
.SH "SEE ALSO"
Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR
share/ruby/fileutils.rb 0000644 00000236021 15173547333 0011173 0 ustar 00 # frozen_string_literal: true
begin
require 'rbconfig'
rescue LoadError
# for make mjit-headers
end
# Namespace for file utility methods for copying, moving, removing, etc.
#
# == What's Here
#
# First, what’s elsewhere. \Module \FileUtils:
#
# - Inherits from {class Object}[rdoc-ref:Object].
# - Supplements {class File}[rdoc-ref:File]
# (but is not included or extended there).
#
# Here, module \FileUtils provides methods that are useful for:
#
# - {Creating}[rdoc-ref:FileUtils@Creating].
# - {Deleting}[rdoc-ref:FileUtils@Deleting].
# - {Querying}[rdoc-ref:FileUtils@Querying].
# - {Setting}[rdoc-ref:FileUtils@Setting].
# - {Comparing}[rdoc-ref:FileUtils@Comparing].
# - {Copying}[rdoc-ref:FileUtils@Copying].
# - {Moving}[rdoc-ref:FileUtils@Moving].
# - {Options}[rdoc-ref:FileUtils@Options].
#
# === Creating
#
# - ::mkdir: Creates directories.
# - ::mkdir_p, ::makedirs, ::mkpath: Creates directories,
# also creating ancestor directories as needed.
# - ::link_entry: Creates a hard link.
# - ::ln, ::link: Creates hard links.
# - ::ln_s, ::symlink: Creates symbolic links.
# - ::ln_sf: Creates symbolic links, overwriting if necessary.
# - ::ln_sr: Creates symbolic links relative to targets
#
# === Deleting
#
# - ::remove_dir: Removes a directory and its descendants.
# - ::remove_entry: Removes an entry, including its descendants if it is a directory.
# - ::remove_entry_secure: Like ::remove_entry, but removes securely.
# - ::remove_file: Removes a file entry.
# - ::rm, ::remove: Removes entries.
# - ::rm_f, ::safe_unlink: Like ::rm, but removes forcibly.
# - ::rm_r: Removes entries and their descendants.
# - ::rm_rf, ::rmtree: Like ::rm_r, but removes forcibly.
# - ::rmdir: Removes directories.
#
# === Querying
#
# - ::pwd, ::getwd: Returns the path to the working directory.
# - ::uptodate?: Returns whether a given entry is newer than given other entries.
#
# === Setting
#
# - ::cd, ::chdir: Sets the working directory.
# - ::chmod: Sets permissions for an entry.
# - ::chmod_R: Sets permissions for an entry and its descendants.
# - ::chown: Sets the owner and group for entries.
# - ::chown_R: Sets the owner and group for entries and their descendants.
# - ::touch: Sets modification and access times for entries,
# creating if necessary.
#
# === Comparing
#
# - ::compare_file, ::cmp, ::identical?: Returns whether two entries are identical.
# - ::compare_stream: Returns whether two streams are identical.
#
# === Copying
#
# - ::copy_entry: Recursively copies an entry.
# - ::copy_file: Copies an entry.
# - ::copy_stream: Copies a stream.
# - ::cp, ::copy: Copies files.
# - ::cp_lr: Recursively creates hard links.
# - ::cp_r: Recursively copies files, retaining mode, owner, and group.
# - ::install: Recursively copies files, optionally setting mode,
# owner, and group.
#
# === Moving
#
# - ::mv, ::move: Moves entries.
#
# === Options
#
# - ::collect_method: Returns the names of methods that accept a given option.
# - ::commands: Returns the names of methods that accept options.
# - ::have_option?: Returns whether a given method accepts a given option.
# - ::options: Returns all option names.
# - ::options_of: Returns the names of the options for a given method.
#
# == Path Arguments
#
# Some methods in \FileUtils accept _path_ arguments,
# which are interpreted as paths to filesystem entries:
#
# - If the argument is a string, that value is the path.
# - If the argument has method +:to_path+, it is converted via that method.
# - If the argument has method +:to_str+, it is converted via that method.
#
# == About the Examples
#
# Some examples here involve trees of file entries.
# For these, we sometimes display trees using the
# {tree command-line utility}[https://en.wikipedia.org/wiki/Tree_(command)],
# which is a recursive directory-listing utility that produces
# a depth-indented listing of files and directories.
#
# We use a helper method to launch the command and control the format:
#
# def tree(dirpath = '.')
# command = "tree --noreport --charset=ascii #{dirpath}"
# system(command)
# end
#
# To illustrate:
#
# tree('src0')
# # => src0
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
#
# == Avoiding the TOCTTOU Vulnerability
#
# For certain methods that recursively remove entries,
# there is a potential vulnerability called the
# {Time-of-check to time-of-use}[https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use],
# or TOCTTOU, vulnerability that can exist when:
#
# - An ancestor directory of the entry at the target path is world writable;
# such directories include <tt>/tmp</tt>.
# - The directory tree at the target path includes:
#
# - A world-writable descendant directory.
# - A symbolic link.
#
# To avoid that vulnerability, you can use this method to remove entries:
#
# - FileUtils.remove_entry_secure: removes recursively
# if the target path points to a directory.
#
# Also available are these methods,
# each of which calls \FileUtils.remove_entry_secure:
#
# - FileUtils.rm_r with keyword argument <tt>secure: true</tt>.
# - FileUtils.rm_rf with keyword argument <tt>secure: true</tt>.
#
# Finally, this method for moving entries calls \FileUtils.remove_entry_secure
# if the source and destination are on different file systems
# (which means that the "move" is really a copy and remove):
#
# - FileUtils.mv with keyword argument <tt>secure: true</tt>.
#
# \Method \FileUtils.remove_entry_secure removes securely
# by applying a special pre-process:
#
# - If the target path points to a directory, this method uses methods
# {File#chown}[rdoc-ref:File#chown]
# and {File#chmod}[rdoc-ref:File#chmod]
# in removing directories.
# - The owner of the target directory should be either the current process
# or the super user (root).
#
# WARNING: You must ensure that *ALL* parent directories cannot be
# moved by other untrusted users. For example, parent directories
# should not be owned by untrusted users, and should not be world
# writable except when the sticky bit is set.
#
# For details of this security vulnerability, see Perl cases:
#
# - {CVE-2005-0448}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448].
# - {CVE-2004-0452}[https://cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452].
#
module FileUtils
VERSION = "1.7.0"
def self.private_module_function(name) #:nodoc:
module_function name
private_class_method name
end
#
# Returns a string containing the path to the current directory:
#
# FileUtils.pwd # => "/rdoc/fileutils"
#
# FileUtils.getwd is an alias for FileUtils.pwd.
#
# Related: FileUtils.cd.
#
def pwd
Dir.pwd
end
module_function :pwd
alias getwd pwd
module_function :getwd
# Changes the working directory to the given +dir+, which
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments]:
#
# With no block given,
# changes the current directory to the directory at +dir+; returns zero:
#
# FileUtils.pwd # => "/rdoc/fileutils"
# FileUtils.cd('..')
# FileUtils.pwd # => "/rdoc"
# FileUtils.cd('fileutils')
#
# With a block given, changes the current directory to the directory
# at +dir+, calls the block with argument +dir+,
# and restores the original current directory; returns the block's value:
#
# FileUtils.pwd # => "/rdoc/fileutils"
# FileUtils.cd('..') { |arg| [arg, FileUtils.pwd] } # => ["..", "/rdoc"]
# FileUtils.pwd # => "/rdoc/fileutils"
#
# Keyword arguments:
#
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.cd('..')
# FileUtils.cd('fileutils')
#
# Output:
#
# cd ..
# cd fileutils
#
# FileUtils.chdir is an alias for FileUtils.cd.
#
# Related: FileUtils.pwd.
#
def cd(dir, verbose: nil, &block) # :yield: dir
fu_output_message "cd #{dir}" if verbose
result = Dir.chdir(dir, &block)
fu_output_message 'cd -' if verbose and block
result
end
module_function :cd
alias chdir cd
module_function :chdir
#
# Returns +true+ if the file at path +new+
# is newer than all the files at paths in array +old_list+;
# +false+ otherwise.
#
# Argument +new+ and the elements of +old_list+
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments]:
#
# FileUtils.uptodate?('Rakefile', ['Gemfile', 'README.md']) # => true
# FileUtils.uptodate?('Gemfile', ['Rakefile', 'README.md']) # => false
#
# A non-existent file is considered to be infinitely old.
#
# Related: FileUtils.touch.
#
def uptodate?(new, old_list)
return false unless File.exist?(new)
new_time = File.mtime(new)
old_list.each do |old|
if File.exist?(old)
return false unless new_time > File.mtime(old)
end
end
true
end
module_function :uptodate?
def remove_trailing_slash(dir) #:nodoc:
dir == '/' ? dir : dir.chomp(?/)
end
private_module_function :remove_trailing_slash
#
# Creates directories at the paths in the given +list+
# (a single path or an array of paths);
# returns +list+ if it is an array, <tt>[list]</tt> otherwise.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# With no keyword arguments, creates a directory at each +path+ in +list+
# by calling: <tt>Dir.mkdir(path, mode)</tt>;
# see {Dir.mkdir}[rdoc-ref:Dir.mkdir]:
#
# FileUtils.mkdir(%w[tmp0 tmp1]) # => ["tmp0", "tmp1"]
# FileUtils.mkdir('tmp4') # => ["tmp4"]
#
# Keyword arguments:
#
# - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
# see {File.chmod}[rdoc-ref:File.chmod].
# - <tt>noop: true</tt> - does not create directories.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.mkdir(%w[tmp0 tmp1], verbose: true)
# FileUtils.mkdir(%w[tmp2 tmp3], mode: 0700, verbose: true)
#
# Output:
#
# mkdir tmp0 tmp1
# mkdir -m 700 tmp2 tmp3
#
# Raises an exception if any path points to an existing
# file or directory, or if for any reason a directory cannot be created.
#
# Related: FileUtils.mkdir_p.
#
def mkdir(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
fu_mkdir dir, mode
end
end
module_function :mkdir
#
# Creates directories at the paths in the given +list+
# (a single path or an array of paths),
# also creating ancestor directories as needed;
# returns +list+ if it is an array, <tt>[list]</tt> otherwise.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# With no keyword arguments, creates a directory at each +path+ in +list+,
# along with any needed ancestor directories,
# by calling: <tt>Dir.mkdir(path, mode)</tt>;
# see {Dir.mkdir}[rdoc-ref:Dir.mkdir]:
#
# FileUtils.mkdir_p(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
# FileUtils.mkdir_p('tmp4/tmp5') # => ["tmp4/tmp5"]
#
# Keyword arguments:
#
# - <tt>mode: <i>mode</i></tt> - also calls <tt>File.chmod(mode, path)</tt>;
# see {File.chmod}[rdoc-ref:File.chmod].
# - <tt>noop: true</tt> - does not create directories.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.mkdir_p(%w[tmp0 tmp1], verbose: true)
# FileUtils.mkdir_p(%w[tmp2 tmp3], mode: 0700, verbose: true)
#
# Output:
#
# mkdir -p tmp0 tmp1
# mkdir -p -m 700 tmp2 tmp3
#
# Raises an exception if for any reason a directory cannot be created.
#
# FileUtils.mkpath and FileUtils.makedirs are aliases for FileUtils.mkdir_p.
#
# Related: FileUtils.mkdir.
#
def mkdir_p(list, mode: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "mkdir -p #{mode ? ('-m %03o ' % mode) : ''}#{list.join ' '}" if verbose
return *list if noop
list.each do |item|
path = remove_trailing_slash(item)
stack = []
until File.directory?(path) || File.dirname(path) == path
stack.push path
path = File.dirname(path)
end
stack.reverse_each do |dir|
begin
fu_mkdir dir, mode
rescue SystemCallError
raise unless File.directory?(dir)
end
end
end
return *list
end
module_function :mkdir_p
alias mkpath mkdir_p
alias makedirs mkdir_p
module_function :mkpath
module_function :makedirs
def fu_mkdir(path, mode) #:nodoc:
path = remove_trailing_slash(path)
if mode
Dir.mkdir path, mode
File.chmod mode, path
else
Dir.mkdir path
end
end
private_module_function :fu_mkdir
#
# Removes directories at the paths in the given +list+
# (a single path or an array of paths);
# returns +list+, if it is an array, <tt>[list]</tt> otherwise.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# With no keyword arguments, removes the directory at each +path+ in +list+,
# by calling: <tt>Dir.rmdir(path)</tt>;
# see {Dir.rmdir}[rdoc-ref:Dir.rmdir]:
#
# FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3]) # => ["tmp0/tmp1", "tmp2/tmp3"]
# FileUtils.rmdir('tmp4/tmp5') # => ["tmp4/tmp5"]
#
# Keyword arguments:
#
# - <tt>parents: true</tt> - removes successive ancestor directories
# if empty.
# - <tt>noop: true</tt> - does not remove directories.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.rmdir(%w[tmp0/tmp1 tmp2/tmp3], parents: true, verbose: true)
# FileUtils.rmdir('tmp4/tmp5', parents: true, verbose: true)
#
# Output:
#
# rmdir -p tmp0/tmp1 tmp2/tmp3
# rmdir -p tmp4/tmp5
#
# Raises an exception if a directory does not exist
# or if for any reason a directory cannot be removed.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rmdir(list, parents: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rmdir #{parents ? '-p ' : ''}#{list.join ' '}" if verbose
return if noop
list.each do |dir|
Dir.rmdir(dir = remove_trailing_slash(dir))
if parents
begin
until (parent = File.dirname(dir)) == '.' or parent == dir
dir = parent
Dir.rmdir(dir)
end
rescue Errno::ENOTEMPTY, Errno::EEXIST, Errno::ENOENT
end
end
end
end
module_function :rmdir
# Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# When +src+ is the path to an existing file
# and +dest+ is the path to a non-existent file,
# creates a hard link at +dest+ pointing to +src+; returns zero:
#
# Dir.children('tmp0/') # => ["t.txt"]
# Dir.children('tmp1/') # => []
# FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk') # => 0
# Dir.children('tmp1/') # => ["t.lnk"]
#
# When +src+ is the path to an existing file
# and +dest+ is the path to an existing directory,
# creates a hard link at <tt>dest/src</tt> pointing to +src+; returns zero:
#
# Dir.children('tmp2') # => ["t.dat"]
# Dir.children('tmp3') # => []
# FileUtils.ln('tmp2/t.dat', 'tmp3') # => 0
# Dir.children('tmp3') # => ["t.dat"]
#
# When +src+ is an array of paths to existing files
# and +dest+ is the path to an existing directory,
# then for each path +target+ in +src+,
# creates a hard link at <tt>dest/target</tt> pointing to +target+;
# returns +src+:
#
# Dir.children('tmp4/') # => []
# FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/') # => ["tmp0/t.txt", "tmp2/t.dat"]
# Dir.children('tmp4/') # => ["t.dat", "t.txt"]
#
# Keyword arguments:
#
# - <tt>force: true</tt> - overwrites +dest+ if it exists.
# - <tt>noop: true</tt> - does not create links.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.ln('tmp0/t.txt', 'tmp1/t.lnk', verbose: true)
# FileUtils.ln('tmp2/t.dat', 'tmp3', verbose: true)
# FileUtils.ln(['tmp0/t.txt', 'tmp2/t.dat'], 'tmp4/', verbose: true)
#
# Output:
#
# ln tmp0/t.txt tmp1/t.lnk
# ln tmp2/t.dat tmp3
# ln tmp0/t.txt tmp2/t.dat tmp4/
#
# Raises an exception if +dest+ is the path to an existing file
# and keyword argument +force+ is not +true+.
#
# FileUtils#link is an alias for FileUtils#ln.
#
# Related: FileUtils.link_entry (has different options).
#
def ln(src, dest, force: nil, noop: nil, verbose: nil)
fu_output_message "ln#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.link s, d
end
end
module_function :ln
alias link ln
module_function :link
# Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link].
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ is the path to a directory and +dest+ does not exist,
# creates links +dest+ and descendents pointing to +src+ and its descendents:
#
# tree('src0')
# # => src0
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
# File.exist?('dest0') # => false
# FileUtils.cp_lr('src0', 'dest0')
# tree('dest0')
# # => dest0
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
#
# If +src+ and +dest+ are both paths to directories,
# creates links <tt>dest/src</tt> and descendents
# pointing to +src+ and its descendents:
#
# tree('src1')
# # => src1
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.mkdir('dest1')
# FileUtils.cp_lr('src1', 'dest1')
# tree('dest1')
# # => dest1
# # `-- src1
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
#
# If +src+ is an array of paths to entries and +dest+ is the path to a directory,
# for each path +filepath+ in +src+, creates a link at <tt>dest/filepath</tt>
# pointing to that path:
#
# tree('src2')
# # => src2
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.mkdir('dest2')
# FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2')
# tree('dest2')
# # => dest2
# # |-- sub0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- sub1
# # |-- src2.txt
# # `-- src3.txt
#
# Keyword arguments:
#
# - <tt>dereference_root: false</tt> - if +src+ is a symbolic link,
# does not dereference it.
# - <tt>noop: true</tt> - does not create links.
# - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.cp_lr('src0', 'dest0', noop: true, verbose: true)
# FileUtils.cp_lr('src1', 'dest1', noop: true, verbose: true)
# FileUtils.cp_lr(['src2/sub0', 'src2/sub1'], 'dest2', noop: true, verbose: true)
#
# Output:
#
# cp -lr src0 dest0
# cp -lr src1 dest1
# cp -lr src2/sub0 src2/sub1 dest2
#
# Raises an exception if +dest+ is the path to an existing file or directory
# and keyword argument <tt>remove_destination: true</tt> is not given.
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def cp_lr(src, dest, noop: nil, verbose: nil,
dereference_root: true, remove_destination: false)
fu_output_message "cp -lr#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
link_entry s, d, dereference_root, remove_destination
end
end
module_function :cp_lr
# Creates {symbolic links}[https://en.wikipedia.org/wiki/Symbolic_link].
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ is the path to an existing file:
#
# - When +dest+ is the path to a non-existent file,
# creates a symbolic link at +dest+ pointing to +src+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.ln_s('src0.txt', 'dest0.txt')
# File.symlink?('dest0.txt') # => true
#
# - When +dest+ is the path to an existing file,
# creates a symbolic link at +dest+ pointing to +src+
# if and only if keyword argument <tt>force: true</tt> is given
# (raises an exception otherwise):
#
# FileUtils.touch('src1.txt')
# FileUtils.touch('dest1.txt')
# FileUtils.ln_s('src1.txt', 'dest1.txt', force: true)
# FileTest.symlink?('dest1.txt') # => true
#
# FileUtils.ln_s('src1.txt', 'dest1.txt') # Raises Errno::EEXIST.
#
# If +dest+ is the path to a directory,
# creates a symbolic link at <tt>dest/src</tt> pointing to +src+:
#
# FileUtils.touch('src2.txt')
# FileUtils.mkdir('destdir2')
# FileUtils.ln_s('src2.txt', 'destdir2')
# File.symlink?('destdir2/src2.txt') # => true
#
# If +src+ is an array of paths to existing files and +dest+ is a directory,
# for each child +child+ in +src+ creates a symbolic link <tt>dest/child</tt>
# pointing to +child+:
#
# FileUtils.mkdir('srcdir3')
# FileUtils.touch('srcdir3/src0.txt')
# FileUtils.touch('srcdir3/src1.txt')
# FileUtils.mkdir('destdir3')
# FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3')
# File.symlink?('destdir3/src0.txt') # => true
# File.symlink?('destdir3/src1.txt') # => true
#
# Keyword arguments:
#
# - <tt>force: true</tt> - overwrites +dest+ if it exists.
# - <tt>relative: false</tt> - create links relative to +dest+.
# - <tt>noop: true</tt> - does not create links.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.ln_s('src0.txt', 'dest0.txt', noop: true, verbose: true)
# FileUtils.ln_s('src1.txt', 'destdir1', noop: true, verbose: true)
# FileUtils.ln_s('src2.txt', 'dest2.txt', force: true, noop: true, verbose: true)
# FileUtils.ln_s(['srcdir3/src0.txt', 'srcdir3/src1.txt'], 'destdir3', noop: true, verbose: true)
#
# Output:
#
# ln -s src0.txt dest0.txt
# ln -s src1.txt destdir1
# ln -sf src2.txt dest2.txt
# ln -s srcdir3/src0.txt srcdir3/src1.txt destdir3
#
# FileUtils.symlink is an alias for FileUtils.ln_s.
#
# Related: FileUtils.ln_sf.
#
def ln_s(src, dest, force: nil, relative: false, target_directory: true, noop: nil, verbose: nil)
if relative
return ln_sr(src, dest, force: force, noop: noop, verbose: verbose)
end
fu_output_message "ln -s#{force ? 'f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest0(src, dest) do |s,d|
remove_file d, true if force
File.symlink s, d
end
end
module_function :ln_s
alias symlink ln_s
module_function :symlink
# Like FileUtils.ln_s, but always with keyword argument <tt>force: true</tt> given.
#
def ln_sf(src, dest, noop: nil, verbose: nil)
ln_s src, dest, force: true, noop: noop, verbose: verbose
end
module_function :ln_sf
# Like FileUtils.ln_s, but create links relative to +dest+.
#
def ln_sr(src, dest, target_directory: true, force: nil, noop: nil, verbose: nil)
options = "#{force ? 'f' : ''}#{target_directory ? '' : 'T'}"
dest = File.path(dest)
srcs = Array(src)
link = proc do |s, target_dir_p = true|
s = File.path(s)
if target_dir_p
d = File.join(destdirs = dest, File.basename(s))
else
destdirs = File.dirname(d = dest)
end
destdirs = fu_split_path(File.realpath(destdirs))
if fu_starting_path?(s)
srcdirs = fu_split_path((File.realdirpath(s) rescue File.expand_path(s)))
base = fu_relative_components_from(srcdirs, destdirs)
s = File.join(*base)
else
srcdirs = fu_clean_components(*fu_split_path(s))
base = fu_relative_components_from(fu_split_path(Dir.pwd), destdirs)
while srcdirs.first&. == ".." and base.last&.!=("..") and !fu_starting_path?(base.last)
srcdirs.shift
base.pop
end
s = File.join(*base, *srcdirs)
end
fu_output_message "ln -s#{options} #{s} #{d}" if verbose
next if noop
remove_file d, true if force
File.symlink s, d
end
case srcs.size
when 0
when 1
link[srcs[0], target_directory && File.directory?(dest)]
else
srcs.each(&link)
end
end
module_function :ln_sr
# Creates {hard links}[https://en.wikipedia.org/wiki/Hard_link]; returns +nil+.
#
# Arguments +src+ and +dest+
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ is the path to a file and +dest+ does not exist,
# creates a hard link at +dest+ pointing to +src+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.link_entry('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
# If +src+ is the path to a directory and +dest+ does not exist,
# recursively creates hard links at +dest+ pointing to paths in +src+:
#
# FileUtils.mkdir_p(['src1/dir0', 'src1/dir1'])
# src_file_paths = [
# 'src1/dir0/t0.txt',
# 'src1/dir0/t1.txt',
# 'src1/dir1/t2.txt',
# 'src1/dir1/t3.txt',
# ]
# FileUtils.touch(src_file_paths)
# File.directory?('dest1') # => true
# FileUtils.link_entry('src1', 'dest1')
# File.file?('dest1/dir0/t0.txt') # => true
# File.file?('dest1/dir0/t1.txt') # => true
# File.file?('dest1/dir1/t2.txt') # => true
# File.file?('dest1/dir1/t3.txt') # => true
#
# Keyword arguments:
#
# - <tt>dereference_root: true</tt> - dereferences +src+ if it is a symbolic link.
# - <tt>remove_destination: true</tt> - removes +dest+ before creating links.
#
# Raises an exception if +dest+ is the path to an existing file or directory
# and keyword argument <tt>remove_destination: true</tt> is not given.
#
# Related: FileUtils.ln (has different options).
#
def link_entry(src, dest, dereference_root = false, remove_destination = false)
Entry_.new(src, nil, dereference_root).traverse do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && File.file?(destent.path)
ent.link destent.path
end
end
module_function :link_entry
# Copies files.
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ is the path to a file and +dest+ is not the path to a directory,
# copies +src+ to +dest+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.cp('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
# If +src+ is the path to a file and +dest+ is the path to a directory,
# copies +src+ to <tt>dest/src</tt>:
#
# FileUtils.touch('src1.txt')
# FileUtils.mkdir('dest1')
# FileUtils.cp('src1.txt', 'dest1')
# File.file?('dest1/src1.txt') # => true
#
# If +src+ is an array of paths to files and +dest+ is the path to a directory,
# copies from each +src+ to +dest+:
#
# src_file_paths = ['src2.txt', 'src2.dat']
# FileUtils.touch(src_file_paths)
# FileUtils.mkdir('dest2')
# FileUtils.cp(src_file_paths, 'dest2')
# File.file?('dest2/src2.txt') # => true
# File.file?('dest2/src2.dat') # => true
#
# Keyword arguments:
#
# - <tt>preserve: true</tt> - preserves file times.
# - <tt>noop: true</tt> - does not copy files.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.cp('src0.txt', 'dest0.txt', noop: true, verbose: true)
# FileUtils.cp('src1.txt', 'dest1', noop: true, verbose: true)
# FileUtils.cp(src_file_paths, 'dest2', noop: true, verbose: true)
#
# Output:
#
# cp src0.txt dest0.txt
# cp src1.txt dest1
# cp src2.txt src2.dat dest2
#
# Raises an exception if +src+ is a directory.
#
# FileUtils.copy is an alias for FileUtils.cp.
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def cp(src, dest, preserve: nil, noop: nil, verbose: nil)
fu_output_message "cp#{preserve ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_file s, d, preserve
end
end
module_function :cp
alias copy cp
module_function :copy
# Recursively copies files.
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# The mode, owner, and group are retained in the copy;
# to change those, use FileUtils.install instead.
#
# If +src+ is the path to a file and +dest+ is not the path to a directory,
# copies +src+ to +dest+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.cp_r('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
# If +src+ is the path to a file and +dest+ is the path to a directory,
# copies +src+ to <tt>dest/src</tt>:
#
# FileUtils.touch('src1.txt')
# FileUtils.mkdir('dest1')
# FileUtils.cp_r('src1.txt', 'dest1')
# File.file?('dest1/src1.txt') # => true
#
# If +src+ is the path to a directory and +dest+ does not exist,
# recursively copies +src+ to +dest+:
#
# tree('src2')
# # => src2
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.exist?('dest2') # => false
# FileUtils.cp_r('src2', 'dest2')
# tree('dest2')
# # => dest2
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
#
# If +src+ and +dest+ are paths to directories,
# recursively copies +src+ to <tt>dest/src</tt>:
#
# tree('src3')
# # => src3
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.mkdir('dest3')
# FileUtils.cp_r('src3', 'dest3')
# tree('dest3')
# # => dest3
# # `-- src3
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
#
# If +src+ is an array of paths and +dest+ is a directory,
# recursively copies from each path in +src+ to +dest+;
# the paths in +src+ may point to files and/or directories.
#
# Keyword arguments:
#
# - <tt>dereference_root: false</tt> - if +src+ is a symbolic link,
# does not dereference it.
# - <tt>noop: true</tt> - does not copy files.
# - <tt>preserve: true</tt> - preserves file times.
# - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.cp_r('src0.txt', 'dest0.txt', noop: true, verbose: true)
# FileUtils.cp_r('src1.txt', 'dest1', noop: true, verbose: true)
# FileUtils.cp_r('src2', 'dest2', noop: true, verbose: true)
# FileUtils.cp_r('src3', 'dest3', noop: true, verbose: true)
#
# Output:
#
# cp -r src0.txt dest0.txt
# cp -r src1.txt dest1
# cp -r src2 dest2
# cp -r src3 dest3
#
# Raises an exception of +src+ is the path to a directory
# and +dest+ is the path to a file.
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def cp_r(src, dest, preserve: nil, noop: nil, verbose: nil,
dereference_root: true, remove_destination: nil)
fu_output_message "cp -r#{preserve ? 'p' : ''}#{remove_destination ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
copy_entry s, d, preserve, dereference_root, remove_destination
end
end
module_function :cp_r
# Recursively copies files from +src+ to +dest+.
#
# Arguments +src+ and +dest+
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ is the path to a file, copies +src+ to +dest+:
#
# FileUtils.touch('src0.txt')
# File.exist?('dest0.txt') # => false
# FileUtils.copy_entry('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
# If +src+ is a directory, recursively copies +src+ to +dest+:
#
# tree('src1')
# # => src1
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.copy_entry('src1', 'dest1')
# tree('dest1')
# # => dest1
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
#
# The recursive copying preserves file types for regular files,
# directories, and symbolic links;
# other file types (FIFO streams, device files, etc.) are not supported.
#
# Keyword arguments:
#
# - <tt>dereference_root: true</tt> - if +src+ is a symbolic link,
# follows the link.
# - <tt>preserve: true</tt> - preserves file times.
# - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
if dereference_root
src = File.realpath(src)
end
Entry_.new(src, nil, false).wrap_traverse(proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
File.unlink destent.path if remove_destination && (File.file?(destent.path) || File.symlink?(destent.path))
ent.copy destent.path
end, proc do |ent|
destent = Entry_.new(dest, ent.rel, false)
ent.copy_metadata destent.path if preserve
end)
end
module_function :copy_entry
# Copies file from +src+ to +dest+, which should not be directories.
#
# Arguments +src+ and +dest+
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# Examples:
#
# FileUtils.touch('src0.txt')
# FileUtils.copy_file('src0.txt', 'dest0.txt')
# File.file?('dest0.txt') # => true
#
# Keyword arguments:
#
# - <tt>dereference: false</tt> - if +src+ is a symbolic link,
# does not follow the link.
# - <tt>preserve: true</tt> - preserves file times.
# - <tt>remove_destination: true</tt> - removes +dest+ before copying files.
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_file(src, dest, preserve = false, dereference = true)
ent = Entry_.new(src, nil, dereference)
ent.copy_file dest
ent.copy_metadata dest if preserve
end
module_function :copy_file
# Copies \IO stream +src+ to \IO stream +dest+ via
# {IO.copy_stream}[rdoc-ref:IO.copy_stream].
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def copy_stream(src, dest)
IO.copy_stream(src, dest)
end
module_function :copy_stream
# Moves entries.
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# If +src+ and +dest+ are on different file systems,
# first copies, then removes +src+.
#
# May cause a local vulnerability if not called with keyword argument
# <tt>secure: true</tt>;
# see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
#
# If +src+ is the path to a single file or directory and +dest+ does not exist,
# moves +src+ to +dest+:
#
# tree('src0')
# # => src0
# # |-- src0.txt
# # `-- src1.txt
# File.exist?('dest0') # => false
# FileUtils.mv('src0', 'dest0')
# File.exist?('src0') # => false
# tree('dest0')
# # => dest0
# # |-- src0.txt
# # `-- src1.txt
#
# If +src+ is an array of paths to files and directories
# and +dest+ is the path to a directory,
# copies from each path in the array to +dest+:
#
# File.file?('src1.txt') # => true
# tree('src1')
# # => src1
# # |-- src.dat
# # `-- src.txt
# Dir.empty?('dest1') # => true
# FileUtils.mv(['src1.txt', 'src1'], 'dest1')
# tree('dest1')
# # => dest1
# # |-- src1
# # | |-- src.dat
# # | `-- src.txt
# # `-- src1.txt
#
# Keyword arguments:
#
# - <tt>force: true</tt> - if the move includes removing +src+
# (that is, if +src+ and +dest+ are on different file systems),
# ignores raised exceptions of StandardError and its descendants.
# - <tt>noop: true</tt> - does not move files.
# - <tt>secure: true</tt> - removes +src+ securely;
# see details at FileUtils.remove_entry_secure.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.mv('src0', 'dest0', noop: true, verbose: true)
# FileUtils.mv(['src1.txt', 'src1'], 'dest1', noop: true, verbose: true)
#
# Output:
#
# mv src0 dest0
# mv src1.txt src1 dest1
#
# FileUtils.move is an alias for FileUtils.mv.
#
def mv(src, dest, force: nil, noop: nil, verbose: nil, secure: nil)
fu_output_message "mv#{force ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if verbose
return if noop
fu_each_src_dest(src, dest) do |s, d|
destent = Entry_.new(d, nil, true)
begin
if destent.exist?
if destent.directory?
raise Errno::EEXIST, d
end
end
begin
File.rename s, d
rescue Errno::EXDEV,
Errno::EPERM # move from unencrypted to encrypted dir (ext4)
copy_entry s, d, true
if secure
remove_entry_secure s, force
else
remove_entry s, force
end
end
rescue SystemCallError
raise unless force
end
end
end
module_function :mv
alias move mv
module_function :move
# Removes entries at the paths in the given +list+
# (a single path or an array of paths)
# returns +list+, if it is an array, <tt>[list]</tt> otherwise.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# With no keyword arguments, removes files at the paths given in +list+:
#
# FileUtils.touch(['src0.txt', 'src0.dat'])
# FileUtils.rm(['src0.dat', 'src0.txt']) # => ["src0.dat", "src0.txt"]
#
# Keyword arguments:
#
# - <tt>force: true</tt> - ignores raised exceptions of StandardError
# and its descendants.
# - <tt>noop: true</tt> - does not remove files; returns +nil+.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.rm(['src0.dat', 'src0.txt'], noop: true, verbose: true)
#
# Output:
#
# rm src0.dat src0.txt
#
# FileUtils.remove is an alias for FileUtils.rm.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm(list, force: nil, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message "rm#{force ? ' -f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
remove_file path, force
end
end
module_function :rm
alias remove rm
module_function :remove
# Equivalent to:
#
# FileUtils.rm(list, force: true, **kwargs)
#
# Argument +list+ (a single path or an array of paths)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# See FileUtils.rm for keyword arguments.
#
# FileUtils.safe_unlink is an alias for FileUtils.rm_f.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm_f(list, noop: nil, verbose: nil)
rm list, force: true, noop: noop, verbose: verbose
end
module_function :rm_f
alias safe_unlink rm_f
module_function :safe_unlink
# Removes entries at the paths in the given +list+
# (a single path or an array of paths);
# returns +list+, if it is an array, <tt>[list]</tt> otherwise.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# May cause a local vulnerability if not called with keyword argument
# <tt>secure: true</tt>;
# see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
#
# For each file path, removes the file at that path:
#
# FileUtils.touch(['src0.txt', 'src0.dat'])
# FileUtils.rm_r(['src0.dat', 'src0.txt'])
# File.exist?('src0.txt') # => false
# File.exist?('src0.dat') # => false
#
# For each directory path, recursively removes files and directories:
#
# tree('src1')
# # => src1
# # |-- dir0
# # | |-- src0.txt
# # | `-- src1.txt
# # `-- dir1
# # |-- src2.txt
# # `-- src3.txt
# FileUtils.rm_r('src1')
# File.exist?('src1') # => false
#
# Keyword arguments:
#
# - <tt>force: true</tt> - ignores raised exceptions of StandardError
# and its descendants.
# - <tt>noop: true</tt> - does not remove entries; returns +nil+.
# - <tt>secure: true</tt> - removes +src+ securely;
# see details at FileUtils.remove_entry_secure.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.rm_r(['src0.dat', 'src0.txt'], noop: true, verbose: true)
# FileUtils.rm_r('src1', noop: true, verbose: true)
#
# Output:
#
# rm -r src0.dat src0.txt
# rm -r src1
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm_r(list, force: nil, noop: nil, verbose: nil, secure: nil)
list = fu_list(list)
fu_output_message "rm -r#{force ? 'f' : ''} #{list.join ' '}" if verbose
return if noop
list.each do |path|
if secure
remove_entry_secure path, force
else
remove_entry path, force
end
end
end
module_function :rm_r
# Equivalent to:
#
# FileUtils.rm_r(list, force: true, **kwargs)
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# May cause a local vulnerability if not called with keyword argument
# <tt>secure: true</tt>;
# see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
#
# See FileUtils.rm_r for keyword arguments.
#
# FileUtils.rmtree is an alias for FileUtils.rm_rf.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def rm_rf(list, noop: nil, verbose: nil, secure: nil)
rm_r list, force: true, noop: noop, verbose: verbose, secure: secure
end
module_function :rm_rf
alias rmtree rm_rf
module_function :rmtree
# Securely removes the entry given by +path+,
# which should be the entry for a regular file, a symbolic link,
# or a directory.
#
# Argument +path+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Avoids a local vulnerability that can exist in certain circumstances;
# see {Avoiding the TOCTTOU Vulnerability}[rdoc-ref:FileUtils@Avoiding+the+TOCTTOU+Vulnerability].
#
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def remove_entry_secure(path, force = false)
unless fu_have_symlink?
remove_entry path, force
return
end
fullpath = File.expand_path(path)
st = File.lstat(fullpath)
unless st.directory?
File.unlink fullpath
return
end
# is a directory.
parent_st = File.stat(File.dirname(fullpath))
unless parent_st.world_writable?
remove_entry path, force
return
end
unless parent_st.sticky?
raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
end
# freeze tree root
euid = Process.euid
dot_file = fullpath + "/."
begin
File.open(dot_file) {|f|
unless fu_stat_identical_entry?(st, f.stat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
f.chown euid, -1
f.chmod 0700
}
rescue Errno::EISDIR # JRuby in non-native mode can't open files as dirs
File.lstat(dot_file).tap {|fstat|
unless fu_stat_identical_entry?(st, fstat)
# symlink (TOC-to-TOU attack?)
File.unlink fullpath
return
end
File.chown euid, -1, dot_file
File.chmod 0700, dot_file
}
end
unless fu_stat_identical_entry?(st, File.lstat(fullpath))
# TOC-to-TOU attack?
File.unlink fullpath
return
end
# ---- tree root is frozen ----
root = Entry_.new(path)
root.preorder_traverse do |ent|
if ent.directory?
ent.chown euid, -1
ent.chmod 0700
end
end
root.postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end
module_function :remove_entry_secure
def fu_have_symlink? #:nodoc:
File.symlink nil, nil
rescue NotImplementedError
return false
rescue TypeError
return true
end
private_module_function :fu_have_symlink?
def fu_stat_identical_entry?(a, b) #:nodoc:
a.dev == b.dev and a.ino == b.ino
end
private_module_function :fu_stat_identical_entry?
# Removes the entry given by +path+,
# which should be the entry for a regular file, a symbolic link,
# or a directory.
#
# Argument +path+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
# Related: FileUtils.remove_entry_secure.
#
def remove_entry(path, force = false)
Entry_.new(path).postorder_traverse do |ent|
begin
ent.remove
rescue
raise unless force
end
end
rescue
raise unless force
end
module_function :remove_entry
# Removes the file entry given by +path+,
# which should be the entry for a regular file or a symbolic link.
#
# Argument +path+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def remove_file(path, force = false)
Entry_.new(path).remove_file
rescue
raise unless force
end
module_function :remove_file
# Recursively removes the directory entry given by +path+,
# which should be the entry for a regular file, a symbolic link,
# or a directory.
#
# Argument +path+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Optional argument +force+ specifies whether to ignore
# raised exceptions of StandardError and its descendants.
#
# Related: {methods for deleting}[rdoc-ref:FileUtils@Deleting].
#
def remove_dir(path, force = false)
remove_entry path, force # FIXME?? check if it is a directory
end
module_function :remove_dir
# Returns +true+ if the contents of files +a+ and +b+ are identical,
# +false+ otherwise.
#
# Arguments +a+ and +b+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# FileUtils.identical? and FileUtils.cmp are aliases for FileUtils.compare_file.
#
# Related: FileUtils.compare_stream.
#
def compare_file(a, b)
return false unless File.size(a) == File.size(b)
File.open(a, 'rb') {|fa|
File.open(b, 'rb') {|fb|
return compare_stream(fa, fb)
}
}
end
module_function :compare_file
alias identical? compare_file
alias cmp compare_file
module_function :identical?
module_function :cmp
# Returns +true+ if the contents of streams +a+ and +b+ are identical,
# +false+ otherwise.
#
# Arguments +a+ and +b+
# should be {interpretable as a path}[rdoc-ref:FileUtils@Path+Arguments].
#
# Related: FileUtils.compare_file.
#
def compare_stream(a, b)
bsize = fu_stream_blksize(a, b)
sa = String.new(capacity: bsize)
sb = String.new(capacity: bsize)
begin
a.read(bsize, sa)
b.read(bsize, sb)
return true if sa.empty? && sb.empty?
end while sa == sb
false
end
module_function :compare_stream
# Copies a file entry.
# See {install(1)}[https://man7.org/linux/man-pages/man1/install.1.html].
#
# Arguments +src+ (a single path or an array of paths)
# and +dest+ (a single path)
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments];
#
# If the entry at +dest+ does not exist, copies from +src+ to +dest+:
#
# File.read('src0.txt') # => "aaa\n"
# File.exist?('dest0.txt') # => false
# FileUtils.install('src0.txt', 'dest0.txt')
# File.read('dest0.txt') # => "aaa\n"
#
# If +dest+ is a file entry, copies from +src+ to +dest+, overwriting:
#
# File.read('src1.txt') # => "aaa\n"
# File.read('dest1.txt') # => "bbb\n"
# FileUtils.install('src1.txt', 'dest1.txt')
# File.read('dest1.txt') # => "aaa\n"
#
# If +dest+ is a directory entry, copies from +src+ to <tt>dest/src</tt>,
# overwriting if necessary:
#
# File.read('src2.txt') # => "aaa\n"
# File.read('dest2/src2.txt') # => "bbb\n"
# FileUtils.install('src2.txt', 'dest2')
# File.read('dest2/src2.txt') # => "aaa\n"
#
# If +src+ is an array of paths and +dest+ points to a directory,
# copies each path +path+ in +src+ to <tt>dest/path</tt>:
#
# File.file?('src3.txt') # => true
# File.file?('src3.dat') # => true
# FileUtils.mkdir('dest3')
# FileUtils.install(['src3.txt', 'src3.dat'], 'dest3')
# File.file?('dest3/src3.txt') # => true
# File.file?('dest3/src3.dat') # => true
#
# Keyword arguments:
#
# - <tt>group: <i>group</i></tt> - changes the group if not +nil+,
# using {File.chown}[rdoc-ref:File.chown].
# - <tt>mode: <i>permissions</i></tt> - changes the permissions.
# using {File.chmod}[rdoc-ref:File.chmod].
# - <tt>noop: true</tt> - does not copy entries; returns +nil+.
# - <tt>owner: <i>owner</i></tt> - changes the owner if not +nil+,
# using {File.chown}[rdoc-ref:File.chown].
# - <tt>preserve: true</tt> - preserve timestamps
# using {File.utime}[rdoc-ref:File.utime].
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.install('src0.txt', 'dest0.txt', noop: true, verbose: true)
# FileUtils.install('src1.txt', 'dest1.txt', noop: true, verbose: true)
# FileUtils.install('src2.txt', 'dest2', noop: true, verbose: true)
#
# Output:
#
# install -c src0.txt dest0.txt
# install -c src1.txt dest1.txt
# install -c src2.txt dest2
#
# Related: {methods for copying}[rdoc-ref:FileUtils@Copying].
#
def install(src, dest, mode: nil, owner: nil, group: nil, preserve: nil,
noop: nil, verbose: nil)
if verbose
msg = +"install -c"
msg << ' -p' if preserve
msg << ' -m ' << mode_to_s(mode) if mode
msg << " -o #{owner}" if owner
msg << " -g #{group}" if group
msg << ' ' << [src,dest].flatten.join(' ')
fu_output_message msg
end
return if noop
uid = fu_get_uid(owner)
gid = fu_get_gid(group)
fu_each_src_dest(src, dest) do |s, d|
st = File.stat(s)
unless File.exist?(d) and compare_file(s, d)
remove_file d, true
copy_file s, d
File.utime st.atime, st.mtime, d if preserve
File.chmod fu_mode(mode, st), d if mode
File.chown uid, gid, d if uid or gid
end
end
end
module_function :install
def user_mask(target) #:nodoc:
target.each_char.inject(0) do |mask, chr|
case chr
when "u"
mask | 04700
when "g"
mask | 02070
when "o"
mask | 01007
when "a"
mask | 07777
else
raise ArgumentError, "invalid `who' symbol in file mode: #{chr}"
end
end
end
private_module_function :user_mask
def apply_mask(mode, user_mask, op, mode_mask) #:nodoc:
case op
when '='
(mode & ~user_mask) | (user_mask & mode_mask)
when '+'
mode | (user_mask & mode_mask)
when '-'
mode & ~(user_mask & mode_mask)
end
end
private_module_function :apply_mask
def symbolic_modes_to_i(mode_sym, path) #:nodoc:
path = File.stat(path) unless File::Stat === path
mode = path.mode
mode_sym.split(/,/).inject(mode & 07777) do |current_mode, clause|
target, *actions = clause.split(/([=+-])/)
raise ArgumentError, "invalid file mode: #{mode_sym}" if actions.empty?
target = 'a' if target.empty?
user_mask = user_mask(target)
actions.each_slice(2) do |op, perm|
need_apply = op == '='
mode_mask = (perm || '').each_char.inject(0) do |mask, chr|
case chr
when "r"
mask | 0444
when "w"
mask | 0222
when "x"
mask | 0111
when "X"
if path.directory?
mask | 0111
else
mask
end
when "s"
mask | 06000
when "t"
mask | 01000
when "u", "g", "o"
if mask.nonzero?
current_mode = apply_mask(current_mode, user_mask, op, mask)
end
need_apply = false
copy_mask = user_mask(chr)
(current_mode & copy_mask) / (copy_mask & 0111) * (user_mask & 0111)
else
raise ArgumentError, "invalid `perm' symbol in file mode: #{chr}"
end
end
if mode_mask.nonzero? || need_apply
current_mode = apply_mask(current_mode, user_mask, op, mode_mask)
end
end
current_mode
end
end
private_module_function :symbolic_modes_to_i
def fu_mode(mode, path) #:nodoc:
mode.is_a?(String) ? symbolic_modes_to_i(mode, path) : mode
end
private_module_function :fu_mode
def mode_to_s(mode) #:nodoc:
mode.is_a?(String) ? mode : "%o" % mode
end
private_module_function :mode_to_s
# Changes permissions on the entries at the paths given in +list+
# (a single path or an array of paths)
# to the permissions given by +mode+;
# returns +list+ if it is an array, <tt>[list]</tt> otherwise:
#
# - Modifies each entry that is a regular file using
# {File.chmod}[rdoc-ref:File.chmod].
# - Modifies each entry that is a symbolic link using
# {File.lchmod}[rdoc-ref:File.lchmod].
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# Argument +mode+ may be either an integer or a string:
#
# - \Integer +mode+: represents the permission bits to be set:
#
# FileUtils.chmod(0755, 'src0.txt')
# FileUtils.chmod(0644, ['src0.txt', 'src0.dat'])
#
# - \String +mode+: represents the permissions to be set:
#
# The string is of the form <tt>[targets][[operator][perms[,perms]]</tt>, where:
#
# - +targets+ may be any combination of these letters:
#
# - <tt>'u'</tt>: permissions apply to the file's owner.
# - <tt>'g'</tt>: permissions apply to users in the file's group.
# - <tt>'o'</tt>: permissions apply to other users not in the file's group.
# - <tt>'a'</tt> (the default): permissions apply to all users.
#
# - +operator+ may be one of these letters:
#
# - <tt>'+'</tt>: adds permissions.
# - <tt>'-'</tt>: removes permissions.
# - <tt>'='</tt>: sets (replaces) permissions.
#
# - +perms+ (may be repeated, with separating commas)
# may be any combination of these letters:
#
# - <tt>'r'</tt>: Read.
# - <tt>'w'</tt>: Write.
# - <tt>'x'</tt>: Execute (search, for a directory).
# - <tt>'X'</tt>: Search (for a directories only;
# must be used with <tt>'+'</tt>)
# - <tt>'s'</tt>: Uid or gid.
# - <tt>'t'</tt>: Sticky bit.
#
# Examples:
#
# FileUtils.chmod('u=wrx,go=rx', 'src1.txt')
# FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby')
#
# Keyword arguments:
#
# - <tt>noop: true</tt> - does not change permissions; returns +nil+.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.chmod(0755, 'src0.txt', noop: true, verbose: true)
# FileUtils.chmod(0644, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
# FileUtils.chmod('u=wrx,go=rx', 'src1.txt', noop: true, verbose: true)
# FileUtils.chmod('u=wrx,go=rx', '/usr/bin/ruby', noop: true, verbose: true)
#
# Output:
#
# chmod 755 src0.txt
# chmod 644 src0.txt src0.dat
# chmod u=wrx,go=rx src1.txt
# chmod u=wrx,go=rx /usr/bin/ruby
#
# Related: FileUtils.chmod_R.
#
def chmod(mode, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chmod %s %s', mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |path|
Entry_.new(path).chmod(fu_mode(mode, path))
end
end
module_function :chmod
# Like FileUtils.chmod, but changes permissions recursively.
#
def chmod_R(mode, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chmod -R%s %s %s',
(force ? 'f' : ''),
mode_to_s(mode), list.join(' ')) if verbose
return if noop
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chmod(fu_mode(mode, ent.path))
rescue
raise unless force
end
end
end
end
module_function :chmod_R
# Changes the owner and group on the entries at the paths given in +list+
# (a single path or an array of paths)
# to the given +user+ and +group+;
# returns +list+ if it is an array, <tt>[list]</tt> otherwise:
#
# - Modifies each entry that is a regular file using
# {File.chown}[rdoc-ref:File.chown].
# - Modifies each entry that is a symbolic link using
# {File.lchown}[rdoc-ref:File.lchown].
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# User and group:
#
# - Argument +user+ may be a user name or a user id;
# if +nil+ or +-1+, the user is not changed.
# - Argument +group+ may be a group name or a group id;
# if +nil+ or +-1+, the group is not changed.
# - The user must be a member of the group.
#
# Examples:
#
# # One path.
# # User and group as string names.
# File.stat('src0.txt').uid # => 1004
# File.stat('src0.txt').gid # => 1004
# FileUtils.chown('user2', 'group1', 'src0.txt')
# File.stat('src0.txt').uid # => 1006
# File.stat('src0.txt').gid # => 1005
#
# # User and group as uid and gid.
# FileUtils.chown(1004, 1004, 'src0.txt')
# File.stat('src0.txt').uid # => 1004
# File.stat('src0.txt').gid # => 1004
#
# # Array of paths.
# FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'])
#
# # Directory (not recursive).
# FileUtils.chown('user2', 'group1', '.')
#
# Keyword arguments:
#
# - <tt>noop: true</tt> - does not change permissions; returns +nil+.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.chown('user2', 'group1', 'src0.txt', noop: true, verbose: true)
# FileUtils.chown(1004, 1004, 'src0.txt', noop: true, verbose: true)
# FileUtils.chown(1006, 1005, ['src0.txt', 'src0.dat'], noop: true, verbose: true)
# FileUtils.chown('user2', 'group1', path, noop: true, verbose: true)
# FileUtils.chown('user2', 'group1', '.', noop: true, verbose: true)
#
# Output:
#
# chown user2:group1 src0.txt
# chown 1004:1004 src0.txt
# chown 1006:1005 src0.txt src0.dat
# chown user2:group1 src0.txt
# chown user2:group1 .
#
# Related: FileUtils.chown_R.
#
def chown(user, group, list, noop: nil, verbose: nil)
list = fu_list(list)
fu_output_message sprintf('chown %s %s',
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |path|
Entry_.new(path).chown uid, gid
end
end
module_function :chown
# Like FileUtils.chown, but changes owner and group recursively.
#
def chown_R(user, group, list, noop: nil, verbose: nil, force: nil)
list = fu_list(list)
fu_output_message sprintf('chown -R%s %s %s',
(force ? 'f' : ''),
(group ? "#{user}:#{group}" : user || ':'),
list.join(' ')) if verbose
return if noop
uid = fu_get_uid(user)
gid = fu_get_gid(group)
list.each do |root|
Entry_.new(root).traverse do |ent|
begin
ent.chown uid, gid
rescue
raise unless force
end
end
end
end
module_function :chown_R
def fu_get_uid(user) #:nodoc:
return nil unless user
case user
when Integer
user
when /\A\d+\z/
user.to_i
else
require 'etc'
Etc.getpwnam(user) ? Etc.getpwnam(user).uid : nil
end
end
private_module_function :fu_get_uid
def fu_get_gid(group) #:nodoc:
return nil unless group
case group
when Integer
group
when /\A\d+\z/
group.to_i
else
require 'etc'
Etc.getgrnam(group) ? Etc.getgrnam(group).gid : nil
end
end
private_module_function :fu_get_gid
# Updates modification times (mtime) and access times (atime)
# of the entries given by the paths in +list+
# (a single path or an array of paths);
# returns +list+ if it is an array, <tt>[list]</tt> otherwise.
#
# By default, creates an empty file for any path to a non-existent entry;
# use keyword argument +nocreate+ to raise an exception instead.
#
# Argument +list+ or its elements
# should be {interpretable as paths}[rdoc-ref:FileUtils@Path+Arguments].
#
# Examples:
#
# # Single path.
# f = File.new('src0.txt') # Existing file.
# f.atime # => 2022-06-10 11:11:21.200277 -0700
# f.mtime # => 2022-06-10 11:11:21.200277 -0700
# FileUtils.touch('src0.txt')
# f = File.new('src0.txt')
# f.atime # => 2022-06-11 08:28:09.8185343 -0700
# f.mtime # => 2022-06-11 08:28:09.8185343 -0700
#
# # Array of paths.
# FileUtils.touch(['src0.txt', 'src0.dat'])
#
# Keyword arguments:
#
# - <tt>mtime: <i>time</i></tt> - sets the entry's mtime to the given time,
# instead of the current time.
# - <tt>nocreate: true</tt> - raises an exception if the entry does not exist.
# - <tt>noop: true</tt> - does not touch entries; returns +nil+.
# - <tt>verbose: true</tt> - prints an equivalent command:
#
# FileUtils.touch('src0.txt', noop: true, verbose: true)
# FileUtils.touch(['src0.txt', 'src0.dat'], noop: true, verbose: true)
# FileUtils.touch(path, noop: true, verbose: true)
#
# Output:
#
# touch src0.txt
# touch src0.txt src0.dat
# touch src0.txt
#
# Related: FileUtils.uptodate?.
#
def touch(list, noop: nil, verbose: nil, mtime: nil, nocreate: nil)
list = fu_list(list)
t = mtime
if verbose
fu_output_message "touch #{nocreate ? '-c ' : ''}#{t ? t.strftime('-t %Y%m%d%H%M.%S ') : ''}#{list.join ' '}"
end
return if noop
list.each do |path|
created = nocreate
begin
File.utime(t, t, path)
rescue Errno::ENOENT
raise if created
File.open(path, 'a') {
;
}
created = true
retry if t
end
end
end
module_function :touch
private
module StreamUtils_
private
case (defined?(::RbConfig) ? ::RbConfig::CONFIG['host_os'] : ::RUBY_PLATFORM)
when /mswin|mingw/
def fu_windows?; true end
else
def fu_windows?; false end
end
def fu_copy_stream0(src, dest, blksize = nil) #:nodoc:
IO.copy_stream(src, dest)
end
def fu_stream_blksize(*streams)
streams.each do |s|
next unless s.respond_to?(:stat)
size = fu_blksize(s.stat)
return size if size
end
fu_default_blksize()
end
def fu_blksize(st)
s = st.blksize
return nil unless s
return nil if s == 0
s
end
def fu_default_blksize
1024
end
end
include StreamUtils_
extend StreamUtils_
class Entry_ #:nodoc: internal use only
include StreamUtils_
def initialize(a, b = nil, deref = false)
@prefix = @rel = @path = nil
if b
@prefix = a
@rel = b
else
@path = a
end
@deref = deref
@stat = nil
@lstat = nil
end
def inspect
"\#<#{self.class} #{path()}>"
end
def path
if @path
File.path(@path)
else
join(@prefix, @rel)
end
end
def prefix
@prefix || @path
end
def rel
@rel
end
def dereference?
@deref
end
def exist?
begin
lstat
true
rescue Errno::ENOENT
false
end
end
def file?
s = lstat!
s and s.file?
end
def directory?
s = lstat!
s and s.directory?
end
def symlink?
s = lstat!
s and s.symlink?
end
def chardev?
s = lstat!
s and s.chardev?
end
def blockdev?
s = lstat!
s and s.blockdev?
end
def socket?
s = lstat!
s and s.socket?
end
def pipe?
s = lstat!
s and s.pipe?
end
S_IF_DOOR = 0xD000
def door?
s = lstat!
s and (s.mode & 0xF000 == S_IF_DOOR)
end
def entries
opts = {}
opts[:encoding] = fu_windows? ? ::Encoding::UTF_8 : path.encoding
files = Dir.children(path, **opts)
untaint = RUBY_VERSION < '2.7'
files.map {|n| Entry_.new(prefix(), join(rel(), untaint ? n.untaint : n)) }
end
def stat
return @stat if @stat
if lstat() and lstat().symlink?
@stat = File.stat(path())
else
@stat = lstat()
end
@stat
end
def stat!
return @stat if @stat
if lstat! and lstat!.symlink?
@stat = File.stat(path())
else
@stat = lstat!
end
@stat
rescue SystemCallError
nil
end
def lstat
if dereference?
@lstat ||= File.stat(path())
else
@lstat ||= File.lstat(path())
end
end
def lstat!
lstat()
rescue SystemCallError
nil
end
def chmod(mode)
if symlink?
File.lchmod mode, path() if have_lchmod?
else
File.chmod mode, path()
end
rescue Errno::EOPNOTSUPP
end
def chown(uid, gid)
if symlink?
File.lchown uid, gid, path() if have_lchown?
else
File.chown uid, gid, path()
end
end
def link(dest)
case
when directory?
if !File.exist?(dest) and descendant_directory?(dest, path)
raise ArgumentError, "cannot link directory %s to itself %s" % [path, dest]
end
begin
Dir.mkdir dest
rescue
raise unless File.directory?(dest)
end
else
File.link path(), dest
end
end
def copy(dest)
lstat
case
when file?
copy_file dest
when directory?
if !File.exist?(dest) and descendant_directory?(dest, path)
raise ArgumentError, "cannot copy directory %s to itself %s" % [path, dest]
end
begin
Dir.mkdir dest
rescue
raise unless File.directory?(dest)
end
when symlink?
File.symlink File.readlink(path()), dest
when chardev?, blockdev?
raise "cannot handle device file"
when socket?
begin
require 'socket'
rescue LoadError
raise "cannot handle socket"
else
raise "cannot handle socket" unless defined?(UNIXServer)
end
UNIXServer.new(dest).close
File.chmod lstat().mode, dest
when pipe?
raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
File.mkfifo dest, lstat().mode
when door?
raise "cannot handle door: #{path()}"
else
raise "unknown file type: #{path()}"
end
end
def copy_file(dest)
File.open(path()) do |s|
File.open(dest, 'wb', s.stat.mode) do |f|
IO.copy_stream(s, f)
end
end
end
def copy_metadata(path)
st = lstat()
if !st.symlink?
File.utime st.atime, st.mtime, path
end
mode = st.mode
begin
if st.symlink?
begin
File.lchown st.uid, st.gid, path
rescue NotImplementedError
end
else
File.chown st.uid, st.gid, path
end
rescue Errno::EPERM, Errno::EACCES
# clear setuid/setgid
mode &= 01777
end
if st.symlink?
begin
File.lchmod mode, path
rescue NotImplementedError, Errno::EOPNOTSUPP
end
else
File.chmod mode, path
end
end
def remove
if directory?
remove_dir1
else
remove_file
end
end
def remove_dir1
platform_support {
Dir.rmdir path().chomp(?/)
}
end
def remove_file
platform_support {
File.unlink path
}
end
def platform_support
return yield unless fu_windows?
first_time_p = true
begin
yield
rescue Errno::ENOENT
raise
rescue => err
if first_time_p
first_time_p = false
begin
File.chmod 0700, path() # Windows does not have symlink
retry
rescue SystemCallError
end
end
raise err
end
end
def preorder_traverse
stack = [self]
while ent = stack.pop
yield ent
stack.concat ent.entries.reverse if ent.directory?
end
end
alias traverse preorder_traverse
def postorder_traverse
if directory?
begin
children = entries()
rescue Errno::EACCES
# Failed to get the list of children.
# Assuming there is no children, try to process the parent directory.
yield self
return
end
children.each do |ent|
ent.postorder_traverse do |e|
yield e
end
end
end
yield self
end
def wrap_traverse(pre, post)
pre.call self
if directory?
entries.each do |ent|
ent.wrap_traverse pre, post
end
end
post.call self
end
private
@@fileutils_rb_have_lchmod = nil
def have_lchmod?
# This is not MT-safe, but it does not matter.
if @@fileutils_rb_have_lchmod == nil
@@fileutils_rb_have_lchmod = check_have_lchmod?
end
@@fileutils_rb_have_lchmod
end
def check_have_lchmod?
return false unless File.respond_to?(:lchmod)
File.lchmod 0
return true
rescue NotImplementedError
return false
end
@@fileutils_rb_have_lchown = nil
def have_lchown?
# This is not MT-safe, but it does not matter.
if @@fileutils_rb_have_lchown == nil
@@fileutils_rb_have_lchown = check_have_lchown?
end
@@fileutils_rb_have_lchown
end
def check_have_lchown?
return false unless File.respond_to?(:lchown)
File.lchown nil, nil
return true
rescue NotImplementedError
return false
end
def join(dir, base)
return File.path(dir) if not base or base == '.'
return File.path(base) if not dir or dir == '.'
begin
File.join(dir, base)
rescue EncodingError
if fu_windows?
File.join(dir.encode(::Encoding::UTF_8), base.encode(::Encoding::UTF_8))
else
raise
end
end
end
if File::ALT_SEPARATOR
DIRECTORY_TERM = "(?=[/#{Regexp.quote(File::ALT_SEPARATOR)}]|\\z)"
else
DIRECTORY_TERM = "(?=/|\\z)"
end
def descendant_directory?(descendant, ascendant)
if File::FNM_SYSCASE.nonzero?
File.expand_path(File.dirname(descendant)).casecmp(File.expand_path(ascendant)) == 0
else
File.expand_path(File.dirname(descendant)) == File.expand_path(ascendant)
end
end
end # class Entry_
def fu_list(arg) #:nodoc:
[arg].flatten.map {|path| File.path(path) }
end
private_module_function :fu_list
def fu_each_src_dest(src, dest) #:nodoc:
fu_each_src_dest0(src, dest) do |s, d|
raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
yield s, d
end
end
private_module_function :fu_each_src_dest
def fu_each_src_dest0(src, dest, target_directory = true) #:nodoc:
if tmp = Array.try_convert(src)
tmp.each do |s|
s = File.path(s)
yield s, (target_directory ? File.join(dest, File.basename(s)) : dest)
end
else
src = File.path(src)
if target_directory and File.directory?(dest)
yield src, File.join(dest, File.basename(src))
else
yield src, File.path(dest)
end
end
end
private_module_function :fu_each_src_dest0
def fu_same?(a, b) #:nodoc:
File.identical?(a, b)
end
private_module_function :fu_same?
def fu_output_message(msg) #:nodoc:
output = @fileutils_output if defined?(@fileutils_output)
output ||= $stdout
if defined?(@fileutils_label)
msg = @fileutils_label + msg
end
output.puts msg
end
private_module_function :fu_output_message
def fu_split_path(path)
path = File.path(path)
list = []
until (parent, base = File.split(path); parent == path or parent == ".")
list << base
path = parent
end
list << path
list.reverse!
end
private_module_function :fu_split_path
def fu_relative_components_from(target, base) #:nodoc:
i = 0
while target[i]&.== base[i]
i += 1
end
Array.new(base.size-i, '..').concat(target[i..-1])
end
private_module_function :fu_relative_components_from
def fu_clean_components(*comp)
comp.shift while comp.first == "."
return comp if comp.empty?
clean = [comp.shift]
path = File.join(*clean, "") # ending with File::SEPARATOR
while c = comp.shift
if c == ".." and clean.last != ".." and !(fu_have_symlink? && File.symlink?(path))
clean.pop
path.chomp!(%r((?<=\A|/)[^/]+/\z), "")
else
clean << c
path << c << "/"
end
end
clean
end
private_module_function :fu_clean_components
if fu_windows?
def fu_starting_path?(path)
path&.start_with?(%r(\w:|/))
end
else
def fu_starting_path?(path)
path&.start_with?("/")
end
end
private_module_function :fu_starting_path?
# This hash table holds command options.
OPT_TABLE = {} #:nodoc: internal use only
(private_instance_methods & methods(false)).inject(OPT_TABLE) {|tbl, name|
(tbl[name.to_s] = instance_method(name).parameters).map! {|t, n| n if t == :key}.compact!
tbl
}
public
# Returns an array of the string names of \FileUtils methods
# that accept one or more keyword arguments:
#
# FileUtils.commands.sort.take(3) # => ["cd", "chdir", "chmod"]
#
def self.commands
OPT_TABLE.keys
end
# Returns an array of the string keyword names:
#
# FileUtils.options.take(3) # => ["noop", "verbose", "force"]
#
def self.options
OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
end
# Returns +true+ if method +mid+ accepts the given option +opt+, +false+ otherwise;
# the arguments may be strings or symbols:
#
# FileUtils.have_option?(:chmod, :noop) # => true
# FileUtils.have_option?('chmod', 'secure') # => false
#
def self.have_option?(mid, opt)
li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
li.include?(opt)
end
# Returns an array of the string keyword name for method +mid+;
# the argument may be a string or a symbol:
#
# FileUtils.options_of(:rm) # => ["force", "noop", "verbose"]
# FileUtils.options_of('mv') # => ["force", "noop", "verbose", "secure"]
#
def self.options_of(mid)
OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
end
# Returns an array of the string method names of the methods
# that accept the given keyword option +opt+;
# the argument must be a symbol:
#
# FileUtils.collect_method(:preserve) # => ["cp", "copy", "cp_r", "install"]
#
def self.collect_method(opt)
OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
end
private
LOW_METHODS = singleton_methods(false) - collect_method(:noop).map(&:intern) # :nodoc:
module LowMethods # :nodoc: internal use only
private
def _do_nothing(*)end
::FileUtils::LOW_METHODS.map {|name| alias_method name, :_do_nothing}
end
METHODS = singleton_methods() - [:private_module_function, # :nodoc:
:commands, :options, :have_option?, :options_of, :collect_method]
#
# This module has all methods of FileUtils module, but it outputs messages
# before acting. This equates to passing the <tt>:verbose</tt> flag to
# methods in FileUtils.
#
module Verbose
include FileUtils
names = ::FileUtils.collect_method(:verbose)
names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{name}(*args, **options)
super(*args, **options, verbose: true)
end
EOS
end
private(*names)
extend self
class << self
public(*::FileUtils::METHODS)
end
end
#
# This module has all methods of FileUtils module, but never changes
# files/directories. This equates to passing the <tt>:noop</tt> flag
# to methods in FileUtils.
#
module NoWrite
include FileUtils
include LowMethods
names = ::FileUtils.collect_method(:noop)
names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{name}(*args, **options)
super(*args, **options, noop: true)
end
EOS
end
private(*names)
extend self
class << self
public(*::FileUtils::METHODS)
end
end
#
# This module has all methods of FileUtils module, but never changes
# files/directories, with printing message before acting.
# This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
# to methods in FileUtils.
#
module DryRun
include FileUtils
include LowMethods
names = ::FileUtils.collect_method(:noop)
names.each do |name|
module_eval(<<-EOS, __FILE__, __LINE__ + 1)
def #{name}(*args, **options)
super(*args, **options, noop: true, verbose: true)
end
EOS
end
private(*names)
extend self
class << self
public(*::FileUtils::METHODS)
end
end
end
share/ruby/benchmark.rb 0000644 00000044632 15173547333 0011132 0 ustar 00 # frozen_string_literal: true
#--
# benchmark.rb - a performance benchmarking library
#
# $Id$
#
# Created by Gotoken (gotoken@notwork.org).
#
# Documentation by Gotoken (original RD), Lyle Johnson (RDoc conversion), and
# Gavin Sinclair (editing).
#++
#
# == Overview
#
# The Benchmark module provides methods for benchmarking Ruby code, giving
# detailed reports on the time taken for each task.
#
# The Benchmark module provides methods to measure and report the time
# used to execute Ruby code.
#
# * Measure the time to construct the string given by the expression
# <code>"a"*1_000_000_000</code>:
#
# require 'benchmark'
#
# puts Benchmark.measure { "a"*1_000_000_000 }
#
# On my machine (OSX 10.8.3 on i5 1.7 GHz) this generates:
#
# 0.350000 0.400000 0.750000 ( 0.835234)
#
# This report shows the user CPU time, system CPU time, the sum of
# the user and system CPU times, and the elapsed real time. The unit
# of time is seconds.
#
# * Do some experiments sequentially using the #bm method:
#
# require 'benchmark'
#
# n = 5000000
# Benchmark.bm do |x|
# x.report { for i in 1..n; a = "1"; end }
# x.report { n.times do ; a = "1"; end }
# x.report { 1.upto(n) do ; a = "1"; end }
# end
#
# The result:
#
# user system total real
# 1.010000 0.000000 1.010000 ( 1.014479)
# 1.000000 0.000000 1.000000 ( 0.998261)
# 0.980000 0.000000 0.980000 ( 0.981335)
#
# * Continuing the previous example, put a label in each report:
#
# require 'benchmark'
#
# n = 5000000
# Benchmark.bm(7) do |x|
# x.report("for:") { for i in 1..n; a = "1"; end }
# x.report("times:") { n.times do ; a = "1"; end }
# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# end
#
# The result:
#
# user system total real
# for: 1.010000 0.000000 1.010000 ( 1.015688)
# times: 1.000000 0.000000 1.000000 ( 1.003611)
# upto: 1.030000 0.000000 1.030000 ( 1.028098)
#
# * The times for some benchmarks depend on the order in which items
# are run. These differences are due to the cost of memory
# allocation and garbage collection. To avoid these discrepancies,
# the #bmbm method is provided. For example, to compare ways to
# sort an array of floats:
#
# require 'benchmark'
#
# array = (1..1000000).map { rand }
#
# Benchmark.bmbm do |x|
# x.report("sort!") { array.dup.sort! }
# x.report("sort") { array.dup.sort }
# end
#
# The result:
#
# Rehearsal -----------------------------------------
# sort! 1.490000 0.010000 1.500000 ( 1.490520)
# sort 1.460000 0.000000 1.460000 ( 1.463025)
# -------------------------------- total: 2.960000sec
#
# user system total real
# sort! 1.460000 0.000000 1.460000 ( 1.460465)
# sort 1.450000 0.010000 1.460000 ( 1.448327)
#
# * Report statistics of sequential experiments with unique labels,
# using the #benchmark method:
#
# require 'benchmark'
# include Benchmark # we need the CAPTION and FORMAT constants
#
# n = 5000000
# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
# tf = x.report("for:") { for i in 1..n; a = "1"; end }
# tt = x.report("times:") { n.times do ; a = "1"; end }
# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# [tf+tt+tu, (tf+tt+tu)/3]
# end
#
# The result:
#
# user system total real
# for: 0.950000 0.000000 0.950000 ( 0.952039)
# times: 0.980000 0.000000 0.980000 ( 0.984938)
# upto: 0.950000 0.000000 0.950000 ( 0.946787)
# >total: 2.880000 0.000000 2.880000 ( 2.883764)
# >avg: 0.960000 0.000000 0.960000 ( 0.961255)
module Benchmark
BENCHMARK_VERSION = "2002-04-25" # :nodoc:
# Invokes the block with a Benchmark::Report object, which
# may be used to collect and report on the results of individual
# benchmark tests. Reserves +label_width+ leading spaces for
# labels on each line. Prints +caption+ at the top of the
# report, and uses +format+ to format each line.
# (Note: +caption+ must contain a terminating newline character,
# see the default Benchmark::Tms::CAPTION for an example.)
#
# Returns an array of Benchmark::Tms objects.
#
# If the block returns an array of
# Benchmark::Tms objects, these will be used to format
# additional lines of output. If +labels+ parameter are
# given, these are used to label these extra lines.
#
# _Note_: Other methods provide a simpler interface to this one, and are
# suitable for nearly all benchmarking requirements. See the examples in
# Benchmark, and the #bm and #bmbm methods.
#
# Example:
#
# require 'benchmark'
# include Benchmark # we need the CAPTION and FORMAT constants
#
# n = 5000000
# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
# tf = x.report("for:") { for i in 1..n; a = "1"; end }
# tt = x.report("times:") { n.times do ; a = "1"; end }
# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# [tf+tt+tu, (tf+tt+tu)/3]
# end
#
# Generates:
#
# user system total real
# for: 0.970000 0.000000 0.970000 ( 0.970493)
# times: 0.990000 0.000000 0.990000 ( 0.989542)
# upto: 0.970000 0.000000 0.970000 ( 0.972854)
# >total: 2.930000 0.000000 2.930000 ( 2.932889)
# >avg: 0.976667 0.000000 0.976667 ( 0.977630)
#
def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
sync = $stdout.sync
$stdout.sync = true
label_width ||= 0
label_width += 1
format ||= FORMAT
print ' '*label_width + caption unless caption.empty?
report = Report.new(label_width, format)
results = yield(report)
Array === results and results.grep(Tms).each {|t|
print((labels.shift || t.label || "").ljust(label_width), t.format(format))
}
report.list
ensure
$stdout.sync = sync unless sync.nil?
end
# A simple interface to the #benchmark method, #bm generates sequential
# reports with labels. +label_width+ and +labels+ parameters have the same
# meaning as for #benchmark.
#
# require 'benchmark'
#
# n = 5000000
# Benchmark.bm(7) do |x|
# x.report("for:") { for i in 1..n; a = "1"; end }
# x.report("times:") { n.times do ; a = "1"; end }
# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# end
#
# Generates:
#
# user system total real
# for: 0.960000 0.000000 0.960000 ( 0.957966)
# times: 0.960000 0.000000 0.960000 ( 0.960423)
# upto: 0.950000 0.000000 0.950000 ( 0.954864)
#
def bm(label_width = 0, *labels, &blk) # :yield: report
benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
end
# Sometimes benchmark results are skewed because code executed
# earlier encounters different garbage collection overheads than
# that run later. #bmbm attempts to minimize this effect by running
# the tests twice, the first time as a rehearsal in order to get the
# runtime environment stable, the second time for
# real. GC.start is executed before the start of each of
# the real timings; the cost of this is not included in the
# timings. In reality, though, there's only so much that #bmbm can
# do, and the results are not guaranteed to be isolated from garbage
# collection and other effects.
#
# Because #bmbm takes two passes through the tests, it can
# calculate the required label width.
#
# require 'benchmark'
#
# array = (1..1000000).map { rand }
#
# Benchmark.bmbm do |x|
# x.report("sort!") { array.dup.sort! }
# x.report("sort") { array.dup.sort }
# end
#
# Generates:
#
# Rehearsal -----------------------------------------
# sort! 1.440000 0.010000 1.450000 ( 1.446833)
# sort 1.440000 0.000000 1.440000 ( 1.448257)
# -------------------------------- total: 2.890000sec
#
# user system total real
# sort! 1.460000 0.000000 1.460000 ( 1.458065)
# sort 1.450000 0.000000 1.450000 ( 1.455963)
#
# #bmbm yields a Benchmark::Job object and returns an array of
# Benchmark::Tms objects.
#
def bmbm(width = 0) # :yield: job
job = Job.new(width)
yield(job)
width = job.width + 1
sync = $stdout.sync
$stdout.sync = true
# rehearsal
puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
ets = job.list.inject(Tms.new) { |sum,(label,item)|
print label.ljust(width)
res = Benchmark.measure(&item)
print res.format
sum + res
}.format("total: %tsec")
print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')
# take
print ' '*width + CAPTION
job.list.map { |label,item|
GC.start
print label.ljust(width)
Benchmark.measure(label, &item).tap { |res| print res }
}
ensure
$stdout.sync = sync unless sync.nil?
end
#
# Returns the time used to execute the given block as a
# Benchmark::Tms object. Takes +label+ option.
#
# require 'benchmark'
#
# n = 1000000
#
# time = Benchmark.measure do
# n.times { a = "1" }
# end
# puts time
#
# Generates:
#
# 0.220000 0.000000 0.220000 ( 0.227313)
#
def measure(label = "") # :yield:
t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
Benchmark::Tms.new(t1.utime - t0.utime,
t1.stime - t0.stime,
t1.cutime - t0.cutime,
t1.cstime - t0.cstime,
r1 - r0,
label)
end
#
# Returns the elapsed real time used to execute the given block.
#
def realtime # :yield:
r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
end
module_function :benchmark, :measure, :realtime, :bm, :bmbm
#
# A Job is a sequence of labelled blocks to be processed by the
# Benchmark.bmbm method. It is of little direct interest to the user.
#
class Job # :nodoc:
#
# Returns an initialized Job instance.
# Usually, one doesn't call this method directly, as new
# Job objects are created by the #bmbm method.
# +width+ is a initial value for the label offset used in formatting;
# the #bmbm method passes its +width+ argument to this constructor.
#
def initialize(width)
@width = width
@list = []
end
#
# Registers the given label and block pair in the job list.
#
def item(label = "", &blk) # :yield:
raise ArgumentError, "no block" unless block_given?
label = label.to_s
w = label.length
@width = w if @width < w
@list << [label, blk]
self
end
alias report item
# An array of 2-element arrays, consisting of label and block pairs.
attr_reader :list
# Length of the widest label in the #list.
attr_reader :width
end
#
# This class is used by the Benchmark.benchmark and Benchmark.bm methods.
# It is of little direct interest to the user.
#
class Report # :nodoc:
#
# Returns an initialized Report instance.
# Usually, one doesn't call this method directly, as new
# Report objects are created by the #benchmark and #bm methods.
# +width+ and +format+ are the label offset and
# format string used by Tms#format.
#
def initialize(width = 0, format = nil)
@width, @format, @list = width, format, []
end
#
# Prints the +label+ and measured time for the block,
# formatted by +format+. See Tms#format for the
# formatting rules.
#
def item(label = "", *format, &blk) # :yield:
print label.to_s.ljust(@width)
@list << res = Benchmark.measure(label, &blk)
print res.format(@format, *format)
res
end
alias report item
# An array of Benchmark::Tms objects representing each item.
attr_reader :list
end
#
# A data object, representing the times associated with a benchmark
# measurement.
#
class Tms
# Default caption, see also Benchmark::CAPTION
CAPTION = " user system total real\n"
# Default format string, see also Benchmark::FORMAT
FORMAT = "%10.6u %10.6y %10.6t %10.6r\n"
# User CPU time
attr_reader :utime
# System CPU time
attr_reader :stime
# User CPU time of children
attr_reader :cutime
# System CPU time of children
attr_reader :cstime
# Elapsed real time
attr_reader :real
# Total time, that is +utime+ + +stime+ + +cutime+ + +cstime+
attr_reader :total
# Label
attr_reader :label
#
# Returns an initialized Tms object which has
# +utime+ as the user CPU time, +stime+ as the system CPU time,
# +cutime+ as the children's user CPU time, +cstime+ as the children's
# system CPU time, +real+ as the elapsed real time and +label+ as the label.
#
def initialize(utime = 0.0, stime = 0.0, cutime = 0.0, cstime = 0.0, real = 0.0, label = nil)
@utime, @stime, @cutime, @cstime, @real, @label = utime, stime, cutime, cstime, real, label.to_s
@total = @utime + @stime + @cutime + @cstime
end
#
# Returns a new Tms object whose times are the sum of the times for this
# Tms object, plus the time required to execute the code block (+blk+).
#
def add(&blk) # :yield:
self + Benchmark.measure(&blk)
end
#
# An in-place version of #add.
# Changes the times of this Tms object by making it the sum of the times
# for this Tms object, plus the time required to execute
# the code block (+blk+).
#
def add!(&blk)
t = Benchmark.measure(&blk)
@utime = utime + t.utime
@stime = stime + t.stime
@cutime = cutime + t.cutime
@cstime = cstime + t.cstime
@real = real + t.real
self
end
#
# Returns a new Tms object obtained by memberwise summation
# of the individual times for this Tms object with those of the +other+
# Tms object.
# This method and #/() are useful for taking statistics.
#
def +(other); memberwise(:+, other) end
#
# Returns a new Tms object obtained by memberwise subtraction
# of the individual times for the +other+ Tms object from those of this
# Tms object.
#
def -(other); memberwise(:-, other) end
#
# Returns a new Tms object obtained by memberwise multiplication
# of the individual times for this Tms object by +x+.
#
def *(x); memberwise(:*, x) end
#
# Returns a new Tms object obtained by memberwise division
# of the individual times for this Tms object by +x+.
# This method and #+() are useful for taking statistics.
#
def /(x); memberwise(:/, x) end
#
# Returns the contents of this Tms object as
# a formatted string, according to a +format+ string
# like that passed to Kernel.format. In addition, #format
# accepts the following extensions:
#
# <tt>%u</tt>:: Replaced by the user CPU time, as reported by Tms#utime.
# <tt>%y</tt>:: Replaced by the system CPU time, as reported by #stime (Mnemonic: y of "s*y*stem")
# <tt>%U</tt>:: Replaced by the children's user CPU time, as reported by Tms#cutime
# <tt>%Y</tt>:: Replaced by the children's system CPU time, as reported by Tms#cstime
# <tt>%t</tt>:: Replaced by the total CPU time, as reported by Tms#total
# <tt>%r</tt>:: Replaced by the elapsed real time, as reported by Tms#real
# <tt>%n</tt>:: Replaced by the label string, as reported by Tms#label (Mnemonic: n of "*n*ame")
#
# If +format+ is not given, FORMAT is used as default value, detailing the
# user, system and real elapsed time.
#
def format(format = nil, *args)
str = (format || FORMAT).dup
str.gsub!(/(%[-+.\d]*)n/) { "#{$1}s" % label }
str.gsub!(/(%[-+.\d]*)u/) { "#{$1}f" % utime }
str.gsub!(/(%[-+.\d]*)y/) { "#{$1}f" % stime }
str.gsub!(/(%[-+.\d]*)U/) { "#{$1}f" % cutime }
str.gsub!(/(%[-+.\d]*)Y/) { "#{$1}f" % cstime }
str.gsub!(/(%[-+.\d]*)t/) { "#{$1}f" % total }
str.gsub!(/(%[-+.\d]*)r/) { "(#{$1}f)" % real }
format ? str % args : str
end
#
# Same as #format.
#
def to_s
format
end
#
# Returns a new 6-element array, consisting of the
# label, user CPU time, system CPU time, children's
# user CPU time, children's system CPU time and elapsed
# real time.
#
def to_a
[@label, @utime, @stime, @cutime, @cstime, @real]
end
#
# Returns a hash containing the same data as `to_a`.
#
def to_h
{
label: @label,
utime: @utime,
stime: @stime,
cutime: @cutime,
cstime: @cstime,
real: @real
}
end
protected
#
# Returns a new Tms object obtained by memberwise operation +op+
# of the individual times for this Tms object with those of the other
# Tms object (+x+).
#
# +op+ can be a mathematical operation such as <tt>+</tt>, <tt>-</tt>,
# <tt>*</tt>, <tt>/</tt>
#
def memberwise(op, x)
case x
when Benchmark::Tms
Benchmark::Tms.new(utime.__send__(op, x.utime),
stime.__send__(op, x.stime),
cutime.__send__(op, x.cutime),
cstime.__send__(op, x.cstime),
real.__send__(op, x.real)
)
else
Benchmark::Tms.new(utime.__send__(op, x),
stime.__send__(op, x),
cutime.__send__(op, x),
cstime.__send__(op, x),
real.__send__(op, x)
)
end
end
end
# The default caption string (heading above the output times).
CAPTION = Benchmark::Tms::CAPTION
# The default format string used to display times. See also Benchmark::Tms#format.
FORMAT = Benchmark::Tms::FORMAT
end
share/ruby/abbrev.rb 0000644 00000006711 15173547333 0010435 0 ustar 00 # 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$
#++
##
# 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
share/ruby/forwardable/impl.rb 0000644 00000000636 15173547333 0012425 0 ustar 00 # :stopdoc:
module Forwardable
def self._valid_method?(method)
iseq = RubyVM::InstructionSequence.compile("().#{method}", nil, nil, 0, false)
rescue SyntaxError
false
else
iseq.to_a.dig(-1, 1, 1, :mid) == method.to_sym
end
def self._compile_method(src, file, line)
RubyVM::InstructionSequence.compile(src, file, file, line,
trace_instruction: false)
.eval
end
end
share/ruby/time.rb 0000644 00000057337 15173547333 0010144 0 ustar 00 # frozen_string_literal: true
# shareable_constant_value: literal
require 'date'
# :stopdoc:
# = time.rb
#
# When 'time' is required, Time is extended with additional methods for parsing
# and converting Times.
#
# == Features
#
# This library extends the Time class with the following conversions between
# date strings and Time objects:
#
# * date-time defined by {RFC 2822}[http://www.ietf.org/rfc/rfc2822.txt]
# * HTTP-date defined by {RFC 2616}[http://www.ietf.org/rfc/rfc2616.txt]
# * dateTime defined by XML Schema Part 2: Datatypes ({ISO
# 8601}[http://www.iso.org/iso/date_and_time_format])
# * various formats handled by Date._parse
# * custom formats handled by Date._strptime
# :startdoc:
class Time
class << Time
#
# A hash of timezones mapped to hour differences from UTC. The
# set of time zones corresponds to the ones specified by RFC 2822
# and ISO 8601.
#
ZoneOffset = { # :nodoc:
'UTC' => 0,
# ISO 8601
'Z' => 0,
# RFC 822
'UT' => 0, 'GMT' => 0,
'EST' => -5, 'EDT' => -4,
'CST' => -6, 'CDT' => -5,
'MST' => -7, 'MDT' => -6,
'PST' => -8, 'PDT' => -7,
# Following definition of military zones is original one.
# See RFC 1123 and RFC 2822 for the error in RFC 822.
'A' => +1, 'B' => +2, 'C' => +3, 'D' => +4, 'E' => +5, 'F' => +6,
'G' => +7, 'H' => +8, 'I' => +9, 'K' => +10, 'L' => +11, 'M' => +12,
'N' => -1, 'O' => -2, 'P' => -3, 'Q' => -4, 'R' => -5, 'S' => -6,
'T' => -7, 'U' => -8, 'V' => -9, 'W' => -10, 'X' => -11, 'Y' => -12,
}
#
# Return the number of seconds the specified time zone differs
# from UTC.
#
# Numeric time zones that include minutes, such as
# <code>-10:00</code> or <code>+1330</code> will work, as will
# simpler hour-only time zones like <code>-10</code> or
# <code>+13</code>.
#
# Textual time zones listed in ZoneOffset are also supported.
#
# If the time zone does not match any of the above, +zone_offset+
# will check if the local time zone (both with and without
# potential Daylight Saving \Time changes being in effect) matches
# +zone+. Specifying a value for +year+ will change the year used
# to find the local time zone.
#
# If +zone_offset+ is unable to determine the offset, nil will be
# returned.
#
# require 'time'
#
# Time.zone_offset("EST") #=> -18000
#
# You must require 'time' to use this method.
#
def zone_offset(zone, year=self.now.year)
off = nil
zone = zone.upcase
if /\A([+-])(\d\d)(:?)(\d\d)(?:\3(\d\d))?\z/ =~ zone
off = ($1 == '-' ? -1 : 1) * (($2.to_i * 60 + $4.to_i) * 60 + $5.to_i)
elsif zone.match?(/\A[+-]\d\d\z/)
off = zone.to_i * 3600
elsif ZoneOffset.include?(zone)
off = ZoneOffset[zone] * 3600
elsif ((t = self.local(year, 1, 1)).zone.upcase == zone rescue false)
off = t.utc_offset
elsif ((t = self.local(year, 7, 1)).zone.upcase == zone rescue false)
off = t.utc_offset
end
off
end
# :stopdoc:
def zone_utc?(zone)
# * +0000
# In RFC 2822, +0000 indicate a time zone at Universal Time.
# Europe/Lisbon is "a time zone at Universal Time" in Winter.
# Atlantic/Reykjavik is "a time zone at Universal Time".
# Africa/Dakar is "a time zone at Universal Time".
# So +0000 is a local time such as Europe/London, etc.
# * GMT
# GMT is used as a time zone abbreviation in Europe/London,
# Africa/Dakar, etc.
# So it is a local time.
#
# * -0000, -00:00
# In RFC 2822, -0000 the date-time contains no information about the
# local time zone.
# In RFC 3339, -00:00 is used for the time in UTC is known,
# but the offset to local time is unknown.
# They are not appropriate for specific time zone such as
# Europe/London because time zone neutral,
# So -00:00 and -0000 are treated as UTC.
zone.match?(/\A(?:-00:00|-0000|-00|UTC|Z|UT)\z/i)
end
private :zone_utc?
def force_zone!(t, zone, offset=nil)
if zone_utc?(zone)
t.utc
elsif offset ||= zone_offset(zone)
# Prefer the local timezone over the fixed offset timezone because
# the former is a real timezone and latter is an artificial timezone.
t.localtime
if t.utc_offset != offset
# Use the fixed offset timezone only if the local timezone cannot
# represent the given offset.
t.localtime(offset)
end
else
t.localtime
end
end
private :force_zone!
LeapYearMonthDays = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # :nodoc:
CommonYearMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] # :nodoc:
def month_days(y, m)
if ((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)
LeapYearMonthDays[m-1]
else
CommonYearMonthDays[m-1]
end
end
private :month_days
def apply_offset(year, mon, day, hour, min, sec, off)
if off < 0
off = -off
off, o = off.divmod(60)
if o != 0 then sec += o; o, sec = sec.divmod(60); off += o end
off, o = off.divmod(60)
if o != 0 then min += o; o, min = min.divmod(60); off += o end
off, o = off.divmod(24)
if o != 0 then hour += o; o, hour = hour.divmod(24); off += o end
if off != 0
day += off
days = month_days(year, mon)
if days and days < day
mon += 1
if 12 < mon
mon = 1
year += 1
end
day = 1
end
end
elsif 0 < off
off, o = off.divmod(60)
if o != 0 then sec -= o; o, sec = sec.divmod(60); off -= o end
off, o = off.divmod(60)
if o != 0 then min -= o; o, min = min.divmod(60); off -= o end
off, o = off.divmod(24)
if o != 0 then hour -= o; o, hour = hour.divmod(24); off -= o end
if off != 0 then
day -= off
if day < 1
mon -= 1
if mon < 1
year -= 1
mon = 12
end
day = month_days(year, mon)
end
end
end
return year, mon, day, hour, min, sec
end
private :apply_offset
def make_time(date, year, yday, mon, day, hour, min, sec, sec_fraction, zone, now)
if !year && !yday && !mon && !day && !hour && !min && !sec && !sec_fraction
raise ArgumentError, "no time information in #{date.inspect}"
end
off = nil
if year || now
off_year = year || now.year
off = zone_offset(zone, off_year) if zone
end
if yday
unless (1..366) === yday
raise ArgumentError, "yday #{yday} out of range"
end
mon, day = (yday-1).divmod(31)
mon += 1
day += 1
t = make_time(date, year, nil, mon, day, hour, min, sec, sec_fraction, zone, now)
diff = yday - t.yday
return t if diff.zero?
day += diff
if day > 28 and day > (mday = month_days(off_year, mon))
if (mon += 1) > 12
raise ArgumentError, "yday #{yday} out of range"
end
day -= mday
end
return make_time(date, year, nil, mon, day, hour, min, sec, sec_fraction, zone, now)
end
if now and now.respond_to?(:getlocal)
if off
now = now.getlocal(off) if now.utc_offset != off
else
now = now.getlocal
end
end
usec = nil
usec = sec_fraction * 1000000 if sec_fraction
if now
begin
break if year; year = now.year
break if mon; mon = now.mon
break if day; day = now.day
break if hour; hour = now.hour
break if min; min = now.min
break if sec; sec = now.sec
break if sec_fraction; usec = now.tv_usec
end until true
end
year ||= 1970
mon ||= 1
day ||= 1
hour ||= 0
min ||= 0
sec ||= 0
usec ||= 0
if year != off_year
off = nil
off = zone_offset(zone, year) if zone
end
if off
year, mon, day, hour, min, sec =
apply_offset(year, mon, day, hour, min, sec, off)
t = self.utc(year, mon, day, hour, min, sec, usec)
force_zone!(t, zone, off)
t
else
self.local(year, mon, day, hour, min, sec, usec)
end
end
private :make_time
# :startdoc:
#
# Takes a string representation of a Time and attempts to parse it
# using a heuristic.
#
# This method **does not** function as a validator. If the input
# string does not match valid formats strictly, you may get a
# cryptic result. Should consider to use `Time.strptime` instead
# of this method as possible.
#
# require 'time'
#
# Time.parse("2010-10-31") #=> 2010-10-31 00:00:00 -0500
#
# Any missing pieces of the date are inferred based on the current date.
#
# require 'time'
#
# # assuming the current date is "2011-10-31"
# Time.parse("12:00") #=> 2011-10-31 12:00:00 -0500
#
# We can change the date used to infer our missing elements by passing a second
# object that responds to #mon, #day and #year, such as Date, Time or DateTime.
# We can also use our own object.
#
# require 'time'
#
# class MyDate
# attr_reader :mon, :day, :year
#
# def initialize(mon, day, year)
# @mon, @day, @year = mon, day, year
# end
# end
#
# d = Date.parse("2010-10-28")
# t = Time.parse("2010-10-29")
# dt = DateTime.parse("2010-10-30")
# md = MyDate.new(10,31,2010)
#
# Time.parse("12:00", d) #=> 2010-10-28 12:00:00 -0500
# Time.parse("12:00", t) #=> 2010-10-29 12:00:00 -0500
# Time.parse("12:00", dt) #=> 2010-10-30 12:00:00 -0500
# Time.parse("12:00", md) #=> 2010-10-31 12:00:00 -0500
#
# If a block is given, the year described in +date+ is converted
# by the block. This is specifically designed for handling two
# digit years. For example, if you wanted to treat all two digit
# years prior to 70 as the year 2000+ you could write this:
#
# require 'time'
#
# Time.parse("01-10-31") {|year| year + (year < 70 ? 2000 : 1900)}
# #=> 2001-10-31 00:00:00 -0500
# Time.parse("70-10-31") {|year| year + (year < 70 ? 2000 : 1900)}
# #=> 1970-10-31 00:00:00 -0500
#
# If the upper components of the given time are broken or missing, they are
# supplied with those of +now+. For the lower components, the minimum
# values (1 or 0) are assumed if broken or missing. For example:
#
# require 'time'
#
# # Suppose it is "Thu Nov 29 14:33:20 2001" now and
# # your time zone is EST which is GMT-5.
# now = Time.parse("Thu Nov 29 14:33:20 2001")
# Time.parse("16:30", now) #=> 2001-11-29 16:30:00 -0500
# Time.parse("7/23", now) #=> 2001-07-23 00:00:00 -0500
# Time.parse("Aug 31", now) #=> 2001-08-31 00:00:00 -0500
# Time.parse("Aug 2000", now) #=> 2000-08-01 00:00:00 -0500
#
# Since there are numerous conflicts among locally defined time zone
# abbreviations all over the world, this method is not intended to
# understand all of them. For example, the abbreviation "CST" is
# used variously as:
#
# -06:00 in America/Chicago,
# -05:00 in America/Havana,
# +08:00 in Asia/Harbin,
# +09:30 in Australia/Darwin,
# +10:30 in Australia/Adelaide,
# etc.
#
# Based on this fact, this method only understands the time zone
# abbreviations described in RFC 822 and the system time zone, in the
# order named. (i.e. a definition in RFC 822 overrides the system
# time zone definition.) The system time zone is taken from
# <tt>Time.local(year, 1, 1).zone</tt> and
# <tt>Time.local(year, 7, 1).zone</tt>.
# If the extracted time zone abbreviation does not match any of them,
# it is ignored and the given time is regarded as a local time.
#
# ArgumentError is raised if Date._parse cannot extract information from
# +date+ or if the Time class cannot represent specified date.
#
# This method can be used as a fail-safe for other parsing methods as:
#
# Time.rfc2822(date) rescue Time.parse(date)
# Time.httpdate(date) rescue Time.parse(date)
# Time.xmlschema(date) rescue Time.parse(date)
#
# A failure of Time.parse should be checked, though.
#
# You must require 'time' to use this method.
#
def parse(date, now=self.now)
comp = !block_given?
d = Date._parse(date, comp)
year = d[:year]
year = yield(year) if year && !comp
make_time(date, year, d[:yday], d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
end
#
# Works similar to +parse+ except that instead of using a
# heuristic to detect the format of the input string, you provide
# a second argument that describes the format of the string.
#
# If a block is given, the year described in +date+ is converted by the
# block. For example:
#
# Time.strptime(...) {|y| y < 100 ? (y >= 69 ? y + 1900 : y + 2000) : y}
#
# Below is a list of the formatting options:
#
# %a :: The abbreviated weekday name ("Sun")
# %A :: The full weekday name ("Sunday")
# %b :: The abbreviated month name ("Jan")
# %B :: The full month name ("January")
# %c :: The preferred local date and time representation
# %C :: Century (20 in 2009)
# %d :: Day of the month (01..31)
# %D :: Date (%m/%d/%y)
# %e :: Day of the month, blank-padded ( 1..31)
# %F :: Equivalent to %Y-%m-%d (the ISO 8601 date format)
# %g :: The last two digits of the commercial year
# %G :: The week-based year according to ISO-8601 (week 1 starts on Monday
# and includes January 4)
# %h :: Equivalent to %b
# %H :: Hour of the day, 24-hour clock (00..23)
# %I :: Hour of the day, 12-hour clock (01..12)
# %j :: Day of the year (001..366)
# %k :: hour, 24-hour clock, blank-padded ( 0..23)
# %l :: hour, 12-hour clock, blank-padded ( 0..12)
# %L :: Millisecond of the second (000..999)
# %m :: Month of the year (01..12)
# %M :: Minute of the hour (00..59)
# %n :: Newline (\n)
# %N :: Fractional seconds digits
# %p :: Meridian indicator ("AM" or "PM")
# %P :: Meridian indicator ("am" or "pm")
# %r :: time, 12-hour (same as %I:%M:%S %p)
# %R :: time, 24-hour (%H:%M)
# %s :: Number of seconds since 1970-01-01 00:00:00 UTC.
# %S :: Second of the minute (00..60)
# %t :: Tab character (\t)
# %T :: time, 24-hour (%H:%M:%S)
# %u :: Day of the week as a decimal, Monday being 1. (1..7)
# %U :: Week number of the current year, starting with the first Sunday as
# the first day of the first week (00..53)
# %v :: VMS date (%e-%b-%Y)
# %V :: Week number of year according to ISO 8601 (01..53)
# %W :: Week number of the current year, starting with the first Monday
# as the first day of the first week (00..53)
# %w :: Day of the week (Sunday is 0, 0..6)
# %x :: Preferred representation for the date alone, no time
# %X :: Preferred representation for the time alone, no date
# %y :: Year without a century (00..99)
# %Y :: Year which may include century, if provided
# %z :: Time zone as hour offset from UTC (e.g. +0900)
# %Z :: Time zone name
# %% :: Literal "%" character
# %+ :: date(1) (%a %b %e %H:%M:%S %Z %Y)
#
# require 'time'
#
# Time.strptime("2000-10-31", "%Y-%m-%d") #=> 2000-10-31 00:00:00 -0500
#
# You must require 'time' to use this method.
#
def strptime(date, format, now=self.now)
d = Date._strptime(date, format)
raise ArgumentError, "invalid date or strptime format - `#{date}' `#{format}'" unless d
if seconds = d[:seconds]
if sec_fraction = d[:sec_fraction]
usec = sec_fraction * 1000000
usec *= -1 if seconds < 0
else
usec = 0
end
t = Time.at(seconds, usec)
if zone = d[:zone]
force_zone!(t, zone)
end
else
year = d[:year]
year = yield(year) if year && block_given?
yday = d[:yday]
if (d[:cwyear] && !year) || ((d[:cwday] || d[:cweek]) && !(d[:mon] && d[:mday]))
# make_time doesn't deal with cwyear/cwday/cweek
return Date.strptime(date, format).to_time
end
if (d[:wnum0] || d[:wnum1]) && !yday && !(d[:mon] && d[:mday])
yday = Date.strptime(date, format).yday
end
t = make_time(date, year, yday, d[:mon], d[:mday], d[:hour], d[:min], d[:sec], d[:sec_fraction], d[:zone], now)
end
t
end
MonthValue = { # :nodoc:
'JAN' => 1, 'FEB' => 2, 'MAR' => 3, 'APR' => 4, 'MAY' => 5, 'JUN' => 6,
'JUL' => 7, 'AUG' => 8, 'SEP' => 9, 'OCT' =>10, 'NOV' =>11, 'DEC' =>12
}
#
# Parses +date+ as date-time defined by RFC 2822 and converts it to a Time
# object. The format is identical to the date format defined by RFC 822 and
# updated by RFC 1123.
#
# ArgumentError is raised if +date+ is not compliant with RFC 2822
# or if the Time class cannot represent specified date.
#
# See #rfc2822 for more information on this format.
#
# require 'time'
#
# Time.rfc2822("Wed, 05 Oct 2011 22:26:12 -0400")
# #=> 2010-10-05 22:26:12 -0400
#
# You must require 'time' to use this method.
#
def rfc2822(date)
if /\A\s*
(?:(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*,\s*)?
(\d{1,2})\s+
(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s+
(\d{2,})\s+
(\d{2})\s*
:\s*(\d{2})
(?:\s*:\s*(\d\d))?\s+
([+-]\d{4}|
UT|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT|[A-IK-Z])/ix =~ date
# Since RFC 2822 permit comments, the regexp has no right anchor.
day = $1.to_i
mon = MonthValue[$2.upcase]
year = $3.to_i
short_year_p = $3.length <= 3
hour = $4.to_i
min = $5.to_i
sec = $6 ? $6.to_i : 0
zone = $7
if short_year_p
# following year completion is compliant with RFC 2822.
year = if year < 50
2000 + year
else
1900 + year
end
end
off = zone_offset(zone)
year, mon, day, hour, min, sec =
apply_offset(year, mon, day, hour, min, sec, off)
t = self.utc(year, mon, day, hour, min, sec)
force_zone!(t, zone, off)
t
else
raise ArgumentError.new("not RFC 2822 compliant date: #{date.inspect}")
end
end
alias rfc822 rfc2822
#
# Parses +date+ as an HTTP-date defined by RFC 2616 and converts it to a
# Time object.
#
# ArgumentError is raised if +date+ is not compliant with RFC 2616 or if
# the Time class cannot represent specified date.
#
# See #httpdate for more information on this format.
#
# require 'time'
#
# Time.httpdate("Thu, 06 Oct 2011 02:26:12 GMT")
# #=> 2011-10-06 02:26:12 UTC
#
# You must require 'time' to use this method.
#
def httpdate(date)
if date.match?(/\A\s*
(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun),\x20
(\d{2})\x20
(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
(\d{4})\x20
(\d{2}):(\d{2}):(\d{2})\x20
GMT
\s*\z/ix)
self.rfc2822(date).utc
elsif /\A\s*
(?:Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday),\x20
(\d\d)-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-(\d\d)\x20
(\d\d):(\d\d):(\d\d)\x20
GMT
\s*\z/ix =~ date
year = $3.to_i
if year < 50
year += 2000
else
year += 1900
end
self.utc(year, $2, $1.to_i, $4.to_i, $5.to_i, $6.to_i)
elsif /\A\s*
(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\x20
(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\x20
(\d\d|\x20\d)\x20
(\d\d):(\d\d):(\d\d)\x20
(\d{4})
\s*\z/ix =~ date
self.utc($6.to_i, MonthValue[$1.upcase], $2.to_i,
$3.to_i, $4.to_i, $5.to_i)
else
raise ArgumentError.new("not RFC 2616 compliant date: #{date.inspect}")
end
end
#
# Parses +time+ as a dateTime defined by the XML Schema and converts it to
# a Time object. The format is a restricted version of the format defined
# by ISO 8601.
#
# ArgumentError is raised if +time+ is not compliant with the format or if
# the Time class cannot represent the specified time.
#
# See #xmlschema for more information on this format.
#
# require 'time'
#
# Time.xmlschema("2011-10-05T22:26:12-04:00")
# #=> 2011-10-05 22:26:12-04:00
#
# You must require 'time' to use this method.
#
def xmlschema(time)
if /\A\s*
(-?\d+)-(\d\d)-(\d\d)
T
(\d\d):(\d\d):(\d\d)
(\.\d+)?
(Z|[+-]\d\d(?::?\d\d)?)?
\s*\z/ix =~ time
year = $1.to_i
mon = $2.to_i
day = $3.to_i
hour = $4.to_i
min = $5.to_i
sec = $6.to_i
usec = 0
if $7
usec = Rational($7) * 1000000
end
if $8
zone = $8
off = zone_offset(zone)
year, mon, day, hour, min, sec =
apply_offset(year, mon, day, hour, min, sec, off)
t = self.utc(year, mon, day, hour, min, sec, usec)
force_zone!(t, zone, off)
t
else
self.local(year, mon, day, hour, min, sec, usec)
end
else
raise ArgumentError.new("invalid xmlschema format: #{time.inspect}")
end
end
alias iso8601 xmlschema
end # class << self
#
# Returns a string which represents the time as date-time defined by RFC 2822:
#
# day-of-week, DD month-name CCYY hh:mm:ss zone
#
# where zone is [+-]hhmm.
#
# If +self+ is a UTC time, -0000 is used as zone.
#
# require 'time'
#
# t = Time.now
# t.rfc2822 # => "Wed, 05 Oct 2011 22:26:12 -0400"
#
# You must require 'time' to use this method.
#
def rfc2822
strftime('%a, %d %b %Y %T ') << (utc? ? '-0000' : strftime('%z'))
end
alias rfc822 rfc2822
#
# Returns a string which represents the time as RFC 1123 date of HTTP-date
# defined by RFC 2616:
#
# day-of-week, DD month-name CCYY hh:mm:ss GMT
#
# Note that the result is always UTC (GMT).
#
# require 'time'
#
# t = Time.now
# t.httpdate # => "Thu, 06 Oct 2011 02:26:12 GMT"
#
# You must require 'time' to use this method.
#
def httpdate
getutc.strftime('%a, %d %b %Y %T GMT')
end
#
# Returns a string which represents the time as a dateTime defined by XML
# Schema:
#
# CCYY-MM-DDThh:mm:ssTZD
# CCYY-MM-DDThh:mm:ss.sssTZD
#
# where TZD is Z or [+-]hh:mm.
#
# If self is a UTC time, Z is used as TZD. [+-]hh:mm is used otherwise.
#
# +fraction_digits+ specifies a number of digits to use for fractional
# seconds. Its default value is 0.
#
# require 'time'
#
# t = Time.now
# t.iso8601 # => "2011-10-05T22:26:12-04:00"
#
# You must require 'time' to use this method.
#
def xmlschema(fraction_digits=0)
fraction_digits = fraction_digits.to_i
s = strftime("%FT%T")
if fraction_digits > 0
s << strftime(".%#{fraction_digits}N")
end
s << (utc? ? 'Z' : strftime("%:z"))
end
alias iso8601 xmlschema
end
share/ruby/logger.rb 0000644 00000053710 15173547333 0010454 0 ustar 00 # frozen_string_literal: true
# logger.rb - simple logging utility
# Copyright (C) 2000-2003, 2005, 2008, 2011 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
#
# Documentation:: NAKAMURA, Hiroshi and Gavin Sinclair
# License::
# You can redistribute it and/or modify it under the same terms of Ruby's
# license; either the dual license version in 2003, or any later version.
# Revision:: $Id$
#
# A simple system for logging messages. See Logger for more documentation.
require 'monitor'
require 'rbconfig'
require_relative 'logger/version'
require_relative 'logger/formatter'
require_relative 'logger/log_device'
require_relative 'logger/severity'
require_relative 'logger/errors'
# \Class \Logger provides a simple but sophisticated logging utility that
# you can use to create one or more
# {event logs}[https://en.wikipedia.org/wiki/Logging_(software)#Event_logs]
# for your program.
# Each such log contains a chronological sequence of entries
# that provides a record of the program's activities.
#
# == About the Examples
#
# All examples on this page assume that \Logger has been required:
#
# require 'logger'
#
# == Synopsis
#
# Create a log with Logger.new:
#
# # Single log file.
# logger = Logger.new('t.log')
# # Size-based rotated logging: 3 10-megabyte files.
# logger = Logger.new('t.log', 3, 10485760)
# # Period-based rotated logging: daily (also allowed: 'weekly', 'monthly').
# logger = Logger.new('t.log', 'daily')
# # Log to an IO stream.
# logger = Logger.new($stdout)
#
# Add entries (level, message) with Logger#add:
#
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# logger.add(Logger::INFO, 'Non-error information')
# logger.add(Logger::WARN, 'Non-error warning')
# logger.add(Logger::ERROR, 'Non-fatal error')
# logger.add(Logger::FATAL, 'Fatal error')
# logger.add(Logger::UNKNOWN, 'Most severe')
#
# Close the log with Logger#close:
#
# logger.close
#
# == Entries
#
# You can add entries with method Logger#add:
#
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# logger.add(Logger::INFO, 'Non-error information')
# logger.add(Logger::WARN, 'Non-error warning')
# logger.add(Logger::ERROR, 'Non-fatal error')
# logger.add(Logger::FATAL, 'Fatal error')
# logger.add(Logger::UNKNOWN, 'Most severe')
#
# These shorthand methods also add entries:
#
# logger.debug('Maximal debugging info')
# logger.info('Non-error information')
# logger.warn('Non-error warning')
# logger.error('Non-fatal error')
# logger.fatal('Fatal error')
# logger.unknown('Most severe')
#
# When you call any of these methods,
# the entry may or may not be written to the log,
# depending on the entry's severity and on the log level;
# see {Log Level}[rdoc-ref:Logger@Log+Level]
#
# An entry always has:
#
# - A severity (the required argument to #add).
# - An automatically created timestamp.
#
# And may also have:
#
# - A message.
# - A program name.
#
# Example:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message.', 'mung')
# # => I, [2022-05-07T17:21:46.536234 #20536] INFO -- mung: My message.
#
# The default format for an entry is:
#
# "%s, [%s #%d] %5s -- %s: %s\n"
#
# where the values to be formatted are:
#
# - \Severity (one letter).
# - Timestamp.
# - Process id.
# - \Severity (word).
# - Program name.
# - Message.
#
# You can use a different entry format by:
#
# - Setting a custom format proc (affects following entries);
# see {formatter=}[Logger.html#attribute-i-formatter].
# - Calling any of the methods above with a block
# (affects only the one entry).
# Doing so can have two benefits:
#
# - Context: the block can evaluate the entire program context
# and create a context-dependent message.
# - Performance: the block is not evaluated unless the log level
# permits the entry actually to be written:
#
# logger.error { my_slow_message_generator }
#
# Contrast this with the string form, where the string is
# always evaluated, regardless of the log level:
#
# logger.error("#{my_slow_message_generator}")
#
# === \Severity
#
# The severity of a log entry has two effects:
#
# - Determines whether the entry is selected for inclusion in the log;
# see {Log Level}[rdoc-ref:Logger@Log+Level].
# - Indicates to any log reader (whether a person or a program)
# the relative importance of the entry.
#
# === Timestamp
#
# The timestamp for a log entry is generated automatically
# when the entry is created.
#
# The logged timestamp is formatted by method
# {Time#strftime}[rdoc-ref:Time#strftime]
# using this format string:
#
# '%Y-%m-%dT%H:%M:%S.%6N'
#
# Example:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO)
# # => I, [2022-05-07T17:04:32.318331 #20536] INFO -- : nil
#
# You can set a different format using method #datetime_format=.
#
# === Message
#
# The message is an optional argument to an entry method:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message')
# # => I, [2022-05-07T18:15:37.647581 #20536] INFO -- : My message
#
# For the default entry formatter, <tt>Logger::Formatter</tt>,
# the message object may be:
#
# - A string: used as-is.
# - An Exception: <tt>message.message</tt> is used.
# - Anything else: <tt>message.inspect</tt> is used.
#
# *Note*: Logger::Formatter does not escape or sanitize
# the message passed to it.
# Developers should be aware that malicious data (user input)
# may be in the message, and should explicitly escape untrusted data.
#
# You can use a custom formatter to escape message data;
# see the example at {formatter=}[Logger.html#attribute-i-formatter].
#
# === Program Name
#
# The program name is an optional argument to an entry method:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message', 'mung')
# # => I, [2022-05-07T18:17:38.084716 #20536] INFO -- mung: My message
#
# The default program name for a new logger may be set in the call to
# Logger.new via optional keyword argument +progname+:
#
# logger = Logger.new('t.log', progname: 'mung')
#
# The default program name for an existing logger may be set
# by a call to method #progname=:
#
# logger.progname = 'mung'
#
# The current program name may be retrieved with method
# {progname}[Logger.html#attribute-i-progname]:
#
# logger.progname # => "mung"
#
# == Log Level
#
# The log level setting determines whether an entry is actually
# written to the log, based on the entry's severity.
#
# These are the defined severities (least severe to most severe):
#
# logger = Logger.new($stdout)
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# # => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info
# logger.add(Logger::INFO, 'Non-error information')
# # => I, [2022-05-07T17:59:14.349167 #20536] INFO -- : Non-error information
# logger.add(Logger::WARN, 'Non-error warning')
# # => W, [2022-05-07T18:00:45.337538 #20536] WARN -- : Non-error warning
# logger.add(Logger::ERROR, 'Non-fatal error')
# # => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error
# logger.add(Logger::FATAL, 'Fatal error')
# # => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error
# logger.add(Logger::UNKNOWN, 'Most severe')
# # => A, [2022-05-07T18:07:54.657491 #20536] ANY -- : Most severe
#
# The default initial level setting is Logger::DEBUG, the lowest level,
# which means that all entries are to be written, regardless of severity:
#
# logger = Logger.new($stdout)
# logger.level # => 0
# logger.add(0, "My message")
# # => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message
#
# You can specify a different setting in a new logger
# using keyword argument +level+ with an appropriate value:
#
# logger = Logger.new($stdout, level: Logger::ERROR)
# logger = Logger.new($stdout, level: 'error')
# logger = Logger.new($stdout, level: :error)
# logger.level # => 3
#
# With this level, entries with severity Logger::ERROR and higher
# are written, while those with lower severities are not written:
#
# logger = Logger.new($stdout, level: Logger::ERROR)
# logger.add(3)
# # => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil
# logger.add(2) # Silent.
#
# You can set the log level for an existing logger
# with method #level=:
#
# logger.level = Logger::ERROR
#
# These shorthand methods also set the level:
#
# logger.debug! # => 0
# logger.info! # => 1
# logger.warn! # => 2
# logger.error! # => 3
# logger.fatal! # => 4
#
# You can retrieve the log level with method
# {level}[Logger.html#attribute-i-level]:
#
# logger.level = Logger::ERROR
# logger.level # => 3
#
# These methods return whether a given
# level is to be written:
#
# logger.level = Logger::ERROR
# logger.debug? # => false
# logger.info? # => false
# logger.warn? # => false
# logger.error? # => true
# logger.fatal? # => true
#
# == Log File Rotation
#
# By default, a log file is a single file that grows indefinitely
# (until explicitly closed); there is no file rotation.
#
# To keep log files to a manageable size,
# you can use _log_ _file_ _rotation_, which uses multiple log files:
#
# - Each log file has entries for a non-overlapping
# time interval.
# - Only the most recent log file is open and active;
# the others are closed and inactive.
#
# === Size-Based Rotation
#
# For size-based log file rotation, call Logger.new with:
#
# - Argument +logdev+ as a file path.
# - Argument +shift_age+ with a positive integer:
# the number of log files to be in the rotation.
# - Argument +shift_size+ as a positive integer:
# the maximum size (in bytes) of each log file;
# defaults to 1048576 (1 megabyte).
#
# Examples:
#
# logger = Logger.new('t.log', 3) # Three 1-megabyte files.
# logger = Logger.new('t.log', 5, 10485760) # Five 10-megabyte files.
#
# For these examples, suppose:
#
# logger = Logger.new('t.log', 3)
#
# Logging begins in the new log file, +t.log+;
# the log file is "full" and ready for rotation
# when a new entry would cause its size to exceed +shift_size+.
#
# The first time +t.log+ is full:
#
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# The second time +t.log+ is full:
#
# - +t.log.0 is renamed as +t.log.1+.
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# Each subsequent time that +t.log+ is full,
# the log files are rotated:
#
# - +t.log.1+ is removed.
# - +t.log.0 is renamed as +t.log.1+.
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# === Periodic Rotation
#
# For periodic rotation, call Logger.new with:
#
# - Argument +logdev+ as a file path.
# - Argument +shift_age+ as a string period indicator.
#
# Examples:
#
# logger = Logger.new('t.log', 'daily') # Rotate log files daily.
# logger = Logger.new('t.log', 'weekly') # Rotate log files weekly.
# logger = Logger.new('t.log', 'monthly') # Rotate log files monthly.
#
# Example:
#
# logger = Logger.new('t.log', 'daily')
#
# When the given period expires:
#
# - The base log file, +t.log+ is closed and renamed
# with a date-based suffix such as +t.log.20220509+.
# - A new log file +t.log+ is opened.
# - Nothing is removed.
#
# The default format for the suffix is <tt>'%Y%m%d'</tt>,
# which produces a suffix similar to the one above.
# You can set a different format using create-time option
# +shift_period_suffix+;
# see details and suggestions at
# {Time#strftime}[rdoc-ref:Time#strftime].
#
class Logger
_, name, rev = %w$Id$
if name
name = name.chomp(",v")
else
name = File.basename(__FILE__)
end
rev ||= "v#{VERSION}"
ProgName = "#{name}/#{rev}"
include Severity
# Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
attr_reader :level
# Sets the log level; returns +severity+.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
# Argument +severity+ may be an integer, a string, or a symbol:
#
# logger.level = Logger::ERROR # => 3
# logger.level = 3 # => 3
# logger.level = 'error' # => "error"
# logger.level = :error # => :error
#
# Logger#sev_threshold= is an alias for Logger#level=.
#
def level=(severity)
if severity.is_a?(Integer)
@level = severity
else
case severity.to_s.downcase
when 'debug'
@level = DEBUG
when 'info'
@level = INFO
when 'warn'
@level = WARN
when 'error'
@level = ERROR
when 'fatal'
@level = FATAL
when 'unknown'
@level = UNKNOWN
else
raise ArgumentError, "invalid log level: #{severity}"
end
end
end
# Program name to include in log messages.
attr_accessor :progname
# Sets the date-time format.
#
# Argument +datetime_format+ should be either of these:
#
# - A string suitable for use as a format for method
# {Time#strftime}[rdoc-ref:Time#strftime].
# - +nil+: the logger uses <tt>'%Y-%m-%dT%H:%M:%S.%6N'</tt>.
#
def datetime_format=(datetime_format)
@default_formatter.datetime_format = datetime_format
end
# Returns the date-time format; see #datetime_format=.
#
def datetime_format
@default_formatter.datetime_format
end
# Sets or retrieves the logger entry formatter proc.
#
# When +formatter+ is +nil+, the logger uses Logger::Formatter.
#
# When +formatter+ is a proc, a new entry is formatted by the proc,
# which is called with four arguments:
#
# - +severity+: The severity of the entry.
# - +time+: A Time object representing the entry's timestamp.
# - +progname+: The program name for the entry.
# - +msg+: The message for the entry (string or string-convertible object).
#
# The proc should return a string containing the formatted entry.
#
# This custom formatter uses
# {String#dump}[rdoc-ref:String#dump]
# to escape the message string:
#
# logger = Logger.new($stdout, progname: 'mung')
# original_formatter = logger.formatter || Logger::Formatter.new
# logger.formatter = proc { |severity, time, progname, msg|
# original_formatter.call(severity, time, progname, msg.dump)
# }
# logger.add(Logger::INFO, "hello \n ''")
# logger.add(Logger::INFO, "\f\x00\xff\\\"")
#
# Output:
#
# I, [2022-05-13T13:16:29.637488 #8492] INFO -- mung: "hello \n ''"
# I, [2022-05-13T13:16:29.637610 #8492] INFO -- mung: "\f\x00\xFF\\\""
#
attr_accessor :formatter
alias sev_threshold level
alias sev_threshold= level=
# Returns +true+ if the log level allows entries with severity
# Logger::DEBUG to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def debug?; level <= DEBUG; end
# Sets the log level to Logger::DEBUG.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def debug!; self.level = DEBUG; end
# Returns +true+ if the log level allows entries with severity
# Logger::INFO to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def info?; level <= INFO; end
# Sets the log level to Logger::INFO.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def info!; self.level = INFO; end
# Returns +true+ if the log level allows entries with severity
# Logger::WARN to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def warn?; level <= WARN; end
# Sets the log level to Logger::WARN.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def warn!; self.level = WARN; end
# Returns +true+ if the log level allows entries with severity
# Logger::ERROR to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def error?; level <= ERROR; end
# Sets the log level to Logger::ERROR.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def error!; self.level = ERROR; end
# Returns +true+ if the log level allows entries with severity
# Logger::FATAL to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def fatal?; level <= FATAL; end
# Sets the log level to Logger::FATAL.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def fatal!; self.level = FATAL; end
# :call-seq:
# Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options)
#
# With the single argument +logdev+,
# returns a new logger with all default options:
#
# Logger.new('t.log') # => #<Logger:0x000001e685dc6ac8>
#
# Argument +logdev+ must be one of:
#
# - A string filepath: entries are to be written
# to the file at that path; if the file at that path exists,
# new entries are appended.
# - An IO stream (typically +$stdout+, +$stderr+. or an open file):
# entries are to be written to the given stream.
# - +nil+ or +File::NULL+: no entries are to be written.
#
# Examples:
#
# Logger.new('t.log')
# Logger.new($stdout)
#
# The keyword options are:
#
# - +level+: sets the log level; default value is Logger::DEBUG.
# See {Log Level}[rdoc-ref:Logger@Log+Level]:
#
# Logger.new('t.log', level: Logger::ERROR)
#
# - +progname+: sets the default program name; default is +nil+.
# See {Program Name}[rdoc-ref:Logger@Program+Name]:
#
# Logger.new('t.log', progname: 'mung')
#
# - +formatter+: sets the entry formatter; default is +nil+.
# See {formatter=}[Logger.html#attribute-i-formatter].
# - +datetime_format+: sets the format for entry timestamp;
# default is +nil+.
# See #datetime_format=.
# - +binmode+: sets whether the logger writes in binary mode;
# default is +false+.
# - +shift_period_suffix+: sets the format for the filename suffix
# for periodic log file rotation; default is <tt>'%Y%m%d'</tt>.
# See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation].
#
def initialize(logdev, shift_age = 0, shift_size = 1048576, level: DEBUG,
progname: nil, formatter: nil, datetime_format: nil,
binmode: false, shift_period_suffix: '%Y%m%d')
self.level = level
self.progname = progname
@default_formatter = Formatter.new
self.datetime_format = datetime_format
self.formatter = formatter
@logdev = nil
if logdev && logdev != File::NULL
@logdev = LogDevice.new(logdev, shift_age: shift_age,
shift_size: shift_size,
shift_period_suffix: shift_period_suffix,
binmode: binmode)
end
end
# Sets the logger's output stream:
#
# - If +logdev+ is +nil+, reopens the current output stream.
# - If +logdev+ is a filepath, opens the indicated file for append.
# - If +logdev+ is an IO stream
# (usually <tt>$stdout</tt>, <tt>$stderr</tt>, or an open File object),
# opens the stream for append.
#
# Example:
#
# logger = Logger.new('t.log')
# logger.add(Logger::ERROR, 'one')
# logger.close
# logger.add(Logger::ERROR, 'two') # Prints 'log writing failed. closed stream'
# logger.reopen
# logger.add(Logger::ERROR, 'three')
# logger.close
# File.readlines('t.log')
# # =>
# # ["# Logfile created on 2022-05-12 14:21:19 -0500 by logger.rb/v1.5.0\n",
# # "E, [2022-05-12T14:21:27.596726 #22428] ERROR -- : one\n",
# # "E, [2022-05-12T14:23:05.847241 #22428] ERROR -- : three\n"]
#
def reopen(logdev = nil)
@logdev&.reopen(logdev)
self
end
# Creates a log entry, which may or may not be written to the log,
# depending on the entry's severity and on the log level.
# See {Log Level}[rdoc-ref:Logger@Log+Level]
# and {Entries}[rdoc-ref:Logger@Entries] for details.
#
# Examples:
#
# logger = Logger.new($stdout, progname: 'mung')
# logger.add(Logger::INFO)
# logger.add(Logger::ERROR, 'No good')
# logger.add(Logger::ERROR, 'No good', 'gnum')
#
# Output:
#
# I, [2022-05-12T16:25:31.469726 #36328] INFO -- mung: mung
# E, [2022-05-12T16:25:55.349414 #36328] ERROR -- mung: No good
# E, [2022-05-12T16:26:35.841134 #36328] ERROR -- gnum: No good
#
# These convenience methods have implicit severity:
#
# - #debug.
# - #info.
# - #warn.
# - #error.
# - #fatal.
# - #unknown.
#
def add(severity, message = nil, progname = nil)
severity ||= UNKNOWN
if @logdev.nil? or severity < level
return true
end
if progname.nil?
progname = @progname
end
if message.nil?
if block_given?
message = yield
else
message = progname
progname = @progname
end
end
@logdev.write(
format_message(format_severity(severity), Time.now, progname, message))
true
end
alias log add
# Writes the given +msg+ to the log with no formatting;
# returns the number of characters written,
# or +nil+ if no log device exists:
#
# logger = Logger.new($stdout)
# logger << 'My message.' # => 10
#
# Output:
#
# My message.
#
def <<(msg)
@logdev&.write(msg)
end
# Equivalent to calling #add with severity <tt>Logger::DEBUG</tt>.
#
def debug(progname = nil, &block)
add(DEBUG, nil, progname, &block)
end
# Equivalent to calling #add with severity <tt>Logger::INFO</tt>.
#
def info(progname = nil, &block)
add(INFO, nil, progname, &block)
end
# Equivalent to calling #add with severity <tt>Logger::WARN</tt>.
#
def warn(progname = nil, &block)
add(WARN, nil, progname, &block)
end
# Equivalent to calling #add with severity <tt>Logger::ERROR</tt>.
#
def error(progname = nil, &block)
add(ERROR, nil, progname, &block)
end
# Equivalent to calling #add with severity <tt>Logger::FATAL</tt>.
#
def fatal(progname = nil, &block)
add(FATAL, nil, progname, &block)
end
# Equivalent to calling #add with severity <tt>Logger::UNKNOWN</tt>.
#
def unknown(progname = nil, &block)
add(UNKNOWN, nil, progname, &block)
end
# Closes the logger; returns +nil+:
#
# logger = Logger.new('t.log')
# logger.close # => nil
# logger.info('foo') # Prints "log writing failed. closed stream"
#
# Related: Logger#reopen.
def close
@logdev&.close
end
private
# \Severity label for logging (max 5 chars).
SEV_LABEL = %w(DEBUG INFO WARN ERROR FATAL ANY).freeze
def format_severity(severity)
SEV_LABEL[severity] || 'ANY'
end
def format_message(severity, datetime, progname, msg)
(@formatter || @default_formatter).call(severity, datetime, progname, msg)
end
end
share/ruby/error_highlight.rb 0000644 00000000124 15173547333 0012344 0 ustar 00 require_relative "error_highlight/base"
require_relative "error_highlight/core_ext"
share/ruby/observer.rb 0000644 00000014604 15173547333 0011023 0 ustar 00 # frozen_string_literal: true
#
# Implementation of the _Observer_ object-oriented design pattern. The
# following documentation is copied, with modifications, from "Programming
# Ruby", by Hunt and Thomas; http://www.ruby-doc.org/docs/ProgrammingRuby/html/lib_patterns.html.
#
# See Observable for more info.
# The Observer pattern (also known as publish/subscribe) provides a simple
# mechanism for one object to inform a set of interested third-party objects
# when its state changes.
#
# == Mechanism
#
# The notifying class mixes in the +Observable+
# module, which provides the methods for managing the associated observer
# objects.
#
# The observable object must:
# * assert that it has +#changed+
# * call +#notify_observers+
#
# An observer subscribes to updates using Observable#add_observer, which also
# specifies the method called via #notify_observers. The default method for
# #notify_observers is #update.
#
# === Example
#
# The following example demonstrates this nicely. A +Ticker+, when run,
# continually receives the stock +Price+ for its <tt>@symbol</tt>. A +Warner+
# is a general observer of the price, and two warners are demonstrated, a
# +WarnLow+ and a +WarnHigh+, which print a warning if the price is below or
# above their set limits, respectively.
#
# The +update+ callback allows the warners to run without being explicitly
# called. The system is set up with the +Ticker+ and several observers, and the
# observers do their duty without the top-level code having to interfere.
#
# Note that the contract between publisher and subscriber (observable and
# observer) is not declared or enforced. The +Ticker+ publishes a time and a
# price, and the warners receive that. But if you don't ensure that your
# contracts are correct, nothing else can warn you.
#
# require "observer"
#
# class Ticker ### Periodically fetch a stock price.
# include Observable
#
# def initialize(symbol)
# @symbol = symbol
# end
#
# def run
# last_price = nil
# loop do
# price = Price.fetch(@symbol)
# print "Current price: #{price}\n"
# if price != last_price
# changed # notify observers
# last_price = price
# notify_observers(Time.now, price)
# end
# sleep 1
# end
# end
# end
#
# class Price ### A mock class to fetch a stock price (60 - 140).
# def self.fetch(symbol)
# 60 + rand(80)
# end
# end
#
# class Warner ### An abstract observer of Ticker objects.
# def initialize(ticker, limit)
# @limit = limit
# ticker.add_observer(self)
# end
# end
#
# class WarnLow < Warner
# def update(time, price) # callback for observer
# if price < @limit
# print "--- #{time.to_s}: Price below #@limit: #{price}\n"
# end
# end
# end
#
# class WarnHigh < Warner
# def update(time, price) # callback for observer
# if price > @limit
# print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
# end
# end
# end
#
# ticker = Ticker.new("MSFT")
# WarnLow.new(ticker, 80)
# WarnHigh.new(ticker, 120)
# ticker.run
#
# Produces:
#
# Current price: 83
# Current price: 75
# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 75
# Current price: 90
# Current price: 134
# +++ Sun Jun 09 00:10:25 CDT 2002: Price above 120: 134
# Current price: 134
# Current price: 112
# Current price: 79
# --- Sun Jun 09 00:10:25 CDT 2002: Price below 80: 79
#
# === Usage with procs
#
# The +#notify_observers+ method can also be used with +proc+s by using
# the +:call+ as +func+ parameter.
#
# The following example illustrates the use of a lambda:
#
# require 'observer'
#
# class Ticker
# include Observable
#
# def run
# # logic to retrieve the price (here 77.0)
# changed
# notify_observers(77.0)
# end
# end
#
# ticker = Ticker.new
# warner = ->(price) { puts "New price received: #{price}" }
# ticker.add_observer(warner, :call)
# ticker.run
module Observable
VERSION = "0.1.1"
#
# Add +observer+ as an observer on this object. So that it will receive
# notifications.
#
# +observer+:: the object that will be notified of changes.
# +func+:: Symbol naming the method that will be called when this Observable
# has changes.
#
# This method must return true for +observer.respond_to?+ and will
# receive <tt>*arg</tt> when #notify_observers is called, where
# <tt>*arg</tt> is the value passed to #notify_observers by this
# Observable
def add_observer(observer, func=:update)
@observer_peers = {} unless defined? @observer_peers
unless observer.respond_to? func
raise NoMethodError, "observer does not respond to `#{func}'"
end
@observer_peers[observer] = func
end
#
# Remove +observer+ as an observer on this object so that it will no longer
# receive notifications.
#
# +observer+:: An observer of this Observable
def delete_observer(observer)
@observer_peers.delete observer if defined? @observer_peers
end
#
# Remove all observers associated with this object.
#
def delete_observers
@observer_peers.clear if defined? @observer_peers
end
#
# Return the number of observers associated with this object.
#
def count_observers
if defined? @observer_peers
@observer_peers.size
else
0
end
end
#
# Set the changed state of this object. Notifications will be sent only if
# the changed +state+ is +true+.
#
# +state+:: Boolean indicating the changed state of this Observable.
#
def changed(state=true)
@observer_state = state
end
#
# Returns true if this object's state has been changed since the last
# #notify_observers call.
#
def changed?
if defined? @observer_state and @observer_state
true
else
false
end
end
#
# Notify observers of a change in state *if* this object's changed state is
# +true+.
#
# This will invoke the method named in #add_observer, passing <tt>*arg</tt>.
# The changed state is then set to +false+.
#
# <tt>*arg</tt>:: Any arguments to pass to the observers.
def notify_observers(*arg)
if defined? @observer_state and @observer_state
if defined? @observer_peers
@observer_peers.each do |k, v|
k.__send__(v, *arg)
end
end
@observer_state = false
end
end
end
share/ruby/syslog/logger.rb 0000644 00000013440 15173547333 0011770 0 ustar 00 # 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
share/ruby/cgi/util.rb 0000644 00000017500 15173547333 0010711 0 ustar 00 # frozen_string_literal: true
class CGI
module Util; end
include Util
extend Util
end
module CGI::Util
@@accept_charset = Encoding::UTF_8 unless defined?(@@accept_charset)
# URL-encode a string into application/x-www-form-urlencoded.
# Space characters (+" "+) are encoded with plus signs (+"+"+)
# url_encoded_string = CGI.escape("'Stop!' said Fred")
# # => "%27Stop%21%27+said+Fred"
def escape(string)
encoding = string.encoding
buffer = string.b
buffer.gsub!(/([^ a-zA-Z0-9_.\-~]+)/) do |m|
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
end
buffer.tr!(' ', '+')
buffer.force_encoding(encoding)
end
# URL-decode an application/x-www-form-urlencoded string with encoding(optional).
# string = CGI.unescape("%27Stop%21%27+said+Fred")
# # => "'Stop!' said Fred"
def unescape(string, encoding = @@accept_charset)
str = string.tr('+', ' ')
str = str.b
str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
[m.delete('%')].pack('H*')
end
str.force_encoding(encoding)
str.valid_encoding? ? str : str.force_encoding(string.encoding)
end
# URL-encode a string following RFC 3986
# Space characters (+" "+) are encoded with (+"%20"+)
# url_encoded_string = CGI.escape("'Stop!' said Fred")
# # => "%27Stop%21%27%20said%20Fred"
def escapeURIComponent(string)
encoding = string.encoding
buffer = string.b
buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m|
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
end
buffer.force_encoding(encoding)
end
# URL-decode a string following RFC 3986 with encoding(optional).
# string = CGI.unescape("%27Stop%21%27+said%20Fred")
# # => "'Stop!'+said Fred"
def unescapeURIComponent(string, encoding = @@accept_charset)
str = string.b
str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
[m.delete('%')].pack('H*')
end
str.force_encoding(encoding)
str.valid_encoding? ? str : str.force_encoding(string.encoding)
end
# The set of special characters and their escaped values
TABLE_FOR_ESCAPE_HTML__ = {
"'" => ''',
'&' => '&',
'"' => '"',
'<' => '<',
'>' => '>',
}
# Escape special characters in HTML, namely '&\"<>
# CGI.escapeHTML('Usage: foo "bar" <baz>')
# # => "Usage: foo "bar" <baz>"
def escapeHTML(string)
enc = string.encoding
unless enc.ascii_compatible?
if enc.dummy?
origenc = enc
enc = Encoding::Converter.asciicompat_encoding(enc)
string = enc ? string.encode(enc) : string.b
end
table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}]
string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table)
string.encode!(origenc) if origenc
string
else
string = string.b
string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
string.force_encoding(enc)
end
end
begin
require 'cgi/escape'
rescue LoadError
end
# Unescape a string that has been HTML-escaped
# CGI.unescapeHTML("Usage: foo "bar" <baz>")
# # => "Usage: foo \"bar\" <baz>"
def unescapeHTML(string)
enc = string.encoding
unless enc.ascii_compatible?
if enc.dummy?
origenc = enc
enc = Encoding::Converter.asciicompat_encoding(enc)
string = enc ? string.encode(enc) : string.b
end
string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
case $1.encode(Encoding::US_ASCII)
when 'apos' then "'".encode(enc)
when 'amp' then '&'.encode(enc)
when 'quot' then '"'.encode(enc)
when 'gt' then '>'.encode(enc)
when 'lt' then '<'.encode(enc)
when /\A#0*(\d+)\z/ then $1.to_i.chr(enc)
when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
end
end
string.encode!(origenc) if origenc
return string
end
return string unless string.include? '&'
charlimit = case enc
when Encoding::UTF_8; 0x10ffff
when Encoding::ISO_8859_1; 256
else 128
end
string = string.b
string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do
match = $1.dup
case match
when 'apos' then "'"
when 'amp' then '&'
when 'quot' then '"'
when 'gt' then '>'
when 'lt' then '<'
when /\A#0*(\d+)\z/
n = $1.to_i
if n < charlimit
n.chr(enc)
else
"&##{$1};"
end
when /\A#x([0-9a-f]+)\z/i
n = $1.hex
if n < charlimit
n.chr(enc)
else
"&#x#{$1};"
end
else
"&#{match};"
end
end
string.force_encoding enc
end
# Synonym for CGI.escapeHTML(str)
alias escape_html escapeHTML
# Synonym for CGI.unescapeHTML(str)
alias unescape_html unescapeHTML
# Escape only the tags of certain HTML elements in +string+.
#
# Takes an element or elements or array of elements. Each element
# is specified by the name of the element, without angle brackets.
# This matches both the start and the end tag of that element.
# The attribute list of the open tag will also be escaped (for
# instance, the double-quotes surrounding attribute values).
#
# print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
# # "<BR><A HREF="url"></A>"
#
# print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
# # "<BR><A HREF="url"></A>"
def escapeElement(string, *elements)
elements = elements[0] if elements[0].kind_of?(Array)
unless elements.empty?
string.gsub(/<\/?(?:#{elements.join("|")})\b[^<>]*+>?/im) do
CGI.escapeHTML($&)
end
else
string
end
end
# Undo escaping such as that done by CGI.escapeElement()
#
# print CGI.unescapeElement(
# CGI.escapeHTML('<BR><A HREF="url"></A>'), "A", "IMG")
# # "<BR><A HREF="url"></A>"
#
# print CGI.unescapeElement(
# CGI.escapeHTML('<BR><A HREF="url"></A>'), ["A", "IMG"])
# # "<BR><A HREF="url"></A>"
def unescapeElement(string, *elements)
elements = elements[0] if elements[0].kind_of?(Array)
unless elements.empty?
string.gsub(/<\/?(?:#{elements.join("|")})\b(?>[^&]+|&(?![gl]t;)\w+;)*(?:>)?/im) do
unescapeHTML($&)
end
else
string
end
end
# Synonym for CGI.escapeElement(str)
alias escape_element escapeElement
# Synonym for CGI.unescapeElement(str)
alias unescape_element unescapeElement
# Format a +Time+ object as a String using the format specified by RFC 1123.
#
# CGI.rfc1123_date(Time.now)
# # Sat, 01 Jan 2000 00:00:00 GMT
def rfc1123_date(time)
time.getgm.strftime("%a, %d %b %Y %T GMT")
end
# Prettify (indent) an HTML string.
#
# +string+ is the HTML string to indent. +shift+ is the indentation
# unit to use; it defaults to two spaces.
#
# print CGI.pretty("<HTML><BODY></BODY></HTML>")
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
# print CGI.pretty("<HTML><BODY></BODY></HTML>", "\t")
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
def pretty(string, shift = " ")
lines = string.gsub(/(?!\A)<.*?>/m, "\n\\0").gsub(/<.*?>(?!\n)/m, "\\0\n")
end_pos = 0
while end_pos = lines.index(/^<\/(\w+)/, end_pos)
element = $1.dup
start_pos = lines.rindex(/^\s*<#{element}/i, end_pos)
lines[start_pos ... end_pos] = "__" + lines[start_pos ... end_pos].gsub(/\n(?!\z)/, "\n" + shift) + "__"
end
lines.gsub(/^((?:#{Regexp::quote(shift)})*)__(?=<\/?\w)/, '\1')
end
alias h escapeHTML
end
share/ruby/cgi/cookie.rb 0000644 00000015116 15173547333 0011206 0 ustar 00 # frozen_string_literal: true
require_relative 'util'
class CGI
# Class representing an HTTP cookie.
#
# In addition to its specific fields and methods, a Cookie instance
# is a delegator to the array of its values.
#
# See RFC 2965.
#
# == Examples of use
# cookie1 = CGI::Cookie.new("name", "value1", "value2", ...)
# cookie1 = CGI::Cookie.new("name" => "name", "value" => "value")
# cookie1 = CGI::Cookie.new('name' => 'name',
# 'value' => ['value1', 'value2', ...],
# 'path' => 'path', # optional
# 'domain' => 'domain', # optional
# 'expires' => Time.now, # optional
# 'secure' => true, # optional
# 'httponly' => true # optional
# )
#
# cgi.out("cookie" => [cookie1, cookie2]) { "string" }
#
# name = cookie1.name
# values = cookie1.value
# path = cookie1.path
# domain = cookie1.domain
# expires = cookie1.expires
# secure = cookie1.secure
# httponly = cookie1.httponly
#
# cookie1.name = 'name'
# cookie1.value = ['value1', 'value2', ...]
# cookie1.path = 'path'
# cookie1.domain = 'domain'
# cookie1.expires = Time.now + 30
# cookie1.secure = true
# cookie1.httponly = true
class Cookie < Array
@@accept_charset="UTF-8" unless defined?(@@accept_charset)
TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z"
PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z"
DOMAIN_VALUE_RE = %r"\A\.?(?<label>(?!-)[-A-Za-z0-9]+(?<!-))(?:\.\g<label>)*\z"
# Create a new CGI::Cookie object.
#
# :call-seq:
# Cookie.new(name_string,*value)
# Cookie.new(options_hash)
#
# +name_string+::
# The name of the cookie; in this form, there is no #domain or
# #expiration. The #path is gleaned from the +SCRIPT_NAME+ environment
# variable, and #secure is false.
# <tt>*value</tt>::
# value or list of values of the cookie
# +options_hash+::
# A Hash of options to initialize this Cookie. Possible options are:
#
# name:: the name of the cookie. Required.
# value:: the cookie's value or list of values.
# path:: the path for which this cookie applies. Defaults to
# the value of the +SCRIPT_NAME+ environment variable.
# domain:: the domain for which this cookie applies.
# expires:: the time at which this cookie expires, as a +Time+ object.
# secure:: whether this cookie is a secure cookie or not (default to
# false). Secure cookies are only transmitted to HTTPS
# servers.
# httponly:: whether this cookie is a HttpOnly cookie or not (default to
# false). HttpOnly cookies are not available to javascript.
#
# These keywords correspond to attributes of the cookie object.
def initialize(name = "", *value)
@domain = nil
@expires = nil
if name.kind_of?(String)
self.name = name
self.path = (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
@secure = false
@httponly = false
return super(value)
end
options = name
unless options.has_key?("name")
raise ArgumentError, "`name' required"
end
self.name = options["name"]
value = Array(options["value"])
# simple support for IE
self.path = options["path"] || (%r|\A(.*/)| =~ ENV["SCRIPT_NAME"] ? $1 : "")
self.domain = options["domain"]
@expires = options["expires"]
@secure = options["secure"] == true
@httponly = options["httponly"] == true
super(value)
end
# Name of this cookie, as a +String+
attr_reader :name
# Set name of this cookie
def name=(str)
if str and !TOKEN_RE.match?(str)
raise ArgumentError, "invalid name: #{str.dump}"
end
@name = str
end
# Path for which this cookie applies, as a +String+
attr_reader :path
# Set path for which this cookie applies
def path=(str)
if str and !PATH_VALUE_RE.match?(str)
raise ArgumentError, "invalid path: #{str.dump}"
end
@path = str
end
# Domain for which this cookie applies, as a +String+
attr_reader :domain
# Set domain for which this cookie applies
def domain=(str)
if str and ((str = str.b).bytesize > 255 or !DOMAIN_VALUE_RE.match?(str))
raise ArgumentError, "invalid domain: #{str.dump}"
end
@domain = str
end
# Time at which this cookie expires, as a +Time+
attr_accessor :expires
# True if this cookie is secure; false otherwise
attr_reader :secure
# True if this cookie is httponly; false otherwise
attr_reader :httponly
# Returns the value or list of values for this cookie.
def value
self
end
# Replaces the value of this cookie with a new value or list of values.
def value=(val)
replace(Array(val))
end
# Set whether the Cookie is a secure cookie or not.
#
# +val+ must be a boolean.
def secure=(val)
@secure = val if val == true or val == false
@secure
end
# Set whether the Cookie is a httponly cookie or not.
#
# +val+ must be a boolean.
def httponly=(val)
@httponly = !!val
end
# Convert the Cookie to its string representation.
def to_s
val = collect{|v| CGI.escape(v) }.join("&")
buf = "#{@name}=#{val}".dup
buf << "; domain=#{@domain}" if @domain
buf << "; path=#{@path}" if @path
buf << "; expires=#{CGI.rfc1123_date(@expires)}" if @expires
buf << "; secure" if @secure
buf << "; HttpOnly" if @httponly
buf
end
# Parse a raw cookie string into a hash of cookie-name=>Cookie
# pairs.
#
# cookies = CGI::Cookie.parse("raw_cookie_string")
# # { "name1" => cookie1, "name2" => cookie2, ... }
#
def self.parse(raw_cookie)
cookies = Hash.new([])
return cookies unless raw_cookie
raw_cookie.split(/;\s?/).each do |pairs|
name, values = pairs.split('=',2)
next unless name and values
values ||= ""
values = values.split('&').collect{|v| CGI.unescape(v,@@accept_charset) }
if cookies.has_key?(name)
cookies[name].concat(values)
else
cookies[name] = Cookie.new(name, *values)
end
end
cookies
end
# A summary of cookie string.
def inspect
"#<CGI::Cookie: #{self.to_s.inspect}>"
end
end # class Cookie
end
share/ruby/cgi/html.rb 0000644 00000104217 15173547333 0010702 0 ustar 00 # frozen_string_literal: true
class CGI
# Base module for HTML-generation mixins.
#
# Provides methods for code generation for tags following
# the various DTD element types.
module TagMaker # :nodoc:
# Generate code for an element with required start and end tags.
#
# - -
def nn_element(element, attributes = {})
s = nOE_element(element, attributes)
if block_given?
s << yield.to_s
end
s << "</#{element.upcase}>"
end
def nn_element_def(attributes = {}, &block)
nn_element(__callee__, attributes, &block)
end
# Generate code for an empty element.
#
# - O EMPTY
def nOE_element(element, attributes = {})
attributes={attributes=>nil} if attributes.kind_of?(String)
s = "<#{element.upcase}".dup
attributes.each do|name, value|
next unless value
s << " "
s << CGI.escapeHTML(name.to_s)
if value != true
s << '="'
s << CGI.escapeHTML(value.to_s)
s << '"'
end
end
s << ">"
end
def nOE_element_def(attributes = {}, &block)
nOE_element(__callee__, attributes, &block)
end
# Generate code for an element for which the end (and possibly the
# start) tag is optional.
#
# O O or - O
def nO_element(element, attributes = {})
s = nOE_element(element, attributes)
if block_given?
s << yield.to_s
s << "</#{element.upcase}>"
end
s
end
def nO_element_def(attributes = {}, &block)
nO_element(__callee__, attributes, &block)
end
end # TagMaker
# Mixin module providing HTML generation methods.
#
# For example,
# cgi.a("http://www.example.com") { "Example" }
# # => "<A HREF=\"http://www.example.com\">Example</A>"
#
# Modules Html3, Html4, etc., contain more basic HTML-generation methods
# (+#title+, +#h1+, etc.).
#
# See class CGI for a detailed example.
#
module HtmlExtension
# Generate an Anchor element as a string.
#
# +href+ can either be a string, giving the URL
# for the HREF attribute, or it can be a hash of
# the element's attributes.
#
# The body of the element is the string returned by the no-argument
# block passed in.
#
# a("http://www.example.com") { "Example" }
# # => "<A HREF=\"http://www.example.com\">Example</A>"
#
# a("HREF" => "http://www.example.com", "TARGET" => "_top") { "Example" }
# # => "<A HREF=\"http://www.example.com\" TARGET=\"_top\">Example</A>"
#
def a(href = "") # :yield:
attributes = if href.kind_of?(String)
{ "HREF" => href }
else
href
end
super(attributes)
end
# Generate a Document Base URI element as a String.
#
# +href+ can either by a string, giving the base URL for the HREF
# attribute, or it can be a has of the element's attributes.
#
# The passed-in no-argument block is ignored.
#
# base("http://www.example.com/cgi")
# # => "<BASE HREF=\"http://www.example.com/cgi\">"
def base(href = "") # :yield:
attributes = if href.kind_of?(String)
{ "HREF" => href }
else
href
end
super(attributes)
end
# Generate a BlockQuote element as a string.
#
# +cite+ can either be a string, give the URI for the source of
# the quoted text, or a hash, giving all attributes of the element,
# or it can be omitted, in which case the element has no attributes.
#
# The body is provided by the passed-in no-argument block
#
# blockquote("http://www.example.com/quotes/foo.html") { "Foo!" }
# #=> "<BLOCKQUOTE CITE=\"http://www.example.com/quotes/foo.html\">Foo!</BLOCKQUOTE>
def blockquote(cite = {}) # :yield:
attributes = if cite.kind_of?(String)
{ "CITE" => cite }
else
cite
end
super(attributes)
end
# Generate a Table Caption element as a string.
#
# +align+ can be a string, giving the alignment of the caption
# (one of top, bottom, left, or right). It can be a hash of
# all the attributes of the element. Or it can be omitted.
#
# The body of the element is provided by the passed-in no-argument block.
#
# caption("left") { "Capital Cities" }
# # => <CAPTION ALIGN=\"left\">Capital Cities</CAPTION>
def caption(align = {}) # :yield:
attributes = if align.kind_of?(String)
{ "ALIGN" => align }
else
align
end
super(attributes)
end
# Generate a Checkbox Input element as a string.
#
# The attributes of the element can be specified as three arguments,
# +name+, +value+, and +checked+. +checked+ is a boolean value;
# if true, the CHECKED attribute will be included in the element.
#
# Alternatively, the attributes can be specified as a hash.
#
# checkbox("name")
# # = checkbox("NAME" => "name")
#
# checkbox("name", "value")
# # = checkbox("NAME" => "name", "VALUE" => "value")
#
# checkbox("name", "value", true)
# # = checkbox("NAME" => "name", "VALUE" => "value", "CHECKED" => true)
def checkbox(name = "", value = nil, checked = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "checkbox", "NAME" => name,
"VALUE" => value, "CHECKED" => checked }
else
name["TYPE"] = "checkbox"
name
end
input(attributes)
end
# Generate a sequence of checkbox elements, as a String.
#
# The checkboxes will all have the same +name+ attribute.
# Each checkbox is followed by a label.
# There will be one checkbox for each value. Each value
# can be specified as a String, which will be used both
# as the value of the VALUE attribute and as the label
# for that checkbox. A single-element array has the
# same effect.
#
# Each value can also be specified as a three-element array.
# The first element is the VALUE attribute; the second is the
# label; and the third is a boolean specifying whether this
# checkbox is CHECKED.
#
# Each value can also be specified as a two-element
# array, by omitting either the value element (defaults
# to the same as the label), or the boolean checked element
# (defaults to false).
#
# checkbox_group("name", "foo", "bar", "baz")
# # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
# # <INPUT TYPE="checkbox" NAME="name" VALUE="bar">bar
# # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
#
# checkbox_group("name", ["foo"], ["bar", true], "baz")
# # <INPUT TYPE="checkbox" NAME="name" VALUE="foo">foo
# # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="bar">bar
# # <INPUT TYPE="checkbox" NAME="name" VALUE="baz">baz
#
# checkbox_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
# # <INPUT TYPE="checkbox" NAME="name" VALUE="1">Foo
# # <INPUT TYPE="checkbox" CHECKED NAME="name" VALUE="2">Bar
# # <INPUT TYPE="checkbox" NAME="name" VALUE="Baz">Baz
#
# checkbox_group("NAME" => "name",
# "VALUES" => ["foo", "bar", "baz"])
#
# checkbox_group("NAME" => "name",
# "VALUES" => [["foo"], ["bar", true], "baz"])
#
# checkbox_group("NAME" => "name",
# "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
def checkbox_group(name = "", *values)
if name.kind_of?(Hash)
values = name["VALUES"]
name = name["NAME"]
end
values.collect{|value|
if value.kind_of?(String)
checkbox(name, value) + value
else
if value[-1] == true || value[-1] == false
checkbox(name, value[0], value[-1]) +
value[-2]
else
checkbox(name, value[0]) +
value[-1]
end
end
}.join
end
# Generate an File Upload Input element as a string.
#
# The attributes of the element can be specified as three arguments,
# +name+, +size+, and +maxlength+. +maxlength+ is the maximum length
# of the file's _name_, not of the file's _contents_.
#
# Alternatively, the attributes can be specified as a hash.
#
# See #multipart_form() for forms that include file uploads.
#
# file_field("name")
# # <INPUT TYPE="file" NAME="name" SIZE="20">
#
# file_field("name", 40)
# # <INPUT TYPE="file" NAME="name" SIZE="40">
#
# file_field("name", 40, 100)
# # <INPUT TYPE="file" NAME="name" SIZE="40" MAXLENGTH="100">
#
# file_field("NAME" => "name", "SIZE" => 40)
# # <INPUT TYPE="file" NAME="name" SIZE="40">
def file_field(name = "", size = 20, maxlength = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "file", "NAME" => name,
"SIZE" => size.to_s }
else
name["TYPE"] = "file"
name
end
attributes["MAXLENGTH"] = maxlength.to_s if maxlength
input(attributes)
end
# Generate a Form element as a string.
#
# +method+ should be either "get" or "post", and defaults to the latter.
# +action+ defaults to the current CGI script name. +enctype+
# defaults to "application/x-www-form-urlencoded".
#
# Alternatively, the attributes can be specified as a hash.
#
# See also #multipart_form() for forms that include file uploads.
#
# form{ "string" }
# # <FORM METHOD="post" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
#
# form("get") { "string" }
# # <FORM METHOD="get" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
#
# form("get", "url") { "string" }
# # <FORM METHOD="get" ACTION="url" ENCTYPE="application/x-www-form-urlencoded">string</FORM>
#
# form("METHOD" => "post", "ENCTYPE" => "enctype") { "string" }
# # <FORM METHOD="post" ENCTYPE="enctype">string</FORM>
def form(method = "post", action = script_name, enctype = "application/x-www-form-urlencoded")
attributes = if method.kind_of?(String)
{ "METHOD" => method, "ACTION" => action,
"ENCTYPE" => enctype }
else
unless method.has_key?("METHOD")
method["METHOD"] = "post"
end
unless method.has_key?("ENCTYPE")
method["ENCTYPE"] = enctype
end
method
end
if block_given?
body = yield
else
body = ""
end
if @output_hidden
body << @output_hidden.collect{|k,v|
"<INPUT TYPE=\"HIDDEN\" NAME=\"#{k}\" VALUE=\"#{v}\">"
}.join
end
super(attributes){body}
end
# Generate a Hidden Input element as a string.
#
# The attributes of the element can be specified as two arguments,
# +name+ and +value+.
#
# Alternatively, the attributes can be specified as a hash.
#
# hidden("name")
# # <INPUT TYPE="hidden" NAME="name">
#
# hidden("name", "value")
# # <INPUT TYPE="hidden" NAME="name" VALUE="value">
#
# hidden("NAME" => "name", "VALUE" => "reset", "ID" => "foo")
# # <INPUT TYPE="hidden" NAME="name" VALUE="value" ID="foo">
def hidden(name = "", value = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "hidden", "NAME" => name, "VALUE" => value }
else
name["TYPE"] = "hidden"
name
end
input(attributes)
end
# Generate a top-level HTML element as a string.
#
# The attributes of the element are specified as a hash. The
# pseudo-attribute "PRETTY" can be used to specify that the generated
# HTML string should be indented. "PRETTY" can also be specified as
# a string as the sole argument to this method. The pseudo-attribute
# "DOCTYPE", if given, is used as the leading DOCTYPE SGML tag; it
# should include the entire text of this tag, including angle brackets.
#
# The body of the html element is supplied as a block.
#
# html{ "string" }
# # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML>string</HTML>
#
# html("LANG" => "ja") { "string" }
# # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML LANG="ja">string</HTML>
#
# html("DOCTYPE" => false) { "string" }
# # <HTML>string</HTML>
#
# html("DOCTYPE" => '<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">') { "string" }
# # <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"><HTML>string</HTML>
#
# html("PRETTY" => " ") { "<BODY></BODY>" }
# # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
# html("PRETTY" => "\t") { "<BODY></BODY>" }
# # <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
# # <HTML>
# # <BODY>
# # </BODY>
# # </HTML>
#
# html("PRETTY") { "<BODY></BODY>" }
# # = html("PRETTY" => " ") { "<BODY></BODY>" }
#
# html(if $VERBOSE then "PRETTY" end) { "HTML string" }
#
def html(attributes = {}) # :yield:
if nil == attributes
attributes = {}
elsif "PRETTY" == attributes
attributes = { "PRETTY" => true }
end
pretty = attributes.delete("PRETTY")
pretty = " " if true == pretty
buf = "".dup
if attributes.has_key?("DOCTYPE")
if attributes["DOCTYPE"]
buf << attributes.delete("DOCTYPE")
else
attributes.delete("DOCTYPE")
end
else
buf << doctype
end
buf << super(attributes)
if pretty
CGI.pretty(buf, pretty)
else
buf
end
end
# Generate an Image Button Input element as a string.
#
# +src+ is the URL of the image to use for the button. +name+
# is the input name. +alt+ is the alternative text for the image.
#
# Alternatively, the attributes can be specified as a hash.
#
# image_button("url")
# # <INPUT TYPE="image" SRC="url">
#
# image_button("url", "name", "string")
# # <INPUT TYPE="image" SRC="url" NAME="name" ALT="string">
#
# image_button("SRC" => "url", "ALT" => "string")
# # <INPUT TYPE="image" SRC="url" ALT="string">
def image_button(src = "", name = nil, alt = nil)
attributes = if src.kind_of?(String)
{ "TYPE" => "image", "SRC" => src, "NAME" => name,
"ALT" => alt }
else
src["TYPE"] = "image"
src["SRC"] ||= ""
src
end
input(attributes)
end
# Generate an Image element as a string.
#
# +src+ is the URL of the image. +alt+ is the alternative text for
# the image. +width+ is the width of the image, and +height+ is
# its height.
#
# Alternatively, the attributes can be specified as a hash.
#
# img("src", "alt", 100, 50)
# # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
#
# img("SRC" => "src", "ALT" => "alt", "WIDTH" => 100, "HEIGHT" => 50)
# # <IMG SRC="src" ALT="alt" WIDTH="100" HEIGHT="50">
def img(src = "", alt = "", width = nil, height = nil)
attributes = if src.kind_of?(String)
{ "SRC" => src, "ALT" => alt }
else
src
end
attributes["WIDTH"] = width.to_s if width
attributes["HEIGHT"] = height.to_s if height
super(attributes)
end
# Generate a Form element with multipart encoding as a String.
#
# Multipart encoding is used for forms that include file uploads.
#
# +action+ is the action to perform. +enctype+ is the encoding
# type, which defaults to "multipart/form-data".
#
# Alternatively, the attributes can be specified as a hash.
#
# multipart_form{ "string" }
# # <FORM METHOD="post" ENCTYPE="multipart/form-data">string</FORM>
#
# multipart_form("url") { "string" }
# # <FORM METHOD="post" ACTION="url" ENCTYPE="multipart/form-data">string</FORM>
def multipart_form(action = nil, enctype = "multipart/form-data")
attributes = if action == nil
{ "METHOD" => "post", "ENCTYPE" => enctype }
elsif action.kind_of?(String)
{ "METHOD" => "post", "ACTION" => action,
"ENCTYPE" => enctype }
else
unless action.has_key?("METHOD")
action["METHOD"] = "post"
end
unless action.has_key?("ENCTYPE")
action["ENCTYPE"] = enctype
end
action
end
if block_given?
form(attributes){ yield }
else
form(attributes)
end
end
# Generate a Password Input element as a string.
#
# +name+ is the name of the input field. +value+ is its default
# value. +size+ is the size of the input field display. +maxlength+
# is the maximum length of the inputted password.
#
# Alternatively, attributes can be specified as a hash.
#
# password_field("name")
# # <INPUT TYPE="password" NAME="name" SIZE="40">
#
# password_field("name", "value")
# # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="40">
#
# password_field("password", "value", 80, 200)
# # <INPUT TYPE="password" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
#
# password_field("NAME" => "name", "VALUE" => "value")
# # <INPUT TYPE="password" NAME="name" VALUE="value">
def password_field(name = "", value = nil, size = 40, maxlength = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "password", "NAME" => name,
"VALUE" => value, "SIZE" => size.to_s }
else
name["TYPE"] = "password"
name
end
attributes["MAXLENGTH"] = maxlength.to_s if maxlength
input(attributes)
end
# Generate a Select element as a string.
#
# +name+ is the name of the element. The +values+ are the options that
# can be selected from the Select menu. Each value can be a String or
# a one, two, or three-element Array. If a String or a one-element
# Array, this is both the value of that option and the text displayed for
# it. If a three-element Array, the elements are the option value, displayed
# text, and a boolean value specifying whether this option starts as selected.
# The two-element version omits either the option value (defaults to the same
# as the display text) or the boolean selected specifier (defaults to false).
#
# The attributes and options can also be specified as a hash. In this
# case, options are specified as an array of values as described above,
# with the hash key of "VALUES".
#
# popup_menu("name", "foo", "bar", "baz")
# # <SELECT NAME="name">
# # <OPTION VALUE="foo">foo</OPTION>
# # <OPTION VALUE="bar">bar</OPTION>
# # <OPTION VALUE="baz">baz</OPTION>
# # </SELECT>
#
# popup_menu("name", ["foo"], ["bar", true], "baz")
# # <SELECT NAME="name">
# # <OPTION VALUE="foo">foo</OPTION>
# # <OPTION VALUE="bar" SELECTED>bar</OPTION>
# # <OPTION VALUE="baz">baz</OPTION>
# # </SELECT>
#
# popup_menu("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
# # <SELECT NAME="name">
# # <OPTION VALUE="1">Foo</OPTION>
# # <OPTION SELECTED VALUE="2">Bar</OPTION>
# # <OPTION VALUE="Baz">Baz</OPTION>
# # </SELECT>
#
# popup_menu("NAME" => "name", "SIZE" => 2, "MULTIPLE" => true,
# "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
# # <SELECT NAME="name" MULTIPLE SIZE="2">
# # <OPTION VALUE="1">Foo</OPTION>
# # <OPTION SELECTED VALUE="2">Bar</OPTION>
# # <OPTION VALUE="Baz">Baz</OPTION>
# # </SELECT>
def popup_menu(name = "", *values)
if name.kind_of?(Hash)
values = name["VALUES"]
size = name["SIZE"].to_s if name["SIZE"]
multiple = name["MULTIPLE"]
name = name["NAME"]
else
size = nil
multiple = nil
end
select({ "NAME" => name, "SIZE" => size,
"MULTIPLE" => multiple }){
values.collect{|value|
if value.kind_of?(String)
option({ "VALUE" => value }){ value }
else
if value[value.size - 1] == true
option({ "VALUE" => value[0], "SELECTED" => true }){
value[value.size - 2]
}
else
option({ "VALUE" => value[0] }){
value[value.size - 1]
}
end
end
}.join
}
end
# Generates a radio-button Input element.
#
# +name+ is the name of the input field. +value+ is the value of
# the field if checked. +checked+ specifies whether the field
# starts off checked.
#
# Alternatively, the attributes can be specified as a hash.
#
# radio_button("name", "value")
# # <INPUT TYPE="radio" NAME="name" VALUE="value">
#
# radio_button("name", "value", true)
# # <INPUT TYPE="radio" NAME="name" VALUE="value" CHECKED>
#
# radio_button("NAME" => "name", "VALUE" => "value", "ID" => "foo")
# # <INPUT TYPE="radio" NAME="name" VALUE="value" ID="foo">
def radio_button(name = "", value = nil, checked = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "radio", "NAME" => name,
"VALUE" => value, "CHECKED" => checked }
else
name["TYPE"] = "radio"
name
end
input(attributes)
end
# Generate a sequence of radio button Input elements, as a String.
#
# This works the same as #checkbox_group(). However, it is not valid
# to have more than one radiobutton in a group checked.
#
# radio_group("name", "foo", "bar", "baz")
# # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
# # <INPUT TYPE="radio" NAME="name" VALUE="bar">bar
# # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
#
# radio_group("name", ["foo"], ["bar", true], "baz")
# # <INPUT TYPE="radio" NAME="name" VALUE="foo">foo
# # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="bar">bar
# # <INPUT TYPE="radio" NAME="name" VALUE="baz">baz
#
# radio_group("name", ["1", "Foo"], ["2", "Bar", true], "Baz")
# # <INPUT TYPE="radio" NAME="name" VALUE="1">Foo
# # <INPUT TYPE="radio" CHECKED NAME="name" VALUE="2">Bar
# # <INPUT TYPE="radio" NAME="name" VALUE="Baz">Baz
#
# radio_group("NAME" => "name",
# "VALUES" => ["foo", "bar", "baz"])
#
# radio_group("NAME" => "name",
# "VALUES" => [["foo"], ["bar", true], "baz"])
#
# radio_group("NAME" => "name",
# "VALUES" => [["1", "Foo"], ["2", "Bar", true], "Baz"])
def radio_group(name = "", *values)
if name.kind_of?(Hash)
values = name["VALUES"]
name = name["NAME"]
end
values.collect{|value|
if value.kind_of?(String)
radio_button(name, value) + value
else
if value[-1] == true || value[-1] == false
radio_button(name, value[0], value[-1]) +
value[-2]
else
radio_button(name, value[0]) +
value[-1]
end
end
}.join
end
# Generate a reset button Input element, as a String.
#
# This resets the values on a form to their initial values. +value+
# is the text displayed on the button. +name+ is the name of this button.
#
# Alternatively, the attributes can be specified as a hash.
#
# reset
# # <INPUT TYPE="reset">
#
# reset("reset")
# # <INPUT TYPE="reset" VALUE="reset">
#
# reset("VALUE" => "reset", "ID" => "foo")
# # <INPUT TYPE="reset" VALUE="reset" ID="foo">
def reset(value = nil, name = nil)
attributes = if (not value) or value.kind_of?(String)
{ "TYPE" => "reset", "VALUE" => value, "NAME" => name }
else
value["TYPE"] = "reset"
value
end
input(attributes)
end
alias scrolling_list popup_menu
# Generate a submit button Input element, as a String.
#
# +value+ is the text to display on the button. +name+ is the name
# of the input.
#
# Alternatively, the attributes can be specified as a hash.
#
# submit
# # <INPUT TYPE="submit">
#
# submit("ok")
# # <INPUT TYPE="submit" VALUE="ok">
#
# submit("ok", "button1")
# # <INPUT TYPE="submit" VALUE="ok" NAME="button1">
#
# submit("VALUE" => "ok", "NAME" => "button1", "ID" => "foo")
# # <INPUT TYPE="submit" VALUE="ok" NAME="button1" ID="foo">
def submit(value = nil, name = nil)
attributes = if (not value) or value.kind_of?(String)
{ "TYPE" => "submit", "VALUE" => value, "NAME" => name }
else
value["TYPE"] = "submit"
value
end
input(attributes)
end
# Generate a text field Input element, as a String.
#
# +name+ is the name of the input field. +value+ is its initial
# value. +size+ is the size of the input area. +maxlength+
# is the maximum length of input accepted.
#
# Alternatively, the attributes can be specified as a hash.
#
# text_field("name")
# # <INPUT TYPE="text" NAME="name" SIZE="40">
#
# text_field("name", "value")
# # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="40">
#
# text_field("name", "value", 80)
# # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80">
#
# text_field("name", "value", 80, 200)
# # <INPUT TYPE="text" NAME="name" VALUE="value" SIZE="80" MAXLENGTH="200">
#
# text_field("NAME" => "name", "VALUE" => "value")
# # <INPUT TYPE="text" NAME="name" VALUE="value">
def text_field(name = "", value = nil, size = 40, maxlength = nil)
attributes = if name.kind_of?(String)
{ "TYPE" => "text", "NAME" => name, "VALUE" => value,
"SIZE" => size.to_s }
else
name["TYPE"] = "text"
name
end
attributes["MAXLENGTH"] = maxlength.to_s if maxlength
input(attributes)
end
# Generate a TextArea element, as a String.
#
# +name+ is the name of the textarea. +cols+ is the number of
# columns and +rows+ is the number of rows in the display.
#
# Alternatively, the attributes can be specified as a hash.
#
# The body is provided by the passed-in no-argument block
#
# textarea("name")
# # = textarea("NAME" => "name", "COLS" => 70, "ROWS" => 10)
#
# textarea("name", 40, 5)
# # = textarea("NAME" => "name", "COLS" => 40, "ROWS" => 5)
def textarea(name = "", cols = 70, rows = 10) # :yield:
attributes = if name.kind_of?(String)
{ "NAME" => name, "COLS" => cols.to_s,
"ROWS" => rows.to_s }
else
name
end
super(attributes)
end
end # HtmlExtension
# Mixin module for HTML version 3 generation methods.
module Html3 # :nodoc:
include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
%|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">|
end
instance_method(:nn_element_def).tap do |m|
# - -
for element in %w[ A TT I B U STRIKE BIG SMALL SUB SUP EM STRONG
DFN CODE SAMP KBD VAR CITE FONT ADDRESS DIV CENTER MAP
APPLET PRE XMP LISTING DL OL UL DIR MENU SELECT TABLE TITLE
STYLE SCRIPT H1 H2 H3 H4 H5 H6 TEXTAREA FORM BLOCKQUOTE
CAPTION ]
define_method(element.downcase, m)
end
end
instance_method(:nOE_element_def).tap do |m|
# - O EMPTY
for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
ISINDEX META ]
define_method(element.downcase, m)
end
end
instance_method(:nO_element_def).tap do |m|
# O O or - O
for element in %w[ HTML HEAD BODY P PLAINTEXT DT DD LI OPTION TR
TH TD ]
define_method(element.downcase, m)
end
end
end # Html3
# Mixin module for HTML version 4 generation methods.
module Html4 # :nodoc:
include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
%|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">|
end
# Initialize the HTML generation methods for this version.
# - -
instance_method(:nn_element_def).tap do |m|
for element in %w[ TT I B BIG SMALL EM STRONG DFN CODE SAMP KBD
VAR CITE ABBR ACRONYM SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT OPTGROUP
FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
TEXTAREA FORM A BLOCKQUOTE CAPTION ]
define_method(element.downcase, m)
end
end
# - O EMPTY
instance_method(:nOE_element_def).tap do |m|
for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META ]
define_method(element.downcase, m)
end
end
# O O or - O
instance_method(:nO_element_def).tap do |m|
for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
COLGROUP TR TH TD HEAD ]
define_method(element.downcase, m)
end
end
end # Html4
# Mixin module for HTML version 4 transitional generation methods.
module Html4Tr # :nodoc:
include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
%|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">|
end
# Initialise the HTML generation methods for this version.
# - -
instance_method(:nn_element_def).tap do |m|
for element in %w[ TT I B U S STRIKE BIG SMALL EM STRONG DFN
CODE SAMP KBD VAR CITE ABBR ACRONYM FONT SUB SUP SPAN BDO
ADDRESS DIV CENTER MAP OBJECT APPLET H1 H2 H3 H4 H5 H6 PRE Q
INS DEL DL OL UL DIR MENU LABEL SELECT OPTGROUP FIELDSET
LEGEND BUTTON TABLE IFRAME NOFRAMES TITLE STYLE SCRIPT
NOSCRIPT TEXTAREA FORM A BLOCKQUOTE CAPTION ]
define_method(element.downcase, m)
end
end
# - O EMPTY
instance_method(:nOE_element_def).tap do |m|
for element in %w[ IMG BASE BASEFONT BR AREA LINK PARAM HR INPUT
COL ISINDEX META ]
define_method(element.downcase, m)
end
end
# O O or - O
instance_method(:nO_element_def).tap do |m|
for element in %w[ HTML BODY P DT DD LI OPTION THEAD TFOOT TBODY
COLGROUP TR TH TD HEAD ]
define_method(element.downcase, m)
end
end
end # Html4Tr
# Mixin module for generating HTML version 4 with framesets.
module Html4Fr # :nodoc:
include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
%|<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">|
end
# Initialise the HTML generation methods for this version.
# - -
instance_method(:nn_element_def).tap do |m|
for element in %w[ FRAMESET ]
define_method(element.downcase, m)
end
end
# - O EMPTY
instance_method(:nOE_element_def).tap do |m|
for element in %w[ FRAME ]
define_method(element.downcase, m)
end
end
end # Html4Fr
# Mixin module for HTML version 5 generation methods.
module Html5 # :nodoc:
include TagMaker
# The DOCTYPE declaration for this version of HTML
def doctype
%|<!DOCTYPE HTML>|
end
# Initialise the HTML generation methods for this version.
# - -
instance_method(:nn_element_def).tap do |m|
for element in %w[ SECTION NAV ARTICLE ASIDE HGROUP HEADER
FOOTER FIGURE FIGCAPTION S TIME U MARK RUBY BDI IFRAME
VIDEO AUDIO CANVAS DATALIST OUTPUT PROGRESS METER DETAILS
SUMMARY MENU DIALOG I B SMALL EM STRONG DFN CODE SAMP KBD
VAR CITE ABBR SUB SUP SPAN BDO ADDRESS DIV MAP OBJECT
H1 H2 H3 H4 H5 H6 PRE Q INS DEL DL OL UL LABEL SELECT
FIELDSET LEGEND BUTTON TABLE TITLE STYLE SCRIPT NOSCRIPT
TEXTAREA FORM A BLOCKQUOTE CAPTION ]
define_method(element.downcase, m)
end
end
# - O EMPTY
instance_method(:nOE_element_def).tap do |m|
for element in %w[ IMG BASE BR AREA LINK PARAM HR INPUT COL META
COMMAND EMBED KEYGEN SOURCE TRACK WBR ]
define_method(element.downcase, m)
end
end
# O O or - O
instance_method(:nO_element_def).tap do |m|
for element in %w[ HTML HEAD BODY P DT DD LI OPTION THEAD TFOOT TBODY
OPTGROUP COLGROUP RT RP TR TH TD ]
define_method(element.downcase, m)
end
end
end # Html5
class HTML3
include Html3
include HtmlExtension
end
class HTML4
include Html4
include HtmlExtension
end
class HTML4Tr
include Html4Tr
include HtmlExtension
end
class HTML4Fr
include Html4Tr
include Html4Fr
include HtmlExtension
end
class HTML5
include Html5
include HtmlExtension
end
end
share/ruby/cgi/session/pstore.rb 0000644 00000005121 15173547333 0012727 0 ustar 00 # frozen_string_literal: true
#
# cgi/session/pstore.rb - persistent storage of marshalled session data
#
# Documentation: William Webber (william@williamwebber.com)
#
# == Overview
#
# This file provides the CGI::Session::PStore class, which builds
# persistent of session data on top of the pstore library. See
# cgi/session.rb for more details on session storage managers.
require_relative '../session'
require 'pstore'
class CGI
class Session
# PStore-based session storage class.
#
# This builds upon the top-level PStore class provided by the
# library file pstore.rb. Session data is marshalled and stored
# in a file. File locking and transaction services are provided.
class PStore
# Create a new CGI::Session::PStore instance
#
# This constructor is used internally by CGI::Session. The
# user does not generally need to call it directly.
#
# +session+ is the session for which this instance is being
# created. The session id must only contain alphanumeric
# characters; automatically generated session ids observe
# this requirement.
#
# +option+ is a hash of options for the initializer. The
# following options are recognised:
#
# tmpdir:: the directory to use for storing the PStore
# file. Defaults to Dir::tmpdir (generally "/tmp"
# on Unix systems).
# prefix:: the prefix to add to the session id when generating
# the filename for this session's PStore file.
# Defaults to the empty string.
#
# This session's PStore file will be created if it does
# not exist, or opened if it does.
def initialize(session, option={})
option = {'suffix'=>''}.update(option)
path, @hash = session.new_store_file(option)
@p = ::PStore.new(path)
@p.transaction do |p|
File.chmod(0600, p.path)
end
end
# Restore session state from the session's PStore file.
#
# Returns the session state as a hash.
def restore
unless @hash
@p.transaction do
@hash = @p['hash'] || {}
end
end
@hash
end
# Save session state to the session's PStore file.
def update
@p.transaction do
@p['hash'] = @hash
end
end
# Update and close the session's PStore file.
def close
update
end
# Close and delete the session's PStore file.
def delete
path = @p.path
File::unlink path
end
end
end
end
# :enddoc:
share/ruby/cgi/session.rb 0000644 00000046311 15173547333 0011421 0 ustar 00 # frozen_string_literal: true
#
# cgi/session.rb - session support for cgi scripts
#
# Copyright (C) 2001 Yukihiro "Matz" Matsumoto
# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
# Copyright (C) 2000 Information-technology Promotion Agency, Japan
#
# Author: Yukihiro "Matz" Matsumoto
#
# Documentation: William Webber (william@williamwebber.com)
require 'cgi'
require 'tmpdir'
class CGI
# == Overview
#
# This file provides the CGI::Session class, which provides session
# support for CGI scripts. A session is a sequence of HTTP requests
# and responses linked together and associated with a single client.
# Information associated with the session is stored
# on the server between requests. A session id is passed between client
# and server with every request and response, transparently
# to the user. This adds state information to the otherwise stateless
# HTTP request/response protocol.
#
# == Lifecycle
#
# A CGI::Session instance is created from a CGI object. By default,
# this CGI::Session instance will start a new session if none currently
# exists, or continue the current session for this client if one does
# exist. The +new_session+ option can be used to either always or
# never create a new session. See #new() for more details.
#
# #delete() deletes a session from session storage. It
# does not however remove the session id from the client. If the client
# makes another request with the same id, the effect will be to start
# a new session with the old session's id.
#
# == Setting and retrieving session data.
#
# The Session class associates data with a session as key-value pairs.
# This data can be set and retrieved by indexing the Session instance
# using '[]', much the same as hashes (although other hash methods
# are not supported).
#
# When session processing has been completed for a request, the
# session should be closed using the close() method. This will
# store the session's state to persistent storage. If you want
# to store the session's state to persistent storage without
# finishing session processing for this request, call the update()
# method.
#
# == Storing session state
#
# The caller can specify what form of storage to use for the session's
# data with the +database_manager+ option to CGI::Session::new. The
# following storage classes are provided as part of the standard library:
#
# CGI::Session::FileStore:: stores data as plain text in a flat file. Only
# works with String data. This is the default
# storage type.
# CGI::Session::MemoryStore:: stores data in an in-memory hash. The data
# only persists for as long as the current Ruby
# interpreter instance does.
# CGI::Session::PStore:: stores data in Marshalled format. Provided by
# cgi/session/pstore.rb. Supports data of any type,
# and provides file-locking and transaction support.
#
# Custom storage types can also be created by defining a class with
# the following methods:
#
# new(session, options)
# restore # returns hash of session data.
# update
# close
# delete
#
# Changing storage type mid-session does not work. Note in particular
# that by default the FileStore and PStore session data files have the
# same name. If your application switches from one to the other without
# making sure that filenames will be different
# and clients still have old sessions lying around in cookies, then
# things will break nastily!
#
# == Maintaining the session id.
#
# Most session state is maintained on the server. However, a session
# id must be passed backwards and forwards between client and server
# to maintain a reference to this session state.
#
# The simplest way to do this is via cookies. The CGI::Session class
# provides transparent support for session id communication via cookies
# if the client has cookies enabled.
#
# If the client has cookies disabled, the session id must be included
# as a parameter of all requests sent by the client to the server. The
# CGI::Session class in conjunction with the CGI class will transparently
# add the session id as a hidden input field to all forms generated
# using the CGI#form() HTML generation method. No built-in support is
# provided for other mechanisms, such as URL re-writing. The caller is
# responsible for extracting the session id from the session_id
# attribute and manually encoding it in URLs and adding it as a hidden
# input to HTML forms created by other mechanisms. Also, session expiry
# is not automatically handled.
#
# == Examples of use
#
# === Setting the user's name
#
# require 'cgi'
# require 'cgi/session'
# require 'cgi/session/pstore' # provides CGI::Session::PStore
#
# cgi = CGI.new("html4")
#
# session = CGI::Session.new(cgi,
# 'database_manager' => CGI::Session::PStore, # use PStore
# 'session_key' => '_rb_sess_id', # custom session key
# 'session_expires' => Time.now + 30 * 60, # 30 minute timeout
# 'prefix' => 'pstore_sid_') # PStore option
# if cgi.has_key?('user_name') and cgi['user_name'] != ''
# # coerce to String: cgi[] returns the
# # string-like CGI::QueryExtension::Value
# session['user_name'] = cgi['user_name'].to_s
# elsif !session['user_name']
# session['user_name'] = "guest"
# end
# session.close
#
# === Creating a new session safely
#
# require 'cgi'
# require 'cgi/session'
#
# cgi = CGI.new("html4")
#
# # We make sure to delete an old session if one exists,
# # not just to free resources, but to prevent the session
# # from being maliciously hijacked later on.
# begin
# session = CGI::Session.new(cgi, 'new_session' => false)
# session.delete
# rescue ArgumentError # if no old session
# end
# session = CGI::Session.new(cgi, 'new_session' => true)
# session.close
#
class Session
class NoSession < RuntimeError #:nodoc:
end
# The id of this session.
attr_reader :session_id, :new_session
def Session::callback(dbman) #:nodoc:
Proc.new{
dbman[0].close unless dbman.empty?
}
end
# Create a new session id.
#
# The session id is a secure random number by SecureRandom
# if possible, otherwise an SHA512 hash based upon the time,
# a random number, and a constant string. This routine is
# used internally for automatically generated session ids.
def create_new_id
require 'securerandom'
begin
# by OpenSSL, or system provided entropy pool
session_id = SecureRandom.hex(16)
rescue NotImplementedError
# never happens on modern systems
require 'digest'
d = Digest('SHA512').new
now = Time::now
d.update(now.to_s)
d.update(String(now.usec))
d.update(String(rand(0)))
d.update(String($$))
d.update('foobar')
session_id = d.hexdigest[0, 32]
end
session_id
end
private :create_new_id
# Create a new file to store the session data.
#
# This file will be created if it does not exist, or opened if it
# does.
#
# This path is generated under _tmpdir_ from _prefix_, the
# digested session id, and _suffix_.
#
# +option+ is a hash of options for the initializer. The
# following options are recognised:
#
# tmpdir:: the directory to use for storing the FileStore
# file. Defaults to Dir::tmpdir (generally "/tmp"
# on Unix systems).
# prefix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to "cgi_sid_".
# suffix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to the empty string.
def new_store_file(option={}) # :nodoc:
dir = option['tmpdir'] || Dir::tmpdir
prefix = option['prefix']
suffix = option['suffix']
require 'digest/md5'
md5 = Digest::MD5.hexdigest(session_id)[0,16]
path = dir+"/"
path << prefix if prefix
path << md5
path << suffix if suffix
if File::exist? path
hash = nil
elsif new_session
hash = {}
else
raise NoSession, "uninitialized session"
end
return path, hash
end
# Create a new CGI::Session object for +request+.
#
# +request+ is an instance of the +CGI+ class (see cgi.rb).
# +option+ is a hash of options for initialising this
# CGI::Session instance. The following options are
# recognised:
#
# session_key:: the parameter name used for the session id.
# Defaults to '_session_id'.
# session_id:: the session id to use. If not provided, then
# it is retrieved from the +session_key+ parameter
# of the request, or automatically generated for
# a new session.
# new_session:: if true, force creation of a new session. If not set,
# a new session is only created if none currently
# exists. If false, a new session is never created,
# and if none currently exists and the +session_id+
# option is not set, an ArgumentError is raised.
# database_manager:: the name of the class providing storage facilities
# for session state persistence. Built-in support
# is provided for +FileStore+ (the default),
# +MemoryStore+, and +PStore+ (from
# cgi/session/pstore.rb). See the documentation for
# these classes for more details.
#
# The following options are also recognised, but only apply if the
# session id is stored in a cookie.
#
# session_expires:: the time the current session expires, as a
# +Time+ object. If not set, the session will terminate
# when the user's browser is closed.
# session_domain:: the hostname domain for which this session is valid.
# If not set, defaults to the hostname of the server.
# session_secure:: if +true+, this session will only work over HTTPS.
# session_path:: the path for which this session applies. Defaults
# to the directory of the CGI script.
#
# +option+ is also passed on to the session storage class initializer; see
# the documentation for each session storage class for the options
# they support.
#
# The retrieved or created session is automatically added to +request+
# as a cookie, and also to its +output_hidden+ table, which is used
# to add hidden input elements to forms.
#
# *WARNING* the +output_hidden+
# fields are surrounded by a <fieldset> tag in HTML 4 generation, which
# is _not_ invisible on many browsers; you may wish to disable the
# use of fieldsets with code similar to the following
# (see http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/37805)
#
# cgi = CGI.new("html4")
# class << cgi
# undef_method :fieldset
# end
#
def initialize(request, option={})
@new_session = false
session_key = option['session_key'] || '_session_id'
session_id = option['session_id']
unless session_id
if option['new_session']
session_id = create_new_id
@new_session = true
end
end
unless session_id
if request.key?(session_key)
session_id = request[session_key]
session_id = session_id.read if session_id.respond_to?(:read)
end
unless session_id
session_id, = request.cookies[session_key]
end
unless session_id
unless option.fetch('new_session', true)
raise ArgumentError, "session_key `%s' should be supplied"%session_key
end
session_id = create_new_id
@new_session = true
end
end
@session_id = session_id
dbman = option['database_manager'] || FileStore
begin
@dbman = dbman::new(self, option)
rescue NoSession
unless option.fetch('new_session', true)
raise ArgumentError, "invalid session_id `%s'"%session_id
end
session_id = @session_id = create_new_id unless session_id
@new_session=true
retry
end
request.instance_eval do
@output_hidden = {session_key => session_id} unless option['no_hidden']
@output_cookies = [
Cookie::new("name" => session_key,
"value" => session_id,
"expires" => option['session_expires'],
"domain" => option['session_domain'],
"secure" => option['session_secure'],
"path" =>
if option['session_path']
option['session_path']
elsif ENV["SCRIPT_NAME"]
File::dirname(ENV["SCRIPT_NAME"])
else
""
end)
] unless option['no_cookies']
end
@dbprot = [@dbman]
ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
end
# Retrieve the session data for key +key+.
def [](key)
@data ||= @dbman.restore
@data[key]
end
# Set the session data for key +key+.
def []=(key, val)
@write_lock ||= true
@data ||= @dbman.restore
@data[key] = val
end
# Store session data on the server. For some session storage types,
# this is a no-op.
def update
@dbman.update
end
# Store session data on the server and close the session storage.
# For some session storage types, this is a no-op.
def close
@dbman.close
@dbprot.clear
end
# Delete the session from storage. Also closes the storage.
#
# Note that the session's data is _not_ automatically deleted
# upon the session expiring.
def delete
@dbman.delete
@dbprot.clear
end
# File-based session storage class.
#
# Implements session storage as a flat file of 'key=value' values.
# This storage type only works directly with String values; the
# user is responsible for converting other types to Strings when
# storing and from Strings when retrieving.
class FileStore
# Create a new FileStore instance.
#
# This constructor is used internally by CGI::Session. The
# user does not generally need to call it directly.
#
# +session+ is the session for which this instance is being
# created. The session id must only contain alphanumeric
# characters; automatically generated session ids observe
# this requirement.
#
# +option+ is a hash of options for the initializer. The
# following options are recognised:
#
# tmpdir:: the directory to use for storing the FileStore
# file. Defaults to Dir::tmpdir (generally "/tmp"
# on Unix systems).
# prefix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to "cgi_sid_".
# suffix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to the empty string.
#
# This session's FileStore file will be created if it does
# not exist, or opened if it does.
def initialize(session, option={})
option = {'prefix' => 'cgi_sid_'}.update(option)
@path, @hash = session.new_store_file(option)
end
# Restore session state from the session's FileStore file.
#
# Returns the session state as a hash.
def restore
unless @hash
@hash = {}
begin
lockf = File.open(@path+".lock", "r")
lockf.flock File::LOCK_SH
f = File.open(@path, 'r')
for line in f
line.chomp!
k, v = line.split('=',2)
@hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v))
end
ensure
f&.close
lockf&.close
end
end
@hash
end
# Save session state to the session's FileStore file.
def update
return unless @hash
begin
lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600)
lockf.flock File::LOCK_EX
f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600)
for k,v in @hash
f.printf "%s=%s\n", CGI.escape(k), CGI.escape(String(Marshal.dump(v)))
end
f.close
File.rename @path+".new", @path
ensure
f&.close
lockf&.close
end
end
# Update and close the session's FileStore file.
def close
update
end
# Close and delete the session's FileStore file.
def delete
File::unlink @path+".lock" rescue nil
File::unlink @path+".new" rescue nil
File::unlink @path rescue nil
end
end
# In-memory session storage class.
#
# Implements session storage as a global in-memory hash. Session
# data will only persist for as long as the Ruby interpreter
# instance does.
class MemoryStore
GLOBAL_HASH_TABLE = {} #:nodoc:
# Create a new MemoryStore instance.
#
# +session+ is the session this instance is associated with.
# +option+ is a list of initialisation options. None are
# currently recognized.
def initialize(session, option=nil)
@session_id = session.session_id
unless GLOBAL_HASH_TABLE.key?(@session_id)
unless session.new_session
raise CGI::Session::NoSession, "uninitialized session"
end
GLOBAL_HASH_TABLE[@session_id] = {}
end
end
# Restore session state.
#
# Returns session data as a hash.
def restore
GLOBAL_HASH_TABLE[@session_id]
end
# Update session state.
#
# A no-op.
def update
# don't need to update; hash is shared
end
# Close session storage.
#
# A no-op.
def close
# don't need to close
end
# Delete the session state.
def delete
GLOBAL_HASH_TABLE.delete(@session_id)
end
end
# Dummy session storage class.
#
# Implements session storage place holder. No actual storage
# will be done.
class NullStore
# Create a new NullStore instance.
#
# +session+ is the session this instance is associated with.
# +option+ is a list of initialisation options. None are
# currently recognised.
def initialize(session, option=nil)
end
# Restore (empty) session state.
def restore
{}
end
# Update session state.
#
# A no-op.
def update
end
# Close session storage.
#
# A no-op.
def close
end
# Delete the session state.
#
# A no-op.
def delete
end
end
end
end
share/ruby/cgi/core.rb 0000644 00000072445 15173547334 0010676 0 ustar 00 # frozen_string_literal: true
#--
# Methods for generating HTML, parsing CGI-related parameters, and
# generating HTTP responses.
#++
class CGI
unless const_defined?(:Util)
module Util
@@accept_charset = "UTF-8" # :nodoc:
end
include Util
extend Util
end
$CGI_ENV = ENV # for FCGI support
# String for carriage return
CR = "\015"
# String for linefeed
LF = "\012"
# Standard internet newline sequence
EOL = CR + LF
REVISION = '$Id$' #:nodoc:
# Whether processing will be required in binary vs text
NEEDS_BINMODE = File::BINARY != 0
# Path separators in different environments.
PATH_SEPARATOR = {'UNIX'=>'/', 'WINDOWS'=>'\\', 'MACINTOSH'=>':'}
# HTTP status codes.
HTTP_STATUS = {
"OK" => "200 OK",
"PARTIAL_CONTENT" => "206 Partial Content",
"MULTIPLE_CHOICES" => "300 Multiple Choices",
"MOVED" => "301 Moved Permanently",
"REDIRECT" => "302 Found",
"NOT_MODIFIED" => "304 Not Modified",
"BAD_REQUEST" => "400 Bad Request",
"AUTH_REQUIRED" => "401 Authorization Required",
"FORBIDDEN" => "403 Forbidden",
"NOT_FOUND" => "404 Not Found",
"METHOD_NOT_ALLOWED" => "405 Method Not Allowed",
"NOT_ACCEPTABLE" => "406 Not Acceptable",
"LENGTH_REQUIRED" => "411 Length Required",
"PRECONDITION_FAILED" => "412 Precondition Failed",
"SERVER_ERROR" => "500 Internal Server Error",
"NOT_IMPLEMENTED" => "501 Method Not Implemented",
"BAD_GATEWAY" => "502 Bad Gateway",
"VARIANT_ALSO_VARIES" => "506 Variant Also Negotiates"
}
# :startdoc:
# Synonym for ENV.
def env_table
ENV
end
# Synonym for $stdin.
def stdinput
$stdin
end
# Synonym for $stdout.
def stdoutput
$stdout
end
private :env_table, :stdinput, :stdoutput
# Create an HTTP header block as a string.
#
# :call-seq:
# http_header(content_type_string="text/html")
# http_header(headers_hash)
#
# Includes the empty line that ends the header block.
#
# +content_type_string+::
# If this form is used, this string is the <tt>Content-Type</tt>
# +headers_hash+::
# A Hash of header values. The following header keys are recognized:
#
# type:: The Content-Type header. Defaults to "text/html"
# charset:: The charset of the body, appended to the Content-Type header.
# nph:: A boolean value. If true, prepend protocol string and status
# code, and date; and sets default values for "server" and
# "connection" if not explicitly set.
# status::
# The HTTP status code as a String, returned as the Status header. The
# values are:
#
# OK:: 200 OK
# PARTIAL_CONTENT:: 206 Partial Content
# MULTIPLE_CHOICES:: 300 Multiple Choices
# MOVED:: 301 Moved Permanently
# REDIRECT:: 302 Found
# NOT_MODIFIED:: 304 Not Modified
# BAD_REQUEST:: 400 Bad Request
# AUTH_REQUIRED:: 401 Authorization Required
# FORBIDDEN:: 403 Forbidden
# NOT_FOUND:: 404 Not Found
# METHOD_NOT_ALLOWED:: 405 Method Not Allowed
# NOT_ACCEPTABLE:: 406 Not Acceptable
# LENGTH_REQUIRED:: 411 Length Required
# PRECONDITION_FAILED:: 412 Precondition Failed
# SERVER_ERROR:: 500 Internal Server Error
# NOT_IMPLEMENTED:: 501 Method Not Implemented
# BAD_GATEWAY:: 502 Bad Gateway
# VARIANT_ALSO_VARIES:: 506 Variant Also Negotiates
#
# server:: The server software, returned as the Server header.
# connection:: The connection type, returned as the Connection header (for
# instance, "close".
# length:: The length of the content that will be sent, returned as the
# Content-Length header.
# language:: The language of the content, returned as the Content-Language
# header.
# expires:: The time on which the current content expires, as a +Time+
# object, returned as the Expires header.
# cookie::
# A cookie or cookies, returned as one or more Set-Cookie headers. The
# value can be the literal string of the cookie; a CGI::Cookie object;
# an Array of literal cookie strings or Cookie objects; or a hash all of
# whose values are literal cookie strings or Cookie objects.
#
# These cookies are in addition to the cookies held in the
# @output_cookies field.
#
# Other headers can also be set; they are appended as key: value.
#
# Examples:
#
# http_header
# # Content-Type: text/html
#
# http_header("text/plain")
# # Content-Type: text/plain
#
# http_header("nph" => true,
# "status" => "OK", # == "200 OK"
# # "status" => "200 GOOD",
# "server" => ENV['SERVER_SOFTWARE'],
# "connection" => "close",
# "type" => "text/html",
# "charset" => "iso-2022-jp",
# # Content-Type: text/html; charset=iso-2022-jp
# "length" => 103,
# "language" => "ja",
# "expires" => Time.now + 30,
# "cookie" => [cookie1, cookie2],
# "my_header1" => "my_value",
# "my_header2" => "my_value")
#
# This method does not perform charset conversion.
def http_header(options='text/html')
if options.is_a?(String)
content_type = options
buf = _header_for_string(content_type)
elsif options.is_a?(Hash)
if options.size == 1 && options.has_key?('type')
content_type = options['type']
buf = _header_for_string(content_type)
else
buf = _header_for_hash(options.dup)
end
else
raise ArgumentError.new("expected String or Hash but got #{options.class}")
end
if defined?(MOD_RUBY)
_header_for_modruby(buf)
return ''
else
buf << EOL # empty line of separator
return buf
end
end # http_header()
# This method is an alias for #http_header, when HTML5 tag maker is inactive.
#
# NOTE: use #http_header to create HTTP header blocks, this alias is only
# provided for backwards compatibility.
#
# Using #header with the HTML5 tag maker will create a <header> element.
alias :header :http_header
def _no_crlf_check(str)
if str
str = str.to_s
raise "A HTTP status or header field must not include CR and LF" if str =~ /[\r\n]/
str
else
nil
end
end
private :_no_crlf_check
def _header_for_string(content_type) #:nodoc:
buf = ''.dup
if nph?()
buf << "#{_no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'} 200 OK#{EOL}"
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
buf << "Server: #{_no_crlf_check($CGI_ENV['SERVER_SOFTWARE'])}#{EOL}"
buf << "Connection: close#{EOL}"
end
buf << "Content-Type: #{_no_crlf_check(content_type)}#{EOL}"
if @output_cookies
@output_cookies.each {|cookie| buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}" }
end
return buf
end # _header_for_string
private :_header_for_string
def _header_for_hash(options) #:nodoc:
buf = ''.dup
## add charset to option['type']
options['type'] ||= 'text/html'
charset = options.delete('charset')
options['type'] += "; charset=#{charset}" if charset
## NPH
options.delete('nph') if defined?(MOD_RUBY)
if options.delete('nph') || nph?()
protocol = _no_crlf_check($CGI_ENV['SERVER_PROTOCOL']) || 'HTTP/1.0'
status = options.delete('status')
status = HTTP_STATUS[status] || _no_crlf_check(status) || '200 OK'
buf << "#{protocol} #{status}#{EOL}"
buf << "Date: #{CGI.rfc1123_date(Time.now)}#{EOL}"
options['server'] ||= $CGI_ENV['SERVER_SOFTWARE'] || ''
options['connection'] ||= 'close'
end
## common headers
status = options.delete('status')
buf << "Status: #{HTTP_STATUS[status] || _no_crlf_check(status)}#{EOL}" if status
server = options.delete('server')
buf << "Server: #{_no_crlf_check(server)}#{EOL}" if server
connection = options.delete('connection')
buf << "Connection: #{_no_crlf_check(connection)}#{EOL}" if connection
type = options.delete('type')
buf << "Content-Type: #{_no_crlf_check(type)}#{EOL}" #if type
length = options.delete('length')
buf << "Content-Length: #{_no_crlf_check(length)}#{EOL}" if length
language = options.delete('language')
buf << "Content-Language: #{_no_crlf_check(language)}#{EOL}" if language
expires = options.delete('expires')
buf << "Expires: #{CGI.rfc1123_date(expires)}#{EOL}" if expires
## cookie
if cookie = options.delete('cookie')
case cookie
when String, Cookie
buf << "Set-Cookie: #{_no_crlf_check(cookie)}#{EOL}"
when Array
arr = cookie
arr.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
when Hash
hash = cookie
hash.each_value {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
end
end
if @output_cookies
@output_cookies.each {|c| buf << "Set-Cookie: #{_no_crlf_check(c)}#{EOL}" }
end
## other headers
options.each do |key, value|
buf << "#{_no_crlf_check(key)}: #{_no_crlf_check(value)}#{EOL}"
end
return buf
end # _header_for_hash
private :_header_for_hash
def nph? #:nodoc:
return /IIS\/(\d+)/ =~ $CGI_ENV['SERVER_SOFTWARE'] && $1.to_i < 5
end
def _header_for_modruby(buf) #:nodoc:
request = Apache::request
buf.scan(/([^:]+): (.+)#{EOL}/o) do |name, value|
$stderr.printf("name:%s value:%s\n", name, value) if $DEBUG
case name
when 'Set-Cookie'
request.headers_out.add(name, value)
when /^status$/i
request.status_line = value
request.status = value.to_i
when /^content-type$/i
request.content_type = value
when /^content-encoding$/i
request.content_encoding = value
when /^location$/i
request.status = 302 if request.status == 200
request.headers_out[name] = value
else
request.headers_out[name] = value
end
end
request.send_http_header
return ''
end
private :_header_for_modruby
# Print an HTTP header and body to $DEFAULT_OUTPUT ($>)
#
# :call-seq:
# cgi.out(content_type_string='text/html')
# cgi.out(headers_hash)
#
# +content_type_string+::
# If a string is passed, it is assumed to be the content type.
# +headers_hash+::
# This is a Hash of headers, similar to that used by #http_header.
# +block+::
# A block is required and should evaluate to the body of the response.
#
# <tt>Content-Length</tt> is automatically calculated from the size of
# the String returned by the content block.
#
# If <tt>ENV['REQUEST_METHOD'] == "HEAD"</tt>, then only the header
# is output (the content block is still required, but it is ignored).
#
# If the charset is "iso-2022-jp" or "euc-jp" or "shift_jis" then the
# content is converted to this charset, and the language is set to "ja".
#
# Example:
#
# cgi = CGI.new
# cgi.out{ "string" }
# # Content-Type: text/html
# # Content-Length: 6
# #
# # string
#
# cgi.out("text/plain") { "string" }
# # Content-Type: text/plain
# # Content-Length: 6
# #
# # string
#
# cgi.out("nph" => true,
# "status" => "OK", # == "200 OK"
# "server" => ENV['SERVER_SOFTWARE'],
# "connection" => "close",
# "type" => "text/html",
# "charset" => "iso-2022-jp",
# # Content-Type: text/html; charset=iso-2022-jp
# "language" => "ja",
# "expires" => Time.now + (3600 * 24 * 30),
# "cookie" => [cookie1, cookie2],
# "my_header1" => "my_value",
# "my_header2" => "my_value") { "string" }
# # HTTP/1.1 200 OK
# # Date: Sun, 15 May 2011 17:35:54 GMT
# # Server: Apache 2.2.0
# # Connection: close
# # Content-Type: text/html; charset=iso-2022-jp
# # Content-Length: 6
# # Content-Language: ja
# # Expires: Tue, 14 Jun 2011 17:35:54 GMT
# # Set-Cookie: foo
# # Set-Cookie: bar
# # my_header1: my_value
# # my_header2: my_value
# #
# # string
def out(options = "text/html") # :yield:
options = { "type" => options } if options.kind_of?(String)
content = yield
options["length"] = content.bytesize.to_s
output = stdoutput
output.binmode if defined? output.binmode
output.print http_header(options)
output.print content unless "HEAD" == env_table['REQUEST_METHOD']
end
# Print an argument or list of arguments to the default output stream
#
# cgi = CGI.new
# cgi.print # default: cgi.print == $DEFAULT_OUTPUT.print
def print(*options)
stdoutput.print(*options)
end
# Parse an HTTP query string into a hash of key=>value pairs.
#
# params = CGI.parse("query_string")
# # {"name1" => ["value1", "value2", ...],
# # "name2" => ["value1", "value2", ...], ... }
#
def self.parse(query)
params = {}
query.split(/[&;]/).each do |pairs|
key, value = pairs.split('=',2).collect{|v| CGI.unescape(v) }
next unless key
params[key] ||= []
params[key].push(value) if value
end
params.default=[].freeze
params
end
# Maximum content length of post data
##MAX_CONTENT_LENGTH = 2 * 1024 * 1024
# Maximum number of request parameters when multipart
MAX_MULTIPART_COUNT = 128
# Mixin module that provides the following:
#
# 1. Access to the CGI environment variables as methods. See
# documentation to the CGI class for a list of these variables. The
# methods are exposed by removing the leading +HTTP_+ (if it exists) and
# downcasing the name. For example, +auth_type+ will return the
# environment variable +AUTH_TYPE+, and +accept+ will return the value
# for +HTTP_ACCEPT+.
#
# 2. Access to cookies, including the cookies attribute.
#
# 3. Access to parameters, including the params attribute, and overloading
# #[] to perform parameter value lookup by key.
#
# 4. The initialize_query method, for initializing the above
# mechanisms, handling multipart forms, and allowing the
# class to be used in "offline" mode.
#
module QueryExtension
%w[ CONTENT_LENGTH SERVER_PORT ].each do |env|
define_method(env.delete_prefix('HTTP_').downcase) do
(val = env_table[env]) && Integer(val)
end
end
%w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO
PATH_TRANSLATED QUERY_STRING REMOTE_ADDR REMOTE_HOST
REMOTE_IDENT REMOTE_USER REQUEST_METHOD SCRIPT_NAME
SERVER_NAME SERVER_PROTOCOL SERVER_SOFTWARE
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
define_method(env.delete_prefix('HTTP_').downcase) do
env_table[env]
end
end
# Get the raw cookies as a string.
def raw_cookie
env_table["HTTP_COOKIE"]
end
# Get the raw RFC2965 cookies as a string.
def raw_cookie2
env_table["HTTP_COOKIE2"]
end
# Get the cookies as a hash of cookie-name=>Cookie pairs.
attr_accessor :cookies
# Get the parameters as a hash of name=>values pairs, where
# values is an Array.
attr_reader :params
# Get the uploaded files as a hash of name=>values pairs
attr_reader :files
# Set all the parameters.
def params=(hash)
@params.clear
@params.update(hash)
end
##
# Parses multipart form elements according to
# http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2
#
# Returns a hash of multipart form parameters with bodies of type StringIO or
# Tempfile depending on whether the multipart form element exceeds 10 KB
#
# params[name => body]
#
def read_multipart(boundary, content_length)
## read first boundary
stdin = stdinput
first_line = "--#{boundary}#{EOL}"
content_length -= first_line.bytesize
status = stdin.read(first_line.bytesize)
raise EOFError.new("no content body") unless status
raise EOFError.new("bad content body") unless first_line == status
## parse and set params
params = {}
@files = {}
boundary_rexp = /--#{Regexp.quote(boundary)}(#{EOL}|--)/
boundary_size = "#{EOL}--#{boundary}#{EOL}".bytesize
buf = ''.dup
bufsize = 10 * 1024
max_count = MAX_MULTIPART_COUNT
n = 0
tempfiles = []
while true
(n += 1) < max_count or raise StandardError.new("too many parameters.")
## create body (StringIO or Tempfile)
body = create_body(bufsize < content_length)
tempfiles << body if defined?(Tempfile) && body.kind_of?(Tempfile)
class << body
if method_defined?(:path)
alias local_path path
else
def local_path
nil
end
end
attr_reader :original_filename, :content_type
end
## find head and boundary
head = nil
separator = EOL * 2
until head && matched = boundary_rexp.match(buf)
if !head && pos = buf.index(separator)
len = pos + EOL.bytesize
head = buf[0, len]
buf = buf[(pos+separator.bytesize)..-1]
else
if head && buf.size > boundary_size
len = buf.size - boundary_size
body.print(buf[0, len])
buf[0, len] = ''
end
c = stdin.read(bufsize < content_length ? bufsize : content_length)
raise EOFError.new("bad content body") if c.nil? || c.empty?
buf << c
content_length -= c.bytesize
end
end
## read to end of boundary
m = matched
len = m.begin(0)
s = buf[0, len]
if s =~ /(\r?\n)\z/
s = buf[0, len - $1.bytesize]
end
body.print(s)
buf = buf[m.end(0)..-1]
boundary_end = m[1]
content_length = -1 if boundary_end == '--'
## reset file cursor position
body.rewind
## original filename
/Content-Disposition:.* filename=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
filename = $1 || $2 || ''.dup
filename = CGI.unescape(filename) if unescape_filename?()
body.instance_variable_set(:@original_filename, filename)
## content type
/Content-Type: (.*)/i.match(head)
(content_type = $1 || ''.dup).chomp!
body.instance_variable_set(:@content_type, content_type)
## query parameter name
/Content-Disposition:.* name=(?:"(.*?)"|([^;\r\n]*))/i.match(head)
name = $1 || $2 || ''
if body.original_filename.empty?
value=body.read.dup.force_encoding(@accept_charset)
body.close! if defined?(Tempfile) && body.kind_of?(Tempfile)
(params[name] ||= []) << value
unless value.valid_encoding?
if @accept_charset_error_block
@accept_charset_error_block.call(name,value)
else
raise InvalidEncoding,"Accept-Charset encoding error"
end
end
class << params[name].last;self;end.class_eval do
define_method(:read){self}
define_method(:original_filename){""}
define_method(:content_type){""}
end
else
(params[name] ||= []) << body
@files[name]=body
end
## break loop
break if content_length == -1
end
raise EOFError, "bad boundary end of body part" unless boundary_end =~ /--/
params.default = []
params
rescue Exception
if tempfiles
tempfiles.each {|t|
if t.path
t.close!
end
}
end
raise
end # read_multipart
private :read_multipart
def create_body(is_large) #:nodoc:
if is_large
require 'tempfile'
body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
else
begin
require 'stringio'
body = StringIO.new("".b)
rescue LoadError
require 'tempfile'
body = Tempfile.new('CGI', encoding: Encoding::ASCII_8BIT)
end
end
body.binmode if defined? body.binmode
return body
end
def unescape_filename? #:nodoc:
user_agent = $CGI_ENV['HTTP_USER_AGENT']
return false unless user_agent
return /Mac/i.match(user_agent) && /Mozilla/i.match(user_agent) && !/MSIE/i.match(user_agent)
end
# offline mode. read name=value pairs on standard input.
def read_from_cmdline
require "shellwords"
string = unless ARGV.empty?
ARGV.join(' ')
else
if STDIN.tty?
STDERR.print(
%|(offline mode: enter name=value pairs on standard input)\n|
)
end
array = readlines rescue nil
if not array.nil?
array.join(' ').gsub(/\n/n, '')
else
""
end
end.gsub(/\\=/n, '%3D').gsub(/\\&/n, '%26')
words = Shellwords.shellwords(string)
if words.find{|x| /=/n.match(x) }
words.join('&')
else
words.join('+')
end
end
private :read_from_cmdline
# A wrapper class to use a StringIO object as the body and switch
# to a TempFile when the passed threshold is passed.
# Initialize the data from the query.
#
# Handles multipart forms (in particular, forms that involve file uploads).
# Reads query parameters in the @params field, and cookies into @cookies.
def initialize_query()
if ("POST" == env_table['REQUEST_METHOD']) and
%r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?| =~ env_table['CONTENT_TYPE']
current_max_multipart_length = @max_multipart_length.respond_to?(:call) ? @max_multipart_length.call : @max_multipart_length
raise StandardError.new("too large multipart data.") if env_table['CONTENT_LENGTH'].to_i > current_max_multipart_length
boundary = $1.dup
@multipart = true
@params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH']))
else
@multipart = false
@params = CGI.parse(
case env_table['REQUEST_METHOD']
when "GET", "HEAD"
if defined?(MOD_RUBY)
Apache::request.args or ""
else
env_table['QUERY_STRING'] or ""
end
when "POST"
stdinput.binmode if defined? stdinput.binmode
stdinput.read(Integer(env_table['CONTENT_LENGTH'])) or ''
else
read_from_cmdline
end.dup.force_encoding(@accept_charset)
)
unless Encoding.find(@accept_charset) == Encoding::ASCII_8BIT
@params.each do |key,values|
values.each do |value|
unless value.valid_encoding?
if @accept_charset_error_block
@accept_charset_error_block.call(key,value)
else
raise InvalidEncoding,"Accept-Charset encoding error"
end
end
end
end
end
end
@cookies = CGI::Cookie.parse((env_table['HTTP_COOKIE'] or env_table['COOKIE']))
end
private :initialize_query
# Returns whether the form contained multipart/form-data
def multipart?
@multipart
end
# Get the value for the parameter with a given key.
#
# If the parameter has multiple values, only the first will be
# retrieved; use #params to get the array of values.
def [](key)
params = @params[key]
return '' unless params
value = params[0]
if @multipart
if value
return value
elsif defined? StringIO
StringIO.new("".b)
else
Tempfile.new("CGI",encoding: Encoding::ASCII_8BIT)
end
else
str = if value then value.dup else "" end
str
end
end
# Return all query parameter names as an array of String.
def keys(*args)
@params.keys(*args)
end
# Returns true if a given query string parameter exists.
def has_key?(*args)
@params.has_key?(*args)
end
alias key? has_key?
alias include? has_key?
end # QueryExtension
# Exception raised when there is an invalid encoding detected
class InvalidEncoding < Exception; end
# @@accept_charset is default accept character set.
# This default value default is "UTF-8"
# If you want to change the default accept character set
# when create a new CGI instance, set this:
#
# CGI.accept_charset = "EUC-JP"
#
@@accept_charset="UTF-8" if false # needed for rdoc?
# Return the accept character set for all new CGI instances.
def self.accept_charset
@@accept_charset
end
# Set the accept character set for all new CGI instances.
def self.accept_charset=(accept_charset)
@@accept_charset=accept_charset
end
# Return the accept character set for this CGI instance.
attr_reader :accept_charset
# @@max_multipart_length is the maximum length of multipart data.
# The default value is 128 * 1024 * 1024 bytes
#
# The default can be set to something else in the CGI constructor,
# via the :max_multipart_length key in the option hash.
#
# See CGI.new documentation.
#
@@max_multipart_length= 128 * 1024 * 1024
# Create a new CGI instance.
#
# :call-seq:
# CGI.new(tag_maker) { block }
# CGI.new(options_hash = {}) { block }
#
#
# <tt>tag_maker</tt>::
# This is the same as using the +options_hash+ form with the value <tt>{
# :tag_maker => tag_maker }</tt> Note that it is recommended to use the
# +options_hash+ form, since it also allows you specify the charset you
# will accept.
# <tt>options_hash</tt>::
# A Hash that recognizes three options:
#
# <tt>:accept_charset</tt>::
# specifies encoding of received query string. If omitted,
# <tt>@@accept_charset</tt> is used. If the encoding is not valid, a
# CGI::InvalidEncoding will be raised.
#
# Example. Suppose <tt>@@accept_charset</tt> is "UTF-8"
#
# when not specified:
#
# cgi=CGI.new # @accept_charset # => "UTF-8"
#
# when specified as "EUC-JP":
#
# cgi=CGI.new(:accept_charset => "EUC-JP") # => "EUC-JP"
#
# <tt>:tag_maker</tt>::
# String that specifies which version of the HTML generation methods to
# use. If not specified, no HTML generation methods will be loaded.
#
# The following values are supported:
#
# "html3":: HTML 3.x
# "html4":: HTML 4.0
# "html4Tr":: HTML 4.0 Transitional
# "html4Fr":: HTML 4.0 with Framesets
# "html5":: HTML 5
#
# <tt>:max_multipart_length</tt>::
# Specifies maximum length of multipart data. Can be an Integer scalar or
# a lambda, that will be evaluated when the request is parsed. This
# allows more complex logic to be set when determining whether to accept
# multipart data (e.g. consult a registered users upload allowance)
#
# Default is 128 * 1024 * 1024 bytes
#
# cgi=CGI.new(:max_multipart_length => 268435456) # simple scalar
#
# cgi=CGI.new(:max_multipart_length => -> {check_filesystem}) # lambda
#
# <tt>block</tt>::
# If provided, the block is called when an invalid encoding is
# encountered. For example:
#
# encoding_errors={}
# cgi=CGI.new(:accept_charset=>"EUC-JP") do |name,value|
# encoding_errors[name] = value
# end
#
# Finally, if the CGI object is not created in a standard CGI call
# environment (that is, it can't locate REQUEST_METHOD in its environment),
# then it will run in "offline" mode. In this mode, it reads its parameters
# from the command line or (failing that) from standard input. Otherwise,
# cookies and other parameters are parsed automatically from the standard
# CGI locations, which varies according to the REQUEST_METHOD.
def initialize(options = {}, &block) # :yields: name, value
@accept_charset_error_block = block_given? ? block : nil
@options={
:accept_charset=>@@accept_charset,
:max_multipart_length=>@@max_multipart_length
}
case options
when Hash
@options.merge!(options)
when String
@options[:tag_maker]=options
end
@accept_charset=@options[:accept_charset]
@max_multipart_length=@options[:max_multipart_length]
if defined?(MOD_RUBY) && !ENV.key?("GATEWAY_INTERFACE")
Apache.request.setup_cgi_env
end
extend QueryExtension
@multipart = false
initialize_query() # set @params, @cookies
@output_cookies = nil
@output_hidden = nil
case @options[:tag_maker]
when "html3"
require_relative 'html'
extend Html3
extend HtmlExtension
when "html4"
require_relative 'html'
extend Html4
extend HtmlExtension
when "html4Tr"
require_relative 'html'
extend Html4Tr
extend HtmlExtension
when "html4Fr"
require_relative 'html'
extend Html4Tr
extend Html4Fr
extend HtmlExtension
when "html5"
require_relative 'html'
extend Html5
extend HtmlExtension
end
end
end # class CGI
share/ruby/coverage.rb 0000644 00000000560 15173547334 0010764 0 ustar 00 require "coverage.so"
module Coverage
def self.line_stub(file)
lines = File.foreach(file).map { nil }
iseqs = [RubyVM::InstructionSequence.compile_file(file)]
until iseqs.empty?
iseq = iseqs.pop
iseq.trace_points.each {|n, type| lines[n - 1] = 0 if type == :line }
iseq.each_child {|child| iseqs << child }
end
lines
end
end
share/ruby/weakref.rb 0000644 00000002554 15173547334 0010622 0 ustar 00 # frozen_string_literal: true
require "delegate"
# Weak Reference class that allows a referenced object to be
# garbage-collected.
#
# A WeakRef may be used exactly like the object it references.
#
# Usage:
#
# foo = Object.new # create a new object instance
# p foo.to_s # original's class
# foo = WeakRef.new(foo) # reassign foo with WeakRef instance
# p foo.to_s # should be same class
# GC.start # start the garbage collector
# p foo.to_s # should raise exception (recycled)
#
class WeakRef < Delegator
VERSION = "0.1.2"
##
# RefError is raised when a referenced object has been recycled by the
# garbage collector
class RefError < StandardError
end
@@__map = ::ObjectSpace::WeakMap.new
##
# Creates a weak reference to +orig+
def initialize(orig)
case orig
when true, false, nil
@delegate_sd_obj = orig
else
@@__map[self] = orig
end
super
end
def __getobj__ # :nodoc:
@@__map[self] or defined?(@delegate_sd_obj) ? @delegate_sd_obj :
Kernel::raise(RefError, "Invalid Reference - probably recycled", Kernel::caller(2))
end
def __setobj__(obj) # :nodoc:
end
##
# Returns true if the referenced object is still alive.
def weakref_alive?
@@__map.key?(self) or defined?(@delegate_sd_obj)
end
end
share/ruby/random/formatter.rb 0000644 00000016157 15173547334 0012465 0 ustar 00 # -*- coding: us-ascii -*-
# frozen_string_literal: true
# == \Random number formatter.
#
# Formats generated random numbers in many manners. When <tt>'random/formatter'</tt>
# is required, several methods are added to empty core module <tt>Random::Formatter</tt>,
# making them available as Random's instance and module methods.
#
# Standard library SecureRandom is also extended with the module, and the methods
# described below are available as a module methods in it.
#
# === Examples
#
# Generate random hexadecimal strings:
#
# require 'random/formatter'
#
# prng = Random.new
# prng.hex(10) #=> "52750b30ffbc7de3b362"
# prng.hex(10) #=> "92b15d6c8dc4beb5f559"
# prng.hex(13) #=> "39b290146bea6ce975c37cfc23"
# # or just
# Random.hex #=> "1aed0c631e41be7f77365415541052ee"
#
# Generate random base64 strings:
#
# prng.base64(10) #=> "EcmTPZwWRAozdA=="
# prng.base64(10) #=> "KO1nIU+p9DKxGg=="
# prng.base64(12) #=> "7kJSM/MzBJI+75j8"
# Random.base64(4) #=> "bsQ3fQ=="
#
# Generate random binary strings:
#
# prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
# prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
# Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43"
#
# Generate alphanumeric strings:
#
# prng.alphanumeric(10) #=> "S8baxMJnPl"
# prng.alphanumeric(10) #=> "aOxAg8BAJe"
# Random.alphanumeric #=> "TmP9OsJHJLtaZYhP"
#
# Generate UUIDs:
#
# prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
# prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
# Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd"
#
# All methods are available in the standard library SecureRandom, too:
#
# SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf"
module Random::Formatter
# Generate a random binary string.
#
# The argument _n_ specifies the length of the result string.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in future.
#
# The result may contain any byte: "\x00" - "\xff".
#
# require 'random/formatter'
#
# Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
# # or
# prng = Random.new
# prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
def random_bytes(n=nil)
n = n ? n.to_int : 16
gen_random(n)
end
# Generate a random hexadecimal string.
#
# The argument _n_ specifies the length, in bytes, of the random number to be generated.
# The length of the resulting hexadecimal string is twice of _n_.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in the future.
#
# The result may contain 0-9 and a-f.
#
# require 'random/formatter'
#
# Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
# # or
# prng = Random.new
# prng.hex #=> "91dc3bfb4de5b11d029d376634589b61"
def hex(n=nil)
random_bytes(n).unpack1("H*")
end
# Generate a random base64 string.
#
# The argument _n_ specifies the length, in bytes, of the random number
# to be generated. The length of the result string is about 4/3 of _n_.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in the future.
#
# The result may contain A-Z, a-z, 0-9, "+", "/" and "=".
#
# require 'random/formatter'
#
# Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
# # or
# prng = Random.new
# prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
#
# See RFC 3548 for the definition of base64.
def base64(n=nil)
[random_bytes(n)].pack("m0")
end
# Generate a random URL-safe base64 string.
#
# The argument _n_ specifies the length, in bytes, of the random number
# to be generated. The length of the result string is about 4/3 of _n_.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in the future.
#
# The boolean argument _padding_ specifies the padding.
# If it is false or nil, padding is not generated.
# Otherwise padding is generated.
# By default, padding is not generated because "=" may be used as a URL delimiter.
#
# The result may contain A-Z, a-z, 0-9, "-" and "_".
# "=" is also used if _padding_ is true.
#
# require 'random/formatter'
#
# Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
# # or
# prng = Random.new
# prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
#
# prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
# prng.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg=="
#
# See RFC 3548 for the definition of URL-safe base64.
def urlsafe_base64(n=nil, padding=false)
s = [random_bytes(n)].pack("m0")
s.tr!("+/", "-_")
s.delete!("=") unless padding
s
end
# Generate a random v4 UUID (Universally Unique IDentifier).
#
# require 'random/formatter'
#
# Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
# Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
# # or
# prng = Random.new
# prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
#
# The version 4 UUID is purely random (except the version).
# It doesn't contain meaningful information such as MAC addresses, timestamps, etc.
#
# The result contains 122 random bits (15.25 random bytes).
#
# See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID.
#
def uuid
ary = random_bytes(16).unpack("NnnnnN")
ary[2] = (ary[2] & 0x0fff) | 0x4000
ary[3] = (ary[3] & 0x3fff) | 0x8000
"%08x-%04x-%04x-%04x-%04x%08x" % ary
end
private def gen_random(n)
self.bytes(n)
end
# Generate a string that randomly draws from a
# source array of characters.
#
# The argument _source_ specifies the array of characters from which
# to generate the string.
# The argument _n_ specifies the length, in characters, of the string to be
# generated.
#
# The result may contain whatever characters are in the source array.
#
# require 'random/formatter'
#
# prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
# prng.choose([*'0'..'9'], 5) #=> "27309"
private def choose(source, n)
size = source.size
m = 1
limit = size
while limit * size <= 0x100000000
limit *= size
m += 1
end
result = ''.dup
while m <= n
rs = random_number(limit)
is = rs.digits(size)
(m-is.length).times { is << 0 }
result << source.values_at(*is).join('')
n -= m
end
if 0 < n
rs = random_number(limit)
is = rs.digits(size)
if is.length < n
(n-is.length).times { is << 0 }
else
is.pop while n < is.length
end
result.concat source.values_at(*is).join('')
end
result
end
ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
# Generate a random alphanumeric string.
#
# The argument _n_ specifies the length, in characters, of the alphanumeric
# string to be generated.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in the future.
#
# The result may contain A-Z, a-z and 0-9.
#
# require 'random/formatter'
#
# Random.alphanumeric #=> "2BuBuLf3WfSKyQbR"
# # or
# prng = Random.new
# prng.alphanumeric(10) #=> "i6K93NdqiH"
def alphanumeric(n=nil)
n = 16 if n.nil?
choose(ALPHANUMERIC, n)
end
end
share/gems/gems/json-2.6.3/lib/json.rb 0000644 00000046455 15173547334 0013247 0 ustar 00 #frozen_string_literal: false
require 'json/common'
##
# = JavaScript \Object Notation (\JSON)
#
# \JSON is a lightweight data-interchange format.
#
# A \JSON value is one of the following:
# - Double-quoted text: <tt>"foo"</tt>.
# - Number: +1+, +1.0+, +2.0e2+.
# - Boolean: +true+, +false+.
# - Null: +null+.
# - \Array: an ordered list of values, enclosed by square brackets:
# ["foo", 1, 1.0, 2.0e2, true, false, null]
#
# - \Object: a collection of name/value pairs, enclosed by curly braces;
# each name is double-quoted text;
# the values may be any \JSON values:
# {"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}
#
# A \JSON array or object may contain nested arrays, objects, and scalars
# to any depth:
# {"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}
# [{"foo": 0, "bar": 1}, ["baz", 2]]
#
# == Using \Module \JSON
#
# To make module \JSON available in your code, begin with:
# require 'json'
#
# All examples here assume that this has been done.
#
# === Parsing \JSON
#
# You can parse a \String containing \JSON data using
# either of two methods:
# - <tt>JSON.parse(source, opts)</tt>
# - <tt>JSON.parse!(source, opts)</tt>
#
# where
# - +source+ is a Ruby object.
# - +opts+ is a \Hash object containing options
# that control both input allowed and output formatting.
#
# The difference between the two methods
# is that JSON.parse! omits some checks
# and may not be safe for some +source+ data;
# use it only for data from trusted sources.
# Use the safer method JSON.parse for less trusted sources.
#
# ==== Parsing \JSON Arrays
#
# When +source+ is a \JSON array, JSON.parse by default returns a Ruby \Array:
# json = '["foo", 1, 1.0, 2.0e2, true, false, null]'
# ruby = JSON.parse(json)
# ruby # => ["foo", 1, 1.0, 200.0, true, false, nil]
# ruby.class # => Array
#
# The \JSON array may contain nested arrays, objects, and scalars
# to any depth:
# json = '[{"foo": 0, "bar": 1}, ["baz", 2]]'
# JSON.parse(json) # => [{"foo"=>0, "bar"=>1}, ["baz", 2]]
#
# ==== Parsing \JSON \Objects
#
# When the source is a \JSON object, JSON.parse by default returns a Ruby \Hash:
# json = '{"a": "foo", "b": 1, "c": 1.0, "d": 2.0e2, "e": true, "f": false, "g": null}'
# ruby = JSON.parse(json)
# ruby # => {"a"=>"foo", "b"=>1, "c"=>1.0, "d"=>200.0, "e"=>true, "f"=>false, "g"=>nil}
# ruby.class # => Hash
#
# The \JSON object may contain nested arrays, objects, and scalars
# to any depth:
# json = '{"foo": {"bar": 1, "baz": 2}, "bat": [0, 1, 2]}'
# JSON.parse(json) # => {"foo"=>{"bar"=>1, "baz"=>2}, "bat"=>[0, 1, 2]}
#
# ==== Parsing \JSON Scalars
#
# When the source is a \JSON scalar (not an array or object),
# JSON.parse returns a Ruby scalar.
#
# \String:
# ruby = JSON.parse('"foo"')
# ruby # => 'foo'
# ruby.class # => String
# \Integer:
# ruby = JSON.parse('1')
# ruby # => 1
# ruby.class # => Integer
# \Float:
# ruby = JSON.parse('1.0')
# ruby # => 1.0
# ruby.class # => Float
# ruby = JSON.parse('2.0e2')
# ruby # => 200
# ruby.class # => Float
# Boolean:
# ruby = JSON.parse('true')
# ruby # => true
# ruby.class # => TrueClass
# ruby = JSON.parse('false')
# ruby # => false
# ruby.class # => FalseClass
# Null:
# ruby = JSON.parse('null')
# ruby # => nil
# ruby.class # => NilClass
#
# ==== Parsing Options
#
# ====== Input Options
#
# Option +max_nesting+ (\Integer) specifies the maximum nesting depth allowed;
# defaults to +100+; specify +false+ to disable depth checking.
#
# With the default, +false+:
# source = '[0, [1, [2, [3]]]]'
# ruby = JSON.parse(source)
# ruby # => [0, [1, [2, [3]]]]
# Too deep:
# # Raises JSON::NestingError (nesting of 2 is too deep):
# JSON.parse(source, {max_nesting: 1})
# Bad value:
# # Raises TypeError (wrong argument type Symbol (expected Fixnum)):
# JSON.parse(source, {max_nesting: :foo})
#
# ---
#
# Option +allow_nan+ (boolean) specifies whether to allow
# NaN, Infinity, and MinusInfinity in +source+;
# defaults to +false+.
#
# With the default, +false+:
# # Raises JSON::ParserError (225: unexpected token at '[NaN]'):
# JSON.parse('[NaN]')
# # Raises JSON::ParserError (232: unexpected token at '[Infinity]'):
# JSON.parse('[Infinity]')
# # Raises JSON::ParserError (248: unexpected token at '[-Infinity]'):
# JSON.parse('[-Infinity]')
# Allow:
# source = '[NaN, Infinity, -Infinity]'
# ruby = JSON.parse(source, {allow_nan: true})
# ruby # => [NaN, Infinity, -Infinity]
#
# ====== Output Options
#
# Option +symbolize_names+ (boolean) specifies whether returned \Hash keys
# should be Symbols;
# defaults to +false+ (use Strings).
#
# With the default, +false+:
# source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
# ruby = JSON.parse(source)
# ruby # => {"a"=>"foo", "b"=>1.0, "c"=>true, "d"=>false, "e"=>nil}
# Use Symbols:
# ruby = JSON.parse(source, {symbolize_names: true})
# ruby # => {:a=>"foo", :b=>1.0, :c=>true, :d=>false, :e=>nil}
#
# ---
#
# Option +object_class+ (\Class) specifies the Ruby class to be used
# for each \JSON object;
# defaults to \Hash.
#
# With the default, \Hash:
# source = '{"a": "foo", "b": 1.0, "c": true, "d": false, "e": null}'
# ruby = JSON.parse(source)
# ruby.class # => Hash
# Use class \OpenStruct:
# ruby = JSON.parse(source, {object_class: OpenStruct})
# ruby # => #<OpenStruct a="foo", b=1.0, c=true, d=false, e=nil>
#
# ---
#
# Option +array_class+ (\Class) specifies the Ruby class to be used
# for each \JSON array;
# defaults to \Array.
#
# With the default, \Array:
# source = '["foo", 1.0, true, false, null]'
# ruby = JSON.parse(source)
# ruby.class # => Array
# Use class \Set:
# ruby = JSON.parse(source, {array_class: Set})
# ruby # => #<Set: {"foo", 1.0, true, false, nil}>
#
# ---
#
# Option +create_additions+ (boolean) specifies whether to use \JSON additions in parsing.
# See {\JSON Additions}[#module-JSON-label-JSON+Additions].
#
# === Generating \JSON
#
# To generate a Ruby \String containing \JSON data,
# use method <tt>JSON.generate(source, opts)</tt>, where
# - +source+ is a Ruby object.
# - +opts+ is a \Hash object containing options
# that control both input allowed and output formatting.
#
# ==== Generating \JSON from Arrays
#
# When the source is a Ruby \Array, JSON.generate returns
# a \String containing a \JSON array:
# ruby = [0, 's', :foo]
# json = JSON.generate(ruby)
# json # => '[0,"s","foo"]'
#
# The Ruby \Array array may contain nested arrays, hashes, and scalars
# to any depth:
# ruby = [0, [1, 2], {foo: 3, bar: 4}]
# json = JSON.generate(ruby)
# json # => '[0,[1,2],{"foo":3,"bar":4}]'
#
# ==== Generating \JSON from Hashes
#
# When the source is a Ruby \Hash, JSON.generate returns
# a \String containing a \JSON object:
# ruby = {foo: 0, bar: 's', baz: :bat}
# json = JSON.generate(ruby)
# json # => '{"foo":0,"bar":"s","baz":"bat"}'
#
# The Ruby \Hash array may contain nested arrays, hashes, and scalars
# to any depth:
# ruby = {foo: [0, 1], bar: {baz: 2, bat: 3}, bam: :bad}
# json = JSON.generate(ruby)
# json # => '{"foo":[0,1],"bar":{"baz":2,"bat":3},"bam":"bad"}'
#
# ==== Generating \JSON from Other Objects
#
# When the source is neither an \Array nor a \Hash,
# the generated \JSON data depends on the class of the source.
#
# When the source is a Ruby \Integer or \Float, JSON.generate returns
# a \String containing a \JSON number:
# JSON.generate(42) # => '42'
# JSON.generate(0.42) # => '0.42'
#
# When the source is a Ruby \String, JSON.generate returns
# a \String containing a \JSON string (with double-quotes):
# JSON.generate('A string') # => '"A string"'
#
# When the source is +true+, +false+ or +nil+, JSON.generate returns
# a \String containing the corresponding \JSON token:
# JSON.generate(true) # => 'true'
# JSON.generate(false) # => 'false'
# JSON.generate(nil) # => 'null'
#
# When the source is none of the above, JSON.generate returns
# a \String containing a \JSON string representation of the source:
# JSON.generate(:foo) # => '"foo"'
# JSON.generate(Complex(0, 0)) # => '"0+0i"'
# JSON.generate(Dir.new('.')) # => '"#<Dir>"'
#
# ==== Generating Options
#
# ====== Input Options
#
# Option +allow_nan+ (boolean) specifies whether
# +NaN+, +Infinity+, and <tt>-Infinity</tt> may be generated;
# defaults to +false+.
#
# With the default, +false+:
# # Raises JSON::GeneratorError (920: NaN not allowed in JSON):
# JSON.generate(JSON::NaN)
# # Raises JSON::GeneratorError (917: Infinity not allowed in JSON):
# JSON.generate(JSON::Infinity)
# # Raises JSON::GeneratorError (917: -Infinity not allowed in JSON):
# JSON.generate(JSON::MinusInfinity)
#
# Allow:
# ruby = [Float::NaN, Float::Infinity, Float::MinusInfinity]
# JSON.generate(ruby, allow_nan: true) # => '[NaN,Infinity,-Infinity]'
#
# ---
#
# Option +max_nesting+ (\Integer) specifies the maximum nesting depth
# in +obj+; defaults to +100+.
#
# With the default, +100+:
# obj = [[[[[[0]]]]]]
# JSON.generate(obj) # => '[[[[[[0]]]]]]'
#
# Too deep:
# # Raises JSON::NestingError (nesting of 2 is too deep):
# JSON.generate(obj, max_nesting: 2)
#
# ====== Output Options
#
# The default formatting options generate the most compact
# \JSON data, all on one line and with no whitespace.
#
# You can use these formatting options to generate
# \JSON data in a more open format, using whitespace.
# See also JSON.pretty_generate.
#
# - Option +array_nl+ (\String) specifies a string (usually a newline)
# to be inserted after each \JSON array; defaults to the empty \String, <tt>''</tt>.
# - Option +object_nl+ (\String) specifies a string (usually a newline)
# to be inserted after each \JSON object; defaults to the empty \String, <tt>''</tt>.
# - Option +indent+ (\String) specifies the string (usually spaces) to be
# used for indentation; defaults to the empty \String, <tt>''</tt>;
# defaults to the empty \String, <tt>''</tt>;
# has no effect unless options +array_nl+ or +object_nl+ specify newlines.
# - Option +space+ (\String) specifies a string (usually a space) to be
# inserted after the colon in each \JSON object's pair;
# defaults to the empty \String, <tt>''</tt>.
# - Option +space_before+ (\String) specifies a string (usually a space) to be
# inserted before the colon in each \JSON object's pair;
# defaults to the empty \String, <tt>''</tt>.
#
# In this example, +obj+ is used first to generate the shortest
# \JSON data (no whitespace), then again with all formatting options
# specified:
#
# obj = {foo: [:bar, :baz], bat: {bam: 0, bad: 1}}
# json = JSON.generate(obj)
# puts 'Compact:', json
# opts = {
# array_nl: "\n",
# object_nl: "\n",
# indent: ' ',
# space_before: ' ',
# space: ' '
# }
# puts 'Open:', JSON.generate(obj, opts)
#
# Output:
# Compact:
# {"foo":["bar","baz"],"bat":{"bam":0,"bad":1}}
# Open:
# {
# "foo" : [
# "bar",
# "baz"
# ],
# "bat" : {
# "bam" : 0,
# "bad" : 1
# }
# }
#
# == \JSON Additions
#
# When you "round trip" a non-\String object from Ruby to \JSON and back,
# you have a new \String, instead of the object you began with:
# ruby0 = Range.new(0, 2)
# json = JSON.generate(ruby0)
# json # => '0..2"'
# ruby1 = JSON.parse(json)
# ruby1 # => '0..2'
# ruby1.class # => String
#
# You can use \JSON _additions_ to preserve the original object.
# The addition is an extension of a ruby class, so that:
# - \JSON.generate stores more information in the \JSON string.
# - \JSON.parse, called with option +create_additions+,
# uses that information to create a proper Ruby object.
#
# This example shows a \Range being generated into \JSON
# and parsed back into Ruby, both without and with
# the addition for \Range:
# ruby = Range.new(0, 2)
# # This passage does not use the addition for Range.
# json0 = JSON.generate(ruby)
# ruby0 = JSON.parse(json0)
# # This passage uses the addition for Range.
# require 'json/add/range'
# json1 = JSON.generate(ruby)
# ruby1 = JSON.parse(json1, create_additions: true)
# # Make a nice display.
# display = <<EOT
# Generated JSON:
# Without addition: #{json0} (#{json0.class})
# With addition: #{json1} (#{json1.class})
# Parsed JSON:
# Without addition: #{ruby0.inspect} (#{ruby0.class})
# With addition: #{ruby1.inspect} (#{ruby1.class})
# EOT
# puts display
#
# This output shows the different results:
# Generated JSON:
# Without addition: "0..2" (String)
# With addition: {"json_class":"Range","a":[0,2,false]} (String)
# Parsed JSON:
# Without addition: "0..2" (String)
# With addition: 0..2 (Range)
#
# The \JSON module includes additions for certain classes.
# You can also craft custom additions.
# See {Custom \JSON Additions}[#module-JSON-label-Custom+JSON+Additions].
#
# === Built-in Additions
#
# The \JSON module includes additions for certain classes.
# To use an addition, +require+ its source:
# - BigDecimal: <tt>require 'json/add/bigdecimal'</tt>
# - Complex: <tt>require 'json/add/complex'</tt>
# - Date: <tt>require 'json/add/date'</tt>
# - DateTime: <tt>require 'json/add/date_time'</tt>
# - Exception: <tt>require 'json/add/exception'</tt>
# - OpenStruct: <tt>require 'json/add/ostruct'</tt>
# - Range: <tt>require 'json/add/range'</tt>
# - Rational: <tt>require 'json/add/rational'</tt>
# - Regexp: <tt>require 'json/add/regexp'</tt>
# - Set: <tt>require 'json/add/set'</tt>
# - Struct: <tt>require 'json/add/struct'</tt>
# - Symbol: <tt>require 'json/add/symbol'</tt>
# - Time: <tt>require 'json/add/time'</tt>
#
# To reduce punctuation clutter, the examples below
# show the generated \JSON via +puts+, rather than the usual +inspect+,
#
# \BigDecimal:
# require 'json/add/bigdecimal'
# ruby0 = BigDecimal(0) # 0.0
# json = JSON.generate(ruby0) # {"json_class":"BigDecimal","b":"27:0.0"}
# ruby1 = JSON.parse(json, create_additions: true) # 0.0
# ruby1.class # => BigDecimal
#
# \Complex:
# require 'json/add/complex'
# ruby0 = Complex(1+0i) # 1+0i
# json = JSON.generate(ruby0) # {"json_class":"Complex","r":1,"i":0}
# ruby1 = JSON.parse(json, create_additions: true) # 1+0i
# ruby1.class # Complex
#
# \Date:
# require 'json/add/date'
# ruby0 = Date.today # 2020-05-02
# json = JSON.generate(ruby0) # {"json_class":"Date","y":2020,"m":5,"d":2,"sg":2299161.0}
# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02
# ruby1.class # Date
#
# \DateTime:
# require 'json/add/date_time'
# ruby0 = DateTime.now # 2020-05-02T10:38:13-05:00
# json = JSON.generate(ruby0) # {"json_class":"DateTime","y":2020,"m":5,"d":2,"H":10,"M":38,"S":13,"of":"-5/24","sg":2299161.0}
# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02T10:38:13-05:00
# ruby1.class # DateTime
#
# \Exception (and its subclasses including \RuntimeError):
# require 'json/add/exception'
# ruby0 = Exception.new('A message') # A message
# json = JSON.generate(ruby0) # {"json_class":"Exception","m":"A message","b":null}
# ruby1 = JSON.parse(json, create_additions: true) # A message
# ruby1.class # Exception
# ruby0 = RuntimeError.new('Another message') # Another message
# json = JSON.generate(ruby0) # {"json_class":"RuntimeError","m":"Another message","b":null}
# ruby1 = JSON.parse(json, create_additions: true) # Another message
# ruby1.class # RuntimeError
#
# \OpenStruct:
# require 'json/add/ostruct'
# ruby0 = OpenStruct.new(name: 'Matz', language: 'Ruby') # #<OpenStruct name="Matz", language="Ruby">
# json = JSON.generate(ruby0) # {"json_class":"OpenStruct","t":{"name":"Matz","language":"Ruby"}}
# ruby1 = JSON.parse(json, create_additions: true) # #<OpenStruct name="Matz", language="Ruby">
# ruby1.class # OpenStruct
#
# \Range:
# require 'json/add/range'
# ruby0 = Range.new(0, 2) # 0..2
# json = JSON.generate(ruby0) # {"json_class":"Range","a":[0,2,false]}
# ruby1 = JSON.parse(json, create_additions: true) # 0..2
# ruby1.class # Range
#
# \Rational:
# require 'json/add/rational'
# ruby0 = Rational(1, 3) # 1/3
# json = JSON.generate(ruby0) # {"json_class":"Rational","n":1,"d":3}
# ruby1 = JSON.parse(json, create_additions: true) # 1/3
# ruby1.class # Rational
#
# \Regexp:
# require 'json/add/regexp'
# ruby0 = Regexp.new('foo') # (?-mix:foo)
# json = JSON.generate(ruby0) # {"json_class":"Regexp","o":0,"s":"foo"}
# ruby1 = JSON.parse(json, create_additions: true) # (?-mix:foo)
# ruby1.class # Regexp
#
# \Set:
# require 'json/add/set'
# ruby0 = Set.new([0, 1, 2]) # #<Set: {0, 1, 2}>
# json = JSON.generate(ruby0) # {"json_class":"Set","a":[0,1,2]}
# ruby1 = JSON.parse(json, create_additions: true) # #<Set: {0, 1, 2}>
# ruby1.class # Set
#
# \Struct:
# require 'json/add/struct'
# Customer = Struct.new(:name, :address) # Customer
# ruby0 = Customer.new("Dave", "123 Main") # #<struct Customer name="Dave", address="123 Main">
# json = JSON.generate(ruby0) # {"json_class":"Customer","v":["Dave","123 Main"]}
# ruby1 = JSON.parse(json, create_additions: true) # #<struct Customer name="Dave", address="123 Main">
# ruby1.class # Customer
#
# \Symbol:
# require 'json/add/symbol'
# ruby0 = :foo # foo
# json = JSON.generate(ruby0) # {"json_class":"Symbol","s":"foo"}
# ruby1 = JSON.parse(json, create_additions: true) # foo
# ruby1.class # Symbol
#
# \Time:
# require 'json/add/time'
# ruby0 = Time.now # 2020-05-02 11:28:26 -0500
# json = JSON.generate(ruby0) # {"json_class":"Time","s":1588436906,"n":840560000}
# ruby1 = JSON.parse(json, create_additions: true) # 2020-05-02 11:28:26 -0500
# ruby1.class # Time
#
#
# === Custom \JSON Additions
#
# In addition to the \JSON additions provided,
# you can craft \JSON additions of your own,
# either for Ruby built-in classes or for user-defined classes.
#
# Here's a user-defined class +Foo+:
# class Foo
# attr_accessor :bar, :baz
# def initialize(bar, baz)
# self.bar = bar
# self.baz = baz
# end
# end
#
# Here's the \JSON addition for it:
# # Extend class Foo with JSON addition.
# class Foo
# # Serialize Foo object with its class name and arguments
# def to_json(*args)
# {
# JSON.create_id => self.class.name,
# 'a' => [ bar, baz ]
# }.to_json(*args)
# end
# # Deserialize JSON string by constructing new Foo object with arguments.
# def self.json_create(object)
# new(*object['a'])
# end
# end
#
# Demonstration:
# require 'json'
# # This Foo object has no custom addition.
# foo0 = Foo.new(0, 1)
# json0 = JSON.generate(foo0)
# obj0 = JSON.parse(json0)
# # Lood the custom addition.
# require_relative 'foo_addition'
# # This foo has the custom addition.
# foo1 = Foo.new(0, 1)
# json1 = JSON.generate(foo1)
# obj1 = JSON.parse(json1, create_additions: true)
# # Make a nice display.
# display = <<EOT
# Generated JSON:
# Without custom addition: #{json0} (#{json0.class})
# With custom addition: #{json1} (#{json1.class})
# Parsed JSON:
# Without custom addition: #{obj0.inspect} (#{obj0.class})
# With custom addition: #{obj1.inspect} (#{obj1.class})
# EOT
# puts display
#
# Output:
#
# Generated JSON:
# Without custom addition: "#<Foo:0x0000000006534e80>" (String)
# With custom addition: {"json_class":"Foo","a":[0,1]} (String)
# Parsed JSON:
# Without custom addition: "#<Foo:0x0000000006534e80>" (String)
# With custom addition: #<Foo:0x0000000006473bb8 @bar=0, @baz=1> (Foo)
#
module JSON
require 'json/version'
begin
require 'json/ext'
rescue LoadError
require 'json/pure'
end
end
share/ruby/prettyprint.rb 0000644 00000037631 15173547334 0011606 0 ustar 00 # 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 an Integer
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 an Integer, 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+ Integer 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
share/ruby/base64.rb 0000644 00000006553 15173547334 0010265 0 ustar 00 # frozen_string_literal: true
#
# = base64.rb: methods for base64-encoding and -decoding strings
#
# The Base64 module provides for the encoding (#encode64, #strict_encode64,
# #urlsafe_encode64) and decoding (#decode64, #strict_decode64,
# #urlsafe_decode64) of binary data using a Base64 representation.
#
# == Example
#
# A simple encoding and decoding.
#
# require "base64"
#
# enc = Base64.encode64('Send reinforcements')
# # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n"
# plain = Base64.decode64(enc)
# # -> "Send reinforcements"
#
# The purpose of using base64 to encode data is that it translates any
# binary data into purely printable characters.
module Base64
module_function
# Returns the Base64-encoded version of +bin+.
# This method complies with RFC 2045.
# Line feeds are added to every 60 encoded characters.
#
# require 'base64'
# Base64.encode64("Now is the time for all good coders\nto learn Ruby")
#
# <i>Generates:</i>
#
# Tm93IGlzIHRoZSB0aW1lIGZvciBhbGwgZ29vZCBjb2RlcnMKdG8gbGVhcm4g
# UnVieQ==
def encode64(bin)
[bin].pack("m")
end
# Returns the Base64-decoded version of +str+.
# This method complies with RFC 2045.
# Characters outside the base alphabet are ignored.
#
# require 'base64'
# str = 'VGhpcyBpcyBsaW5lIG9uZQpUaGlzIG' +
# 'lzIGxpbmUgdHdvClRoaXMgaXMgbGlu' +
# 'ZSB0aHJlZQpBbmQgc28gb24uLi4K'
# puts Base64.decode64(str)
#
# <i>Generates:</i>
#
# This is line one
# This is line two
# This is line three
# And so on...
def decode64(str)
str.unpack1("m")
end
# Returns the Base64-encoded version of +bin+.
# This method complies with RFC 4648.
# No line feeds are added.
def strict_encode64(bin)
[bin].pack("m0")
end
# Returns the Base64-decoded version of +str+.
# This method complies with RFC 4648.
# ArgumentError is raised if +str+ is incorrectly padded or contains
# non-alphabet characters. Note that CR or LF are also rejected.
def strict_decode64(str)
str.unpack1("m0")
end
# Returns the Base64-encoded version of +bin+.
# This method complies with ``Base 64 Encoding with URL and Filename Safe
# Alphabet'' in RFC 4648.
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
# Note that the result can still contain '='.
# You can remove the padding by setting +padding+ as false.
def urlsafe_encode64(bin, padding: true)
str = strict_encode64(bin)
str.chomp!("==") or str.chomp!("=") unless padding
str.tr!("+/", "-_")
str
end
# Returns the Base64-decoded version of +str+.
# This method complies with ``Base 64 Encoding with URL and Filename Safe
# Alphabet'' in RFC 4648.
# The alphabet uses '-' instead of '+' and '_' instead of '/'.
#
# The padding character is optional.
# This method accepts both correctly-padded and unpadded input.
# Note that it still rejects incorrectly-padded input.
def urlsafe_decode64(str)
# NOTE: RFC 4648 does say nothing about unpadded input, but says that
# "the excess pad characters MAY also be ignored", so it is inferred that
# unpadded input is also acceptable.
if !str.end_with?("=") && str.length % 4 != 0
str = str.ljust((str.length + 3) & ~3, "=")
str.tr!("-_", "+/")
else
str = str.tr("-_", "+/")
end
strict_decode64(str)
end
end
share/ruby/did_you_mean.rb 0000644 00000012500 15173547334 0011622 0 ustar 00 require_relative "did_you_mean/version"
require_relative "did_you_mean/core_ext/name_error"
require_relative "did_you_mean/spell_checker"
require_relative 'did_you_mean/spell_checkers/name_error_checkers'
require_relative 'did_you_mean/spell_checkers/method_name_checker'
require_relative 'did_you_mean/spell_checkers/key_error_checker'
require_relative 'did_you_mean/spell_checkers/null_checker'
require_relative 'did_you_mean/spell_checkers/require_path_checker'
require_relative 'did_you_mean/spell_checkers/pattern_key_name_checker'
require_relative 'did_you_mean/formatter'
require_relative 'did_you_mean/tree_spell_checker'
# The +DidYouMean+ gem adds functionality to suggest possible method/class
# names upon errors such as +NameError+ and +NoMethodError+. In Ruby 2.3 or
# later, it is automatically activated during startup.
#
# @example
#
# methosd
# # => NameError: undefined local variable or method `methosd' for main:Object
# # Did you mean? methods
# # method
#
# OBject
# # => NameError: uninitialized constant OBject
# # Did you mean? Object
#
# @full_name = "Yuki Nishijima"
# first_name, last_name = full_name.split(" ")
# # => NameError: undefined local variable or method `full_name' for main:Object
# # Did you mean? @full_name
#
# @@full_name = "Yuki Nishijima"
# @@full_anme
# # => NameError: uninitialized class variable @@full_anme in Object
# # Did you mean? @@full_name
#
# full_name = "Yuki Nishijima"
# full_name.starts_with?("Y")
# # => NoMethodError: undefined method `starts_with?' for "Yuki Nishijima":String
# # Did you mean? start_with?
#
# hash = {foo: 1, bar: 2, baz: 3}
# hash.fetch(:fooo)
# # => KeyError: key not found: :fooo
# # Did you mean? :foo
#
#
# == Disabling +did_you_mean+
#
# Occasionally, you may want to disable the +did_you_mean+ gem for e.g.
# debugging issues in the error object itself. You can disable it entirely by
# specifying +--disable-did_you_mean+ option to the +ruby+ command:
#
# $ ruby --disable-did_you_mean -e "1.zeor?"
# -e:1:in `<main>': undefined method `zeor?' for 1:Integer (NameError)
#
# When you do not have direct access to the +ruby+ command (e.g.
# +rails console+, +irb+), you could applyoptions using the +RUBYOPT+
# environment variable:
#
# $ RUBYOPT='--disable-did_you_mean' irb
# irb:0> 1.zeor?
# # => NoMethodError (undefined method `zeor?' for 1:Integer)
#
#
# == Getting the original error message
#
# Sometimes, you do not want to disable the gem entirely, but need to get the
# original error message without suggestions (e.g. testing). In this case, you
# could use the +#original_message+ method on the error object:
#
# no_method_error = begin
# 1.zeor?
# rescue NoMethodError => error
# error
# end
#
# no_method_error.message
# # => NoMethodError (undefined method `zeor?' for 1:Integer)
# # Did you mean? zero?
#
# no_method_error.original_message
# # => NoMethodError (undefined method `zeor?' for 1:Integer)
#
module DidYouMean
# Map of error types and spell checker objects.
@spell_checkers = Hash.new(NullChecker)
# Returns a sharable hash map of error types and spell checker objects.
def self.spell_checkers
@spell_checkers
end
# Adds +DidYouMean+ functionality to an error using a given spell checker
def self.correct_error(error_class, spell_checker)
if defined?(Ractor)
new_mapping = { **@spell_checkers, error_class.to_s => spell_checker }
new_mapping.default = NullChecker
@spell_checkers = Ractor.make_shareable(new_mapping)
else
spell_checkers[error_class.to_s] = spell_checker
end
error_class.prepend(Correctable) if error_class.is_a?(Class) && !(error_class < Correctable)
end
correct_error NameError, NameErrorCheckers
correct_error KeyError, KeyErrorChecker
correct_error NoMethodError, MethodNameChecker
correct_error LoadError, RequirePathChecker if RUBY_VERSION >= '2.8.0'
correct_error NoMatchingPatternKeyError, PatternKeyNameChecker if defined?(::NoMatchingPatternKeyError)
# TODO: Remove on 3.3:
class DeprecatedMapping # :nodoc:
def []=(key, value)
warn "Calling `DidYouMean::SPELL_CHECKERS[#{key.to_s}] = #{value.to_s}' has been deprecated. " \
"Please call `DidYouMean.correct_error(#{key.to_s}, #{value.to_s})' instead."
DidYouMean.correct_error(key, value)
end
def merge!(hash)
warn "Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. " \
"Please call `DidYouMean.correct_error(error_name, spell_checker)' instead."
hash.each do |error_class, spell_checker|
DidYouMean.correct_error(error_class, spell_checker)
end
end
end
# TODO: Remove on 3.3:
SPELL_CHECKERS = DeprecatedMapping.new
deprecate_constant :SPELL_CHECKERS
private_constant :DeprecatedMapping
# Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+.
def self.formatter
if defined?(Ractor)
Ractor.current[:__did_you_mean_formatter__] || Formatter
else
Formatter
end
end
# Updates the primary formatter used to format the suggestions.
def self.formatter=(formatter)
if defined?(Ractor)
Ractor.current[:__did_you_mean_formatter__] = formatter
end
end
end
share/ruby/benchmark/version.rb 0000644 00000000107 15173547334 0012605 0 ustar 00 # frozen_string_literal: true
module Benchmark
VERSION = "0.2.1"
end
share/ruby/securerandom.rb 0000644 00000004162 15173547334 0011662 0 ustar 00 # -*- coding: us-ascii -*-
# frozen_string_literal: true
require 'random/formatter'
# == Secure random number generator interface.
#
# This library is an interface to secure random number generators which are
# suitable for generating session keys in HTTP cookies, etc.
#
# You can use this library in your application by requiring it:
#
# require 'securerandom'
#
# It supports the following secure random number generators:
#
# * openssl
# * /dev/urandom
# * Win32
#
# SecureRandom is extended by the Random::Formatter module which
# defines the following methods:
#
# * alphanumeric
# * base64
# * choose
# * gen_random
# * hex
# * rand
# * random_bytes
# * random_number
# * urlsafe_base64
# * uuid
#
# These methods are usable as class methods of SecureRandom such as
# +SecureRandom.hex+.
#
# If a secure random number generator is not available,
# +NotImplementedError+ is raised.
module SecureRandom
class << self
def bytes(n)
return gen_random(n)
end
private
def gen_random_openssl(n)
@pid = 0 unless defined?(@pid)
pid = $$
unless @pid == pid
now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
OpenSSL::Random.random_add([now, @pid, pid].join(""), 0.0)
seed = Random.urandom(16)
if (seed)
OpenSSL::Random.random_add(seed, 16)
end
@pid = pid
end
return OpenSSL::Random.random_bytes(n)
end
def gen_random_urandom(n)
ret = Random.urandom(n)
unless ret
raise NotImplementedError, "No random device"
end
unless ret.length == n
raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes"
end
ret
end
begin
# Check if Random.urandom is available
Random.urandom(1)
alias gen_random gen_random_urandom
rescue RuntimeError
begin
require 'openssl'
rescue NoMethodError
raise NotImplementedError, "No random device"
else
alias gen_random gen_random_openssl
end
end
public :gen_random
end
end
SecureRandom.extend(Random::Formatter)
share/ruby/kconv.rb 0000644 00000013345 15173547334 0010316 0 ustar 00 # frozen_string_literal: false
#
# kconv.rb - Kanji Converter.
#
# $Id$
#
# ----
#
# kconv.rb implements the Kconv class for Kanji Converter. Additionally,
# some methods in String classes are added to allow easy conversion.
#
require 'nkf'
#
# Kanji Converter for Ruby.
#
module Kconv
#
# Public Constants
#
#Constant of Encoding
# Auto-Detect
AUTO = NKF::AUTO
# ISO-2022-JP
JIS = NKF::JIS
# EUC-JP
EUC = NKF::EUC
# Shift_JIS
SJIS = NKF::SJIS
# BINARY
BINARY = NKF::BINARY
# NOCONV
NOCONV = NKF::NOCONV
# ASCII
ASCII = NKF::ASCII
# UTF-8
UTF8 = NKF::UTF8
# UTF-16
UTF16 = NKF::UTF16
# UTF-32
UTF32 = NKF::UTF32
# UNKNOWN
UNKNOWN = NKF::UNKNOWN
#
# Public Methods
#
# call-seq:
# Kconv.kconv(str, to_enc, from_enc=nil)
#
# Convert <code>str</code> to <code>to_enc</code>.
# <code>to_enc</code> and <code>from_enc</code> are given as constants of Kconv or Encoding objects.
def kconv(str, to_enc, from_enc=nil)
opt = ''
opt += ' --ic=' + from_enc.to_s if from_enc
opt += ' --oc=' + to_enc.to_s if to_enc
::NKF::nkf(opt, str)
end
module_function :kconv
#
# Encode to
#
# call-seq:
# Kconv.tojis(str) => string
#
# Convert <code>str</code> to ISO-2022-JP
def tojis(str)
kconv(str, JIS)
end
module_function :tojis
# call-seq:
# Kconv.toeuc(str) => string
#
# Convert <code>str</code> to EUC-JP
def toeuc(str)
kconv(str, EUC)
end
module_function :toeuc
# call-seq:
# Kconv.tosjis(str) => string
#
# Convert <code>str</code> to Shift_JIS
def tosjis(str)
kconv(str, SJIS)
end
module_function :tosjis
# call-seq:
# Kconv.toutf8(str) => string
#
# Convert <code>str</code> to UTF-8
def toutf8(str)
kconv(str, UTF8)
end
module_function :toutf8
# call-seq:
# Kconv.toutf16(str) => string
#
# Convert <code>str</code> to UTF-16
def toutf16(str)
kconv(str, UTF16)
end
module_function :toutf16
# call-seq:
# Kconv.toutf32(str) => string
#
# Convert <code>str</code> to UTF-32
def toutf32(str)
kconv(str, UTF32)
end
module_function :toutf32
# call-seq:
# Kconv.tolocale => string
#
# Convert <code>self</code> to locale encoding
def tolocale(str)
kconv(str, Encoding.locale_charmap)
end
module_function :tolocale
#
# guess
#
# call-seq:
# Kconv.guess(str) => encoding
#
# Guess input encoding by NKF.guess
def guess(str)
::NKF::guess(str)
end
module_function :guess
#
# isEncoding
#
# call-seq:
# Kconv.iseuc(str) => true or false
#
# Returns whether input encoding is EUC-JP or not.
#
# *Note* don't expect this return value is MatchData.
def iseuc(str)
str.dup.force_encoding(EUC).valid_encoding?
end
module_function :iseuc
# call-seq:
# Kconv.issjis(str) => true or false
#
# Returns whether input encoding is Shift_JIS or not.
def issjis(str)
str.dup.force_encoding(SJIS).valid_encoding?
end
module_function :issjis
# call-seq:
# Kconv.isjis(str) => true or false
#
# Returns whether input encoding is ISO-2022-JP or not.
def isjis(str)
/\A [\t\n\r\x20-\x7E]*
(?:
(?:\x1b \x28 I [\x21-\x7E]*
|\x1b \x28 J [\x21-\x7E]*
|\x1b \x24 @ (?:[\x21-\x7E]{2})*
|\x1b \x24 B (?:[\x21-\x7E]{2})*
|\x1b \x24 \x28 D (?:[\x21-\x7E]{2})*
)*
\x1b \x28 B [\t\n\r\x20-\x7E]*
)*
\z/nox =~ str.dup.force_encoding('BINARY') ? true : false
end
module_function :isjis
# call-seq:
# Kconv.isutf8(str) => true or false
#
# Returns whether input encoding is UTF-8 or not.
def isutf8(str)
str.dup.force_encoding(UTF8).valid_encoding?
end
module_function :isutf8
end
class String
# call-seq:
# String#kconv(to_enc, from_enc)
#
# Convert <code>self</code> to <code>to_enc</code>.
# <code>to_enc</code> and <code>from_enc</code> are given as constants of Kconv or Encoding objects.
def kconv(to_enc, from_enc=nil)
from_enc = self.encoding if !from_enc && self.encoding != Encoding.list[0]
Kconv::kconv(self, to_enc, from_enc)
end
#
# to Encoding
#
# call-seq:
# String#tojis => string
#
# Convert <code>self</code> to ISO-2022-JP
def tojis; Kconv.tojis(self) end
# call-seq:
# String#toeuc => string
#
# Convert <code>self</code> to EUC-JP
def toeuc; Kconv.toeuc(self) end
# call-seq:
# String#tosjis => string
#
# Convert <code>self</code> to Shift_JIS
def tosjis; Kconv.tosjis(self) end
# call-seq:
# String#toutf8 => string
#
# Convert <code>self</code> to UTF-8
def toutf8; Kconv.toutf8(self) end
# call-seq:
# String#toutf16 => string
#
# Convert <code>self</code> to UTF-16
def toutf16; Kconv.toutf16(self) end
# call-seq:
# String#toutf32 => string
#
# Convert <code>self</code> to UTF-32
def toutf32; Kconv.toutf32(self) end
# call-seq:
# String#tolocale => string
#
# Convert <code>self</code> to locale encoding
def tolocale; Kconv.tolocale(self) end
#
# is Encoding
#
# call-seq:
# String#iseuc => true or false
#
# Returns whether <code>self</code>'s encoding is EUC-JP or not.
def iseuc; Kconv.iseuc(self) end
# call-seq:
# String#issjis => true or false
#
# Returns whether <code>self</code>'s encoding is Shift_JIS or not.
def issjis; Kconv.issjis(self) end
# call-seq:
# String#isjis => true or false
#
# Returns whether <code>self</code>'s encoding is ISO-2022-JP or not.
def isjis; Kconv.isjis(self) end
# call-seq:
# String#isutf8 => true or false
#
# Returns whether <code>self</code>'s encoding is UTF-8 or not.
def isutf8; Kconv.isutf8(self) end
end
share/ruby/optionparser.rb 0000644 00000000073 15173547334 0011715 0 ustar 00 # frozen_string_literal: false
require_relative 'optparse'
share/ruby/open-uri.rb 0000644 00000063022 15173547334 0010731 0 ustar 00 # frozen_string_literal: true
require 'uri'
require 'stringio'
require 'time'
module URI
# Allows the opening of various resources including URIs.
#
# If the first argument responds to the 'open' method, 'open' is called on
# it with the rest of the arguments.
#
# If the first argument is a string that begins with <code>(protocol)://</code>, it is parsed by
# URI.parse. If the parsed object responds to the 'open' method,
# 'open' is called on it with the rest of the arguments.
#
# Otherwise, Kernel#open is called.
#
# OpenURI::OpenRead#open provides URI::HTTP#open, URI::HTTPS#open and
# URI::FTP#open, Kernel#open.
#
# We can accept URIs and strings that begin with http://, https:// and
# ftp://. In these cases, the opened file object is extended by OpenURI::Meta.
def self.open(name, *rest, &block)
if name.respond_to?(:open)
name.open(*rest, &block)
elsif name.respond_to?(:to_str) &&
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
(uri = URI.parse(name)).respond_to?(:open)
uri.open(*rest, &block)
else
super
end
end
end
# OpenURI is an easy-to-use wrapper for Net::HTTP, Net::HTTPS and Net::FTP.
#
# == Example
#
# It is possible to open an http, https or ftp URL as though it were a file:
#
# URI.open("http://www.ruby-lang.org/") {|f|
# f.each_line {|line| p line}
# }
#
# The opened file has several getter methods for its meta-information, as
# follows, since it is extended by OpenURI::Meta.
#
# URI.open("http://www.ruby-lang.org/en") {|f|
# f.each_line {|line| p line}
# p f.base_uri # <URI::HTTP:0x40e6ef2 URL:http://www.ruby-lang.org/en/>
# p f.content_type # "text/html"
# p f.charset # "iso-8859-1"
# p f.content_encoding # []
# p f.last_modified # Thu Dec 05 02:45:02 UTC 2002
# }
#
# Additional header fields can be specified by an optional hash argument.
#
# URI.open("http://www.ruby-lang.org/en/",
# "User-Agent" => "Ruby/#{RUBY_VERSION}",
# "From" => "foo@bar.invalid",
# "Referer" => "http://www.ruby-lang.org/") {|f|
# # ...
# }
#
# The environment variables such as http_proxy, https_proxy and ftp_proxy
# are in effect by default. Here we disable proxy:
#
# URI.open("http://www.ruby-lang.org/en/", :proxy => nil) {|f|
# # ...
# }
#
# See OpenURI::OpenRead.open and URI.open for more on available options.
#
# URI objects can be opened in a similar way.
#
# uri = URI.parse("http://www.ruby-lang.org/en/")
# uri.open {|f|
# # ...
# }
#
# URI objects can be read directly. The returned string is also extended by
# OpenURI::Meta.
#
# str = uri.read
# p str.base_uri
#
# Author:: Tanaka Akira <akr@m17n.org>
module OpenURI
Options = {
:proxy => true,
:proxy_http_basic_authentication => true,
:progress_proc => true,
:content_length_proc => true,
:http_basic_authentication => true,
:read_timeout => true,
:open_timeout => true,
:ssl_ca_cert => nil,
:ssl_verify_mode => nil,
:ssl_min_version => nil,
:ssl_max_version => nil,
:ftp_active_mode => false,
:redirect => true,
:encoding => nil,
}
def OpenURI.check_options(options) # :nodoc:
options.each {|k, v|
next unless Symbol === k
unless Options.include? k
raise ArgumentError, "unrecognized option: #{k}"
end
}
end
def OpenURI.scan_open_optional_arguments(*rest) # :nodoc:
if !rest.empty? && (String === rest.first || Integer === rest.first)
mode = rest.shift
if !rest.empty? && Integer === rest.first
perm = rest.shift
end
end
return mode, perm, rest
end
def OpenURI.open_uri(name, *rest) # :nodoc:
uri = URI::Generic === name ? name : URI.parse(name)
mode, _, rest = OpenURI.scan_open_optional_arguments(*rest)
options = rest.shift if !rest.empty? && Hash === rest.first
raise ArgumentError.new("extra arguments") if !rest.empty?
options ||= {}
OpenURI.check_options(options)
if /\Arb?(?:\Z|:([^:]+))/ =~ mode
encoding, = $1,Encoding.find($1) if $1
mode = nil
end
if options.has_key? :encoding
if !encoding.nil?
raise ArgumentError, "encoding specified twice"
end
encoding = Encoding.find(options[:encoding])
end
unless mode == nil ||
mode == 'r' || mode == 'rb' ||
mode == File::RDONLY
raise ArgumentError.new("invalid access mode #{mode} (#{uri.class} resource is read only.)")
end
io = open_loop(uri, options)
io.set_encoding(encoding) if encoding
if block_given?
begin
yield io
ensure
if io.respond_to? :close!
io.close! # Tempfile
else
io.close if !io.closed?
end
end
else
io
end
end
def OpenURI.open_loop(uri, options) # :nodoc:
proxy_opts = []
proxy_opts << :proxy_http_basic_authentication if options.include? :proxy_http_basic_authentication
proxy_opts << :proxy if options.include? :proxy
proxy_opts.compact!
if 1 < proxy_opts.length
raise ArgumentError, "multiple proxy options specified"
end
case proxy_opts.first
when :proxy_http_basic_authentication
opt_proxy, proxy_user, proxy_pass = options.fetch(:proxy_http_basic_authentication)
proxy_user = proxy_user.to_str
proxy_pass = proxy_pass.to_str
if opt_proxy == true
raise ArgumentError.new("Invalid authenticated proxy option: #{options[:proxy_http_basic_authentication].inspect}")
end
when :proxy
opt_proxy = options.fetch(:proxy)
proxy_user = nil
proxy_pass = nil
when nil
opt_proxy = true
proxy_user = nil
proxy_pass = nil
end
case opt_proxy
when true
find_proxy = lambda {|u| pxy = u.find_proxy; pxy ? [pxy, nil, nil] : nil}
when nil, false
find_proxy = lambda {|u| nil}
when String
opt_proxy = URI.parse(opt_proxy)
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
when URI::Generic
find_proxy = lambda {|u| [opt_proxy, proxy_user, proxy_pass]}
else
raise ArgumentError.new("Invalid proxy option: #{opt_proxy}")
end
uri_set = {}
buf = nil
while true
redirect = catch(:open_uri_redirect) {
buf = Buffer.new
uri.buffer_open(buf, find_proxy.call(uri), options)
nil
}
if redirect
if redirect.relative?
# Although it violates RFC2616, Location: field may have relative
# URI. It is converted to absolute URI using uri as a base URI.
redirect = uri + redirect
end
if !options.fetch(:redirect, true)
raise HTTPRedirect.new(buf.io.status.join(' '), buf.io, redirect)
end
unless OpenURI.redirectable?(uri, redirect)
raise "redirection forbidden: #{uri} -> #{redirect}"
end
if options.include? :http_basic_authentication
# send authentication only for the URI directly specified.
options = options.dup
options.delete :http_basic_authentication
end
uri = redirect
raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s
uri_set[uri.to_s] = true
else
break
end
end
io = buf.io
io.base_uri = uri
io
end
def OpenURI.redirectable?(uri1, uri2) # :nodoc:
# This test is intended to forbid a redirection from http://... to
# file:///etc/passwd, file:///dev/zero, etc. CVE-2011-1521
# https to http redirect is also forbidden intentionally.
# It avoids sending secure cookie or referer by non-secure HTTP protocol.
# (RFC 2109 4.3.1, RFC 2965 3.3, RFC 2616 15.1.3)
# However this is ad hoc. It should be extensible/configurable.
uri1.scheme.downcase == uri2.scheme.downcase ||
(/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:https?|ftp)\z/i =~ uri2.scheme)
end
def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
if proxy
proxy_uri, proxy_user, proxy_pass = proxy
raise "Non-HTTP proxy URI: #{proxy_uri}" if proxy_uri.class != URI::HTTP
end
if target.userinfo
raise ArgumentError, "userinfo not supported. [RFC3986]"
end
header = {}
options.each {|k, v| header[k] = v if String === k }
require 'net/http'
klass = Net::HTTP
if URI::HTTP === target
# HTTP or HTTPS
if proxy
unless proxy_user && proxy_pass
proxy_user, proxy_pass = proxy_uri.userinfo.split(':') if proxy_uri.userinfo
end
if proxy_user && proxy_pass
klass = Net::HTTP::Proxy(proxy_uri.hostname, proxy_uri.port, proxy_user, proxy_pass)
else
klass = Net::HTTP::Proxy(proxy_uri.hostname, proxy_uri.port)
end
end
target_host = target.hostname
target_port = target.port
request_uri = target.request_uri
else
# FTP over HTTP proxy
target_host = proxy_uri.hostname
target_port = proxy_uri.port
request_uri = target.to_s
if proxy_user && proxy_pass
header["Proxy-Authorization"] =
'Basic ' + ["#{proxy_user}:#{proxy_pass}"].pack('m0')
end
end
http = proxy ? klass.new(target_host, target_port) : klass.new(target_host, target_port, nil)
if target.class == URI::HTTPS
require 'net/https'
http.use_ssl = true
http.verify_mode = options[:ssl_verify_mode] || OpenSSL::SSL::VERIFY_PEER
http.min_version = options[:ssl_min_version]
http.max_version = options[:ssl_max_version]
store = OpenSSL::X509::Store.new
if options[:ssl_ca_cert]
Array(options[:ssl_ca_cert]).each do |cert|
if File.directory? cert
store.add_path cert
else
store.add_file cert
end
end
else
store.set_default_paths
end
http.cert_store = store
end
if options.include? :read_timeout
http.read_timeout = options[:read_timeout]
end
if options.include? :open_timeout
http.open_timeout = options[:open_timeout]
end
resp = nil
http.start {
req = Net::HTTP::Get.new(request_uri, header)
if options.include? :http_basic_authentication
user, pass = options[:http_basic_authentication]
req.basic_auth user, pass
end
http.request(req) {|response|
resp = response
if options[:content_length_proc] && Net::HTTPSuccess === resp
if resp.key?('Content-Length')
options[:content_length_proc].call(resp['Content-Length'].to_i)
else
options[:content_length_proc].call(nil)
end
end
resp.read_body {|str|
buf << str
if options[:progress_proc] && Net::HTTPSuccess === resp
options[:progress_proc].call(buf.size)
end
str.clear
}
}
}
io = buf.io
io.rewind
io.status = [resp.code, resp.message]
resp.each_name {|name| buf.io.meta_add_field2 name, resp.get_fields(name) }
case resp
when Net::HTTPSuccess
when Net::HTTPMovedPermanently, # 301
Net::HTTPFound, # 302
Net::HTTPSeeOther, # 303
Net::HTTPTemporaryRedirect, # 307
Net::HTTPPermanentRedirect # 308
begin
loc_uri = URI.parse(resp['location'])
rescue URI::InvalidURIError
raise OpenURI::HTTPError.new(io.status.join(' ') + ' (Invalid Location URI)', io)
end
throw :open_uri_redirect, loc_uri
else
raise OpenURI::HTTPError.new(io.status.join(' '), io)
end
end
class HTTPError < StandardError
def initialize(message, io)
super(message)
@io = io
end
attr_reader :io
end
# Raised on redirection,
# only occurs when +redirect+ option for HTTP is +false+.
class HTTPRedirect < HTTPError
def initialize(message, io, uri)
super(message, io)
@uri = uri
end
attr_reader :uri
end
class Buffer # :nodoc: all
def initialize
@io = StringIO.new
@size = 0
end
attr_reader :size
StringMax = 10240
def <<(str)
@io << str
@size += str.length
if StringIO === @io && StringMax < @size
require 'tempfile'
io = Tempfile.new('open-uri')
io.binmode
Meta.init io, @io if Meta === @io
io << @io.string
@io = io
end
end
def io
Meta.init @io unless Meta === @io
@io
end
end
# :stopdoc:
RE_LWS = /[\r\n\t ]+/n
RE_TOKEN = %r{[^\x00- ()<>@,;:\\"/\[\]?={}\x7f]+}n
RE_QUOTED_STRING = %r{"(?:[\r\n\t !#-\[\]-~\x80-\xff]|\\[\x00-\x7f])*"}n
RE_PARAMETERS = %r{(?:;#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?=#{RE_LWS}?(?:#{RE_TOKEN}|#{RE_QUOTED_STRING})#{RE_LWS}?)*}n
# :startdoc:
# Mixin for holding meta-information.
module Meta
def Meta.init(obj, src=nil) # :nodoc:
obj.extend Meta
obj.instance_eval {
@base_uri = nil
@meta = {} # name to string. legacy.
@metas = {} # name to array of strings.
}
if src
obj.status = src.status
obj.base_uri = src.base_uri
src.metas.each {|name, values|
obj.meta_add_field2(name, values)
}
end
end
# returns an Array that consists of status code and message.
attr_accessor :status
# returns a URI that is the base of relative URIs in the data.
# It may differ from the URI supplied by a user due to redirection.
attr_accessor :base_uri
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash values are a field body.
# If there are multiple field with same field name,
# the field values are concatenated with a comma.
attr_reader :meta
# returns a Hash that represents header fields.
# The Hash keys are downcased for canonicalization.
# The Hash value are an array of field values.
attr_reader :metas
def meta_setup_encoding # :nodoc:
charset = self.charset
enc = nil
if charset
begin
enc = Encoding.find(charset)
rescue ArgumentError
end
end
enc = Encoding::ASCII_8BIT unless enc
if self.respond_to? :force_encoding
self.force_encoding(enc)
elsif self.respond_to? :string
self.string.force_encoding(enc)
else # Tempfile
self.set_encoding enc
end
end
def meta_add_field2(name, values) # :nodoc:
name = name.downcase
@metas[name] = values
@meta[name] = values.join(', ')
meta_setup_encoding if name == 'content-type'
end
def meta_add_field(name, value) # :nodoc:
meta_add_field2(name, [value])
end
# returns a Time that represents the Last-Modified field.
def last_modified
if vs = @metas['last-modified']
v = vs.join(', ')
Time.httpdate(v)
else
nil
end
end
def content_type_parse # :nodoc:
vs = @metas['content-type']
# The last (?:;#{RE_LWS}?)? matches extra ";" which violates RFC2045.
if vs && %r{\A#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?/(#{RE_TOKEN})#{RE_LWS}?(#{RE_PARAMETERS})(?:;#{RE_LWS}?)?\z}no =~ vs.join(', ')
type = $1.downcase
subtype = $2.downcase
parameters = []
$3.scan(/;#{RE_LWS}?(#{RE_TOKEN})#{RE_LWS}?=#{RE_LWS}?(?:(#{RE_TOKEN})|(#{RE_QUOTED_STRING}))/no) {|att, val, qval|
if qval
val = qval[1...-1].gsub(/[\r\n\t !#-\[\]-~\x80-\xff]+|(\\[\x00-\x7f])/n) { $1 ? $1[1,1] : $& }
end
parameters << [att.downcase, val]
}
["#{type}/#{subtype}", *parameters]
else
nil
end
end
# returns "type/subtype" which is MIME Content-Type.
# It is downcased for canonicalization.
# Content-Type parameters are stripped.
def content_type
type, *_ = content_type_parse
type || 'application/octet-stream'
end
# returns a charset parameter in Content-Type field.
# It is downcased for canonicalization.
#
# If charset parameter is not given but a block is given,
# the block is called and its result is returned.
# It can be used to guess charset.
#
# If charset parameter and block is not given,
# nil is returned except text type.
# In that case, "utf-8" is returned as defined by RFC6838 4.2.1
def charset
type, *parameters = content_type_parse
if pair = parameters.assoc('charset')
pair.last.downcase
elsif block_given?
yield
elsif type && %r{\Atext/} =~ type
"utf-8" # RFC6838 4.2.1
else
nil
end
end
# Returns a list of encodings in Content-Encoding field as an array of
# strings.
#
# The encodings are downcased for canonicalization.
def content_encoding
vs = @metas['content-encoding']
if vs && %r{\A#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?(?:,#{RE_LWS}?#{RE_TOKEN}#{RE_LWS}?)*}o =~ (v = vs.join(', '))
v.scan(RE_TOKEN).map {|content_coding| content_coding.downcase}
else
[]
end
end
end
# Mixin for HTTP and FTP URIs.
module OpenRead
# OpenURI::OpenRead#open provides `open' for URI::HTTP and URI::FTP.
#
# OpenURI::OpenRead#open takes optional 3 arguments as:
#
# OpenURI::OpenRead#open([mode [, perm]] [, options]) [{|io| ... }]
#
# OpenURI::OpenRead#open returns an IO-like object if block is not given.
# Otherwise it yields the IO object and return the value of the block.
# The IO object is extended with OpenURI::Meta.
#
# +mode+ and +perm+ are the same as Kernel#open.
#
# However, +mode+ must be read mode because OpenURI::OpenRead#open doesn't
# support write mode (yet).
# Also +perm+ is ignored because it is meaningful only for file creation.
#
# +options+ must be a hash.
#
# Each option with a string key specifies an extra header field for HTTP.
# I.e., it is ignored for FTP without HTTP proxy.
#
# The hash may include other options, where keys are symbols:
#
# [:proxy]
# Synopsis:
# :proxy => "http://proxy.foo.com:8000/"
# :proxy => URI.parse("http://proxy.foo.com:8000/")
# :proxy => true
# :proxy => false
# :proxy => nil
#
# If :proxy option is specified, the value should be String, URI,
# boolean or nil.
#
# When String or URI is given, it is treated as proxy URI.
#
# When true is given or the option itself is not specified,
# environment variable `scheme_proxy' is examined.
# `scheme' is replaced by `http', `https' or `ftp'.
#
# When false or nil is given, the environment variables are ignored and
# connection will be made to a server directly.
#
# [:proxy_http_basic_authentication]
# Synopsis:
# :proxy_http_basic_authentication =>
# ["http://proxy.foo.com:8000/", "proxy-user", "proxy-password"]
# :proxy_http_basic_authentication =>
# [URI.parse("http://proxy.foo.com:8000/"),
# "proxy-user", "proxy-password"]
#
# If :proxy option is specified, the value should be an Array with 3
# elements. It should contain a proxy URI, a proxy user name and a proxy
# password. The proxy URI should be a String, an URI or nil. The proxy
# user name and password should be a String.
#
# If nil is given for the proxy URI, this option is just ignored.
#
# If :proxy and :proxy_http_basic_authentication is specified,
# ArgumentError is raised.
#
# [:http_basic_authentication]
# Synopsis:
# :http_basic_authentication=>[user, password]
#
# If :http_basic_authentication is specified,
# the value should be an array which contains 2 strings:
# username and password.
# It is used for HTTP Basic authentication defined by RFC 2617.
#
# [:content_length_proc]
# Synopsis:
# :content_length_proc => lambda {|content_length| ... }
#
# If :content_length_proc option is specified, the option value procedure
# is called before actual transfer is started.
# It takes one argument, which is expected content length in bytes.
#
# If two or more transfers are performed by HTTP redirection, the
# procedure is called only once for the last transfer.
#
# When expected content length is unknown, the procedure is called with
# nil. This happens when the HTTP response has no Content-Length header.
#
# [:progress_proc]
# Synopsis:
# :progress_proc => lambda {|size| ...}
#
# If :progress_proc option is specified, the proc is called with one
# argument each time when `open' gets content fragment from network.
# The argument +size+ is the accumulated transferred size in bytes.
#
# If two or more transfer is done by HTTP redirection, the procedure
# is called only one for a last transfer.
#
# :progress_proc and :content_length_proc are intended to be used for
# progress bar.
# For example, it can be implemented as follows using Ruby/ProgressBar.
#
# pbar = nil
# open("http://...",
# :content_length_proc => lambda {|t|
# if t && 0 < t
# pbar = ProgressBar.new("...", t)
# pbar.file_transfer_mode
# end
# },
# :progress_proc => lambda {|s|
# pbar.set s if pbar
# }) {|f| ... }
#
# [:read_timeout]
# Synopsis:
# :read_timeout=>nil (no timeout)
# :read_timeout=>10 (10 second)
#
# :read_timeout option specifies a timeout of read for http connections.
#
# [:open_timeout]
# Synopsis:
# :open_timeout=>nil (no timeout)
# :open_timeout=>10 (10 second)
#
# :open_timeout option specifies a timeout of open for http connections.
#
# [:ssl_ca_cert]
# Synopsis:
# :ssl_ca_cert=>filename or an Array of filenames
#
# :ssl_ca_cert is used to specify CA certificate for SSL.
# If it is given, default certificates are not used.
#
# [:ssl_verify_mode]
# Synopsis:
# :ssl_verify_mode=>mode
#
# :ssl_verify_mode is used to specify openssl verify mode.
#
# [:ssl_min_version]
# Synopsis:
# :ssl_min_version=>:TLS1_2
#
# :ssl_min_version option specifies the minimum allowed SSL/TLS protocol
# version. See also OpenSSL::SSL::SSLContext#min_version=.
#
# [:ssl_max_version]
# Synopsis:
# :ssl_max_version=>:TLS1_2
#
# :ssl_max_version option specifies the maximum allowed SSL/TLS protocol
# version. See also OpenSSL::SSL::SSLContext#max_version=.
#
# [:ftp_active_mode]
# Synopsis:
# :ftp_active_mode=>bool
#
# <tt>:ftp_active_mode => true</tt> is used to make ftp active mode.
# Ruby 1.9 uses passive mode by default.
# Note that the active mode is default in Ruby 1.8 or prior.
#
# [:redirect]
# Synopsis:
# :redirect=>bool
#
# +:redirect+ is true by default. <tt>:redirect => false</tt> is used to
# disable all HTTP redirects.
#
# OpenURI::HTTPRedirect exception raised on redirection.
# Using +true+ also means that redirections between http and ftp are
# permitted.
#
def open(*rest, &block)
OpenURI.open_uri(self, *rest, &block)
end
# OpenURI::OpenRead#read([ options ]) reads a content referenced by self and
# returns the content as string.
# The string is extended with OpenURI::Meta.
# The argument +options+ is same as OpenURI::OpenRead#open.
def read(options={})
self.open(options) {|f|
str = f.read
Meta.init str, f
str
}
end
end
end
module URI
class HTTP
def buffer_open(buf, proxy, options) # :nodoc:
OpenURI.open_http(buf, self, proxy, options)
end
include OpenURI::OpenRead
end
class FTP
def buffer_open(buf, proxy, options) # :nodoc:
if proxy
OpenURI.open_http(buf, self, proxy, options)
return
end
begin
require 'net/ftp'
rescue LoadError
abort "net/ftp is not found. You may need to `gem install net-ftp` to install net/ftp."
end
path = self.path
path = path.sub(%r{\A/}, '%2F') # re-encode the beginning slash because uri library decodes it.
directories = path.split(%r{/}, -1)
directories.each {|d|
d.gsub!(/%([0-9A-Fa-f][0-9A-Fa-f])/) { [$1].pack("H2") }
}
unless filename = directories.pop
raise ArgumentError, "no filename: #{self.inspect}"
end
directories.each {|d|
if /[\r\n]/ =~ d
raise ArgumentError, "invalid directory: #{d.inspect}"
end
}
if /[\r\n]/ =~ filename
raise ArgumentError, "invalid filename: #{filename.inspect}"
end
typecode = self.typecode
if typecode && /\A[aid]\z/ !~ typecode
raise ArgumentError, "invalid typecode: #{typecode.inspect}"
end
# The access sequence is defined by RFC 1738
ftp = Net::FTP.new
ftp.connect(self.hostname, self.port)
ftp.passive = !options[:ftp_active_mode]
# todo: extract user/passwd from .netrc.
user = 'anonymous'
passwd = nil
user, passwd = self.userinfo.split(/:/) if self.userinfo
ftp.login(user, passwd)
directories.each {|cwd|
ftp.voidcmd("CWD #{cwd}")
}
if typecode
# xxx: typecode D is not handled.
ftp.voidcmd("TYPE #{typecode.upcase}")
end
if options[:content_length_proc]
options[:content_length_proc].call(ftp.size(filename))
end
ftp.retrbinary("RETR #{filename}", 4096) { |str|
buf << str
options[:progress_proc].call(buf.size) if options[:progress_proc]
}
ftp.close
buf.io.rewind
end
include OpenURI::OpenRead
end
end
share/ruby/unicode_normalize/normalize.rb 0000644 00000014003 15173547334 0014674 0 ustar 00 # 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_relative 'tables'
# :stopdoc:
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
share/ruby/unicode_normalize/tables.rb 0000644 00000666566 15173547334 0014201 0 ustar 00 # coding: us-ascii
# frozen_string_literal: true
# automatically generated by template/unicode_norm_gen.tmpl
module UnicodeNormalize # :nodoc:
accents = "" \
"[\u0300-\u034E" \
"\u0350-\u036F" \
"\u0483-\u0487" \
"\u0591-\u05BD" \
"\u05BF" \
"\u05C1\u05C2" \
"\u05C4\u05C5" \
"\u05C7" \
"\u0610-\u061A" \
"\u064B-\u065F" \
"\u0670" \
"\u06D6-\u06DC" \
"\u06DF-\u06E4" \
"\u06E7\u06E8" \
"\u06EA-\u06ED" \
"\u0711" \
"\u0730-\u074A" \
"\u07EB-\u07F3" \
"\u07FD" \
"\u0816-\u0819" \
"\u081B-\u0823" \
"\u0825-\u0827" \
"\u0829-\u082D" \
"\u0859-\u085B" \
"\u0898-\u089F" \
"\u08CA-\u08E1" \
"\u08E3-\u08FF" \
"\u093C" \
"\u094D" \
"\u0951-\u0954" \
"\u09BC" \
"\u09BE" \
"\u09CD" \
"\u09D7" \
"\u09FE" \
"\u0A3C" \
"\u0A4D" \
"\u0ABC" \
"\u0ACD" \
"\u0B3C" \
"\u0B3E" \
"\u0B4D" \
"\u0B56\u0B57" \
"\u0BBE" \
"\u0BCD" \
"\u0BD7" \
"\u0C3C" \
"\u0C4D" \
"\u0C55\u0C56" \
"\u0CBC" \
"\u0CC2" \
"\u0CCD" \
"\u0CD5\u0CD6" \
"\u0D3B\u0D3C" \
"\u0D3E" \
"\u0D4D" \
"\u0D57" \
"\u0DCA" \
"\u0DCF" \
"\u0DDF" \
"\u0E38-\u0E3A" \
"\u0E48-\u0E4B" \
"\u0EB8-\u0EBA" \
"\u0EC8-\u0ECB" \
"\u0F18\u0F19" \
"\u0F35" \
"\u0F37" \
"\u0F39" \
"\u0F71\u0F72" \
"\u0F74" \
"\u0F7A-\u0F7D" \
"\u0F80" \
"\u0F82-\u0F84" \
"\u0F86\u0F87" \
"\u0FC6" \
"\u102E" \
"\u1037" \
"\u1039\u103A" \
"\u108D" \
"\u135D-\u135F" \
"\u1714\u1715" \
"\u1734" \
"\u17D2" \
"\u17DD" \
"\u18A9" \
"\u1939-\u193B" \
"\u1A17\u1A18" \
"\u1A60" \
"\u1A75-\u1A7C" \
"\u1A7F" \
"\u1AB0-\u1ABD" \
"\u1ABF-\u1ACE" \
"\u1B34\u1B35" \
"\u1B44" \
"\u1B6B-\u1B73" \
"\u1BAA\u1BAB" \
"\u1BE6" \
"\u1BF2\u1BF3" \
"\u1C37" \
"\u1CD0-\u1CD2" \
"\u1CD4-\u1CE0" \
"\u1CE2-\u1CE8" \
"\u1CED" \
"\u1CF4" \
"\u1CF8\u1CF9" \
"\u1DC0-\u1DFF" \
"\u20D0-\u20DC" \
"\u20E1" \
"\u20E5-\u20F0" \
"\u2CEF-\u2CF1" \
"\u2D7F" \
"\u2DE0-\u2DFF" \
"\u302A-\u302F" \
"\u3099\u309A" \
"\uA66F" \
"\uA674-\uA67D" \
"\uA69E\uA69F" \
"\uA6F0\uA6F1" \
"\uA806" \
"\uA82C" \
"\uA8C4" \
"\uA8E0-\uA8F1" \
"\uA92B-\uA92D" \
"\uA953" \
"\uA9B3" \
"\uA9C0" \
"\uAAB0" \
"\uAAB2-\uAAB4" \
"\uAAB7\uAAB8" \
"\uAABE\uAABF" \
"\uAAC1" \
"\uAAF6" \
"\uABED" \
"\uFB1E" \
"\uFE20-\uFE2F" \
"\u{101FD}" \
"\u{102E0}" \
"\u{10376}-\u{1037A}" \
"\u{10A0D}" \
"\u{10A0F}" \
"\u{10A38}-\u{10A3A}" \
"\u{10A3F}" \
"\u{10AE5}\u{10AE6}" \
"\u{10D24}-\u{10D27}" \
"\u{10EAB}\u{10EAC}" \
"\u{10EFD}-\u{10EFF}" \
"\u{10F46}-\u{10F50}" \
"\u{10F82}-\u{10F85}" \
"\u{11046}" \
"\u{11070}" \
"\u{1107F}" \
"\u{110B9}\u{110BA}" \
"\u{11100}-\u{11102}" \
"\u{11127}" \
"\u{11133}\u{11134}" \
"\u{11173}" \
"\u{111C0}" \
"\u{111CA}" \
"\u{11235}\u{11236}" \
"\u{112E9}\u{112EA}" \
"\u{1133B}\u{1133C}" \
"\u{1133E}" \
"\u{1134D}" \
"\u{11357}" \
"\u{11366}-\u{1136C}" \
"\u{11370}-\u{11374}" \
"\u{11442}" \
"\u{11446}" \
"\u{1145E}" \
"\u{114B0}" \
"\u{114BA}" \
"\u{114BD}" \
"\u{114C2}\u{114C3}" \
"\u{115AF}" \
"\u{115BF}\u{115C0}" \
"\u{1163F}" \
"\u{116B6}\u{116B7}" \
"\u{1172B}" \
"\u{11839}\u{1183A}" \
"\u{11930}" \
"\u{1193D}\u{1193E}" \
"\u{11943}" \
"\u{119E0}" \
"\u{11A34}" \
"\u{11A47}" \
"\u{11A99}" \
"\u{11C3F}" \
"\u{11D42}" \
"\u{11D44}\u{11D45}" \
"\u{11D97}" \
"\u{11F41}\u{11F42}" \
"\u{16AF0}-\u{16AF4}" \
"\u{16B30}-\u{16B36}" \
"\u{16FF0}\u{16FF1}" \
"\u{1BC9E}" \
"\u{1D165}-\u{1D169}" \
"\u{1D16D}-\u{1D172}" \
"\u{1D17B}-\u{1D182}" \
"\u{1D185}-\u{1D18B}" \
"\u{1D1AA}-\u{1D1AD}" \
"\u{1D242}-\u{1D244}" \
"\u{1E000}-\u{1E006}" \
"\u{1E008}-\u{1E018}" \
"\u{1E01B}-\u{1E021}" \
"\u{1E023}\u{1E024}" \
"\u{1E026}-\u{1E02A}" \
"\u{1E08F}" \
"\u{1E130}-\u{1E136}" \
"\u{1E2AE}" \
"\u{1E2EC}-\u{1E2EF}" \
"\u{1E4EC}-\u{1E4EF}" \
"\u{1E8D0}-\u{1E8D6}" \
"\u{1E944}-\u{1E94A}" \
"]"
ACCENTS = accents
REGEXP_D_STRING = "#{'' # composition starters and composition exclusions
}" \
"[\u00C0-\u00C5" \
"\u00C7-\u00CF" \
"\u00D1-\u00D6" \
"\u00D9-\u00DD" \
"\u00E0-\u00E5" \
"\u00E7-\u00EF" \
"\u00F1-\u00F6" \
"\u00F9-\u00FD" \
"\u00FF-\u010F" \
"\u0112-\u0125" \
"\u0128-\u0130" \
"\u0134-\u0137" \
"\u0139-\u013E" \
"\u0143-\u0148" \
"\u014C-\u0151" \
"\u0154-\u0165" \
"\u0168-\u017E" \
"\u01A0\u01A1" \
"\u01AF\u01B0" \
"\u01CD-\u01DC" \
"\u01DE-\u01E3" \
"\u01E6-\u01F0" \
"\u01F4\u01F5" \
"\u01F8-\u021B" \
"\u021E\u021F" \
"\u0226-\u0233" \
"\u0340\u0341" \
"\u0343\u0344" \
"\u0374" \
"\u037E" \
"\u0385-\u038A" \
"\u038C" \
"\u038E-\u0390" \
"\u03AA-\u03B0" \
"\u03CA-\u03CE" \
"\u03D3\u03D4" \
"\u0400\u0401" \
"\u0403" \
"\u0407" \
"\u040C-\u040E" \
"\u0419" \
"\u0439" \
"\u0450\u0451" \
"\u0453" \
"\u0457" \
"\u045C-\u045E" \
"\u0476\u0477" \
"\u04C1\u04C2" \
"\u04D0-\u04D3" \
"\u04D6\u04D7" \
"\u04DA-\u04DF" \
"\u04E2-\u04E7" \
"\u04EA-\u04F5" \
"\u04F8\u04F9" \
"\u0622-\u0626" \
"\u06C0" \
"\u06C2" \
"\u06D3" \
"\u0929" \
"\u0931" \
"\u0934" \
"\u0958-\u095F" \
"\u09CB\u09CC" \
"\u09DC\u09DD" \
"\u09DF" \
"\u0A33" \
"\u0A36" \
"\u0A59-\u0A5B" \
"\u0A5E" \
"\u0B48" \
"\u0B4B\u0B4C" \
"\u0B5C\u0B5D" \
"\u0B94" \
"\u0BCA-\u0BCC" \
"\u0C48" \
"\u0CC0" \
"\u0CC7\u0CC8" \
"\u0CCA\u0CCB" \
"\u0D4A-\u0D4C" \
"\u0DDA" \
"\u0DDC-\u0DDE" \
"\u0F43" \
"\u0F4D" \
"\u0F52" \
"\u0F57" \
"\u0F5C" \
"\u0F69" \
"\u0F73" \
"\u0F75\u0F76" \
"\u0F78" \
"\u0F81" \
"\u0F93" \
"\u0F9D" \
"\u0FA2" \
"\u0FA7" \
"\u0FAC" \
"\u0FB9" \
"\u1026" \
"\u1B06" \
"\u1B08" \
"\u1B0A" \
"\u1B0C" \
"\u1B0E" \
"\u1B12" \
"\u1B3B" \
"\u1B3D" \
"\u1B40\u1B41" \
"\u1B43" \
"\u1E00-\u1E99" \
"\u1E9B" \
"\u1EA0-\u1EF9" \
"\u1F00-\u1F15" \
"\u1F18-\u1F1D" \
"\u1F20-\u1F45" \
"\u1F48-\u1F4D" \
"\u1F50-\u1F57" \
"\u1F59" \
"\u1F5B" \
"\u1F5D" \
"\u1F5F-\u1F7D" \
"\u1F80-\u1FB4" \
"\u1FB6-\u1FBC" \
"\u1FBE" \
"\u1FC1-\u1FC4" \
"\u1FC6-\u1FD3" \
"\u1FD6-\u1FDB" \
"\u1FDD-\u1FEF" \
"\u1FF2-\u1FF4" \
"\u1FF6-\u1FFD" \
"\u2000\u2001" \
"\u2126" \
"\u212A\u212B" \
"\u219A\u219B" \
"\u21AE" \
"\u21CD-\u21CF" \
"\u2204" \
"\u2209" \
"\u220C" \
"\u2224" \
"\u2226" \
"\u2241" \
"\u2244" \
"\u2247" \
"\u2249" \
"\u2260" \
"\u2262" \
"\u226D-\u2271" \
"\u2274\u2275" \
"\u2278\u2279" \
"\u2280\u2281" \
"\u2284\u2285" \
"\u2288\u2289" \
"\u22AC-\u22AF" \
"\u22E0-\u22E3" \
"\u22EA-\u22ED" \
"\u2329\u232A" \
"\u2ADC" \
"\u304C" \
"\u304E" \
"\u3050" \
"\u3052" \
"\u3054" \
"\u3056" \
"\u3058" \
"\u305A" \
"\u305C" \
"\u305E" \
"\u3060" \
"\u3062" \
"\u3065" \
"\u3067" \
"\u3069" \
"\u3070\u3071" \
"\u3073\u3074" \
"\u3076\u3077" \
"\u3079\u307A" \
"\u307C\u307D" \
"\u3094" \
"\u309E" \
"\u30AC" \
"\u30AE" \
"\u30B0" \
"\u30B2" \
"\u30B4" \
"\u30B6" \
"\u30B8" \
"\u30BA" \
"\u30BC" \
"\u30BE" \
"\u30C0" \
"\u30C2" \
"\u30C5" \
"\u30C7" \
"\u30C9" \
"\u30D0\u30D1" \
"\u30D3\u30D4" \
"\u30D6\u30D7" \
"\u30D9\u30DA" \
"\u30DC\u30DD" \
"\u30F4" \
"\u30F7-\u30FA" \
"\u30FE" \
"\uF900-\uFA0D" \
"\uFA10" \
"\uFA12" \
"\uFA15-\uFA1E" \
"\uFA20" \
"\uFA22" \
"\uFA25\uFA26" \
"\uFA2A-\uFA6D" \
"\uFA70-\uFAD9" \
"\uFB1D" \
"\uFB1F" \
"\uFB2A-\uFB36" \
"\uFB38-\uFB3C" \
"\uFB3E" \
"\uFB40\uFB41" \
"\uFB43\uFB44" \
"\uFB46-\uFB4E" \
"\u{1109A}" \
"\u{1109C}" \
"\u{110AB}" \
"\u{1112E}\u{1112F}" \
"\u{1134B}\u{1134C}" \
"\u{114BB}\u{114BC}" \
"\u{114BE}" \
"\u{115BA}\u{115BB}" \
"\u{11938}" \
"\u{1D15E}-\u{1D164}" \
"\u{1D1BB}-\u{1D1C0}" \
"\u{2F800}-\u{2FA1D}" \
"]#{accents}*" \
"|#{'' # characters that can be the result of a composition, except composition starters
}" \
"[<->" \
"A-P" \
"R-Z" \
"a-p" \
"r-z" \
"\u00A8" \
"\u00C6" \
"\u00D8" \
"\u00E6" \
"\u00F8" \
"\u017F" \
"\u01B7" \
"\u0292" \
"\u0391" \
"\u0395" \
"\u0397" \
"\u0399" \
"\u039F" \
"\u03A1" \
"\u03A5" \
"\u03A9" \
"\u03B1" \
"\u03B5" \
"\u03B7" \
"\u03B9" \
"\u03BF" \
"\u03C1" \
"\u03C5" \
"\u03C9" \
"\u03D2" \
"\u0406" \
"\u0410" \
"\u0413" \
"\u0415-\u0418" \
"\u041A" \
"\u041E" \
"\u0423" \
"\u0427" \
"\u042B" \
"\u042D" \
"\u0430" \
"\u0433" \
"\u0435-\u0438" \
"\u043A" \
"\u043E" \
"\u0443" \
"\u0447" \
"\u044B" \
"\u044D" \
"\u0456" \
"\u0474\u0475" \
"\u04D8\u04D9" \
"\u04E8\u04E9" \
"\u0627" \
"\u0648" \
"\u064A" \
"\u06C1" \
"\u06D2" \
"\u06D5" \
"\u0928" \
"\u0930" \
"\u0933" \
"\u09C7" \
"\u0B47" \
"\u0B92" \
"\u0BC6\u0BC7" \
"\u0C46" \
"\u0CBF" \
"\u0CC6" \
"\u0D46\u0D47" \
"\u0DD9" \
"\u1025" \
"\u1B05" \
"\u1B07" \
"\u1B09" \
"\u1B0B" \
"\u1B0D" \
"\u1B11" \
"\u1B3A" \
"\u1B3C" \
"\u1B3E\u1B3F" \
"\u1B42" \
"\u1FBF" \
"\u1FFE" \
"\u2190" \
"\u2192" \
"\u2194" \
"\u21D0" \
"\u21D2" \
"\u21D4" \
"\u2203" \
"\u2208" \
"\u220B" \
"\u2223" \
"\u2225" \
"\u223C" \
"\u2243" \
"\u2245" \
"\u2248" \
"\u224D" \
"\u2261" \
"\u2264\u2265" \
"\u2272\u2273" \
"\u2276\u2277" \
"\u227A-\u227D" \
"\u2282\u2283" \
"\u2286\u2287" \
"\u2291\u2292" \
"\u22A2" \
"\u22A8\u22A9" \
"\u22AB" \
"\u22B2-\u22B5" \
"\u3046" \
"\u304B" \
"\u304D" \
"\u304F" \
"\u3051" \
"\u3053" \
"\u3055" \
"\u3057" \
"\u3059" \
"\u305B" \
"\u305D" \
"\u305F" \
"\u3061" \
"\u3064" \
"\u3066" \
"\u3068" \
"\u306F" \
"\u3072" \
"\u3075" \
"\u3078" \
"\u307B" \
"\u309D" \
"\u30A6" \
"\u30AB" \
"\u30AD" \
"\u30AF" \
"\u30B1" \
"\u30B3" \
"\u30B5" \
"\u30B7" \
"\u30B9" \
"\u30BB" \
"\u30BD" \
"\u30BF" \
"\u30C1" \
"\u30C4" \
"\u30C6" \
"\u30C8" \
"\u30CF" \
"\u30D2" \
"\u30D5" \
"\u30D8" \
"\u30DB" \
"\u30EF-\u30F2" \
"\u30FD" \
"\u{11099}" \
"\u{1109B}" \
"\u{110A5}" \
"\u{11131}\u{11132}" \
"\u{11347}" \
"\u{114B9}" \
"\u{115B8}\u{115B9}" \
"\u{11935}" \
"]?#{accents}+" \
"|#{'' # precomposed Hangul syllables
}" \
"[\u{AC00}-\u{D7A4}]"
REGEXP_C_STRING = "#{'' # composition exclusions
}" \
"[\u0340\u0341" \
"\u0343\u0344" \
"\u0374" \
"\u037E" \
"\u0387" \
"\u0958-\u095F" \
"\u09DC\u09DD" \
"\u09DF" \
"\u0A33" \
"\u0A36" \
"\u0A59-\u0A5B" \
"\u0A5E" \
"\u0B5C\u0B5D" \
"\u0F43" \
"\u0F4D" \
"\u0F52" \
"\u0F57" \
"\u0F5C" \
"\u0F69" \
"\u0F73" \
"\u0F75\u0F76" \
"\u0F78" \
"\u0F81" \
"\u0F93" \
"\u0F9D" \
"\u0FA2" \
"\u0FA7" \
"\u0FAC" \
"\u0FB9" \
"\u1F71" \
"\u1F73" \
"\u1F75" \
"\u1F77" \
"\u1F79" \
"\u1F7B" \
"\u1F7D" \
"\u1FBB" \
"\u1FBE" \
"\u1FC9" \
"\u1FCB" \
"\u1FD3" \
"\u1FDB" \
"\u1FE3" \
"\u1FEB" \
"\u1FEE\u1FEF" \
"\u1FF9" \
"\u1FFB" \
"\u1FFD" \
"\u2000\u2001" \
"\u2126" \
"\u212A\u212B" \
"\u2329\u232A" \
"\u2ADC" \
"\uF900-\uFA0D" \
"\uFA10" \
"\uFA12" \
"\uFA15-\uFA1E" \
"\uFA20" \
"\uFA22" \
"\uFA25\uFA26" \
"\uFA2A-\uFA6D" \
"\uFA70-\uFAD9" \
"\uFB1D" \
"\uFB1F" \
"\uFB2A-\uFB36" \
"\uFB38-\uFB3C" \
"\uFB3E" \
"\uFB40\uFB41" \
"\uFB43\uFB44" \
"\uFB46-\uFB4E" \
"\u{1D15E}-\u{1D164}" \
"\u{1D1BB}-\u{1D1C0}" \
"\u{2F800}-\u{2FA1D}" \
"]#{accents}*" \
"|#{'' # composition starters and characters that can be the result of a composition
}" \
"[<->" \
"A-P" \
"R-Z" \
"a-p" \
"r-z" \
"\u00A8" \
"\u00C0-\u00CF" \
"\u00D1-\u00D6" \
"\u00D8-\u00DD" \
"\u00E0-\u00EF" \
"\u00F1-\u00F6" \
"\u00F8-\u00FD" \
"\u00FF-\u010F" \
"\u0112-\u0125" \
"\u0128-\u0130" \
"\u0134-\u0137" \
"\u0139-\u013E" \
"\u0143-\u0148" \
"\u014C-\u0151" \
"\u0154-\u0165" \
"\u0168-\u017F" \
"\u01A0\u01A1" \
"\u01AF\u01B0" \
"\u01B7" \
"\u01CD-\u01DC" \
"\u01DE-\u01E3" \
"\u01E6-\u01F0" \
"\u01F4\u01F5" \
"\u01F8-\u021B" \
"\u021E\u021F" \
"\u0226-\u0233" \
"\u0292" \
"\u0385\u0386" \
"\u0388-\u038A" \
"\u038C" \
"\u038E-\u0391" \
"\u0395" \
"\u0397" \
"\u0399" \
"\u039F" \
"\u03A1" \
"\u03A5" \
"\u03A9-\u03B1" \
"\u03B5" \
"\u03B7" \
"\u03B9" \
"\u03BF" \
"\u03C1" \
"\u03C5" \
"\u03C9-\u03CE" \
"\u03D2-\u03D4" \
"\u0400\u0401" \
"\u0403" \
"\u0406\u0407" \
"\u040C-\u040E" \
"\u0410" \
"\u0413" \
"\u0415-\u041A" \
"\u041E" \
"\u0423" \
"\u0427" \
"\u042B" \
"\u042D" \
"\u0430" \
"\u0433" \
"\u0435-\u043A" \
"\u043E" \
"\u0443" \
"\u0447" \
"\u044B" \
"\u044D" \
"\u0450\u0451" \
"\u0453" \
"\u0456\u0457" \
"\u045C-\u045E" \
"\u0474-\u0477" \
"\u04C1\u04C2" \
"\u04D0-\u04D3" \
"\u04D6-\u04DF" \
"\u04E2-\u04F5" \
"\u04F8\u04F9" \
"\u0622-\u0627" \
"\u0648" \
"\u064A" \
"\u06C0-\u06C2" \
"\u06D2\u06D3" \
"\u06D5" \
"\u0928\u0929" \
"\u0930\u0931" \
"\u0933\u0934" \
"\u09C7" \
"\u09CB\u09CC" \
"\u0B47\u0B48" \
"\u0B4B\u0B4C" \
"\u0B92" \
"\u0B94" \
"\u0BC6\u0BC7" \
"\u0BCA-\u0BCC" \
"\u0C46" \
"\u0C48" \
"\u0CBF\u0CC0" \
"\u0CC6-\u0CC8" \
"\u0CCA\u0CCB" \
"\u0D46\u0D47" \
"\u0D4A-\u0D4C" \
"\u0DD9\u0DDA" \
"\u0DDC-\u0DDE" \
"\u1025\u1026" \
"\u1B05-\u1B0E" \
"\u1B11\u1B12" \
"\u1B3A-\u1B43" \
"\u1E00-\u1E99" \
"\u1E9B" \
"\u1EA0-\u1EF9" \
"\u1F00-\u1F15" \
"\u1F18-\u1F1D" \
"\u1F20-\u1F45" \
"\u1F48-\u1F4D" \
"\u1F50-\u1F57" \
"\u1F59" \
"\u1F5B" \
"\u1F5D" \
"\u1F5F-\u1F70" \
"\u1F72" \
"\u1F74" \
"\u1F76" \
"\u1F78" \
"\u1F7A" \
"\u1F7C" \
"\u1F80-\u1FB4" \
"\u1FB6-\u1FBA" \
"\u1FBC" \
"\u1FBF" \
"\u1FC1-\u1FC4" \
"\u1FC6-\u1FC8" \
"\u1FCA" \
"\u1FCC-\u1FD2" \
"\u1FD6-\u1FDA" \
"\u1FDD-\u1FE2" \
"\u1FE4-\u1FEA" \
"\u1FEC\u1FED" \
"\u1FF2-\u1FF4" \
"\u1FF6-\u1FF8" \
"\u1FFA" \
"\u1FFC" \
"\u1FFE" \
"\u2190" \
"\u2192" \
"\u2194" \
"\u219A\u219B" \
"\u21AE" \
"\u21CD-\u21D0" \
"\u21D2" \
"\u21D4" \
"\u2203\u2204" \
"\u2208\u2209" \
"\u220B\u220C" \
"\u2223-\u2226" \
"\u223C" \
"\u2241" \
"\u2243-\u2245" \
"\u2247-\u2249" \
"\u224D" \
"\u2260-\u2262" \
"\u2264\u2265" \
"\u226D-\u227D" \
"\u2280-\u2289" \
"\u2291\u2292" \
"\u22A2" \
"\u22A8\u22A9" \
"\u22AB-\u22AF" \
"\u22B2-\u22B5" \
"\u22E0-\u22E3" \
"\u22EA-\u22ED" \
"\u3046" \
"\u304B-\u3062" \
"\u3064-\u3069" \
"\u306F-\u307D" \
"\u3094" \
"\u309D\u309E" \
"\u30A6" \
"\u30AB-\u30C2" \
"\u30C4-\u30C9" \
"\u30CF-\u30DD" \
"\u30EF-\u30F2" \
"\u30F4" \
"\u30F7-\u30FA" \
"\u30FD\u30FE" \
"\u{11099}-\u{1109C}" \
"\u{110A5}" \
"\u{110AB}" \
"\u{1112E}\u{1112F}" \
"\u{11131}\u{11132}" \
"\u{11347}" \
"\u{1134B}\u{1134C}" \
"\u{114B9}" \
"\u{114BB}\u{114BC}" \
"\u{114BE}" \
"\u{115B8}-\u{115BB}" \
"\u{11935}" \
"\u{11938}" \
"]?#{accents}+" \
"|#{'' # Hangul syllables with separate trailer
}" \
"[\uAC00" \
"\uAC1C" \
"\uAC38" \
"\uAC54" \
"\uAC70" \
"\uAC8C" \
"\uACA8" \
"\uACC4" \
"\uACE0" \
"\uACFC" \
"\uAD18" \
"\uAD34" \
"\uAD50" \
"\uAD6C" \
"\uAD88" \
"\uADA4" \
"\uADC0" \
"\uADDC" \
"\uADF8" \
"\uAE14" \
"\uAE30" \
"\uAE4C" \
"\uAE68" \
"\uAE84" \
"\uAEA0" \
"\uAEBC" \
"\uAED8" \
"\uAEF4" \
"\uAF10" \
"\uAF2C" \
"\uAF48" \
"\uAF64" \
"\uAF80" \
"\uAF9C" \
"\uAFB8" \
"\uAFD4" \
"\uAFF0" \
"\uB00C" \
"\uB028" \
"\uB044" \
"\uB060" \
"\uB07C" \
"\uB098" \
"\uB0B4" \
"\uB0D0" \
"\uB0EC" \
"\uB108" \
"\uB124" \
"\uB140" \
"\uB15C" \
"\uB178" \
"\uB194" \
"\uB1B0" \
"\uB1CC" \
"\uB1E8" \
"\uB204" \
"\uB220" \
"\uB23C" \
"\uB258" \
"\uB274" \
"\uB290" \
"\uB2AC" \
"\uB2C8" \
"\uB2E4" \
"\uB300" \
"\uB31C" \
"\uB338" \
"\uB354" \
"\uB370" \
"\uB38C" \
"\uB3A8" \
"\uB3C4" \
"\uB3E0" \
"\uB3FC" \
"\uB418" \
"\uB434" \
"\uB450" \
"\uB46C" \
"\uB488" \
"\uB4A4" \
"\uB4C0" \
"\uB4DC" \
"\uB4F8" \
"\uB514" \
"\uB530" \
"\uB54C" \
"\uB568" \
"\uB584" \
"\uB5A0" \
"\uB5BC" \
"\uB5D8" \
"\uB5F4" \
"\uB610" \
"\uB62C" \
"\uB648" \
"\uB664" \
"\uB680" \
"\uB69C" \
"\uB6B8" \
"\uB6D4" \
"\uB6F0" \
"\uB70C" \
"\uB728" \
"\uB744" \
"\uB760" \
"\uB77C" \
"\uB798" \
"\uB7B4" \
"\uB7D0" \
"\uB7EC" \
"\uB808" \
"\uB824" \
"\uB840" \
"\uB85C" \
"\uB878" \
"\uB894" \
"\uB8B0" \
"\uB8CC" \
"\uB8E8" \
"\uB904" \
"\uB920" \
"\uB93C" \
"\uB958" \
"\uB974" \
"\uB990" \
"\uB9AC" \
"\uB9C8" \
"\uB9E4" \
"\uBA00" \
"\uBA1C" \
"\uBA38" \
"\uBA54" \
"\uBA70" \
"\uBA8C" \
"\uBAA8" \
"\uBAC4" \
"\uBAE0" \
"\uBAFC" \
"\uBB18" \
"\uBB34" \
"\uBB50" \
"\uBB6C" \
"\uBB88" \
"\uBBA4" \
"\uBBC0" \
"\uBBDC" \
"\uBBF8" \
"\uBC14" \
"\uBC30" \
"\uBC4C" \
"\uBC68" \
"\uBC84" \
"\uBCA0" \
"\uBCBC" \
"\uBCD8" \
"\uBCF4" \
"\uBD10" \
"\uBD2C" \
"\uBD48" \
"\uBD64" \
"\uBD80" \
"\uBD9C" \
"\uBDB8" \
"\uBDD4" \
"\uBDF0" \
"\uBE0C" \
"\uBE28" \
"\uBE44" \
"\uBE60" \
"\uBE7C" \
"\uBE98" \
"\uBEB4" \
"\uBED0" \
"\uBEEC" \
"\uBF08" \
"\uBF24" \
"\uBF40" \
"\uBF5C" \
"\uBF78" \
"\uBF94" \
"\uBFB0" \
"\uBFCC" \
"\uBFE8" \
"\uC004" \
"\uC020" \
"\uC03C" \
"\uC058" \
"\uC074" \
"\uC090" \
"\uC0AC" \
"\uC0C8" \
"\uC0E4" \
"\uC100" \
"\uC11C" \
"\uC138" \
"\uC154" \
"\uC170" \
"\uC18C" \
"\uC1A8" \
"\uC1C4" \
"\uC1E0" \
"\uC1FC" \
"\uC218" \
"\uC234" \
"\uC250" \
"\uC26C" \
"\uC288" \
"\uC2A4" \
"\uC2C0" \
"\uC2DC" \
"\uC2F8" \
"\uC314" \
"\uC330" \
"\uC34C" \
"\uC368" \
"\uC384" \
"\uC3A0" \
"\uC3BC" \
"\uC3D8" \
"\uC3F4" \
"\uC410" \
"\uC42C" \
"\uC448" \
"\uC464" \
"\uC480" \
"\uC49C" \
"\uC4B8" \
"\uC4D4" \
"\uC4F0" \
"\uC50C" \
"\uC528" \
"\uC544" \
"\uC560" \
"\uC57C" \
"\uC598" \
"\uC5B4" \
"\uC5D0" \
"\uC5EC" \
"\uC608" \
"\uC624" \
"\uC640" \
"\uC65C" \
"\uC678" \
"\uC694" \
"\uC6B0" \
"\uC6CC" \
"\uC6E8" \
"\uC704" \
"\uC720" \
"\uC73C" \
"\uC758" \
"\uC774" \
"\uC790" \
"\uC7AC" \
"\uC7C8" \
"\uC7E4" \
"\uC800" \
"\uC81C" \
"\uC838" \
"\uC854" \
"\uC870" \
"\uC88C" \
"\uC8A8" \
"\uC8C4" \
"\uC8E0" \
"\uC8FC" \
"\uC918" \
"\uC934" \
"\uC950" \
"\uC96C" \
"\uC988" \
"\uC9A4" \
"\uC9C0" \
"\uC9DC" \
"\uC9F8" \
"\uCA14" \
"\uCA30" \
"\uCA4C" \
"\uCA68" \
"\uCA84" \
"\uCAA0" \
"\uCABC" \
"\uCAD8" \
"\uCAF4" \
"\uCB10" \
"\uCB2C" \
"\uCB48" \
"\uCB64" \
"\uCB80" \
"\uCB9C" \
"\uCBB8" \
"\uCBD4" \
"\uCBF0" \
"\uCC0C" \
"\uCC28" \
"\uCC44" \
"\uCC60" \
"\uCC7C" \
"\uCC98" \
"\uCCB4" \
"\uCCD0" \
"\uCCEC" \
"\uCD08" \
"\uCD24" \
"\uCD40" \
"\uCD5C" \
"\uCD78" \
"\uCD94" \
"\uCDB0" \
"\uCDCC" \
"\uCDE8" \
"\uCE04" \
"\uCE20" \
"\uCE3C" \
"\uCE58" \
"\uCE74" \
"\uCE90" \
"\uCEAC" \
"\uCEC8" \
"\uCEE4" \
"\uCF00" \
"\uCF1C" \
"\uCF38" \
"\uCF54" \
"\uCF70" \
"\uCF8C" \
"\uCFA8" \
"\uCFC4" \
"\uCFE0" \
"\uCFFC" \
"\uD018" \
"\uD034" \
"\uD050" \
"\uD06C" \
"\uD088" \
"\uD0A4" \
"\uD0C0" \
"\uD0DC" \
"\uD0F8" \
"\uD114" \
"\uD130" \
"\uD14C" \
"\uD168" \
"\uD184" \
"\uD1A0" \
"\uD1BC" \
"\uD1D8" \
"\uD1F4" \
"\uD210" \
"\uD22C" \
"\uD248" \
"\uD264" \
"\uD280" \
"\uD29C" \
"\uD2B8" \
"\uD2D4" \
"\uD2F0" \
"\uD30C" \
"\uD328" \
"\uD344" \
"\uD360" \
"\uD37C" \
"\uD398" \
"\uD3B4" \
"\uD3D0" \
"\uD3EC" \
"\uD408" \
"\uD424" \
"\uD440" \
"\uD45C" \
"\uD478" \
"\uD494" \
"\uD4B0" \
"\uD4CC" \
"\uD4E8" \
"\uD504" \
"\uD520" \
"\uD53C" \
"\uD558" \
"\uD574" \
"\uD590" \
"\uD5AC" \
"\uD5C8" \
"\uD5E4" \
"\uD600" \
"\uD61C" \
"\uD638" \
"\uD654" \
"\uD670" \
"\uD68C" \
"\uD6A8" \
"\uD6C4" \
"\uD6E0" \
"\uD6FC" \
"\uD718" \
"\uD734" \
"\uD750" \
"\uD76C" \
"\uD788" \
"][\u11A8-\u11C2]" \
"|#{'' # decomposed Hangul syllables
}" \
"[\u1100-\u1112][\u1161-\u1175][\u11A8-\u11C2]?"
REGEXP_K_STRING = "" \
"[\u00A0" \
"\u00A8" \
"\u00AA" \
"\u00AF" \
"\u00B2-\u00B5" \
"\u00B8-\u00BA" \
"\u00BC-\u00BE" \
"\u0132\u0133" \
"\u013F\u0140" \
"\u0149" \
"\u017F" \
"\u01C4-\u01CC" \
"\u01F1-\u01F3" \
"\u02B0-\u02B8" \
"\u02D8-\u02DD" \
"\u02E0-\u02E4" \
"\u037A" \
"\u0384\u0385" \
"\u03D0-\u03D6" \
"\u03F0-\u03F2" \
"\u03F4\u03F5" \
"\u03F9" \
"\u0587" \
"\u0675-\u0678" \
"\u0E33" \
"\u0EB3" \
"\u0EDC\u0EDD" \
"\u0F0C" \
"\u0F77" \
"\u0F79" \
"\u10FC" \
"\u1D2C-\u1D2E" \
"\u1D30-\u1D3A" \
"\u1D3C-\u1D4D" \
"\u1D4F-\u1D6A" \
"\u1D78" \
"\u1D9B-\u1DBF" \
"\u1E9A\u1E9B" \
"\u1FBD" \
"\u1FBF-\u1FC1" \
"\u1FCD-\u1FCF" \
"\u1FDD-\u1FDF" \
"\u1FED\u1FEE" \
"\u1FFD\u1FFE" \
"\u2000-\u200A" \
"\u2011" \
"\u2017" \
"\u2024-\u2026" \
"\u202F" \
"\u2033\u2034" \
"\u2036\u2037" \
"\u203C" \
"\u203E" \
"\u2047-\u2049" \
"\u2057" \
"\u205F" \
"\u2070\u2071" \
"\u2074-\u208E" \
"\u2090-\u209C" \
"\u20A8" \
"\u2100-\u2103" \
"\u2105-\u2107" \
"\u2109-\u2113" \
"\u2115\u2116" \
"\u2119-\u211D" \
"\u2120-\u2122" \
"\u2124" \
"\u2128" \
"\u212C\u212D" \
"\u212F-\u2131" \
"\u2133-\u2139" \
"\u213B-\u2140" \
"\u2145-\u2149" \
"\u2150-\u217F" \
"\u2189" \
"\u222C\u222D" \
"\u222F\u2230" \
"\u2460-\u24EA" \
"\u2A0C" \
"\u2A74-\u2A76" \
"\u2C7C\u2C7D" \
"\u2D6F" \
"\u2E9F" \
"\u2EF3" \
"\u2F00-\u2FD5" \
"\u3000" \
"\u3036" \
"\u3038-\u303A" \
"\u309B\u309C" \
"\u309F" \
"\u30FF" \
"\u3131-\u318E" \
"\u3192-\u319F" \
"\u3200-\u321E" \
"\u3220-\u3247" \
"\u3250-\u327E" \
"\u3280-\u33FF" \
"\uA69C\uA69D" \
"\uA770" \
"\uA7F2-\uA7F4" \
"\uA7F8\uA7F9" \
"\uAB5C-\uAB5F" \
"\uAB69" \
"\uFB00-\uFB06" \
"\uFB13-\uFB17" \
"\uFB20-\uFB29" \
"\uFB4F-\uFBB1" \
"\uFBD3-\uFD3D" \
"\uFD50-\uFD8F" \
"\uFD92-\uFDC7" \
"\uFDF0-\uFDFC" \
"\uFE10-\uFE19" \
"\uFE30-\uFE44" \
"\uFE47-\uFE52" \
"\uFE54-\uFE66" \
"\uFE68-\uFE6B" \
"\uFE70-\uFE72" \
"\uFE74" \
"\uFE76-\uFEFC" \
"\uFF01-\uFFBE" \
"\uFFC2-\uFFC7" \
"\uFFCA-\uFFCF" \
"\uFFD2-\uFFD7" \
"\uFFDA-\uFFDC" \
"\uFFE0-\uFFE6" \
"\uFFE8-\uFFEE" \
"\u{10781}-\u{10785}" \
"\u{10787}-\u{107B0}" \
"\u{107B2}-\u{107BA}" \
"\u{1D400}-\u{1D454}" \
"\u{1D456}-\u{1D49C}" \
"\u{1D49E}\u{1D49F}" \
"\u{1D4A2}" \
"\u{1D4A5}\u{1D4A6}" \
"\u{1D4A9}-\u{1D4AC}" \
"\u{1D4AE}-\u{1D4B9}" \
"\u{1D4BB}" \
"\u{1D4BD}-\u{1D4C3}" \
"\u{1D4C5}-\u{1D505}" \
"\u{1D507}-\u{1D50A}" \
"\u{1D50D}-\u{1D514}" \
"\u{1D516}-\u{1D51C}" \
"\u{1D51E}-\u{1D539}" \
"\u{1D53B}-\u{1D53E}" \
"\u{1D540}-\u{1D544}" \
"\u{1D546}" \
"\u{1D54A}-\u{1D550}" \
"\u{1D552}-\u{1D6A5}" \
"\u{1D6A8}-\u{1D7CB}" \
"\u{1D7CE}-\u{1D7FF}" \
"\u{1E030}-\u{1E06D}" \
"\u{1EE00}-\u{1EE03}" \
"\u{1EE05}-\u{1EE1F}" \
"\u{1EE21}\u{1EE22}" \
"\u{1EE24}" \
"\u{1EE27}" \
"\u{1EE29}-\u{1EE32}" \
"\u{1EE34}-\u{1EE37}" \
"\u{1EE39}" \
"\u{1EE3B}" \
"\u{1EE42}" \
"\u{1EE47}" \
"\u{1EE49}" \
"\u{1EE4B}" \
"\u{1EE4D}-\u{1EE4F}" \
"\u{1EE51}\u{1EE52}" \
"\u{1EE54}" \
"\u{1EE57}" \
"\u{1EE59}" \
"\u{1EE5B}" \
"\u{1EE5D}" \
"\u{1EE5F}" \
"\u{1EE61}\u{1EE62}" \
"\u{1EE64}" \
"\u{1EE67}-\u{1EE6A}" \
"\u{1EE6C}-\u{1EE72}" \
"\u{1EE74}-\u{1EE77}" \
"\u{1EE79}-\u{1EE7C}" \
"\u{1EE7E}" \
"\u{1EE80}-\u{1EE89}" \
"\u{1EE8B}-\u{1EE9B}" \
"\u{1EEA1}-\u{1EEA3}" \
"\u{1EEA5}-\u{1EEA9}" \
"\u{1EEAB}-\u{1EEBB}" \
"\u{1F100}-\u{1F10A}" \
"\u{1F110}-\u{1F12E}" \
"\u{1F130}-\u{1F14F}" \
"\u{1F16A}-\u{1F16C}" \
"\u{1F190}" \
"\u{1F200}-\u{1F202}" \
"\u{1F210}-\u{1F23B}" \
"\u{1F240}-\u{1F248}" \
"\u{1F250}\u{1F251}" \
"\u{1FBF0}-\u{1FBF9}" \
"]"
class_table = {
"\u0300"=>230,
"\u0301"=>230,
"\u0302"=>230,
"\u0303"=>230,
"\u0304"=>230,
"\u0305"=>230,
"\u0306"=>230,
"\u0307"=>230,
"\u0308"=>230,
"\u0309"=>230,
"\u030A"=>230,
"\u030B"=>230,
"\u030C"=>230,
"\u030D"=>230,
"\u030E"=>230,
"\u030F"=>230,
"\u0310"=>230,
"\u0311"=>230,
"\u0312"=>230,
"\u0313"=>230,
"\u0314"=>230,
"\u0315"=>232,
"\u0316"=>220,
"\u0317"=>220,
"\u0318"=>220,
"\u0319"=>220,
"\u031A"=>232,
"\u031B"=>216,
"\u031C"=>220,
"\u031D"=>220,
"\u031E"=>220,
"\u031F"=>220,
"\u0320"=>220,
"\u0321"=>202,
"\u0322"=>202,
"\u0323"=>220,
"\u0324"=>220,
"\u0325"=>220,
"\u0326"=>220,
"\u0327"=>202,
"\u0328"=>202,
"\u0329"=>220,
"\u032A"=>220,
"\u032B"=>220,
"\u032C"=>220,
"\u032D"=>220,
"\u032E"=>220,
"\u032F"=>220,
"\u0330"=>220,
"\u0331"=>220,
"\u0332"=>220,
"\u0333"=>220,
"\u0334"=>1,
"\u0335"=>1,
"\u0336"=>1,
"\u0337"=>1,
"\u0338"=>1,
"\u0339"=>220,
"\u033A"=>220,
"\u033B"=>220,
"\u033C"=>220,
"\u033D"=>230,
"\u033E"=>230,
"\u033F"=>230,
"\u0340"=>230,
"\u0341"=>230,
"\u0342"=>230,
"\u0343"=>230,
"\u0344"=>230,
"\u0345"=>240,
"\u0346"=>230,
"\u0347"=>220,
"\u0348"=>220,
"\u0349"=>220,
"\u034A"=>230,
"\u034B"=>230,
"\u034C"=>230,
"\u034D"=>220,
"\u034E"=>220,
"\u0350"=>230,
"\u0351"=>230,
"\u0352"=>230,
"\u0353"=>220,
"\u0354"=>220,
"\u0355"=>220,
"\u0356"=>220,
"\u0357"=>230,
"\u0358"=>232,
"\u0359"=>220,
"\u035A"=>220,
"\u035B"=>230,
"\u035C"=>233,
"\u035D"=>234,
"\u035E"=>234,
"\u035F"=>233,
"\u0360"=>234,
"\u0361"=>234,
"\u0362"=>233,
"\u0363"=>230,
"\u0364"=>230,
"\u0365"=>230,
"\u0366"=>230,
"\u0367"=>230,
"\u0368"=>230,
"\u0369"=>230,
"\u036A"=>230,
"\u036B"=>230,
"\u036C"=>230,
"\u036D"=>230,
"\u036E"=>230,
"\u036F"=>230,
"\u0483"=>230,
"\u0484"=>230,
"\u0485"=>230,
"\u0486"=>230,
"\u0487"=>230,
"\u0591"=>220,
"\u0592"=>230,
"\u0593"=>230,
"\u0594"=>230,
"\u0595"=>230,
"\u0596"=>220,
"\u0597"=>230,
"\u0598"=>230,
"\u0599"=>230,
"\u059A"=>222,
"\u059B"=>220,
"\u059C"=>230,
"\u059D"=>230,
"\u059E"=>230,
"\u059F"=>230,
"\u05A0"=>230,
"\u05A1"=>230,
"\u05A2"=>220,
"\u05A3"=>220,
"\u05A4"=>220,
"\u05A5"=>220,
"\u05A6"=>220,
"\u05A7"=>220,
"\u05A8"=>230,
"\u05A9"=>230,
"\u05AA"=>220,
"\u05AB"=>230,
"\u05AC"=>230,
"\u05AD"=>222,
"\u05AE"=>228,
"\u05AF"=>230,
"\u05B0"=>10,
"\u05B1"=>11,
"\u05B2"=>12,
"\u05B3"=>13,
"\u05B4"=>14,
"\u05B5"=>15,
"\u05B6"=>16,
"\u05B7"=>17,
"\u05B8"=>18,
"\u05B9"=>19,
"\u05BA"=>19,
"\u05BB"=>20,
"\u05BC"=>21,
"\u05BD"=>22,
"\u05BF"=>23,
"\u05C1"=>24,
"\u05C2"=>25,
"\u05C4"=>230,
"\u05C5"=>220,
"\u05C7"=>18,
"\u0610"=>230,
"\u0611"=>230,
"\u0612"=>230,
"\u0613"=>230,
"\u0614"=>230,
"\u0615"=>230,
"\u0616"=>230,
"\u0617"=>230,
"\u0618"=>30,
"\u0619"=>31,
"\u061A"=>32,
"\u064B"=>27,
"\u064C"=>28,
"\u064D"=>29,
"\u064E"=>30,
"\u064F"=>31,
"\u0650"=>32,
"\u0651"=>33,
"\u0652"=>34,
"\u0653"=>230,
"\u0654"=>230,
"\u0655"=>220,
"\u0656"=>220,
"\u0657"=>230,
"\u0658"=>230,
"\u0659"=>230,
"\u065A"=>230,
"\u065B"=>230,
"\u065C"=>220,
"\u065D"=>230,
"\u065E"=>230,
"\u065F"=>220,
"\u0670"=>35,
"\u06D6"=>230,
"\u06D7"=>230,
"\u06D8"=>230,
"\u06D9"=>230,
"\u06DA"=>230,
"\u06DB"=>230,
"\u06DC"=>230,
"\u06DF"=>230,
"\u06E0"=>230,
"\u06E1"=>230,
"\u06E2"=>230,
"\u06E3"=>220,
"\u06E4"=>230,
"\u06E7"=>230,
"\u06E8"=>230,
"\u06EA"=>220,
"\u06EB"=>230,
"\u06EC"=>230,
"\u06ED"=>220,
"\u0711"=>36,
"\u0730"=>230,
"\u0731"=>220,
"\u0732"=>230,
"\u0733"=>230,
"\u0734"=>220,
"\u0735"=>230,
"\u0736"=>230,
"\u0737"=>220,
"\u0738"=>220,
"\u0739"=>220,
"\u073A"=>230,
"\u073B"=>220,
"\u073C"=>220,
"\u073D"=>230,
"\u073E"=>220,
"\u073F"=>230,
"\u0740"=>230,
"\u0741"=>230,
"\u0742"=>220,
"\u0743"=>230,
"\u0744"=>220,
"\u0745"=>230,
"\u0746"=>220,
"\u0747"=>230,
"\u0748"=>220,
"\u0749"=>230,
"\u074A"=>230,
"\u07EB"=>230,
"\u07EC"=>230,
"\u07ED"=>230,
"\u07EE"=>230,
"\u07EF"=>230,
"\u07F0"=>230,
"\u07F1"=>230,
"\u07F2"=>220,
"\u07F3"=>230,
"\u07FD"=>220,
"\u0816"=>230,
"\u0817"=>230,
"\u0818"=>230,
"\u0819"=>230,
"\u081B"=>230,
"\u081C"=>230,
"\u081D"=>230,
"\u081E"=>230,
"\u081F"=>230,
"\u0820"=>230,
"\u0821"=>230,
"\u0822"=>230,
"\u0823"=>230,
"\u0825"=>230,
"\u0826"=>230,
"\u0827"=>230,
"\u0829"=>230,
"\u082A"=>230,
"\u082B"=>230,
"\u082C"=>230,
"\u082D"=>230,
"\u0859"=>220,
"\u085A"=>220,
"\u085B"=>220,
"\u0898"=>230,
"\u0899"=>220,
"\u089A"=>220,
"\u089B"=>220,
"\u089C"=>230,
"\u089D"=>230,
"\u089E"=>230,
"\u089F"=>230,
"\u08CA"=>230,
"\u08CB"=>230,
"\u08CC"=>230,
"\u08CD"=>230,
"\u08CE"=>230,
"\u08CF"=>220,
"\u08D0"=>220,
"\u08D1"=>220,
"\u08D2"=>220,
"\u08D3"=>220,
"\u08D4"=>230,
"\u08D5"=>230,
"\u08D6"=>230,
"\u08D7"=>230,
"\u08D8"=>230,
"\u08D9"=>230,
"\u08DA"=>230,
"\u08DB"=>230,
"\u08DC"=>230,
"\u08DD"=>230,
"\u08DE"=>230,
"\u08DF"=>230,
"\u08E0"=>230,
"\u08E1"=>230,
"\u08E3"=>220,
"\u08E4"=>230,
"\u08E5"=>230,
"\u08E6"=>220,
"\u08E7"=>230,
"\u08E8"=>230,
"\u08E9"=>220,
"\u08EA"=>230,
"\u08EB"=>230,
"\u08EC"=>230,
"\u08ED"=>220,
"\u08EE"=>220,
"\u08EF"=>220,
"\u08F0"=>27,
"\u08F1"=>28,
"\u08F2"=>29,
"\u08F3"=>230,
"\u08F4"=>230,
"\u08F5"=>230,
"\u08F6"=>220,
"\u08F7"=>230,
"\u08F8"=>230,
"\u08F9"=>220,
"\u08FA"=>220,
"\u08FB"=>230,
"\u08FC"=>230,
"\u08FD"=>230,
"\u08FE"=>230,
"\u08FF"=>230,
"\u093C"=>7,
"\u094D"=>9,
"\u0951"=>230,
"\u0952"=>220,
"\u0953"=>230,
"\u0954"=>230,
"\u09BC"=>7,
"\u09CD"=>9,
"\u09FE"=>230,
"\u0A3C"=>7,
"\u0A4D"=>9,
"\u0ABC"=>7,
"\u0ACD"=>9,
"\u0B3C"=>7,
"\u0B4D"=>9,
"\u0BCD"=>9,
"\u0C3C"=>7,
"\u0C4D"=>9,
"\u0C55"=>84,
"\u0C56"=>91,
"\u0CBC"=>7,
"\u0CCD"=>9,
"\u0D3B"=>9,
"\u0D3C"=>9,
"\u0D4D"=>9,
"\u0DCA"=>9,
"\u0E38"=>103,
"\u0E39"=>103,
"\u0E3A"=>9,
"\u0E48"=>107,
"\u0E49"=>107,
"\u0E4A"=>107,
"\u0E4B"=>107,
"\u0EB8"=>118,
"\u0EB9"=>118,
"\u0EBA"=>9,
"\u0EC8"=>122,
"\u0EC9"=>122,
"\u0ECA"=>122,
"\u0ECB"=>122,
"\u0F18"=>220,
"\u0F19"=>220,
"\u0F35"=>220,
"\u0F37"=>220,
"\u0F39"=>216,
"\u0F71"=>129,
"\u0F72"=>130,
"\u0F74"=>132,
"\u0F7A"=>130,
"\u0F7B"=>130,
"\u0F7C"=>130,
"\u0F7D"=>130,
"\u0F80"=>130,
"\u0F82"=>230,
"\u0F83"=>230,
"\u0F84"=>9,
"\u0F86"=>230,
"\u0F87"=>230,
"\u0FC6"=>220,
"\u1037"=>7,
"\u1039"=>9,
"\u103A"=>9,
"\u108D"=>220,
"\u135D"=>230,
"\u135E"=>230,
"\u135F"=>230,
"\u1714"=>9,
"\u1715"=>9,
"\u1734"=>9,
"\u17D2"=>9,
"\u17DD"=>230,
"\u18A9"=>228,
"\u1939"=>222,
"\u193A"=>230,
"\u193B"=>220,
"\u1A17"=>230,
"\u1A18"=>220,
"\u1A60"=>9,
"\u1A75"=>230,
"\u1A76"=>230,
"\u1A77"=>230,
"\u1A78"=>230,
"\u1A79"=>230,
"\u1A7A"=>230,
"\u1A7B"=>230,
"\u1A7C"=>230,
"\u1A7F"=>220,
"\u1AB0"=>230,
"\u1AB1"=>230,
"\u1AB2"=>230,
"\u1AB3"=>230,
"\u1AB4"=>230,
"\u1AB5"=>220,
"\u1AB6"=>220,
"\u1AB7"=>220,
"\u1AB8"=>220,
"\u1AB9"=>220,
"\u1ABA"=>220,
"\u1ABB"=>230,
"\u1ABC"=>230,
"\u1ABD"=>220,
"\u1ABF"=>220,
"\u1AC0"=>220,
"\u1AC1"=>230,
"\u1AC2"=>230,
"\u1AC3"=>220,
"\u1AC4"=>220,
"\u1AC5"=>230,
"\u1AC6"=>230,
"\u1AC7"=>230,
"\u1AC8"=>230,
"\u1AC9"=>230,
"\u1ACA"=>220,
"\u1ACB"=>230,
"\u1ACC"=>230,
"\u1ACD"=>230,
"\u1ACE"=>230,
"\u1B34"=>7,
"\u1B44"=>9,
"\u1B6B"=>230,
"\u1B6C"=>220,
"\u1B6D"=>230,
"\u1B6E"=>230,
"\u1B6F"=>230,
"\u1B70"=>230,
"\u1B71"=>230,
"\u1B72"=>230,
"\u1B73"=>230,
"\u1BAA"=>9,
"\u1BAB"=>9,
"\u1BE6"=>7,
"\u1BF2"=>9,
"\u1BF3"=>9,
"\u1C37"=>7,
"\u1CD0"=>230,
"\u1CD1"=>230,
"\u1CD2"=>230,
"\u1CD4"=>1,
"\u1CD5"=>220,
"\u1CD6"=>220,
"\u1CD7"=>220,
"\u1CD8"=>220,
"\u1CD9"=>220,
"\u1CDA"=>230,
"\u1CDB"=>230,
"\u1CDC"=>220,
"\u1CDD"=>220,
"\u1CDE"=>220,
"\u1CDF"=>220,
"\u1CE0"=>230,
"\u1CE2"=>1,
"\u1CE3"=>1,
"\u1CE4"=>1,
"\u1CE5"=>1,
"\u1CE6"=>1,
"\u1CE7"=>1,
"\u1CE8"=>1,
"\u1CED"=>220,
"\u1CF4"=>230,
"\u1CF8"=>230,
"\u1CF9"=>230,
"\u1DC0"=>230,
"\u1DC1"=>230,
"\u1DC2"=>220,
"\u1DC3"=>230,
"\u1DC4"=>230,
"\u1DC5"=>230,
"\u1DC6"=>230,
"\u1DC7"=>230,
"\u1DC8"=>230,
"\u1DC9"=>230,
"\u1DCA"=>220,
"\u1DCB"=>230,
"\u1DCC"=>230,
"\u1DCD"=>234,
"\u1DCE"=>214,
"\u1DCF"=>220,
"\u1DD0"=>202,
"\u1DD1"=>230,
"\u1DD2"=>230,
"\u1DD3"=>230,
"\u1DD4"=>230,
"\u1DD5"=>230,
"\u1DD6"=>230,
"\u1DD7"=>230,
"\u1DD8"=>230,
"\u1DD9"=>230,
"\u1DDA"=>230,
"\u1DDB"=>230,
"\u1DDC"=>230,
"\u1DDD"=>230,
"\u1DDE"=>230,
"\u1DDF"=>230,
"\u1DE0"=>230,
"\u1DE1"=>230,
"\u1DE2"=>230,
"\u1DE3"=>230,
"\u1DE4"=>230,
"\u1DE5"=>230,
"\u1DE6"=>230,
"\u1DE7"=>230,
"\u1DE8"=>230,
"\u1DE9"=>230,
"\u1DEA"=>230,
"\u1DEB"=>230,
"\u1DEC"=>230,
"\u1DED"=>230,
"\u1DEE"=>230,
"\u1DEF"=>230,
"\u1DF0"=>230,
"\u1DF1"=>230,
"\u1DF2"=>230,
"\u1DF3"=>230,
"\u1DF4"=>230,
"\u1DF5"=>230,
"\u1DF6"=>232,
"\u1DF7"=>228,
"\u1DF8"=>228,
"\u1DF9"=>220,
"\u1DFA"=>218,
"\u1DFB"=>230,
"\u1DFC"=>233,
"\u1DFD"=>220,
"\u1DFE"=>230,
"\u1DFF"=>220,
"\u20D0"=>230,
"\u20D1"=>230,
"\u20D2"=>1,
"\u20D3"=>1,
"\u20D4"=>230,
"\u20D5"=>230,
"\u20D6"=>230,
"\u20D7"=>230,
"\u20D8"=>1,
"\u20D9"=>1,
"\u20DA"=>1,
"\u20DB"=>230,
"\u20DC"=>230,
"\u20E1"=>230,
"\u20E5"=>1,
"\u20E6"=>1,
"\u20E7"=>230,
"\u20E8"=>220,
"\u20E9"=>230,
"\u20EA"=>1,
"\u20EB"=>1,
"\u20EC"=>220,
"\u20ED"=>220,
"\u20EE"=>220,
"\u20EF"=>220,
"\u20F0"=>230,
"\u2CEF"=>230,
"\u2CF0"=>230,
"\u2CF1"=>230,
"\u2D7F"=>9,
"\u2DE0"=>230,
"\u2DE1"=>230,
"\u2DE2"=>230,
"\u2DE3"=>230,
"\u2DE4"=>230,
"\u2DE5"=>230,
"\u2DE6"=>230,
"\u2DE7"=>230,
"\u2DE8"=>230,
"\u2DE9"=>230,
"\u2DEA"=>230,
"\u2DEB"=>230,
"\u2DEC"=>230,
"\u2DED"=>230,
"\u2DEE"=>230,
"\u2DEF"=>230,
"\u2DF0"=>230,
"\u2DF1"=>230,
"\u2DF2"=>230,
"\u2DF3"=>230,
"\u2DF4"=>230,
"\u2DF5"=>230,
"\u2DF6"=>230,
"\u2DF7"=>230,
"\u2DF8"=>230,
"\u2DF9"=>230,
"\u2DFA"=>230,
"\u2DFB"=>230,
"\u2DFC"=>230,
"\u2DFD"=>230,
"\u2DFE"=>230,
"\u2DFF"=>230,
"\u302A"=>218,
"\u302B"=>228,
"\u302C"=>232,
"\u302D"=>222,
"\u302E"=>224,
"\u302F"=>224,
"\u3099"=>8,
"\u309A"=>8,
"\uA66F"=>230,
"\uA674"=>230,
"\uA675"=>230,
"\uA676"=>230,
"\uA677"=>230,
"\uA678"=>230,
"\uA679"=>230,
"\uA67A"=>230,
"\uA67B"=>230,
"\uA67C"=>230,
"\uA67D"=>230,
"\uA69E"=>230,
"\uA69F"=>230,
"\uA6F0"=>230,
"\uA6F1"=>230,
"\uA806"=>9,
"\uA82C"=>9,
"\uA8C4"=>9,
"\uA8E0"=>230,
"\uA8E1"=>230,
"\uA8E2"=>230,
"\uA8E3"=>230,
"\uA8E4"=>230,
"\uA8E5"=>230,
"\uA8E6"=>230,
"\uA8E7"=>230,
"\uA8E8"=>230,
"\uA8E9"=>230,
"\uA8EA"=>230,
"\uA8EB"=>230,
"\uA8EC"=>230,
"\uA8ED"=>230,
"\uA8EE"=>230,
"\uA8EF"=>230,
"\uA8F0"=>230,
"\uA8F1"=>230,
"\uA92B"=>220,
"\uA92C"=>220,
"\uA92D"=>220,
"\uA953"=>9,
"\uA9B3"=>7,
"\uA9C0"=>9,
"\uAAB0"=>230,
"\uAAB2"=>230,
"\uAAB3"=>230,
"\uAAB4"=>220,
"\uAAB7"=>230,
"\uAAB8"=>230,
"\uAABE"=>230,
"\uAABF"=>230,
"\uAAC1"=>230,
"\uAAF6"=>9,
"\uABED"=>9,
"\uFB1E"=>26,
"\uFE20"=>230,
"\uFE21"=>230,
"\uFE22"=>230,
"\uFE23"=>230,
"\uFE24"=>230,
"\uFE25"=>230,
"\uFE26"=>230,
"\uFE27"=>220,
"\uFE28"=>220,
"\uFE29"=>220,
"\uFE2A"=>220,
"\uFE2B"=>220,
"\uFE2C"=>220,
"\uFE2D"=>220,
"\uFE2E"=>230,
"\uFE2F"=>230,
"\u{101FD}"=>220,
"\u{102E0}"=>220,
"\u{10376}"=>230,
"\u{10377}"=>230,
"\u{10378}"=>230,
"\u{10379}"=>230,
"\u{1037A}"=>230,
"\u{10A0D}"=>220,
"\u{10A0F}"=>230,
"\u{10A38}"=>230,
"\u{10A39}"=>1,
"\u{10A3A}"=>220,
"\u{10A3F}"=>9,
"\u{10AE5}"=>230,
"\u{10AE6}"=>220,
"\u{10D24}"=>230,
"\u{10D25}"=>230,
"\u{10D26}"=>230,
"\u{10D27}"=>230,
"\u{10EAB}"=>230,
"\u{10EAC}"=>230,
"\u{10EFD}"=>220,
"\u{10EFE}"=>220,
"\u{10EFF}"=>220,
"\u{10F46}"=>220,
"\u{10F47}"=>220,
"\u{10F48}"=>230,
"\u{10F49}"=>230,
"\u{10F4A}"=>230,
"\u{10F4B}"=>220,
"\u{10F4C}"=>230,
"\u{10F4D}"=>220,
"\u{10F4E}"=>220,
"\u{10F4F}"=>220,
"\u{10F50}"=>220,
"\u{10F82}"=>230,
"\u{10F83}"=>220,
"\u{10F84}"=>230,
"\u{10F85}"=>220,
"\u{11046}"=>9,
"\u{11070}"=>9,
"\u{1107F}"=>9,
"\u{110B9}"=>9,
"\u{110BA}"=>7,
"\u{11100}"=>230,
"\u{11101}"=>230,
"\u{11102}"=>230,
"\u{11133}"=>9,
"\u{11134}"=>9,
"\u{11173}"=>7,
"\u{111C0}"=>9,
"\u{111CA}"=>7,
"\u{11235}"=>9,
"\u{11236}"=>7,
"\u{112E9}"=>7,
"\u{112EA}"=>9,
"\u{1133B}"=>7,
"\u{1133C}"=>7,
"\u{1134D}"=>9,
"\u{11366}"=>230,
"\u{11367}"=>230,
"\u{11368}"=>230,
"\u{11369}"=>230,
"\u{1136A}"=>230,
"\u{1136B}"=>230,
"\u{1136C}"=>230,
"\u{11370}"=>230,
"\u{11371}"=>230,
"\u{11372}"=>230,
"\u{11373}"=>230,
"\u{11374}"=>230,
"\u{11442}"=>9,
"\u{11446}"=>7,
"\u{1145E}"=>230,
"\u{114C2}"=>9,
"\u{114C3}"=>7,
"\u{115BF}"=>9,
"\u{115C0}"=>7,
"\u{1163F}"=>9,
"\u{116B6}"=>9,
"\u{116B7}"=>7,
"\u{1172B}"=>9,
"\u{11839}"=>9,
"\u{1183A}"=>7,
"\u{1193D}"=>9,
"\u{1193E}"=>9,
"\u{11943}"=>7,
"\u{119E0}"=>9,
"\u{11A34}"=>9,
"\u{11A47}"=>9,
"\u{11A99}"=>9,
"\u{11C3F}"=>9,
"\u{11D42}"=>7,
"\u{11D44}"=>9,
"\u{11D45}"=>9,
"\u{11D97}"=>9,
"\u{11F41}"=>9,
"\u{11F42}"=>9,
"\u{16AF0}"=>1,
"\u{16AF1}"=>1,
"\u{16AF2}"=>1,
"\u{16AF3}"=>1,
"\u{16AF4}"=>1,
"\u{16B30}"=>230,
"\u{16B31}"=>230,
"\u{16B32}"=>230,
"\u{16B33}"=>230,
"\u{16B34}"=>230,
"\u{16B35}"=>230,
"\u{16B36}"=>230,
"\u{16FF0}"=>6,
"\u{16FF1}"=>6,
"\u{1BC9E}"=>1,
"\u{1D165}"=>216,
"\u{1D166}"=>216,
"\u{1D167}"=>1,
"\u{1D168}"=>1,
"\u{1D169}"=>1,
"\u{1D16D}"=>226,
"\u{1D16E}"=>216,
"\u{1D16F}"=>216,
"\u{1D170}"=>216,
"\u{1D171}"=>216,
"\u{1D172}"=>216,
"\u{1D17B}"=>220,
"\u{1D17C}"=>220,
"\u{1D17D}"=>220,
"\u{1D17E}"=>220,
"\u{1D17F}"=>220,
"\u{1D180}"=>220,
"\u{1D181}"=>220,
"\u{1D182}"=>220,
"\u{1D185}"=>230,
"\u{1D186}"=>230,
"\u{1D187}"=>230,
"\u{1D188}"=>230,
"\u{1D189}"=>230,
"\u{1D18A}"=>220,
"\u{1D18B}"=>220,
"\u{1D1AA}"=>230,
"\u{1D1AB}"=>230,
"\u{1D1AC}"=>230,
"\u{1D1AD}"=>230,
"\u{1D242}"=>230,
"\u{1D243}"=>230,
"\u{1D244}"=>230,
"\u{1E000}"=>230,
"\u{1E001}"=>230,
"\u{1E002}"=>230,
"\u{1E003}"=>230,
"\u{1E004}"=>230,
"\u{1E005}"=>230,
"\u{1E006}"=>230,
"\u{1E008}"=>230,
"\u{1E009}"=>230,
"\u{1E00A}"=>230,
"\u{1E00B}"=>230,
"\u{1E00C}"=>230,
"\u{1E00D}"=>230,
"\u{1E00E}"=>230,
"\u{1E00F}"=>230,
"\u{1E010}"=>230,
"\u{1E011}"=>230,
"\u{1E012}"=>230,
"\u{1E013}"=>230,
"\u{1E014}"=>230,
"\u{1E015}"=>230,
"\u{1E016}"=>230,
"\u{1E017}"=>230,
"\u{1E018}"=>230,
"\u{1E01B}"=>230,
"\u{1E01C}"=>230,
"\u{1E01D}"=>230,
"\u{1E01E}"=>230,
"\u{1E01F}"=>230,
"\u{1E020}"=>230,
"\u{1E021}"=>230,
"\u{1E023}"=>230,
"\u{1E024}"=>230,
"\u{1E026}"=>230,
"\u{1E027}"=>230,
"\u{1E028}"=>230,
"\u{1E029}"=>230,
"\u{1E02A}"=>230,
"\u{1E08F}"=>230,
"\u{1E130}"=>230,
"\u{1E131}"=>230,
"\u{1E132}"=>230,
"\u{1E133}"=>230,
"\u{1E134}"=>230,
"\u{1E135}"=>230,
"\u{1E136}"=>230,
"\u{1E2AE}"=>230,
"\u{1E2EC}"=>230,
"\u{1E2ED}"=>230,
"\u{1E2EE}"=>230,
"\u{1E2EF}"=>230,
"\u{1E4EC}"=>232,
"\u{1E4ED}"=>232,
"\u{1E4EE}"=>220,
"\u{1E4EF}"=>230,
"\u{1E8D0}"=>220,
"\u{1E8D1}"=>220,
"\u{1E8D2}"=>220,
"\u{1E8D3}"=>220,
"\u{1E8D4}"=>220,
"\u{1E8D5}"=>220,
"\u{1E8D6}"=>220,
"\u{1E944}"=>230,
"\u{1E945}"=>230,
"\u{1E946}"=>230,
"\u{1E947}"=>230,
"\u{1E948}"=>230,
"\u{1E949}"=>230,
"\u{1E94A}"=>7,
}
class_table.default = 0
CLASS_TABLE = class_table.freeze
DECOMPOSITION_TABLE = {
"\u00C0"=>"A\u0300",
"\u00C1"=>"A\u0301",
"\u00C2"=>"A\u0302",
"\u00C3"=>"A\u0303",
"\u00C4"=>"A\u0308",
"\u00C5"=>"A\u030A",
"\u00C7"=>"C\u0327",
"\u00C8"=>"E\u0300",
"\u00C9"=>"E\u0301",
"\u00CA"=>"E\u0302",
"\u00CB"=>"E\u0308",
"\u00CC"=>"I\u0300",
"\u00CD"=>"I\u0301",
"\u00CE"=>"I\u0302",
"\u00CF"=>"I\u0308",
"\u00D1"=>"N\u0303",
"\u00D2"=>"O\u0300",
"\u00D3"=>"O\u0301",
"\u00D4"=>"O\u0302",
"\u00D5"=>"O\u0303",
"\u00D6"=>"O\u0308",
"\u00D9"=>"U\u0300",
"\u00DA"=>"U\u0301",
"\u00DB"=>"U\u0302",
"\u00DC"=>"U\u0308",
"\u00DD"=>"Y\u0301",
"\u00E0"=>"a\u0300",
"\u00E1"=>"a\u0301",
"\u00E2"=>"a\u0302",
"\u00E3"=>"a\u0303",
"\u00E4"=>"a\u0308",
"\u00E5"=>"a\u030A",
"\u00E7"=>"c\u0327",
"\u00E8"=>"e\u0300",
"\u00E9"=>"e\u0301",
"\u00EA"=>"e\u0302",
"\u00EB"=>"e\u0308",
"\u00EC"=>"i\u0300",
"\u00ED"=>"i\u0301",
"\u00EE"=>"i\u0302",
"\u00EF"=>"i\u0308",
"\u00F1"=>"n\u0303",
"\u00F2"=>"o\u0300",
"\u00F3"=>"o\u0301",
"\u00F4"=>"o\u0302",
"\u00F5"=>"o\u0303",
"\u00F6"=>"o\u0308",
"\u00F9"=>"u\u0300",
"\u00FA"=>"u\u0301",
"\u00FB"=>"u\u0302",
"\u00FC"=>"u\u0308",
"\u00FD"=>"y\u0301",
"\u00FF"=>"y\u0308",
"\u0100"=>"A\u0304",
"\u0101"=>"a\u0304",
"\u0102"=>"A\u0306",
"\u0103"=>"a\u0306",
"\u0104"=>"A\u0328",
"\u0105"=>"a\u0328",
"\u0106"=>"C\u0301",
"\u0107"=>"c\u0301",
"\u0108"=>"C\u0302",
"\u0109"=>"c\u0302",
"\u010A"=>"C\u0307",
"\u010B"=>"c\u0307",
"\u010C"=>"C\u030C",
"\u010D"=>"c\u030C",
"\u010E"=>"D\u030C",
"\u010F"=>"d\u030C",
"\u0112"=>"E\u0304",
"\u0113"=>"e\u0304",
"\u0114"=>"E\u0306",
"\u0115"=>"e\u0306",
"\u0116"=>"E\u0307",
"\u0117"=>"e\u0307",
"\u0118"=>"E\u0328",
"\u0119"=>"e\u0328",
"\u011A"=>"E\u030C",
"\u011B"=>"e\u030C",
"\u011C"=>"G\u0302",
"\u011D"=>"g\u0302",
"\u011E"=>"G\u0306",
"\u011F"=>"g\u0306",
"\u0120"=>"G\u0307",
"\u0121"=>"g\u0307",
"\u0122"=>"G\u0327",
"\u0123"=>"g\u0327",
"\u0124"=>"H\u0302",
"\u0125"=>"h\u0302",
"\u0128"=>"I\u0303",
"\u0129"=>"i\u0303",
"\u012A"=>"I\u0304",
"\u012B"=>"i\u0304",
"\u012C"=>"I\u0306",
"\u012D"=>"i\u0306",
"\u012E"=>"I\u0328",
"\u012F"=>"i\u0328",
"\u0130"=>"I\u0307",
"\u0134"=>"J\u0302",
"\u0135"=>"j\u0302",
"\u0136"=>"K\u0327",
"\u0137"=>"k\u0327",
"\u0139"=>"L\u0301",
"\u013A"=>"l\u0301",
"\u013B"=>"L\u0327",
"\u013C"=>"l\u0327",
"\u013D"=>"L\u030C",
"\u013E"=>"l\u030C",
"\u0143"=>"N\u0301",
"\u0144"=>"n\u0301",
"\u0145"=>"N\u0327",
"\u0146"=>"n\u0327",
"\u0147"=>"N\u030C",
"\u0148"=>"n\u030C",
"\u014C"=>"O\u0304",
"\u014D"=>"o\u0304",
"\u014E"=>"O\u0306",
"\u014F"=>"o\u0306",
"\u0150"=>"O\u030B",
"\u0151"=>"o\u030B",
"\u0154"=>"R\u0301",
"\u0155"=>"r\u0301",
"\u0156"=>"R\u0327",
"\u0157"=>"r\u0327",
"\u0158"=>"R\u030C",
"\u0159"=>"r\u030C",
"\u015A"=>"S\u0301",
"\u015B"=>"s\u0301",
"\u015C"=>"S\u0302",
"\u015D"=>"s\u0302",
"\u015E"=>"S\u0327",
"\u015F"=>"s\u0327",
"\u0160"=>"S\u030C",
"\u0161"=>"s\u030C",
"\u0162"=>"T\u0327",
"\u0163"=>"t\u0327",
"\u0164"=>"T\u030C",
"\u0165"=>"t\u030C",
"\u0168"=>"U\u0303",
"\u0169"=>"u\u0303",
"\u016A"=>"U\u0304",
"\u016B"=>"u\u0304",
"\u016C"=>"U\u0306",
"\u016D"=>"u\u0306",
"\u016E"=>"U\u030A",
"\u016F"=>"u\u030A",
"\u0170"=>"U\u030B",
"\u0171"=>"u\u030B",
"\u0172"=>"U\u0328",
"\u0173"=>"u\u0328",
"\u0174"=>"W\u0302",
"\u0175"=>"w\u0302",
"\u0176"=>"Y\u0302",
"\u0177"=>"y\u0302",
"\u0178"=>"Y\u0308",
"\u0179"=>"Z\u0301",
"\u017A"=>"z\u0301",
"\u017B"=>"Z\u0307",
"\u017C"=>"z\u0307",
"\u017D"=>"Z\u030C",
"\u017E"=>"z\u030C",
"\u01A0"=>"O\u031B",
"\u01A1"=>"o\u031B",
"\u01AF"=>"U\u031B",
"\u01B0"=>"u\u031B",
"\u01CD"=>"A\u030C",
"\u01CE"=>"a\u030C",
"\u01CF"=>"I\u030C",
"\u01D0"=>"i\u030C",
"\u01D1"=>"O\u030C",
"\u01D2"=>"o\u030C",
"\u01D3"=>"U\u030C",
"\u01D4"=>"u\u030C",
"\u01D5"=>"U\u0308\u0304",
"\u01D6"=>"u\u0308\u0304",
"\u01D7"=>"U\u0308\u0301",
"\u01D8"=>"u\u0308\u0301",
"\u01D9"=>"U\u0308\u030C",
"\u01DA"=>"u\u0308\u030C",
"\u01DB"=>"U\u0308\u0300",
"\u01DC"=>"u\u0308\u0300",
"\u01DE"=>"A\u0308\u0304",
"\u01DF"=>"a\u0308\u0304",
"\u01E0"=>"A\u0307\u0304",
"\u01E1"=>"a\u0307\u0304",
"\u01E2"=>"\u00C6\u0304",
"\u01E3"=>"\u00E6\u0304",
"\u01E6"=>"G\u030C",
"\u01E7"=>"g\u030C",
"\u01E8"=>"K\u030C",
"\u01E9"=>"k\u030C",
"\u01EA"=>"O\u0328",
"\u01EB"=>"o\u0328",
"\u01EC"=>"O\u0328\u0304",
"\u01ED"=>"o\u0328\u0304",
"\u01EE"=>"\u01B7\u030C",
"\u01EF"=>"\u0292\u030C",
"\u01F0"=>"j\u030C",
"\u01F4"=>"G\u0301",
"\u01F5"=>"g\u0301",
"\u01F8"=>"N\u0300",
"\u01F9"=>"n\u0300",
"\u01FA"=>"A\u030A\u0301",
"\u01FB"=>"a\u030A\u0301",
"\u01FC"=>"\u00C6\u0301",
"\u01FD"=>"\u00E6\u0301",
"\u01FE"=>"\u00D8\u0301",
"\u01FF"=>"\u00F8\u0301",
"\u0200"=>"A\u030F",
"\u0201"=>"a\u030F",
"\u0202"=>"A\u0311",
"\u0203"=>"a\u0311",
"\u0204"=>"E\u030F",
"\u0205"=>"e\u030F",
"\u0206"=>"E\u0311",
"\u0207"=>"e\u0311",
"\u0208"=>"I\u030F",
"\u0209"=>"i\u030F",
"\u020A"=>"I\u0311",
"\u020B"=>"i\u0311",
"\u020C"=>"O\u030F",
"\u020D"=>"o\u030F",
"\u020E"=>"O\u0311",
"\u020F"=>"o\u0311",
"\u0210"=>"R\u030F",
"\u0211"=>"r\u030F",
"\u0212"=>"R\u0311",
"\u0213"=>"r\u0311",
"\u0214"=>"U\u030F",
"\u0215"=>"u\u030F",
"\u0216"=>"U\u0311",
"\u0217"=>"u\u0311",
"\u0218"=>"S\u0326",
"\u0219"=>"s\u0326",
"\u021A"=>"T\u0326",
"\u021B"=>"t\u0326",
"\u021E"=>"H\u030C",
"\u021F"=>"h\u030C",
"\u0226"=>"A\u0307",
"\u0227"=>"a\u0307",
"\u0228"=>"E\u0327",
"\u0229"=>"e\u0327",
"\u022A"=>"O\u0308\u0304",
"\u022B"=>"o\u0308\u0304",
"\u022C"=>"O\u0303\u0304",
"\u022D"=>"o\u0303\u0304",
"\u022E"=>"O\u0307",
"\u022F"=>"o\u0307",
"\u0230"=>"O\u0307\u0304",
"\u0231"=>"o\u0307\u0304",
"\u0232"=>"Y\u0304",
"\u0233"=>"y\u0304",
"\u0340"=>"\u0300",
"\u0341"=>"\u0301",
"\u0343"=>"\u0313",
"\u0344"=>"\u0308\u0301",
"\u0374"=>"\u02B9",
"\u037E"=>";",
"\u0385"=>"\u00A8\u0301",
"\u0386"=>"\u0391\u0301",
"\u0387"=>"\u00B7",
"\u0388"=>"\u0395\u0301",
"\u0389"=>"\u0397\u0301",
"\u038A"=>"\u0399\u0301",
"\u038C"=>"\u039F\u0301",
"\u038E"=>"\u03A5\u0301",
"\u038F"=>"\u03A9\u0301",
"\u0390"=>"\u03B9\u0308\u0301",
"\u03AA"=>"\u0399\u0308",
"\u03AB"=>"\u03A5\u0308",
"\u03AC"=>"\u03B1\u0301",
"\u03AD"=>"\u03B5\u0301",
"\u03AE"=>"\u03B7\u0301",
"\u03AF"=>"\u03B9\u0301",
"\u03B0"=>"\u03C5\u0308\u0301",
"\u03CA"=>"\u03B9\u0308",
"\u03CB"=>"\u03C5\u0308",
"\u03CC"=>"\u03BF\u0301",
"\u03CD"=>"\u03C5\u0301",
"\u03CE"=>"\u03C9\u0301",
"\u03D3"=>"\u03D2\u0301",
"\u03D4"=>"\u03D2\u0308",
"\u0400"=>"\u0415\u0300",
"\u0401"=>"\u0415\u0308",
"\u0403"=>"\u0413\u0301",
"\u0407"=>"\u0406\u0308",
"\u040C"=>"\u041A\u0301",
"\u040D"=>"\u0418\u0300",
"\u040E"=>"\u0423\u0306",
"\u0419"=>"\u0418\u0306",
"\u0439"=>"\u0438\u0306",
"\u0450"=>"\u0435\u0300",
"\u0451"=>"\u0435\u0308",
"\u0453"=>"\u0433\u0301",
"\u0457"=>"\u0456\u0308",
"\u045C"=>"\u043A\u0301",
"\u045D"=>"\u0438\u0300",
"\u045E"=>"\u0443\u0306",
"\u0476"=>"\u0474\u030F",
"\u0477"=>"\u0475\u030F",
"\u04C1"=>"\u0416\u0306",
"\u04C2"=>"\u0436\u0306",
"\u04D0"=>"\u0410\u0306",
"\u04D1"=>"\u0430\u0306",
"\u04D2"=>"\u0410\u0308",
"\u04D3"=>"\u0430\u0308",
"\u04D6"=>"\u0415\u0306",
"\u04D7"=>"\u0435\u0306",
"\u04DA"=>"\u04D8\u0308",
"\u04DB"=>"\u04D9\u0308",
"\u04DC"=>"\u0416\u0308",
"\u04DD"=>"\u0436\u0308",
"\u04DE"=>"\u0417\u0308",
"\u04DF"=>"\u0437\u0308",
"\u04E2"=>"\u0418\u0304",
"\u04E3"=>"\u0438\u0304",
"\u04E4"=>"\u0418\u0308",
"\u04E5"=>"\u0438\u0308",
"\u04E6"=>"\u041E\u0308",
"\u04E7"=>"\u043E\u0308",
"\u04EA"=>"\u04E8\u0308",
"\u04EB"=>"\u04E9\u0308",
"\u04EC"=>"\u042D\u0308",
"\u04ED"=>"\u044D\u0308",
"\u04EE"=>"\u0423\u0304",
"\u04EF"=>"\u0443\u0304",
"\u04F0"=>"\u0423\u0308",
"\u04F1"=>"\u0443\u0308",
"\u04F2"=>"\u0423\u030B",
"\u04F3"=>"\u0443\u030B",
"\u04F4"=>"\u0427\u0308",
"\u04F5"=>"\u0447\u0308",
"\u04F8"=>"\u042B\u0308",
"\u04F9"=>"\u044B\u0308",
"\u0622"=>"\u0627\u0653",
"\u0623"=>"\u0627\u0654",
"\u0624"=>"\u0648\u0654",
"\u0625"=>"\u0627\u0655",
"\u0626"=>"\u064A\u0654",
"\u06C0"=>"\u06D5\u0654",
"\u06C2"=>"\u06C1\u0654",
"\u06D3"=>"\u06D2\u0654",
"\u0929"=>"\u0928\u093C",
"\u0931"=>"\u0930\u093C",
"\u0934"=>"\u0933\u093C",
"\u0958"=>"\u0915\u093C",
"\u0959"=>"\u0916\u093C",
"\u095A"=>"\u0917\u093C",
"\u095B"=>"\u091C\u093C",
"\u095C"=>"\u0921\u093C",
"\u095D"=>"\u0922\u093C",
"\u095E"=>"\u092B\u093C",
"\u095F"=>"\u092F\u093C",
"\u09CB"=>"\u09C7\u09BE",
"\u09CC"=>"\u09C7\u09D7",
"\u09DC"=>"\u09A1\u09BC",
"\u09DD"=>"\u09A2\u09BC",
"\u09DF"=>"\u09AF\u09BC",
"\u0A33"=>"\u0A32\u0A3C",
"\u0A36"=>"\u0A38\u0A3C",
"\u0A59"=>"\u0A16\u0A3C",
"\u0A5A"=>"\u0A17\u0A3C",
"\u0A5B"=>"\u0A1C\u0A3C",
"\u0A5E"=>"\u0A2B\u0A3C",
"\u0B48"=>"\u0B47\u0B56",
"\u0B4B"=>"\u0B47\u0B3E",
"\u0B4C"=>"\u0B47\u0B57",
"\u0B5C"=>"\u0B21\u0B3C",
"\u0B5D"=>"\u0B22\u0B3C",
"\u0B94"=>"\u0B92\u0BD7",
"\u0BCA"=>"\u0BC6\u0BBE",
"\u0BCB"=>"\u0BC7\u0BBE",
"\u0BCC"=>"\u0BC6\u0BD7",
"\u0C48"=>"\u0C46\u0C56",
"\u0CC0"=>"\u0CBF\u0CD5",
"\u0CC7"=>"\u0CC6\u0CD5",
"\u0CC8"=>"\u0CC6\u0CD6",
"\u0CCA"=>"\u0CC6\u0CC2",
"\u0CCB"=>"\u0CC6\u0CC2\u0CD5",
"\u0D4A"=>"\u0D46\u0D3E",
"\u0D4B"=>"\u0D47\u0D3E",
"\u0D4C"=>"\u0D46\u0D57",
"\u0DDA"=>"\u0DD9\u0DCA",
"\u0DDC"=>"\u0DD9\u0DCF",
"\u0DDD"=>"\u0DD9\u0DCF\u0DCA",
"\u0DDE"=>"\u0DD9\u0DDF",
"\u0F43"=>"\u0F42\u0FB7",
"\u0F4D"=>"\u0F4C\u0FB7",
"\u0F52"=>"\u0F51\u0FB7",
"\u0F57"=>"\u0F56\u0FB7",
"\u0F5C"=>"\u0F5B\u0FB7",
"\u0F69"=>"\u0F40\u0FB5",
"\u0F73"=>"\u0F71\u0F72",
"\u0F75"=>"\u0F71\u0F74",
"\u0F76"=>"\u0FB2\u0F80",
"\u0F78"=>"\u0FB3\u0F80",
"\u0F81"=>"\u0F71\u0F80",
"\u0F93"=>"\u0F92\u0FB7",
"\u0F9D"=>"\u0F9C\u0FB7",
"\u0FA2"=>"\u0FA1\u0FB7",
"\u0FA7"=>"\u0FA6\u0FB7",
"\u0FAC"=>"\u0FAB\u0FB7",
"\u0FB9"=>"\u0F90\u0FB5",
"\u1026"=>"\u1025\u102E",
"\u1B06"=>"\u1B05\u1B35",
"\u1B08"=>"\u1B07\u1B35",
"\u1B0A"=>"\u1B09\u1B35",
"\u1B0C"=>"\u1B0B\u1B35",
"\u1B0E"=>"\u1B0D\u1B35",
"\u1B12"=>"\u1B11\u1B35",
"\u1B3B"=>"\u1B3A\u1B35",
"\u1B3D"=>"\u1B3C\u1B35",
"\u1B40"=>"\u1B3E\u1B35",
"\u1B41"=>"\u1B3F\u1B35",
"\u1B43"=>"\u1B42\u1B35",
"\u1E00"=>"A\u0325",
"\u1E01"=>"a\u0325",
"\u1E02"=>"B\u0307",
"\u1E03"=>"b\u0307",
"\u1E04"=>"B\u0323",
"\u1E05"=>"b\u0323",
"\u1E06"=>"B\u0331",
"\u1E07"=>"b\u0331",
"\u1E08"=>"C\u0327\u0301",
"\u1E09"=>"c\u0327\u0301",
"\u1E0A"=>"D\u0307",
"\u1E0B"=>"d\u0307",
"\u1E0C"=>"D\u0323",
"\u1E0D"=>"d\u0323",
"\u1E0E"=>"D\u0331",
"\u1E0F"=>"d\u0331",
"\u1E10"=>"D\u0327",
"\u1E11"=>"d\u0327",
"\u1E12"=>"D\u032D",
"\u1E13"=>"d\u032D",
"\u1E14"=>"E\u0304\u0300",
"\u1E15"=>"e\u0304\u0300",
"\u1E16"=>"E\u0304\u0301",
"\u1E17"=>"e\u0304\u0301",
"\u1E18"=>"E\u032D",
"\u1E19"=>"e\u032D",
"\u1E1A"=>"E\u0330",
"\u1E1B"=>"e\u0330",
"\u1E1C"=>"E\u0327\u0306",
"\u1E1D"=>"e\u0327\u0306",
"\u1E1E"=>"F\u0307",
"\u1E1F"=>"f\u0307",
"\u1E20"=>"G\u0304",
"\u1E21"=>"g\u0304",
"\u1E22"=>"H\u0307",
"\u1E23"=>"h\u0307",
"\u1E24"=>"H\u0323",
"\u1E25"=>"h\u0323",
"\u1E26"=>"H\u0308",
"\u1E27"=>"h\u0308",
"\u1E28"=>"H\u0327",
"\u1E29"=>"h\u0327",
"\u1E2A"=>"H\u032E",
"\u1E2B"=>"h\u032E",
"\u1E2C"=>"I\u0330",
"\u1E2D"=>"i\u0330",
"\u1E2E"=>"I\u0308\u0301",
"\u1E2F"=>"i\u0308\u0301",
"\u1E30"=>"K\u0301",
"\u1E31"=>"k\u0301",
"\u1E32"=>"K\u0323",
"\u1E33"=>"k\u0323",
"\u1E34"=>"K\u0331",
"\u1E35"=>"k\u0331",
"\u1E36"=>"L\u0323",
"\u1E37"=>"l\u0323",
"\u1E38"=>"L\u0323\u0304",
"\u1E39"=>"l\u0323\u0304",
"\u1E3A"=>"L\u0331",
"\u1E3B"=>"l\u0331",
"\u1E3C"=>"L\u032D",
"\u1E3D"=>"l\u032D",
"\u1E3E"=>"M\u0301",
"\u1E3F"=>"m\u0301",
"\u1E40"=>"M\u0307",
"\u1E41"=>"m\u0307",
"\u1E42"=>"M\u0323",
"\u1E43"=>"m\u0323",
"\u1E44"=>"N\u0307",
"\u1E45"=>"n\u0307",
"\u1E46"=>"N\u0323",
"\u1E47"=>"n\u0323",
"\u1E48"=>"N\u0331",
"\u1E49"=>"n\u0331",
"\u1E4A"=>"N\u032D",
"\u1E4B"=>"n\u032D",
"\u1E4C"=>"O\u0303\u0301",
"\u1E4D"=>"o\u0303\u0301",
"\u1E4E"=>"O\u0303\u0308",
"\u1E4F"=>"o\u0303\u0308",
"\u1E50"=>"O\u0304\u0300",
"\u1E51"=>"o\u0304\u0300",
"\u1E52"=>"O\u0304\u0301",
"\u1E53"=>"o\u0304\u0301",
"\u1E54"=>"P\u0301",
"\u1E55"=>"p\u0301",
"\u1E56"=>"P\u0307",
"\u1E57"=>"p\u0307",
"\u1E58"=>"R\u0307",
"\u1E59"=>"r\u0307",
"\u1E5A"=>"R\u0323",
"\u1E5B"=>"r\u0323",
"\u1E5C"=>"R\u0323\u0304",
"\u1E5D"=>"r\u0323\u0304",
"\u1E5E"=>"R\u0331",
"\u1E5F"=>"r\u0331",
"\u1E60"=>"S\u0307",
"\u1E61"=>"s\u0307",
"\u1E62"=>"S\u0323",
"\u1E63"=>"s\u0323",
"\u1E64"=>"S\u0301\u0307",
"\u1E65"=>"s\u0301\u0307",
"\u1E66"=>"S\u030C\u0307",
"\u1E67"=>"s\u030C\u0307",
"\u1E68"=>"S\u0323\u0307",
"\u1E69"=>"s\u0323\u0307",
"\u1E6A"=>"T\u0307",
"\u1E6B"=>"t\u0307",
"\u1E6C"=>"T\u0323",
"\u1E6D"=>"t\u0323",
"\u1E6E"=>"T\u0331",
"\u1E6F"=>"t\u0331",
"\u1E70"=>"T\u032D",
"\u1E71"=>"t\u032D",
"\u1E72"=>"U\u0324",
"\u1E73"=>"u\u0324",
"\u1E74"=>"U\u0330",
"\u1E75"=>"u\u0330",
"\u1E76"=>"U\u032D",
"\u1E77"=>"u\u032D",
"\u1E78"=>"U\u0303\u0301",
"\u1E79"=>"u\u0303\u0301",
"\u1E7A"=>"U\u0304\u0308",
"\u1E7B"=>"u\u0304\u0308",
"\u1E7C"=>"V\u0303",
"\u1E7D"=>"v\u0303",
"\u1E7E"=>"V\u0323",
"\u1E7F"=>"v\u0323",
"\u1E80"=>"W\u0300",
"\u1E81"=>"w\u0300",
"\u1E82"=>"W\u0301",
"\u1E83"=>"w\u0301",
"\u1E84"=>"W\u0308",
"\u1E85"=>"w\u0308",
"\u1E86"=>"W\u0307",
"\u1E87"=>"w\u0307",
"\u1E88"=>"W\u0323",
"\u1E89"=>"w\u0323",
"\u1E8A"=>"X\u0307",
"\u1E8B"=>"x\u0307",
"\u1E8C"=>"X\u0308",
"\u1E8D"=>"x\u0308",
"\u1E8E"=>"Y\u0307",
"\u1E8F"=>"y\u0307",
"\u1E90"=>"Z\u0302",
"\u1E91"=>"z\u0302",
"\u1E92"=>"Z\u0323",
"\u1E93"=>"z\u0323",
"\u1E94"=>"Z\u0331",
"\u1E95"=>"z\u0331",
"\u1E96"=>"h\u0331",
"\u1E97"=>"t\u0308",
"\u1E98"=>"w\u030A",
"\u1E99"=>"y\u030A",
"\u1E9B"=>"\u017F\u0307",
"\u1EA0"=>"A\u0323",
"\u1EA1"=>"a\u0323",
"\u1EA2"=>"A\u0309",
"\u1EA3"=>"a\u0309",
"\u1EA4"=>"A\u0302\u0301",
"\u1EA5"=>"a\u0302\u0301",
"\u1EA6"=>"A\u0302\u0300",
"\u1EA7"=>"a\u0302\u0300",
"\u1EA8"=>"A\u0302\u0309",
"\u1EA9"=>"a\u0302\u0309",
"\u1EAA"=>"A\u0302\u0303",
"\u1EAB"=>"a\u0302\u0303",
"\u1EAC"=>"A\u0323\u0302",
"\u1EAD"=>"a\u0323\u0302",
"\u1EAE"=>"A\u0306\u0301",
"\u1EAF"=>"a\u0306\u0301",
"\u1EB0"=>"A\u0306\u0300",
"\u1EB1"=>"a\u0306\u0300",
"\u1EB2"=>"A\u0306\u0309",
"\u1EB3"=>"a\u0306\u0309",
"\u1EB4"=>"A\u0306\u0303",
"\u1EB5"=>"a\u0306\u0303",
"\u1EB6"=>"A\u0323\u0306",
"\u1EB7"=>"a\u0323\u0306",
"\u1EB8"=>"E\u0323",
"\u1EB9"=>"e\u0323",
"\u1EBA"=>"E\u0309",
"\u1EBB"=>"e\u0309",
"\u1EBC"=>"E\u0303",
"\u1EBD"=>"e\u0303",
"\u1EBE"=>"E\u0302\u0301",
"\u1EBF"=>"e\u0302\u0301",
"\u1EC0"=>"E\u0302\u0300",
"\u1EC1"=>"e\u0302\u0300",
"\u1EC2"=>"E\u0302\u0309",
"\u1EC3"=>"e\u0302\u0309",
"\u1EC4"=>"E\u0302\u0303",
"\u1EC5"=>"e\u0302\u0303",
"\u1EC6"=>"E\u0323\u0302",
"\u1EC7"=>"e\u0323\u0302",
"\u1EC8"=>"I\u0309",
"\u1EC9"=>"i\u0309",
"\u1ECA"=>"I\u0323",
"\u1ECB"=>"i\u0323",
"\u1ECC"=>"O\u0323",
"\u1ECD"=>"o\u0323",
"\u1ECE"=>"O\u0309",
"\u1ECF"=>"o\u0309",
"\u1ED0"=>"O\u0302\u0301",
"\u1ED1"=>"o\u0302\u0301",
"\u1ED2"=>"O\u0302\u0300",
"\u1ED3"=>"o\u0302\u0300",
"\u1ED4"=>"O\u0302\u0309",
"\u1ED5"=>"o\u0302\u0309",
"\u1ED6"=>"O\u0302\u0303",
"\u1ED7"=>"o\u0302\u0303",
"\u1ED8"=>"O\u0323\u0302",
"\u1ED9"=>"o\u0323\u0302",
"\u1EDA"=>"O\u031B\u0301",
"\u1EDB"=>"o\u031B\u0301",
"\u1EDC"=>"O\u031B\u0300",
"\u1EDD"=>"o\u031B\u0300",
"\u1EDE"=>"O\u031B\u0309",
"\u1EDF"=>"o\u031B\u0309",
"\u1EE0"=>"O\u031B\u0303",
"\u1EE1"=>"o\u031B\u0303",
"\u1EE2"=>"O\u031B\u0323",
"\u1EE3"=>"o\u031B\u0323",
"\u1EE4"=>"U\u0323",
"\u1EE5"=>"u\u0323",
"\u1EE6"=>"U\u0309",
"\u1EE7"=>"u\u0309",
"\u1EE8"=>"U\u031B\u0301",
"\u1EE9"=>"u\u031B\u0301",
"\u1EEA"=>"U\u031B\u0300",
"\u1EEB"=>"u\u031B\u0300",
"\u1EEC"=>"U\u031B\u0309",
"\u1EED"=>"u\u031B\u0309",
"\u1EEE"=>"U\u031B\u0303",
"\u1EEF"=>"u\u031B\u0303",
"\u1EF0"=>"U\u031B\u0323",
"\u1EF1"=>"u\u031B\u0323",
"\u1EF2"=>"Y\u0300",
"\u1EF3"=>"y\u0300",
"\u1EF4"=>"Y\u0323",
"\u1EF5"=>"y\u0323",
"\u1EF6"=>"Y\u0309",
"\u1EF7"=>"y\u0309",
"\u1EF8"=>"Y\u0303",
"\u1EF9"=>"y\u0303",
"\u1F00"=>"\u03B1\u0313",
"\u1F01"=>"\u03B1\u0314",
"\u1F02"=>"\u03B1\u0313\u0300",
"\u1F03"=>"\u03B1\u0314\u0300",
"\u1F04"=>"\u03B1\u0313\u0301",
"\u1F05"=>"\u03B1\u0314\u0301",
"\u1F06"=>"\u03B1\u0313\u0342",
"\u1F07"=>"\u03B1\u0314\u0342",
"\u1F08"=>"\u0391\u0313",
"\u1F09"=>"\u0391\u0314",
"\u1F0A"=>"\u0391\u0313\u0300",
"\u1F0B"=>"\u0391\u0314\u0300",
"\u1F0C"=>"\u0391\u0313\u0301",
"\u1F0D"=>"\u0391\u0314\u0301",
"\u1F0E"=>"\u0391\u0313\u0342",
"\u1F0F"=>"\u0391\u0314\u0342",
"\u1F10"=>"\u03B5\u0313",
"\u1F11"=>"\u03B5\u0314",
"\u1F12"=>"\u03B5\u0313\u0300",
"\u1F13"=>"\u03B5\u0314\u0300",
"\u1F14"=>"\u03B5\u0313\u0301",
"\u1F15"=>"\u03B5\u0314\u0301",
"\u1F18"=>"\u0395\u0313",
"\u1F19"=>"\u0395\u0314",
"\u1F1A"=>"\u0395\u0313\u0300",
"\u1F1B"=>"\u0395\u0314\u0300",
"\u1F1C"=>"\u0395\u0313\u0301",
"\u1F1D"=>"\u0395\u0314\u0301",
"\u1F20"=>"\u03B7\u0313",
"\u1F21"=>"\u03B7\u0314",
"\u1F22"=>"\u03B7\u0313\u0300",
"\u1F23"=>"\u03B7\u0314\u0300",
"\u1F24"=>"\u03B7\u0313\u0301",
"\u1F25"=>"\u03B7\u0314\u0301",
"\u1F26"=>"\u03B7\u0313\u0342",
"\u1F27"=>"\u03B7\u0314\u0342",
"\u1F28"=>"\u0397\u0313",
"\u1F29"=>"\u0397\u0314",
"\u1F2A"=>"\u0397\u0313\u0300",
"\u1F2B"=>"\u0397\u0314\u0300",
"\u1F2C"=>"\u0397\u0313\u0301",
"\u1F2D"=>"\u0397\u0314\u0301",
"\u1F2E"=>"\u0397\u0313\u0342",
"\u1F2F"=>"\u0397\u0314\u0342",
"\u1F30"=>"\u03B9\u0313",
"\u1F31"=>"\u03B9\u0314",
"\u1F32"=>"\u03B9\u0313\u0300",
"\u1F33"=>"\u03B9\u0314\u0300",
"\u1F34"=>"\u03B9\u0313\u0301",
"\u1F35"=>"\u03B9\u0314\u0301",
"\u1F36"=>"\u03B9\u0313\u0342",
"\u1F37"=>"\u03B9\u0314\u0342",
"\u1F38"=>"\u0399\u0313",
"\u1F39"=>"\u0399\u0314",
"\u1F3A"=>"\u0399\u0313\u0300",
"\u1F3B"=>"\u0399\u0314\u0300",
"\u1F3C"=>"\u0399\u0313\u0301",
"\u1F3D"=>"\u0399\u0314\u0301",
"\u1F3E"=>"\u0399\u0313\u0342",
"\u1F3F"=>"\u0399\u0314\u0342",
"\u1F40"=>"\u03BF\u0313",
"\u1F41"=>"\u03BF\u0314",
"\u1F42"=>"\u03BF\u0313\u0300",
"\u1F43"=>"\u03BF\u0314\u0300",
"\u1F44"=>"\u03BF\u0313\u0301",
"\u1F45"=>"\u03BF\u0314\u0301",
"\u1F48"=>"\u039F\u0313",
"\u1F49"=>"\u039F\u0314",
"\u1F4A"=>"\u039F\u0313\u0300",
"\u1F4B"=>"\u039F\u0314\u0300",
"\u1F4C"=>"\u039F\u0313\u0301",
"\u1F4D"=>"\u039F\u0314\u0301",
"\u1F50"=>"\u03C5\u0313",
"\u1F51"=>"\u03C5\u0314",
"\u1F52"=>"\u03C5\u0313\u0300",
"\u1F53"=>"\u03C5\u0314\u0300",
"\u1F54"=>"\u03C5\u0313\u0301",
"\u1F55"=>"\u03C5\u0314\u0301",
"\u1F56"=>"\u03C5\u0313\u0342",
"\u1F57"=>"\u03C5\u0314\u0342",
"\u1F59"=>"\u03A5\u0314",
"\u1F5B"=>"\u03A5\u0314\u0300",
"\u1F5D"=>"\u03A5\u0314\u0301",
"\u1F5F"=>"\u03A5\u0314\u0342",
"\u1F60"=>"\u03C9\u0313",
"\u1F61"=>"\u03C9\u0314",
"\u1F62"=>"\u03C9\u0313\u0300",
"\u1F63"=>"\u03C9\u0314\u0300",
"\u1F64"=>"\u03C9\u0313\u0301",
"\u1F65"=>"\u03C9\u0314\u0301",
"\u1F66"=>"\u03C9\u0313\u0342",
"\u1F67"=>"\u03C9\u0314\u0342",
"\u1F68"=>"\u03A9\u0313",
"\u1F69"=>"\u03A9\u0314",
"\u1F6A"=>"\u03A9\u0313\u0300",
"\u1F6B"=>"\u03A9\u0314\u0300",
"\u1F6C"=>"\u03A9\u0313\u0301",
"\u1F6D"=>"\u03A9\u0314\u0301",
"\u1F6E"=>"\u03A9\u0313\u0342",
"\u1F6F"=>"\u03A9\u0314\u0342",
"\u1F70"=>"\u03B1\u0300",
"\u1F71"=>"\u03B1\u0301",
"\u1F72"=>"\u03B5\u0300",
"\u1F73"=>"\u03B5\u0301",
"\u1F74"=>"\u03B7\u0300",
"\u1F75"=>"\u03B7\u0301",
"\u1F76"=>"\u03B9\u0300",
"\u1F77"=>"\u03B9\u0301",
"\u1F78"=>"\u03BF\u0300",
"\u1F79"=>"\u03BF\u0301",
"\u1F7A"=>"\u03C5\u0300",
"\u1F7B"=>"\u03C5\u0301",
"\u1F7C"=>"\u03C9\u0300",
"\u1F7D"=>"\u03C9\u0301",
"\u1F80"=>"\u03B1\u0313\u0345",
"\u1F81"=>"\u03B1\u0314\u0345",
"\u1F82"=>"\u03B1\u0313\u0300\u0345",
"\u1F83"=>"\u03B1\u0314\u0300\u0345",
"\u1F84"=>"\u03B1\u0313\u0301\u0345",
"\u1F85"=>"\u03B1\u0314\u0301\u0345",
"\u1F86"=>"\u03B1\u0313\u0342\u0345",
"\u1F87"=>"\u03B1\u0314\u0342\u0345",
"\u1F88"=>"\u0391\u0313\u0345",
"\u1F89"=>"\u0391\u0314\u0345",
"\u1F8A"=>"\u0391\u0313\u0300\u0345",
"\u1F8B"=>"\u0391\u0314\u0300\u0345",
"\u1F8C"=>"\u0391\u0313\u0301\u0345",
"\u1F8D"=>"\u0391\u0314\u0301\u0345",
"\u1F8E"=>"\u0391\u0313\u0342\u0345",
"\u1F8F"=>"\u0391\u0314\u0342\u0345",
"\u1F90"=>"\u03B7\u0313\u0345",
"\u1F91"=>"\u03B7\u0314\u0345",
"\u1F92"=>"\u03B7\u0313\u0300\u0345",
"\u1F93"=>"\u03B7\u0314\u0300\u0345",
"\u1F94"=>"\u03B7\u0313\u0301\u0345",
"\u1F95"=>"\u03B7\u0314\u0301\u0345",
"\u1F96"=>"\u03B7\u0313\u0342\u0345",
"\u1F97"=>"\u03B7\u0314\u0342\u0345",
"\u1F98"=>"\u0397\u0313\u0345",
"\u1F99"=>"\u0397\u0314\u0345",
"\u1F9A"=>"\u0397\u0313\u0300\u0345",
"\u1F9B"=>"\u0397\u0314\u0300\u0345",
"\u1F9C"=>"\u0397\u0313\u0301\u0345",
"\u1F9D"=>"\u0397\u0314\u0301\u0345",
"\u1F9E"=>"\u0397\u0313\u0342\u0345",
"\u1F9F"=>"\u0397\u0314\u0342\u0345",
"\u1FA0"=>"\u03C9\u0313\u0345",
"\u1FA1"=>"\u03C9\u0314\u0345",
"\u1FA2"=>"\u03C9\u0313\u0300\u0345",
"\u1FA3"=>"\u03C9\u0314\u0300\u0345",
"\u1FA4"=>"\u03C9\u0313\u0301\u0345",
"\u1FA5"=>"\u03C9\u0314\u0301\u0345",
"\u1FA6"=>"\u03C9\u0313\u0342\u0345",
"\u1FA7"=>"\u03C9\u0314\u0342\u0345",
"\u1FA8"=>"\u03A9\u0313\u0345",
"\u1FA9"=>"\u03A9\u0314\u0345",
"\u1FAA"=>"\u03A9\u0313\u0300\u0345",
"\u1FAB"=>"\u03A9\u0314\u0300\u0345",
"\u1FAC"=>"\u03A9\u0313\u0301\u0345",
"\u1FAD"=>"\u03A9\u0314\u0301\u0345",
"\u1FAE"=>"\u03A9\u0313\u0342\u0345",
"\u1FAF"=>"\u03A9\u0314\u0342\u0345",
"\u1FB0"=>"\u03B1\u0306",
"\u1FB1"=>"\u03B1\u0304",
"\u1FB2"=>"\u03B1\u0300\u0345",
"\u1FB3"=>"\u03B1\u0345",
"\u1FB4"=>"\u03B1\u0301\u0345",
"\u1FB6"=>"\u03B1\u0342",
"\u1FB7"=>"\u03B1\u0342\u0345",
"\u1FB8"=>"\u0391\u0306",
"\u1FB9"=>"\u0391\u0304",
"\u1FBA"=>"\u0391\u0300",
"\u1FBB"=>"\u0391\u0301",
"\u1FBC"=>"\u0391\u0345",
"\u1FBE"=>"\u03B9",
"\u1FC1"=>"\u00A8\u0342",
"\u1FC2"=>"\u03B7\u0300\u0345",
"\u1FC3"=>"\u03B7\u0345",
"\u1FC4"=>"\u03B7\u0301\u0345",
"\u1FC6"=>"\u03B7\u0342",
"\u1FC7"=>"\u03B7\u0342\u0345",
"\u1FC8"=>"\u0395\u0300",
"\u1FC9"=>"\u0395\u0301",
"\u1FCA"=>"\u0397\u0300",
"\u1FCB"=>"\u0397\u0301",
"\u1FCC"=>"\u0397\u0345",
"\u1FCD"=>"\u1FBF\u0300",
"\u1FCE"=>"\u1FBF\u0301",
"\u1FCF"=>"\u1FBF\u0342",
"\u1FD0"=>"\u03B9\u0306",
"\u1FD1"=>"\u03B9\u0304",
"\u1FD2"=>"\u03B9\u0308\u0300",
"\u1FD3"=>"\u03B9\u0308\u0301",
"\u1FD6"=>"\u03B9\u0342",
"\u1FD7"=>"\u03B9\u0308\u0342",
"\u1FD8"=>"\u0399\u0306",
"\u1FD9"=>"\u0399\u0304",
"\u1FDA"=>"\u0399\u0300",
"\u1FDB"=>"\u0399\u0301",
"\u1FDD"=>"\u1FFE\u0300",
"\u1FDE"=>"\u1FFE\u0301",
"\u1FDF"=>"\u1FFE\u0342",
"\u1FE0"=>"\u03C5\u0306",
"\u1FE1"=>"\u03C5\u0304",
"\u1FE2"=>"\u03C5\u0308\u0300",
"\u1FE3"=>"\u03C5\u0308\u0301",
"\u1FE4"=>"\u03C1\u0313",
"\u1FE5"=>"\u03C1\u0314",
"\u1FE6"=>"\u03C5\u0342",
"\u1FE7"=>"\u03C5\u0308\u0342",
"\u1FE8"=>"\u03A5\u0306",
"\u1FE9"=>"\u03A5\u0304",
"\u1FEA"=>"\u03A5\u0300",
"\u1FEB"=>"\u03A5\u0301",
"\u1FEC"=>"\u03A1\u0314",
"\u1FED"=>"\u00A8\u0300",
"\u1FEE"=>"\u00A8\u0301",
"\u1FEF"=>"`",
"\u1FF2"=>"\u03C9\u0300\u0345",
"\u1FF3"=>"\u03C9\u0345",
"\u1FF4"=>"\u03C9\u0301\u0345",
"\u1FF6"=>"\u03C9\u0342",
"\u1FF7"=>"\u03C9\u0342\u0345",
"\u1FF8"=>"\u039F\u0300",
"\u1FF9"=>"\u039F\u0301",
"\u1FFA"=>"\u03A9\u0300",
"\u1FFB"=>"\u03A9\u0301",
"\u1FFC"=>"\u03A9\u0345",
"\u1FFD"=>"\u00B4",
"\u2000"=>"\u2002",
"\u2001"=>"\u2003",
"\u2126"=>"\u03A9",
"\u212A"=>"K",
"\u212B"=>"A\u030A",
"\u219A"=>"\u2190\u0338",
"\u219B"=>"\u2192\u0338",
"\u21AE"=>"\u2194\u0338",
"\u21CD"=>"\u21D0\u0338",
"\u21CE"=>"\u21D4\u0338",
"\u21CF"=>"\u21D2\u0338",
"\u2204"=>"\u2203\u0338",
"\u2209"=>"\u2208\u0338",
"\u220C"=>"\u220B\u0338",
"\u2224"=>"\u2223\u0338",
"\u2226"=>"\u2225\u0338",
"\u2241"=>"\u223C\u0338",
"\u2244"=>"\u2243\u0338",
"\u2247"=>"\u2245\u0338",
"\u2249"=>"\u2248\u0338",
"\u2260"=>"=\u0338",
"\u2262"=>"\u2261\u0338",
"\u226D"=>"\u224D\u0338",
"\u226E"=>"<\u0338",
"\u226F"=>">\u0338",
"\u2270"=>"\u2264\u0338",
"\u2271"=>"\u2265\u0338",
"\u2274"=>"\u2272\u0338",
"\u2275"=>"\u2273\u0338",
"\u2278"=>"\u2276\u0338",
"\u2279"=>"\u2277\u0338",
"\u2280"=>"\u227A\u0338",
"\u2281"=>"\u227B\u0338",
"\u2284"=>"\u2282\u0338",
"\u2285"=>"\u2283\u0338",
"\u2288"=>"\u2286\u0338",
"\u2289"=>"\u2287\u0338",
"\u22AC"=>"\u22A2\u0338",
"\u22AD"=>"\u22A8\u0338",
"\u22AE"=>"\u22A9\u0338",
"\u22AF"=>"\u22AB\u0338",
"\u22E0"=>"\u227C\u0338",
"\u22E1"=>"\u227D\u0338",
"\u22E2"=>"\u2291\u0338",
"\u22E3"=>"\u2292\u0338",
"\u22EA"=>"\u22B2\u0338",
"\u22EB"=>"\u22B3\u0338",
"\u22EC"=>"\u22B4\u0338",
"\u22ED"=>"\u22B5\u0338",
"\u2329"=>"\u3008",
"\u232A"=>"\u3009",
"\u2ADC"=>"\u2ADD\u0338",
"\u304C"=>"\u304B\u3099",
"\u304E"=>"\u304D\u3099",
"\u3050"=>"\u304F\u3099",
"\u3052"=>"\u3051\u3099",
"\u3054"=>"\u3053\u3099",
"\u3056"=>"\u3055\u3099",
"\u3058"=>"\u3057\u3099",
"\u305A"=>"\u3059\u3099",
"\u305C"=>"\u305B\u3099",
"\u305E"=>"\u305D\u3099",
"\u3060"=>"\u305F\u3099",
"\u3062"=>"\u3061\u3099",
"\u3065"=>"\u3064\u3099",
"\u3067"=>"\u3066\u3099",
"\u3069"=>"\u3068\u3099",
"\u3070"=>"\u306F\u3099",
"\u3071"=>"\u306F\u309A",
"\u3073"=>"\u3072\u3099",
"\u3074"=>"\u3072\u309A",
"\u3076"=>"\u3075\u3099",
"\u3077"=>"\u3075\u309A",
"\u3079"=>"\u3078\u3099",
"\u307A"=>"\u3078\u309A",
"\u307C"=>"\u307B\u3099",
"\u307D"=>"\u307B\u309A",
"\u3094"=>"\u3046\u3099",
"\u309E"=>"\u309D\u3099",
"\u30AC"=>"\u30AB\u3099",
"\u30AE"=>"\u30AD\u3099",
"\u30B0"=>"\u30AF\u3099",
"\u30B2"=>"\u30B1\u3099",
"\u30B4"=>"\u30B3\u3099",
"\u30B6"=>"\u30B5\u3099",
"\u30B8"=>"\u30B7\u3099",
"\u30BA"=>"\u30B9\u3099",
"\u30BC"=>"\u30BB\u3099",
"\u30BE"=>"\u30BD\u3099",
"\u30C0"=>"\u30BF\u3099",
"\u30C2"=>"\u30C1\u3099",
"\u30C5"=>"\u30C4\u3099",
"\u30C7"=>"\u30C6\u3099",
"\u30C9"=>"\u30C8\u3099",
"\u30D0"=>"\u30CF\u3099",
"\u30D1"=>"\u30CF\u309A",
"\u30D3"=>"\u30D2\u3099",
"\u30D4"=>"\u30D2\u309A",
"\u30D6"=>"\u30D5\u3099",
"\u30D7"=>"\u30D5\u309A",
"\u30D9"=>"\u30D8\u3099",
"\u30DA"=>"\u30D8\u309A",
"\u30DC"=>"\u30DB\u3099",
"\u30DD"=>"\u30DB\u309A",
"\u30F4"=>"\u30A6\u3099",
"\u30F7"=>"\u30EF\u3099",
"\u30F8"=>"\u30F0\u3099",
"\u30F9"=>"\u30F1\u3099",
"\u30FA"=>"\u30F2\u3099",
"\u30FE"=>"\u30FD\u3099",
"\uF900"=>"\u8C48",
"\uF901"=>"\u66F4",
"\uF902"=>"\u8ECA",
"\uF903"=>"\u8CC8",
"\uF904"=>"\u6ED1",
"\uF905"=>"\u4E32",
"\uF906"=>"\u53E5",
"\uF907"=>"\u9F9C",
"\uF908"=>"\u9F9C",
"\uF909"=>"\u5951",
"\uF90A"=>"\u91D1",
"\uF90B"=>"\u5587",
"\uF90C"=>"\u5948",
"\uF90D"=>"\u61F6",
"\uF90E"=>"\u7669",
"\uF90F"=>"\u7F85",
"\uF910"=>"\u863F",
"\uF911"=>"\u87BA",
"\uF912"=>"\u88F8",
"\uF913"=>"\u908F",
"\uF914"=>"\u6A02",
"\uF915"=>"\u6D1B",
"\uF916"=>"\u70D9",
"\uF917"=>"\u73DE",
"\uF918"=>"\u843D",
"\uF919"=>"\u916A",
"\uF91A"=>"\u99F1",
"\uF91B"=>"\u4E82",
"\uF91C"=>"\u5375",
"\uF91D"=>"\u6B04",
"\uF91E"=>"\u721B",
"\uF91F"=>"\u862D",
"\uF920"=>"\u9E1E",
"\uF921"=>"\u5D50",
"\uF922"=>"\u6FEB",
"\uF923"=>"\u85CD",
"\uF924"=>"\u8964",
"\uF925"=>"\u62C9",
"\uF926"=>"\u81D8",
"\uF927"=>"\u881F",
"\uF928"=>"\u5ECA",
"\uF929"=>"\u6717",
"\uF92A"=>"\u6D6A",
"\uF92B"=>"\u72FC",
"\uF92C"=>"\u90CE",
"\uF92D"=>"\u4F86",
"\uF92E"=>"\u51B7",
"\uF92F"=>"\u52DE",
"\uF930"=>"\u64C4",
"\uF931"=>"\u6AD3",
"\uF932"=>"\u7210",
"\uF933"=>"\u76E7",
"\uF934"=>"\u8001",
"\uF935"=>"\u8606",
"\uF936"=>"\u865C",
"\uF937"=>"\u8DEF",
"\uF938"=>"\u9732",
"\uF939"=>"\u9B6F",
"\uF93A"=>"\u9DFA",
"\uF93B"=>"\u788C",
"\uF93C"=>"\u797F",
"\uF93D"=>"\u7DA0",
"\uF93E"=>"\u83C9",
"\uF93F"=>"\u9304",
"\uF940"=>"\u9E7F",
"\uF941"=>"\u8AD6",
"\uF942"=>"\u58DF",
"\uF943"=>"\u5F04",
"\uF944"=>"\u7C60",
"\uF945"=>"\u807E",
"\uF946"=>"\u7262",
"\uF947"=>"\u78CA",
"\uF948"=>"\u8CC2",
"\uF949"=>"\u96F7",
"\uF94A"=>"\u58D8",
"\uF94B"=>"\u5C62",
"\uF94C"=>"\u6A13",
"\uF94D"=>"\u6DDA",
"\uF94E"=>"\u6F0F",
"\uF94F"=>"\u7D2F",
"\uF950"=>"\u7E37",
"\uF951"=>"\u964B",
"\uF952"=>"\u52D2",
"\uF953"=>"\u808B",
"\uF954"=>"\u51DC",
"\uF955"=>"\u51CC",
"\uF956"=>"\u7A1C",
"\uF957"=>"\u7DBE",
"\uF958"=>"\u83F1",
"\uF959"=>"\u9675",
"\uF95A"=>"\u8B80",
"\uF95B"=>"\u62CF",
"\uF95C"=>"\u6A02",
"\uF95D"=>"\u8AFE",
"\uF95E"=>"\u4E39",
"\uF95F"=>"\u5BE7",
"\uF960"=>"\u6012",
"\uF961"=>"\u7387",
"\uF962"=>"\u7570",
"\uF963"=>"\u5317",
"\uF964"=>"\u78FB",
"\uF965"=>"\u4FBF",
"\uF966"=>"\u5FA9",
"\uF967"=>"\u4E0D",
"\uF968"=>"\u6CCC",
"\uF969"=>"\u6578",
"\uF96A"=>"\u7D22",
"\uF96B"=>"\u53C3",
"\uF96C"=>"\u585E",
"\uF96D"=>"\u7701",
"\uF96E"=>"\u8449",
"\uF96F"=>"\u8AAA",
"\uF970"=>"\u6BBA",
"\uF971"=>"\u8FB0",
"\uF972"=>"\u6C88",
"\uF973"=>"\u62FE",
"\uF974"=>"\u82E5",
"\uF975"=>"\u63A0",
"\uF976"=>"\u7565",
"\uF977"=>"\u4EAE",
"\uF978"=>"\u5169",
"\uF979"=>"\u51C9",
"\uF97A"=>"\u6881",
"\uF97B"=>"\u7CE7",
"\uF97C"=>"\u826F",
"\uF97D"=>"\u8AD2",
"\uF97E"=>"\u91CF",
"\uF97F"=>"\u52F5",
"\uF980"=>"\u5442",
"\uF981"=>"\u5973",
"\uF982"=>"\u5EEC",
"\uF983"=>"\u65C5",
"\uF984"=>"\u6FFE",
"\uF985"=>"\u792A",
"\uF986"=>"\u95AD",
"\uF987"=>"\u9A6A",
"\uF988"=>"\u9E97",
"\uF989"=>"\u9ECE",
"\uF98A"=>"\u529B",
"\uF98B"=>"\u66C6",
"\uF98C"=>"\u6B77",
"\uF98D"=>"\u8F62",
"\uF98E"=>"\u5E74",
"\uF98F"=>"\u6190",
"\uF990"=>"\u6200",
"\uF991"=>"\u649A",
"\uF992"=>"\u6F23",
"\uF993"=>"\u7149",
"\uF994"=>"\u7489",
"\uF995"=>"\u79CA",
"\uF996"=>"\u7DF4",
"\uF997"=>"\u806F",
"\uF998"=>"\u8F26",
"\uF999"=>"\u84EE",
"\uF99A"=>"\u9023",
"\uF99B"=>"\u934A",
"\uF99C"=>"\u5217",
"\uF99D"=>"\u52A3",
"\uF99E"=>"\u54BD",
"\uF99F"=>"\u70C8",
"\uF9A0"=>"\u88C2",
"\uF9A1"=>"\u8AAA",
"\uF9A2"=>"\u5EC9",
"\uF9A3"=>"\u5FF5",
"\uF9A4"=>"\u637B",
"\uF9A5"=>"\u6BAE",
"\uF9A6"=>"\u7C3E",
"\uF9A7"=>"\u7375",
"\uF9A8"=>"\u4EE4",
"\uF9A9"=>"\u56F9",
"\uF9AA"=>"\u5BE7",
"\uF9AB"=>"\u5DBA",
"\uF9AC"=>"\u601C",
"\uF9AD"=>"\u73B2",
"\uF9AE"=>"\u7469",
"\uF9AF"=>"\u7F9A",
"\uF9B0"=>"\u8046",
"\uF9B1"=>"\u9234",
"\uF9B2"=>"\u96F6",
"\uF9B3"=>"\u9748",
"\uF9B4"=>"\u9818",
"\uF9B5"=>"\u4F8B",
"\uF9B6"=>"\u79AE",
"\uF9B7"=>"\u91B4",
"\uF9B8"=>"\u96B8",
"\uF9B9"=>"\u60E1",
"\uF9BA"=>"\u4E86",
"\uF9BB"=>"\u50DA",
"\uF9BC"=>"\u5BEE",
"\uF9BD"=>"\u5C3F",
"\uF9BE"=>"\u6599",
"\uF9BF"=>"\u6A02",
"\uF9C0"=>"\u71CE",
"\uF9C1"=>"\u7642",
"\uF9C2"=>"\u84FC",
"\uF9C3"=>"\u907C",
"\uF9C4"=>"\u9F8D",
"\uF9C5"=>"\u6688",
"\uF9C6"=>"\u962E",
"\uF9C7"=>"\u5289",
"\uF9C8"=>"\u677B",
"\uF9C9"=>"\u67F3",
"\uF9CA"=>"\u6D41",
"\uF9CB"=>"\u6E9C",
"\uF9CC"=>"\u7409",
"\uF9CD"=>"\u7559",
"\uF9CE"=>"\u786B",
"\uF9CF"=>"\u7D10",
"\uF9D0"=>"\u985E",
"\uF9D1"=>"\u516D",
"\uF9D2"=>"\u622E",
"\uF9D3"=>"\u9678",
"\uF9D4"=>"\u502B",
"\uF9D5"=>"\u5D19",
"\uF9D6"=>"\u6DEA",
"\uF9D7"=>"\u8F2A",
"\uF9D8"=>"\u5F8B",
"\uF9D9"=>"\u6144",
"\uF9DA"=>"\u6817",
"\uF9DB"=>"\u7387",
"\uF9DC"=>"\u9686",
"\uF9DD"=>"\u5229",
"\uF9DE"=>"\u540F",
"\uF9DF"=>"\u5C65",
"\uF9E0"=>"\u6613",
"\uF9E1"=>"\u674E",
"\uF9E2"=>"\u68A8",
"\uF9E3"=>"\u6CE5",
"\uF9E4"=>"\u7406",
"\uF9E5"=>"\u75E2",
"\uF9E6"=>"\u7F79",
"\uF9E7"=>"\u88CF",
"\uF9E8"=>"\u88E1",
"\uF9E9"=>"\u91CC",
"\uF9EA"=>"\u96E2",
"\uF9EB"=>"\u533F",
"\uF9EC"=>"\u6EBA",
"\uF9ED"=>"\u541D",
"\uF9EE"=>"\u71D0",
"\uF9EF"=>"\u7498",
"\uF9F0"=>"\u85FA",
"\uF9F1"=>"\u96A3",
"\uF9F2"=>"\u9C57",
"\uF9F3"=>"\u9E9F",
"\uF9F4"=>"\u6797",
"\uF9F5"=>"\u6DCB",
"\uF9F6"=>"\u81E8",
"\uF9F7"=>"\u7ACB",
"\uF9F8"=>"\u7B20",
"\uF9F9"=>"\u7C92",
"\uF9FA"=>"\u72C0",
"\uF9FB"=>"\u7099",
"\uF9FC"=>"\u8B58",
"\uF9FD"=>"\u4EC0",
"\uF9FE"=>"\u8336",
"\uF9FF"=>"\u523A",
"\uFA00"=>"\u5207",
"\uFA01"=>"\u5EA6",
"\uFA02"=>"\u62D3",
"\uFA03"=>"\u7CD6",
"\uFA04"=>"\u5B85",
"\uFA05"=>"\u6D1E",
"\uFA06"=>"\u66B4",
"\uFA07"=>"\u8F3B",
"\uFA08"=>"\u884C",
"\uFA09"=>"\u964D",
"\uFA0A"=>"\u898B",
"\uFA0B"=>"\u5ED3",
"\uFA0C"=>"\u5140",
"\uFA0D"=>"\u55C0",
"\uFA10"=>"\u585A",
"\uFA12"=>"\u6674",
"\uFA15"=>"\u51DE",
"\uFA16"=>"\u732A",
"\uFA17"=>"\u76CA",
"\uFA18"=>"\u793C",
"\uFA19"=>"\u795E",
"\uFA1A"=>"\u7965",
"\uFA1B"=>"\u798F",
"\uFA1C"=>"\u9756",
"\uFA1D"=>"\u7CBE",
"\uFA1E"=>"\u7FBD",
"\uFA20"=>"\u8612",
"\uFA22"=>"\u8AF8",
"\uFA25"=>"\u9038",
"\uFA26"=>"\u90FD",
"\uFA2A"=>"\u98EF",
"\uFA2B"=>"\u98FC",
"\uFA2C"=>"\u9928",
"\uFA2D"=>"\u9DB4",
"\uFA2E"=>"\u90DE",
"\uFA2F"=>"\u96B7",
"\uFA30"=>"\u4FAE",
"\uFA31"=>"\u50E7",
"\uFA32"=>"\u514D",
"\uFA33"=>"\u52C9",
"\uFA34"=>"\u52E4",
"\uFA35"=>"\u5351",
"\uFA36"=>"\u559D",
"\uFA37"=>"\u5606",
"\uFA38"=>"\u5668",
"\uFA39"=>"\u5840",
"\uFA3A"=>"\u58A8",
"\uFA3B"=>"\u5C64",
"\uFA3C"=>"\u5C6E",
"\uFA3D"=>"\u6094",
"\uFA3E"=>"\u6168",
"\uFA3F"=>"\u618E",
"\uFA40"=>"\u61F2",
"\uFA41"=>"\u654F",
"\uFA42"=>"\u65E2",
"\uFA43"=>"\u6691",
"\uFA44"=>"\u6885",
"\uFA45"=>"\u6D77",
"\uFA46"=>"\u6E1A",
"\uFA47"=>"\u6F22",
"\uFA48"=>"\u716E",
"\uFA49"=>"\u722B",
"\uFA4A"=>"\u7422",
"\uFA4B"=>"\u7891",
"\uFA4C"=>"\u793E",
"\uFA4D"=>"\u7949",
"\uFA4E"=>"\u7948",
"\uFA4F"=>"\u7950",
"\uFA50"=>"\u7956",
"\uFA51"=>"\u795D",
"\uFA52"=>"\u798D",
"\uFA53"=>"\u798E",
"\uFA54"=>"\u7A40",
"\uFA55"=>"\u7A81",
"\uFA56"=>"\u7BC0",
"\uFA57"=>"\u7DF4",
"\uFA58"=>"\u7E09",
"\uFA59"=>"\u7E41",
"\uFA5A"=>"\u7F72",
"\uFA5B"=>"\u8005",
"\uFA5C"=>"\u81ED",
"\uFA5D"=>"\u8279",
"\uFA5E"=>"\u8279",
"\uFA5F"=>"\u8457",
"\uFA60"=>"\u8910",
"\uFA61"=>"\u8996",
"\uFA62"=>"\u8B01",
"\uFA63"=>"\u8B39",
"\uFA64"=>"\u8CD3",
"\uFA65"=>"\u8D08",
"\uFA66"=>"\u8FB6",
"\uFA67"=>"\u9038",
"\uFA68"=>"\u96E3",
"\uFA69"=>"\u97FF",
"\uFA6A"=>"\u983B",
"\uFA6B"=>"\u6075",
"\uFA6C"=>"\u{242EE}",
"\uFA6D"=>"\u8218",
"\uFA70"=>"\u4E26",
"\uFA71"=>"\u51B5",
"\uFA72"=>"\u5168",
"\uFA73"=>"\u4F80",
"\uFA74"=>"\u5145",
"\uFA75"=>"\u5180",
"\uFA76"=>"\u52C7",
"\uFA77"=>"\u52FA",
"\uFA78"=>"\u559D",
"\uFA79"=>"\u5555",
"\uFA7A"=>"\u5599",
"\uFA7B"=>"\u55E2",
"\uFA7C"=>"\u585A",
"\uFA7D"=>"\u58B3",
"\uFA7E"=>"\u5944",
"\uFA7F"=>"\u5954",
"\uFA80"=>"\u5A62",
"\uFA81"=>"\u5B28",
"\uFA82"=>"\u5ED2",
"\uFA83"=>"\u5ED9",
"\uFA84"=>"\u5F69",
"\uFA85"=>"\u5FAD",
"\uFA86"=>"\u60D8",
"\uFA87"=>"\u614E",
"\uFA88"=>"\u6108",
"\uFA89"=>"\u618E",
"\uFA8A"=>"\u6160",
"\uFA8B"=>"\u61F2",
"\uFA8C"=>"\u6234",
"\uFA8D"=>"\u63C4",
"\uFA8E"=>"\u641C",
"\uFA8F"=>"\u6452",
"\uFA90"=>"\u6556",
"\uFA91"=>"\u6674",
"\uFA92"=>"\u6717",
"\uFA93"=>"\u671B",
"\uFA94"=>"\u6756",
"\uFA95"=>"\u6B79",
"\uFA96"=>"\u6BBA",
"\uFA97"=>"\u6D41",
"\uFA98"=>"\u6EDB",
"\uFA99"=>"\u6ECB",
"\uFA9A"=>"\u6F22",
"\uFA9B"=>"\u701E",
"\uFA9C"=>"\u716E",
"\uFA9D"=>"\u77A7",
"\uFA9E"=>"\u7235",
"\uFA9F"=>"\u72AF",
"\uFAA0"=>"\u732A",
"\uFAA1"=>"\u7471",
"\uFAA2"=>"\u7506",
"\uFAA3"=>"\u753B",
"\uFAA4"=>"\u761D",
"\uFAA5"=>"\u761F",
"\uFAA6"=>"\u76CA",
"\uFAA7"=>"\u76DB",
"\uFAA8"=>"\u76F4",
"\uFAA9"=>"\u774A",
"\uFAAA"=>"\u7740",
"\uFAAB"=>"\u78CC",
"\uFAAC"=>"\u7AB1",
"\uFAAD"=>"\u7BC0",
"\uFAAE"=>"\u7C7B",
"\uFAAF"=>"\u7D5B",
"\uFAB0"=>"\u7DF4",
"\uFAB1"=>"\u7F3E",
"\uFAB2"=>"\u8005",
"\uFAB3"=>"\u8352",
"\uFAB4"=>"\u83EF",
"\uFAB5"=>"\u8779",
"\uFAB6"=>"\u8941",
"\uFAB7"=>"\u8986",
"\uFAB8"=>"\u8996",
"\uFAB9"=>"\u8ABF",
"\uFABA"=>"\u8AF8",
"\uFABB"=>"\u8ACB",
"\uFABC"=>"\u8B01",
"\uFABD"=>"\u8AFE",
"\uFABE"=>"\u8AED",
"\uFABF"=>"\u8B39",
"\uFAC0"=>"\u8B8A",
"\uFAC1"=>"\u8D08",
"\uFAC2"=>"\u8F38",
"\uFAC3"=>"\u9072",
"\uFAC4"=>"\u9199",
"\uFAC5"=>"\u9276",
"\uFAC6"=>"\u967C",
"\uFAC7"=>"\u96E3",
"\uFAC8"=>"\u9756",
"\uFAC9"=>"\u97DB",
"\uFACA"=>"\u97FF",
"\uFACB"=>"\u980B",
"\uFACC"=>"\u983B",
"\uFACD"=>"\u9B12",
"\uFACE"=>"\u9F9C",
"\uFACF"=>"\u{2284A}",
"\uFAD0"=>"\u{22844}",
"\uFAD1"=>"\u{233D5}",
"\uFAD2"=>"\u3B9D",
"\uFAD3"=>"\u4018",
"\uFAD4"=>"\u4039",
"\uFAD5"=>"\u{25249}",
"\uFAD6"=>"\u{25CD0}",
"\uFAD7"=>"\u{27ED3}",
"\uFAD8"=>"\u9F43",
"\uFAD9"=>"\u9F8E",
"\uFB1D"=>"\u05D9\u05B4",
"\uFB1F"=>"\u05F2\u05B7",
"\uFB2A"=>"\u05E9\u05C1",
"\uFB2B"=>"\u05E9\u05C2",
"\uFB2C"=>"\u05E9\u05BC\u05C1",
"\uFB2D"=>"\u05E9\u05BC\u05C2",
"\uFB2E"=>"\u05D0\u05B7",
"\uFB2F"=>"\u05D0\u05B8",
"\uFB30"=>"\u05D0\u05BC",
"\uFB31"=>"\u05D1\u05BC",
"\uFB32"=>"\u05D2\u05BC",
"\uFB33"=>"\u05D3\u05BC",
"\uFB34"=>"\u05D4\u05BC",
"\uFB35"=>"\u05D5\u05BC",
"\uFB36"=>"\u05D6\u05BC",
"\uFB38"=>"\u05D8\u05BC",
"\uFB39"=>"\u05D9\u05BC",
"\uFB3A"=>"\u05DA\u05BC",
"\uFB3B"=>"\u05DB\u05BC",
"\uFB3C"=>"\u05DC\u05BC",
"\uFB3E"=>"\u05DE\u05BC",
"\uFB40"=>"\u05E0\u05BC",
"\uFB41"=>"\u05E1\u05BC",
"\uFB43"=>"\u05E3\u05BC",
"\uFB44"=>"\u05E4\u05BC",
"\uFB46"=>"\u05E6\u05BC",
"\uFB47"=>"\u05E7\u05BC",
"\uFB48"=>"\u05E8\u05BC",
"\uFB49"=>"\u05E9\u05BC",
"\uFB4A"=>"\u05EA\u05BC",
"\uFB4B"=>"\u05D5\u05B9",
"\uFB4C"=>"\u05D1\u05BF",
"\uFB4D"=>"\u05DB\u05BF",
"\uFB4E"=>"\u05E4\u05BF",
"\u{1109A}"=>"\u{11099}\u{110BA}",
"\u{1109C}"=>"\u{1109B}\u{110BA}",
"\u{110AB}"=>"\u{110A5}\u{110BA}",
"\u{1112E}"=>"\u{11131}\u{11127}",
"\u{1112F}"=>"\u{11132}\u{11127}",
"\u{1134B}"=>"\u{11347}\u{1133E}",
"\u{1134C}"=>"\u{11347}\u{11357}",
"\u{114BB}"=>"\u{114B9}\u{114BA}",
"\u{114BC}"=>"\u{114B9}\u{114B0}",
"\u{114BE}"=>"\u{114B9}\u{114BD}",
"\u{115BA}"=>"\u{115B8}\u{115AF}",
"\u{115BB}"=>"\u{115B9}\u{115AF}",
"\u{11938}"=>"\u{11935}\u{11930}",
"\u{1D15E}"=>"\u{1D157}\u{1D165}",
"\u{1D15F}"=>"\u{1D158}\u{1D165}",
"\u{1D160}"=>"\u{1D158}\u{1D165}\u{1D16E}",
"\u{1D161}"=>"\u{1D158}\u{1D165}\u{1D16F}",
"\u{1D162}"=>"\u{1D158}\u{1D165}\u{1D170}",
"\u{1D163}"=>"\u{1D158}\u{1D165}\u{1D171}",
"\u{1D164}"=>"\u{1D158}\u{1D165}\u{1D172}",
"\u{1D1BB}"=>"\u{1D1B9}\u{1D165}",
"\u{1D1BC}"=>"\u{1D1BA}\u{1D165}",
"\u{1D1BD}"=>"\u{1D1B9}\u{1D165}\u{1D16E}",
"\u{1D1BE}"=>"\u{1D1BA}\u{1D165}\u{1D16E}",
"\u{1D1BF}"=>"\u{1D1B9}\u{1D165}\u{1D16F}",
"\u{1D1C0}"=>"\u{1D1BA}\u{1D165}\u{1D16F}",
"\u{2F800}"=>"\u4E3D",
"\u{2F801}"=>"\u4E38",
"\u{2F802}"=>"\u4E41",
"\u{2F803}"=>"\u{20122}",
"\u{2F804}"=>"\u4F60",
"\u{2F805}"=>"\u4FAE",
"\u{2F806}"=>"\u4FBB",
"\u{2F807}"=>"\u5002",
"\u{2F808}"=>"\u507A",
"\u{2F809}"=>"\u5099",
"\u{2F80A}"=>"\u50E7",
"\u{2F80B}"=>"\u50CF",
"\u{2F80C}"=>"\u349E",
"\u{2F80D}"=>"\u{2063A}",
"\u{2F80E}"=>"\u514D",
"\u{2F80F}"=>"\u5154",
"\u{2F810}"=>"\u5164",
"\u{2F811}"=>"\u5177",
"\u{2F812}"=>"\u{2051C}",
"\u{2F813}"=>"\u34B9",
"\u{2F814}"=>"\u5167",
"\u{2F815}"=>"\u518D",
"\u{2F816}"=>"\u{2054B}",
"\u{2F817}"=>"\u5197",
"\u{2F818}"=>"\u51A4",
"\u{2F819}"=>"\u4ECC",
"\u{2F81A}"=>"\u51AC",
"\u{2F81B}"=>"\u51B5",
"\u{2F81C}"=>"\u{291DF}",
"\u{2F81D}"=>"\u51F5",
"\u{2F81E}"=>"\u5203",
"\u{2F81F}"=>"\u34DF",
"\u{2F820}"=>"\u523B",
"\u{2F821}"=>"\u5246",
"\u{2F822}"=>"\u5272",
"\u{2F823}"=>"\u5277",
"\u{2F824}"=>"\u3515",
"\u{2F825}"=>"\u52C7",
"\u{2F826}"=>"\u52C9",
"\u{2F827}"=>"\u52E4",
"\u{2F828}"=>"\u52FA",
"\u{2F829}"=>"\u5305",
"\u{2F82A}"=>"\u5306",
"\u{2F82B}"=>"\u5317",
"\u{2F82C}"=>"\u5349",
"\u{2F82D}"=>"\u5351",
"\u{2F82E}"=>"\u535A",
"\u{2F82F}"=>"\u5373",
"\u{2F830}"=>"\u537D",
"\u{2F831}"=>"\u537F",
"\u{2F832}"=>"\u537F",
"\u{2F833}"=>"\u537F",
"\u{2F834}"=>"\u{20A2C}",
"\u{2F835}"=>"\u7070",
"\u{2F836}"=>"\u53CA",
"\u{2F837}"=>"\u53DF",
"\u{2F838}"=>"\u{20B63}",
"\u{2F839}"=>"\u53EB",
"\u{2F83A}"=>"\u53F1",
"\u{2F83B}"=>"\u5406",
"\u{2F83C}"=>"\u549E",
"\u{2F83D}"=>"\u5438",
"\u{2F83E}"=>"\u5448",
"\u{2F83F}"=>"\u5468",
"\u{2F840}"=>"\u54A2",
"\u{2F841}"=>"\u54F6",
"\u{2F842}"=>"\u5510",
"\u{2F843}"=>"\u5553",
"\u{2F844}"=>"\u5563",
"\u{2F845}"=>"\u5584",
"\u{2F846}"=>"\u5584",
"\u{2F847}"=>"\u5599",
"\u{2F848}"=>"\u55AB",
"\u{2F849}"=>"\u55B3",
"\u{2F84A}"=>"\u55C2",
"\u{2F84B}"=>"\u5716",
"\u{2F84C}"=>"\u5606",
"\u{2F84D}"=>"\u5717",
"\u{2F84E}"=>"\u5651",
"\u{2F84F}"=>"\u5674",
"\u{2F850}"=>"\u5207",
"\u{2F851}"=>"\u58EE",
"\u{2F852}"=>"\u57CE",
"\u{2F853}"=>"\u57F4",
"\u{2F854}"=>"\u580D",
"\u{2F855}"=>"\u578B",
"\u{2F856}"=>"\u5832",
"\u{2F857}"=>"\u5831",
"\u{2F858}"=>"\u58AC",
"\u{2F859}"=>"\u{214E4}",
"\u{2F85A}"=>"\u58F2",
"\u{2F85B}"=>"\u58F7",
"\u{2F85C}"=>"\u5906",
"\u{2F85D}"=>"\u591A",
"\u{2F85E}"=>"\u5922",
"\u{2F85F}"=>"\u5962",
"\u{2F860}"=>"\u{216A8}",
"\u{2F861}"=>"\u{216EA}",
"\u{2F862}"=>"\u59EC",
"\u{2F863}"=>"\u5A1B",
"\u{2F864}"=>"\u5A27",
"\u{2F865}"=>"\u59D8",
"\u{2F866}"=>"\u5A66",
"\u{2F867}"=>"\u36EE",
"\u{2F868}"=>"\u36FC",
"\u{2F869}"=>"\u5B08",
"\u{2F86A}"=>"\u5B3E",
"\u{2F86B}"=>"\u5B3E",
"\u{2F86C}"=>"\u{219C8}",
"\u{2F86D}"=>"\u5BC3",
"\u{2F86E}"=>"\u5BD8",
"\u{2F86F}"=>"\u5BE7",
"\u{2F870}"=>"\u5BF3",
"\u{2F871}"=>"\u{21B18}",
"\u{2F872}"=>"\u5BFF",
"\u{2F873}"=>"\u5C06",
"\u{2F874}"=>"\u5F53",
"\u{2F875}"=>"\u5C22",
"\u{2F876}"=>"\u3781",
"\u{2F877}"=>"\u5C60",
"\u{2F878}"=>"\u5C6E",
"\u{2F879}"=>"\u5CC0",
"\u{2F87A}"=>"\u5C8D",
"\u{2F87B}"=>"\u{21DE4}",
"\u{2F87C}"=>"\u5D43",
"\u{2F87D}"=>"\u{21DE6}",
"\u{2F87E}"=>"\u5D6E",
"\u{2F87F}"=>"\u5D6B",
"\u{2F880}"=>"\u5D7C",
"\u{2F881}"=>"\u5DE1",
"\u{2F882}"=>"\u5DE2",
"\u{2F883}"=>"\u382F",
"\u{2F884}"=>"\u5DFD",
"\u{2F885}"=>"\u5E28",
"\u{2F886}"=>"\u5E3D",
"\u{2F887}"=>"\u5E69",
"\u{2F888}"=>"\u3862",
"\u{2F889}"=>"\u{22183}",
"\u{2F88A}"=>"\u387C",
"\u{2F88B}"=>"\u5EB0",
"\u{2F88C}"=>"\u5EB3",
"\u{2F88D}"=>"\u5EB6",
"\u{2F88E}"=>"\u5ECA",
"\u{2F88F}"=>"\u{2A392}",
"\u{2F890}"=>"\u5EFE",
"\u{2F891}"=>"\u{22331}",
"\u{2F892}"=>"\u{22331}",
"\u{2F893}"=>"\u8201",
"\u{2F894}"=>"\u5F22",
"\u{2F895}"=>"\u5F22",
"\u{2F896}"=>"\u38C7",
"\u{2F897}"=>"\u{232B8}",
"\u{2F898}"=>"\u{261DA}",
"\u{2F899}"=>"\u5F62",
"\u{2F89A}"=>"\u5F6B",
"\u{2F89B}"=>"\u38E3",
"\u{2F89C}"=>"\u5F9A",
"\u{2F89D}"=>"\u5FCD",
"\u{2F89E}"=>"\u5FD7",
"\u{2F89F}"=>"\u5FF9",
"\u{2F8A0}"=>"\u6081",
"\u{2F8A1}"=>"\u393A",
"\u{2F8A2}"=>"\u391C",
"\u{2F8A3}"=>"\u6094",
"\u{2F8A4}"=>"\u{226D4}",
"\u{2F8A5}"=>"\u60C7",
"\u{2F8A6}"=>"\u6148",
"\u{2F8A7}"=>"\u614C",
"\u{2F8A8}"=>"\u614E",
"\u{2F8A9}"=>"\u614C",
"\u{2F8AA}"=>"\u617A",
"\u{2F8AB}"=>"\u618E",
"\u{2F8AC}"=>"\u61B2",
"\u{2F8AD}"=>"\u61A4",
"\u{2F8AE}"=>"\u61AF",
"\u{2F8AF}"=>"\u61DE",
"\u{2F8B0}"=>"\u61F2",
"\u{2F8B1}"=>"\u61F6",
"\u{2F8B2}"=>"\u6210",
"\u{2F8B3}"=>"\u621B",
"\u{2F8B4}"=>"\u625D",
"\u{2F8B5}"=>"\u62B1",
"\u{2F8B6}"=>"\u62D4",
"\u{2F8B7}"=>"\u6350",
"\u{2F8B8}"=>"\u{22B0C}",
"\u{2F8B9}"=>"\u633D",
"\u{2F8BA}"=>"\u62FC",
"\u{2F8BB}"=>"\u6368",
"\u{2F8BC}"=>"\u6383",
"\u{2F8BD}"=>"\u63E4",
"\u{2F8BE}"=>"\u{22BF1}",
"\u{2F8BF}"=>"\u6422",
"\u{2F8C0}"=>"\u63C5",
"\u{2F8C1}"=>"\u63A9",
"\u{2F8C2}"=>"\u3A2E",
"\u{2F8C3}"=>"\u6469",
"\u{2F8C4}"=>"\u647E",
"\u{2F8C5}"=>"\u649D",
"\u{2F8C6}"=>"\u6477",
"\u{2F8C7}"=>"\u3A6C",
"\u{2F8C8}"=>"\u654F",
"\u{2F8C9}"=>"\u656C",
"\u{2F8CA}"=>"\u{2300A}",
"\u{2F8CB}"=>"\u65E3",
"\u{2F8CC}"=>"\u66F8",
"\u{2F8CD}"=>"\u6649",
"\u{2F8CE}"=>"\u3B19",
"\u{2F8CF}"=>"\u6691",
"\u{2F8D0}"=>"\u3B08",
"\u{2F8D1}"=>"\u3AE4",
"\u{2F8D2}"=>"\u5192",
"\u{2F8D3}"=>"\u5195",
"\u{2F8D4}"=>"\u6700",
"\u{2F8D5}"=>"\u669C",
"\u{2F8D6}"=>"\u80AD",
"\u{2F8D7}"=>"\u43D9",
"\u{2F8D8}"=>"\u6717",
"\u{2F8D9}"=>"\u671B",
"\u{2F8DA}"=>"\u6721",
"\u{2F8DB}"=>"\u675E",
"\u{2F8DC}"=>"\u6753",
"\u{2F8DD}"=>"\u{233C3}",
"\u{2F8DE}"=>"\u3B49",
"\u{2F8DF}"=>"\u67FA",
"\u{2F8E0}"=>"\u6785",
"\u{2F8E1}"=>"\u6852",
"\u{2F8E2}"=>"\u6885",
"\u{2F8E3}"=>"\u{2346D}",
"\u{2F8E4}"=>"\u688E",
"\u{2F8E5}"=>"\u681F",
"\u{2F8E6}"=>"\u6914",
"\u{2F8E7}"=>"\u3B9D",
"\u{2F8E8}"=>"\u6942",
"\u{2F8E9}"=>"\u69A3",
"\u{2F8EA}"=>"\u69EA",
"\u{2F8EB}"=>"\u6AA8",
"\u{2F8EC}"=>"\u{236A3}",
"\u{2F8ED}"=>"\u6ADB",
"\u{2F8EE}"=>"\u3C18",
"\u{2F8EF}"=>"\u6B21",
"\u{2F8F0}"=>"\u{238A7}",
"\u{2F8F1}"=>"\u6B54",
"\u{2F8F2}"=>"\u3C4E",
"\u{2F8F3}"=>"\u6B72",
"\u{2F8F4}"=>"\u6B9F",
"\u{2F8F5}"=>"\u6BBA",
"\u{2F8F6}"=>"\u6BBB",
"\u{2F8F7}"=>"\u{23A8D}",
"\u{2F8F8}"=>"\u{21D0B}",
"\u{2F8F9}"=>"\u{23AFA}",
"\u{2F8FA}"=>"\u6C4E",
"\u{2F8FB}"=>"\u{23CBC}",
"\u{2F8FC}"=>"\u6CBF",
"\u{2F8FD}"=>"\u6CCD",
"\u{2F8FE}"=>"\u6C67",
"\u{2F8FF}"=>"\u6D16",
"\u{2F900}"=>"\u6D3E",
"\u{2F901}"=>"\u6D77",
"\u{2F902}"=>"\u6D41",
"\u{2F903}"=>"\u6D69",
"\u{2F904}"=>"\u6D78",
"\u{2F905}"=>"\u6D85",
"\u{2F906}"=>"\u{23D1E}",
"\u{2F907}"=>"\u6D34",
"\u{2F908}"=>"\u6E2F",
"\u{2F909}"=>"\u6E6E",
"\u{2F90A}"=>"\u3D33",
"\u{2F90B}"=>"\u6ECB",
"\u{2F90C}"=>"\u6EC7",
"\u{2F90D}"=>"\u{23ED1}",
"\u{2F90E}"=>"\u6DF9",
"\u{2F90F}"=>"\u6F6E",
"\u{2F910}"=>"\u{23F5E}",
"\u{2F911}"=>"\u{23F8E}",
"\u{2F912}"=>"\u6FC6",
"\u{2F913}"=>"\u7039",
"\u{2F914}"=>"\u701E",
"\u{2F915}"=>"\u701B",
"\u{2F916}"=>"\u3D96",
"\u{2F917}"=>"\u704A",
"\u{2F918}"=>"\u707D",
"\u{2F919}"=>"\u7077",
"\u{2F91A}"=>"\u70AD",
"\u{2F91B}"=>"\u{20525}",
"\u{2F91C}"=>"\u7145",
"\u{2F91D}"=>"\u{24263}",
"\u{2F91E}"=>"\u719C",
"\u{2F91F}"=>"\u{243AB}",
"\u{2F920}"=>"\u7228",
"\u{2F921}"=>"\u7235",
"\u{2F922}"=>"\u7250",
"\u{2F923}"=>"\u{24608}",
"\u{2F924}"=>"\u7280",
"\u{2F925}"=>"\u7295",
"\u{2F926}"=>"\u{24735}",
"\u{2F927}"=>"\u{24814}",
"\u{2F928}"=>"\u737A",
"\u{2F929}"=>"\u738B",
"\u{2F92A}"=>"\u3EAC",
"\u{2F92B}"=>"\u73A5",
"\u{2F92C}"=>"\u3EB8",
"\u{2F92D}"=>"\u3EB8",
"\u{2F92E}"=>"\u7447",
"\u{2F92F}"=>"\u745C",
"\u{2F930}"=>"\u7471",
"\u{2F931}"=>"\u7485",
"\u{2F932}"=>"\u74CA",
"\u{2F933}"=>"\u3F1B",
"\u{2F934}"=>"\u7524",
"\u{2F935}"=>"\u{24C36}",
"\u{2F936}"=>"\u753E",
"\u{2F937}"=>"\u{24C92}",
"\u{2F938}"=>"\u7570",
"\u{2F939}"=>"\u{2219F}",
"\u{2F93A}"=>"\u7610",
"\u{2F93B}"=>"\u{24FA1}",
"\u{2F93C}"=>"\u{24FB8}",
"\u{2F93D}"=>"\u{25044}",
"\u{2F93E}"=>"\u3FFC",
"\u{2F93F}"=>"\u4008",
"\u{2F940}"=>"\u76F4",
"\u{2F941}"=>"\u{250F3}",
"\u{2F942}"=>"\u{250F2}",
"\u{2F943}"=>"\u{25119}",
"\u{2F944}"=>"\u{25133}",
"\u{2F945}"=>"\u771E",
"\u{2F946}"=>"\u771F",
"\u{2F947}"=>"\u771F",
"\u{2F948}"=>"\u774A",
"\u{2F949}"=>"\u4039",
"\u{2F94A}"=>"\u778B",
"\u{2F94B}"=>"\u4046",
"\u{2F94C}"=>"\u4096",
"\u{2F94D}"=>"\u{2541D}",
"\u{2F94E}"=>"\u784E",
"\u{2F94F}"=>"\u788C",
"\u{2F950}"=>"\u78CC",
"\u{2F951}"=>"\u40E3",
"\u{2F952}"=>"\u{25626}",
"\u{2F953}"=>"\u7956",
"\u{2F954}"=>"\u{2569A}",
"\u{2F955}"=>"\u{256C5}",
"\u{2F956}"=>"\u798F",
"\u{2F957}"=>"\u79EB",
"\u{2F958}"=>"\u412F",
"\u{2F959}"=>"\u7A40",
"\u{2F95A}"=>"\u7A4A",
"\u{2F95B}"=>"\u7A4F",
"\u{2F95C}"=>"\u{2597C}",
"\u{2F95D}"=>"\u{25AA7}",
"\u{2F95E}"=>"\u{25AA7}",
"\u{2F95F}"=>"\u7AEE",
"\u{2F960}"=>"\u4202",
"\u{2F961}"=>"\u{25BAB}",
"\u{2F962}"=>"\u7BC6",
"\u{2F963}"=>"\u7BC9",
"\u{2F964}"=>"\u4227",
"\u{2F965}"=>"\u{25C80}",
"\u{2F966}"=>"\u7CD2",
"\u{2F967}"=>"\u42A0",
"\u{2F968}"=>"\u7CE8",
"\u{2F969}"=>"\u7CE3",
"\u{2F96A}"=>"\u7D00",
"\u{2F96B}"=>"\u{25F86}",
"\u{2F96C}"=>"\u7D63",
"\u{2F96D}"=>"\u4301",
"\u{2F96E}"=>"\u7DC7",
"\u{2F96F}"=>"\u7E02",
"\u{2F970}"=>"\u7E45",
"\u{2F971}"=>"\u4334",
"\u{2F972}"=>"\u{26228}",
"\u{2F973}"=>"\u{26247}",
"\u{2F974}"=>"\u4359",
"\u{2F975}"=>"\u{262D9}",
"\u{2F976}"=>"\u7F7A",
"\u{2F977}"=>"\u{2633E}",
"\u{2F978}"=>"\u7F95",
"\u{2F979}"=>"\u7FFA",
"\u{2F97A}"=>"\u8005",
"\u{2F97B}"=>"\u{264DA}",
"\u{2F97C}"=>"\u{26523}",
"\u{2F97D}"=>"\u8060",
"\u{2F97E}"=>"\u{265A8}",
"\u{2F97F}"=>"\u8070",
"\u{2F980}"=>"\u{2335F}",
"\u{2F981}"=>"\u43D5",
"\u{2F982}"=>"\u80B2",
"\u{2F983}"=>"\u8103",
"\u{2F984}"=>"\u440B",
"\u{2F985}"=>"\u813E",
"\u{2F986}"=>"\u5AB5",
"\u{2F987}"=>"\u{267A7}",
"\u{2F988}"=>"\u{267B5}",
"\u{2F989}"=>"\u{23393}",
"\u{2F98A}"=>"\u{2339C}",
"\u{2F98B}"=>"\u8201",
"\u{2F98C}"=>"\u8204",
"\u{2F98D}"=>"\u8F9E",
"\u{2F98E}"=>"\u446B",
"\u{2F98F}"=>"\u8291",
"\u{2F990}"=>"\u828B",
"\u{2F991}"=>"\u829D",
"\u{2F992}"=>"\u52B3",
"\u{2F993}"=>"\u82B1",
"\u{2F994}"=>"\u82B3",
"\u{2F995}"=>"\u82BD",
"\u{2F996}"=>"\u82E6",
"\u{2F997}"=>"\u{26B3C}",
"\u{2F998}"=>"\u82E5",
"\u{2F999}"=>"\u831D",
"\u{2F99A}"=>"\u8363",
"\u{2F99B}"=>"\u83AD",
"\u{2F99C}"=>"\u8323",
"\u{2F99D}"=>"\u83BD",
"\u{2F99E}"=>"\u83E7",
"\u{2F99F}"=>"\u8457",
"\u{2F9A0}"=>"\u8353",
"\u{2F9A1}"=>"\u83CA",
"\u{2F9A2}"=>"\u83CC",
"\u{2F9A3}"=>"\u83DC",
"\u{2F9A4}"=>"\u{26C36}",
"\u{2F9A5}"=>"\u{26D6B}",
"\u{2F9A6}"=>"\u{26CD5}",
"\u{2F9A7}"=>"\u452B",
"\u{2F9A8}"=>"\u84F1",
"\u{2F9A9}"=>"\u84F3",
"\u{2F9AA}"=>"\u8516",
"\u{2F9AB}"=>"\u{273CA}",
"\u{2F9AC}"=>"\u8564",
"\u{2F9AD}"=>"\u{26F2C}",
"\u{2F9AE}"=>"\u455D",
"\u{2F9AF}"=>"\u4561",
"\u{2F9B0}"=>"\u{26FB1}",
"\u{2F9B1}"=>"\u{270D2}",
"\u{2F9B2}"=>"\u456B",
"\u{2F9B3}"=>"\u8650",
"\u{2F9B4}"=>"\u865C",
"\u{2F9B5}"=>"\u8667",
"\u{2F9B6}"=>"\u8669",
"\u{2F9B7}"=>"\u86A9",
"\u{2F9B8}"=>"\u8688",
"\u{2F9B9}"=>"\u870E",
"\u{2F9BA}"=>"\u86E2",
"\u{2F9BB}"=>"\u8779",
"\u{2F9BC}"=>"\u8728",
"\u{2F9BD}"=>"\u876B",
"\u{2F9BE}"=>"\u8786",
"\u{2F9BF}"=>"\u45D7",
"\u{2F9C0}"=>"\u87E1",
"\u{2F9C1}"=>"\u8801",
"\u{2F9C2}"=>"\u45F9",
"\u{2F9C3}"=>"\u8860",
"\u{2F9C4}"=>"\u8863",
"\u{2F9C5}"=>"\u{27667}",
"\u{2F9C6}"=>"\u88D7",
"\u{2F9C7}"=>"\u88DE",
"\u{2F9C8}"=>"\u4635",
"\u{2F9C9}"=>"\u88FA",
"\u{2F9CA}"=>"\u34BB",
"\u{2F9CB}"=>"\u{278AE}",
"\u{2F9CC}"=>"\u{27966}",
"\u{2F9CD}"=>"\u46BE",
"\u{2F9CE}"=>"\u46C7",
"\u{2F9CF}"=>"\u8AA0",
"\u{2F9D0}"=>"\u8AED",
"\u{2F9D1}"=>"\u8B8A",
"\u{2F9D2}"=>"\u8C55",
"\u{2F9D3}"=>"\u{27CA8}",
"\u{2F9D4}"=>"\u8CAB",
"\u{2F9D5}"=>"\u8CC1",
"\u{2F9D6}"=>"\u8D1B",
"\u{2F9D7}"=>"\u8D77",
"\u{2F9D8}"=>"\u{27F2F}",
"\u{2F9D9}"=>"\u{20804}",
"\u{2F9DA}"=>"\u8DCB",
"\u{2F9DB}"=>"\u8DBC",
"\u{2F9DC}"=>"\u8DF0",
"\u{2F9DD}"=>"\u{208DE}",
"\u{2F9DE}"=>"\u8ED4",
"\u{2F9DF}"=>"\u8F38",
"\u{2F9E0}"=>"\u{285D2}",
"\u{2F9E1}"=>"\u{285ED}",
"\u{2F9E2}"=>"\u9094",
"\u{2F9E3}"=>"\u90F1",
"\u{2F9E4}"=>"\u9111",
"\u{2F9E5}"=>"\u{2872E}",
"\u{2F9E6}"=>"\u911B",
"\u{2F9E7}"=>"\u9238",
"\u{2F9E8}"=>"\u92D7",
"\u{2F9E9}"=>"\u92D8",
"\u{2F9EA}"=>"\u927C",
"\u{2F9EB}"=>"\u93F9",
"\u{2F9EC}"=>"\u9415",
"\u{2F9ED}"=>"\u{28BFA}",
"\u{2F9EE}"=>"\u958B",
"\u{2F9EF}"=>"\u4995",
"\u{2F9F0}"=>"\u95B7",
"\u{2F9F1}"=>"\u{28D77}",
"\u{2F9F2}"=>"\u49E6",
"\u{2F9F3}"=>"\u96C3",
"\u{2F9F4}"=>"\u5DB2",
"\u{2F9F5}"=>"\u9723",
"\u{2F9F6}"=>"\u{29145}",
"\u{2F9F7}"=>"\u{2921A}",
"\u{2F9F8}"=>"\u4A6E",
"\u{2F9F9}"=>"\u4A76",
"\u{2F9FA}"=>"\u97E0",
"\u{2F9FB}"=>"\u{2940A}",
"\u{2F9FC}"=>"\u4AB2",
"\u{2F9FD}"=>"\u{29496}",
"\u{2F9FE}"=>"\u980B",
"\u{2F9FF}"=>"\u980B",
"\u{2FA00}"=>"\u9829",
"\u{2FA01}"=>"\u{295B6}",
"\u{2FA02}"=>"\u98E2",
"\u{2FA03}"=>"\u4B33",
"\u{2FA04}"=>"\u9929",
"\u{2FA05}"=>"\u99A7",
"\u{2FA06}"=>"\u99C2",
"\u{2FA07}"=>"\u99FE",
"\u{2FA08}"=>"\u4BCE",
"\u{2FA09}"=>"\u{29B30}",
"\u{2FA0A}"=>"\u9B12",
"\u{2FA0B}"=>"\u9C40",
"\u{2FA0C}"=>"\u9CFD",
"\u{2FA0D}"=>"\u4CCE",
"\u{2FA0E}"=>"\u4CED",
"\u{2FA0F}"=>"\u9D67",
"\u{2FA10}"=>"\u{2A0CE}",
"\u{2FA11}"=>"\u4CF8",
"\u{2FA12}"=>"\u{2A105}",
"\u{2FA13}"=>"\u{2A20E}",
"\u{2FA14}"=>"\u{2A291}",
"\u{2FA15}"=>"\u9EBB",
"\u{2FA16}"=>"\u4D56",
"\u{2FA17}"=>"\u9EF9",
"\u{2FA18}"=>"\u9EFE",
"\u{2FA19}"=>"\u9F05",
"\u{2FA1A}"=>"\u9F0F",
"\u{2FA1B}"=>"\u9F16",
"\u{2FA1C}"=>"\u9F3B",
"\u{2FA1D}"=>"\u{2A600}",
}.freeze
KOMPATIBLE_TABLE = {
"\u00A0"=>" ",
"\u00A8"=>" \u0308",
"\u00AA"=>"a",
"\u00AF"=>" \u0304",
"\u00B2"=>"2",
"\u00B3"=>"3",
"\u00B4"=>" \u0301",
"\u00B5"=>"\u03BC",
"\u00B8"=>" \u0327",
"\u00B9"=>"1",
"\u00BA"=>"o",
"\u00BC"=>"1\u20444",
"\u00BD"=>"1\u20442",
"\u00BE"=>"3\u20444",
"\u0132"=>"IJ",
"\u0133"=>"ij",
"\u013F"=>"L\u00B7",
"\u0140"=>"l\u00B7",
"\u0149"=>"\u02BCn",
"\u017F"=>"s",
"\u01C4"=>"D\u017D",
"\u01C5"=>"D\u017E",
"\u01C6"=>"d\u017E",
"\u01C7"=>"LJ",
"\u01C8"=>"Lj",
"\u01C9"=>"lj",
"\u01CA"=>"NJ",
"\u01CB"=>"Nj",
"\u01CC"=>"nj",
"\u01F1"=>"DZ",
"\u01F2"=>"Dz",
"\u01F3"=>"dz",
"\u02B0"=>"h",
"\u02B1"=>"\u0266",
"\u02B2"=>"j",
"\u02B3"=>"r",
"\u02B4"=>"\u0279",
"\u02B5"=>"\u027B",
"\u02B6"=>"\u0281",
"\u02B7"=>"w",
"\u02B8"=>"y",
"\u02D8"=>" \u0306",
"\u02D9"=>" \u0307",
"\u02DA"=>" \u030A",
"\u02DB"=>" \u0328",
"\u02DC"=>" \u0303",
"\u02DD"=>" \u030B",
"\u02E0"=>"\u0263",
"\u02E1"=>"l",
"\u02E2"=>"s",
"\u02E3"=>"x",
"\u02E4"=>"\u0295",
"\u037A"=>" \u0345",
"\u0384"=>" \u0301",
"\u03D0"=>"\u03B2",
"\u03D1"=>"\u03B8",
"\u03D2"=>"\u03A5",
"\u03D5"=>"\u03C6",
"\u03D6"=>"\u03C0",
"\u03F0"=>"\u03BA",
"\u03F1"=>"\u03C1",
"\u03F2"=>"\u03C2",
"\u03F4"=>"\u0398",
"\u03F5"=>"\u03B5",
"\u03F9"=>"\u03A3",
"\u0587"=>"\u0565\u0582",
"\u0675"=>"\u0627\u0674",
"\u0676"=>"\u0648\u0674",
"\u0677"=>"\u06C7\u0674",
"\u0678"=>"\u064A\u0674",
"\u0E33"=>"\u0E4D\u0E32",
"\u0EB3"=>"\u0ECD\u0EB2",
"\u0EDC"=>"\u0EAB\u0E99",
"\u0EDD"=>"\u0EAB\u0EA1",
"\u0F0C"=>"\u0F0B",
"\u0F77"=>"\u0FB2\u0F81",
"\u0F79"=>"\u0FB3\u0F81",
"\u10FC"=>"\u10DC",
"\u1D2C"=>"A",
"\u1D2D"=>"\u00C6",
"\u1D2E"=>"B",
"\u1D30"=>"D",
"\u1D31"=>"E",
"\u1D32"=>"\u018E",
"\u1D33"=>"G",
"\u1D34"=>"H",
"\u1D35"=>"I",
"\u1D36"=>"J",
"\u1D37"=>"K",
"\u1D38"=>"L",
"\u1D39"=>"M",
"\u1D3A"=>"N",
"\u1D3C"=>"O",
"\u1D3D"=>"\u0222",
"\u1D3E"=>"P",
"\u1D3F"=>"R",
"\u1D40"=>"T",
"\u1D41"=>"U",
"\u1D42"=>"W",
"\u1D43"=>"a",
"\u1D44"=>"\u0250",
"\u1D45"=>"\u0251",
"\u1D46"=>"\u1D02",
"\u1D47"=>"b",
"\u1D48"=>"d",
"\u1D49"=>"e",
"\u1D4A"=>"\u0259",
"\u1D4B"=>"\u025B",
"\u1D4C"=>"\u025C",
"\u1D4D"=>"g",
"\u1D4F"=>"k",
"\u1D50"=>"m",
"\u1D51"=>"\u014B",
"\u1D52"=>"o",
"\u1D53"=>"\u0254",
"\u1D54"=>"\u1D16",
"\u1D55"=>"\u1D17",
"\u1D56"=>"p",
"\u1D57"=>"t",
"\u1D58"=>"u",
"\u1D59"=>"\u1D1D",
"\u1D5A"=>"\u026F",
"\u1D5B"=>"v",
"\u1D5C"=>"\u1D25",
"\u1D5D"=>"\u03B2",
"\u1D5E"=>"\u03B3",
"\u1D5F"=>"\u03B4",
"\u1D60"=>"\u03C6",
"\u1D61"=>"\u03C7",
"\u1D62"=>"i",
"\u1D63"=>"r",
"\u1D64"=>"u",
"\u1D65"=>"v",
"\u1D66"=>"\u03B2",
"\u1D67"=>"\u03B3",
"\u1D68"=>"\u03C1",
"\u1D69"=>"\u03C6",
"\u1D6A"=>"\u03C7",
"\u1D78"=>"\u043D",
"\u1D9B"=>"\u0252",
"\u1D9C"=>"c",
"\u1D9D"=>"\u0255",
"\u1D9E"=>"\u00F0",
"\u1D9F"=>"\u025C",
"\u1DA0"=>"f",
"\u1DA1"=>"\u025F",
"\u1DA2"=>"\u0261",
"\u1DA3"=>"\u0265",
"\u1DA4"=>"\u0268",
"\u1DA5"=>"\u0269",
"\u1DA6"=>"\u026A",
"\u1DA7"=>"\u1D7B",
"\u1DA8"=>"\u029D",
"\u1DA9"=>"\u026D",
"\u1DAA"=>"\u1D85",
"\u1DAB"=>"\u029F",
"\u1DAC"=>"\u0271",
"\u1DAD"=>"\u0270",
"\u1DAE"=>"\u0272",
"\u1DAF"=>"\u0273",
"\u1DB0"=>"\u0274",
"\u1DB1"=>"\u0275",
"\u1DB2"=>"\u0278",
"\u1DB3"=>"\u0282",
"\u1DB4"=>"\u0283",
"\u1DB5"=>"\u01AB",
"\u1DB6"=>"\u0289",
"\u1DB7"=>"\u028A",
"\u1DB8"=>"\u1D1C",
"\u1DB9"=>"\u028B",
"\u1DBA"=>"\u028C",
"\u1DBB"=>"z",
"\u1DBC"=>"\u0290",
"\u1DBD"=>"\u0291",
"\u1DBE"=>"\u0292",
"\u1DBF"=>"\u03B8",
"\u1E9A"=>"a\u02BE",
"\u1FBD"=>" \u0313",
"\u1FBF"=>" \u0313",
"\u1FC0"=>" \u0342",
"\u1FFE"=>" \u0314",
"\u2002"=>" ",
"\u2003"=>" ",
"\u2004"=>" ",
"\u2005"=>" ",
"\u2006"=>" ",
"\u2007"=>" ",
"\u2008"=>" ",
"\u2009"=>" ",
"\u200A"=>" ",
"\u2011"=>"\u2010",
"\u2017"=>" \u0333",
"\u2024"=>".",
"\u2025"=>"..",
"\u2026"=>"...",
"\u202F"=>" ",
"\u2033"=>"\u2032\u2032",
"\u2034"=>"\u2032\u2032\u2032",
"\u2036"=>"\u2035\u2035",
"\u2037"=>"\u2035\u2035\u2035",
"\u203C"=>"!!",
"\u203E"=>" \u0305",
"\u2047"=>"??",
"\u2048"=>"?!",
"\u2049"=>"!?",
"\u2057"=>"\u2032\u2032\u2032\u2032",
"\u205F"=>" ",
"\u2070"=>"0",
"\u2071"=>"i",
"\u2074"=>"4",
"\u2075"=>"5",
"\u2076"=>"6",
"\u2077"=>"7",
"\u2078"=>"8",
"\u2079"=>"9",
"\u207A"=>"+",
"\u207B"=>"\u2212",
"\u207C"=>"=",
"\u207D"=>"(",
"\u207E"=>")",
"\u207F"=>"n",
"\u2080"=>"0",
"\u2081"=>"1",
"\u2082"=>"2",
"\u2083"=>"3",
"\u2084"=>"4",
"\u2085"=>"5",
"\u2086"=>"6",
"\u2087"=>"7",
"\u2088"=>"8",
"\u2089"=>"9",
"\u208A"=>"+",
"\u208B"=>"\u2212",
"\u208C"=>"=",
"\u208D"=>"(",
"\u208E"=>")",
"\u2090"=>"a",
"\u2091"=>"e",
"\u2092"=>"o",
"\u2093"=>"x",
"\u2094"=>"\u0259",
"\u2095"=>"h",
"\u2096"=>"k",
"\u2097"=>"l",
"\u2098"=>"m",
"\u2099"=>"n",
"\u209A"=>"p",
"\u209B"=>"s",
"\u209C"=>"t",
"\u20A8"=>"Rs",
"\u2100"=>"a/c",
"\u2101"=>"a/s",
"\u2102"=>"C",
"\u2103"=>"\u00B0C",
"\u2105"=>"c/o",
"\u2106"=>"c/u",
"\u2107"=>"\u0190",
"\u2109"=>"\u00B0F",
"\u210A"=>"g",
"\u210B"=>"H",
"\u210C"=>"H",
"\u210D"=>"H",
"\u210E"=>"h",
"\u210F"=>"\u0127",
"\u2110"=>"I",
"\u2111"=>"I",
"\u2112"=>"L",
"\u2113"=>"l",
"\u2115"=>"N",
"\u2116"=>"No",
"\u2119"=>"P",
"\u211A"=>"Q",
"\u211B"=>"R",
"\u211C"=>"R",
"\u211D"=>"R",
"\u2120"=>"SM",
"\u2121"=>"TEL",
"\u2122"=>"TM",
"\u2124"=>"Z",
"\u2128"=>"Z",
"\u212C"=>"B",
"\u212D"=>"C",
"\u212F"=>"e",
"\u2130"=>"E",
"\u2131"=>"F",
"\u2133"=>"M",
"\u2134"=>"o",
"\u2135"=>"\u05D0",
"\u2136"=>"\u05D1",
"\u2137"=>"\u05D2",
"\u2138"=>"\u05D3",
"\u2139"=>"i",
"\u213B"=>"FAX",
"\u213C"=>"\u03C0",
"\u213D"=>"\u03B3",
"\u213E"=>"\u0393",
"\u213F"=>"\u03A0",
"\u2140"=>"\u2211",
"\u2145"=>"D",
"\u2146"=>"d",
"\u2147"=>"e",
"\u2148"=>"i",
"\u2149"=>"j",
"\u2150"=>"1\u20447",
"\u2151"=>"1\u20449",
"\u2152"=>"1\u204410",
"\u2153"=>"1\u20443",
"\u2154"=>"2\u20443",
"\u2155"=>"1\u20445",
"\u2156"=>"2\u20445",
"\u2157"=>"3\u20445",
"\u2158"=>"4\u20445",
"\u2159"=>"1\u20446",
"\u215A"=>"5\u20446",
"\u215B"=>"1\u20448",
"\u215C"=>"3\u20448",
"\u215D"=>"5\u20448",
"\u215E"=>"7\u20448",
"\u215F"=>"1\u2044",
"\u2160"=>"I",
"\u2161"=>"II",
"\u2162"=>"III",
"\u2163"=>"IV",
"\u2164"=>"V",
"\u2165"=>"VI",
"\u2166"=>"VII",
"\u2167"=>"VIII",
"\u2168"=>"IX",
"\u2169"=>"X",
"\u216A"=>"XI",
"\u216B"=>"XII",
"\u216C"=>"L",
"\u216D"=>"C",
"\u216E"=>"D",
"\u216F"=>"M",
"\u2170"=>"i",
"\u2171"=>"ii",
"\u2172"=>"iii",
"\u2173"=>"iv",
"\u2174"=>"v",
"\u2175"=>"vi",
"\u2176"=>"vii",
"\u2177"=>"viii",
"\u2178"=>"ix",
"\u2179"=>"x",
"\u217A"=>"xi",
"\u217B"=>"xii",
"\u217C"=>"l",
"\u217D"=>"c",
"\u217E"=>"d",
"\u217F"=>"m",
"\u2189"=>"0\u20443",
"\u222C"=>"\u222B\u222B",
"\u222D"=>"\u222B\u222B\u222B",
"\u222F"=>"\u222E\u222E",
"\u2230"=>"\u222E\u222E\u222E",
"\u2460"=>"1",
"\u2461"=>"2",
"\u2462"=>"3",
"\u2463"=>"4",
"\u2464"=>"5",
"\u2465"=>"6",
"\u2466"=>"7",
"\u2467"=>"8",
"\u2468"=>"9",
"\u2469"=>"10",
"\u246A"=>"11",
"\u246B"=>"12",
"\u246C"=>"13",
"\u246D"=>"14",
"\u246E"=>"15",
"\u246F"=>"16",
"\u2470"=>"17",
"\u2471"=>"18",
"\u2472"=>"19",
"\u2473"=>"20",
"\u2474"=>"(1)",
"\u2475"=>"(2)",
"\u2476"=>"(3)",
"\u2477"=>"(4)",
"\u2478"=>"(5)",
"\u2479"=>"(6)",
"\u247A"=>"(7)",
"\u247B"=>"(8)",
"\u247C"=>"(9)",
"\u247D"=>"(10)",
"\u247E"=>"(11)",
"\u247F"=>"(12)",
"\u2480"=>"(13)",
"\u2481"=>"(14)",
"\u2482"=>"(15)",
"\u2483"=>"(16)",
"\u2484"=>"(17)",
"\u2485"=>"(18)",
"\u2486"=>"(19)",
"\u2487"=>"(20)",
"\u2488"=>"1.",
"\u2489"=>"2.",
"\u248A"=>"3.",
"\u248B"=>"4.",
"\u248C"=>"5.",
"\u248D"=>"6.",
"\u248E"=>"7.",
"\u248F"=>"8.",
"\u2490"=>"9.",
"\u2491"=>"10.",
"\u2492"=>"11.",
"\u2493"=>"12.",
"\u2494"=>"13.",
"\u2495"=>"14.",
"\u2496"=>"15.",
"\u2497"=>"16.",
"\u2498"=>"17.",
"\u2499"=>"18.",
"\u249A"=>"19.",
"\u249B"=>"20.",
"\u249C"=>"(a)",
"\u249D"=>"(b)",
"\u249E"=>"(c)",
"\u249F"=>"(d)",
"\u24A0"=>"(e)",
"\u24A1"=>"(f)",
"\u24A2"=>"(g)",
"\u24A3"=>"(h)",
"\u24A4"=>"(i)",
"\u24A5"=>"(j)",
"\u24A6"=>"(k)",
"\u24A7"=>"(l)",
"\u24A8"=>"(m)",
"\u24A9"=>"(n)",
"\u24AA"=>"(o)",
"\u24AB"=>"(p)",
"\u24AC"=>"(q)",
"\u24AD"=>"(r)",
"\u24AE"=>"(s)",
"\u24AF"=>"(t)",
"\u24B0"=>"(u)",
"\u24B1"=>"(v)",
"\u24B2"=>"(w)",
"\u24B3"=>"(x)",
"\u24B4"=>"(y)",
"\u24B5"=>"(z)",
"\u24B6"=>"A",
"\u24B7"=>"B",
"\u24B8"=>"C",
"\u24B9"=>"D",
"\u24BA"=>"E",
"\u24BB"=>"F",
"\u24BC"=>"G",
"\u24BD"=>"H",
"\u24BE"=>"I",
"\u24BF"=>"J",
"\u24C0"=>"K",
"\u24C1"=>"L",
"\u24C2"=>"M",
"\u24C3"=>"N",
"\u24C4"=>"O",
"\u24C5"=>"P",
"\u24C6"=>"Q",
"\u24C7"=>"R",
"\u24C8"=>"S",
"\u24C9"=>"T",
"\u24CA"=>"U",
"\u24CB"=>"V",
"\u24CC"=>"W",
"\u24CD"=>"X",
"\u24CE"=>"Y",
"\u24CF"=>"Z",
"\u24D0"=>"a",
"\u24D1"=>"b",
"\u24D2"=>"c",
"\u24D3"=>"d",
"\u24D4"=>"e",
"\u24D5"=>"f",
"\u24D6"=>"g",
"\u24D7"=>"h",
"\u24D8"=>"i",
"\u24D9"=>"j",
"\u24DA"=>"k",
"\u24DB"=>"l",
"\u24DC"=>"m",
"\u24DD"=>"n",
"\u24DE"=>"o",
"\u24DF"=>"p",
"\u24E0"=>"q",
"\u24E1"=>"r",
"\u24E2"=>"s",
"\u24E3"=>"t",
"\u24E4"=>"u",
"\u24E5"=>"v",
"\u24E6"=>"w",
"\u24E7"=>"x",
"\u24E8"=>"y",
"\u24E9"=>"z",
"\u24EA"=>"0",
"\u2A0C"=>"\u222B\u222B\u222B\u222B",
"\u2A74"=>"::=",
"\u2A75"=>"==",
"\u2A76"=>"===",
"\u2C7C"=>"j",
"\u2C7D"=>"V",
"\u2D6F"=>"\u2D61",
"\u2E9F"=>"\u6BCD",
"\u2EF3"=>"\u9F9F",
"\u2F00"=>"\u4E00",
"\u2F01"=>"\u4E28",
"\u2F02"=>"\u4E36",
"\u2F03"=>"\u4E3F",
"\u2F04"=>"\u4E59",
"\u2F05"=>"\u4E85",
"\u2F06"=>"\u4E8C",
"\u2F07"=>"\u4EA0",
"\u2F08"=>"\u4EBA",
"\u2F09"=>"\u513F",
"\u2F0A"=>"\u5165",
"\u2F0B"=>"\u516B",
"\u2F0C"=>"\u5182",
"\u2F0D"=>"\u5196",
"\u2F0E"=>"\u51AB",
"\u2F0F"=>"\u51E0",
"\u2F10"=>"\u51F5",
"\u2F11"=>"\u5200",
"\u2F12"=>"\u529B",
"\u2F13"=>"\u52F9",
"\u2F14"=>"\u5315",
"\u2F15"=>"\u531A",
"\u2F16"=>"\u5338",
"\u2F17"=>"\u5341",
"\u2F18"=>"\u535C",
"\u2F19"=>"\u5369",
"\u2F1A"=>"\u5382",
"\u2F1B"=>"\u53B6",
"\u2F1C"=>"\u53C8",
"\u2F1D"=>"\u53E3",
"\u2F1E"=>"\u56D7",
"\u2F1F"=>"\u571F",
"\u2F20"=>"\u58EB",
"\u2F21"=>"\u5902",
"\u2F22"=>"\u590A",
"\u2F23"=>"\u5915",
"\u2F24"=>"\u5927",
"\u2F25"=>"\u5973",
"\u2F26"=>"\u5B50",
"\u2F27"=>"\u5B80",
"\u2F28"=>"\u5BF8",
"\u2F29"=>"\u5C0F",
"\u2F2A"=>"\u5C22",
"\u2F2B"=>"\u5C38",
"\u2F2C"=>"\u5C6E",
"\u2F2D"=>"\u5C71",
"\u2F2E"=>"\u5DDB",
"\u2F2F"=>"\u5DE5",
"\u2F30"=>"\u5DF1",
"\u2F31"=>"\u5DFE",
"\u2F32"=>"\u5E72",
"\u2F33"=>"\u5E7A",
"\u2F34"=>"\u5E7F",
"\u2F35"=>"\u5EF4",
"\u2F36"=>"\u5EFE",
"\u2F37"=>"\u5F0B",
"\u2F38"=>"\u5F13",
"\u2F39"=>"\u5F50",
"\u2F3A"=>"\u5F61",
"\u2F3B"=>"\u5F73",
"\u2F3C"=>"\u5FC3",
"\u2F3D"=>"\u6208",
"\u2F3E"=>"\u6236",
"\u2F3F"=>"\u624B",
"\u2F40"=>"\u652F",
"\u2F41"=>"\u6534",
"\u2F42"=>"\u6587",
"\u2F43"=>"\u6597",
"\u2F44"=>"\u65A4",
"\u2F45"=>"\u65B9",
"\u2F46"=>"\u65E0",
"\u2F47"=>"\u65E5",
"\u2F48"=>"\u66F0",
"\u2F49"=>"\u6708",
"\u2F4A"=>"\u6728",
"\u2F4B"=>"\u6B20",
"\u2F4C"=>"\u6B62",
"\u2F4D"=>"\u6B79",
"\u2F4E"=>"\u6BB3",
"\u2F4F"=>"\u6BCB",
"\u2F50"=>"\u6BD4",
"\u2F51"=>"\u6BDB",
"\u2F52"=>"\u6C0F",
"\u2F53"=>"\u6C14",
"\u2F54"=>"\u6C34",
"\u2F55"=>"\u706B",
"\u2F56"=>"\u722A",
"\u2F57"=>"\u7236",
"\u2F58"=>"\u723B",
"\u2F59"=>"\u723F",
"\u2F5A"=>"\u7247",
"\u2F5B"=>"\u7259",
"\u2F5C"=>"\u725B",
"\u2F5D"=>"\u72AC",
"\u2F5E"=>"\u7384",
"\u2F5F"=>"\u7389",
"\u2F60"=>"\u74DC",
"\u2F61"=>"\u74E6",
"\u2F62"=>"\u7518",
"\u2F63"=>"\u751F",
"\u2F64"=>"\u7528",
"\u2F65"=>"\u7530",
"\u2F66"=>"\u758B",
"\u2F67"=>"\u7592",
"\u2F68"=>"\u7676",
"\u2F69"=>"\u767D",
"\u2F6A"=>"\u76AE",
"\u2F6B"=>"\u76BF",
"\u2F6C"=>"\u76EE",
"\u2F6D"=>"\u77DB",
"\u2F6E"=>"\u77E2",
"\u2F6F"=>"\u77F3",
"\u2F70"=>"\u793A",
"\u2F71"=>"\u79B8",
"\u2F72"=>"\u79BE",
"\u2F73"=>"\u7A74",
"\u2F74"=>"\u7ACB",
"\u2F75"=>"\u7AF9",
"\u2F76"=>"\u7C73",
"\u2F77"=>"\u7CF8",
"\u2F78"=>"\u7F36",
"\u2F79"=>"\u7F51",
"\u2F7A"=>"\u7F8A",
"\u2F7B"=>"\u7FBD",
"\u2F7C"=>"\u8001",
"\u2F7D"=>"\u800C",
"\u2F7E"=>"\u8012",
"\u2F7F"=>"\u8033",
"\u2F80"=>"\u807F",
"\u2F81"=>"\u8089",
"\u2F82"=>"\u81E3",
"\u2F83"=>"\u81EA",
"\u2F84"=>"\u81F3",
"\u2F85"=>"\u81FC",
"\u2F86"=>"\u820C",
"\u2F87"=>"\u821B",
"\u2F88"=>"\u821F",
"\u2F89"=>"\u826E",
"\u2F8A"=>"\u8272",
"\u2F8B"=>"\u8278",
"\u2F8C"=>"\u864D",
"\u2F8D"=>"\u866B",
"\u2F8E"=>"\u8840",
"\u2F8F"=>"\u884C",
"\u2F90"=>"\u8863",
"\u2F91"=>"\u897E",
"\u2F92"=>"\u898B",
"\u2F93"=>"\u89D2",
"\u2F94"=>"\u8A00",
"\u2F95"=>"\u8C37",
"\u2F96"=>"\u8C46",
"\u2F97"=>"\u8C55",
"\u2F98"=>"\u8C78",
"\u2F99"=>"\u8C9D",
"\u2F9A"=>"\u8D64",
"\u2F9B"=>"\u8D70",
"\u2F9C"=>"\u8DB3",
"\u2F9D"=>"\u8EAB",
"\u2F9E"=>"\u8ECA",
"\u2F9F"=>"\u8F9B",
"\u2FA0"=>"\u8FB0",
"\u2FA1"=>"\u8FB5",
"\u2FA2"=>"\u9091",
"\u2FA3"=>"\u9149",
"\u2FA4"=>"\u91C6",
"\u2FA5"=>"\u91CC",
"\u2FA6"=>"\u91D1",
"\u2FA7"=>"\u9577",
"\u2FA8"=>"\u9580",
"\u2FA9"=>"\u961C",
"\u2FAA"=>"\u96B6",
"\u2FAB"=>"\u96B9",
"\u2FAC"=>"\u96E8",
"\u2FAD"=>"\u9751",
"\u2FAE"=>"\u975E",
"\u2FAF"=>"\u9762",
"\u2FB0"=>"\u9769",
"\u2FB1"=>"\u97CB",
"\u2FB2"=>"\u97ED",
"\u2FB3"=>"\u97F3",
"\u2FB4"=>"\u9801",
"\u2FB5"=>"\u98A8",
"\u2FB6"=>"\u98DB",
"\u2FB7"=>"\u98DF",
"\u2FB8"=>"\u9996",
"\u2FB9"=>"\u9999",
"\u2FBA"=>"\u99AC",
"\u2FBB"=>"\u9AA8",
"\u2FBC"=>"\u9AD8",
"\u2FBD"=>"\u9ADF",
"\u2FBE"=>"\u9B25",
"\u2FBF"=>"\u9B2F",
"\u2FC0"=>"\u9B32",
"\u2FC1"=>"\u9B3C",
"\u2FC2"=>"\u9B5A",
"\u2FC3"=>"\u9CE5",
"\u2FC4"=>"\u9E75",
"\u2FC5"=>"\u9E7F",
"\u2FC6"=>"\u9EA5",
"\u2FC7"=>"\u9EBB",
"\u2FC8"=>"\u9EC3",
"\u2FC9"=>"\u9ECD",
"\u2FCA"=>"\u9ED1",
"\u2FCB"=>"\u9EF9",
"\u2FCC"=>"\u9EFD",
"\u2FCD"=>"\u9F0E",
"\u2FCE"=>"\u9F13",
"\u2FCF"=>"\u9F20",
"\u2FD0"=>"\u9F3B",
"\u2FD1"=>"\u9F4A",
"\u2FD2"=>"\u9F52",
"\u2FD3"=>"\u9F8D",
"\u2FD4"=>"\u9F9C",
"\u2FD5"=>"\u9FA0",
"\u3000"=>" ",
"\u3036"=>"\u3012",
"\u3038"=>"\u5341",
"\u3039"=>"\u5344",
"\u303A"=>"\u5345",
"\u309B"=>" \u3099",
"\u309C"=>" \u309A",
"\u309F"=>"\u3088\u308A",
"\u30FF"=>"\u30B3\u30C8",
"\u3131"=>"\u1100",
"\u3132"=>"\u1101",
"\u3133"=>"\u11AA",
"\u3134"=>"\u1102",
"\u3135"=>"\u11AC",
"\u3136"=>"\u11AD",
"\u3137"=>"\u1103",
"\u3138"=>"\u1104",
"\u3139"=>"\u1105",
"\u313A"=>"\u11B0",
"\u313B"=>"\u11B1",
"\u313C"=>"\u11B2",
"\u313D"=>"\u11B3",
"\u313E"=>"\u11B4",
"\u313F"=>"\u11B5",
"\u3140"=>"\u111A",
"\u3141"=>"\u1106",
"\u3142"=>"\u1107",
"\u3143"=>"\u1108",
"\u3144"=>"\u1121",
"\u3145"=>"\u1109",
"\u3146"=>"\u110A",
"\u3147"=>"\u110B",
"\u3148"=>"\u110C",
"\u3149"=>"\u110D",
"\u314A"=>"\u110E",
"\u314B"=>"\u110F",
"\u314C"=>"\u1110",
"\u314D"=>"\u1111",
"\u314E"=>"\u1112",
"\u314F"=>"\u1161",
"\u3150"=>"\u1162",
"\u3151"=>"\u1163",
"\u3152"=>"\u1164",
"\u3153"=>"\u1165",
"\u3154"=>"\u1166",
"\u3155"=>"\u1167",
"\u3156"=>"\u1168",
"\u3157"=>"\u1169",
"\u3158"=>"\u116A",
"\u3159"=>"\u116B",
"\u315A"=>"\u116C",
"\u315B"=>"\u116D",
"\u315C"=>"\u116E",
"\u315D"=>"\u116F",
"\u315E"=>"\u1170",
"\u315F"=>"\u1171",
"\u3160"=>"\u1172",
"\u3161"=>"\u1173",
"\u3162"=>"\u1174",
"\u3163"=>"\u1175",
"\u3164"=>"\u1160",
"\u3165"=>"\u1114",
"\u3166"=>"\u1115",
"\u3167"=>"\u11C7",
"\u3168"=>"\u11C8",
"\u3169"=>"\u11CC",
"\u316A"=>"\u11CE",
"\u316B"=>"\u11D3",
"\u316C"=>"\u11D7",
"\u316D"=>"\u11D9",
"\u316E"=>"\u111C",
"\u316F"=>"\u11DD",
"\u3170"=>"\u11DF",
"\u3171"=>"\u111D",
"\u3172"=>"\u111E",
"\u3173"=>"\u1120",
"\u3174"=>"\u1122",
"\u3175"=>"\u1123",
"\u3176"=>"\u1127",
"\u3177"=>"\u1129",
"\u3178"=>"\u112B",
"\u3179"=>"\u112C",
"\u317A"=>"\u112D",
"\u317B"=>"\u112E",
"\u317C"=>"\u112F",
"\u317D"=>"\u1132",
"\u317E"=>"\u1136",
"\u317F"=>"\u1140",
"\u3180"=>"\u1147",
"\u3181"=>"\u114C",
"\u3182"=>"\u11F1",
"\u3183"=>"\u11F2",
"\u3184"=>"\u1157",
"\u3185"=>"\u1158",
"\u3186"=>"\u1159",
"\u3187"=>"\u1184",
"\u3188"=>"\u1185",
"\u3189"=>"\u1188",
"\u318A"=>"\u1191",
"\u318B"=>"\u1192",
"\u318C"=>"\u1194",
"\u318D"=>"\u119E",
"\u318E"=>"\u11A1",
"\u3192"=>"\u4E00",
"\u3193"=>"\u4E8C",
"\u3194"=>"\u4E09",
"\u3195"=>"\u56DB",
"\u3196"=>"\u4E0A",
"\u3197"=>"\u4E2D",
"\u3198"=>"\u4E0B",
"\u3199"=>"\u7532",
"\u319A"=>"\u4E59",
"\u319B"=>"\u4E19",
"\u319C"=>"\u4E01",
"\u319D"=>"\u5929",
"\u319E"=>"\u5730",
"\u319F"=>"\u4EBA",
"\u3200"=>"(\u1100)",
"\u3201"=>"(\u1102)",
"\u3202"=>"(\u1103)",
"\u3203"=>"(\u1105)",
"\u3204"=>"(\u1106)",
"\u3205"=>"(\u1107)",
"\u3206"=>"(\u1109)",
"\u3207"=>"(\u110B)",
"\u3208"=>"(\u110C)",
"\u3209"=>"(\u110E)",
"\u320A"=>"(\u110F)",
"\u320B"=>"(\u1110)",
"\u320C"=>"(\u1111)",
"\u320D"=>"(\u1112)",
"\u320E"=>"(\u1100\u1161)",
"\u320F"=>"(\u1102\u1161)",
"\u3210"=>"(\u1103\u1161)",
"\u3211"=>"(\u1105\u1161)",
"\u3212"=>"(\u1106\u1161)",
"\u3213"=>"(\u1107\u1161)",
"\u3214"=>"(\u1109\u1161)",
"\u3215"=>"(\u110B\u1161)",
"\u3216"=>"(\u110C\u1161)",
"\u3217"=>"(\u110E\u1161)",
"\u3218"=>"(\u110F\u1161)",
"\u3219"=>"(\u1110\u1161)",
"\u321A"=>"(\u1111\u1161)",
"\u321B"=>"(\u1112\u1161)",
"\u321C"=>"(\u110C\u116E)",
"\u321D"=>"(\u110B\u1169\u110C\u1165\u11AB)",
"\u321E"=>"(\u110B\u1169\u1112\u116E)",
"\u3220"=>"(\u4E00)",
"\u3221"=>"(\u4E8C)",
"\u3222"=>"(\u4E09)",
"\u3223"=>"(\u56DB)",
"\u3224"=>"(\u4E94)",
"\u3225"=>"(\u516D)",
"\u3226"=>"(\u4E03)",
"\u3227"=>"(\u516B)",
"\u3228"=>"(\u4E5D)",
"\u3229"=>"(\u5341)",
"\u322A"=>"(\u6708)",
"\u322B"=>"(\u706B)",
"\u322C"=>"(\u6C34)",
"\u322D"=>"(\u6728)",
"\u322E"=>"(\u91D1)",
"\u322F"=>"(\u571F)",
"\u3230"=>"(\u65E5)",
"\u3231"=>"(\u682A)",
"\u3232"=>"(\u6709)",
"\u3233"=>"(\u793E)",
"\u3234"=>"(\u540D)",
"\u3235"=>"(\u7279)",
"\u3236"=>"(\u8CA1)",
"\u3237"=>"(\u795D)",
"\u3238"=>"(\u52B4)",
"\u3239"=>"(\u4EE3)",
"\u323A"=>"(\u547C)",
"\u323B"=>"(\u5B66)",
"\u323C"=>"(\u76E3)",
"\u323D"=>"(\u4F01)",
"\u323E"=>"(\u8CC7)",
"\u323F"=>"(\u5354)",
"\u3240"=>"(\u796D)",
"\u3241"=>"(\u4F11)",
"\u3242"=>"(\u81EA)",
"\u3243"=>"(\u81F3)",
"\u3244"=>"\u554F",
"\u3245"=>"\u5E7C",
"\u3246"=>"\u6587",
"\u3247"=>"\u7B8F",
"\u3250"=>"PTE",
"\u3251"=>"21",
"\u3252"=>"22",
"\u3253"=>"23",
"\u3254"=>"24",
"\u3255"=>"25",
"\u3256"=>"26",
"\u3257"=>"27",
"\u3258"=>"28",
"\u3259"=>"29",
"\u325A"=>"30",
"\u325B"=>"31",
"\u325C"=>"32",
"\u325D"=>"33",
"\u325E"=>"34",
"\u325F"=>"35",
"\u3260"=>"\u1100",
"\u3261"=>"\u1102",
"\u3262"=>"\u1103",
"\u3263"=>"\u1105",
"\u3264"=>"\u1106",
"\u3265"=>"\u1107",
"\u3266"=>"\u1109",
"\u3267"=>"\u110B",
"\u3268"=>"\u110C",
"\u3269"=>"\u110E",
"\u326A"=>"\u110F",
"\u326B"=>"\u1110",
"\u326C"=>"\u1111",
"\u326D"=>"\u1112",
"\u326E"=>"\u1100\u1161",
"\u326F"=>"\u1102\u1161",
"\u3270"=>"\u1103\u1161",
"\u3271"=>"\u1105\u1161",
"\u3272"=>"\u1106\u1161",
"\u3273"=>"\u1107\u1161",
"\u3274"=>"\u1109\u1161",
"\u3275"=>"\u110B\u1161",
"\u3276"=>"\u110C\u1161",
"\u3277"=>"\u110E\u1161",
"\u3278"=>"\u110F\u1161",
"\u3279"=>"\u1110\u1161",
"\u327A"=>"\u1111\u1161",
"\u327B"=>"\u1112\u1161",
"\u327C"=>"\u110E\u1161\u11B7\u1100\u1169",
"\u327D"=>"\u110C\u116E\u110B\u1174",
"\u327E"=>"\u110B\u116E",
"\u3280"=>"\u4E00",
"\u3281"=>"\u4E8C",
"\u3282"=>"\u4E09",
"\u3283"=>"\u56DB",
"\u3284"=>"\u4E94",
"\u3285"=>"\u516D",
"\u3286"=>"\u4E03",
"\u3287"=>"\u516B",
"\u3288"=>"\u4E5D",
"\u3289"=>"\u5341",
"\u328A"=>"\u6708",
"\u328B"=>"\u706B",
"\u328C"=>"\u6C34",
"\u328D"=>"\u6728",
"\u328E"=>"\u91D1",
"\u328F"=>"\u571F",
"\u3290"=>"\u65E5",
"\u3291"=>"\u682A",
"\u3292"=>"\u6709",
"\u3293"=>"\u793E",
"\u3294"=>"\u540D",
"\u3295"=>"\u7279",
"\u3296"=>"\u8CA1",
"\u3297"=>"\u795D",
"\u3298"=>"\u52B4",
"\u3299"=>"\u79D8",
"\u329A"=>"\u7537",
"\u329B"=>"\u5973",
"\u329C"=>"\u9069",
"\u329D"=>"\u512A",
"\u329E"=>"\u5370",
"\u329F"=>"\u6CE8",
"\u32A0"=>"\u9805",
"\u32A1"=>"\u4F11",
"\u32A2"=>"\u5199",
"\u32A3"=>"\u6B63",
"\u32A4"=>"\u4E0A",
"\u32A5"=>"\u4E2D",
"\u32A6"=>"\u4E0B",
"\u32A7"=>"\u5DE6",
"\u32A8"=>"\u53F3",
"\u32A9"=>"\u533B",
"\u32AA"=>"\u5B97",
"\u32AB"=>"\u5B66",
"\u32AC"=>"\u76E3",
"\u32AD"=>"\u4F01",
"\u32AE"=>"\u8CC7",
"\u32AF"=>"\u5354",
"\u32B0"=>"\u591C",
"\u32B1"=>"36",
"\u32B2"=>"37",
"\u32B3"=>"38",
"\u32B4"=>"39",
"\u32B5"=>"40",
"\u32B6"=>"41",
"\u32B7"=>"42",
"\u32B8"=>"43",
"\u32B9"=>"44",
"\u32BA"=>"45",
"\u32BB"=>"46",
"\u32BC"=>"47",
"\u32BD"=>"48",
"\u32BE"=>"49",
"\u32BF"=>"50",
"\u32C0"=>"1\u6708",
"\u32C1"=>"2\u6708",
"\u32C2"=>"3\u6708",
"\u32C3"=>"4\u6708",
"\u32C4"=>"5\u6708",
"\u32C5"=>"6\u6708",
"\u32C6"=>"7\u6708",
"\u32C7"=>"8\u6708",
"\u32C8"=>"9\u6708",
"\u32C9"=>"10\u6708",
"\u32CA"=>"11\u6708",
"\u32CB"=>"12\u6708",
"\u32CC"=>"Hg",
"\u32CD"=>"erg",
"\u32CE"=>"eV",
"\u32CF"=>"LTD",
"\u32D0"=>"\u30A2",
"\u32D1"=>"\u30A4",
"\u32D2"=>"\u30A6",
"\u32D3"=>"\u30A8",
"\u32D4"=>"\u30AA",
"\u32D5"=>"\u30AB",
"\u32D6"=>"\u30AD",
"\u32D7"=>"\u30AF",
"\u32D8"=>"\u30B1",
"\u32D9"=>"\u30B3",
"\u32DA"=>"\u30B5",
"\u32DB"=>"\u30B7",
"\u32DC"=>"\u30B9",
"\u32DD"=>"\u30BB",
"\u32DE"=>"\u30BD",
"\u32DF"=>"\u30BF",
"\u32E0"=>"\u30C1",
"\u32E1"=>"\u30C4",
"\u32E2"=>"\u30C6",
"\u32E3"=>"\u30C8",
"\u32E4"=>"\u30CA",
"\u32E5"=>"\u30CB",
"\u32E6"=>"\u30CC",
"\u32E7"=>"\u30CD",
"\u32E8"=>"\u30CE",
"\u32E9"=>"\u30CF",
"\u32EA"=>"\u30D2",
"\u32EB"=>"\u30D5",
"\u32EC"=>"\u30D8",
"\u32ED"=>"\u30DB",
"\u32EE"=>"\u30DE",
"\u32EF"=>"\u30DF",
"\u32F0"=>"\u30E0",
"\u32F1"=>"\u30E1",
"\u32F2"=>"\u30E2",
"\u32F3"=>"\u30E4",
"\u32F4"=>"\u30E6",
"\u32F5"=>"\u30E8",
"\u32F6"=>"\u30E9",
"\u32F7"=>"\u30EA",
"\u32F8"=>"\u30EB",
"\u32F9"=>"\u30EC",
"\u32FA"=>"\u30ED",
"\u32FB"=>"\u30EF",
"\u32FC"=>"\u30F0",
"\u32FD"=>"\u30F1",
"\u32FE"=>"\u30F2",
"\u32FF"=>"\u4EE4\u548C",
"\u3300"=>"\u30A2\u30D1\u30FC\u30C8",
"\u3301"=>"\u30A2\u30EB\u30D5\u30A1",
"\u3302"=>"\u30A2\u30F3\u30DA\u30A2",
"\u3303"=>"\u30A2\u30FC\u30EB",
"\u3304"=>"\u30A4\u30CB\u30F3\u30B0",
"\u3305"=>"\u30A4\u30F3\u30C1",
"\u3306"=>"\u30A6\u30A9\u30F3",
"\u3307"=>"\u30A8\u30B9\u30AF\u30FC\u30C9",
"\u3308"=>"\u30A8\u30FC\u30AB\u30FC",
"\u3309"=>"\u30AA\u30F3\u30B9",
"\u330A"=>"\u30AA\u30FC\u30E0",
"\u330B"=>"\u30AB\u30A4\u30EA",
"\u330C"=>"\u30AB\u30E9\u30C3\u30C8",
"\u330D"=>"\u30AB\u30ED\u30EA\u30FC",
"\u330E"=>"\u30AC\u30ED\u30F3",
"\u330F"=>"\u30AC\u30F3\u30DE",
"\u3310"=>"\u30AE\u30AC",
"\u3311"=>"\u30AE\u30CB\u30FC",
"\u3312"=>"\u30AD\u30E5\u30EA\u30FC",
"\u3313"=>"\u30AE\u30EB\u30C0\u30FC",
"\u3314"=>"\u30AD\u30ED",
"\u3315"=>"\u30AD\u30ED\u30B0\u30E9\u30E0",
"\u3316"=>"\u30AD\u30ED\u30E1\u30FC\u30C8\u30EB",
"\u3317"=>"\u30AD\u30ED\u30EF\u30C3\u30C8",
"\u3318"=>"\u30B0\u30E9\u30E0",
"\u3319"=>"\u30B0\u30E9\u30E0\u30C8\u30F3",
"\u331A"=>"\u30AF\u30EB\u30BC\u30A4\u30ED",
"\u331B"=>"\u30AF\u30ED\u30FC\u30CD",
"\u331C"=>"\u30B1\u30FC\u30B9",
"\u331D"=>"\u30B3\u30EB\u30CA",
"\u331E"=>"\u30B3\u30FC\u30DD",
"\u331F"=>"\u30B5\u30A4\u30AF\u30EB",
"\u3320"=>"\u30B5\u30F3\u30C1\u30FC\u30E0",
"\u3321"=>"\u30B7\u30EA\u30F3\u30B0",
"\u3322"=>"\u30BB\u30F3\u30C1",
"\u3323"=>"\u30BB\u30F3\u30C8",
"\u3324"=>"\u30C0\u30FC\u30B9",
"\u3325"=>"\u30C7\u30B7",
"\u3326"=>"\u30C9\u30EB",
"\u3327"=>"\u30C8\u30F3",
"\u3328"=>"\u30CA\u30CE",
"\u3329"=>"\u30CE\u30C3\u30C8",
"\u332A"=>"\u30CF\u30A4\u30C4",
"\u332B"=>"\u30D1\u30FC\u30BB\u30F3\u30C8",
"\u332C"=>"\u30D1\u30FC\u30C4",
"\u332D"=>"\u30D0\u30FC\u30EC\u30EB",
"\u332E"=>"\u30D4\u30A2\u30B9\u30C8\u30EB",
"\u332F"=>"\u30D4\u30AF\u30EB",
"\u3330"=>"\u30D4\u30B3",
"\u3331"=>"\u30D3\u30EB",
"\u3332"=>"\u30D5\u30A1\u30E9\u30C3\u30C9",
"\u3333"=>"\u30D5\u30A3\u30FC\u30C8",
"\u3334"=>"\u30D6\u30C3\u30B7\u30A7\u30EB",
"\u3335"=>"\u30D5\u30E9\u30F3",
"\u3336"=>"\u30D8\u30AF\u30BF\u30FC\u30EB",
"\u3337"=>"\u30DA\u30BD",
"\u3338"=>"\u30DA\u30CB\u30D2",
"\u3339"=>"\u30D8\u30EB\u30C4",
"\u333A"=>"\u30DA\u30F3\u30B9",
"\u333B"=>"\u30DA\u30FC\u30B8",
"\u333C"=>"\u30D9\u30FC\u30BF",
"\u333D"=>"\u30DD\u30A4\u30F3\u30C8",
"\u333E"=>"\u30DC\u30EB\u30C8",
"\u333F"=>"\u30DB\u30F3",
"\u3340"=>"\u30DD\u30F3\u30C9",
"\u3341"=>"\u30DB\u30FC\u30EB",
"\u3342"=>"\u30DB\u30FC\u30F3",
"\u3343"=>"\u30DE\u30A4\u30AF\u30ED",
"\u3344"=>"\u30DE\u30A4\u30EB",
"\u3345"=>"\u30DE\u30C3\u30CF",
"\u3346"=>"\u30DE\u30EB\u30AF",
"\u3347"=>"\u30DE\u30F3\u30B7\u30E7\u30F3",
"\u3348"=>"\u30DF\u30AF\u30ED\u30F3",
"\u3349"=>"\u30DF\u30EA",
"\u334A"=>"\u30DF\u30EA\u30D0\u30FC\u30EB",
"\u334B"=>"\u30E1\u30AC",
"\u334C"=>"\u30E1\u30AC\u30C8\u30F3",
"\u334D"=>"\u30E1\u30FC\u30C8\u30EB",
"\u334E"=>"\u30E4\u30FC\u30C9",
"\u334F"=>"\u30E4\u30FC\u30EB",
"\u3350"=>"\u30E6\u30A2\u30F3",
"\u3351"=>"\u30EA\u30C3\u30C8\u30EB",
"\u3352"=>"\u30EA\u30E9",
"\u3353"=>"\u30EB\u30D4\u30FC",
"\u3354"=>"\u30EB\u30FC\u30D6\u30EB",
"\u3355"=>"\u30EC\u30E0",
"\u3356"=>"\u30EC\u30F3\u30C8\u30B2\u30F3",
"\u3357"=>"\u30EF\u30C3\u30C8",
"\u3358"=>"0\u70B9",
"\u3359"=>"1\u70B9",
"\u335A"=>"2\u70B9",
"\u335B"=>"3\u70B9",
"\u335C"=>"4\u70B9",
"\u335D"=>"5\u70B9",
"\u335E"=>"6\u70B9",
"\u335F"=>"7\u70B9",
"\u3360"=>"8\u70B9",
"\u3361"=>"9\u70B9",
"\u3362"=>"10\u70B9",
"\u3363"=>"11\u70B9",
"\u3364"=>"12\u70B9",
"\u3365"=>"13\u70B9",
"\u3366"=>"14\u70B9",
"\u3367"=>"15\u70B9",
"\u3368"=>"16\u70B9",
"\u3369"=>"17\u70B9",
"\u336A"=>"18\u70B9",
"\u336B"=>"19\u70B9",
"\u336C"=>"20\u70B9",
"\u336D"=>"21\u70B9",
"\u336E"=>"22\u70B9",
"\u336F"=>"23\u70B9",
"\u3370"=>"24\u70B9",
"\u3371"=>"hPa",
"\u3372"=>"da",
"\u3373"=>"AU",
"\u3374"=>"bar",
"\u3375"=>"oV",
"\u3376"=>"pc",
"\u3377"=>"dm",
"\u3378"=>"dm2",
"\u3379"=>"dm3",
"\u337A"=>"IU",
"\u337B"=>"\u5E73\u6210",
"\u337C"=>"\u662D\u548C",
"\u337D"=>"\u5927\u6B63",
"\u337E"=>"\u660E\u6CBB",
"\u337F"=>"\u682A\u5F0F\u4F1A\u793E",
"\u3380"=>"pA",
"\u3381"=>"nA",
"\u3382"=>"\u03BCA",
"\u3383"=>"mA",
"\u3384"=>"kA",
"\u3385"=>"KB",
"\u3386"=>"MB",
"\u3387"=>"GB",
"\u3388"=>"cal",
"\u3389"=>"kcal",
"\u338A"=>"pF",
"\u338B"=>"nF",
"\u338C"=>"\u03BCF",
"\u338D"=>"\u03BCg",
"\u338E"=>"mg",
"\u338F"=>"kg",
"\u3390"=>"Hz",
"\u3391"=>"kHz",
"\u3392"=>"MHz",
"\u3393"=>"GHz",
"\u3394"=>"THz",
"\u3395"=>"\u03BCl",
"\u3396"=>"ml",
"\u3397"=>"dl",
"\u3398"=>"kl",
"\u3399"=>"fm",
"\u339A"=>"nm",
"\u339B"=>"\u03BCm",
"\u339C"=>"mm",
"\u339D"=>"cm",
"\u339E"=>"km",
"\u339F"=>"mm2",
"\u33A0"=>"cm2",
"\u33A1"=>"m2",
"\u33A2"=>"km2",
"\u33A3"=>"mm3",
"\u33A4"=>"cm3",
"\u33A5"=>"m3",
"\u33A6"=>"km3",
"\u33A7"=>"m\u2215s",
"\u33A8"=>"m\u2215s2",
"\u33A9"=>"Pa",
"\u33AA"=>"kPa",
"\u33AB"=>"MPa",
"\u33AC"=>"GPa",
"\u33AD"=>"rad",
"\u33AE"=>"rad\u2215s",
"\u33AF"=>"rad\u2215s2",
"\u33B0"=>"ps",
"\u33B1"=>"ns",
"\u33B2"=>"\u03BCs",
"\u33B3"=>"ms",
"\u33B4"=>"pV",
"\u33B5"=>"nV",
"\u33B6"=>"\u03BCV",
"\u33B7"=>"mV",
"\u33B8"=>"kV",
"\u33B9"=>"MV",
"\u33BA"=>"pW",
"\u33BB"=>"nW",
"\u33BC"=>"\u03BCW",
"\u33BD"=>"mW",
"\u33BE"=>"kW",
"\u33BF"=>"MW",
"\u33C0"=>"k\u03A9",
"\u33C1"=>"M\u03A9",
"\u33C2"=>"a.m.",
"\u33C3"=>"Bq",
"\u33C4"=>"cc",
"\u33C5"=>"cd",
"\u33C6"=>"C\u2215kg",
"\u33C7"=>"Co.",
"\u33C8"=>"dB",
"\u33C9"=>"Gy",
"\u33CA"=>"ha",
"\u33CB"=>"HP",
"\u33CC"=>"in",
"\u33CD"=>"KK",
"\u33CE"=>"KM",
"\u33CF"=>"kt",
"\u33D0"=>"lm",
"\u33D1"=>"ln",
"\u33D2"=>"log",
"\u33D3"=>"lx",
"\u33D4"=>"mb",
"\u33D5"=>"mil",
"\u33D6"=>"mol",
"\u33D7"=>"PH",
"\u33D8"=>"p.m.",
"\u33D9"=>"PPM",
"\u33DA"=>"PR",
"\u33DB"=>"sr",
"\u33DC"=>"Sv",
"\u33DD"=>"Wb",
"\u33DE"=>"V\u2215m",
"\u33DF"=>"A\u2215m",
"\u33E0"=>"1\u65E5",
"\u33E1"=>"2\u65E5",
"\u33E2"=>"3\u65E5",
"\u33E3"=>"4\u65E5",
"\u33E4"=>"5\u65E5",
"\u33E5"=>"6\u65E5",
"\u33E6"=>"7\u65E5",
"\u33E7"=>"8\u65E5",
"\u33E8"=>"9\u65E5",
"\u33E9"=>"10\u65E5",
"\u33EA"=>"11\u65E5",
"\u33EB"=>"12\u65E5",
"\u33EC"=>"13\u65E5",
"\u33ED"=>"14\u65E5",
"\u33EE"=>"15\u65E5",
"\u33EF"=>"16\u65E5",
"\u33F0"=>"17\u65E5",
"\u33F1"=>"18\u65E5",
"\u33F2"=>"19\u65E5",
"\u33F3"=>"20\u65E5",
"\u33F4"=>"21\u65E5",
"\u33F5"=>"22\u65E5",
"\u33F6"=>"23\u65E5",
"\u33F7"=>"24\u65E5",
"\u33F8"=>"25\u65E5",
"\u33F9"=>"26\u65E5",
"\u33FA"=>"27\u65E5",
"\u33FB"=>"28\u65E5",
"\u33FC"=>"29\u65E5",
"\u33FD"=>"30\u65E5",
"\u33FE"=>"31\u65E5",
"\u33FF"=>"gal",
"\uA69C"=>"\u044A",
"\uA69D"=>"\u044C",
"\uA770"=>"\uA76F",
"\uA7F2"=>"C",
"\uA7F3"=>"F",
"\uA7F4"=>"Q",
"\uA7F8"=>"\u0126",
"\uA7F9"=>"\u0153",
"\uAB5C"=>"\uA727",
"\uAB5D"=>"\uAB37",
"\uAB5E"=>"\u026B",
"\uAB5F"=>"\uAB52",
"\uAB69"=>"\u028D",
"\uFB00"=>"ff",
"\uFB01"=>"fi",
"\uFB02"=>"fl",
"\uFB03"=>"ffi",
"\uFB04"=>"ffl",
"\uFB05"=>"st",
"\uFB06"=>"st",
"\uFB13"=>"\u0574\u0576",
"\uFB14"=>"\u0574\u0565",
"\uFB15"=>"\u0574\u056B",
"\uFB16"=>"\u057E\u0576",
"\uFB17"=>"\u0574\u056D",
"\uFB20"=>"\u05E2",
"\uFB21"=>"\u05D0",
"\uFB22"=>"\u05D3",
"\uFB23"=>"\u05D4",
"\uFB24"=>"\u05DB",
"\uFB25"=>"\u05DC",
"\uFB26"=>"\u05DD",
"\uFB27"=>"\u05E8",
"\uFB28"=>"\u05EA",
"\uFB29"=>"+",
"\uFB4F"=>"\u05D0\u05DC",
"\uFB50"=>"\u0671",
"\uFB51"=>"\u0671",
"\uFB52"=>"\u067B",
"\uFB53"=>"\u067B",
"\uFB54"=>"\u067B",
"\uFB55"=>"\u067B",
"\uFB56"=>"\u067E",
"\uFB57"=>"\u067E",
"\uFB58"=>"\u067E",
"\uFB59"=>"\u067E",
"\uFB5A"=>"\u0680",
"\uFB5B"=>"\u0680",
"\uFB5C"=>"\u0680",
"\uFB5D"=>"\u0680",
"\uFB5E"=>"\u067A",
"\uFB5F"=>"\u067A",
"\uFB60"=>"\u067A",
"\uFB61"=>"\u067A",
"\uFB62"=>"\u067F",
"\uFB63"=>"\u067F",
"\uFB64"=>"\u067F",
"\uFB65"=>"\u067F",
"\uFB66"=>"\u0679",
"\uFB67"=>"\u0679",
"\uFB68"=>"\u0679",
"\uFB69"=>"\u0679",
"\uFB6A"=>"\u06A4",
"\uFB6B"=>"\u06A4",
"\uFB6C"=>"\u06A4",
"\uFB6D"=>"\u06A4",
"\uFB6E"=>"\u06A6",
"\uFB6F"=>"\u06A6",
"\uFB70"=>"\u06A6",
"\uFB71"=>"\u06A6",
"\uFB72"=>"\u0684",
"\uFB73"=>"\u0684",
"\uFB74"=>"\u0684",
"\uFB75"=>"\u0684",
"\uFB76"=>"\u0683",
"\uFB77"=>"\u0683",
"\uFB78"=>"\u0683",
"\uFB79"=>"\u0683",
"\uFB7A"=>"\u0686",
"\uFB7B"=>"\u0686",
"\uFB7C"=>"\u0686",
"\uFB7D"=>"\u0686",
"\uFB7E"=>"\u0687",
"\uFB7F"=>"\u0687",
"\uFB80"=>"\u0687",
"\uFB81"=>"\u0687",
"\uFB82"=>"\u068D",
"\uFB83"=>"\u068D",
"\uFB84"=>"\u068C",
"\uFB85"=>"\u068C",
"\uFB86"=>"\u068E",
"\uFB87"=>"\u068E",
"\uFB88"=>"\u0688",
"\uFB89"=>"\u0688",
"\uFB8A"=>"\u0698",
"\uFB8B"=>"\u0698",
"\uFB8C"=>"\u0691",
"\uFB8D"=>"\u0691",
"\uFB8E"=>"\u06A9",
"\uFB8F"=>"\u06A9",
"\uFB90"=>"\u06A9",
"\uFB91"=>"\u06A9",
"\uFB92"=>"\u06AF",
"\uFB93"=>"\u06AF",
"\uFB94"=>"\u06AF",
"\uFB95"=>"\u06AF",
"\uFB96"=>"\u06B3",
"\uFB97"=>"\u06B3",
"\uFB98"=>"\u06B3",
"\uFB99"=>"\u06B3",
"\uFB9A"=>"\u06B1",
"\uFB9B"=>"\u06B1",
"\uFB9C"=>"\u06B1",
"\uFB9D"=>"\u06B1",
"\uFB9E"=>"\u06BA",
"\uFB9F"=>"\u06BA",
"\uFBA0"=>"\u06BB",
"\uFBA1"=>"\u06BB",
"\uFBA2"=>"\u06BB",
"\uFBA3"=>"\u06BB",
"\uFBA4"=>"\u06C0",
"\uFBA5"=>"\u06C0",
"\uFBA6"=>"\u06C1",
"\uFBA7"=>"\u06C1",
"\uFBA8"=>"\u06C1",
"\uFBA9"=>"\u06C1",
"\uFBAA"=>"\u06BE",
"\uFBAB"=>"\u06BE",
"\uFBAC"=>"\u06BE",
"\uFBAD"=>"\u06BE",
"\uFBAE"=>"\u06D2",
"\uFBAF"=>"\u06D2",
"\uFBB0"=>"\u06D3",
"\uFBB1"=>"\u06D3",
"\uFBD3"=>"\u06AD",
"\uFBD4"=>"\u06AD",
"\uFBD5"=>"\u06AD",
"\uFBD6"=>"\u06AD",
"\uFBD7"=>"\u06C7",
"\uFBD8"=>"\u06C7",
"\uFBD9"=>"\u06C6",
"\uFBDA"=>"\u06C6",
"\uFBDB"=>"\u06C8",
"\uFBDC"=>"\u06C8",
"\uFBDD"=>"\u06C7\u0674",
"\uFBDE"=>"\u06CB",
"\uFBDF"=>"\u06CB",
"\uFBE0"=>"\u06C5",
"\uFBE1"=>"\u06C5",
"\uFBE2"=>"\u06C9",
"\uFBE3"=>"\u06C9",
"\uFBE4"=>"\u06D0",
"\uFBE5"=>"\u06D0",
"\uFBE6"=>"\u06D0",
"\uFBE7"=>"\u06D0",
"\uFBE8"=>"\u0649",
"\uFBE9"=>"\u0649",
"\uFBEA"=>"\u0626\u0627",
"\uFBEB"=>"\u0626\u0627",
"\uFBEC"=>"\u0626\u06D5",
"\uFBED"=>"\u0626\u06D5",
"\uFBEE"=>"\u0626\u0648",
"\uFBEF"=>"\u0626\u0648",
"\uFBF0"=>"\u0626\u06C7",
"\uFBF1"=>"\u0626\u06C7",
"\uFBF2"=>"\u0626\u06C6",
"\uFBF3"=>"\u0626\u06C6",
"\uFBF4"=>"\u0626\u06C8",
"\uFBF5"=>"\u0626\u06C8",
"\uFBF6"=>"\u0626\u06D0",
"\uFBF7"=>"\u0626\u06D0",
"\uFBF8"=>"\u0626\u06D0",
"\uFBF9"=>"\u0626\u0649",
"\uFBFA"=>"\u0626\u0649",
"\uFBFB"=>"\u0626\u0649",
"\uFBFC"=>"\u06CC",
"\uFBFD"=>"\u06CC",
"\uFBFE"=>"\u06CC",
"\uFBFF"=>"\u06CC",
"\uFC00"=>"\u0626\u062C",
"\uFC01"=>"\u0626\u062D",
"\uFC02"=>"\u0626\u0645",
"\uFC03"=>"\u0626\u0649",
"\uFC04"=>"\u0626\u064A",
"\uFC05"=>"\u0628\u062C",
"\uFC06"=>"\u0628\u062D",
"\uFC07"=>"\u0628\u062E",
"\uFC08"=>"\u0628\u0645",
"\uFC09"=>"\u0628\u0649",
"\uFC0A"=>"\u0628\u064A",
"\uFC0B"=>"\u062A\u062C",
"\uFC0C"=>"\u062A\u062D",
"\uFC0D"=>"\u062A\u062E",
"\uFC0E"=>"\u062A\u0645",
"\uFC0F"=>"\u062A\u0649",
"\uFC10"=>"\u062A\u064A",
"\uFC11"=>"\u062B\u062C",
"\uFC12"=>"\u062B\u0645",
"\uFC13"=>"\u062B\u0649",
"\uFC14"=>"\u062B\u064A",
"\uFC15"=>"\u062C\u062D",
"\uFC16"=>"\u062C\u0645",
"\uFC17"=>"\u062D\u062C",
"\uFC18"=>"\u062D\u0645",
"\uFC19"=>"\u062E\u062C",
"\uFC1A"=>"\u062E\u062D",
"\uFC1B"=>"\u062E\u0645",
"\uFC1C"=>"\u0633\u062C",
"\uFC1D"=>"\u0633\u062D",
"\uFC1E"=>"\u0633\u062E",
"\uFC1F"=>"\u0633\u0645",
"\uFC20"=>"\u0635\u062D",
"\uFC21"=>"\u0635\u0645",
"\uFC22"=>"\u0636\u062C",
"\uFC23"=>"\u0636\u062D",
"\uFC24"=>"\u0636\u062E",
"\uFC25"=>"\u0636\u0645",
"\uFC26"=>"\u0637\u062D",
"\uFC27"=>"\u0637\u0645",
"\uFC28"=>"\u0638\u0645",
"\uFC29"=>"\u0639\u062C",
"\uFC2A"=>"\u0639\u0645",
"\uFC2B"=>"\u063A\u062C",
"\uFC2C"=>"\u063A\u0645",
"\uFC2D"=>"\u0641\u062C",
"\uFC2E"=>"\u0641\u062D",
"\uFC2F"=>"\u0641\u062E",
"\uFC30"=>"\u0641\u0645",
"\uFC31"=>"\u0641\u0649",
"\uFC32"=>"\u0641\u064A",
"\uFC33"=>"\u0642\u062D",
"\uFC34"=>"\u0642\u0645",
"\uFC35"=>"\u0642\u0649",
"\uFC36"=>"\u0642\u064A",
"\uFC37"=>"\u0643\u0627",
"\uFC38"=>"\u0643\u062C",
"\uFC39"=>"\u0643\u062D",
"\uFC3A"=>"\u0643\u062E",
"\uFC3B"=>"\u0643\u0644",
"\uFC3C"=>"\u0643\u0645",
"\uFC3D"=>"\u0643\u0649",
"\uFC3E"=>"\u0643\u064A",
"\uFC3F"=>"\u0644\u062C",
"\uFC40"=>"\u0644\u062D",
"\uFC41"=>"\u0644\u062E",
"\uFC42"=>"\u0644\u0645",
"\uFC43"=>"\u0644\u0649",
"\uFC44"=>"\u0644\u064A",
"\uFC45"=>"\u0645\u062C",
"\uFC46"=>"\u0645\u062D",
"\uFC47"=>"\u0645\u062E",
"\uFC48"=>"\u0645\u0645",
"\uFC49"=>"\u0645\u0649",
"\uFC4A"=>"\u0645\u064A",
"\uFC4B"=>"\u0646\u062C",
"\uFC4C"=>"\u0646\u062D",
"\uFC4D"=>"\u0646\u062E",
"\uFC4E"=>"\u0646\u0645",
"\uFC4F"=>"\u0646\u0649",
"\uFC50"=>"\u0646\u064A",
"\uFC51"=>"\u0647\u062C",
"\uFC52"=>"\u0647\u0645",
"\uFC53"=>"\u0647\u0649",
"\uFC54"=>"\u0647\u064A",
"\uFC55"=>"\u064A\u062C",
"\uFC56"=>"\u064A\u062D",
"\uFC57"=>"\u064A\u062E",
"\uFC58"=>"\u064A\u0645",
"\uFC59"=>"\u064A\u0649",
"\uFC5A"=>"\u064A\u064A",
"\uFC5B"=>"\u0630\u0670",
"\uFC5C"=>"\u0631\u0670",
"\uFC5D"=>"\u0649\u0670",
"\uFC5E"=>" \u064C\u0651",
"\uFC5F"=>" \u064D\u0651",
"\uFC60"=>" \u064E\u0651",
"\uFC61"=>" \u064F\u0651",
"\uFC62"=>" \u0650\u0651",
"\uFC63"=>" \u0651\u0670",
"\uFC64"=>"\u0626\u0631",
"\uFC65"=>"\u0626\u0632",
"\uFC66"=>"\u0626\u0645",
"\uFC67"=>"\u0626\u0646",
"\uFC68"=>"\u0626\u0649",
"\uFC69"=>"\u0626\u064A",
"\uFC6A"=>"\u0628\u0631",
"\uFC6B"=>"\u0628\u0632",
"\uFC6C"=>"\u0628\u0645",
"\uFC6D"=>"\u0628\u0646",
"\uFC6E"=>"\u0628\u0649",
"\uFC6F"=>"\u0628\u064A",
"\uFC70"=>"\u062A\u0631",
"\uFC71"=>"\u062A\u0632",
"\uFC72"=>"\u062A\u0645",
"\uFC73"=>"\u062A\u0646",
"\uFC74"=>"\u062A\u0649",
"\uFC75"=>"\u062A\u064A",
"\uFC76"=>"\u062B\u0631",
"\uFC77"=>"\u062B\u0632",
"\uFC78"=>"\u062B\u0645",
"\uFC79"=>"\u062B\u0646",
"\uFC7A"=>"\u062B\u0649",
"\uFC7B"=>"\u062B\u064A",
"\uFC7C"=>"\u0641\u0649",
"\uFC7D"=>"\u0641\u064A",
"\uFC7E"=>"\u0642\u0649",
"\uFC7F"=>"\u0642\u064A",
"\uFC80"=>"\u0643\u0627",
"\uFC81"=>"\u0643\u0644",
"\uFC82"=>"\u0643\u0645",
"\uFC83"=>"\u0643\u0649",
"\uFC84"=>"\u0643\u064A",
"\uFC85"=>"\u0644\u0645",
"\uFC86"=>"\u0644\u0649",
"\uFC87"=>"\u0644\u064A",
"\uFC88"=>"\u0645\u0627",
"\uFC89"=>"\u0645\u0645",
"\uFC8A"=>"\u0646\u0631",
"\uFC8B"=>"\u0646\u0632",
"\uFC8C"=>"\u0646\u0645",
"\uFC8D"=>"\u0646\u0646",
"\uFC8E"=>"\u0646\u0649",
"\uFC8F"=>"\u0646\u064A",
"\uFC90"=>"\u0649\u0670",
"\uFC91"=>"\u064A\u0631",
"\uFC92"=>"\u064A\u0632",
"\uFC93"=>"\u064A\u0645",
"\uFC94"=>"\u064A\u0646",
"\uFC95"=>"\u064A\u0649",
"\uFC96"=>"\u064A\u064A",
"\uFC97"=>"\u0626\u062C",
"\uFC98"=>"\u0626\u062D",
"\uFC99"=>"\u0626\u062E",
"\uFC9A"=>"\u0626\u0645",
"\uFC9B"=>"\u0626\u0647",
"\uFC9C"=>"\u0628\u062C",
"\uFC9D"=>"\u0628\u062D",
"\uFC9E"=>"\u0628\u062E",
"\uFC9F"=>"\u0628\u0645",
"\uFCA0"=>"\u0628\u0647",
"\uFCA1"=>"\u062A\u062C",
"\uFCA2"=>"\u062A\u062D",
"\uFCA3"=>"\u062A\u062E",
"\uFCA4"=>"\u062A\u0645",
"\uFCA5"=>"\u062A\u0647",
"\uFCA6"=>"\u062B\u0645",
"\uFCA7"=>"\u062C\u062D",
"\uFCA8"=>"\u062C\u0645",
"\uFCA9"=>"\u062D\u062C",
"\uFCAA"=>"\u062D\u0645",
"\uFCAB"=>"\u062E\u062C",
"\uFCAC"=>"\u062E\u0645",
"\uFCAD"=>"\u0633\u062C",
"\uFCAE"=>"\u0633\u062D",
"\uFCAF"=>"\u0633\u062E",
"\uFCB0"=>"\u0633\u0645",
"\uFCB1"=>"\u0635\u062D",
"\uFCB2"=>"\u0635\u062E",
"\uFCB3"=>"\u0635\u0645",
"\uFCB4"=>"\u0636\u062C",
"\uFCB5"=>"\u0636\u062D",
"\uFCB6"=>"\u0636\u062E",
"\uFCB7"=>"\u0636\u0645",
"\uFCB8"=>"\u0637\u062D",
"\uFCB9"=>"\u0638\u0645",
"\uFCBA"=>"\u0639\u062C",
"\uFCBB"=>"\u0639\u0645",
"\uFCBC"=>"\u063A\u062C",
"\uFCBD"=>"\u063A\u0645",
"\uFCBE"=>"\u0641\u062C",
"\uFCBF"=>"\u0641\u062D",
"\uFCC0"=>"\u0641\u062E",
"\uFCC1"=>"\u0641\u0645",
"\uFCC2"=>"\u0642\u062D",
"\uFCC3"=>"\u0642\u0645",
"\uFCC4"=>"\u0643\u062C",
"\uFCC5"=>"\u0643\u062D",
"\uFCC6"=>"\u0643\u062E",
"\uFCC7"=>"\u0643\u0644",
"\uFCC8"=>"\u0643\u0645",
"\uFCC9"=>"\u0644\u062C",
"\uFCCA"=>"\u0644\u062D",
"\uFCCB"=>"\u0644\u062E",
"\uFCCC"=>"\u0644\u0645",
"\uFCCD"=>"\u0644\u0647",
"\uFCCE"=>"\u0645\u062C",
"\uFCCF"=>"\u0645\u062D",
"\uFCD0"=>"\u0645\u062E",
"\uFCD1"=>"\u0645\u0645",
"\uFCD2"=>"\u0646\u062C",
"\uFCD3"=>"\u0646\u062D",
"\uFCD4"=>"\u0646\u062E",
"\uFCD5"=>"\u0646\u0645",
"\uFCD6"=>"\u0646\u0647",
"\uFCD7"=>"\u0647\u062C",
"\uFCD8"=>"\u0647\u0645",
"\uFCD9"=>"\u0647\u0670",
"\uFCDA"=>"\u064A\u062C",
"\uFCDB"=>"\u064A\u062D",
"\uFCDC"=>"\u064A\u062E",
"\uFCDD"=>"\u064A\u0645",
"\uFCDE"=>"\u064A\u0647",
"\uFCDF"=>"\u0626\u0645",
"\uFCE0"=>"\u0626\u0647",
"\uFCE1"=>"\u0628\u0645",
"\uFCE2"=>"\u0628\u0647",
"\uFCE3"=>"\u062A\u0645",
"\uFCE4"=>"\u062A\u0647",
"\uFCE5"=>"\u062B\u0645",
"\uFCE6"=>"\u062B\u0647",
"\uFCE7"=>"\u0633\u0645",
"\uFCE8"=>"\u0633\u0647",
"\uFCE9"=>"\u0634\u0645",
"\uFCEA"=>"\u0634\u0647",
"\uFCEB"=>"\u0643\u0644",
"\uFCEC"=>"\u0643\u0645",
"\uFCED"=>"\u0644\u0645",
"\uFCEE"=>"\u0646\u0645",
"\uFCEF"=>"\u0646\u0647",
"\uFCF0"=>"\u064A\u0645",
"\uFCF1"=>"\u064A\u0647",
"\uFCF2"=>"\u0640\u064E\u0651",
"\uFCF3"=>"\u0640\u064F\u0651",
"\uFCF4"=>"\u0640\u0650\u0651",
"\uFCF5"=>"\u0637\u0649",
"\uFCF6"=>"\u0637\u064A",
"\uFCF7"=>"\u0639\u0649",
"\uFCF8"=>"\u0639\u064A",
"\uFCF9"=>"\u063A\u0649",
"\uFCFA"=>"\u063A\u064A",
"\uFCFB"=>"\u0633\u0649",
"\uFCFC"=>"\u0633\u064A",
"\uFCFD"=>"\u0634\u0649",
"\uFCFE"=>"\u0634\u064A",
"\uFCFF"=>"\u062D\u0649",
"\uFD00"=>"\u062D\u064A",
"\uFD01"=>"\u062C\u0649",
"\uFD02"=>"\u062C\u064A",
"\uFD03"=>"\u062E\u0649",
"\uFD04"=>"\u062E\u064A",
"\uFD05"=>"\u0635\u0649",
"\uFD06"=>"\u0635\u064A",
"\uFD07"=>"\u0636\u0649",
"\uFD08"=>"\u0636\u064A",
"\uFD09"=>"\u0634\u062C",
"\uFD0A"=>"\u0634\u062D",
"\uFD0B"=>"\u0634\u062E",
"\uFD0C"=>"\u0634\u0645",
"\uFD0D"=>"\u0634\u0631",
"\uFD0E"=>"\u0633\u0631",
"\uFD0F"=>"\u0635\u0631",
"\uFD10"=>"\u0636\u0631",
"\uFD11"=>"\u0637\u0649",
"\uFD12"=>"\u0637\u064A",
"\uFD13"=>"\u0639\u0649",
"\uFD14"=>"\u0639\u064A",
"\uFD15"=>"\u063A\u0649",
"\uFD16"=>"\u063A\u064A",
"\uFD17"=>"\u0633\u0649",
"\uFD18"=>"\u0633\u064A",
"\uFD19"=>"\u0634\u0649",
"\uFD1A"=>"\u0634\u064A",
"\uFD1B"=>"\u062D\u0649",
"\uFD1C"=>"\u062D\u064A",
"\uFD1D"=>"\u062C\u0649",
"\uFD1E"=>"\u062C\u064A",
"\uFD1F"=>"\u062E\u0649",
"\uFD20"=>"\u062E\u064A",
"\uFD21"=>"\u0635\u0649",
"\uFD22"=>"\u0635\u064A",
"\uFD23"=>"\u0636\u0649",
"\uFD24"=>"\u0636\u064A",
"\uFD25"=>"\u0634\u062C",
"\uFD26"=>"\u0634\u062D",
"\uFD27"=>"\u0634\u062E",
"\uFD28"=>"\u0634\u0645",
"\uFD29"=>"\u0634\u0631",
"\uFD2A"=>"\u0633\u0631",
"\uFD2B"=>"\u0635\u0631",
"\uFD2C"=>"\u0636\u0631",
"\uFD2D"=>"\u0634\u062C",
"\uFD2E"=>"\u0634\u062D",
"\uFD2F"=>"\u0634\u062E",
"\uFD30"=>"\u0634\u0645",
"\uFD31"=>"\u0633\u0647",
"\uFD32"=>"\u0634\u0647",
"\uFD33"=>"\u0637\u0645",
"\uFD34"=>"\u0633\u062C",
"\uFD35"=>"\u0633\u062D",
"\uFD36"=>"\u0633\u062E",
"\uFD37"=>"\u0634\u062C",
"\uFD38"=>"\u0634\u062D",
"\uFD39"=>"\u0634\u062E",
"\uFD3A"=>"\u0637\u0645",
"\uFD3B"=>"\u0638\u0645",
"\uFD3C"=>"\u0627\u064B",
"\uFD3D"=>"\u0627\u064B",
"\uFD50"=>"\u062A\u062C\u0645",
"\uFD51"=>"\u062A\u062D\u062C",
"\uFD52"=>"\u062A\u062D\u062C",
"\uFD53"=>"\u062A\u062D\u0645",
"\uFD54"=>"\u062A\u062E\u0645",
"\uFD55"=>"\u062A\u0645\u062C",
"\uFD56"=>"\u062A\u0645\u062D",
"\uFD57"=>"\u062A\u0645\u062E",
"\uFD58"=>"\u062C\u0645\u062D",
"\uFD59"=>"\u062C\u0645\u062D",
"\uFD5A"=>"\u062D\u0645\u064A",
"\uFD5B"=>"\u062D\u0645\u0649",
"\uFD5C"=>"\u0633\u062D\u062C",
"\uFD5D"=>"\u0633\u062C\u062D",
"\uFD5E"=>"\u0633\u062C\u0649",
"\uFD5F"=>"\u0633\u0645\u062D",
"\uFD60"=>"\u0633\u0645\u062D",
"\uFD61"=>"\u0633\u0645\u062C",
"\uFD62"=>"\u0633\u0645\u0645",
"\uFD63"=>"\u0633\u0645\u0645",
"\uFD64"=>"\u0635\u062D\u062D",
"\uFD65"=>"\u0635\u062D\u062D",
"\uFD66"=>"\u0635\u0645\u0645",
"\uFD67"=>"\u0634\u062D\u0645",
"\uFD68"=>"\u0634\u062D\u0645",
"\uFD69"=>"\u0634\u062C\u064A",
"\uFD6A"=>"\u0634\u0645\u062E",
"\uFD6B"=>"\u0634\u0645\u062E",
"\uFD6C"=>"\u0634\u0645\u0645",
"\uFD6D"=>"\u0634\u0645\u0645",
"\uFD6E"=>"\u0636\u062D\u0649",
"\uFD6F"=>"\u0636\u062E\u0645",
"\uFD70"=>"\u0636\u062E\u0645",
"\uFD71"=>"\u0637\u0645\u062D",
"\uFD72"=>"\u0637\u0645\u062D",
"\uFD73"=>"\u0637\u0645\u0645",
"\uFD74"=>"\u0637\u0645\u064A",
"\uFD75"=>"\u0639\u062C\u0645",
"\uFD76"=>"\u0639\u0645\u0645",
"\uFD77"=>"\u0639\u0645\u0645",
"\uFD78"=>"\u0639\u0645\u0649",
"\uFD79"=>"\u063A\u0645\u0645",
"\uFD7A"=>"\u063A\u0645\u064A",
"\uFD7B"=>"\u063A\u0645\u0649",
"\uFD7C"=>"\u0641\u062E\u0645",
"\uFD7D"=>"\u0641\u062E\u0645",
"\uFD7E"=>"\u0642\u0645\u062D",
"\uFD7F"=>"\u0642\u0645\u0645",
"\uFD80"=>"\u0644\u062D\u0645",
"\uFD81"=>"\u0644\u062D\u064A",
"\uFD82"=>"\u0644\u062D\u0649",
"\uFD83"=>"\u0644\u062C\u062C",
"\uFD84"=>"\u0644\u062C\u062C",
"\uFD85"=>"\u0644\u062E\u0645",
"\uFD86"=>"\u0644\u062E\u0645",
"\uFD87"=>"\u0644\u0645\u062D",
"\uFD88"=>"\u0644\u0645\u062D",
"\uFD89"=>"\u0645\u062D\u062C",
"\uFD8A"=>"\u0645\u062D\u0645",
"\uFD8B"=>"\u0645\u062D\u064A",
"\uFD8C"=>"\u0645\u062C\u062D",
"\uFD8D"=>"\u0645\u062C\u0645",
"\uFD8E"=>"\u0645\u062E\u062C",
"\uFD8F"=>"\u0645\u062E\u0645",
"\uFD92"=>"\u0645\u062C\u062E",
"\uFD93"=>"\u0647\u0645\u062C",
"\uFD94"=>"\u0647\u0645\u0645",
"\uFD95"=>"\u0646\u062D\u0645",
"\uFD96"=>"\u0646\u062D\u0649",
"\uFD97"=>"\u0646\u062C\u0645",
"\uFD98"=>"\u0646\u062C\u0645",
"\uFD99"=>"\u0646\u062C\u0649",
"\uFD9A"=>"\u0646\u0645\u064A",
"\uFD9B"=>"\u0646\u0645\u0649",
"\uFD9C"=>"\u064A\u0645\u0645",
"\uFD9D"=>"\u064A\u0645\u0645",
"\uFD9E"=>"\u0628\u062E\u064A",
"\uFD9F"=>"\u062A\u062C\u064A",
"\uFDA0"=>"\u062A\u062C\u0649",
"\uFDA1"=>"\u062A\u062E\u064A",
"\uFDA2"=>"\u062A\u062E\u0649",
"\uFDA3"=>"\u062A\u0645\u064A",
"\uFDA4"=>"\u062A\u0645\u0649",
"\uFDA5"=>"\u062C\u0645\u064A",
"\uFDA6"=>"\u062C\u062D\u0649",
"\uFDA7"=>"\u062C\u0645\u0649",
"\uFDA8"=>"\u0633\u062E\u0649",
"\uFDA9"=>"\u0635\u062D\u064A",
"\uFDAA"=>"\u0634\u062D\u064A",
"\uFDAB"=>"\u0636\u062D\u064A",
"\uFDAC"=>"\u0644\u062C\u064A",
"\uFDAD"=>"\u0644\u0645\u064A",
"\uFDAE"=>"\u064A\u062D\u064A",
"\uFDAF"=>"\u064A\u062C\u064A",
"\uFDB0"=>"\u064A\u0645\u064A",
"\uFDB1"=>"\u0645\u0645\u064A",
"\uFDB2"=>"\u0642\u0645\u064A",
"\uFDB3"=>"\u0646\u062D\u064A",
"\uFDB4"=>"\u0642\u0645\u062D",
"\uFDB5"=>"\u0644\u062D\u0645",
"\uFDB6"=>"\u0639\u0645\u064A",
"\uFDB7"=>"\u0643\u0645\u064A",
"\uFDB8"=>"\u0646\u062C\u062D",
"\uFDB9"=>"\u0645\u062E\u064A",
"\uFDBA"=>"\u0644\u062C\u0645",
"\uFDBB"=>"\u0643\u0645\u0645",
"\uFDBC"=>"\u0644\u062C\u0645",
"\uFDBD"=>"\u0646\u062C\u062D",
"\uFDBE"=>"\u062C\u062D\u064A",
"\uFDBF"=>"\u062D\u062C\u064A",
"\uFDC0"=>"\u0645\u062C\u064A",
"\uFDC1"=>"\u0641\u0645\u064A",
"\uFDC2"=>"\u0628\u062D\u064A",
"\uFDC3"=>"\u0643\u0645\u0645",
"\uFDC4"=>"\u0639\u062C\u0645",
"\uFDC5"=>"\u0635\u0645\u0645",
"\uFDC6"=>"\u0633\u062E\u064A",
"\uFDC7"=>"\u0646\u062C\u064A",
"\uFDF0"=>"\u0635\u0644\u06D2",
"\uFDF1"=>"\u0642\u0644\u06D2",
"\uFDF2"=>"\u0627\u0644\u0644\u0647",
"\uFDF3"=>"\u0627\u0643\u0628\u0631",
"\uFDF4"=>"\u0645\u062D\u0645\u062F",
"\uFDF5"=>"\u0635\u0644\u0639\u0645",
"\uFDF6"=>"\u0631\u0633\u0648\u0644",
"\uFDF7"=>"\u0639\u0644\u064A\u0647",
"\uFDF8"=>"\u0648\u0633\u0644\u0645",
"\uFDF9"=>"\u0635\u0644\u0649",
"\uFDFA"=>"\u0635\u0644\u0649 \u0627\u0644\u0644\u0647 \u0639\u0644\u064A\u0647 \u0648\u0633\u0644\u0645",
"\uFDFB"=>"\u062C\u0644 \u062C\u0644\u0627\u0644\u0647",
"\uFDFC"=>"\u0631\u06CC\u0627\u0644",
"\uFE10"=>",",
"\uFE11"=>"\u3001",
"\uFE12"=>"\u3002",
"\uFE13"=>":",
"\uFE14"=>";",
"\uFE15"=>"!",
"\uFE16"=>"?",
"\uFE17"=>"\u3016",
"\uFE18"=>"\u3017",
"\uFE19"=>"...",
"\uFE30"=>"..",
"\uFE31"=>"\u2014",
"\uFE32"=>"\u2013",
"\uFE33"=>"_",
"\uFE34"=>"_",
"\uFE35"=>"(",
"\uFE36"=>")",
"\uFE37"=>"{",
"\uFE38"=>"}",
"\uFE39"=>"\u3014",
"\uFE3A"=>"\u3015",
"\uFE3B"=>"\u3010",
"\uFE3C"=>"\u3011",
"\uFE3D"=>"\u300A",
"\uFE3E"=>"\u300B",
"\uFE3F"=>"\u3008",
"\uFE40"=>"\u3009",
"\uFE41"=>"\u300C",
"\uFE42"=>"\u300D",
"\uFE43"=>"\u300E",
"\uFE44"=>"\u300F",
"\uFE47"=>"[",
"\uFE48"=>"]",
"\uFE49"=>" \u0305",
"\uFE4A"=>" \u0305",
"\uFE4B"=>" \u0305",
"\uFE4C"=>" \u0305",
"\uFE4D"=>"_",
"\uFE4E"=>"_",
"\uFE4F"=>"_",
"\uFE50"=>",",
"\uFE51"=>"\u3001",
"\uFE52"=>".",
"\uFE54"=>";",
"\uFE55"=>":",
"\uFE56"=>"?",
"\uFE57"=>"!",
"\uFE58"=>"\u2014",
"\uFE59"=>"(",
"\uFE5A"=>")",
"\uFE5B"=>"{",
"\uFE5C"=>"}",
"\uFE5D"=>"\u3014",
"\uFE5E"=>"\u3015",
"\uFE5F"=>"#",
"\uFE60"=>"&",
"\uFE61"=>"*",
"\uFE62"=>"+",
"\uFE63"=>"-",
"\uFE64"=>"<",
"\uFE65"=>">",
"\uFE66"=>"=",
"\uFE68"=>"\\",
"\uFE69"=>"$",
"\uFE6A"=>"%",
"\uFE6B"=>"@",
"\uFE70"=>" \u064B",
"\uFE71"=>"\u0640\u064B",
"\uFE72"=>" \u064C",
"\uFE74"=>" \u064D",
"\uFE76"=>" \u064E",
"\uFE77"=>"\u0640\u064E",
"\uFE78"=>" \u064F",
"\uFE79"=>"\u0640\u064F",
"\uFE7A"=>" \u0650",
"\uFE7B"=>"\u0640\u0650",
"\uFE7C"=>" \u0651",
"\uFE7D"=>"\u0640\u0651",
"\uFE7E"=>" \u0652",
"\uFE7F"=>"\u0640\u0652",
"\uFE80"=>"\u0621",
"\uFE81"=>"\u0622",
"\uFE82"=>"\u0622",
"\uFE83"=>"\u0623",
"\uFE84"=>"\u0623",
"\uFE85"=>"\u0624",
"\uFE86"=>"\u0624",
"\uFE87"=>"\u0625",
"\uFE88"=>"\u0625",
"\uFE89"=>"\u0626",
"\uFE8A"=>"\u0626",
"\uFE8B"=>"\u0626",
"\uFE8C"=>"\u0626",
"\uFE8D"=>"\u0627",
"\uFE8E"=>"\u0627",
"\uFE8F"=>"\u0628",
"\uFE90"=>"\u0628",
"\uFE91"=>"\u0628",
"\uFE92"=>"\u0628",
"\uFE93"=>"\u0629",
"\uFE94"=>"\u0629",
"\uFE95"=>"\u062A",
"\uFE96"=>"\u062A",
"\uFE97"=>"\u062A",
"\uFE98"=>"\u062A",
"\uFE99"=>"\u062B",
"\uFE9A"=>"\u062B",
"\uFE9B"=>"\u062B",
"\uFE9C"=>"\u062B",
"\uFE9D"=>"\u062C",
"\uFE9E"=>"\u062C",
"\uFE9F"=>"\u062C",
"\uFEA0"=>"\u062C",
"\uFEA1"=>"\u062D",
"\uFEA2"=>"\u062D",
"\uFEA3"=>"\u062D",
"\uFEA4"=>"\u062D",
"\uFEA5"=>"\u062E",
"\uFEA6"=>"\u062E",
"\uFEA7"=>"\u062E",
"\uFEA8"=>"\u062E",
"\uFEA9"=>"\u062F",
"\uFEAA"=>"\u062F",
"\uFEAB"=>"\u0630",
"\uFEAC"=>"\u0630",
"\uFEAD"=>"\u0631",
"\uFEAE"=>"\u0631",
"\uFEAF"=>"\u0632",
"\uFEB0"=>"\u0632",
"\uFEB1"=>"\u0633",
"\uFEB2"=>"\u0633",
"\uFEB3"=>"\u0633",
"\uFEB4"=>"\u0633",
"\uFEB5"=>"\u0634",
"\uFEB6"=>"\u0634",
"\uFEB7"=>"\u0634",
"\uFEB8"=>"\u0634",
"\uFEB9"=>"\u0635",
"\uFEBA"=>"\u0635",
"\uFEBB"=>"\u0635",
"\uFEBC"=>"\u0635",
"\uFEBD"=>"\u0636",
"\uFEBE"=>"\u0636",
"\uFEBF"=>"\u0636",
"\uFEC0"=>"\u0636",
"\uFEC1"=>"\u0637",
"\uFEC2"=>"\u0637",
"\uFEC3"=>"\u0637",
"\uFEC4"=>"\u0637",
"\uFEC5"=>"\u0638",
"\uFEC6"=>"\u0638",
"\uFEC7"=>"\u0638",
"\uFEC8"=>"\u0638",
"\uFEC9"=>"\u0639",
"\uFECA"=>"\u0639",
"\uFECB"=>"\u0639",
"\uFECC"=>"\u0639",
"\uFECD"=>"\u063A",
"\uFECE"=>"\u063A",
"\uFECF"=>"\u063A",
"\uFED0"=>"\u063A",
"\uFED1"=>"\u0641",
"\uFED2"=>"\u0641",
"\uFED3"=>"\u0641",
"\uFED4"=>"\u0641",
"\uFED5"=>"\u0642",
"\uFED6"=>"\u0642",
"\uFED7"=>"\u0642",
"\uFED8"=>"\u0642",
"\uFED9"=>"\u0643",
"\uFEDA"=>"\u0643",
"\uFEDB"=>"\u0643",
"\uFEDC"=>"\u0643",
"\uFEDD"=>"\u0644",
"\uFEDE"=>"\u0644",
"\uFEDF"=>"\u0644",
"\uFEE0"=>"\u0644",
"\uFEE1"=>"\u0645",
"\uFEE2"=>"\u0645",
"\uFEE3"=>"\u0645",
"\uFEE4"=>"\u0645",
"\uFEE5"=>"\u0646",
"\uFEE6"=>"\u0646",
"\uFEE7"=>"\u0646",
"\uFEE8"=>"\u0646",
"\uFEE9"=>"\u0647",
"\uFEEA"=>"\u0647",
"\uFEEB"=>"\u0647",
"\uFEEC"=>"\u0647",
"\uFEED"=>"\u0648",
"\uFEEE"=>"\u0648",
"\uFEEF"=>"\u0649",
"\uFEF0"=>"\u0649",
"\uFEF1"=>"\u064A",
"\uFEF2"=>"\u064A",
"\uFEF3"=>"\u064A",
"\uFEF4"=>"\u064A",
"\uFEF5"=>"\u0644\u0622",
"\uFEF6"=>"\u0644\u0622",
"\uFEF7"=>"\u0644\u0623",
"\uFEF8"=>"\u0644\u0623",
"\uFEF9"=>"\u0644\u0625",
"\uFEFA"=>"\u0644\u0625",
"\uFEFB"=>"\u0644\u0627",
"\uFEFC"=>"\u0644\u0627",
"\uFF01"=>"!",
"\uFF02"=>"\"",
"\uFF03"=>"#",
"\uFF04"=>"$",
"\uFF05"=>"%",
"\uFF06"=>"&",
"\uFF07"=>"'",
"\uFF08"=>"(",
"\uFF09"=>")",
"\uFF0A"=>"*",
"\uFF0B"=>"+",
"\uFF0C"=>",",
"\uFF0D"=>"-",
"\uFF0E"=>".",
"\uFF0F"=>"/",
"\uFF10"=>"0",
"\uFF11"=>"1",
"\uFF12"=>"2",
"\uFF13"=>"3",
"\uFF14"=>"4",
"\uFF15"=>"5",
"\uFF16"=>"6",
"\uFF17"=>"7",
"\uFF18"=>"8",
"\uFF19"=>"9",
"\uFF1A"=>":",
"\uFF1B"=>";",
"\uFF1C"=>"<",
"\uFF1D"=>"=",
"\uFF1E"=>">",
"\uFF1F"=>"?",
"\uFF20"=>"@",
"\uFF21"=>"A",
"\uFF22"=>"B",
"\uFF23"=>"C",
"\uFF24"=>"D",
"\uFF25"=>"E",
"\uFF26"=>"F",
"\uFF27"=>"G",
"\uFF28"=>"H",
"\uFF29"=>"I",
"\uFF2A"=>"J",
"\uFF2B"=>"K",
"\uFF2C"=>"L",
"\uFF2D"=>"M",
"\uFF2E"=>"N",
"\uFF2F"=>"O",
"\uFF30"=>"P",
"\uFF31"=>"Q",
"\uFF32"=>"R",
"\uFF33"=>"S",
"\uFF34"=>"T",
"\uFF35"=>"U",
"\uFF36"=>"V",
"\uFF37"=>"W",
"\uFF38"=>"X",
"\uFF39"=>"Y",
"\uFF3A"=>"Z",
"\uFF3B"=>"[",
"\uFF3C"=>"\\",
"\uFF3D"=>"]",
"\uFF3E"=>"^",
"\uFF3F"=>"_",
"\uFF40"=>"`",
"\uFF41"=>"a",
"\uFF42"=>"b",
"\uFF43"=>"c",
"\uFF44"=>"d",
"\uFF45"=>"e",
"\uFF46"=>"f",
"\uFF47"=>"g",
"\uFF48"=>"h",
"\uFF49"=>"i",
"\uFF4A"=>"j",
"\uFF4B"=>"k",
"\uFF4C"=>"l",
"\uFF4D"=>"m",
"\uFF4E"=>"n",
"\uFF4F"=>"o",
"\uFF50"=>"p",
"\uFF51"=>"q",
"\uFF52"=>"r",
"\uFF53"=>"s",
"\uFF54"=>"t",
"\uFF55"=>"u",
"\uFF56"=>"v",
"\uFF57"=>"w",
"\uFF58"=>"x",
"\uFF59"=>"y",
"\uFF5A"=>"z",
"\uFF5B"=>"{",
"\uFF5C"=>"|",
"\uFF5D"=>"}",
"\uFF5E"=>"~",
"\uFF5F"=>"\u2985",
"\uFF60"=>"\u2986",
"\uFF61"=>"\u3002",
"\uFF62"=>"\u300C",
"\uFF63"=>"\u300D",
"\uFF64"=>"\u3001",
"\uFF65"=>"\u30FB",
"\uFF66"=>"\u30F2",
"\uFF67"=>"\u30A1",
"\uFF68"=>"\u30A3",
"\uFF69"=>"\u30A5",
"\uFF6A"=>"\u30A7",
"\uFF6B"=>"\u30A9",
"\uFF6C"=>"\u30E3",
"\uFF6D"=>"\u30E5",
"\uFF6E"=>"\u30E7",
"\uFF6F"=>"\u30C3",
"\uFF70"=>"\u30FC",
"\uFF71"=>"\u30A2",
"\uFF72"=>"\u30A4",
"\uFF73"=>"\u30A6",
"\uFF74"=>"\u30A8",
"\uFF75"=>"\u30AA",
"\uFF76"=>"\u30AB",
"\uFF77"=>"\u30AD",
"\uFF78"=>"\u30AF",
"\uFF79"=>"\u30B1",
"\uFF7A"=>"\u30B3",
"\uFF7B"=>"\u30B5",
"\uFF7C"=>"\u30B7",
"\uFF7D"=>"\u30B9",
"\uFF7E"=>"\u30BB",
"\uFF7F"=>"\u30BD",
"\uFF80"=>"\u30BF",
"\uFF81"=>"\u30C1",
"\uFF82"=>"\u30C4",
"\uFF83"=>"\u30C6",
"\uFF84"=>"\u30C8",
"\uFF85"=>"\u30CA",
"\uFF86"=>"\u30CB",
"\uFF87"=>"\u30CC",
"\uFF88"=>"\u30CD",
"\uFF89"=>"\u30CE",
"\uFF8A"=>"\u30CF",
"\uFF8B"=>"\u30D2",
"\uFF8C"=>"\u30D5",
"\uFF8D"=>"\u30D8",
"\uFF8E"=>"\u30DB",
"\uFF8F"=>"\u30DE",
"\uFF90"=>"\u30DF",
"\uFF91"=>"\u30E0",
"\uFF92"=>"\u30E1",
"\uFF93"=>"\u30E2",
"\uFF94"=>"\u30E4",
"\uFF95"=>"\u30E6",
"\uFF96"=>"\u30E8",
"\uFF97"=>"\u30E9",
"\uFF98"=>"\u30EA",
"\uFF99"=>"\u30EB",
"\uFF9A"=>"\u30EC",
"\uFF9B"=>"\u30ED",
"\uFF9C"=>"\u30EF",
"\uFF9D"=>"\u30F3",
"\uFF9E"=>"\u3099",
"\uFF9F"=>"\u309A",
"\uFFA0"=>"\u1160",
"\uFFA1"=>"\u1100",
"\uFFA2"=>"\u1101",
"\uFFA3"=>"\u11AA",
"\uFFA4"=>"\u1102",
"\uFFA5"=>"\u11AC",
"\uFFA6"=>"\u11AD",
"\uFFA7"=>"\u1103",
"\uFFA8"=>"\u1104",
"\uFFA9"=>"\u1105",
"\uFFAA"=>"\u11B0",
"\uFFAB"=>"\u11B1",
"\uFFAC"=>"\u11B2",
"\uFFAD"=>"\u11B3",
"\uFFAE"=>"\u11B4",
"\uFFAF"=>"\u11B5",
"\uFFB0"=>"\u111A",
"\uFFB1"=>"\u1106",
"\uFFB2"=>"\u1107",
"\uFFB3"=>"\u1108",
"\uFFB4"=>"\u1121",
"\uFFB5"=>"\u1109",
"\uFFB6"=>"\u110A",
"\uFFB7"=>"\u110B",
"\uFFB8"=>"\u110C",
"\uFFB9"=>"\u110D",
"\uFFBA"=>"\u110E",
"\uFFBB"=>"\u110F",
"\uFFBC"=>"\u1110",
"\uFFBD"=>"\u1111",
"\uFFBE"=>"\u1112",
"\uFFC2"=>"\u1161",
"\uFFC3"=>"\u1162",
"\uFFC4"=>"\u1163",
"\uFFC5"=>"\u1164",
"\uFFC6"=>"\u1165",
"\uFFC7"=>"\u1166",
"\uFFCA"=>"\u1167",
"\uFFCB"=>"\u1168",
"\uFFCC"=>"\u1169",
"\uFFCD"=>"\u116A",
"\uFFCE"=>"\u116B",
"\uFFCF"=>"\u116C",
"\uFFD2"=>"\u116D",
"\uFFD3"=>"\u116E",
"\uFFD4"=>"\u116F",
"\uFFD5"=>"\u1170",
"\uFFD6"=>"\u1171",
"\uFFD7"=>"\u1172",
"\uFFDA"=>"\u1173",
"\uFFDB"=>"\u1174",
"\uFFDC"=>"\u1175",
"\uFFE0"=>"\u00A2",
"\uFFE1"=>"\u00A3",
"\uFFE2"=>"\u00AC",
"\uFFE3"=>" \u0304",
"\uFFE4"=>"\u00A6",
"\uFFE5"=>"\u00A5",
"\uFFE6"=>"\u20A9",
"\uFFE8"=>"\u2502",
"\uFFE9"=>"\u2190",
"\uFFEA"=>"\u2191",
"\uFFEB"=>"\u2192",
"\uFFEC"=>"\u2193",
"\uFFED"=>"\u25A0",
"\uFFEE"=>"\u25CB",
"\u{10781}"=>"\u02D0",
"\u{10782}"=>"\u02D1",
"\u{10783}"=>"\u00E6",
"\u{10784}"=>"\u0299",
"\u{10785}"=>"\u0253",
"\u{10787}"=>"\u02A3",
"\u{10788}"=>"\uAB66",
"\u{10789}"=>"\u02A5",
"\u{1078A}"=>"\u02A4",
"\u{1078B}"=>"\u0256",
"\u{1078C}"=>"\u0257",
"\u{1078D}"=>"\u1D91",
"\u{1078E}"=>"\u0258",
"\u{1078F}"=>"\u025E",
"\u{10790}"=>"\u02A9",
"\u{10791}"=>"\u0264",
"\u{10792}"=>"\u0262",
"\u{10793}"=>"\u0260",
"\u{10794}"=>"\u029B",
"\u{10795}"=>"\u0127",
"\u{10796}"=>"\u029C",
"\u{10797}"=>"\u0267",
"\u{10798}"=>"\u0284",
"\u{10799}"=>"\u02AA",
"\u{1079A}"=>"\u02AB",
"\u{1079B}"=>"\u026C",
"\u{1079C}"=>"\u{1DF04}",
"\u{1079D}"=>"\uA78E",
"\u{1079E}"=>"\u026E",
"\u{1079F}"=>"\u{1DF05}",
"\u{107A0}"=>"\u028E",
"\u{107A1}"=>"\u{1DF06}",
"\u{107A2}"=>"\u00F8",
"\u{107A3}"=>"\u0276",
"\u{107A4}"=>"\u0277",
"\u{107A5}"=>"q",
"\u{107A6}"=>"\u027A",
"\u{107A7}"=>"\u{1DF08}",
"\u{107A8}"=>"\u027D",
"\u{107A9}"=>"\u027E",
"\u{107AA}"=>"\u0280",
"\u{107AB}"=>"\u02A8",
"\u{107AC}"=>"\u02A6",
"\u{107AD}"=>"\uAB67",
"\u{107AE}"=>"\u02A7",
"\u{107AF}"=>"\u0288",
"\u{107B0}"=>"\u2C71",
"\u{107B2}"=>"\u028F",
"\u{107B3}"=>"\u02A1",
"\u{107B4}"=>"\u02A2",
"\u{107B5}"=>"\u0298",
"\u{107B6}"=>"\u01C0",
"\u{107B7}"=>"\u01C1",
"\u{107B8}"=>"\u01C2",
"\u{107B9}"=>"\u{1DF0A}",
"\u{107BA}"=>"\u{1DF1E}",
"\u{1D400}"=>"A",
"\u{1D401}"=>"B",
"\u{1D402}"=>"C",
"\u{1D403}"=>"D",
"\u{1D404}"=>"E",
"\u{1D405}"=>"F",
"\u{1D406}"=>"G",
"\u{1D407}"=>"H",
"\u{1D408}"=>"I",
"\u{1D409}"=>"J",
"\u{1D40A}"=>"K",
"\u{1D40B}"=>"L",
"\u{1D40C}"=>"M",
"\u{1D40D}"=>"N",
"\u{1D40E}"=>"O",
"\u{1D40F}"=>"P",
"\u{1D410}"=>"Q",
"\u{1D411}"=>"R",
"\u{1D412}"=>"S",
"\u{1D413}"=>"T",
"\u{1D414}"=>"U",
"\u{1D415}"=>"V",
"\u{1D416}"=>"W",
"\u{1D417}"=>"X",
"\u{1D418}"=>"Y",
"\u{1D419}"=>"Z",
"\u{1D41A}"=>"a",
"\u{1D41B}"=>"b",
"\u{1D41C}"=>"c",
"\u{1D41D}"=>"d",
"\u{1D41E}"=>"e",
"\u{1D41F}"=>"f",
"\u{1D420}"=>"g",
"\u{1D421}"=>"h",
"\u{1D422}"=>"i",
"\u{1D423}"=>"j",
"\u{1D424}"=>"k",
"\u{1D425}"=>"l",
"\u{1D426}"=>"m",
"\u{1D427}"=>"n",
"\u{1D428}"=>"o",
"\u{1D429}"=>"p",
"\u{1D42A}"=>"q",
"\u{1D42B}"=>"r",
"\u{1D42C}"=>"s",
"\u{1D42D}"=>"t",
"\u{1D42E}"=>"u",
"\u{1D42F}"=>"v",
"\u{1D430}"=>"w",
"\u{1D431}"=>"x",
"\u{1D432}"=>"y",
"\u{1D433}"=>"z",
"\u{1D434}"=>"A",
"\u{1D435}"=>"B",
"\u{1D436}"=>"C",
"\u{1D437}"=>"D",
"\u{1D438}"=>"E",
"\u{1D439}"=>"F",
"\u{1D43A}"=>"G",
"\u{1D43B}"=>"H",
"\u{1D43C}"=>"I",
"\u{1D43D}"=>"J",
"\u{1D43E}"=>"K",
"\u{1D43F}"=>"L",
"\u{1D440}"=>"M",
"\u{1D441}"=>"N",
"\u{1D442}"=>"O",
"\u{1D443}"=>"P",
"\u{1D444}"=>"Q",
"\u{1D445}"=>"R",
"\u{1D446}"=>"S",
"\u{1D447}"=>"T",
"\u{1D448}"=>"U",
"\u{1D449}"=>"V",
"\u{1D44A}"=>"W",
"\u{1D44B}"=>"X",
"\u{1D44C}"=>"Y",
"\u{1D44D}"=>"Z",
"\u{1D44E}"=>"a",
"\u{1D44F}"=>"b",
"\u{1D450}"=>"c",
"\u{1D451}"=>"d",
"\u{1D452}"=>"e",
"\u{1D453}"=>"f",
"\u{1D454}"=>"g",
"\u{1D456}"=>"i",
"\u{1D457}"=>"j",
"\u{1D458}"=>"k",
"\u{1D459}"=>"l",
"\u{1D45A}"=>"m",
"\u{1D45B}"=>"n",
"\u{1D45C}"=>"o",
"\u{1D45D}"=>"p",
"\u{1D45E}"=>"q",
"\u{1D45F}"=>"r",
"\u{1D460}"=>"s",
"\u{1D461}"=>"t",
"\u{1D462}"=>"u",
"\u{1D463}"=>"v",
"\u{1D464}"=>"w",
"\u{1D465}"=>"x",
"\u{1D466}"=>"y",
"\u{1D467}"=>"z",
"\u{1D468}"=>"A",
"\u{1D469}"=>"B",
"\u{1D46A}"=>"C",
"\u{1D46B}"=>"D",
"\u{1D46C}"=>"E",
"\u{1D46D}"=>"F",
"\u{1D46E}"=>"G",
"\u{1D46F}"=>"H",
"\u{1D470}"=>"I",
"\u{1D471}"=>"J",
"\u{1D472}"=>"K",
"\u{1D473}"=>"L",
"\u{1D474}"=>"M",
"\u{1D475}"=>"N",
"\u{1D476}"=>"O",
"\u{1D477}"=>"P",
"\u{1D478}"=>"Q",
"\u{1D479}"=>"R",
"\u{1D47A}"=>"S",
"\u{1D47B}"=>"T",
"\u{1D47C}"=>"U",
"\u{1D47D}"=>"V",
"\u{1D47E}"=>"W",
"\u{1D47F}"=>"X",
"\u{1D480}"=>"Y",
"\u{1D481}"=>"Z",
"\u{1D482}"=>"a",
"\u{1D483}"=>"b",
"\u{1D484}"=>"c",
"\u{1D485}"=>"d",
"\u{1D486}"=>"e",
"\u{1D487}"=>"f",
"\u{1D488}"=>"g",
"\u{1D489}"=>"h",
"\u{1D48A}"=>"i",
"\u{1D48B}"=>"j",
"\u{1D48C}"=>"k",
"\u{1D48D}"=>"l",
"\u{1D48E}"=>"m",
"\u{1D48F}"=>"n",
"\u{1D490}"=>"o",
"\u{1D491}"=>"p",
"\u{1D492}"=>"q",
"\u{1D493}"=>"r",
"\u{1D494}"=>"s",
"\u{1D495}"=>"t",
"\u{1D496}"=>"u",
"\u{1D497}"=>"v",
"\u{1D498}"=>"w",
"\u{1D499}"=>"x",
"\u{1D49A}"=>"y",
"\u{1D49B}"=>"z",
"\u{1D49C}"=>"A",
"\u{1D49E}"=>"C",
"\u{1D49F}"=>"D",
"\u{1D4A2}"=>"G",
"\u{1D4A5}"=>"J",
"\u{1D4A6}"=>"K",
"\u{1D4A9}"=>"N",
"\u{1D4AA}"=>"O",
"\u{1D4AB}"=>"P",
"\u{1D4AC}"=>"Q",
"\u{1D4AE}"=>"S",
"\u{1D4AF}"=>"T",
"\u{1D4B0}"=>"U",
"\u{1D4B1}"=>"V",
"\u{1D4B2}"=>"W",
"\u{1D4B3}"=>"X",
"\u{1D4B4}"=>"Y",
"\u{1D4B5}"=>"Z",
"\u{1D4B6}"=>"a",
"\u{1D4B7}"=>"b",
"\u{1D4B8}"=>"c",
"\u{1D4B9}"=>"d",
"\u{1D4BB}"=>"f",
"\u{1D4BD}"=>"h",
"\u{1D4BE}"=>"i",
"\u{1D4BF}"=>"j",
"\u{1D4C0}"=>"k",
"\u{1D4C1}"=>"l",
"\u{1D4C2}"=>"m",
"\u{1D4C3}"=>"n",
"\u{1D4C5}"=>"p",
"\u{1D4C6}"=>"q",
"\u{1D4C7}"=>"r",
"\u{1D4C8}"=>"s",
"\u{1D4C9}"=>"t",
"\u{1D4CA}"=>"u",
"\u{1D4CB}"=>"v",
"\u{1D4CC}"=>"w",
"\u{1D4CD}"=>"x",
"\u{1D4CE}"=>"y",
"\u{1D4CF}"=>"z",
"\u{1D4D0}"=>"A",
"\u{1D4D1}"=>"B",
"\u{1D4D2}"=>"C",
"\u{1D4D3}"=>"D",
"\u{1D4D4}"=>"E",
"\u{1D4D5}"=>"F",
"\u{1D4D6}"=>"G",
"\u{1D4D7}"=>"H",
"\u{1D4D8}"=>"I",
"\u{1D4D9}"=>"J",
"\u{1D4DA}"=>"K",
"\u{1D4DB}"=>"L",
"\u{1D4DC}"=>"M",
"\u{1D4DD}"=>"N",
"\u{1D4DE}"=>"O",
"\u{1D4DF}"=>"P",
"\u{1D4E0}"=>"Q",
"\u{1D4E1}"=>"R",
"\u{1D4E2}"=>"S",
"\u{1D4E3}"=>"T",
"\u{1D4E4}"=>"U",
"\u{1D4E5}"=>"V",
"\u{1D4E6}"=>"W",
"\u{1D4E7}"=>"X",
"\u{1D4E8}"=>"Y",
"\u{1D4E9}"=>"Z",
"\u{1D4EA}"=>"a",
"\u{1D4EB}"=>"b",
"\u{1D4EC}"=>"c",
"\u{1D4ED}"=>"d",
"\u{1D4EE}"=>"e",
"\u{1D4EF}"=>"f",
"\u{1D4F0}"=>"g",
"\u{1D4F1}"=>"h",
"\u{1D4F2}"=>"i",
"\u{1D4F3}"=>"j",
"\u{1D4F4}"=>"k",
"\u{1D4F5}"=>"l",
"\u{1D4F6}"=>"m",
"\u{1D4F7}"=>"n",
"\u{1D4F8}"=>"o",
"\u{1D4F9}"=>"p",
"\u{1D4FA}"=>"q",
"\u{1D4FB}"=>"r",
"\u{1D4FC}"=>"s",
"\u{1D4FD}"=>"t",
"\u{1D4FE}"=>"u",
"\u{1D4FF}"=>"v",
"\u{1D500}"=>"w",
"\u{1D501}"=>"x",
"\u{1D502}"=>"y",
"\u{1D503}"=>"z",
"\u{1D504}"=>"A",
"\u{1D505}"=>"B",
"\u{1D507}"=>"D",
"\u{1D508}"=>"E",
"\u{1D509}"=>"F",
"\u{1D50A}"=>"G",
"\u{1D50D}"=>"J",
"\u{1D50E}"=>"K",
"\u{1D50F}"=>"L",
"\u{1D510}"=>"M",
"\u{1D511}"=>"N",
"\u{1D512}"=>"O",
"\u{1D513}"=>"P",
"\u{1D514}"=>"Q",
"\u{1D516}"=>"S",
"\u{1D517}"=>"T",
"\u{1D518}"=>"U",
"\u{1D519}"=>"V",
"\u{1D51A}"=>"W",
"\u{1D51B}"=>"X",
"\u{1D51C}"=>"Y",
"\u{1D51E}"=>"a",
"\u{1D51F}"=>"b",
"\u{1D520}"=>"c",
"\u{1D521}"=>"d",
"\u{1D522}"=>"e",
"\u{1D523}"=>"f",
"\u{1D524}"=>"g",
"\u{1D525}"=>"h",
"\u{1D526}"=>"i",
"\u{1D527}"=>"j",
"\u{1D528}"=>"k",
"\u{1D529}"=>"l",
"\u{1D52A}"=>"m",
"\u{1D52B}"=>"n",
"\u{1D52C}"=>"o",
"\u{1D52D}"=>"p",
"\u{1D52E}"=>"q",
"\u{1D52F}"=>"r",
"\u{1D530}"=>"s",
"\u{1D531}"=>"t",
"\u{1D532}"=>"u",
"\u{1D533}"=>"v",
"\u{1D534}"=>"w",
"\u{1D535}"=>"x",
"\u{1D536}"=>"y",
"\u{1D537}"=>"z",
"\u{1D538}"=>"A",
"\u{1D539}"=>"B",
"\u{1D53B}"=>"D",
"\u{1D53C}"=>"E",
"\u{1D53D}"=>"F",
"\u{1D53E}"=>"G",
"\u{1D540}"=>"I",
"\u{1D541}"=>"J",
"\u{1D542}"=>"K",
"\u{1D543}"=>"L",
"\u{1D544}"=>"M",
"\u{1D546}"=>"O",
"\u{1D54A}"=>"S",
"\u{1D54B}"=>"T",
"\u{1D54C}"=>"U",
"\u{1D54D}"=>"V",
"\u{1D54E}"=>"W",
"\u{1D54F}"=>"X",
"\u{1D550}"=>"Y",
"\u{1D552}"=>"a",
"\u{1D553}"=>"b",
"\u{1D554}"=>"c",
"\u{1D555}"=>"d",
"\u{1D556}"=>"e",
"\u{1D557}"=>"f",
"\u{1D558}"=>"g",
"\u{1D559}"=>"h",
"\u{1D55A}"=>"i",
"\u{1D55B}"=>"j",
"\u{1D55C}"=>"k",
"\u{1D55D}"=>"l",
"\u{1D55E}"=>"m",
"\u{1D55F}"=>"n",
"\u{1D560}"=>"o",
"\u{1D561}"=>"p",
"\u{1D562}"=>"q",
"\u{1D563}"=>"r",
"\u{1D564}"=>"s",
"\u{1D565}"=>"t",
"\u{1D566}"=>"u",
"\u{1D567}"=>"v",
"\u{1D568}"=>"w",
"\u{1D569}"=>"x",
"\u{1D56A}"=>"y",
"\u{1D56B}"=>"z",
"\u{1D56C}"=>"A",
"\u{1D56D}"=>"B",
"\u{1D56E}"=>"C",
"\u{1D56F}"=>"D",
"\u{1D570}"=>"E",
"\u{1D571}"=>"F",
"\u{1D572}"=>"G",
"\u{1D573}"=>"H",
"\u{1D574}"=>"I",
"\u{1D575}"=>"J",
"\u{1D576}"=>"K",
"\u{1D577}"=>"L",
"\u{1D578}"=>"M",
"\u{1D579}"=>"N",
"\u{1D57A}"=>"O",
"\u{1D57B}"=>"P",
"\u{1D57C}"=>"Q",
"\u{1D57D}"=>"R",
"\u{1D57E}"=>"S",
"\u{1D57F}"=>"T",
"\u{1D580}"=>"U",
"\u{1D581}"=>"V",
"\u{1D582}"=>"W",
"\u{1D583}"=>"X",
"\u{1D584}"=>"Y",
"\u{1D585}"=>"Z",
"\u{1D586}"=>"a",
"\u{1D587}"=>"b",
"\u{1D588}"=>"c",
"\u{1D589}"=>"d",
"\u{1D58A}"=>"e",
"\u{1D58B}"=>"f",
"\u{1D58C}"=>"g",
"\u{1D58D}"=>"h",
"\u{1D58E}"=>"i",
"\u{1D58F}"=>"j",
"\u{1D590}"=>"k",
"\u{1D591}"=>"l",
"\u{1D592}"=>"m",
"\u{1D593}"=>"n",
"\u{1D594}"=>"o",
"\u{1D595}"=>"p",
"\u{1D596}"=>"q",
"\u{1D597}"=>"r",
"\u{1D598}"=>"s",
"\u{1D599}"=>"t",
"\u{1D59A}"=>"u",
"\u{1D59B}"=>"v",
"\u{1D59C}"=>"w",
"\u{1D59D}"=>"x",
"\u{1D59E}"=>"y",
"\u{1D59F}"=>"z",
"\u{1D5A0}"=>"A",
"\u{1D5A1}"=>"B",
"\u{1D5A2}"=>"C",
"\u{1D5A3}"=>"D",
"\u{1D5A4}"=>"E",
"\u{1D5A5}"=>"F",
"\u{1D5A6}"=>"G",
"\u{1D5A7}"=>"H",
"\u{1D5A8}"=>"I",
"\u{1D5A9}"=>"J",
"\u{1D5AA}"=>"K",
"\u{1D5AB}"=>"L",
"\u{1D5AC}"=>"M",
"\u{1D5AD}"=>"N",
"\u{1D5AE}"=>"O",
"\u{1D5AF}"=>"P",
"\u{1D5B0}"=>"Q",
"\u{1D5B1}"=>"R",
"\u{1D5B2}"=>"S",
"\u{1D5B3}"=>"T",
"\u{1D5B4}"=>"U",
"\u{1D5B5}"=>"V",
"\u{1D5B6}"=>"W",
"\u{1D5B7}"=>"X",
"\u{1D5B8}"=>"Y",
"\u{1D5B9}"=>"Z",
"\u{1D5BA}"=>"a",
"\u{1D5BB}"=>"b",
"\u{1D5BC}"=>"c",
"\u{1D5BD}"=>"d",
"\u{1D5BE}"=>"e",
"\u{1D5BF}"=>"f",
"\u{1D5C0}"=>"g",
"\u{1D5C1}"=>"h",
"\u{1D5C2}"=>"i",
"\u{1D5C3}"=>"j",
"\u{1D5C4}"=>"k",
"\u{1D5C5}"=>"l",
"\u{1D5C6}"=>"m",
"\u{1D5C7}"=>"n",
"\u{1D5C8}"=>"o",
"\u{1D5C9}"=>"p",
"\u{1D5CA}"=>"q",
"\u{1D5CB}"=>"r",
"\u{1D5CC}"=>"s",
"\u{1D5CD}"=>"t",
"\u{1D5CE}"=>"u",
"\u{1D5CF}"=>"v",
"\u{1D5D0}"=>"w",
"\u{1D5D1}"=>"x",
"\u{1D5D2}"=>"y",
"\u{1D5D3}"=>"z",
"\u{1D5D4}"=>"A",
"\u{1D5D5}"=>"B",
"\u{1D5D6}"=>"C",
"\u{1D5D7}"=>"D",
"\u{1D5D8}"=>"E",
"\u{1D5D9}"=>"F",
"\u{1D5DA}"=>"G",
"\u{1D5DB}"=>"H",
"\u{1D5DC}"=>"I",
"\u{1D5DD}"=>"J",
"\u{1D5DE}"=>"K",
"\u{1D5DF}"=>"L",
"\u{1D5E0}"=>"M",
"\u{1D5E1}"=>"N",
"\u{1D5E2}"=>"O",
"\u{1D5E3}"=>"P",
"\u{1D5E4}"=>"Q",
"\u{1D5E5}"=>"R",
"\u{1D5E6}"=>"S",
"\u{1D5E7}"=>"T",
"\u{1D5E8}"=>"U",
"\u{1D5E9}"=>"V",
"\u{1D5EA}"=>"W",
"\u{1D5EB}"=>"X",
"\u{1D5EC}"=>"Y",
"\u{1D5ED}"=>"Z",
"\u{1D5EE}"=>"a",
"\u{1D5EF}"=>"b",
"\u{1D5F0}"=>"c",
"\u{1D5F1}"=>"d",
"\u{1D5F2}"=>"e",
"\u{1D5F3}"=>"f",
"\u{1D5F4}"=>"g",
"\u{1D5F5}"=>"h",
"\u{1D5F6}"=>"i",
"\u{1D5F7}"=>"j",
"\u{1D5F8}"=>"k",
"\u{1D5F9}"=>"l",
"\u{1D5FA}"=>"m",
"\u{1D5FB}"=>"n",
"\u{1D5FC}"=>"o",
"\u{1D5FD}"=>"p",
"\u{1D5FE}"=>"q",
"\u{1D5FF}"=>"r",
"\u{1D600}"=>"s",
"\u{1D601}"=>"t",
"\u{1D602}"=>"u",
"\u{1D603}"=>"v",
"\u{1D604}"=>"w",
"\u{1D605}"=>"x",
"\u{1D606}"=>"y",
"\u{1D607}"=>"z",
"\u{1D608}"=>"A",
"\u{1D609}"=>"B",
"\u{1D60A}"=>"C",
"\u{1D60B}"=>"D",
"\u{1D60C}"=>"E",
"\u{1D60D}"=>"F",
"\u{1D60E}"=>"G",
"\u{1D60F}"=>"H",
"\u{1D610}"=>"I",
"\u{1D611}"=>"J",
"\u{1D612}"=>"K",
"\u{1D613}"=>"L",
"\u{1D614}"=>"M",
"\u{1D615}"=>"N",
"\u{1D616}"=>"O",
"\u{1D617}"=>"P",
"\u{1D618}"=>"Q",
"\u{1D619}"=>"R",
"\u{1D61A}"=>"S",
"\u{1D61B}"=>"T",
"\u{1D61C}"=>"U",
"\u{1D61D}"=>"V",
"\u{1D61E}"=>"W",
"\u{1D61F}"=>"X",
"\u{1D620}"=>"Y",
"\u{1D621}"=>"Z",
"\u{1D622}"=>"a",
"\u{1D623}"=>"b",
"\u{1D624}"=>"c",
"\u{1D625}"=>"d",
"\u{1D626}"=>"e",
"\u{1D627}"=>"f",
"\u{1D628}"=>"g",
"\u{1D629}"=>"h",
"\u{1D62A}"=>"i",
"\u{1D62B}"=>"j",
"\u{1D62C}"=>"k",
"\u{1D62D}"=>"l",
"\u{1D62E}"=>"m",
"\u{1D62F}"=>"n",
"\u{1D630}"=>"o",
"\u{1D631}"=>"p",
"\u{1D632}"=>"q",
"\u{1D633}"=>"r",
"\u{1D634}"=>"s",
"\u{1D635}"=>"t",
"\u{1D636}"=>"u",
"\u{1D637}"=>"v",
"\u{1D638}"=>"w",
"\u{1D639}"=>"x",
"\u{1D63A}"=>"y",
"\u{1D63B}"=>"z",
"\u{1D63C}"=>"A",
"\u{1D63D}"=>"B",
"\u{1D63E}"=>"C",
"\u{1D63F}"=>"D",
"\u{1D640}"=>"E",
"\u{1D641}"=>"F",
"\u{1D642}"=>"G",
"\u{1D643}"=>"H",
"\u{1D644}"=>"I",
"\u{1D645}"=>"J",
"\u{1D646}"=>"K",
"\u{1D647}"=>"L",
"\u{1D648}"=>"M",
"\u{1D649}"=>"N",
"\u{1D64A}"=>"O",
"\u{1D64B}"=>"P",
"\u{1D64C}"=>"Q",
"\u{1D64D}"=>"R",
"\u{1D64E}"=>"S",
"\u{1D64F}"=>"T",
"\u{1D650}"=>"U",
"\u{1D651}"=>"V",
"\u{1D652}"=>"W",
"\u{1D653}"=>"X",
"\u{1D654}"=>"Y",
"\u{1D655}"=>"Z",
"\u{1D656}"=>"a",
"\u{1D657}"=>"b",
"\u{1D658}"=>"c",
"\u{1D659}"=>"d",
"\u{1D65A}"=>"e",
"\u{1D65B}"=>"f",
"\u{1D65C}"=>"g",
"\u{1D65D}"=>"h",
"\u{1D65E}"=>"i",
"\u{1D65F}"=>"j",
"\u{1D660}"=>"k",
"\u{1D661}"=>"l",
"\u{1D662}"=>"m",
"\u{1D663}"=>"n",
"\u{1D664}"=>"o",
"\u{1D665}"=>"p",
"\u{1D666}"=>"q",
"\u{1D667}"=>"r",
"\u{1D668}"=>"s",
"\u{1D669}"=>"t",
"\u{1D66A}"=>"u",
"\u{1D66B}"=>"v",
"\u{1D66C}"=>"w",
"\u{1D66D}"=>"x",
"\u{1D66E}"=>"y",
"\u{1D66F}"=>"z",
"\u{1D670}"=>"A",
"\u{1D671}"=>"B",
"\u{1D672}"=>"C",
"\u{1D673}"=>"D",
"\u{1D674}"=>"E",
"\u{1D675}"=>"F",
"\u{1D676}"=>"G",
"\u{1D677}"=>"H",
"\u{1D678}"=>"I",
"\u{1D679}"=>"J",
"\u{1D67A}"=>"K",
"\u{1D67B}"=>"L",
"\u{1D67C}"=>"M",
"\u{1D67D}"=>"N",
"\u{1D67E}"=>"O",
"\u{1D67F}"=>"P",
"\u{1D680}"=>"Q",
"\u{1D681}"=>"R",
"\u{1D682}"=>"S",
"\u{1D683}"=>"T",
"\u{1D684}"=>"U",
"\u{1D685}"=>"V",
"\u{1D686}"=>"W",
"\u{1D687}"=>"X",
"\u{1D688}"=>"Y",
"\u{1D689}"=>"Z",
"\u{1D68A}"=>"a",
"\u{1D68B}"=>"b",
"\u{1D68C}"=>"c",
"\u{1D68D}"=>"d",
"\u{1D68E}"=>"e",
"\u{1D68F}"=>"f",
"\u{1D690}"=>"g",
"\u{1D691}"=>"h",
"\u{1D692}"=>"i",
"\u{1D693}"=>"j",
"\u{1D694}"=>"k",
"\u{1D695}"=>"l",
"\u{1D696}"=>"m",
"\u{1D697}"=>"n",
"\u{1D698}"=>"o",
"\u{1D699}"=>"p",
"\u{1D69A}"=>"q",
"\u{1D69B}"=>"r",
"\u{1D69C}"=>"s",
"\u{1D69D}"=>"t",
"\u{1D69E}"=>"u",
"\u{1D69F}"=>"v",
"\u{1D6A0}"=>"w",
"\u{1D6A1}"=>"x",
"\u{1D6A2}"=>"y",
"\u{1D6A3}"=>"z",
"\u{1D6A4}"=>"\u0131",
"\u{1D6A5}"=>"\u0237",
"\u{1D6A8}"=>"\u0391",
"\u{1D6A9}"=>"\u0392",
"\u{1D6AA}"=>"\u0393",
"\u{1D6AB}"=>"\u0394",
"\u{1D6AC}"=>"\u0395",
"\u{1D6AD}"=>"\u0396",
"\u{1D6AE}"=>"\u0397",
"\u{1D6AF}"=>"\u0398",
"\u{1D6B0}"=>"\u0399",
"\u{1D6B1}"=>"\u039A",
"\u{1D6B2}"=>"\u039B",
"\u{1D6B3}"=>"\u039C",
"\u{1D6B4}"=>"\u039D",
"\u{1D6B5}"=>"\u039E",
"\u{1D6B6}"=>"\u039F",
"\u{1D6B7}"=>"\u03A0",
"\u{1D6B8}"=>"\u03A1",
"\u{1D6B9}"=>"\u0398",
"\u{1D6BA}"=>"\u03A3",
"\u{1D6BB}"=>"\u03A4",
"\u{1D6BC}"=>"\u03A5",
"\u{1D6BD}"=>"\u03A6",
"\u{1D6BE}"=>"\u03A7",
"\u{1D6BF}"=>"\u03A8",
"\u{1D6C0}"=>"\u03A9",
"\u{1D6C1}"=>"\u2207",
"\u{1D6C2}"=>"\u03B1",
"\u{1D6C3}"=>"\u03B2",
"\u{1D6C4}"=>"\u03B3",
"\u{1D6C5}"=>"\u03B4",
"\u{1D6C6}"=>"\u03B5",
"\u{1D6C7}"=>"\u03B6",
"\u{1D6C8}"=>"\u03B7",
"\u{1D6C9}"=>"\u03B8",
"\u{1D6CA}"=>"\u03B9",
"\u{1D6CB}"=>"\u03BA",
"\u{1D6CC}"=>"\u03BB",
"\u{1D6CD}"=>"\u03BC",
"\u{1D6CE}"=>"\u03BD",
"\u{1D6CF}"=>"\u03BE",
"\u{1D6D0}"=>"\u03BF",
"\u{1D6D1}"=>"\u03C0",
"\u{1D6D2}"=>"\u03C1",
"\u{1D6D3}"=>"\u03C2",
"\u{1D6D4}"=>"\u03C3",
"\u{1D6D5}"=>"\u03C4",
"\u{1D6D6}"=>"\u03C5",
"\u{1D6D7}"=>"\u03C6",
"\u{1D6D8}"=>"\u03C7",
"\u{1D6D9}"=>"\u03C8",
"\u{1D6DA}"=>"\u03C9",
"\u{1D6DB}"=>"\u2202",
"\u{1D6DC}"=>"\u03B5",
"\u{1D6DD}"=>"\u03B8",
"\u{1D6DE}"=>"\u03BA",
"\u{1D6DF}"=>"\u03C6",
"\u{1D6E0}"=>"\u03C1",
"\u{1D6E1}"=>"\u03C0",
"\u{1D6E2}"=>"\u0391",
"\u{1D6E3}"=>"\u0392",
"\u{1D6E4}"=>"\u0393",
"\u{1D6E5}"=>"\u0394",
"\u{1D6E6}"=>"\u0395",
"\u{1D6E7}"=>"\u0396",
"\u{1D6E8}"=>"\u0397",
"\u{1D6E9}"=>"\u0398",
"\u{1D6EA}"=>"\u0399",
"\u{1D6EB}"=>"\u039A",
"\u{1D6EC}"=>"\u039B",
"\u{1D6ED}"=>"\u039C",
"\u{1D6EE}"=>"\u039D",
"\u{1D6EF}"=>"\u039E",
"\u{1D6F0}"=>"\u039F",
"\u{1D6F1}"=>"\u03A0",
"\u{1D6F2}"=>"\u03A1",
"\u{1D6F3}"=>"\u0398",
"\u{1D6F4}"=>"\u03A3",
"\u{1D6F5}"=>"\u03A4",
"\u{1D6F6}"=>"\u03A5",
"\u{1D6F7}"=>"\u03A6",
"\u{1D6F8}"=>"\u03A7",
"\u{1D6F9}"=>"\u03A8",
"\u{1D6FA}"=>"\u03A9",
"\u{1D6FB}"=>"\u2207",
"\u{1D6FC}"=>"\u03B1",
"\u{1D6FD}"=>"\u03B2",
"\u{1D6FE}"=>"\u03B3",
"\u{1D6FF}"=>"\u03B4",
"\u{1D700}"=>"\u03B5",
"\u{1D701}"=>"\u03B6",
"\u{1D702}"=>"\u03B7",
"\u{1D703}"=>"\u03B8",
"\u{1D704}"=>"\u03B9",
"\u{1D705}"=>"\u03BA",
"\u{1D706}"=>"\u03BB",
"\u{1D707}"=>"\u03BC",
"\u{1D708}"=>"\u03BD",
"\u{1D709}"=>"\u03BE",
"\u{1D70A}"=>"\u03BF",
"\u{1D70B}"=>"\u03C0",
"\u{1D70C}"=>"\u03C1",
"\u{1D70D}"=>"\u03C2",
"\u{1D70E}"=>"\u03C3",
"\u{1D70F}"=>"\u03C4",
"\u{1D710}"=>"\u03C5",
"\u{1D711}"=>"\u03C6",
"\u{1D712}"=>"\u03C7",
"\u{1D713}"=>"\u03C8",
"\u{1D714}"=>"\u03C9",
"\u{1D715}"=>"\u2202",
"\u{1D716}"=>"\u03B5",
"\u{1D717}"=>"\u03B8",
"\u{1D718}"=>"\u03BA",
"\u{1D719}"=>"\u03C6",
"\u{1D71A}"=>"\u03C1",
"\u{1D71B}"=>"\u03C0",
"\u{1D71C}"=>"\u0391",
"\u{1D71D}"=>"\u0392",
"\u{1D71E}"=>"\u0393",
"\u{1D71F}"=>"\u0394",
"\u{1D720}"=>"\u0395",
"\u{1D721}"=>"\u0396",
"\u{1D722}"=>"\u0397",
"\u{1D723}"=>"\u0398",
"\u{1D724}"=>"\u0399",
"\u{1D725}"=>"\u039A",
"\u{1D726}"=>"\u039B",
"\u{1D727}"=>"\u039C",
"\u{1D728}"=>"\u039D",
"\u{1D729}"=>"\u039E",
"\u{1D72A}"=>"\u039F",
"\u{1D72B}"=>"\u03A0",
"\u{1D72C}"=>"\u03A1",
"\u{1D72D}"=>"\u0398",
"\u{1D72E}"=>"\u03A3",
"\u{1D72F}"=>"\u03A4",
"\u{1D730}"=>"\u03A5",
"\u{1D731}"=>"\u03A6",
"\u{1D732}"=>"\u03A7",
"\u{1D733}"=>"\u03A8",
"\u{1D734}"=>"\u03A9",
"\u{1D735}"=>"\u2207",
"\u{1D736}"=>"\u03B1",
"\u{1D737}"=>"\u03B2",
"\u{1D738}"=>"\u03B3",
"\u{1D739}"=>"\u03B4",
"\u{1D73A}"=>"\u03B5",
"\u{1D73B}"=>"\u03B6",
"\u{1D73C}"=>"\u03B7",
"\u{1D73D}"=>"\u03B8",
"\u{1D73E}"=>"\u03B9",
"\u{1D73F}"=>"\u03BA",
"\u{1D740}"=>"\u03BB",
"\u{1D741}"=>"\u03BC",
"\u{1D742}"=>"\u03BD",
"\u{1D743}"=>"\u03BE",
"\u{1D744}"=>"\u03BF",
"\u{1D745}"=>"\u03C0",
"\u{1D746}"=>"\u03C1",
"\u{1D747}"=>"\u03C2",
"\u{1D748}"=>"\u03C3",
"\u{1D749}"=>"\u03C4",
"\u{1D74A}"=>"\u03C5",
"\u{1D74B}"=>"\u03C6",
"\u{1D74C}"=>"\u03C7",
"\u{1D74D}"=>"\u03C8",
"\u{1D74E}"=>"\u03C9",
"\u{1D74F}"=>"\u2202",
"\u{1D750}"=>"\u03B5",
"\u{1D751}"=>"\u03B8",
"\u{1D752}"=>"\u03BA",
"\u{1D753}"=>"\u03C6",
"\u{1D754}"=>"\u03C1",
"\u{1D755}"=>"\u03C0",
"\u{1D756}"=>"\u0391",
"\u{1D757}"=>"\u0392",
"\u{1D758}"=>"\u0393",
"\u{1D759}"=>"\u0394",
"\u{1D75A}"=>"\u0395",
"\u{1D75B}"=>"\u0396",
"\u{1D75C}"=>"\u0397",
"\u{1D75D}"=>"\u0398",
"\u{1D75E}"=>"\u0399",
"\u{1D75F}"=>"\u039A",
"\u{1D760}"=>"\u039B",
"\u{1D761}"=>"\u039C",
"\u{1D762}"=>"\u039D",
"\u{1D763}"=>"\u039E",
"\u{1D764}"=>"\u039F",
"\u{1D765}"=>"\u03A0",
"\u{1D766}"=>"\u03A1",
"\u{1D767}"=>"\u0398",
"\u{1D768}"=>"\u03A3",
"\u{1D769}"=>"\u03A4",
"\u{1D76A}"=>"\u03A5",
"\u{1D76B}"=>"\u03A6",
"\u{1D76C}"=>"\u03A7",
"\u{1D76D}"=>"\u03A8",
"\u{1D76E}"=>"\u03A9",
"\u{1D76F}"=>"\u2207",
"\u{1D770}"=>"\u03B1",
"\u{1D771}"=>"\u03B2",
"\u{1D772}"=>"\u03B3",
"\u{1D773}"=>"\u03B4",
"\u{1D774}"=>"\u03B5",
"\u{1D775}"=>"\u03B6",
"\u{1D776}"=>"\u03B7",
"\u{1D777}"=>"\u03B8",
"\u{1D778}"=>"\u03B9",
"\u{1D779}"=>"\u03BA",
"\u{1D77A}"=>"\u03BB",
"\u{1D77B}"=>"\u03BC",
"\u{1D77C}"=>"\u03BD",
"\u{1D77D}"=>"\u03BE",
"\u{1D77E}"=>"\u03BF",
"\u{1D77F}"=>"\u03C0",
"\u{1D780}"=>"\u03C1",
"\u{1D781}"=>"\u03C2",
"\u{1D782}"=>"\u03C3",
"\u{1D783}"=>"\u03C4",
"\u{1D784}"=>"\u03C5",
"\u{1D785}"=>"\u03C6",
"\u{1D786}"=>"\u03C7",
"\u{1D787}"=>"\u03C8",
"\u{1D788}"=>"\u03C9",
"\u{1D789}"=>"\u2202",
"\u{1D78A}"=>"\u03B5",
"\u{1D78B}"=>"\u03B8",
"\u{1D78C}"=>"\u03BA",
"\u{1D78D}"=>"\u03C6",
"\u{1D78E}"=>"\u03C1",
"\u{1D78F}"=>"\u03C0",
"\u{1D790}"=>"\u0391",
"\u{1D791}"=>"\u0392",
"\u{1D792}"=>"\u0393",
"\u{1D793}"=>"\u0394",
"\u{1D794}"=>"\u0395",
"\u{1D795}"=>"\u0396",
"\u{1D796}"=>"\u0397",
"\u{1D797}"=>"\u0398",
"\u{1D798}"=>"\u0399",
"\u{1D799}"=>"\u039A",
"\u{1D79A}"=>"\u039B",
"\u{1D79B}"=>"\u039C",
"\u{1D79C}"=>"\u039D",
"\u{1D79D}"=>"\u039E",
"\u{1D79E}"=>"\u039F",
"\u{1D79F}"=>"\u03A0",
"\u{1D7A0}"=>"\u03A1",
"\u{1D7A1}"=>"\u0398",
"\u{1D7A2}"=>"\u03A3",
"\u{1D7A3}"=>"\u03A4",
"\u{1D7A4}"=>"\u03A5",
"\u{1D7A5}"=>"\u03A6",
"\u{1D7A6}"=>"\u03A7",
"\u{1D7A7}"=>"\u03A8",
"\u{1D7A8}"=>"\u03A9",
"\u{1D7A9}"=>"\u2207",
"\u{1D7AA}"=>"\u03B1",
"\u{1D7AB}"=>"\u03B2",
"\u{1D7AC}"=>"\u03B3",
"\u{1D7AD}"=>"\u03B4",
"\u{1D7AE}"=>"\u03B5",
"\u{1D7AF}"=>"\u03B6",
"\u{1D7B0}"=>"\u03B7",
"\u{1D7B1}"=>"\u03B8",
"\u{1D7B2}"=>"\u03B9",
"\u{1D7B3}"=>"\u03BA",
"\u{1D7B4}"=>"\u03BB",
"\u{1D7B5}"=>"\u03BC",
"\u{1D7B6}"=>"\u03BD",
"\u{1D7B7}"=>"\u03BE",
"\u{1D7B8}"=>"\u03BF",
"\u{1D7B9}"=>"\u03C0",
"\u{1D7BA}"=>"\u03C1",
"\u{1D7BB}"=>"\u03C2",
"\u{1D7BC}"=>"\u03C3",
"\u{1D7BD}"=>"\u03C4",
"\u{1D7BE}"=>"\u03C5",
"\u{1D7BF}"=>"\u03C6",
"\u{1D7C0}"=>"\u03C7",
"\u{1D7C1}"=>"\u03C8",
"\u{1D7C2}"=>"\u03C9",
"\u{1D7C3}"=>"\u2202",
"\u{1D7C4}"=>"\u03B5",
"\u{1D7C5}"=>"\u03B8",
"\u{1D7C6}"=>"\u03BA",
"\u{1D7C7}"=>"\u03C6",
"\u{1D7C8}"=>"\u03C1",
"\u{1D7C9}"=>"\u03C0",
"\u{1D7CA}"=>"\u03DC",
"\u{1D7CB}"=>"\u03DD",
"\u{1D7CE}"=>"0",
"\u{1D7CF}"=>"1",
"\u{1D7D0}"=>"2",
"\u{1D7D1}"=>"3",
"\u{1D7D2}"=>"4",
"\u{1D7D3}"=>"5",
"\u{1D7D4}"=>"6",
"\u{1D7D5}"=>"7",
"\u{1D7D6}"=>"8",
"\u{1D7D7}"=>"9",
"\u{1D7D8}"=>"0",
"\u{1D7D9}"=>"1",
"\u{1D7DA}"=>"2",
"\u{1D7DB}"=>"3",
"\u{1D7DC}"=>"4",
"\u{1D7DD}"=>"5",
"\u{1D7DE}"=>"6",
"\u{1D7DF}"=>"7",
"\u{1D7E0}"=>"8",
"\u{1D7E1}"=>"9",
"\u{1D7E2}"=>"0",
"\u{1D7E3}"=>"1",
"\u{1D7E4}"=>"2",
"\u{1D7E5}"=>"3",
"\u{1D7E6}"=>"4",
"\u{1D7E7}"=>"5",
"\u{1D7E8}"=>"6",
"\u{1D7E9}"=>"7",
"\u{1D7EA}"=>"8",
"\u{1D7EB}"=>"9",
"\u{1D7EC}"=>"0",
"\u{1D7ED}"=>"1",
"\u{1D7EE}"=>"2",
"\u{1D7EF}"=>"3",
"\u{1D7F0}"=>"4",
"\u{1D7F1}"=>"5",
"\u{1D7F2}"=>"6",
"\u{1D7F3}"=>"7",
"\u{1D7F4}"=>"8",
"\u{1D7F5}"=>"9",
"\u{1D7F6}"=>"0",
"\u{1D7F7}"=>"1",
"\u{1D7F8}"=>"2",
"\u{1D7F9}"=>"3",
"\u{1D7FA}"=>"4",
"\u{1D7FB}"=>"5",
"\u{1D7FC}"=>"6",
"\u{1D7FD}"=>"7",
"\u{1D7FE}"=>"8",
"\u{1D7FF}"=>"9",
"\u{1E030}"=>"\u0430",
"\u{1E031}"=>"\u0431",
"\u{1E032}"=>"\u0432",
"\u{1E033}"=>"\u0433",
"\u{1E034}"=>"\u0434",
"\u{1E035}"=>"\u0435",
"\u{1E036}"=>"\u0436",
"\u{1E037}"=>"\u0437",
"\u{1E038}"=>"\u0438",
"\u{1E039}"=>"\u043A",
"\u{1E03A}"=>"\u043B",
"\u{1E03B}"=>"\u043C",
"\u{1E03C}"=>"\u043E",
"\u{1E03D}"=>"\u043F",
"\u{1E03E}"=>"\u0440",
"\u{1E03F}"=>"\u0441",
"\u{1E040}"=>"\u0442",
"\u{1E041}"=>"\u0443",
"\u{1E042}"=>"\u0444",
"\u{1E043}"=>"\u0445",
"\u{1E044}"=>"\u0446",
"\u{1E045}"=>"\u0447",
"\u{1E046}"=>"\u0448",
"\u{1E047}"=>"\u044B",
"\u{1E048}"=>"\u044D",
"\u{1E049}"=>"\u044E",
"\u{1E04A}"=>"\uA689",
"\u{1E04B}"=>"\u04D9",
"\u{1E04C}"=>"\u0456",
"\u{1E04D}"=>"\u0458",
"\u{1E04E}"=>"\u04E9",
"\u{1E04F}"=>"\u04AF",
"\u{1E050}"=>"\u04CF",
"\u{1E051}"=>"\u0430",
"\u{1E052}"=>"\u0431",
"\u{1E053}"=>"\u0432",
"\u{1E054}"=>"\u0433",
"\u{1E055}"=>"\u0434",
"\u{1E056}"=>"\u0435",
"\u{1E057}"=>"\u0436",
"\u{1E058}"=>"\u0437",
"\u{1E059}"=>"\u0438",
"\u{1E05A}"=>"\u043A",
"\u{1E05B}"=>"\u043B",
"\u{1E05C}"=>"\u043E",
"\u{1E05D}"=>"\u043F",
"\u{1E05E}"=>"\u0441",
"\u{1E05F}"=>"\u0443",
"\u{1E060}"=>"\u0444",
"\u{1E061}"=>"\u0445",
"\u{1E062}"=>"\u0446",
"\u{1E063}"=>"\u0447",
"\u{1E064}"=>"\u0448",
"\u{1E065}"=>"\u044A",
"\u{1E066}"=>"\u044B",
"\u{1E067}"=>"\u0491",
"\u{1E068}"=>"\u0456",
"\u{1E069}"=>"\u0455",
"\u{1E06A}"=>"\u045F",
"\u{1E06B}"=>"\u04AB",
"\u{1E06C}"=>"\uA651",
"\u{1E06D}"=>"\u04B1",
"\u{1EE00}"=>"\u0627",
"\u{1EE01}"=>"\u0628",
"\u{1EE02}"=>"\u062C",
"\u{1EE03}"=>"\u062F",
"\u{1EE05}"=>"\u0648",
"\u{1EE06}"=>"\u0632",
"\u{1EE07}"=>"\u062D",
"\u{1EE08}"=>"\u0637",
"\u{1EE09}"=>"\u064A",
"\u{1EE0A}"=>"\u0643",
"\u{1EE0B}"=>"\u0644",
"\u{1EE0C}"=>"\u0645",
"\u{1EE0D}"=>"\u0646",
"\u{1EE0E}"=>"\u0633",
"\u{1EE0F}"=>"\u0639",
"\u{1EE10}"=>"\u0641",
"\u{1EE11}"=>"\u0635",
"\u{1EE12}"=>"\u0642",
"\u{1EE13}"=>"\u0631",
"\u{1EE14}"=>"\u0634",
"\u{1EE15}"=>"\u062A",
"\u{1EE16}"=>"\u062B",
"\u{1EE17}"=>"\u062E",
"\u{1EE18}"=>"\u0630",
"\u{1EE19}"=>"\u0636",
"\u{1EE1A}"=>"\u0638",
"\u{1EE1B}"=>"\u063A",
"\u{1EE1C}"=>"\u066E",
"\u{1EE1D}"=>"\u06BA",
"\u{1EE1E}"=>"\u06A1",
"\u{1EE1F}"=>"\u066F",
"\u{1EE21}"=>"\u0628",
"\u{1EE22}"=>"\u062C",
"\u{1EE24}"=>"\u0647",
"\u{1EE27}"=>"\u062D",
"\u{1EE29}"=>"\u064A",
"\u{1EE2A}"=>"\u0643",
"\u{1EE2B}"=>"\u0644",
"\u{1EE2C}"=>"\u0645",
"\u{1EE2D}"=>"\u0646",
"\u{1EE2E}"=>"\u0633",
"\u{1EE2F}"=>"\u0639",
"\u{1EE30}"=>"\u0641",
"\u{1EE31}"=>"\u0635",
"\u{1EE32}"=>"\u0642",
"\u{1EE34}"=>"\u0634",
"\u{1EE35}"=>"\u062A",
"\u{1EE36}"=>"\u062B",
"\u{1EE37}"=>"\u062E",
"\u{1EE39}"=>"\u0636",
"\u{1EE3B}"=>"\u063A",
"\u{1EE42}"=>"\u062C",
"\u{1EE47}"=>"\u062D",
"\u{1EE49}"=>"\u064A",
"\u{1EE4B}"=>"\u0644",
"\u{1EE4D}"=>"\u0646",
"\u{1EE4E}"=>"\u0633",
"\u{1EE4F}"=>"\u0639",
"\u{1EE51}"=>"\u0635",
"\u{1EE52}"=>"\u0642",
"\u{1EE54}"=>"\u0634",
"\u{1EE57}"=>"\u062E",
"\u{1EE59}"=>"\u0636",
"\u{1EE5B}"=>"\u063A",
"\u{1EE5D}"=>"\u06BA",
"\u{1EE5F}"=>"\u066F",
"\u{1EE61}"=>"\u0628",
"\u{1EE62}"=>"\u062C",
"\u{1EE64}"=>"\u0647",
"\u{1EE67}"=>"\u062D",
"\u{1EE68}"=>"\u0637",
"\u{1EE69}"=>"\u064A",
"\u{1EE6A}"=>"\u0643",
"\u{1EE6C}"=>"\u0645",
"\u{1EE6D}"=>"\u0646",
"\u{1EE6E}"=>"\u0633",
"\u{1EE6F}"=>"\u0639",
"\u{1EE70}"=>"\u0641",
"\u{1EE71}"=>"\u0635",
"\u{1EE72}"=>"\u0642",
"\u{1EE74}"=>"\u0634",
"\u{1EE75}"=>"\u062A",
"\u{1EE76}"=>"\u062B",
"\u{1EE77}"=>"\u062E",
"\u{1EE79}"=>"\u0636",
"\u{1EE7A}"=>"\u0638",
"\u{1EE7B}"=>"\u063A",
"\u{1EE7C}"=>"\u066E",
"\u{1EE7E}"=>"\u06A1",
"\u{1EE80}"=>"\u0627",
"\u{1EE81}"=>"\u0628",
"\u{1EE82}"=>"\u062C",
"\u{1EE83}"=>"\u062F",
"\u{1EE84}"=>"\u0647",
"\u{1EE85}"=>"\u0648",
"\u{1EE86}"=>"\u0632",
"\u{1EE87}"=>"\u062D",
"\u{1EE88}"=>"\u0637",
"\u{1EE89}"=>"\u064A",
"\u{1EE8B}"=>"\u0644",
"\u{1EE8C}"=>"\u0645",
"\u{1EE8D}"=>"\u0646",
"\u{1EE8E}"=>"\u0633",
"\u{1EE8F}"=>"\u0639",
"\u{1EE90}"=>"\u0641",
"\u{1EE91}"=>"\u0635",
"\u{1EE92}"=>"\u0642",
"\u{1EE93}"=>"\u0631",
"\u{1EE94}"=>"\u0634",
"\u{1EE95}"=>"\u062A",
"\u{1EE96}"=>"\u062B",
"\u{1EE97}"=>"\u062E",
"\u{1EE98}"=>"\u0630",
"\u{1EE99}"=>"\u0636",
"\u{1EE9A}"=>"\u0638",
"\u{1EE9B}"=>"\u063A",
"\u{1EEA1}"=>"\u0628",
"\u{1EEA2}"=>"\u062C",
"\u{1EEA3}"=>"\u062F",
"\u{1EEA5}"=>"\u0648",
"\u{1EEA6}"=>"\u0632",
"\u{1EEA7}"=>"\u062D",
"\u{1EEA8}"=>"\u0637",
"\u{1EEA9}"=>"\u064A",
"\u{1EEAB}"=>"\u0644",
"\u{1EEAC}"=>"\u0645",
"\u{1EEAD}"=>"\u0646",
"\u{1EEAE}"=>"\u0633",
"\u{1EEAF}"=>"\u0639",
"\u{1EEB0}"=>"\u0641",
"\u{1EEB1}"=>"\u0635",
"\u{1EEB2}"=>"\u0642",
"\u{1EEB3}"=>"\u0631",
"\u{1EEB4}"=>"\u0634",
"\u{1EEB5}"=>"\u062A",
"\u{1EEB6}"=>"\u062B",
"\u{1EEB7}"=>"\u062E",
"\u{1EEB8}"=>"\u0630",
"\u{1EEB9}"=>"\u0636",
"\u{1EEBA}"=>"\u0638",
"\u{1EEBB}"=>"\u063A",
"\u{1F100}"=>"0.",
"\u{1F101}"=>"0,",
"\u{1F102}"=>"1,",
"\u{1F103}"=>"2,",
"\u{1F104}"=>"3,",
"\u{1F105}"=>"4,",
"\u{1F106}"=>"5,",
"\u{1F107}"=>"6,",
"\u{1F108}"=>"7,",
"\u{1F109}"=>"8,",
"\u{1F10A}"=>"9,",
"\u{1F110}"=>"(A)",
"\u{1F111}"=>"(B)",
"\u{1F112}"=>"(C)",
"\u{1F113}"=>"(D)",
"\u{1F114}"=>"(E)",
"\u{1F115}"=>"(F)",
"\u{1F116}"=>"(G)",
"\u{1F117}"=>"(H)",
"\u{1F118}"=>"(I)",
"\u{1F119}"=>"(J)",
"\u{1F11A}"=>"(K)",
"\u{1F11B}"=>"(L)",
"\u{1F11C}"=>"(M)",
"\u{1F11D}"=>"(N)",
"\u{1F11E}"=>"(O)",
"\u{1F11F}"=>"(P)",
"\u{1F120}"=>"(Q)",
"\u{1F121}"=>"(R)",
"\u{1F122}"=>"(S)",
"\u{1F123}"=>"(T)",
"\u{1F124}"=>"(U)",
"\u{1F125}"=>"(V)",
"\u{1F126}"=>"(W)",
"\u{1F127}"=>"(X)",
"\u{1F128}"=>"(Y)",
"\u{1F129}"=>"(Z)",
"\u{1F12A}"=>"\u3014S\u3015",
"\u{1F12B}"=>"C",
"\u{1F12C}"=>"R",
"\u{1F12D}"=>"CD",
"\u{1F12E}"=>"WZ",
"\u{1F130}"=>"A",
"\u{1F131}"=>"B",
"\u{1F132}"=>"C",
"\u{1F133}"=>"D",
"\u{1F134}"=>"E",
"\u{1F135}"=>"F",
"\u{1F136}"=>"G",
"\u{1F137}"=>"H",
"\u{1F138}"=>"I",
"\u{1F139}"=>"J",
"\u{1F13A}"=>"K",
"\u{1F13B}"=>"L",
"\u{1F13C}"=>"M",
"\u{1F13D}"=>"N",
"\u{1F13E}"=>"O",
"\u{1F13F}"=>"P",
"\u{1F140}"=>"Q",
"\u{1F141}"=>"R",
"\u{1F142}"=>"S",
"\u{1F143}"=>"T",
"\u{1F144}"=>"U",
"\u{1F145}"=>"V",
"\u{1F146}"=>"W",
"\u{1F147}"=>"X",
"\u{1F148}"=>"Y",
"\u{1F149}"=>"Z",
"\u{1F14A}"=>"HV",
"\u{1F14B}"=>"MV",
"\u{1F14C}"=>"SD",
"\u{1F14D}"=>"SS",
"\u{1F14E}"=>"PPV",
"\u{1F14F}"=>"WC",
"\u{1F16A}"=>"MC",
"\u{1F16B}"=>"MD",
"\u{1F16C}"=>"MR",
"\u{1F190}"=>"DJ",
"\u{1F200}"=>"\u307B\u304B",
"\u{1F201}"=>"\u30B3\u30B3",
"\u{1F202}"=>"\u30B5",
"\u{1F210}"=>"\u624B",
"\u{1F211}"=>"\u5B57",
"\u{1F212}"=>"\u53CC",
"\u{1F213}"=>"\u30C7",
"\u{1F214}"=>"\u4E8C",
"\u{1F215}"=>"\u591A",
"\u{1F216}"=>"\u89E3",
"\u{1F217}"=>"\u5929",
"\u{1F218}"=>"\u4EA4",
"\u{1F219}"=>"\u6620",
"\u{1F21A}"=>"\u7121",
"\u{1F21B}"=>"\u6599",
"\u{1F21C}"=>"\u524D",
"\u{1F21D}"=>"\u5F8C",
"\u{1F21E}"=>"\u518D",
"\u{1F21F}"=>"\u65B0",
"\u{1F220}"=>"\u521D",
"\u{1F221}"=>"\u7D42",
"\u{1F222}"=>"\u751F",
"\u{1F223}"=>"\u8CA9",
"\u{1F224}"=>"\u58F0",
"\u{1F225}"=>"\u5439",
"\u{1F226}"=>"\u6F14",
"\u{1F227}"=>"\u6295",
"\u{1F228}"=>"\u6355",
"\u{1F229}"=>"\u4E00",
"\u{1F22A}"=>"\u4E09",
"\u{1F22B}"=>"\u904A",
"\u{1F22C}"=>"\u5DE6",
"\u{1F22D}"=>"\u4E2D",
"\u{1F22E}"=>"\u53F3",
"\u{1F22F}"=>"\u6307",
"\u{1F230}"=>"\u8D70",
"\u{1F231}"=>"\u6253",
"\u{1F232}"=>"\u7981",
"\u{1F233}"=>"\u7A7A",
"\u{1F234}"=>"\u5408",
"\u{1F235}"=>"\u6E80",
"\u{1F236}"=>"\u6709",
"\u{1F237}"=>"\u6708",
"\u{1F238}"=>"\u7533",
"\u{1F239}"=>"\u5272",
"\u{1F23A}"=>"\u55B6",
"\u{1F23B}"=>"\u914D",
"\u{1F240}"=>"\u3014\u672C\u3015",
"\u{1F241}"=>"\u3014\u4E09\u3015",
"\u{1F242}"=>"\u3014\u4E8C\u3015",
"\u{1F243}"=>"\u3014\u5B89\u3015",
"\u{1F244}"=>"\u3014\u70B9\u3015",
"\u{1F245}"=>"\u3014\u6253\u3015",
"\u{1F246}"=>"\u3014\u76D7\u3015",
"\u{1F247}"=>"\u3014\u52DD\u3015",
"\u{1F248}"=>"\u3014\u6557\u3015",
"\u{1F250}"=>"\u5F97",
"\u{1F251}"=>"\u53EF",
"\u{1FBF0}"=>"0",
"\u{1FBF1}"=>"1",
"\u{1FBF2}"=>"2",
"\u{1FBF3}"=>"3",
"\u{1FBF4}"=>"4",
"\u{1FBF5}"=>"5",
"\u{1FBF6}"=>"6",
"\u{1FBF7}"=>"7",
"\u{1FBF8}"=>"8",
"\u{1FBF9}"=>"9",
"\u0385"=>" \u0308\u0301",
"\u03D3"=>"\u03A5\u0301",
"\u03D4"=>"\u03A5\u0308",
"\u1E9B"=>"s\u0307",
"\u1FC1"=>" \u0308\u0342",
"\u1FCD"=>" \u0313\u0300",
"\u1FCE"=>" \u0313\u0301",
"\u1FCF"=>" \u0313\u0342",
"\u1FDD"=>" \u0314\u0300",
"\u1FDE"=>" \u0314\u0301",
"\u1FDF"=>" \u0314\u0342",
"\u1FED"=>" \u0308\u0300",
"\u1FEE"=>" \u0308\u0301",
"\u1FFD"=>" \u0301",
"\u2000"=>" ",
"\u2001"=>" ",
}.freeze
COMPOSITION_TABLE = {
"A\u0300"=>"\u00C0",
"A\u0301"=>"\u00C1",
"A\u0302"=>"\u00C2",
"A\u0303"=>"\u00C3",
"A\u0308"=>"\u00C4",
"A\u030A"=>"\u00C5",
"C\u0327"=>"\u00C7",
"E\u0300"=>"\u00C8",
"E\u0301"=>"\u00C9",
"E\u0302"=>"\u00CA",
"E\u0308"=>"\u00CB",
"I\u0300"=>"\u00CC",
"I\u0301"=>"\u00CD",
"I\u0302"=>"\u00CE",
"I\u0308"=>"\u00CF",
"N\u0303"=>"\u00D1",
"O\u0300"=>"\u00D2",
"O\u0301"=>"\u00D3",
"O\u0302"=>"\u00D4",
"O\u0303"=>"\u00D5",
"O\u0308"=>"\u00D6",
"U\u0300"=>"\u00D9",
"U\u0301"=>"\u00DA",
"U\u0302"=>"\u00DB",
"U\u0308"=>"\u00DC",
"Y\u0301"=>"\u00DD",
"a\u0300"=>"\u00E0",
"a\u0301"=>"\u00E1",
"a\u0302"=>"\u00E2",
"a\u0303"=>"\u00E3",
"a\u0308"=>"\u00E4",
"a\u030A"=>"\u00E5",
"c\u0327"=>"\u00E7",
"e\u0300"=>"\u00E8",
"e\u0301"=>"\u00E9",
"e\u0302"=>"\u00EA",
"e\u0308"=>"\u00EB",
"i\u0300"=>"\u00EC",
"i\u0301"=>"\u00ED",
"i\u0302"=>"\u00EE",
"i\u0308"=>"\u00EF",
"n\u0303"=>"\u00F1",
"o\u0300"=>"\u00F2",
"o\u0301"=>"\u00F3",
"o\u0302"=>"\u00F4",
"o\u0303"=>"\u00F5",
"o\u0308"=>"\u00F6",
"u\u0300"=>"\u00F9",
"u\u0301"=>"\u00FA",
"u\u0302"=>"\u00FB",
"u\u0308"=>"\u00FC",
"y\u0301"=>"\u00FD",
"y\u0308"=>"\u00FF",
"A\u0304"=>"\u0100",
"a\u0304"=>"\u0101",
"A\u0306"=>"\u0102",
"a\u0306"=>"\u0103",
"A\u0328"=>"\u0104",
"a\u0328"=>"\u0105",
"C\u0301"=>"\u0106",
"c\u0301"=>"\u0107",
"C\u0302"=>"\u0108",
"c\u0302"=>"\u0109",
"C\u0307"=>"\u010A",
"c\u0307"=>"\u010B",
"C\u030C"=>"\u010C",
"c\u030C"=>"\u010D",
"D\u030C"=>"\u010E",
"d\u030C"=>"\u010F",
"E\u0304"=>"\u0112",
"e\u0304"=>"\u0113",
"E\u0306"=>"\u0114",
"e\u0306"=>"\u0115",
"E\u0307"=>"\u0116",
"e\u0307"=>"\u0117",
"E\u0328"=>"\u0118",
"e\u0328"=>"\u0119",
"E\u030C"=>"\u011A",
"e\u030C"=>"\u011B",
"G\u0302"=>"\u011C",
"g\u0302"=>"\u011D",
"G\u0306"=>"\u011E",
"g\u0306"=>"\u011F",
"G\u0307"=>"\u0120",
"g\u0307"=>"\u0121",
"G\u0327"=>"\u0122",
"g\u0327"=>"\u0123",
"H\u0302"=>"\u0124",
"h\u0302"=>"\u0125",
"I\u0303"=>"\u0128",
"i\u0303"=>"\u0129",
"I\u0304"=>"\u012A",
"i\u0304"=>"\u012B",
"I\u0306"=>"\u012C",
"i\u0306"=>"\u012D",
"I\u0328"=>"\u012E",
"i\u0328"=>"\u012F",
"I\u0307"=>"\u0130",
"J\u0302"=>"\u0134",
"j\u0302"=>"\u0135",
"K\u0327"=>"\u0136",
"k\u0327"=>"\u0137",
"L\u0301"=>"\u0139",
"l\u0301"=>"\u013A",
"L\u0327"=>"\u013B",
"l\u0327"=>"\u013C",
"L\u030C"=>"\u013D",
"l\u030C"=>"\u013E",
"N\u0301"=>"\u0143",
"n\u0301"=>"\u0144",
"N\u0327"=>"\u0145",
"n\u0327"=>"\u0146",
"N\u030C"=>"\u0147",
"n\u030C"=>"\u0148",
"O\u0304"=>"\u014C",
"o\u0304"=>"\u014D",
"O\u0306"=>"\u014E",
"o\u0306"=>"\u014F",
"O\u030B"=>"\u0150",
"o\u030B"=>"\u0151",
"R\u0301"=>"\u0154",
"r\u0301"=>"\u0155",
"R\u0327"=>"\u0156",
"r\u0327"=>"\u0157",
"R\u030C"=>"\u0158",
"r\u030C"=>"\u0159",
"S\u0301"=>"\u015A",
"s\u0301"=>"\u015B",
"S\u0302"=>"\u015C",
"s\u0302"=>"\u015D",
"S\u0327"=>"\u015E",
"s\u0327"=>"\u015F",
"S\u030C"=>"\u0160",
"s\u030C"=>"\u0161",
"T\u0327"=>"\u0162",
"t\u0327"=>"\u0163",
"T\u030C"=>"\u0164",
"t\u030C"=>"\u0165",
"U\u0303"=>"\u0168",
"u\u0303"=>"\u0169",
"U\u0304"=>"\u016A",
"u\u0304"=>"\u016B",
"U\u0306"=>"\u016C",
"u\u0306"=>"\u016D",
"U\u030A"=>"\u016E",
"u\u030A"=>"\u016F",
"U\u030B"=>"\u0170",
"u\u030B"=>"\u0171",
"U\u0328"=>"\u0172",
"u\u0328"=>"\u0173",
"W\u0302"=>"\u0174",
"w\u0302"=>"\u0175",
"Y\u0302"=>"\u0176",
"y\u0302"=>"\u0177",
"Y\u0308"=>"\u0178",
"Z\u0301"=>"\u0179",
"z\u0301"=>"\u017A",
"Z\u0307"=>"\u017B",
"z\u0307"=>"\u017C",
"Z\u030C"=>"\u017D",
"z\u030C"=>"\u017E",
"O\u031B"=>"\u01A0",
"o\u031B"=>"\u01A1",
"U\u031B"=>"\u01AF",
"u\u031B"=>"\u01B0",
"A\u030C"=>"\u01CD",
"a\u030C"=>"\u01CE",
"I\u030C"=>"\u01CF",
"i\u030C"=>"\u01D0",
"O\u030C"=>"\u01D1",
"o\u030C"=>"\u01D2",
"U\u030C"=>"\u01D3",
"u\u030C"=>"\u01D4",
"\u00DC\u0304"=>"\u01D5",
"\u00FC\u0304"=>"\u01D6",
"\u00DC\u0301"=>"\u01D7",
"\u00FC\u0301"=>"\u01D8",
"\u00DC\u030C"=>"\u01D9",
"\u00FC\u030C"=>"\u01DA",
"\u00DC\u0300"=>"\u01DB",
"\u00FC\u0300"=>"\u01DC",
"\u00C4\u0304"=>"\u01DE",
"\u00E4\u0304"=>"\u01DF",
"\u0226\u0304"=>"\u01E0",
"\u0227\u0304"=>"\u01E1",
"\u00C6\u0304"=>"\u01E2",
"\u00E6\u0304"=>"\u01E3",
"G\u030C"=>"\u01E6",
"g\u030C"=>"\u01E7",
"K\u030C"=>"\u01E8",
"k\u030C"=>"\u01E9",
"O\u0328"=>"\u01EA",
"o\u0328"=>"\u01EB",
"\u01EA\u0304"=>"\u01EC",
"\u01EB\u0304"=>"\u01ED",
"\u01B7\u030C"=>"\u01EE",
"\u0292\u030C"=>"\u01EF",
"j\u030C"=>"\u01F0",
"G\u0301"=>"\u01F4",
"g\u0301"=>"\u01F5",
"N\u0300"=>"\u01F8",
"n\u0300"=>"\u01F9",
"\u00C5\u0301"=>"\u01FA",
"\u00E5\u0301"=>"\u01FB",
"\u00C6\u0301"=>"\u01FC",
"\u00E6\u0301"=>"\u01FD",
"\u00D8\u0301"=>"\u01FE",
"\u00F8\u0301"=>"\u01FF",
"A\u030F"=>"\u0200",
"a\u030F"=>"\u0201",
"A\u0311"=>"\u0202",
"a\u0311"=>"\u0203",
"E\u030F"=>"\u0204",
"e\u030F"=>"\u0205",
"E\u0311"=>"\u0206",
"e\u0311"=>"\u0207",
"I\u030F"=>"\u0208",
"i\u030F"=>"\u0209",
"I\u0311"=>"\u020A",
"i\u0311"=>"\u020B",
"O\u030F"=>"\u020C",
"o\u030F"=>"\u020D",
"O\u0311"=>"\u020E",
"o\u0311"=>"\u020F",
"R\u030F"=>"\u0210",
"r\u030F"=>"\u0211",
"R\u0311"=>"\u0212",
"r\u0311"=>"\u0213",
"U\u030F"=>"\u0214",
"u\u030F"=>"\u0215",
"U\u0311"=>"\u0216",
"u\u0311"=>"\u0217",
"S\u0326"=>"\u0218",
"s\u0326"=>"\u0219",
"T\u0326"=>"\u021A",
"t\u0326"=>"\u021B",
"H\u030C"=>"\u021E",
"h\u030C"=>"\u021F",
"A\u0307"=>"\u0226",
"a\u0307"=>"\u0227",
"E\u0327"=>"\u0228",
"e\u0327"=>"\u0229",
"\u00D6\u0304"=>"\u022A",
"\u00F6\u0304"=>"\u022B",
"\u00D5\u0304"=>"\u022C",
"\u00F5\u0304"=>"\u022D",
"O\u0307"=>"\u022E",
"o\u0307"=>"\u022F",
"\u022E\u0304"=>"\u0230",
"\u022F\u0304"=>"\u0231",
"Y\u0304"=>"\u0232",
"y\u0304"=>"\u0233",
"\u00A8\u0301"=>"\u0385",
"\u0391\u0301"=>"\u0386",
"\u0395\u0301"=>"\u0388",
"\u0397\u0301"=>"\u0389",
"\u0399\u0301"=>"\u038A",
"\u039F\u0301"=>"\u038C",
"\u03A5\u0301"=>"\u038E",
"\u03A9\u0301"=>"\u038F",
"\u03CA\u0301"=>"\u0390",
"\u0399\u0308"=>"\u03AA",
"\u03A5\u0308"=>"\u03AB",
"\u03B1\u0301"=>"\u03AC",
"\u03B5\u0301"=>"\u03AD",
"\u03B7\u0301"=>"\u03AE",
"\u03B9\u0301"=>"\u03AF",
"\u03CB\u0301"=>"\u03B0",
"\u03B9\u0308"=>"\u03CA",
"\u03C5\u0308"=>"\u03CB",
"\u03BF\u0301"=>"\u03CC",
"\u03C5\u0301"=>"\u03CD",
"\u03C9\u0301"=>"\u03CE",
"\u03D2\u0301"=>"\u03D3",
"\u03D2\u0308"=>"\u03D4",
"\u0415\u0300"=>"\u0400",
"\u0415\u0308"=>"\u0401",
"\u0413\u0301"=>"\u0403",
"\u0406\u0308"=>"\u0407",
"\u041A\u0301"=>"\u040C",
"\u0418\u0300"=>"\u040D",
"\u0423\u0306"=>"\u040E",
"\u0418\u0306"=>"\u0419",
"\u0438\u0306"=>"\u0439",
"\u0435\u0300"=>"\u0450",
"\u0435\u0308"=>"\u0451",
"\u0433\u0301"=>"\u0453",
"\u0456\u0308"=>"\u0457",
"\u043A\u0301"=>"\u045C",
"\u0438\u0300"=>"\u045D",
"\u0443\u0306"=>"\u045E",
"\u0474\u030F"=>"\u0476",
"\u0475\u030F"=>"\u0477",
"\u0416\u0306"=>"\u04C1",
"\u0436\u0306"=>"\u04C2",
"\u0410\u0306"=>"\u04D0",
"\u0430\u0306"=>"\u04D1",
"\u0410\u0308"=>"\u04D2",
"\u0430\u0308"=>"\u04D3",
"\u0415\u0306"=>"\u04D6",
"\u0435\u0306"=>"\u04D7",
"\u04D8\u0308"=>"\u04DA",
"\u04D9\u0308"=>"\u04DB",
"\u0416\u0308"=>"\u04DC",
"\u0436\u0308"=>"\u04DD",
"\u0417\u0308"=>"\u04DE",
"\u0437\u0308"=>"\u04DF",
"\u0418\u0304"=>"\u04E2",
"\u0438\u0304"=>"\u04E3",
"\u0418\u0308"=>"\u04E4",
"\u0438\u0308"=>"\u04E5",
"\u041E\u0308"=>"\u04E6",
"\u043E\u0308"=>"\u04E7",
"\u04E8\u0308"=>"\u04EA",
"\u04E9\u0308"=>"\u04EB",
"\u042D\u0308"=>"\u04EC",
"\u044D\u0308"=>"\u04ED",
"\u0423\u0304"=>"\u04EE",
"\u0443\u0304"=>"\u04EF",
"\u0423\u0308"=>"\u04F0",
"\u0443\u0308"=>"\u04F1",
"\u0423\u030B"=>"\u04F2",
"\u0443\u030B"=>"\u04F3",
"\u0427\u0308"=>"\u04F4",
"\u0447\u0308"=>"\u04F5",
"\u042B\u0308"=>"\u04F8",
"\u044B\u0308"=>"\u04F9",
"\u0627\u0653"=>"\u0622",
"\u0627\u0654"=>"\u0623",
"\u0648\u0654"=>"\u0624",
"\u0627\u0655"=>"\u0625",
"\u064A\u0654"=>"\u0626",
"\u06D5\u0654"=>"\u06C0",
"\u06C1\u0654"=>"\u06C2",
"\u06D2\u0654"=>"\u06D3",
"\u0928\u093C"=>"\u0929",
"\u0930\u093C"=>"\u0931",
"\u0933\u093C"=>"\u0934",
"\u09C7\u09BE"=>"\u09CB",
"\u09C7\u09D7"=>"\u09CC",
"\u0B47\u0B56"=>"\u0B48",
"\u0B47\u0B3E"=>"\u0B4B",
"\u0B47\u0B57"=>"\u0B4C",
"\u0B92\u0BD7"=>"\u0B94",
"\u0BC6\u0BBE"=>"\u0BCA",
"\u0BC7\u0BBE"=>"\u0BCB",
"\u0BC6\u0BD7"=>"\u0BCC",
"\u0C46\u0C56"=>"\u0C48",
"\u0CBF\u0CD5"=>"\u0CC0",
"\u0CC6\u0CD5"=>"\u0CC7",
"\u0CC6\u0CD6"=>"\u0CC8",
"\u0CC6\u0CC2"=>"\u0CCA",
"\u0CCA\u0CD5"=>"\u0CCB",
"\u0D46\u0D3E"=>"\u0D4A",
"\u0D47\u0D3E"=>"\u0D4B",
"\u0D46\u0D57"=>"\u0D4C",
"\u0DD9\u0DCA"=>"\u0DDA",
"\u0DD9\u0DCF"=>"\u0DDC",
"\u0DDC\u0DCA"=>"\u0DDD",
"\u0DD9\u0DDF"=>"\u0DDE",
"\u1025\u102E"=>"\u1026",
"\u1B05\u1B35"=>"\u1B06",
"\u1B07\u1B35"=>"\u1B08",
"\u1B09\u1B35"=>"\u1B0A",
"\u1B0B\u1B35"=>"\u1B0C",
"\u1B0D\u1B35"=>"\u1B0E",
"\u1B11\u1B35"=>"\u1B12",
"\u1B3A\u1B35"=>"\u1B3B",
"\u1B3C\u1B35"=>"\u1B3D",
"\u1B3E\u1B35"=>"\u1B40",
"\u1B3F\u1B35"=>"\u1B41",
"\u1B42\u1B35"=>"\u1B43",
"A\u0325"=>"\u1E00",
"a\u0325"=>"\u1E01",
"B\u0307"=>"\u1E02",
"b\u0307"=>"\u1E03",
"B\u0323"=>"\u1E04",
"b\u0323"=>"\u1E05",
"B\u0331"=>"\u1E06",
"b\u0331"=>"\u1E07",
"\u00C7\u0301"=>"\u1E08",
"\u00E7\u0301"=>"\u1E09",
"D\u0307"=>"\u1E0A",
"d\u0307"=>"\u1E0B",
"D\u0323"=>"\u1E0C",
"d\u0323"=>"\u1E0D",
"D\u0331"=>"\u1E0E",
"d\u0331"=>"\u1E0F",
"D\u0327"=>"\u1E10",
"d\u0327"=>"\u1E11",
"D\u032D"=>"\u1E12",
"d\u032D"=>"\u1E13",
"\u0112\u0300"=>"\u1E14",
"\u0113\u0300"=>"\u1E15",
"\u0112\u0301"=>"\u1E16",
"\u0113\u0301"=>"\u1E17",
"E\u032D"=>"\u1E18",
"e\u032D"=>"\u1E19",
"E\u0330"=>"\u1E1A",
"e\u0330"=>"\u1E1B",
"\u0228\u0306"=>"\u1E1C",
"\u0229\u0306"=>"\u1E1D",
"F\u0307"=>"\u1E1E",
"f\u0307"=>"\u1E1F",
"G\u0304"=>"\u1E20",
"g\u0304"=>"\u1E21",
"H\u0307"=>"\u1E22",
"h\u0307"=>"\u1E23",
"H\u0323"=>"\u1E24",
"h\u0323"=>"\u1E25",
"H\u0308"=>"\u1E26",
"h\u0308"=>"\u1E27",
"H\u0327"=>"\u1E28",
"h\u0327"=>"\u1E29",
"H\u032E"=>"\u1E2A",
"h\u032E"=>"\u1E2B",
"I\u0330"=>"\u1E2C",
"i\u0330"=>"\u1E2D",
"\u00CF\u0301"=>"\u1E2E",
"\u00EF\u0301"=>"\u1E2F",
"K\u0301"=>"\u1E30",
"k\u0301"=>"\u1E31",
"K\u0323"=>"\u1E32",
"k\u0323"=>"\u1E33",
"K\u0331"=>"\u1E34",
"k\u0331"=>"\u1E35",
"L\u0323"=>"\u1E36",
"l\u0323"=>"\u1E37",
"\u1E36\u0304"=>"\u1E38",
"\u1E37\u0304"=>"\u1E39",
"L\u0331"=>"\u1E3A",
"l\u0331"=>"\u1E3B",
"L\u032D"=>"\u1E3C",
"l\u032D"=>"\u1E3D",
"M\u0301"=>"\u1E3E",
"m\u0301"=>"\u1E3F",
"M\u0307"=>"\u1E40",
"m\u0307"=>"\u1E41",
"M\u0323"=>"\u1E42",
"m\u0323"=>"\u1E43",
"N\u0307"=>"\u1E44",
"n\u0307"=>"\u1E45",
"N\u0323"=>"\u1E46",
"n\u0323"=>"\u1E47",
"N\u0331"=>"\u1E48",
"n\u0331"=>"\u1E49",
"N\u032D"=>"\u1E4A",
"n\u032D"=>"\u1E4B",
"\u00D5\u0301"=>"\u1E4C",
"\u00F5\u0301"=>"\u1E4D",
"\u00D5\u0308"=>"\u1E4E",
"\u00F5\u0308"=>"\u1E4F",
"\u014C\u0300"=>"\u1E50",
"\u014D\u0300"=>"\u1E51",
"\u014C\u0301"=>"\u1E52",
"\u014D\u0301"=>"\u1E53",
"P\u0301"=>"\u1E54",
"p\u0301"=>"\u1E55",
"P\u0307"=>"\u1E56",
"p\u0307"=>"\u1E57",
"R\u0307"=>"\u1E58",
"r\u0307"=>"\u1E59",
"R\u0323"=>"\u1E5A",
"r\u0323"=>"\u1E5B",
"\u1E5A\u0304"=>"\u1E5C",
"\u1E5B\u0304"=>"\u1E5D",
"R\u0331"=>"\u1E5E",
"r\u0331"=>"\u1E5F",
"S\u0307"=>"\u1E60",
"s\u0307"=>"\u1E61",
"S\u0323"=>"\u1E62",
"s\u0323"=>"\u1E63",
"\u015A\u0307"=>"\u1E64",
"\u015B\u0307"=>"\u1E65",
"\u0160\u0307"=>"\u1E66",
"\u0161\u0307"=>"\u1E67",
"\u1E62\u0307"=>"\u1E68",
"\u1E63\u0307"=>"\u1E69",
"T\u0307"=>"\u1E6A",
"t\u0307"=>"\u1E6B",
"T\u0323"=>"\u1E6C",
"t\u0323"=>"\u1E6D",
"T\u0331"=>"\u1E6E",
"t\u0331"=>"\u1E6F",
"T\u032D"=>"\u1E70",
"t\u032D"=>"\u1E71",
"U\u0324"=>"\u1E72",
"u\u0324"=>"\u1E73",
"U\u0330"=>"\u1E74",
"u\u0330"=>"\u1E75",
"U\u032D"=>"\u1E76",
"u\u032D"=>"\u1E77",
"\u0168\u0301"=>"\u1E78",
"\u0169\u0301"=>"\u1E79",
"\u016A\u0308"=>"\u1E7A",
"\u016B\u0308"=>"\u1E7B",
"V\u0303"=>"\u1E7C",
"v\u0303"=>"\u1E7D",
"V\u0323"=>"\u1E7E",
"v\u0323"=>"\u1E7F",
"W\u0300"=>"\u1E80",
"w\u0300"=>"\u1E81",
"W\u0301"=>"\u1E82",
"w\u0301"=>"\u1E83",
"W\u0308"=>"\u1E84",
"w\u0308"=>"\u1E85",
"W\u0307"=>"\u1E86",
"w\u0307"=>"\u1E87",
"W\u0323"=>"\u1E88",
"w\u0323"=>"\u1E89",
"X\u0307"=>"\u1E8A",
"x\u0307"=>"\u1E8B",
"X\u0308"=>"\u1E8C",
"x\u0308"=>"\u1E8D",
"Y\u0307"=>"\u1E8E",
"y\u0307"=>"\u1E8F",
"Z\u0302"=>"\u1E90",
"z\u0302"=>"\u1E91",
"Z\u0323"=>"\u1E92",
"z\u0323"=>"\u1E93",
"Z\u0331"=>"\u1E94",
"z\u0331"=>"\u1E95",
"h\u0331"=>"\u1E96",
"t\u0308"=>"\u1E97",
"w\u030A"=>"\u1E98",
"y\u030A"=>"\u1E99",
"\u017F\u0307"=>"\u1E9B",
"A\u0323"=>"\u1EA0",
"a\u0323"=>"\u1EA1",
"A\u0309"=>"\u1EA2",
"a\u0309"=>"\u1EA3",
"\u00C2\u0301"=>"\u1EA4",
"\u00E2\u0301"=>"\u1EA5",
"\u00C2\u0300"=>"\u1EA6",
"\u00E2\u0300"=>"\u1EA7",
"\u00C2\u0309"=>"\u1EA8",
"\u00E2\u0309"=>"\u1EA9",
"\u00C2\u0303"=>"\u1EAA",
"\u00E2\u0303"=>"\u1EAB",
"\u1EA0\u0302"=>"\u1EAC",
"\u1EA1\u0302"=>"\u1EAD",
"\u0102\u0301"=>"\u1EAE",
"\u0103\u0301"=>"\u1EAF",
"\u0102\u0300"=>"\u1EB0",
"\u0103\u0300"=>"\u1EB1",
"\u0102\u0309"=>"\u1EB2",
"\u0103\u0309"=>"\u1EB3",
"\u0102\u0303"=>"\u1EB4",
"\u0103\u0303"=>"\u1EB5",
"\u1EA0\u0306"=>"\u1EB6",
"\u1EA1\u0306"=>"\u1EB7",
"E\u0323"=>"\u1EB8",
"e\u0323"=>"\u1EB9",
"E\u0309"=>"\u1EBA",
"e\u0309"=>"\u1EBB",
"E\u0303"=>"\u1EBC",
"e\u0303"=>"\u1EBD",
"\u00CA\u0301"=>"\u1EBE",
"\u00EA\u0301"=>"\u1EBF",
"\u00CA\u0300"=>"\u1EC0",
"\u00EA\u0300"=>"\u1EC1",
"\u00CA\u0309"=>"\u1EC2",
"\u00EA\u0309"=>"\u1EC3",
"\u00CA\u0303"=>"\u1EC4",
"\u00EA\u0303"=>"\u1EC5",
"\u1EB8\u0302"=>"\u1EC6",
"\u1EB9\u0302"=>"\u1EC7",
"I\u0309"=>"\u1EC8",
"i\u0309"=>"\u1EC9",
"I\u0323"=>"\u1ECA",
"i\u0323"=>"\u1ECB",
"O\u0323"=>"\u1ECC",
"o\u0323"=>"\u1ECD",
"O\u0309"=>"\u1ECE",
"o\u0309"=>"\u1ECF",
"\u00D4\u0301"=>"\u1ED0",
"\u00F4\u0301"=>"\u1ED1",
"\u00D4\u0300"=>"\u1ED2",
"\u00F4\u0300"=>"\u1ED3",
"\u00D4\u0309"=>"\u1ED4",
"\u00F4\u0309"=>"\u1ED5",
"\u00D4\u0303"=>"\u1ED6",
"\u00F4\u0303"=>"\u1ED7",
"\u1ECC\u0302"=>"\u1ED8",
"\u1ECD\u0302"=>"\u1ED9",
"\u01A0\u0301"=>"\u1EDA",
"\u01A1\u0301"=>"\u1EDB",
"\u01A0\u0300"=>"\u1EDC",
"\u01A1\u0300"=>"\u1EDD",
"\u01A0\u0309"=>"\u1EDE",
"\u01A1\u0309"=>"\u1EDF",
"\u01A0\u0303"=>"\u1EE0",
"\u01A1\u0303"=>"\u1EE1",
"\u01A0\u0323"=>"\u1EE2",
"\u01A1\u0323"=>"\u1EE3",
"U\u0323"=>"\u1EE4",
"u\u0323"=>"\u1EE5",
"U\u0309"=>"\u1EE6",
"u\u0309"=>"\u1EE7",
"\u01AF\u0301"=>"\u1EE8",
"\u01B0\u0301"=>"\u1EE9",
"\u01AF\u0300"=>"\u1EEA",
"\u01B0\u0300"=>"\u1EEB",
"\u01AF\u0309"=>"\u1EEC",
"\u01B0\u0309"=>"\u1EED",
"\u01AF\u0303"=>"\u1EEE",
"\u01B0\u0303"=>"\u1EEF",
"\u01AF\u0323"=>"\u1EF0",
"\u01B0\u0323"=>"\u1EF1",
"Y\u0300"=>"\u1EF2",
"y\u0300"=>"\u1EF3",
"Y\u0323"=>"\u1EF4",
"y\u0323"=>"\u1EF5",
"Y\u0309"=>"\u1EF6",
"y\u0309"=>"\u1EF7",
"Y\u0303"=>"\u1EF8",
"y\u0303"=>"\u1EF9",
"\u03B1\u0313"=>"\u1F00",
"\u03B1\u0314"=>"\u1F01",
"\u1F00\u0300"=>"\u1F02",
"\u1F01\u0300"=>"\u1F03",
"\u1F00\u0301"=>"\u1F04",
"\u1F01\u0301"=>"\u1F05",
"\u1F00\u0342"=>"\u1F06",
"\u1F01\u0342"=>"\u1F07",
"\u0391\u0313"=>"\u1F08",
"\u0391\u0314"=>"\u1F09",
"\u1F08\u0300"=>"\u1F0A",
"\u1F09\u0300"=>"\u1F0B",
"\u1F08\u0301"=>"\u1F0C",
"\u1F09\u0301"=>"\u1F0D",
"\u1F08\u0342"=>"\u1F0E",
"\u1F09\u0342"=>"\u1F0F",
"\u03B5\u0313"=>"\u1F10",
"\u03B5\u0314"=>"\u1F11",
"\u1F10\u0300"=>"\u1F12",
"\u1F11\u0300"=>"\u1F13",
"\u1F10\u0301"=>"\u1F14",
"\u1F11\u0301"=>"\u1F15",
"\u0395\u0313"=>"\u1F18",
"\u0395\u0314"=>"\u1F19",
"\u1F18\u0300"=>"\u1F1A",
"\u1F19\u0300"=>"\u1F1B",
"\u1F18\u0301"=>"\u1F1C",
"\u1F19\u0301"=>"\u1F1D",
"\u03B7\u0313"=>"\u1F20",
"\u03B7\u0314"=>"\u1F21",
"\u1F20\u0300"=>"\u1F22",
"\u1F21\u0300"=>"\u1F23",
"\u1F20\u0301"=>"\u1F24",
"\u1F21\u0301"=>"\u1F25",
"\u1F20\u0342"=>"\u1F26",
"\u1F21\u0342"=>"\u1F27",
"\u0397\u0313"=>"\u1F28",
"\u0397\u0314"=>"\u1F29",
"\u1F28\u0300"=>"\u1F2A",
"\u1F29\u0300"=>"\u1F2B",
"\u1F28\u0301"=>"\u1F2C",
"\u1F29\u0301"=>"\u1F2D",
"\u1F28\u0342"=>"\u1F2E",
"\u1F29\u0342"=>"\u1F2F",
"\u03B9\u0313"=>"\u1F30",
"\u03B9\u0314"=>"\u1F31",
"\u1F30\u0300"=>"\u1F32",
"\u1F31\u0300"=>"\u1F33",
"\u1F30\u0301"=>"\u1F34",
"\u1F31\u0301"=>"\u1F35",
"\u1F30\u0342"=>"\u1F36",
"\u1F31\u0342"=>"\u1F37",
"\u0399\u0313"=>"\u1F38",
"\u0399\u0314"=>"\u1F39",
"\u1F38\u0300"=>"\u1F3A",
"\u1F39\u0300"=>"\u1F3B",
"\u1F38\u0301"=>"\u1F3C",
"\u1F39\u0301"=>"\u1F3D",
"\u1F38\u0342"=>"\u1F3E",
"\u1F39\u0342"=>"\u1F3F",
"\u03BF\u0313"=>"\u1F40",
"\u03BF\u0314"=>"\u1F41",
"\u1F40\u0300"=>"\u1F42",
"\u1F41\u0300"=>"\u1F43",
"\u1F40\u0301"=>"\u1F44",
"\u1F41\u0301"=>"\u1F45",
"\u039F\u0313"=>"\u1F48",
"\u039F\u0314"=>"\u1F49",
"\u1F48\u0300"=>"\u1F4A",
"\u1F49\u0300"=>"\u1F4B",
"\u1F48\u0301"=>"\u1F4C",
"\u1F49\u0301"=>"\u1F4D",
"\u03C5\u0313"=>"\u1F50",
"\u03C5\u0314"=>"\u1F51",
"\u1F50\u0300"=>"\u1F52",
"\u1F51\u0300"=>"\u1F53",
"\u1F50\u0301"=>"\u1F54",
"\u1F51\u0301"=>"\u1F55",
"\u1F50\u0342"=>"\u1F56",
"\u1F51\u0342"=>"\u1F57",
"\u03A5\u0314"=>"\u1F59",
"\u1F59\u0300"=>"\u1F5B",
"\u1F59\u0301"=>"\u1F5D",
"\u1F59\u0342"=>"\u1F5F",
"\u03C9\u0313"=>"\u1F60",
"\u03C9\u0314"=>"\u1F61",
"\u1F60\u0300"=>"\u1F62",
"\u1F61\u0300"=>"\u1F63",
"\u1F60\u0301"=>"\u1F64",
"\u1F61\u0301"=>"\u1F65",
"\u1F60\u0342"=>"\u1F66",
"\u1F61\u0342"=>"\u1F67",
"\u03A9\u0313"=>"\u1F68",
"\u03A9\u0314"=>"\u1F69",
"\u1F68\u0300"=>"\u1F6A",
"\u1F69\u0300"=>"\u1F6B",
"\u1F68\u0301"=>"\u1F6C",
"\u1F69\u0301"=>"\u1F6D",
"\u1F68\u0342"=>"\u1F6E",
"\u1F69\u0342"=>"\u1F6F",
"\u03B1\u0300"=>"\u1F70",
"\u03B5\u0300"=>"\u1F72",
"\u03B7\u0300"=>"\u1F74",
"\u03B9\u0300"=>"\u1F76",
"\u03BF\u0300"=>"\u1F78",
"\u03C5\u0300"=>"\u1F7A",
"\u03C9\u0300"=>"\u1F7C",
"\u1F00\u0345"=>"\u1F80",
"\u1F01\u0345"=>"\u1F81",
"\u1F02\u0345"=>"\u1F82",
"\u1F03\u0345"=>"\u1F83",
"\u1F04\u0345"=>"\u1F84",
"\u1F05\u0345"=>"\u1F85",
"\u1F06\u0345"=>"\u1F86",
"\u1F07\u0345"=>"\u1F87",
"\u1F08\u0345"=>"\u1F88",
"\u1F09\u0345"=>"\u1F89",
"\u1F0A\u0345"=>"\u1F8A",
"\u1F0B\u0345"=>"\u1F8B",
"\u1F0C\u0345"=>"\u1F8C",
"\u1F0D\u0345"=>"\u1F8D",
"\u1F0E\u0345"=>"\u1F8E",
"\u1F0F\u0345"=>"\u1F8F",
"\u1F20\u0345"=>"\u1F90",
"\u1F21\u0345"=>"\u1F91",
"\u1F22\u0345"=>"\u1F92",
"\u1F23\u0345"=>"\u1F93",
"\u1F24\u0345"=>"\u1F94",
"\u1F25\u0345"=>"\u1F95",
"\u1F26\u0345"=>"\u1F96",
"\u1F27\u0345"=>"\u1F97",
"\u1F28\u0345"=>"\u1F98",
"\u1F29\u0345"=>"\u1F99",
"\u1F2A\u0345"=>"\u1F9A",
"\u1F2B\u0345"=>"\u1F9B",
"\u1F2C\u0345"=>"\u1F9C",
"\u1F2D\u0345"=>"\u1F9D",
"\u1F2E\u0345"=>"\u1F9E",
"\u1F2F\u0345"=>"\u1F9F",
"\u1F60\u0345"=>"\u1FA0",
"\u1F61\u0345"=>"\u1FA1",
"\u1F62\u0345"=>"\u1FA2",
"\u1F63\u0345"=>"\u1FA3",
"\u1F64\u0345"=>"\u1FA4",
"\u1F65\u0345"=>"\u1FA5",
"\u1F66\u0345"=>"\u1FA6",
"\u1F67\u0345"=>"\u1FA7",
"\u1F68\u0345"=>"\u1FA8",
"\u1F69\u0345"=>"\u1FA9",
"\u1F6A\u0345"=>"\u1FAA",
"\u1F6B\u0345"=>"\u1FAB",
"\u1F6C\u0345"=>"\u1FAC",
"\u1F6D\u0345"=>"\u1FAD",
"\u1F6E\u0345"=>"\u1FAE",
"\u1F6F\u0345"=>"\u1FAF",
"\u03B1\u0306"=>"\u1FB0",
"\u03B1\u0304"=>"\u1FB1",
"\u1F70\u0345"=>"\u1FB2",
"\u03B1\u0345"=>"\u1FB3",
"\u03AC\u0345"=>"\u1FB4",
"\u03B1\u0342"=>"\u1FB6",
"\u1FB6\u0345"=>"\u1FB7",
"\u0391\u0306"=>"\u1FB8",
"\u0391\u0304"=>"\u1FB9",
"\u0391\u0300"=>"\u1FBA",
"\u0391\u0345"=>"\u1FBC",
"\u00A8\u0342"=>"\u1FC1",
"\u1F74\u0345"=>"\u1FC2",
"\u03B7\u0345"=>"\u1FC3",
"\u03AE\u0345"=>"\u1FC4",
"\u03B7\u0342"=>"\u1FC6",
"\u1FC6\u0345"=>"\u1FC7",
"\u0395\u0300"=>"\u1FC8",
"\u0397\u0300"=>"\u1FCA",
"\u0397\u0345"=>"\u1FCC",
"\u1FBF\u0300"=>"\u1FCD",
"\u1FBF\u0301"=>"\u1FCE",
"\u1FBF\u0342"=>"\u1FCF",
"\u03B9\u0306"=>"\u1FD0",
"\u03B9\u0304"=>"\u1FD1",
"\u03CA\u0300"=>"\u1FD2",
"\u03B9\u0342"=>"\u1FD6",
"\u03CA\u0342"=>"\u1FD7",
"\u0399\u0306"=>"\u1FD8",
"\u0399\u0304"=>"\u1FD9",
"\u0399\u0300"=>"\u1FDA",
"\u1FFE\u0300"=>"\u1FDD",
"\u1FFE\u0301"=>"\u1FDE",
"\u1FFE\u0342"=>"\u1FDF",
"\u03C5\u0306"=>"\u1FE0",
"\u03C5\u0304"=>"\u1FE1",
"\u03CB\u0300"=>"\u1FE2",
"\u03C1\u0313"=>"\u1FE4",
"\u03C1\u0314"=>"\u1FE5",
"\u03C5\u0342"=>"\u1FE6",
"\u03CB\u0342"=>"\u1FE7",
"\u03A5\u0306"=>"\u1FE8",
"\u03A5\u0304"=>"\u1FE9",
"\u03A5\u0300"=>"\u1FEA",
"\u03A1\u0314"=>"\u1FEC",
"\u00A8\u0300"=>"\u1FED",
"\u1F7C\u0345"=>"\u1FF2",
"\u03C9\u0345"=>"\u1FF3",
"\u03CE\u0345"=>"\u1FF4",
"\u03C9\u0342"=>"\u1FF6",
"\u1FF6\u0345"=>"\u1FF7",
"\u039F\u0300"=>"\u1FF8",
"\u03A9\u0300"=>"\u1FFA",
"\u03A9\u0345"=>"\u1FFC",
"\u2190\u0338"=>"\u219A",
"\u2192\u0338"=>"\u219B",
"\u2194\u0338"=>"\u21AE",
"\u21D0\u0338"=>"\u21CD",
"\u21D4\u0338"=>"\u21CE",
"\u21D2\u0338"=>"\u21CF",
"\u2203\u0338"=>"\u2204",
"\u2208\u0338"=>"\u2209",
"\u220B\u0338"=>"\u220C",
"\u2223\u0338"=>"\u2224",
"\u2225\u0338"=>"\u2226",
"\u223C\u0338"=>"\u2241",
"\u2243\u0338"=>"\u2244",
"\u2245\u0338"=>"\u2247",
"\u2248\u0338"=>"\u2249",
"=\u0338"=>"\u2260",
"\u2261\u0338"=>"\u2262",
"\u224D\u0338"=>"\u226D",
"<\u0338"=>"\u226E",
">\u0338"=>"\u226F",
"\u2264\u0338"=>"\u2270",
"\u2265\u0338"=>"\u2271",
"\u2272\u0338"=>"\u2274",
"\u2273\u0338"=>"\u2275",
"\u2276\u0338"=>"\u2278",
"\u2277\u0338"=>"\u2279",
"\u227A\u0338"=>"\u2280",
"\u227B\u0338"=>"\u2281",
"\u2282\u0338"=>"\u2284",
"\u2283\u0338"=>"\u2285",
"\u2286\u0338"=>"\u2288",
"\u2287\u0338"=>"\u2289",
"\u22A2\u0338"=>"\u22AC",
"\u22A8\u0338"=>"\u22AD",
"\u22A9\u0338"=>"\u22AE",
"\u22AB\u0338"=>"\u22AF",
"\u227C\u0338"=>"\u22E0",
"\u227D\u0338"=>"\u22E1",
"\u2291\u0338"=>"\u22E2",
"\u2292\u0338"=>"\u22E3",
"\u22B2\u0338"=>"\u22EA",
"\u22B3\u0338"=>"\u22EB",
"\u22B4\u0338"=>"\u22EC",
"\u22B5\u0338"=>"\u22ED",
"\u304B\u3099"=>"\u304C",
"\u304D\u3099"=>"\u304E",
"\u304F\u3099"=>"\u3050",
"\u3051\u3099"=>"\u3052",
"\u3053\u3099"=>"\u3054",
"\u3055\u3099"=>"\u3056",
"\u3057\u3099"=>"\u3058",
"\u3059\u3099"=>"\u305A",
"\u305B\u3099"=>"\u305C",
"\u305D\u3099"=>"\u305E",
"\u305F\u3099"=>"\u3060",
"\u3061\u3099"=>"\u3062",
"\u3064\u3099"=>"\u3065",
"\u3066\u3099"=>"\u3067",
"\u3068\u3099"=>"\u3069",
"\u306F\u3099"=>"\u3070",
"\u306F\u309A"=>"\u3071",
"\u3072\u3099"=>"\u3073",
"\u3072\u309A"=>"\u3074",
"\u3075\u3099"=>"\u3076",
"\u3075\u309A"=>"\u3077",
"\u3078\u3099"=>"\u3079",
"\u3078\u309A"=>"\u307A",
"\u307B\u3099"=>"\u307C",
"\u307B\u309A"=>"\u307D",
"\u3046\u3099"=>"\u3094",
"\u309D\u3099"=>"\u309E",
"\u30AB\u3099"=>"\u30AC",
"\u30AD\u3099"=>"\u30AE",
"\u30AF\u3099"=>"\u30B0",
"\u30B1\u3099"=>"\u30B2",
"\u30B3\u3099"=>"\u30B4",
"\u30B5\u3099"=>"\u30B6",
"\u30B7\u3099"=>"\u30B8",
"\u30B9\u3099"=>"\u30BA",
"\u30BB\u3099"=>"\u30BC",
"\u30BD\u3099"=>"\u30BE",
"\u30BF\u3099"=>"\u30C0",
"\u30C1\u3099"=>"\u30C2",
"\u30C4\u3099"=>"\u30C5",
"\u30C6\u3099"=>"\u30C7",
"\u30C8\u3099"=>"\u30C9",
"\u30CF\u3099"=>"\u30D0",
"\u30CF\u309A"=>"\u30D1",
"\u30D2\u3099"=>"\u30D3",
"\u30D2\u309A"=>"\u30D4",
"\u30D5\u3099"=>"\u30D6",
"\u30D5\u309A"=>"\u30D7",
"\u30D8\u3099"=>"\u30D9",
"\u30D8\u309A"=>"\u30DA",
"\u30DB\u3099"=>"\u30DC",
"\u30DB\u309A"=>"\u30DD",
"\u30A6\u3099"=>"\u30F4",
"\u30EF\u3099"=>"\u30F7",
"\u30F0\u3099"=>"\u30F8",
"\u30F1\u3099"=>"\u30F9",
"\u30F2\u3099"=>"\u30FA",
"\u30FD\u3099"=>"\u30FE",
"\u{11099}\u{110BA}"=>"\u{1109A}",
"\u{1109B}\u{110BA}"=>"\u{1109C}",
"\u{110A5}\u{110BA}"=>"\u{110AB}",
"\u{11131}\u{11127}"=>"\u{1112E}",
"\u{11132}\u{11127}"=>"\u{1112F}",
"\u{11347}\u{1133E}"=>"\u{1134B}",
"\u{11347}\u{11357}"=>"\u{1134C}",
"\u{114B9}\u{114BA}"=>"\u{114BB}",
"\u{114B9}\u{114B0}"=>"\u{114BC}",
"\u{114B9}\u{114BD}"=>"\u{114BE}",
"\u{115B8}\u{115AF}"=>"\u{115BA}",
"\u{115B9}\u{115AF}"=>"\u{115BB}",
"\u{11935}\u{11930}"=>"\u{11938}",
}.freeze
end
share/ruby/monitor.rb 0000644 00000015410 15173547334 0010660 0 ustar 00 # 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[https://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.
#
require 'monitor.so'
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
#
# 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.mon_check_owner
@monitor.wait_for_cond(@cond, timeout)
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.mon_check_owner
@cond.signal
end
#
# Wakes up all threads waiting for this lock.
#
def broadcast
@monitor.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
@mon_data.try_enter
end
# For backward compatibility
alias try_mon_enter mon_try_enter
#
# Enters exclusive section.
#
def mon_enter
@mon_data.enter
end
#
# Leaves exclusive section.
#
def mon_exit
mon_check_owner
@mon_data.exit
end
#
# Returns true if this monitor is locked by any thread
#
def mon_locked?
@mon_data.mon_locked?
end
#
# Returns true if this monitor is locked by current thread.
#
def mon_owned?
@mon_data.mon_owned?
end
#
# Enters exclusive section and executes the block. Leaves the exclusive
# section automatically when the block exits. See example under
# +MonitorMixin+.
#
def mon_synchronize(&b)
@mon_data.synchronize(&b)
end
alias synchronize mon_synchronize
#
# Creates a new MonitorMixin::ConditionVariable associated with the
# Monitor object.
#
def new_cond
unless defined?(@mon_data)
mon_initialize
@mon_initialized_by_new_cond = true
end
return ConditionVariable.new(@mon_data)
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(...)
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
if defined?(@mon_data)
if defined?(@mon_initialized_by_new_cond)
return # already initialized.
elsif @mon_data_owner_object_id == self.object_id
raise ThreadError, "already initialized"
end
end
@mon_data = ::Monitor.new
@mon_data_owner_object_id = self.object_id
end
def mon_check_owner
@mon_data.mon_check_owner
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
def new_cond
::MonitorMixin::ConditionVariable.new(self)
end
# for compatibility
alias try_mon_enter try_enter
alias mon_try_enter try_enter
alias mon_enter enter
alias mon_exit exit
alias mon_synchronize synchronize
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
share/ruby/find.rb 0000644 00000004774 15173547334 0010124 0 ustar 00 # 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).start_with?('.')
# 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
begin
s = File.lstat(file)
rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, Errno::ELOOP, Errno::ENAMETOOLONG, Errno::EINVAL
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, Errno::EINVAL
raise unless ignore_error
next
end
fs.sort!
fs.reverse_each {|f|
f = File.join(file, f)
ps.unshift f
}
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
share/ruby/set.rb 0000644 00000062500 15173547334 0007766 0 ustar 00 # frozen_string_literal: true
# :markup: markdown
#
# set.rb - defines the Set class
#
# Copyright (c) 2002-2020 Akinori MUSHA <knu@iDaemons.org>
#
# Documentation by Akinori MUSHA and Gavin Sinclair.
#
# All rights reserved. You can redistribute and/or modify it under the same
# terms as Ruby.
##
# This library provides the Set class, which deals with a collection
# of unordered values with no duplicates. It is a hybrid of Array's
# intuitive inter-operation facilities and Hash's fast lookup.
#
# The method `to_set` is added to Enumerable for convenience.
#
# Set implements a collection of unordered values with no duplicates.
# This is a hybrid of Array's intuitive inter-operation facilities and
# Hash's fast lookup.
#
# Set is easy to use with Enumerable objects (implementing `each`).
# Most of the initializer methods and binary operators accept generic
# Enumerable objects besides sets and arrays. An Enumerable object
# can be converted to Set using the `to_set` method.
#
# Set uses Hash as storage, so you must note the following points:
#
# * Equality of elements is determined according to Object#eql? and
# Object#hash. Use Set#compare_by_identity to make a set compare
# its elements by their identity.
# * Set assumes that the identity of each element does not change
# while it is stored. Modifying an element of a set will render the
# set to an unreliable state.
# * When a string is to be stored, a frozen copy of the string is
# stored instead unless the original string is already frozen.
#
# ## Comparison
#
# The comparison operators `<`, `>`, `<=`, and `>=` are implemented as
# shorthand for the {proper_,}{subset?,superset?} methods. The `<=>`
# operator reflects this order, or return `nil` for sets that both
# have distinct elements (`{x, y}` vs. `{x, z}` for example).
#
# ## Example
#
# ```ruby
# require 'set'
# s1 = Set[1, 2] #=> #<Set: {1, 2}>
# s2 = [1, 2].to_set #=> #<Set: {1, 2}>
# s1 == s2 #=> true
# s1.add("foo") #=> #<Set: {1, 2, "foo"}>
# s1.merge([2, 6]) #=> #<Set: {1, 2, "foo", 6}>
# s1.subset?(s2) #=> false
# s2.subset?(s1) #=> true
# ```
#
# ## Contact
#
# - Akinori MUSHA <<knu@iDaemons.org>> (current maintainer)
#
# ## What's Here
#
# First, what's elsewhere. \Class \Set:
#
# - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
# - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
# which provides dozens of additional methods.
#
# In particular, class \Set does not have many methods of its own
# for fetching or for iterating.
# Instead, it relies on those in \Enumerable.
#
# Here, class \Set provides methods that are useful for:
#
# - [Creating a Set](#class-Set-label-Methods+for+Creating+a+Set)
# - [Set Operations](#class-Set-label-Methods+for+Set+Operations)
# - [Comparing](#class-Set-label-Methods+for+Comparing)
# - [Querying](#class-Set-label-Methods+for+Querying)
# - [Assigning](#class-Set-label-Methods+for+Assigning)
# - [Deleting](#class-Set-label-Methods+for+Deleting)
# - [Converting](#class-Set-label-Methods+for+Converting)
# - [Iterating](#class-Set-label-Methods+for+Iterating)
# - [And more....](#class-Set-label-Other+Methods)
#
# ### Methods for Creating a \Set
#
# - ::[]:
# Returns a new set containing the given objects.
# - ::new:
# Returns a new set containing either the given objects
# (if no block given) or the return values from the called block
# (if a block given).
#
# ### Methods for \Set Operations
#
# - [|](#method-i-7C) (aliased as #union and #+):
# Returns a new set containing all elements from +self+
# and all elements from a given enumerable (no duplicates).
# - [&](#method-i-26) (aliased as #intersection):
# Returns a new set containing all elements common to +self+
# and a given enumerable.
# - [-](#method-i-2D) (aliased as #difference):
# Returns a copy of +self+ with all elements
# in a given enumerable removed.
# - [\^](#method-i-5E):
# Returns a new set containing all elements from +self+
# and a given enumerable except those common to both.
#
# ### Methods for Comparing
#
# - [<=>](#method-i-3C-3D-3E):
# Returns -1, 0, or 1 as +self+ is less than, equal to,
# or greater than a given object.
# - [==](#method-i-3D-3D):
# Returns whether +self+ and a given enumerable are equal,
# as determined by Object#eql?.
# - \#compare_by_identity?:
# Returns whether the set considers only identity
# when comparing elements.
#
# ### Methods for Querying
#
# - \#length (aliased as #size):
# Returns the count of elements.
# - \#empty?:
# Returns whether the set has no elements.
# - \#include? (aliased as #member? and #===):
# Returns whether a given object is an element in the set.
# - \#subset? (aliased as [<=](#method-i-3C-3D)):
# Returns whether a given object is a subset of the set.
# - \#proper_subset? (aliased as [<](#method-i-3C)):
# Returns whether a given enumerable is a proper subset of the set.
# - \#superset? (aliased as [>=](#method-i-3E-3D])):
# Returns whether a given enumerable is a superset of the set.
# - \#proper_superset? (aliased as [>](#method-i-3E)):
# Returns whether a given enumerable is a proper superset of the set.
# - \#disjoint?:
# Returns +true+ if the set and a given enumerable
# have no common elements, +false+ otherwise.
# - \#intersect?:
# Returns +true+ if the set and a given enumerable:
# have any common elements, +false+ otherwise.
# - \#compare_by_identity?:
# Returns whether the set considers only identity
# when comparing elements.
#
# ### Methods for Assigning
#
# - \#add (aliased as #<<):
# Adds a given object to the set; returns +self+.
# - \#add?:
# If the given object is not an element in the set,
# adds it and returns +self+; otherwise, returns +nil+.
# - \#merge:
# Adds each given object to the set; returns +self+.
# - \#replace:
# Replaces the contents of the set with the contents
# of a given enumerable.
#
# ### Methods for Deleting
#
# - \#clear:
# Removes all elements in the set; returns +self+.
# - \#delete:
# Removes a given object from the set; returns +self+.
# - \#delete?:
# If the given object is an element in the set,
# removes it and returns +self+; otherwise, returns +nil+.
# - \#subtract:
# Removes each given object from the set; returns +self+.
# - \#delete_if - Removes elements specified by a given block.
# - \#select! (aliased as #filter!):
# Removes elements not specified by a given block.
# - \#keep_if:
# Removes elements not specified by a given block.
# - \#reject!
# Removes elements specified by a given block.
#
# ### Methods for Converting
#
# - \#classify:
# Returns a hash that classifies the elements,
# as determined by the given block.
# - \#collect! (aliased as #map!):
# Replaces each element with a block return-value.
# - \#divide:
# Returns a hash that classifies the elements,
# as determined by the given block;
# differs from #classify in that the block may accept
# either one or two arguments.
# - \#flatten:
# Returns a new set that is a recursive flattening of +self+.
# \#flatten!:
# Replaces each nested set in +self+ with the elements from that set.
# - \#inspect (aliased as #to_s):
# Returns a string displaying the elements.
# - \#join:
# Returns a string containing all elements, converted to strings
# as needed, and joined by the given record separator.
# - \#to_a:
# Returns an array containing all set elements.
# - \#to_set:
# Returns +self+ if given no arguments and no block;
# with a block given, returns a new set consisting of block
# return values.
#
# ### Methods for Iterating
#
# - \#each:
# Calls the block with each successive element; returns +self+.
#
# ### Other Methods
#
# - \#reset:
# Resets the internal state; useful if an object
# has been modified while an element in the set.
#
class Set
include Enumerable
# Creates a new set containing the given objects.
#
# Set[1, 2] # => #<Set: {1, 2}>
# Set[1, 2, 1] # => #<Set: {1, 2}>
# Set[1, 'c', :s] # => #<Set: {1, "c", :s}>
def self.[](*ary)
new(ary)
end
# Creates a new set containing the elements of the given enumerable
# object.
#
# If a block is given, the elements of enum are preprocessed by the
# given block.
#
# Set.new([1, 2]) #=> #<Set: {1, 2}>
# Set.new([1, 2, 1]) #=> #<Set: {1, 2}>
# Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}>
# Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}>
# Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}>
def initialize(enum = nil, &block) # :yields: o
@hash ||= Hash.new(false)
enum.nil? and return
if block
do_with_enum(enum) { |o| add(block[o]) }
else
merge(enum)
end
end
# Makes the set compare its elements by their identity and returns
# self. This method may not be supported by all subclasses of Set.
def compare_by_identity
if @hash.respond_to?(:compare_by_identity)
@hash.compare_by_identity
self
else
raise NotImplementedError, "#{self.class.name}\##{__method__} is not implemented"
end
end
# Returns true if the set will compare its elements by their
# identity. Also see Set#compare_by_identity.
def compare_by_identity?
@hash.respond_to?(:compare_by_identity?) && @hash.compare_by_identity?
end
def do_with_enum(enum, &block) # :nodoc:
if enum.respond_to?(:each_entry)
enum.each_entry(&block) if block
elsif enum.respond_to?(:each)
enum.each(&block) if block
else
raise ArgumentError, "value must be enumerable"
end
end
private :do_with_enum
# Dup internal hash.
def initialize_dup(orig)
super
@hash = orig.instance_variable_get(:@hash).dup
end
if Kernel.instance_method(:initialize_clone).arity != 1
# Clone internal hash.
def initialize_clone(orig, **options)
super
@hash = orig.instance_variable_get(:@hash).clone(**options)
end
else
# Clone internal hash.
def initialize_clone(orig)
super
@hash = orig.instance_variable_get(:@hash).clone
end
end
def freeze # :nodoc:
@hash.freeze
super
end
# Returns the number of elements.
def size
@hash.size
end
alias length size
# Returns true if the set contains no elements.
def empty?
@hash.empty?
end
# Removes all elements and returns self.
#
# set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
# set.clear #=> #<Set: {}>
# set #=> #<Set: {}>
def clear
@hash.clear
self
end
# Replaces the contents of the set with the contents of the given
# enumerable object and returns self.
#
# set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
# set.replace([1, 2]) #=> #<Set: {1, 2}>
# set #=> #<Set: {1, 2}>
def replace(enum)
if enum.instance_of?(self.class)
@hash.replace(enum.instance_variable_get(:@hash))
self
else
do_with_enum(enum) # make sure enum is enumerable before calling clear
clear
merge(enum)
end
end
# Converts the set to an array. The order of elements is uncertain.
#
# Set[1, 2].to_a #=> [1, 2]
# Set[1, 'c', :s].to_a #=> [1, "c", :s]
def to_a
@hash.keys
end
# Returns self if no arguments are given. Otherwise, converts the
# set to another with `klass.new(self, *args, &block)`.
#
# In subclasses, returns `klass.new(self, *args, &block)` unless
# overridden.
def to_set(klass = Set, *args, &block)
return self if instance_of?(Set) && klass == Set && block.nil? && args.empty?
klass.new(self, *args, &block)
end
def flatten_merge(set, seen = Set.new) # :nodoc:
set.each { |e|
if e.is_a?(Set)
if seen.include?(e_id = e.object_id)
raise ArgumentError, "tried to flatten recursive Set"
end
seen.add(e_id)
flatten_merge(e, seen)
seen.delete(e_id)
else
add(e)
end
}
self
end
protected :flatten_merge
# Returns a new set that is a copy of the set, flattening each
# containing set recursively.
def flatten
self.class.new.flatten_merge(self)
end
# Equivalent to Set#flatten, but replaces the receiver with the
# result in place. Returns nil if no modifications were made.
def flatten!
replace(flatten()) if any? { |e| e.is_a?(Set) }
end
# Returns true if the set contains the given object.
#
# Note that <code>include?</code> and <code>member?</code> do not test member
# equality using <code>==</code> as do other Enumerables.
#
# See also Enumerable#include?
def include?(o)
@hash[o]
end
alias member? include?
# Returns true if the set is a superset of the given set.
def superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>=)
@hash >= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size >= set.size && set.all? { |o| include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias >= superset?
# Returns true if the set is a proper superset of the given set.
def proper_superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>)
@hash > set.instance_variable_get(:@hash)
when set.is_a?(Set)
size > set.size && set.all? { |o| include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias > proper_superset?
# Returns true if the set is a subset of the given set.
def subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<=)
@hash <= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size <= set.size && all? { |o| set.include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias <= subset?
# Returns true if the set is a proper subset of the given set.
def proper_subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<)
@hash < set.instance_variable_get(:@hash)
when set.is_a?(Set)
size < set.size && all? { |o| set.include?(o) }
else
raise ArgumentError, "value must be a set"
end
end
alias < proper_subset?
# Returns 0 if the set are equal,
# -1 / +1 if the set is a proper subset / superset of the given set,
# or nil if they both have unique elements.
def <=>(set)
return unless set.is_a?(Set)
case size <=> set.size
when -1 then -1 if proper_subset?(set)
when +1 then +1 if proper_superset?(set)
else 0 if self.==(set)
end
end
# Returns true if the set and the given enumerable have at least one
# element in common.
#
# Set[1, 2, 3].intersect? Set[4, 5] #=> false
# Set[1, 2, 3].intersect? Set[3, 4] #=> true
# Set[1, 2, 3].intersect? 4..5 #=> false
# Set[1, 2, 3].intersect? [3, 4] #=> true
def intersect?(set)
case set
when Set
if size < set.size
any? { |o| set.include?(o) }
else
set.any? { |o| include?(o) }
end
when Enumerable
set.any? { |o| include?(o) }
else
raise ArgumentError, "value must be enumerable"
end
end
# Returns true if the set and the given enumerable have
# no element in common. This method is the opposite of `intersect?`.
#
# Set[1, 2, 3].disjoint? Set[3, 4] #=> false
# Set[1, 2, 3].disjoint? Set[4, 5] #=> true
# Set[1, 2, 3].disjoint? [3, 4] #=> false
# Set[1, 2, 3].disjoint? 4..5 #=> true
def disjoint?(set)
!intersect?(set)
end
# Calls the given block once for each element in the set, passing
# the element as parameter. Returns an enumerator if no block is
# given.
def each(&block)
block or return enum_for(__method__) { size }
@hash.each_key(&block)
self
end
# Adds the given object to the set and returns self. Use `merge` to
# add many elements at once.
#
# Set[1, 2].add(3) #=> #<Set: {1, 2, 3}>
# Set[1, 2].add([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
# Set[1, 2].add(2) #=> #<Set: {1, 2}>
def add(o)
@hash[o] = true
self
end
alias << add
# Adds the given object to the set and returns self. If the
# object is already in the set, returns nil.
#
# Set[1, 2].add?(3) #=> #<Set: {1, 2, 3}>
# Set[1, 2].add?([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
# Set[1, 2].add?(2) #=> nil
def add?(o)
add(o) unless include?(o)
end
# Deletes the given object from the set and returns self. Use
# `subtract` to delete many items at once.
def delete(o)
@hash.delete(o)
self
end
# Deletes the given object from the set and returns self. If the
# object is not in the set, returns nil.
def delete?(o)
delete(o) if include?(o)
end
# Deletes every element of the set for which block evaluates to
# true, and returns self. Returns an enumerator if no block is
# given.
def delete_if
block_given? or return enum_for(__method__) { size }
# @hash.delete_if should be faster, but using it breaks the order
# of enumeration in subclasses.
select { |o| yield o }.each { |o| @hash.delete(o) }
self
end
# Deletes every element of the set for which block evaluates to
# false, and returns self. Returns an enumerator if no block is
# given.
def keep_if
block_given? or return enum_for(__method__) { size }
# @hash.keep_if should be faster, but using it breaks the order of
# enumeration in subclasses.
reject { |o| yield o }.each { |o| @hash.delete(o) }
self
end
# Replaces the elements with ones returned by `collect()`.
# Returns an enumerator if no block is given.
def collect!
block_given? or return enum_for(__method__) { size }
set = self.class.new
each { |o| set << yield(o) }
replace(set)
end
alias map! collect!
# Equivalent to Set#delete_if, but returns nil if no changes were
# made. Returns an enumerator if no block is given.
def reject!(&block)
block or return enum_for(__method__) { size }
n = size
delete_if(&block)
self if size != n
end
# Equivalent to Set#keep_if, but returns nil if no changes were
# made. Returns an enumerator if no block is given.
def select!(&block)
block or return enum_for(__method__) { size }
n = size
keep_if(&block)
self if size != n
end
# Equivalent to Set#select!
alias filter! select!
# Merges the elements of the given enumerable object to the set and
# returns self.
def merge(enum)
if enum.instance_of?(self.class)
@hash.update(enum.instance_variable_get(:@hash))
else
do_with_enum(enum) { |o| add(o) }
end
self
end
# Deletes every element that appears in the given enumerable object
# and returns self.
def subtract(enum)
do_with_enum(enum) { |o| delete(o) }
self
end
# Returns a new set built by merging the set and the elements of the
# given enumerable object.
#
# Set[1, 2, 3] | Set[2, 4, 5] #=> #<Set: {1, 2, 3, 4, 5}>
# Set[1, 5, 'z'] | (1..6) #=> #<Set: {1, 5, "z", 2, 3, 4, 6}>
def |(enum)
dup.merge(enum)
end
alias + |
alias union |
# Returns a new set built by duplicating the set, removing every
# element that appears in the given enumerable object.
#
# Set[1, 3, 5] - Set[1, 5] #=> #<Set: {3}>
# Set['a', 'b', 'z'] - ['a', 'c'] #=> #<Set: {"b", "z"}>
def -(enum)
dup.subtract(enum)
end
alias difference -
# Returns a new set containing elements common to the set and the
# given enumerable object.
#
# Set[1, 3, 5] & Set[3, 2, 1] #=> #<Set: {3, 1}>
# Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> #<Set: {"a", "b"}>
def &(enum)
n = self.class.new
if enum.is_a?(Set)
if enum.size > size
each { |o| n.add(o) if enum.include?(o) }
else
enum.each { |o| n.add(o) if include?(o) }
end
else
do_with_enum(enum) { |o| n.add(o) if include?(o) }
end
n
end
alias intersection &
# Returns a new set containing elements exclusive between the set
# and the given enumerable object. `(set ^ enum)` is equivalent to
# `((set | enum) - (set & enum))`.
#
# Set[1, 2] ^ Set[2, 3] #=> #<Set: {3, 1}>
# Set[1, 'b', 'c'] ^ ['b', 'd'] #=> #<Set: {"d", 1, "c"}>
def ^(enum)
n = Set.new(enum)
each { |o| n.add(o) unless n.delete?(o) }
n
end
# Returns true if two sets are equal. The equality of each couple
# of elements is defined according to Object#eql?.
#
# Set[1, 2] == Set[2, 1] #=> true
# Set[1, 3, 5] == Set[1, 5] #=> false
# Set['a', 'b', 'c'] == Set['a', 'c', 'b'] #=> true
# Set['a', 'b', 'c'] == ['a', 'c', 'b'] #=> false
def ==(other)
if self.equal?(other)
true
elsif other.instance_of?(self.class)
@hash == other.instance_variable_get(:@hash)
elsif other.is_a?(Set) && self.size == other.size
other.all? { |o| @hash.include?(o) }
else
false
end
end
def hash # :nodoc:
@hash.hash
end
def eql?(o) # :nodoc:
return false unless o.is_a?(Set)
@hash.eql?(o.instance_variable_get(:@hash))
end
# Resets the internal state after modification to existing elements
# and returns self.
#
# Elements will be reindexed and deduplicated.
def reset
if @hash.respond_to?(:rehash)
@hash.rehash # This should perform frozenness check.
else
raise FrozenError, "can't modify frozen #{self.class.name}" if frozen?
end
self
end
# Returns true if the given object is a member of the set,
# and false otherwise.
#
# Used in case statements:
#
# require 'set'
#
# case :apple
# when Set[:potato, :carrot]
# "vegetable"
# when Set[:apple, :banana]
# "fruit"
# end
# # => "fruit"
#
# Or by itself:
#
# Set[1, 2, 3] === 2 #=> true
# Set[1, 2, 3] === 4 #=> false
#
alias === include?
# Classifies the set by the return value of the given block and
# returns a hash of {value => set of elements} pairs. The block is
# called once for each element of the set, passing the element as
# parameter.
#
# require 'set'
# files = Set.new(Dir.glob("*.rb"))
# hash = files.classify { |f| File.mtime(f).year }
# hash #=> {2000=>#<Set: {"a.rb", "b.rb"}>,
# # 2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>,
# # 2002=>#<Set: {"f.rb"}>}
#
# Returns an enumerator if no block is given.
def classify # :yields: o
block_given? or return enum_for(__method__) { size }
h = {}
each { |i|
(h[yield(i)] ||= self.class.new).add(i)
}
h
end
# Divides the set into a set of subsets according to the commonality
# defined by the given block.
#
# If the arity of the block is 2, elements o1 and o2 are in common
# if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are
# in common if block.call(o1) == block.call(o2).
#
# require 'set'
# numbers = Set[1, 3, 4, 6, 9, 10, 11]
# set = numbers.divide { |i,j| (i - j).abs == 1 }
# set #=> #<Set: {#<Set: {1}>,
# # #<Set: {11, 9, 10}>,
# # #<Set: {3, 4}>,
# # #<Set: {6}>}>
#
# Returns an enumerator if no block is given.
def divide(&func)
func or return enum_for(__method__) { size }
if func.arity == 2
require 'tsort'
class << dig = {} # :nodoc:
include TSort
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end
each { |u|
dig[u] = a = []
each{ |v| func.call(u, v) and a << v }
}
set = Set.new()
dig.each_strongly_connected_component { |css|
set.add(self.class.new(css))
}
set
else
Set.new(classify(&func).values)
end
end
# Returns a string created by converting each element of the set to a string
# See also: Array#join
def join(separator=nil)
to_a.join(separator)
end
InspectKey = :__inspect_key__ # :nodoc:
# Returns a string containing a human-readable representation of the
# set ("#<Set: {element1, element2, ...}>").
def inspect
ids = (Thread.current[InspectKey] ||= [])
if ids.include?(object_id)
return sprintf('#<%s: {...}>', self.class.name)
end
ids << object_id
begin
return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2])
ensure
ids.pop
end
end
alias to_s inspect
def pretty_print(pp) # :nodoc:
pp.group(1, sprintf('#<%s:', self.class.name), '>') {
pp.breakable
pp.group(1, '{', '}') {
pp.seplist(self) { |o|
pp.pp o
}
}
}
end
def pretty_print_cycle(pp) # :nodoc:
pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...')
end
end
module Enumerable
# Makes a set from the enumerable object with given arguments.
# Needs to `require "set"` to use this method.
def to_set(klass = Set, *args, &block)
klass.new(self, *args, &block)
end unless method_defined?(:to_set)
end
autoload :SortedSet, "#{__dir__}/set/sorted_set"
share/ruby/ipaddr.rb 0000644 00000051143 15173547334 0010437 0 ustar 00 # frozen_string_literal: true
#
# ipaddr.rb - A class to manipulate an IP address
#
# Copyright (c) 2002 Hajimu UMEMOTO <ume@mahoroba.org>.
# Copyright (c) 2007, 2009, 2012 Akinori MUSHA <knu@iDaemons.org>.
# All rights reserved.
#
# You can redistribute and/or modify it under the same terms as Ruby.
#
# $Id$
#
# Contact:
# - Akinori MUSHA <knu@iDaemons.org> (current maintainer)
#
# TODO:
# - scope_id support
#
require 'socket'
# IPAddr provides a set of methods to manipulate an IP address. Both IPv4 and
# IPv6 are supported.
#
# == Example
#
# require 'ipaddr'
#
# ipaddr1 = IPAddr.new "3ffe:505:2::1"
#
# p ipaddr1 #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0001/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
#
# p ipaddr1.to_s #=> "3ffe:505:2::1"
#
# ipaddr2 = ipaddr1.mask(48) #=> #<IPAddr: IPv6:3ffe:0505:0002:0000:0000:0000:0000:0000/ffff:ffff:ffff:0000:0000:0000:0000:0000>
#
# p ipaddr2.to_s #=> "3ffe:505:2::"
#
# ipaddr3 = IPAddr.new "192.168.2.0/24"
#
# p ipaddr3 #=> #<IPAddr: IPv4:192.168.2.0/255.255.255.0>
class IPAddr
VERSION = "1.2.5"
# 32 bit mask for IPv4
IN4MASK = 0xffffffff
# 128 bit mask for IPv6
IN6MASK = 0xffffffffffffffffffffffffffffffff
# Format string for IPv6
IN6FORMAT = (["%.4x"] * 8).join(':').freeze
# Regexp _internally_ used for parsing IPv4 address.
RE_IPV4ADDRLIKE = %r{
\A
(\d+) \. (\d+) \. (\d+) \. (\d+)
\z
}x
# Regexp _internally_ used for parsing IPv6 address.
RE_IPV6ADDRLIKE_FULL = %r{
\A
(?:
(?: [\da-f]{1,4} : ){7} [\da-f]{1,4}
|
( (?: [\da-f]{1,4} : ){6} )
(\d+) \. (\d+) \. (\d+) \. (\d+)
)
\z
}xi
# Regexp _internally_ used for parsing IPv6 address.
RE_IPV6ADDRLIKE_COMPRESSED = %r{
\A
( (?: (?: [\da-f]{1,4} : )* [\da-f]{1,4} )? )
::
( (?:
( (?: [\da-f]{1,4} : )* )
(?:
[\da-f]{1,4}
|
(\d+) \. (\d+) \. (\d+) \. (\d+)
)
)? )
\z
}xi
# Generic IPAddr related error. Exceptions raised in this class should
# inherit from Error.
class Error < ArgumentError; end
# Raised when the provided IP address is an invalid address.
class InvalidAddressError < Error; end
# Raised when the address family is invalid such as an address with an
# unsupported family, an address with an inconsistent family, or an address
# who's family cannot be determined.
class AddressFamilyError < Error; end
# Raised when the address is an invalid length.
class InvalidPrefixError < InvalidAddressError; end
# Returns the address family of this IP address.
attr_reader :family
# Creates a new ipaddr containing the given network byte ordered
# string form of an IP address.
def self.new_ntoh(addr)
return new(ntop(addr))
end
# Convert a network byte ordered string form of an IP address into
# human readable form.
def self.ntop(addr)
case addr.size
when 4
addr.unpack('C4').join('.')
when 16
IN6FORMAT % addr.unpack('n8')
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns a new ipaddr built by bitwise AND.
def &(other)
return self.clone.set(@addr & coerce_other(other).to_i)
end
# Returns a new ipaddr built by bitwise OR.
def |(other)
return self.clone.set(@addr | coerce_other(other).to_i)
end
# Returns a new ipaddr built by bitwise right-shift.
def >>(num)
return self.clone.set(@addr >> num)
end
# Returns a new ipaddr built by bitwise left shift.
def <<(num)
return self.clone.set(addr_mask(@addr << num))
end
# Returns a new ipaddr built by bitwise negation.
def ~
return self.clone.set(addr_mask(~@addr))
end
# Returns true if two ipaddrs are equal.
def ==(other)
other = coerce_other(other)
rescue
false
else
@family == other.family && @addr == other.to_i
end
# Returns a new ipaddr built by masking IP address with the given
# prefixlen/netmask. (e.g. 8, 64, "255.255.255.0", etc.)
def mask(prefixlen)
return self.clone.mask!(prefixlen)
end
# Returns true if the given ipaddr is in the range.
#
# e.g.:
# require 'ipaddr'
# net1 = IPAddr.new("192.168.2.0/24")
# net2 = IPAddr.new("192.168.2.100")
# net3 = IPAddr.new("192.168.3.0")
# net4 = IPAddr.new("192.168.2.0/16")
# p net1.include?(net2) #=> true
# p net1.include?(net3) #=> false
# p net1.include?(net4) #=> false
# p net4.include?(net1) #=> true
def include?(other)
other = coerce_other(other)
return false unless other.family == family
range = to_range
other = other.to_range
range.begin <= other.begin && range.end >= other.end
end
alias === include?
# Returns the integer representation of the ipaddr.
def to_i
return @addr
end
# Returns a string containing the IP address representation.
def to_s
str = to_string
return str if ipv4?
str.gsub!(/\b0{1,3}([\da-f]+)\b/i, '\1')
loop do
break if str.sub!(/\A0:0:0:0:0:0:0:0\z/, '::')
break if str.sub!(/\b0:0:0:0:0:0:0\b/, ':')
break if str.sub!(/\b0:0:0:0:0:0\b/, ':')
break if str.sub!(/\b0:0:0:0:0\b/, ':')
break if str.sub!(/\b0:0:0:0\b/, ':')
break if str.sub!(/\b0:0:0\b/, ':')
break if str.sub!(/\b0:0\b/, ':')
break
end
str.sub!(/:{3,}/, '::')
if /\A::(ffff:)?([\da-f]{1,4}):([\da-f]{1,4})\z/i =~ str
str = sprintf('::%s%d.%d.%d.%d', $1, $2.hex / 256, $2.hex % 256, $3.hex / 256, $3.hex % 256)
end
str
end
# Returns a string containing the IP address representation in
# canonical form.
def to_string
str = _to_string(@addr)
if @family == Socket::AF_INET6
str << zone_id.to_s
end
return str
end
# Returns a network byte ordered string form of the IP address.
def hton
case @family
when Socket::AF_INET
return [@addr].pack('N')
when Socket::AF_INET6
return (0..7).map { |i|
(@addr >> (112 - 16 * i)) & 0xffff
}.pack('n8')
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns true if the ipaddr is an IPv4 address.
def ipv4?
return @family == Socket::AF_INET
end
# Returns true if the ipaddr is an IPv6 address.
def ipv6?
return @family == Socket::AF_INET6
end
# Returns true if the ipaddr is a loopback address.
def loopback?
case @family
when Socket::AF_INET
@addr & 0xff000000 == 0x7f000000
when Socket::AF_INET6
@addr == 1
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns true if the ipaddr is a private address. IPv4 addresses
# in 10.0.0.0/8, 172.16.0.0/12 and 192.168.0.0/16 as defined in RFC
# 1918 and IPv6 Unique Local Addresses in fc00::/7 as defined in RFC
# 4193 are considered private.
def private?
case @family
when Socket::AF_INET
@addr & 0xff000000 == 0x0a000000 || # 10.0.0.0/8
@addr & 0xfff00000 == 0xac100000 || # 172.16.0.0/12
@addr & 0xffff0000 == 0xc0a80000 # 192.168.0.0/16
when Socket::AF_INET6
@addr & 0xfe00_0000_0000_0000_0000_0000_0000_0000 == 0xfc00_0000_0000_0000_0000_0000_0000_0000
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns true if the ipaddr is a link-local address. IPv4
# addresses in 169.254.0.0/16 reserved by RFC 3927 and Link-Local
# IPv6 Unicast Addresses in fe80::/10 reserved by RFC 4291 are
# considered link-local.
def link_local?
case @family
when Socket::AF_INET
@addr & 0xffff0000 == 0xa9fe0000 # 169.254.0.0/16
when Socket::AF_INET6
@addr & 0xffc0_0000_0000_0000_0000_0000_0000_0000 == 0xfe80_0000_0000_0000_0000_0000_0000_0000
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns true if the ipaddr is an IPv4-mapped IPv6 address.
def ipv4_mapped?
return ipv6? && (@addr >> 32) == 0xffff
end
# Returns true if the ipaddr is an IPv4-compatible IPv6 address.
def ipv4_compat?
warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE
_ipv4_compat?
end
def _ipv4_compat?
if !ipv6? || (@addr >> 32) != 0
return false
end
a = (@addr & IN4MASK)
return a != 0 && a != 1
end
private :_ipv4_compat?
# Returns a new ipaddr built by converting the native IPv4 address
# into an IPv4-mapped IPv6 address.
def ipv4_mapped
if !ipv4?
raise InvalidAddressError, "not an IPv4 address: #{@addr}"
end
clone = self.clone.set(@addr | 0xffff00000000, Socket::AF_INET6)
clone.instance_variable_set(:@mask_addr, @mask_addr | 0xffffffffffffffffffffffff00000000)
clone
end
# Returns a new ipaddr built by converting the native IPv4 address
# into an IPv4-compatible IPv6 address.
def ipv4_compat
warn "IPAddr\##{__callee__} is obsolete", uplevel: 1 if $VERBOSE
if !ipv4?
raise InvalidAddressError, "not an IPv4 address: #{@addr}"
end
return self.clone.set(@addr, Socket::AF_INET6)
end
# Returns a new ipaddr built by converting the IPv6 address into a
# native IPv4 address. If the IP address is not an IPv4-mapped or
# IPv4-compatible IPv6 address, returns self.
def native
if !ipv4_mapped? && !_ipv4_compat?
return self
end
return self.clone.set(@addr & IN4MASK, Socket::AF_INET)
end
# Returns a string for DNS reverse lookup. It returns a string in
# RFC3172 form for an IPv6 address.
def reverse
case @family
when Socket::AF_INET
return _reverse + ".in-addr.arpa"
when Socket::AF_INET6
return ip6_arpa
else
raise AddressFamilyError, "unsupported address family"
end
end
# Returns a string for DNS reverse lookup compatible with RFC3172.
def ip6_arpa
if !ipv6?
raise InvalidAddressError, "not an IPv6 address: #{@addr}"
end
return _reverse + ".ip6.arpa"
end
# Returns a string for DNS reverse lookup compatible with RFC1886.
def ip6_int
if !ipv6?
raise InvalidAddressError, "not an IPv6 address: #{@addr}"
end
return _reverse + ".ip6.int"
end
# Returns the successor to the ipaddr.
def succ
return self.clone.set(@addr + 1, @family)
end
# Compares the ipaddr with another.
def <=>(other)
other = coerce_other(other)
rescue
nil
else
@addr <=> other.to_i if other.family == @family
end
include Comparable
# Checks equality used by Hash.
def eql?(other)
return self.class == other.class && self.hash == other.hash && self == other
end
# Returns a hash value used by Hash, Set, and Array classes
def hash
return ([@addr, @mask_addr, @zone_id].hash << 1) | (ipv4? ? 0 : 1)
end
# Creates a Range object for the network address.
def to_range
begin_addr = (@addr & @mask_addr)
case @family
when Socket::AF_INET
end_addr = (@addr | (IN4MASK ^ @mask_addr))
when Socket::AF_INET6
end_addr = (@addr | (IN6MASK ^ @mask_addr))
else
raise AddressFamilyError, "unsupported address family"
end
self.class.new(begin_addr, @family)..self.class.new(end_addr, @family)
end
# Returns the prefix length in bits for the ipaddr.
def prefix
case @family
when Socket::AF_INET
n = IN4MASK ^ @mask_addr
i = 32
when Socket::AF_INET6
n = IN6MASK ^ @mask_addr
i = 128
else
raise AddressFamilyError, "unsupported address family"
end
while n.positive?
n >>= 1
i -= 1
end
i
end
# Sets the prefix length in bits
def prefix=(prefix)
case prefix
when Integer
mask!(prefix)
else
raise InvalidPrefixError, "prefix must be an integer: #{@addr}"
end
end
# Returns a string containing a human-readable representation of the
# ipaddr. ("#<IPAddr: family:address/mask>")
def inspect
case @family
when Socket::AF_INET
af = "IPv4"
when Socket::AF_INET6
af = "IPv6"
zone_id = @zone_id.to_s
else
raise AddressFamilyError, "unsupported address family"
end
return sprintf("#<%s: %s:%s%s/%s>", self.class.name,
af, _to_string(@addr), zone_id, _to_string(@mask_addr))
end
# Returns the netmask in string format e.g. 255.255.0.0
def netmask
_to_string(@mask_addr)
end
# Returns the IPv6 zone identifier, if present.
# Raises InvalidAddressError if not an IPv6 address.
def zone_id
if @family == Socket::AF_INET6
@zone_id
else
raise InvalidAddressError, "not an IPv6 address"
end
end
# Returns the IPv6 zone identifier, if present.
# Raises InvalidAddressError if not an IPv6 address.
def zone_id=(zid)
if @family == Socket::AF_INET6
case zid
when nil, /\A%(\w+)\z/
@zone_id = zid
else
raise InvalidAddressError, "invalid zone identifier for address"
end
else
raise InvalidAddressError, "not an IPv6 address"
end
end
protected
# Set +@addr+, the internal stored ip address, to given +addr+. The
# parameter +addr+ is validated using the first +family+ member,
# which is +Socket::AF_INET+ or +Socket::AF_INET6+.
def set(addr, *family)
case family[0] ? family[0] : @family
when Socket::AF_INET
if addr < 0 || addr > IN4MASK
raise InvalidAddressError, "invalid address: #{@addr}"
end
when Socket::AF_INET6
if addr < 0 || addr > IN6MASK
raise InvalidAddressError, "invalid address: #{@addr}"
end
else
raise AddressFamilyError, "unsupported address family"
end
@addr = addr
if family[0]
@family = family[0]
if @family == Socket::AF_INET
@mask_addr &= IN4MASK
end
end
return self
end
# Set current netmask to given mask.
def mask!(mask)
case mask
when String
case mask
when /\A(0|[1-9]+\d*)\z/
prefixlen = mask.to_i
when /\A\d+\z/
raise InvalidPrefixError, "leading zeros in prefix"
else
m = IPAddr.new(mask)
if m.family != @family
raise InvalidPrefixError, "address family is not same: #{@addr}"
end
@mask_addr = m.to_i
n = @mask_addr ^ m.instance_variable_get(:@mask_addr)
unless ((n + 1) & n).zero?
raise InvalidPrefixError, "invalid mask #{mask}: #{@addr}"
end
@addr &= @mask_addr
return self
end
else
prefixlen = mask
end
case @family
when Socket::AF_INET
if prefixlen < 0 || prefixlen > 32
raise InvalidPrefixError, "invalid length: #{@addr}"
end
masklen = 32 - prefixlen
@mask_addr = ((IN4MASK >> masklen) << masklen)
when Socket::AF_INET6
if prefixlen < 0 || prefixlen > 128
raise InvalidPrefixError, "invalid length: #{@addr}"
end
masklen = 128 - prefixlen
@mask_addr = ((IN6MASK >> masklen) << masklen)
else
raise AddressFamilyError, "unsupported address family"
end
@addr = ((@addr >> masklen) << masklen)
return self
end
private
# Creates a new ipaddr object either from a human readable IP
# address representation in string, or from a packed in_addr value
# followed by an address family.
#
# In the former case, the following are the valid formats that will
# be recognized: "address", "address/prefixlen" and "address/mask",
# where IPv6 address may be enclosed in square brackets (`[' and
# `]'). If a prefixlen or a mask is specified, it returns a masked
# IP address. Although the address family is determined
# automatically from a specified string, you can specify one
# explicitly by the optional second argument.
#
# Otherwise an IP address is generated from a packed in_addr value
# and an address family.
#
# The IPAddr class defines many methods and operators, and some of
# those, such as &, |, include? and ==, accept a string, or a packed
# in_addr value instead of an IPAddr object.
def initialize(addr = '::', family = Socket::AF_UNSPEC)
@mask_addr = nil
if !addr.kind_of?(String)
case family
when Socket::AF_INET, Socket::AF_INET6
set(addr.to_i, family)
@mask_addr = (family == Socket::AF_INET) ? IN4MASK : IN6MASK
return
when Socket::AF_UNSPEC
raise AddressFamilyError, "address family must be specified"
else
raise AddressFamilyError, "unsupported address family: #{family}"
end
end
prefix, prefixlen = addr.split('/', 2)
if prefix =~ /\A\[(.*)\]\z/i
prefix = $1
family = Socket::AF_INET6
end
if prefix =~ /\A(.*)(%\w+)\z/
prefix = $1
zone_id = $2
family = Socket::AF_INET6
end
# It seems AI_NUMERICHOST doesn't do the job.
#Socket.getaddrinfo(left, nil, Socket::AF_INET6, Socket::SOCK_STREAM, nil,
# Socket::AI_NUMERICHOST)
@addr = @family = nil
if family == Socket::AF_UNSPEC || family == Socket::AF_INET
@addr = in_addr(prefix)
if @addr
@family = Socket::AF_INET
end
end
if !@addr && (family == Socket::AF_UNSPEC || family == Socket::AF_INET6)
@addr = in6_addr(prefix)
@family = Socket::AF_INET6
end
@zone_id = zone_id
if family != Socket::AF_UNSPEC && @family != family
raise AddressFamilyError, "address family mismatch"
end
if prefixlen
mask!(prefixlen)
else
@mask_addr = (@family == Socket::AF_INET) ? IN4MASK : IN6MASK
end
end
def coerce_other(other)
case other
when IPAddr
other
when String
self.class.new(other)
else
self.class.new(other, @family)
end
end
def in_addr(addr)
case addr
when Array
octets = addr
else
m = RE_IPV4ADDRLIKE.match(addr) or return nil
octets = m.captures
end
octets.inject(0) { |i, s|
(n = s.to_i) < 256 or raise InvalidAddressError, "invalid address: #{@addr}"
s.match(/\A0./) and raise InvalidAddressError, "zero-filled number in IPv4 address is ambiguous: #{@addr}"
i << 8 | n
}
end
def in6_addr(left)
case left
when RE_IPV6ADDRLIKE_FULL
if $2
addr = in_addr($~[2,4])
left = $1 + ':'
else
addr = 0
end
right = ''
when RE_IPV6ADDRLIKE_COMPRESSED
if $4
left.count(':') <= 6 or raise InvalidAddressError, "invalid address: #{@addr}"
addr = in_addr($~[4,4])
left = $1
right = $3 + '0:0'
else
left.count(':') <= ($1.empty? || $2.empty? ? 8 : 7) or
raise InvalidAddressError, "invalid address: #{@addr}"
left = $1
right = $2
addr = 0
end
else
raise InvalidAddressError, "invalid address: #{@addr}"
end
l = left.split(':')
r = right.split(':')
rest = 8 - l.size - r.size
if rest < 0
return nil
end
(l + Array.new(rest, '0') + r).inject(0) { |i, s|
i << 16 | s.hex
} | addr
end
def addr_mask(addr)
case @family
when Socket::AF_INET
return addr & IN4MASK
when Socket::AF_INET6
return addr & IN6MASK
else
raise AddressFamilyError, "unsupported address family"
end
end
def _reverse
case @family
when Socket::AF_INET
return (0..3).map { |i|
(@addr >> (8 * i)) & 0xff
}.join('.')
when Socket::AF_INET6
return ("%.32x" % @addr).reverse!.gsub!(/.(?!$)/, '\&.')
else
raise AddressFamilyError, "unsupported address family"
end
end
def _to_string(addr)
case @family
when Socket::AF_INET
return (0..3).map { |i|
(addr >> (24 - 8 * i)) & 0xff
}.join('.')
when Socket::AF_INET6
return (("%.32x" % addr).gsub!(/.{4}(?!$)/, '\&:'))
else
raise AddressFamilyError, "unsupported address family"
end
end
end
unless Socket.const_defined? :AF_INET6
class Socket < BasicSocket
# IPv6 protocol family
AF_INET6 = Object.new.freeze
end
class << IPSocket
private
def valid_v6?(addr)
case addr
when IPAddr::RE_IPV6ADDRLIKE_FULL
if $2
$~[2,4].all? {|i| i.to_i < 256 }
else
true
end
when IPAddr::RE_IPV6ADDRLIKE_COMPRESSED
if $4
addr.count(':') <= 6 && $~[4,4].all? {|i| i.to_i < 256}
else
addr.count(':') <= 7
end
else
false
end
end
alias getaddress_orig getaddress
public
# Returns a +String+ based representation of a valid DNS hostname,
# IPv4 or IPv6 address.
#
# IPSocket.getaddress 'localhost' #=> "::1"
# IPSocket.getaddress 'broadcasthost' #=> "255.255.255.255"
# IPSocket.getaddress 'www.ruby-lang.org' #=> "221.186.184.68"
# IPSocket.getaddress 'www.ccc.de' #=> "2a00:1328:e102:ccc0::122"
def getaddress(s)
if valid_v6?(s)
s
else
getaddress_orig(s)
end
end
end
end
share/ruby/racc.rb 0000644 00000000211 15173547334 0010072 0 ustar 00 require 'racc/compat'
require 'racc/debugflags'
require 'racc/grammar'
require 'racc/state'
require 'racc/exception'
require 'racc/info'
share/ruby/openssl.rb 0000644 00000002034 15173547334 0010652 0 ustar 00 # frozen_string_literal: true
=begin
= 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'.)
=end
require 'openssl.so'
require_relative 'openssl/bn'
require_relative 'openssl/pkey'
require_relative 'openssl/cipher'
require_relative 'openssl/digest'
require_relative 'openssl/hmac'
require_relative 'openssl/x509'
require_relative 'openssl/ssl'
require_relative 'openssl/pkcs5'
require_relative 'openssl/version'
module OpenSSL
# call-seq:
# OpenSSL.secure_compare(string, string) -> boolean
#
# Constant time memory comparison. Inputs are hashed using SHA-256 to mask
# the length of the secret. Returns +true+ if the strings are identical,
# +false+ otherwise.
def self.secure_compare(a, b)
hashed_a = OpenSSL::Digest.digest('SHA256', a)
hashed_b = OpenSSL::Digest.digest('SHA256', b)
OpenSSL.fixed_length_secure_compare(hashed_a, hashed_b) && a == b
end
end
share/ruby/digest/sha2/loader.rb 0000644 00000000070 15173547334 0012547 0 ustar 00 # frozen_string_literal: true
require 'digest/sha2.so'
share/ruby/digest/loader.rb 0000644 00000000063 15173547334 0011714 0 ustar 00 # frozen_string_literal: true
require 'digest.so'
share/ruby/digest/version.rb 0000644 00000000105 15173547334 0012130 0 ustar 00 # frozen_string_literal: true
module Digest
VERSION = "3.1.1"
end
share/ruby/digest/sha2.rb 0000644 00000007425 15173547334 0011314 0 ustar 00 # frozen_string_literal: false
#--
# sha2.rb - defines Digest::SHA2 class which wraps up the SHA256,
# SHA384, and SHA512 classes.
#++
# Copyright (c) 2006 Akinori MUSHA <knu@iDaemons.org>
#
# All rights reserved. You can redistribute and/or modify it under the same
# terms as Ruby.
#
# $Id$
require 'digest'
require 'digest/sha2/loader'
module Digest
#
# A meta digest provider class for SHA256, SHA384 and SHA512.
#
# FIPS 180-2 describes SHA2 family of digest algorithms. It defines
# three algorithms:
# * one which works on chunks of 512 bits and returns a 256-bit
# digest (SHA256),
# * one which works on chunks of 1024 bits and returns a 384-bit
# digest (SHA384),
# * and one which works on chunks of 1024 bits and returns a 512-bit
# digest (SHA512).
#
# ==Examples
# require 'digest'
#
# # Compute a complete digest
# Digest::SHA2.hexdigest 'abc' # => "ba7816bf8..."
# Digest::SHA2.new(256).hexdigest 'abc' # => "ba7816bf8..."
# Digest::SHA256.hexdigest 'abc' # => "ba7816bf8..."
#
# Digest::SHA2.new(384).hexdigest 'abc' # => "cb00753f4..."
# Digest::SHA384.hexdigest 'abc' # => "cb00753f4..."
#
# Digest::SHA2.new(512).hexdigest 'abc' # => "ddaf35a19..."
# Digest::SHA512.hexdigest 'abc' # => "ddaf35a19..."
#
# # Compute digest by chunks
# sha2 = Digest::SHA2.new # =>#<Digest::SHA2:256>
# sha2.update "ab"
# sha2 << "c" # alias for #update
# sha2.hexdigest # => "ba7816bf8..."
#
# # Use the same object to compute another digest
# sha2.reset
# sha2 << "message"
# sha2.hexdigest # => "ab530a13e..."
#
class SHA2 < Digest::Class
# call-seq:
# Digest::SHA2.new(bitlen = 256) -> digest_obj
#
# Create a new SHA2 hash object with a given bit length.
#
# Valid bit lengths are 256, 384 and 512.
def initialize(bitlen = 256)
case bitlen
when 256
@sha2 = Digest::SHA256.new
when 384
@sha2 = Digest::SHA384.new
when 512
@sha2 = Digest::SHA512.new
else
raise ArgumentError, "unsupported bit length: %s" % bitlen.inspect
end
@bitlen = bitlen
end
# call-seq:
# digest_obj.reset -> digest_obj
#
# Reset the digest to the initial state and return self.
def reset
@sha2.reset
self
end
# call-seq:
# digest_obj.update(string) -> digest_obj
# digest_obj << string -> digest_obj
#
# Update the digest using a given _string_ and return self.
def update(str)
@sha2.update(str)
self
end
alias << update
def finish # :nodoc:
@sha2.digest!
end
private :finish
# call-seq:
# digest_obj.block_length -> Integer
#
# Return the block length of the digest in bytes.
#
# Digest::SHA256.new.block_length * 8
# # => 512
# Digest::SHA384.new.block_length * 8
# # => 1024
# Digest::SHA512.new.block_length * 8
# # => 1024
def block_length
@sha2.block_length
end
# call-seq:
# digest_obj.digest_length -> Integer
#
# Return the length of the hash value (the digest) in bytes.
#
# Digest::SHA256.new.digest_length * 8
# # => 256
# Digest::SHA384.new.digest_length * 8
# # => 384
# Digest::SHA512.new.digest_length * 8
# # => 512
#
# For example, digests produced by Digest::SHA256 will always be 32 bytes
# (256 bits) in size.
def digest_length
@sha2.digest_length
end
def initialize_copy(other) # :nodoc:
@sha2 = other.instance_eval { @sha2.clone }
end
def inspect # :nodoc:
"#<%s:%d %s>" % [self.class.name, @bitlen, hexdigest]
end
end
end
share/ruby/pstore.rb 0000644 00000051023 15173547334 0010505 0 ustar 00 # frozen_string_literal: true
# = PStore -- Transactional File Storage for Ruby Objects
#
# pstore.rb -
# originally by matz
# documentation by Kev Jackson and James Edward Gray II
# improved by Hongli Lai
#
# See PStore for documentation.
require "digest"
# \PStore implements a file based persistence mechanism based on a Hash.
# User code can store hierarchies of Ruby objects (values)
# into the data store by name (keys).
# An object hierarchy may be just a single object.
# User code may later read values back from the data store
# or even update data, as needed.
#
# The transactional behavior ensures that any changes succeed or fail together.
# This can be used to ensure that the data store is not left in a transitory state,
# where some values were updated but others were not.
#
# Behind the scenes, Ruby objects are stored to the data store file with Marshal.
# That carries the usual limitations. Proc objects cannot be marshalled,
# for example.
#
# There are three important concepts here (details at the links):
#
# - {Store}[rdoc-ref:PStore@The+Store]: a store is an instance of \PStore.
# - {Entries}[rdoc-ref:PStore@Entries]: the store is hash-like;
# each entry is the key for a stored object.
# - {Transactions}[rdoc-ref:PStore@Transactions]: each transaction is a collection
# of prospective changes to the store;
# a transaction is defined in the block given with a call
# to PStore#transaction.
#
# == About the Examples
#
# Examples on this page need a store that has known properties.
# They can get a new (and populated) store by calling thus:
#
# example_store do |store|
# # Example code using store goes here.
# end
#
# All we really need to know about +example_store+
# is that it yields a fresh store with a known population of entries;
# its implementation:
#
# require 'pstore'
# require 'tempfile'
# # Yield a pristine store for use in examples.
# def example_store
# # Create the store in a temporary file.
# Tempfile.create do |file|
# store = PStore.new(file)
# # Populate the store.
# store.transaction do
# store[:foo] = 0
# store[:bar] = 1
# store[:baz] = 2
# end
# yield store
# end
# end
#
# == The Store
#
# The contents of the store are maintained in a file whose path is specified
# when the store is created (see PStore.new).
# The objects are stored and retrieved using
# module Marshal, which means that certain objects cannot be added to the store;
# see {Marshal::dump}[rdoc-ref:Marshal.dump].
#
# == Entries
#
# A store may have any number of entries.
# Each entry has a key and a value, just as in a hash:
#
# - Key: as in a hash, the key can be (almost) any object;
# see {Hash Keys}[rdoc-ref:Hash@Hash+Keys].
# You may find it convenient to keep it simple by using only
# symbols or strings as keys.
# - Value: the value may be any object that can be marshalled by \Marshal
# (see {Marshal::dump}[rdoc-ref:Marshal.dump])
# and in fact may be a collection
# (e.g., an array, a hash, a set, a range, etc).
# That collection may in turn contain nested objects,
# including collections, to any depth;
# those objects must also be \Marshal-able.
# See {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
#
# == Transactions
#
# === The Transaction Block
#
# The block given with a call to method #transaction#
# contains a _transaction_,
# which consists of calls to \PStore methods that
# read from or write to the store
# (that is, all \PStore methods except #transaction itself,
# #path, and Pstore.new):
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# store[:bat] = 3
# store.keys # => [:foo, :bar, :baz, :bat]
# end
# end
#
# Execution of the transaction is deferred until the block exits,
# and is executed _atomically_ (all-or-nothing):
# either all transaction calls are executed, or none are.
# This maintains the integrity of the store.
#
# Other code in the block (including even calls to #path and PStore.new)
# is executed immediately, not deferred.
#
# The transaction block:
#
# - May not contain a nested call to #transaction.
# - Is the only context where methods that read from or write to
# the store are allowed.
#
# As seen above, changes in a transaction are made automatically
# when the block exits.
# The block may be exited early by calling method #commit or #abort.
#
# - Method #commit triggers the update to the store and exits the block:
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# store[:bat] = 3
# store.commit
# fail 'Cannot get here'
# end
# store.transaction do
# # Update was completed.
# store.keys # => [:foo, :bar, :baz, :bat]
# end
# end
#
# - Method #abort discards the update to the store and exits the block:
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# store[:bat] = 3
# store.abort
# fail 'Cannot get here'
# end
# store.transaction do
# # Update was not completed.
# store.keys # => [:foo, :bar, :baz]
# end
# end
#
# === Read-Only Transactions
#
# By default, a transaction allows both reading from and writing to
# the store:
#
# store.transaction do
# # Read-write transaction.
# # Any code except a call to #transaction is allowed here.
# end
#
# If argument +read_only+ is passed as +true+,
# only reading is allowed:
#
# store.transaction(true) do
# # Read-only transaction:
# # Calls to #transaction, #[]=, and #delete are not allowed here.
# end
#
# == Hierarchical Values
#
# The value for an entry may be a simple object (as seen above).
# It may also be a hierarchy of objects nested to any depth:
#
# deep_store = PStore.new('deep.store')
# deep_store.transaction do
# array_of_hashes = [{}, {}, {}]
# deep_store[:array_of_hashes] = array_of_hashes
# deep_store[:array_of_hashes] # => [{}, {}, {}]
# hash_of_arrays = {foo: [], bar: [], baz: []}
# deep_store[:hash_of_arrays] = hash_of_arrays
# deep_store[:hash_of_arrays] # => {:foo=>[], :bar=>[], :baz=>[]}
# deep_store[:hash_of_arrays][:foo].push(:bat)
# deep_store[:hash_of_arrays] # => {:foo=>[:bat], :bar=>[], :baz=>[]}
# end
#
# And recall that you can use
# {dig methods}[rdoc-ref:dig_methods.rdoc]
# in a returned hierarchy of objects.
#
# == Working with the Store
#
# === Creating a Store
#
# Use method PStore.new to create a store.
# The new store creates or opens its containing file:
#
# store = PStore.new('t.store')
#
# === Modifying the Store
#
# Use method #[]= to update or create an entry:
#
# example_store do |store|
# store.transaction do
# store[:foo] = 1 # Update.
# store[:bam] = 1 # Create.
# end
# end
#
# Use method #delete to remove an entry:
#
# example_store do |store|
# store.transaction do
# store.delete(:foo)
# store[:foo] # => nil
# end
# end
#
# === Retrieving Values
#
# Use method #fetch (allows default) or #[] (defaults to +nil+)
# to retrieve an entry:
#
# example_store do |store|
# store.transaction do
# store[:foo] # => 0
# store[:nope] # => nil
# store.fetch(:baz) # => 2
# store.fetch(:nope, nil) # => nil
# store.fetch(:nope) # Raises exception.
# end
# end
#
# === Querying the Store
#
# Use method #key? to determine whether a given key exists:
#
# example_store do |store|
# store.transaction do
# store.key?(:foo) # => true
# end
# end
#
# Use method #keys to retrieve keys:
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# end
# end
#
# Use method #path to retrieve the path to the store's underlying file;
# this method may be called from outside a transaction block:
#
# store = PStore.new('t.store')
# store.path # => "t.store"
#
# == Transaction Safety
#
# For transaction safety, see:
#
# - Optional argument +thread_safe+ at method PStore.new.
# - Attribute #ultra_safe.
#
# Needless to say, if you're storing valuable data with \PStore, then you should
# backup the \PStore file from time to time.
#
# == An Example Store
#
# require "pstore"
#
# # A mock wiki object.
# class WikiPage
#
# attr_reader :page_name
#
# def initialize(page_name, author, contents)
# @page_name = page_name
# @revisions = Array.new
# add_revision(author, contents)
# end
#
# def add_revision(author, contents)
# @revisions << {created: Time.now,
# author: author,
# contents: contents}
# end
#
# def wiki_page_references
# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
# end
#
# end
#
# # Create a new wiki page.
# home_page = WikiPage.new("HomePage", "James Edward Gray II",
# "A page about the JoysOfDocumentation..." )
#
# wiki = PStore.new("wiki_pages.pstore")
# # Update page data and the index together, or not at all.
# wiki.transaction do
# # Store page.
# wiki[home_page.page_name] = home_page
# # Create page index.
# wiki[:wiki_index] ||= Array.new
# # Update wiki index.
# wiki[:wiki_index].push(*home_page.wiki_page_references)
# end
#
# # Read wiki data, setting argument read_only to true.
# wiki.transaction(true) do
# wiki.keys.each do |key|
# puts key
# puts wiki[key]
# end
# end
#
class PStore
VERSION = "0.1.2"
RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
# The error type thrown by all PStore methods.
class Error < StandardError
end
# Whether \PStore should do its best to prevent file corruptions,
# even when an unlikely error (such as memory-error or filesystem error) occurs:
#
# - +true+: changes are posted by creating a temporary file,
# writing the updated data to it, then renaming the file to the given #path.
# File integrity is maintained.
# Note: has effect only if the filesystem has atomic file rename
# (as do POSIX platforms Linux, MacOS, FreeBSD and others).
#
# - +false+ (the default): changes are posted by rewinding the open file
# and writing the updated data.
# File integrity is maintained if the filesystem raises
# no unexpected I/O error;
# if such an error occurs during a write to the store,
# the file may become corrupted.
#
attr_accessor :ultra_safe
# Returns a new \PStore object.
#
# Argument +file+ is the path to the file in which objects are to be stored;
# if the file exists, it should be one that was written by \PStore.
#
# path = 't.store'
# store = PStore.new(path)
#
# A \PStore object is
# {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
# If argument +thread_safe+ is given as +true+,
# the object is also thread-safe (at the cost of a small performance penalty):
#
# store = PStore.new(path, true)
#
def initialize(file, thread_safe = false)
dir = File::dirname(file)
unless File::directory? dir
raise PStore::Error, format("directory %s does not exist", dir)
end
if File::exist? file and not File::readable? file
raise PStore::Error, format("file %s not readable", file)
end
@filename = file
@abort = false
@ultra_safe = false
@thread_safe = thread_safe
@lock = Thread::Mutex.new
end
# Raises PStore::Error if the calling code is not in a PStore#transaction.
def in_transaction
raise PStore::Error, "not in transaction" unless @lock.locked?
end
#
# Raises PStore::Error if the calling code is not in a PStore#transaction or
# if the code is in a read-only PStore#transaction.
#
def in_transaction_wr
in_transaction
raise PStore::Error, "in read-only transaction" if @rdonly
end
private :in_transaction, :in_transaction_wr
# Returns the value for the given +key+ if the key exists.
# +nil+ otherwise;
# if not +nil+, the returned value is an object or a hierarchy of objects:
#
# example_store do |store|
# store.transaction do
# store[:foo] # => 0
# store[:nope] # => nil
# end
# end
#
# Returns +nil+ if there is no such key.
#
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
#
# Raises an exception if called outside a transaction block.
def [](key)
in_transaction
@table[key]
end
# Like #[], except that it accepts a default value for the store.
# If the +key+ does not exist:
#
# - Raises an exception if +default+ is +PStore::Error+.
# - Returns the value of +default+ otherwise:
#
# example_store do |store|
# store.transaction do
# store.fetch(:nope, nil) # => nil
# store.fetch(:nope) # Raises an exception.
# end
# end
#
# Raises an exception if called outside a transaction block.
def fetch(key, default=PStore::Error)
in_transaction
unless @table.key? key
if default == PStore::Error
raise PStore::Error, format("undefined key `%s'", key)
else
return default
end
end
@table[key]
end
# Creates or replaces the value for the given +key+:
#
# example_store do |store|
# temp.transaction do
# temp[:bat] = 3
# end
# end
#
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
#
# Raises an exception if called outside a transaction block.
def []=(key, value)
in_transaction_wr
@table[key] = value
end
# Removes and returns the value at +key+ if it exists:
#
# example_store do |store|
# store.transaction do
# store[:bat] = 3
# store.delete(:bat)
# end
# end
#
# Returns +nil+ if there is no such key.
#
# Raises an exception if called outside a transaction block.
def delete(key)
in_transaction_wr
@table.delete key
end
# Returns an array of the existing keys:
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# end
# end
#
# Raises an exception if called outside a transaction block.
#
# PStore#roots is an alias for PStore#keys.
def keys
in_transaction
@table.keys
end
alias roots keys
# Returns +true+ if +key+ exists, +false+ otherwise:
#
# example_store do |store|
# store.transaction do
# store.key?(:foo) # => true
# end
# end
#
# Raises an exception if called outside a transaction block.
#
# PStore#root? is an alias for PStore#key?.
def key?(key)
in_transaction
@table.key? key
end
alias root? key?
# Returns the string file path used to create the store:
#
# store.path # => "flat.store"
#
def path
@filename
end
# Exits the current transaction block, committing any changes
# specified in the transaction block.
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
#
# Raises an exception if called outside a transaction block.
def commit
in_transaction
@abort = false
throw :pstore_abort_transaction
end
# Exits the current transaction block, discarding any changes
# specified in the transaction block.
# See {Committing or Aborting}[rdoc-ref:PStore@Committing+or+Aborting].
#
# Raises an exception if called outside a transaction block.
def abort
in_transaction
@abort = true
throw :pstore_abort_transaction
end
# Opens a transaction block for the store.
# See {Transactions}[rdoc-ref:PStore@Transactions].
#
# With argument +read_only+ as +false+, the block may both read from
# and write to the store.
#
# With argument +read_only+ as +true+, the block may not include calls
# to #transaction, #[]=, or #delete.
#
# Raises an exception if called within a transaction block.
def transaction(read_only = false) # :yields: pstore
value = nil
if !@thread_safe
raise PStore::Error, "nested transaction" unless @lock.try_lock
else
begin
@lock.lock
rescue ThreadError
raise PStore::Error, "nested transaction"
end
end
begin
@rdonly = read_only
@abort = false
file = open_and_lock_file(@filename, read_only)
if file
begin
@table, checksum, original_data_size = load_data(file, read_only)
catch(:pstore_abort_transaction) do
value = yield(self)
end
if !@abort && !read_only
save_data(checksum, original_data_size, file)
end
ensure
file.close
end
else
# This can only occur if read_only == true.
@table = {}
catch(:pstore_abort_transaction) do
value = yield(self)
end
end
ensure
@lock.unlock
end
value
end
private
# Constant for relieving Ruby's garbage collector.
CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo|
begin
break Digest(algo)
rescue LoadError
end
end
EMPTY_STRING = ""
EMPTY_MARSHAL_DATA = Marshal.dump({})
EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA)
#
# Open the specified filename (either in read-only mode or in
# read-write mode) and lock it for reading or writing.
#
# The opened File object will be returned. If _read_only_ is true,
# and the file does not exist, then nil will be returned.
#
# All exceptions are propagated.
#
def open_and_lock_file(filename, read_only)
if read_only
begin
file = File.new(filename, **RD_ACCESS)
begin
file.flock(File::LOCK_SH)
return file
rescue
file.close
raise
end
rescue Errno::ENOENT
return nil
end
else
file = File.new(filename, **RDWR_ACCESS)
file.flock(File::LOCK_EX)
return file
end
end
# Load the given PStore file.
# If +read_only+ is true, the unmarshalled Hash will be returned.
# If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
# Hash, a checksum of the data, and the size of the data.
def load_data(file, read_only)
if read_only
begin
table = load(file)
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
rescue EOFError
# This seems to be a newly-created file.
table = {}
end
table
else
data = file.read
if data.empty?
# This seems to be a newly-created file.
table = {}
checksum = empty_marshal_checksum
size = empty_marshal_data.bytesize
else
table = load(data)
checksum = CHECKSUM_ALGO.digest(data)
size = data.bytesize
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
end
data.replace(EMPTY_STRING)
[table, checksum, size]
end
end
def on_windows?
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
self.class.__send__(:define_method, :on_windows?) do
is_windows
end
is_windows
end
def save_data(original_checksum, original_file_size, file)
new_data = dump(@table)
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
if @ultra_safe && !on_windows?
# Windows doesn't support atomic file renames.
save_data_with_atomic_file_rename_strategy(new_data, file)
else
save_data_with_fast_strategy(new_data, file)
end
end
new_data.replace(EMPTY_STRING)
end
def save_data_with_atomic_file_rename_strategy(data, file)
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
temp_file = File.new(temp_filename, **WR_ACCESS)
begin
temp_file.flock(File::LOCK_EX)
temp_file.write(data)
temp_file.flush
File.rename(temp_filename, @filename)
rescue
File.unlink(temp_file) rescue nil
raise
ensure
temp_file.close
end
end
def save_data_with_fast_strategy(data, file)
file.rewind
file.write(data)
file.truncate(data.bytesize)
end
# This method is just a wrapped around Marshal.dump
# to allow subclass overriding used in YAML::Store.
def dump(table) # :nodoc:
Marshal::dump(table)
end
# This method is just a wrapped around Marshal.load.
# to allow subclass overriding used in YAML::Store.
def load(content) # :nodoc:
Marshal::load(content)
end
def empty_marshal_data
EMPTY_MARSHAL_DATA
end
def empty_marshal_checksum
EMPTY_MARSHAL_CHECKSUM
end
end
share/ruby/un.rb 0000644 00000026257 15173547334 0007626 0 ustar 00 # frozen_string_literal: false
#
# = un.rb
#
# Copyright (c) 2003 WATANABE Hirofumi <eban@ruby-lang.org>
#
# This program is free software.
# You can distribute/modify this program under the same terms of Ruby.
#
# == Utilities to replace common UNIX commands in Makefiles etc
#
# == SYNOPSIS
#
# ruby -run -e cp -- [OPTION] SOURCE DEST
# ruby -run -e ln -- [OPTION] TARGET LINK_NAME
# ruby -run -e mv -- [OPTION] SOURCE DEST
# ruby -run -e rm -- [OPTION] FILE
# ruby -run -e mkdir -- [OPTION] DIRS
# ruby -run -e rmdir -- [OPTION] DIRS
# ruby -run -e install -- [OPTION] SOURCE DEST
# ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
# ruby -run -e touch -- [OPTION] FILE
# ruby -run -e wait_writable -- [OPTION] FILE
# ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
# ruby -run -e httpd -- [OPTION] [DocumentRoot]
# ruby -run -e colorize -- [FILE]
# ruby -run -e help [COMMAND]
require "fileutils"
require "optparse"
module FileUtils
# @fileutils_label = ""
@fileutils_output = $stdout
end
# :nodoc:
def setup(options = "", *long_options)
caller = caller_locations(1, 1)[0].label
opt_hash = {}
argv = []
OptionParser.new do |o|
options.scan(/.:?/) do |s|
opt_name = s.delete(":").intern
o.on("-" + s.tr(":", " ")) do |val|
opt_hash[opt_name] = val
end
end
long_options.each do |s|
opt_name, arg_name = s.split(/(?=[\s=])/, 2)
opt_name.delete_prefix!('--')
s = "--#{opt_name.gsub(/([A-Z]+|[a-z])([A-Z])/, '\1-\2').downcase}#{arg_name}"
puts "#{opt_name}=>#{s}" if $DEBUG
opt_name = opt_name.intern
o.on(s) do |val|
opt_hash[opt_name] = val
end
end
o.on("-v") do opt_hash[:verbose] = true end
o.on("--help") do
UN.help([caller])
exit
end
o.order!(ARGV) do |x|
if /[*?\[{]/ =~ x
argv.concat(Dir[x])
else
argv << x
end
end
end
yield argv, opt_hash
end
##
# Copy SOURCE to DEST, or multiple SOURCE(s) to DIRECTORY
#
# ruby -run -e cp -- [OPTION] SOURCE DEST
#
# -p preserve file attributes if possible
# -r copy recursively
# -l make hard link instead of copying (implies -r)
# -v verbose
#
def cp
setup("prl") do |argv, options|
cmd = "cp"
cmd += "_r" if options.delete :r
cmd = "cp_lr" if options.delete :l
options[:preserve] = true if options.delete :p
dest = argv.pop
argv = argv[0] if argv.size == 1
FileUtils.__send__ cmd, argv, dest, **options
end
end
##
# Create a link to the specified TARGET with LINK_NAME.
#
# ruby -run -e ln -- [OPTION] TARGET LINK_NAME
#
# -s make symbolic links instead of hard links
# -f remove existing destination files
# -v verbose
#
def ln
setup("sf") do |argv, options|
cmd = "ln"
cmd += "_s" if options.delete :s
options[:force] = true if options.delete :f
dest = argv.pop
argv = argv[0] if argv.size == 1
FileUtils.__send__ cmd, argv, dest, **options
end
end
##
# Rename SOURCE to DEST, or move SOURCE(s) to DIRECTORY.
#
# ruby -run -e mv -- [OPTION] SOURCE DEST
#
# -v verbose
#
def mv
setup do |argv, options|
dest = argv.pop
argv = argv[0] if argv.size == 1
FileUtils.mv argv, dest, **options
end
end
##
# Remove the FILE
#
# ruby -run -e rm -- [OPTION] FILE
#
# -f ignore nonexistent files
# -r remove the contents of directories recursively
# -v verbose
#
def rm
setup("fr") do |argv, options|
cmd = "rm"
cmd += "_r" if options.delete :r
options[:force] = true if options.delete :f
FileUtils.__send__ cmd, argv, **options
end
end
##
# Create the DIR, if they do not already exist.
#
# ruby -run -e mkdir -- [OPTION] DIR
#
# -p no error if existing, make parent directories as needed
# -v verbose
#
def mkdir
setup("p") do |argv, options|
cmd = "mkdir"
cmd += "_p" if options.delete :p
FileUtils.__send__ cmd, argv, **options
end
end
##
# Remove the DIR.
#
# ruby -run -e rmdir -- [OPTION] DIR
#
# -p remove DIRECTORY and its ancestors.
# -v verbose
#
def rmdir
setup("p") do |argv, options|
options[:parents] = true if options.delete :p
FileUtils.rmdir argv, **options
end
end
##
# Copy SOURCE to DEST.
#
# ruby -run -e install -- [OPTION] SOURCE DEST
#
# -p apply access/modification times of SOURCE files to
# corresponding destination files
# -m set permission mode (as in chmod), instead of 0755
# -o set owner user id, instead of the current owner
# -g set owner group id, instead of the current group
# -v verbose
#
def install
setup("pm:o:g:") do |argv, options|
(mode = options.delete :m) and options[:mode] = /\A\d/ =~ mode ? mode.oct : mode
options[:preserve] = true if options.delete :p
(owner = options.delete :o) and options[:owner] = owner
(group = options.delete :g) and options[:group] = group
dest = argv.pop
argv = argv[0] if argv.size == 1
FileUtils.install argv, dest, **options
end
end
##
# Change the mode of each FILE to OCTAL-MODE.
#
# ruby -run -e chmod -- [OPTION] OCTAL-MODE FILE
#
# -v verbose
#
def chmod
setup do |argv, options|
mode = argv.shift
mode = /\A\d/ =~ mode ? mode.oct : mode
FileUtils.chmod mode, argv, **options
end
end
##
# Update the access and modification times of each FILE to the current time.
#
# ruby -run -e touch -- [OPTION] FILE
#
# -v verbose
#
def touch
setup do |argv, options|
FileUtils.touch argv, **options
end
end
##
# Wait until the file becomes writable.
#
# ruby -run -e wait_writable -- [OPTION] FILE
#
# -n RETRY count to retry
# -w SEC each wait time in seconds
# -v verbose
#
def wait_writable
setup("n:w:v") do |argv, options|
verbose = options[:verbose]
n = options[:n] and n = Integer(n)
wait = (wait = options[:w]) ? Float(wait) : 0.2
argv.each do |file|
begin
File.open(file, "r+b") {}
rescue Errno::ENOENT
break
rescue Errno::EACCES => e
raise if n and (n -= 1) <= 0
if verbose
puts e
STDOUT.flush
end
sleep wait
retry
end
end
end
end
##
# Create makefile using mkmf.
#
# ruby -run -e mkmf -- [OPTION] EXTNAME [OPTION]
#
# -d ARGS run dir_config
# -h ARGS run have_header
# -l ARGS run have_library
# -f ARGS run have_func
# -v ARGS run have_var
# -t ARGS run have_type
# -m ARGS run have_macro
# -c ARGS run have_const
# --vendor install to vendor_ruby
#
def mkmf
setup("d:h:l:f:v:t:m:c:", "vendor") do |argv, options|
require 'mkmf'
opt = options[:d] and opt.split(/:/).each {|n| dir_config(*n.split(/,/))}
opt = options[:h] and opt.split(/:/).each {|n| have_header(*n.split(/,/))}
opt = options[:l] and opt.split(/:/).each {|n| have_library(*n.split(/,/))}
opt = options[:f] and opt.split(/:/).each {|n| have_func(*n.split(/,/))}
opt = options[:v] and opt.split(/:/).each {|n| have_var(*n.split(/,/))}
opt = options[:t] and opt.split(/:/).each {|n| have_type(*n.split(/,/))}
opt = options[:m] and opt.split(/:/).each {|n| have_macro(*n.split(/,/))}
opt = options[:c] and opt.split(/:/).each {|n| have_const(*n.split(/,/))}
$configure_args["--vendor"] = true if options[:vendor]
create_makefile(*argv)
end
end
##
# Run WEBrick HTTP server.
#
# ruby -run -e httpd -- [OPTION] [DocumentRoot]
#
# --bind-address=ADDR address to bind
# --port=NUM listening port number
# --max-clients=MAX max number of simultaneous clients
# --temp-dir=DIR temporary directory
# --do-not-reverse-lookup disable reverse lookup
# --request-timeout=SECOND request timeout in seconds
# --http-version=VERSION HTTP version
# --server-name=NAME name of the server host
# --server-software=NAME name and version of the server
# --ssl-certificate=CERT The SSL certificate file for the server
# --ssl-private-key=KEY The SSL private key file for the server certificate
# -v verbose
#
def httpd
setup("", "BindAddress=ADDR", "Port=PORT", "MaxClients=NUM", "TempDir=DIR",
"DoNotReverseLookup", "RequestTimeout=SECOND", "HTTPVersion=VERSION",
"ServerName=NAME", "ServerSoftware=NAME",
"SSLCertificate=CERT", "SSLPrivateKey=KEY") do
|argv, options|
begin
require 'webrick'
rescue LoadError
abort "webrick is not found. You may need to `gem install webrick` to install webrick."
end
opt = options[:RequestTimeout] and options[:RequestTimeout] = opt.to_i
[:Port, :MaxClients].each do |name|
opt = options[name] and (options[name] = Integer(opt)) rescue nil
end
if cert = options[:SSLCertificate]
key = options[:SSLPrivateKey] or
raise "--ssl-private-key option must also be given"
require 'webrick/https'
options[:SSLEnable] = true
options[:SSLCertificate] = OpenSSL::X509::Certificate.new(File.read(cert))
options[:SSLPrivateKey] = OpenSSL::PKey.read(File.read(key))
options[:Port] ||= 8443 # HTTPS Alternate
end
options[:Port] ||= 8080 # HTTP Alternate
options[:DocumentRoot] = argv.shift || '.'
s = nil
options[:StartCallback] = proc {
logger = s.logger
logger.info("To access this server, open this URL in a browser:")
s.listeners.each do |listener|
if options[:SSLEnable]
addr = listener.addr
addr[3] = "127.0.0.1" if addr[3] == "0.0.0.0"
addr[3] = "::1" if addr[3] == "::"
logger.info(" https://#{Addrinfo.new(addr).inspect_sockaddr}")
else
logger.info(" http://#{listener.connect_address.inspect_sockaddr}")
end
end
}
s = WEBrick::HTTPServer.new(options)
shut = proc {s.shutdown}
siglist = %w"TERM QUIT"
siglist.concat(%w"HUP INT") if STDIN.tty?
siglist &= Signal.list.keys
siglist.each do |sig|
Signal.trap(sig, shut)
end
s.start
end
end
##
# Colorize ruby code.
#
# ruby -run -e colorize -- [FILE]
#
def colorize
begin
require "irb/color"
rescue LoadError
raise "colorize requires irb 1.1.0 or later"
end
setup do |argv, |
if argv.empty?
puts IRB::Color.colorize_code STDIN.read
return
end
argv.each do |file|
puts IRB::Color.colorize_code File.read(file)
end
end
end
##
# Display help message.
#
# ruby -run -e help [COMMAND]
#
def help
setup do |argv,|
UN.help(argv)
end
end
module UN # :nodoc:
module_function
def help(argv, output: $stdout)
all = argv.empty?
cmd = nil
if all
store = proc {|msg| output << msg}
else
messages = {}
store = proc {|msg| messages[cmd] = msg}
end
File.open(__FILE__) do |me|
while me.gets("##\n")
if help = me.gets("\n\n")
if all or argv.include?(cmd = help[/^#\s*ruby\s.*-e\s+(\w+)/, 1])
store[help.gsub(/^# ?/, "")]
break unless all or argv.size > messages.size
end
end
end
end
if messages
argv.each {|arg| output << messages[arg]}
end
end
end
share/ruby/set/sorted_set.rb 0000644 00000000305 15173547334 0012134 0 ustar 00 begin
require 'sorted_set'
rescue ::LoadError
raise "The `SortedSet` class has been extracted from the `set` library. " \
"You must use the `sorted_set` gem or other alternatives."
end
share/ruby/readline.rb 0000644 00000000275 15173547334 0010757 0 ustar 00 begin
require 'readline.so'
rescue LoadError
require 'reline' unless defined? Reline
Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
Readline = Reline
end
share/gems/gems/json-2.6.3/lib/json 0000755 00000000000 15173547334 0012620 0 ustar 00 share/ruby/drb/timeridconv.rb 0000644 00000004245 15173547334 0012267 0 ustar 00 # frozen_string_literal: false
require_relative 'drb'
require 'monitor'
module DRb
# Timer id conversion keeps objects alive for a certain amount of time after
# their last access. The default time period is 600 seconds and can be
# changed upon initialization.
#
# To use TimerIdConv:
#
# DRb.install_id_conv TimerIdConv.new 60 # one minute
class TimerIdConv < DRbIdConv
class TimerHolder2 # :nodoc:
include MonitorMixin
class InvalidIndexError < RuntimeError; end
def initialize(keeping=600)
super()
@sentinel = Object.new
@gc = {}
@renew = {}
@keeping = keeping
@expires = nil
end
def add(obj)
synchronize do
rotate
key = obj.__id__
@renew[key] = obj
invoke_keeper
return key
end
end
def fetch(key)
synchronize do
rotate
obj = peek(key)
raise InvalidIndexError if obj == @sentinel
@renew[key] = obj # KeepIt
return obj
end
end
private
def peek(key)
return @renew.fetch(key) { @gc.fetch(key, @sentinel) }
end
def invoke_keeper
return if @expires
@expires = Time.now + @keeping
on_gc
end
def on_gc
return unless Thread.main.alive?
return if @expires.nil?
Thread.new { rotate } if @expires < Time.now
ObjectSpace.define_finalizer(Object.new) {on_gc}
end
def rotate
synchronize do
if @expires &.< Time.now
@gc = @renew # GCed
@renew = {}
@expires = @gc.empty? ? nil : Time.now + @keeping
end
end
end
end
# Creates a new TimerIdConv which will hold objects for +keeping+ seconds.
def initialize(keeping=600)
@holder = TimerHolder2.new(keeping)
end
def to_obj(ref) # :nodoc:
return super if ref.nil?
@holder.fetch(ref)
rescue TimerHolder2::InvalidIndexError
raise "invalid reference"
end
def to_id(obj) # :nodoc:
return @holder.add(obj)
end
end
end
# DRb.install_id_conv(TimerIdConv.new)
share/ruby/drb/eq.rb 0000644 00000000423 15173547334 0010343 0 ustar 00 # frozen_string_literal: false
module DRb
class DRbObject # :nodoc:
def ==(other)
return false unless DRbObject === other
(@ref == other.__drbref) && (@uri == other.__drburi)
end
def hash
[@uri, @ref].hash
end
alias eql? ==
end
end
share/ruby/drb/ssl.rb 0000644 00000027022 15173547334 0010543 0 ustar 00 # frozen_string_literal: false
require 'socket'
require 'openssl'
require_relative 'drb'
require 'singleton'
module DRb
# The protocol for DRb over an SSL socket
#
# The URI for a DRb socket over SSL is:
# <code>drbssl://<host>:<port>?<option></code>. The option is optional
class DRbSSLSocket < DRbTCPSocket
# SSLConfig handles the needed SSL information for establishing a
# DRbSSLSocket connection, including generating the X509 / RSA pair.
#
# An instance of this config can be passed to DRbSSLSocket.new,
# DRbSSLSocket.open and DRbSSLSocket.open_server
#
# See DRb::DRbSSLSocket::SSLConfig.new for more details
class SSLConfig
# Default values for a SSLConfig instance.
#
# See DRb::DRbSSLSocket::SSLConfig.new for more details
DEFAULT = {
:SSLCertificate => nil,
:SSLPrivateKey => nil,
:SSLClientCA => nil,
:SSLCACertificatePath => nil,
:SSLCACertificateFile => nil,
:SSLTmpDhCallback => nil,
:SSLVerifyMode => ::OpenSSL::SSL::VERIFY_NONE,
:SSLVerifyDepth => nil,
:SSLVerifyCallback => nil, # custom verification
:SSLCertificateStore => nil,
# Must specify if you use auto generated certificate.
:SSLCertName => nil, # e.g. [["CN","fqdn.example.com"]]
:SSLCertComment => "Generated by Ruby/OpenSSL"
}
# Create a new DRb::DRbSSLSocket::SSLConfig instance
#
# The DRb::DRbSSLSocket will take either a +config+ Hash or an instance
# of SSLConfig, and will setup the certificate for its session for the
# configuration. If want it to generate a generic certificate, the bare
# minimum is to provide the :SSLCertName
#
# === Config options
#
# From +config+ Hash:
#
# :SSLCertificate ::
# An instance of OpenSSL::X509::Certificate. If this is not provided,
# then a generic X509 is generated, with a correspond :SSLPrivateKey
#
# :SSLPrivateKey ::
# A private key instance, like OpenSSL::PKey::RSA. This key must be
# the key that signed the :SSLCertificate
#
# :SSLClientCA ::
# An OpenSSL::X509::Certificate, or Array of certificates that will
# used as ClientCAs in the SSL Context
#
# :SSLCACertificatePath ::
# A path to the directory of CA certificates. The certificates must
# be in PEM format.
#
# :SSLCACertificateFile ::
# A path to a CA certificate file, in PEM format.
#
# :SSLTmpDhCallback ::
# A DH callback. See OpenSSL::SSL::SSLContext.tmp_dh_callback
#
# :SSLVerifyMode ::
# This is the SSL verification mode. See OpenSSL::SSL::VERIFY_* for
# available modes. The default is OpenSSL::SSL::VERIFY_NONE
#
# :SSLVerifyDepth ::
# Number of CA certificates to walk, when verifying a certificate
# chain.
#
# :SSLVerifyCallback ::
# A callback to be used for additional verification. See
# OpenSSL::SSL::SSLContext.verify_callback
#
# :SSLCertificateStore ::
# A OpenSSL::X509::Store used for verification of certificates
#
# :SSLCertName ::
# Issuer name for the certificate. This is required when generating
# the certificate (if :SSLCertificate and :SSLPrivateKey were not
# given). The value of this is to be an Array of pairs:
#
# [["C", "Raleigh"], ["ST","North Carolina"],
# ["CN","fqdn.example.com"]]
#
# See also OpenSSL::X509::Name
#
# :SSLCertComment ::
# A comment to be used for generating the certificate. The default is
# "Generated by Ruby/OpenSSL"
#
#
# === Example
#
# These values can be added after the fact, like a Hash.
#
# require 'drb/ssl'
# c = DRb::DRbSSLSocket::SSLConfig.new {}
# c[:SSLCertificate] =
# OpenSSL::X509::Certificate.new(File.read('mycert.crt'))
# c[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(File.read('mycert.key'))
# c[:SSLVerifyMode] = OpenSSL::SSL::VERIFY_PEER
# c[:SSLCACertificatePath] = "/etc/ssl/certs/"
# c.setup_certificate
#
# or
#
# require 'drb/ssl'
# c = DRb::DRbSSLSocket::SSLConfig.new({
# :SSLCertName => [["CN" => DRb::DRbSSLSocket.getservername]]
# })
# c.setup_certificate
#
def initialize(config)
@config = config
@cert = config[:SSLCertificate]
@pkey = config[:SSLPrivateKey]
@ssl_ctx = nil
end
# A convenience method to access the values like a Hash
def [](key);
@config[key] || DEFAULT[key]
end
# Connect to IO +tcp+, with context of the current certificate
# configuration
def connect(tcp)
ssl = ::OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
ssl.sync = true
ssl.connect
ssl
end
# Accept connection to IO +tcp+, with context of the current certificate
# configuration
def accept(tcp)
ssl = OpenSSL::SSL::SSLSocket.new(tcp, @ssl_ctx)
ssl.sync = true
ssl.accept
ssl
end
# Ensures that :SSLCertificate and :SSLPrivateKey have been provided
# or that a new certificate is generated with the other parameters
# provided.
def setup_certificate
if @cert && @pkey
return
end
rsa = OpenSSL::PKey::RSA.new(2048){|p, n|
next unless self[:verbose]
case p
when 0; $stderr.putc "." # BN_generate_prime
when 1; $stderr.putc "+" # BN_generate_prime
when 2; $stderr.putc "*" # searching good prime,
# n = #of try,
# but also data from BN_generate_prime
when 3; $stderr.putc "\n" # found good prime, n==0 - p, n==1 - q,
# but also data from BN_generate_prime
else; $stderr.putc "*" # BN_generate_prime
end
}
cert = OpenSSL::X509::Certificate.new
cert.version = 3
cert.serial = 0
name = OpenSSL::X509::Name.new(self[:SSLCertName])
cert.subject = name
cert.issuer = name
cert.not_before = Time.now
cert.not_after = Time.now + (365*24*60*60)
cert.public_key = rsa.public_key
ef = OpenSSL::X509::ExtensionFactory.new(nil,cert)
cert.extensions = [
ef.create_extension("basicConstraints","CA:FALSE"),
ef.create_extension("subjectKeyIdentifier", "hash") ]
ef.issuer_certificate = cert
cert.add_extension(ef.create_extension("authorityKeyIdentifier",
"keyid:always,issuer:always"))
if comment = self[:SSLCertComment]
cert.add_extension(ef.create_extension("nsComment", comment))
end
cert.sign(rsa, "SHA256")
@cert = cert
@pkey = rsa
end
# Establish the OpenSSL::SSL::SSLContext with the configuration
# parameters provided.
def setup_ssl_context
ctx = ::OpenSSL::SSL::SSLContext.new
ctx.cert = @cert
ctx.key = @pkey
ctx.client_ca = self[:SSLClientCA]
ctx.ca_path = self[:SSLCACertificatePath]
ctx.ca_file = self[:SSLCACertificateFile]
ctx.tmp_dh_callback = self[:SSLTmpDhCallback]
ctx.verify_mode = self[:SSLVerifyMode]
ctx.verify_depth = self[:SSLVerifyDepth]
ctx.verify_callback = self[:SSLVerifyCallback]
ctx.cert_store = self[:SSLCertificateStore]
@ssl_ctx = ctx
end
end
# Parse the dRuby +uri+ for an SSL connection.
#
# Expects drbssl://...
#
# Raises DRbBadScheme or DRbBadURI if +uri+ is not matching or malformed
def self.parse_uri(uri) # :nodoc:
if /\Adrbssl:\/\/(.*?):(\d+)(\?(.*))?\z/ =~ uri
host = $1
port = $2.to_i
option = $4
[host, port, option]
else
raise(DRbBadScheme, uri) unless uri.start_with?('drbssl:')
raise(DRbBadURI, 'can\'t parse uri:' + uri)
end
end
# Return an DRb::DRbSSLSocket instance as a client-side connection,
# with the SSL connected. This is called from DRb::start_service or while
# connecting to a remote object:
#
# DRb.start_service 'drbssl://localhost:0', front, config
#
# +uri+ is the URI we are connected to,
# <code>'drbssl://localhost:0'</code> above, +config+ is our
# configuration. Either a Hash or DRb::DRbSSLSocket::SSLConfig
def self.open(uri, config)
host, port, = parse_uri(uri)
soc = TCPSocket.open(host, port)
ssl_conf = SSLConfig::new(config)
ssl_conf.setup_ssl_context
ssl = ssl_conf.connect(soc)
self.new(uri, ssl, ssl_conf, true)
end
# Returns a DRb::DRbSSLSocket instance as a server-side connection, with
# the SSL connected. This is called from DRb::start_service or while
# connecting to a remote object:
#
# DRb.start_service 'drbssl://localhost:0', front, config
#
# +uri+ is the URI we are connected to,
# <code>'drbssl://localhost:0'</code> above, +config+ is our
# configuration. Either a Hash or DRb::DRbSSLSocket::SSLConfig
def self.open_server(uri, config)
uri = 'drbssl://:0' unless uri
host, port, = parse_uri(uri)
if host.size == 0
host = getservername
soc = open_server_inaddr_any(host, port)
else
soc = TCPServer.open(host, port)
end
port = soc.addr[1] if port == 0
@uri = "drbssl://#{host}:#{port}"
ssl_conf = SSLConfig.new(config)
ssl_conf.setup_certificate
ssl_conf.setup_ssl_context
self.new(@uri, soc, ssl_conf, false)
end
# This is a convenience method to parse +uri+ and separate out any
# additional options appended in the +uri+.
#
# Returns an option-less uri and the option => [uri,option]
#
# The +config+ is completely unused, so passing nil is sufficient.
def self.uri_option(uri, config) # :nodoc:
host, port, option = parse_uri(uri)
return "drbssl://#{host}:#{port}", option
end
# Create a DRb::DRbSSLSocket instance.
#
# +uri+ is the URI we are connected to.
# +soc+ is the tcp socket we are bound to.
# +config+ is our configuration. Either a Hash or SSLConfig
# +is_established+ is a boolean of whether +soc+ is currently established
#
# This is called automatically based on the DRb protocol.
def initialize(uri, soc, config, is_established)
@ssl = is_established ? soc : nil
super(uri, soc.to_io, config)
end
# Returns the SSL stream
def stream; @ssl; end # :nodoc:
# Closes the SSL stream before closing the dRuby connection.
def close # :nodoc:
if @ssl
@ssl.close
@ssl = nil
end
super
end
def accept # :nodoc:
begin
while true
soc = accept_or_shutdown
return nil unless soc
break if (@acl ? @acl.allow_socket?(soc) : true)
soc.close
end
begin
ssl = @config.accept(soc)
rescue Exception
soc.close
raise
end
self.class.new(uri, ssl, @config, true)
rescue OpenSSL::SSL::SSLError
warn("#{$!.message} (#{$!.class})", uplevel: 0) if @config[:verbose]
retry
end
end
end
DRbProtocol.add_protocol(DRbSSLSocket)
end
share/ruby/drb/observer.rb 0000644 00000001237 15173547334 0011571 0 ustar 00 # frozen_string_literal: false
require 'observer'
module DRb
# The Observable module extended to DRb. See Observable for details.
module DRbObservable
include Observable
# Notifies observers of a change in state. See also
# Observable#notify_observers
def notify_observers(*arg)
if defined? @observer_state and @observer_state
if defined? @observer_peers
@observer_peers.each do |observer, method|
begin
observer.__send__(method, *arg)
rescue
delete_observer(observer)
end
end
end
@observer_state = false
end
end
end
end
share/ruby/drb/gw.rb 0000644 00000006005 15173547334 0010355 0 ustar 00 # frozen_string_literal: false
require_relative 'drb'
require 'monitor'
module DRb
# Gateway id conversion forms a gateway between different DRb protocols or
# networks.
#
# The gateway needs to install this id conversion and create servers for
# each of the protocols or networks it will be a gateway between. It then
# needs to create a server that attaches to each of these networks. For
# example:
#
# require 'drb/drb'
# require 'drb/unix'
# require 'drb/gw'
#
# DRb.install_id_conv DRb::GWIdConv.new
# gw = DRb::GW.new
# s1 = DRb::DRbServer.new 'drbunix:/path/to/gateway', gw
# s2 = DRb::DRbServer.new 'druby://example:10000', gw
#
# s1.thread.join
# s2.thread.join
#
# Each client must register services with the gateway, for example:
#
# DRb.start_service 'drbunix:', nil # an anonymous server
# gw = DRbObject.new nil, 'drbunix:/path/to/gateway'
# gw[:unix] = some_service
# DRb.thread.join
class GWIdConv < DRbIdConv
def to_obj(ref) # :nodoc:
if Array === ref && ref[0] == :DRbObject
return DRbObject.new_with(ref[1], ref[2])
end
super(ref)
end
end
# The GW provides a synchronized store for participants in the gateway to
# communicate.
class GW
include MonitorMixin
# Creates a new GW
def initialize
super()
@hash = {}
end
# Retrieves +key+ from the GW
def [](key)
synchronize do
@hash[key]
end
end
# Stores value +v+ at +key+ in the GW
def []=(key, v)
synchronize do
@hash[key] = v
end
end
end
class DRbObject # :nodoc:
def self._load(s)
uri, ref = Marshal.load(s)
if DRb.uri == uri
return ref ? DRb.to_obj(ref) : DRb.front
end
self.new_with(DRb.uri, [:DRbObject, uri, ref])
end
def _dump(lv)
if DRb.uri == @uri
if Array === @ref && @ref[0] == :DRbObject
Marshal.dump([@ref[1], @ref[2]])
else
Marshal.dump([@uri, @ref]) # ??
end
else
Marshal.dump([DRb.uri, [:DRbObject, @uri, @ref]])
end
end
end
end
=begin
DRb.install_id_conv(DRb::GWIdConv.new)
front = DRb::GW.new
s1 = DRb::DRbServer.new('drbunix:/tmp/gw_b_a', front)
s2 = DRb::DRbServer.new('drbunix:/tmp/gw_b_c', front)
s1.thread.join
s2.thread.join
=end
=begin
# foo.rb
require 'drb/drb'
class Foo
include DRbUndumped
def initialize(name, peer=nil)
@name = name
@peer = peer
end
def ping(obj)
puts "#{@name}: ping: #{obj.inspect}"
@peer.ping(self) if @peer
end
end
=end
=begin
# gw_a.rb
require 'drb/unix'
require 'foo'
obj = Foo.new('a')
DRb.start_service("drbunix:/tmp/gw_a", obj)
robj = DRbObject.new_with_uri('drbunix:/tmp/gw_b_a')
robj[:a] = obj
DRb.thread.join
=end
=begin
# gw_c.rb
require 'drb/unix'
require 'foo'
foo = Foo.new('c', nil)
DRb.start_service("drbunix:/tmp/gw_c", nil)
robj = DRbObject.new_with_uri("drbunix:/tmp/gw_b_c")
puts "c->b"
a = robj[:a]
sleep 2
a.ping(foo)
DRb.thread.join
=end
share/ruby/drb/extserv.rb 0000644 00000001510 15173547334 0011434 0 ustar 00 # frozen_string_literal: false
=begin
external service
Copyright (c) 2000,2002 Masatoshi SEKI
=end
require_relative 'drb'
require 'monitor'
module DRb
class ExtServ
include MonitorMixin
include DRbUndumped
def initialize(there, name, server=nil)
super()
@server = server || DRb::primary_server
@name = name
ro = DRbObject.new(nil, there)
synchronize do
@invoker = ro.register(name, DRbObject.new(self, @server.uri))
end
end
attr_reader :server
def front
DRbObject.new(nil, @server.uri)
end
def stop_service
synchronize do
@invoker.unregister(@name)
server = @server
@server = nil
server.stop_service
true
end
end
def alive?
@server ? @server.alive? : false
end
end
end
share/ruby/drb/extservm.rb 0000644 00000003467 15173547334 0011626 0 ustar 00 # frozen_string_literal: false
=begin
external service manager
Copyright (c) 2000 Masatoshi SEKI
=end
require_relative 'drb'
require 'monitor'
module DRb
class ExtServManager
include DRbUndumped
include MonitorMixin
@@command = {}
def self.command
@@command
end
def self.command=(cmd)
@@command = cmd
end
def initialize
super()
@cond = new_cond
@servers = {}
@waiting = []
@queue = Thread::Queue.new
@thread = invoke_thread
@uri = nil
end
attr_accessor :uri
def service(name)
synchronize do
while true
server = @servers[name]
return server if server && server.alive? # server may be `false'
invoke_service(name)
@cond.wait
end
end
end
def register(name, ro)
synchronize do
@servers[name] = ro
@cond.signal
end
self
end
alias regist register
def unregister(name)
synchronize do
@servers.delete(name)
end
end
alias unregist unregister
private
def invoke_thread
Thread.new do
while name = @queue.pop
invoke_service_command(name, @@command[name])
end
end
end
def invoke_service(name)
@queue.push(name)
end
def invoke_service_command(name, command)
raise "invalid command. name: #{name}" unless command
synchronize do
return if @servers.include?(name)
@servers[name] = false
end
uri = @uri || DRb.uri
if command.respond_to? :to_ary
command = command.to_ary + [uri, name]
pid = spawn(*command)
else
pid = spawn("#{command} #{uri} #{name}")
end
th = Process.detach(pid)
th[:drb_service] = name
th
end
end
end
share/ruby/drb/unix.rb 0000644 00000005433 15173547334 0010727 0 ustar 00 # frozen_string_literal: false
require 'socket'
require_relative 'drb'
require 'tmpdir'
raise(LoadError, "UNIXServer is required") unless defined?(UNIXServer)
module DRb
# Implements DRb over a UNIX socket
#
# DRb UNIX socket URIs look like <code>drbunix:<path>?<option></code>. The
# option is optional.
class DRbUNIXSocket < DRbTCPSocket
# :stopdoc:
def self.parse_uri(uri)
if /\Adrbunix:(.*?)(\?(.*))?\z/ =~ uri
filename = $1
option = $3
[filename, option]
else
raise(DRbBadScheme, uri) unless uri.start_with?('drbunix:')
raise(DRbBadURI, 'can\'t parse uri:' + uri)
end
end
def self.open(uri, config)
filename, = parse_uri(uri)
soc = UNIXSocket.open(filename)
self.new(uri, soc, config)
end
def self.open_server(uri, config)
filename, = parse_uri(uri)
if filename.size == 0
soc = temp_server
filename = soc.path
uri = 'drbunix:' + soc.path
else
soc = UNIXServer.open(filename)
end
owner = config[:UNIXFileOwner]
group = config[:UNIXFileGroup]
if owner || group
require 'etc'
owner = Etc.getpwnam( owner ).uid if owner
group = Etc.getgrnam( group ).gid if group
File.chown owner, group, filename
end
mode = config[:UNIXFileMode]
File.chmod(mode, filename) if mode
self.new(uri, soc, config, true)
end
def self.uri_option(uri, config)
filename, option = parse_uri(uri)
return "drbunix:#{filename}", option
end
def initialize(uri, soc, config={}, server_mode = false)
super(uri, soc, config)
set_sockopt(@socket)
@server_mode = server_mode
@acl = nil
end
# import from tempfile.rb
Max_try = 10
private
def self.temp_server
tmpdir = Dir::tmpdir
n = 0
while true
begin
tmpname = sprintf('%s/druby%d.%d', tmpdir, $$, n)
lock = tmpname + '.lock'
unless File.exist?(tmpname) or File.exist?(lock)
Dir.mkdir(lock)
break
end
rescue
raise "cannot generate tempfile `%s'" % tmpname if n >= Max_try
#sleep(1)
end
n += 1
end
soc = UNIXServer.new(tmpname)
Dir.rmdir(lock)
soc
end
public
def close
return unless @socket
shutdown # DRbProtocol#shutdown
path = @socket.path if @server_mode
@socket.close
File.unlink(path) if @server_mode
@socket = nil
close_shutdown_pipe
end
def accept
s = accept_or_shutdown
return nil unless s
self.class.new(nil, s, @config)
end
def set_sockopt(soc)
# no-op for now
end
end
DRbProtocol.add_protocol(DRbUNIXSocket)
# :startdoc:
end
share/ruby/drb/weakidconv.rb 0000644 00000002175 15173547334 0012076 0 ustar 00 # frozen_string_literal: false
require_relative 'drb'
require 'monitor'
module DRb
# To use WeakIdConv:
#
# DRb.start_service(nil, nil, {:idconv => DRb::WeakIdConv.new})
class WeakIdConv < DRbIdConv
class WeakSet
include MonitorMixin
def initialize
super()
@immutable = {}
@map = ObjectSpace::WeakMap.new
end
def add(obj)
synchronize do
begin
@map[obj] = self
rescue ArgumentError
@immutable[obj.__id__] = obj
end
return obj.__id__
end
end
def fetch(ref)
synchronize do
@immutable.fetch(ref) {
@map.each { |key, _|
return key if key.__id__ == ref
}
raise RangeError.new("invalid reference")
}
end
end
end
def initialize()
super()
@weak_set = WeakSet.new
end
def to_obj(ref) # :nodoc:
return super if ref.nil?
@weak_set.fetch(ref)
end
def to_id(obj) # :nodoc:
return @weak_set.add(obj)
end
end
end
# DRb.install_id_conv(WeakIdConv.new)
share/ruby/drb/version.rb 0000644 00000000043 15173547334 0011421 0 ustar 00 module DRb
VERSION = "2.1.1"
end
share/ruby/drb/invokemethod.rb 0000644 00000001411 15173547334 0012430 0 ustar 00 # frozen_string_literal: false
# for ruby-1.8.0
module DRb # :nodoc: all
class DRbServer
module InvokeMethod18Mixin
def block_yield(x)
if x.size == 1 && x[0].class == Array
x[0] = DRbArray.new(x[0])
end
@block.call(*x)
end
def perform_with_block
@obj.__send__(@msg_id, *@argv) do |*x|
jump_error = nil
begin
block_value = block_yield(x)
rescue LocalJumpError
jump_error = $!
end
if jump_error
case jump_error.reason
when :break
break(jump_error.exit_value)
else
raise jump_error
end
end
block_value
end
end
end
end
end
share/ruby/drb/drb.rb 0000644 00000163163 15173547334 0010520 0 ustar 00 # frozen_string_literal: false
#
# = drb/drb.rb
#
# Distributed Ruby: _dRuby_ version 2.0.4
#
# Copyright (c) 1999-2003 Masatoshi SEKI. You can redistribute it and/or
# modify it under the same terms as Ruby.
#
# Author:: Masatoshi SEKI
#
# Documentation:: William Webber (william@williamwebber.com)
#
# == Overview
#
# dRuby is a distributed object system for Ruby. It allows an object in one
# Ruby process to invoke methods on an object in another Ruby process on the
# same or a different machine.
#
# The Ruby standard library contains the core classes of the dRuby package.
# However, the full package also includes access control lists and the
# Rinda tuple-space distributed task management system, as well as a
# large number of samples. The full dRuby package can be downloaded from
# the dRuby home page (see *References*).
#
# For an introduction and examples of usage see the documentation to the
# DRb module.
#
# == References
#
# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.html]
# The dRuby home page, in Japanese. Contains the full dRuby package
# and links to other Japanese-language sources.
#
# [http://www2a.biglobe.ne.jp/~seki/ruby/druby.en.html]
# The English version of the dRuby home page.
#
# [http://pragprog.com/book/sidruby/the-druby-book]
# The dRuby Book: Distributed and Parallel Computing with Ruby
# by Masatoshi Seki and Makoto Inoue
#
# [http://www.ruby-doc.org/docs/ProgrammingRuby/html/ospace.html]
# The chapter from *Programming* *Ruby* by Dave Thomas and Andy Hunt
# which discusses dRuby.
#
# [http://www.clio.ne.jp/home/web-i31s/Flotuard/Ruby/PRC2K_seki/dRuby.en.html]
# Translation of presentation on Ruby by Masatoshi Seki.
require 'socket'
require 'io/wait'
require 'monitor'
require_relative 'eq'
#
# == Overview
#
# dRuby is a distributed object system for Ruby. It is written in
# pure Ruby and uses its own protocol. No add-in services are needed
# beyond those provided by the Ruby runtime, such as TCP sockets. It
# does not rely on or interoperate with other distributed object
# systems such as CORBA, RMI, or .NET.
#
# dRuby allows methods to be called in one Ruby process upon a Ruby
# object located in another Ruby process, even on another machine.
# References to objects can be passed between processes. Method
# arguments and return values are dumped and loaded in marshalled
# format. All of this is done transparently to both the caller of the
# remote method and the object that it is called upon.
#
# An object in a remote process is locally represented by a
# DRb::DRbObject instance. This acts as a sort of proxy for the
# remote object. Methods called upon this DRbObject instance are
# forwarded to its remote object. This is arranged dynamically at run
# time. There are no statically declared interfaces for remote
# objects, such as CORBA's IDL.
#
# dRuby calls made into a process are handled by a DRb::DRbServer
# instance within that process. This reconstitutes the method call,
# invokes it upon the specified local object, and returns the value to
# the remote caller. Any object can receive calls over dRuby. There
# is no need to implement a special interface, or mixin special
# functionality. Nor, in the general case, does an object need to
# explicitly register itself with a DRbServer in order to receive
# dRuby calls.
#
# One process wishing to make dRuby calls upon another process must
# somehow obtain an initial reference to an object in the remote
# process by some means other than as the return value of a remote
# method call, as there is initially no remote object reference it can
# invoke a method upon. This is done by attaching to the server by
# URI. Each DRbServer binds itself to a URI such as
# 'druby://example.com:8787'. A DRbServer can have an object attached
# to it that acts as the server's *front* *object*. A DRbObject can
# be explicitly created from the server's URI. This DRbObject's
# remote object will be the server's front object. This front object
# can then return references to other Ruby objects in the DRbServer's
# process.
#
# Method calls made over dRuby behave largely the same as normal Ruby
# method calls made within a process. Method calls with blocks are
# supported, as are raising exceptions. In addition to a method's
# standard errors, a dRuby call may also raise one of the
# dRuby-specific errors, all of which are subclasses of DRb::DRbError.
#
# Any type of object can be passed as an argument to a dRuby call or
# returned as its return value. By default, such objects are dumped
# or marshalled at the local end, then loaded or unmarshalled at the
# remote end. The remote end therefore receives a copy of the local
# object, not a distributed reference to it; methods invoked upon this
# copy are executed entirely in the remote process, not passed on to
# the local original. This has semantics similar to pass-by-value.
#
# However, if an object cannot be marshalled, a dRuby reference to it
# is passed or returned instead. This will turn up at the remote end
# as a DRbObject instance. All methods invoked upon this remote proxy
# are forwarded to the local object, as described in the discussion of
# DRbObjects. This has semantics similar to the normal Ruby
# pass-by-reference.
#
# The easiest way to signal that we want an otherwise marshallable
# object to be passed or returned as a DRbObject reference, rather
# than marshalled and sent as a copy, is to include the
# DRb::DRbUndumped mixin module.
#
# dRuby supports calling remote methods with blocks. As blocks (or
# rather the Proc objects that represent them) are not marshallable,
# the block executes in the local, not the remote, context. Each
# value yielded to the block is passed from the remote object to the
# local block, then the value returned by each block invocation is
# passed back to the remote execution context to be collected, before
# the collected values are finally returned to the local context as
# the return value of the method invocation.
#
# == Examples of usage
#
# For more dRuby samples, see the +samples+ directory in the full
# dRuby distribution.
#
# === dRuby in client/server mode
#
# This illustrates setting up a simple client-server drb
# system. Run the server and client code in different terminals,
# starting the server code first.
#
# ==== Server code
#
# require 'drb/drb'
#
# # The URI for the server to connect to
# URI="druby://localhost:8787"
#
# class TimeServer
#
# def get_current_time
# return Time.now
# end
#
# end
#
# # The object that handles requests on the server
# FRONT_OBJECT=TimeServer.new
#
# DRb.start_service(URI, FRONT_OBJECT)
# # Wait for the drb server thread to finish before exiting.
# DRb.thread.join
#
# ==== Client code
#
# require 'drb/drb'
#
# # The URI to connect to
# SERVER_URI="druby://localhost:8787"
#
# # Start a local DRbServer to handle callbacks.
# #
# # Not necessary for this small example, but will be required
# # as soon as we pass a non-marshallable object as an argument
# # to a dRuby call.
# #
# # Note: this must be called at least once per process to take any effect.
# # This is particularly important if your application forks.
# DRb.start_service
#
# timeserver = DRbObject.new_with_uri(SERVER_URI)
# puts timeserver.get_current_time
#
# === Remote objects under dRuby
#
# This example illustrates returning a reference to an object
# from a dRuby call. The Logger instances live in the server
# process. References to them are returned to the client process,
# where methods can be invoked upon them. These methods are
# executed in the server process.
#
# ==== Server code
#
# require 'drb/drb'
#
# URI="druby://localhost:8787"
#
# class Logger
#
# # Make dRuby send Logger instances as dRuby references,
# # not copies.
# include DRb::DRbUndumped
#
# def initialize(n, fname)
# @name = n
# @filename = fname
# end
#
# def log(message)
# File.open(@filename, "a") do |f|
# f.puts("#{Time.now}: #{@name}: #{message}")
# end
# end
#
# end
#
# # We have a central object for creating and retrieving loggers.
# # This retains a local reference to all loggers created. This
# # is so an existing logger can be looked up by name, but also
# # to prevent loggers from being garbage collected. A dRuby
# # reference to an object is not sufficient to prevent it being
# # garbage collected!
# class LoggerFactory
#
# def initialize(bdir)
# @basedir = bdir
# @loggers = {}
# end
#
# def get_logger(name)
# if !@loggers.has_key? name
# # make the filename safe, then declare it to be so
# fname = name.gsub(/[.\/\\\:]/, "_")
# @loggers[name] = Logger.new(name, @basedir + "/" + fname)
# end
# return @loggers[name]
# end
#
# end
#
# FRONT_OBJECT=LoggerFactory.new("/tmp/dlog")
#
# DRb.start_service(URI, FRONT_OBJECT)
# DRb.thread.join
#
# ==== Client code
#
# require 'drb/drb'
#
# SERVER_URI="druby://localhost:8787"
#
# DRb.start_service
#
# log_service=DRbObject.new_with_uri(SERVER_URI)
#
# ["loga", "logb", "logc"].each do |logname|
#
# logger=log_service.get_logger(logname)
#
# logger.log("Hello, world!")
# logger.log("Goodbye, world!")
# logger.log("=== EOT ===")
#
# end
#
# == Security
#
# As with all network services, security needs to be considered when
# using dRuby. By allowing external access to a Ruby object, you are
# not only allowing outside clients to call the methods you have
# defined for that object, but by default to execute arbitrary Ruby
# code on your server. Consider the following:
#
# # !!! UNSAFE CODE !!!
# ro = DRbObject::new_with_uri("druby://your.server.com:8989")
# class << ro
# undef :instance_eval # force call to be passed to remote object
# end
# ro.instance_eval("`rm -rf *`")
#
# The dangers posed by instance_eval and friends are such that a
# DRbServer should only be used when clients are trusted.
#
# A DRbServer can be configured with an access control list to
# selectively allow or deny access from specified IP addresses. The
# main druby distribution provides the ACL class for this purpose. In
# general, this mechanism should only be used alongside, rather than
# as a replacement for, a good firewall.
#
# == dRuby internals
#
# dRuby is implemented using three main components: a remote method
# call marshaller/unmarshaller; a transport protocol; and an
# ID-to-object mapper. The latter two can be directly, and the first
# indirectly, replaced, in order to provide different behaviour and
# capabilities.
#
# Marshalling and unmarshalling of remote method calls is performed by
# a DRb::DRbMessage instance. This uses the Marshal module to dump
# the method call before sending it over the transport layer, then
# reconstitute it at the other end. There is normally no need to
# replace this component, and no direct way is provided to do so.
# However, it is possible to implement an alternative marshalling
# scheme as part of an implementation of the transport layer.
#
# The transport layer is responsible for opening client and server
# network connections and forwarding dRuby request across them.
# Normally, it uses DRb::DRbMessage internally to manage marshalling
# and unmarshalling. The transport layer is managed by
# DRb::DRbProtocol. Multiple protocols can be installed in
# DRbProtocol at the one time; selection between them is determined by
# the scheme of a dRuby URI. The default transport protocol is
# selected by the scheme 'druby:', and implemented by
# DRb::DRbTCPSocket. This uses plain TCP/IP sockets for
# communication. An alternative protocol, using UNIX domain sockets,
# is implemented by DRb::DRbUNIXSocket in the file drb/unix.rb, and
# selected by the scheme 'drbunix:'. A sample implementation over
# HTTP can be found in the samples accompanying the main dRuby
# distribution.
#
# The ID-to-object mapping component maps dRuby object ids to the
# objects they refer to, and vice versa. The implementation to use
# can be specified as part of a DRb::DRbServer's configuration. The
# default implementation is provided by DRb::DRbIdConv. It uses an
# object's ObjectSpace id as its dRuby id. This means that the dRuby
# reference to that object only remains meaningful for the lifetime of
# the object's process and the lifetime of the object within that
# process. A modified implementation is provided by DRb::TimerIdConv
# in the file drb/timeridconv.rb. This implementation retains a local
# reference to all objects exported over dRuby for a configurable
# period of time (defaulting to ten minutes), to prevent them being
# garbage-collected within this time. Another sample implementation
# is provided in sample/name.rb in the main dRuby distribution. This
# allows objects to specify their own id or "name". A dRuby reference
# can be made persistent across processes by having each process
# register an object using the same dRuby name.
#
module DRb
# Superclass of all errors raised in the DRb module.
class DRbError < RuntimeError; end
# Error raised when an error occurs on the underlying communication
# protocol.
class DRbConnError < DRbError; end
# Class responsible for converting between an object and its id.
#
# This, the default implementation, uses an object's local ObjectSpace
# __id__ as its id. This means that an object's identification over
# drb remains valid only while that object instance remains alive
# within the server runtime.
#
# For alternative mechanisms, see DRb::TimerIdConv in drb/timeridconv.rb
# and DRbNameIdConv in sample/name.rb in the full drb distribution.
class DRbIdConv
# Convert an object reference id to an object.
#
# This implementation looks up the reference id in the local object
# space and returns the object it refers to.
def to_obj(ref)
ObjectSpace._id2ref(ref)
end
# Convert an object into a reference id.
#
# This implementation returns the object's __id__ in the local
# object space.
def to_id(obj)
case obj
when Object
obj.nil? ? nil : obj.__id__
when BasicObject
obj.__id__
end
end
end
# Mixin module making an object undumpable or unmarshallable.
#
# If an object which includes this module is returned by method
# called over drb, then the object remains in the server space
# and a reference to the object is returned, rather than the
# object being marshalled and moved into the client space.
module DRbUndumped
def _dump(dummy) # :nodoc:
raise TypeError, 'can\'t dump'
end
end
# Error raised by the DRb module when an attempt is made to refer to
# the context's current drb server but the context does not have one.
# See #current_server.
class DRbServerNotFound < DRbError; end
# Error raised by the DRbProtocol module when it cannot find any
# protocol implementation support the scheme specified in a URI.
class DRbBadURI < DRbError; end
# Error raised by a dRuby protocol when it doesn't support the
# scheme specified in a URI. See DRb::DRbProtocol.
class DRbBadScheme < DRbError; end
# An exception wrapping a DRb::DRbUnknown object
class DRbUnknownError < DRbError
# Create a new DRbUnknownError for the DRb::DRbUnknown object +unknown+
def initialize(unknown)
@unknown = unknown
super(unknown.name)
end
# Get the wrapped DRb::DRbUnknown object.
attr_reader :unknown
def self._load(s) # :nodoc:
Marshal::load(s)
end
def _dump(lv) # :nodoc:
Marshal::dump(@unknown)
end
end
# An exception wrapping an error object
class DRbRemoteError < DRbError
# Creates a new remote error that wraps the Exception +error+
def initialize(error)
@reason = error.class.to_s
super("#{error.message} (#{error.class})")
set_backtrace(error.backtrace)
end
# the class of the error, as a string.
attr_reader :reason
end
# Class wrapping a marshalled object whose type is unknown locally.
#
# If an object is returned by a method invoked over drb, but the
# class of the object is unknown in the client namespace, or
# the object is a constant unknown in the client namespace, then
# the still-marshalled object is returned wrapped in a DRbUnknown instance.
#
# If this object is passed as an argument to a method invoked over
# drb, then the wrapped object is passed instead.
#
# The class or constant name of the object can be read from the
# +name+ attribute. The marshalled object is held in the +buf+
# attribute.
class DRbUnknown
# Create a new DRbUnknown object.
#
# +buf+ is a string containing a marshalled object that could not
# be unmarshalled. +err+ is the error message that was raised
# when the unmarshalling failed. It is used to determine the
# name of the unmarshalled object.
def initialize(err, buf)
case err.to_s
when /uninitialized constant (\S+)/
@name = $1
when /undefined class\/module (\S+)/
@name = $1
else
@name = nil
end
@buf = buf
end
# The name of the unknown thing.
#
# Class name for unknown objects; variable name for unknown
# constants.
attr_reader :name
# Buffer contained the marshalled, unknown object.
attr_reader :buf
def self._load(s) # :nodoc:
begin
Marshal::load(s)
rescue NameError, ArgumentError
DRbUnknown.new($!, s)
end
end
def _dump(lv) # :nodoc:
@buf
end
# Attempt to load the wrapped marshalled object again.
#
# If the class of the object is now known locally, the object
# will be unmarshalled and returned. Otherwise, a new
# but identical DRbUnknown object will be returned.
def reload
self.class._load(@buf)
end
# Create a DRbUnknownError exception containing this object.
def exception
DRbUnknownError.new(self)
end
end
# An Array wrapper that can be sent to another server via DRb.
#
# All entries in the array will be dumped or be references that point to
# the local server.
class DRbArray
# Creates a new DRbArray that either dumps or wraps all the items in the
# Array +ary+ so they can be loaded by a remote DRb server.
def initialize(ary)
@ary = ary.collect { |obj|
if obj.kind_of? DRbUndumped
DRbObject.new(obj)
else
begin
Marshal.dump(obj)
obj
rescue
DRbObject.new(obj)
end
end
}
end
def self._load(s) # :nodoc:
Marshal::load(s)
end
def _dump(lv) # :nodoc:
Marshal.dump(@ary)
end
end
# Handler for sending and receiving drb messages.
#
# This takes care of the low-level marshalling and unmarshalling
# of drb requests and responses sent over the wire between server
# and client. This relieves the implementor of a new drb
# protocol layer with having to deal with these details.
#
# The user does not have to directly deal with this object in
# normal use.
class DRbMessage
def initialize(config) # :nodoc:
@load_limit = config[:load_limit]
@argc_limit = config[:argc_limit]
end
def dump(obj, error=false) # :nodoc:
case obj
when DRbUndumped
obj = make_proxy(obj, error)
when Object
# nothing
else
obj = make_proxy(obj, error)
end
begin
str = Marshal::dump(obj)
rescue
str = Marshal::dump(make_proxy(obj, error))
end
[str.size].pack('N') + str
end
def load(soc) # :nodoc:
begin
sz = soc.read(4) # sizeof (N)
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
raise(DRbConnError, 'connection closed') if sz.nil?
raise(DRbConnError, 'premature header') if sz.size < 4
sz = sz.unpack('N')[0]
raise(DRbConnError, "too large packet #{sz}") if @load_limit < sz
begin
str = soc.read(sz)
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
raise(DRbConnError, 'connection closed') if str.nil?
raise(DRbConnError, 'premature marshal format(can\'t read)') if str.size < sz
DRb.mutex.synchronize do
begin
Marshal::load(str)
rescue NameError, ArgumentError
DRbUnknown.new($!, str)
end
end
end
def send_request(stream, ref, msg_id, arg, b) # :nodoc:
ary = []
ary.push(dump(ref.__drbref))
ary.push(dump(msg_id.id2name))
ary.push(dump(arg.length))
arg.each do |e|
ary.push(dump(e))
end
ary.push(dump(b))
stream.write(ary.join(''))
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
def recv_request(stream) # :nodoc:
ref = load(stream)
ro = DRb.to_obj(ref)
msg = load(stream)
argc = load(stream)
raise(DRbConnError, "too many arguments") if @argc_limit < argc
argv = Array.new(argc, nil)
argc.times do |n|
argv[n] = load(stream)
end
block = load(stream)
return ro, msg, argv, block
end
def send_reply(stream, succ, result) # :nodoc:
stream.write(dump(succ) + dump(result, !succ))
rescue
raise(DRbConnError, $!.message, $!.backtrace)
end
def recv_reply(stream) # :nodoc:
succ = load(stream)
result = load(stream)
[succ, result]
end
private
def make_proxy(obj, error=false) # :nodoc:
if error
DRbRemoteError.new(obj)
else
DRbObject.new(obj)
end
end
end
# Module managing the underlying network protocol(s) used by drb.
#
# By default, drb uses the DRbTCPSocket protocol. Other protocols
# can be defined. A protocol must define the following class methods:
#
# [open(uri, config)] Open a client connection to the server at +uri+,
# using configuration +config+. Return a protocol
# instance for this connection.
# [open_server(uri, config)] Open a server listening at +uri+,
# using configuration +config+. Return a
# protocol instance for this listener.
# [uri_option(uri, config)] Take a URI, possibly containing an option
# component (e.g. a trailing '?param=val'),
# and return a [uri, option] tuple.
#
# All of these methods should raise a DRbBadScheme error if the URI
# does not identify the protocol they support (e.g. "druby:" for
# the standard Ruby protocol). This is how the DRbProtocol module,
# given a URI, determines which protocol implementation serves that
# protocol.
#
# The protocol instance returned by #open_server must have the
# following methods:
#
# [accept] Accept a new connection to the server. Returns a protocol
# instance capable of communicating with the client.
# [close] Close the server connection.
# [uri] Get the URI for this server.
#
# The protocol instance returned by #open must have the following methods:
#
# [send_request (ref, msg_id, arg, b)]
# Send a request to +ref+ with the given message id and arguments.
# This is most easily implemented by calling DRbMessage.send_request,
# providing a stream that sits on top of the current protocol.
# [recv_reply]
# Receive a reply from the server and return it as a [success-boolean,
# reply-value] pair. This is most easily implemented by calling
# DRb.recv_reply, providing a stream that sits on top of the
# current protocol.
# [alive?]
# Is this connection still alive?
# [close]
# Close this connection.
#
# The protocol instance returned by #open_server().accept() must have
# the following methods:
#
# [recv_request]
# Receive a request from the client and return a [object, message,
# args, block] tuple. This is most easily implemented by calling
# DRbMessage.recv_request, providing a stream that sits on top of
# the current protocol.
# [send_reply(succ, result)]
# Send a reply to the client. This is most easily implemented
# by calling DRbMessage.send_reply, providing a stream that sits
# on top of the current protocol.
# [close]
# Close this connection.
#
# A new protocol is registered with the DRbProtocol module using
# the add_protocol method.
#
# For examples of other protocols, see DRbUNIXSocket in drb/unix.rb,
# and HTTP0 in sample/http0.rb and sample/http0serv.rb in the full
# drb distribution.
module DRbProtocol
# Add a new protocol to the DRbProtocol module.
def add_protocol(prot)
@protocol.push(prot)
end
module_function :add_protocol
# Open a client connection to +uri+ with the configuration +config+.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to open the URI. Each protocol signals that it does not handle that
# URI by raising a DRbBadScheme error. If no protocol recognises the
# URI, then a DRbBadURI error is raised. If a protocol accepts the
# URI, but an error occurs in opening it, a DRbConnError is raised.
def open(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open(uri, config)
rescue DRbBadScheme
rescue DRbConnError
raise($!)
rescue
raise(DRbConnError, "#{uri} - #{$!.inspect}")
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return open(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :open
# Open a server listening for connections at +uri+ with
# configuration +config+.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to open a server at the URI. Each protocol signals that it does
# not handle that URI by raising a DRbBadScheme error. If no protocol
# recognises the URI, then a DRbBadURI error is raised. If a protocol
# accepts the URI, but an error occurs in opening it, the underlying
# error is passed on to the caller.
def open_server(uri, config, first=true)
@protocol.each do |prot|
begin
return prot.open_server(uri, config)
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return open_server(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :open_server
# Parse +uri+ into a [uri, option] pair.
#
# The DRbProtocol module asks each registered protocol in turn to
# try to parse the URI. Each protocol signals that it does not handle that
# URI by raising a DRbBadScheme error. If no protocol recognises the
# URI, then a DRbBadURI error is raised.
def uri_option(uri, config, first=true)
@protocol.each do |prot|
begin
uri, opt = prot.uri_option(uri, config)
# opt = nil if opt == ''
return uri, opt
rescue DRbBadScheme
end
end
if first && (config[:auto_load] != false)
auto_load(uri)
return uri_option(uri, config, false)
end
raise DRbBadURI, 'can\'t parse uri:' + uri
end
module_function :uri_option
def auto_load(uri) # :nodoc:
if /\Adrb([a-z0-9]+):/ =~ uri
require("drb/#{$1}") rescue nil
end
end
module_function :auto_load
end
# The default drb protocol which communicates over a TCP socket.
#
# The DRb TCP protocol URI looks like:
# <code>druby://<host>:<port>?<option></code>. The option is optional.
class DRbTCPSocket
# :stopdoc:
private
def self.parse_uri(uri)
if /\Adruby:\/\/(.*?):(\d+)(\?(.*))?\z/ =~ uri
host = $1
port = $2.to_i
option = $4
[host, port, option]
else
raise(DRbBadScheme, uri) unless uri.start_with?('druby:')
raise(DRbBadURI, 'can\'t parse uri:' + uri)
end
end
public
# Open a client connection to +uri+ (DRb URI string) using configuration
# +config+.
#
# This can raise DRb::DRbBadScheme or DRb::DRbBadURI if +uri+ is not for a
# recognized protocol. See DRb::DRbServer.new for information on built-in
# URI protocols.
def self.open(uri, config)
host, port, = parse_uri(uri)
soc = TCPSocket.open(host, port)
self.new(uri, soc, config)
end
# Returns the hostname of this server
def self.getservername
host = Socket::gethostname
begin
Socket::getaddrinfo(host, nil,
Socket::AF_UNSPEC,
Socket::SOCK_STREAM,
0,
Socket::AI_PASSIVE)[0][3]
rescue
'localhost'
end
end
# For the families available for +host+, returns a TCPServer on +port+.
# If +port+ is 0 the first available port is used. IPv4 servers are
# preferred over IPv6 servers.
def self.open_server_inaddr_any(host, port)
infos = Socket::getaddrinfo(host, nil,
Socket::AF_UNSPEC,
Socket::SOCK_STREAM,
0,
Socket::AI_PASSIVE)
families = Hash[*infos.collect { |af, *_| af }.uniq.zip([]).flatten]
return TCPServer.open('0.0.0.0', port) if families.has_key?('AF_INET')
return TCPServer.open('::', port) if families.has_key?('AF_INET6')
return TCPServer.open(port)
# :stopdoc:
end
# Open a server listening for connections at +uri+ using
# configuration +config+.
def self.open_server(uri, config)
uri = 'druby://:0' unless uri
host, port, _ = parse_uri(uri)
config = {:tcp_original_host => host}.update(config)
if host.size == 0
host = getservername
soc = open_server_inaddr_any(host, port)
else
soc = TCPServer.open(host, port)
end
port = soc.addr[1] if port == 0
config[:tcp_port] = port
uri = "druby://#{host}:#{port}"
self.new(uri, soc, config)
end
# Parse +uri+ into a [uri, option] pair.
def self.uri_option(uri, config)
host, port, option = parse_uri(uri)
return "druby://#{host}:#{port}", option
end
# Create a new DRbTCPSocket instance.
#
# +uri+ is the URI we are connected to.
# +soc+ is the tcp socket we are bound to. +config+ is our
# configuration.
def initialize(uri, soc, config={})
@uri = uri
@socket = soc
@config = config
@acl = config[:tcp_acl]
@msg = DRbMessage.new(config)
set_sockopt(@socket)
@shutdown_pipe_r, @shutdown_pipe_w = IO.pipe
end
# Get the URI that we are connected to.
attr_reader :uri
# Get the address of our TCP peer (the other end of the socket
# we are bound to.
def peeraddr
@socket.peeraddr
end
# Get the socket.
def stream; @socket; end
# On the client side, send a request to the server.
def send_request(ref, msg_id, arg, b)
@msg.send_request(stream, ref, msg_id, arg, b)
end
# On the server side, receive a request from the client.
def recv_request
@msg.recv_request(stream)
end
# On the server side, send a reply to the client.
def send_reply(succ, result)
@msg.send_reply(stream, succ, result)
end
# On the client side, receive a reply from the server.
def recv_reply
@msg.recv_reply(stream)
end
public
# Close the connection.
#
# If this is an instance returned by #open_server, then this stops
# listening for new connections altogether. If this is an instance
# returned by #open or by #accept, then it closes this particular
# client-server session.
def close
shutdown
if @socket
@socket.close
@socket = nil
end
close_shutdown_pipe
end
def close_shutdown_pipe
@shutdown_pipe_w.close
@shutdown_pipe_r.close
end
private :close_shutdown_pipe
# On the server side, for an instance returned by #open_server,
# accept a client connection and return a new instance to handle
# the server's side of this client-server session.
def accept
while true
s = accept_or_shutdown
return nil unless s
break if (@acl ? @acl.allow_socket?(s) : true)
s.close
end
if @config[:tcp_original_host].to_s.size == 0
uri = "druby://#{s.addr[3]}:#{@config[:tcp_port]}"
else
uri = @uri
end
self.class.new(uri, s, @config)
end
def accept_or_shutdown
readables, = IO.select([@socket, @shutdown_pipe_r])
if readables.include? @shutdown_pipe_r
return nil
end
@socket.accept
end
private :accept_or_shutdown
# Graceful shutdown
def shutdown
@shutdown_pipe_w.close
end
# Check to see if this connection is alive.
def alive?
return false unless @socket
if @socket.to_io.wait_readable(0)
close
return false
end
true
end
def set_sockopt(soc) # :nodoc:
soc.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
rescue IOError, Errno::ECONNRESET, Errno::EINVAL
# closed/shutdown socket, ignore error
end
end
module DRbProtocol
@protocol = [DRbTCPSocket] # default
end
class DRbURIOption # :nodoc: I don't understand the purpose of this class...
def initialize(option)
@option = option.to_s
end
attr_reader :option
def to_s; @option; end
def ==(other)
return false unless DRbURIOption === other
@option == other.option
end
def hash
@option.hash
end
alias eql? ==
end
# Object wrapping a reference to a remote drb object.
#
# Method calls on this object are relayed to the remote
# object that this object is a stub for.
class DRbObject
# Unmarshall a marshalled DRbObject.
#
# If the referenced object is located within the local server, then
# the object itself is returned. Otherwise, a new DRbObject is
# created to act as a stub for the remote referenced object.
def self._load(s)
uri, ref = Marshal.load(s)
if DRb.here?(uri)
obj = DRb.to_obj(ref)
return obj
end
self.new_with(uri, ref)
end
# Creates a DRb::DRbObject given the reference information to the remote
# host +uri+ and object +ref+.
def self.new_with(uri, ref)
it = self.allocate
it.instance_variable_set(:@uri, uri)
it.instance_variable_set(:@ref, ref)
it
end
# Create a new DRbObject from a URI alone.
def self.new_with_uri(uri)
self.new(nil, uri)
end
# Marshall this object.
#
# The URI and ref of the object are marshalled.
def _dump(lv)
Marshal.dump([@uri, @ref])
end
# Create a new remote object stub.
#
# +obj+ is the (local) object we want to create a stub for. Normally
# this is +nil+. +uri+ is the URI of the remote object that this
# will be a stub for.
def initialize(obj, uri=nil)
@uri = nil
@ref = nil
case obj
when Object
is_nil = obj.nil?
when BasicObject
is_nil = false
end
if is_nil
return if uri.nil?
@uri, option = DRbProtocol.uri_option(uri, DRb.config)
@ref = DRbURIOption.new(option) unless option.nil?
else
@uri = uri ? uri : (DRb.uri rescue nil)
@ref = obj ? DRb.to_id(obj) : nil
end
end
# Get the URI of the remote object.
def __drburi
@uri
end
# Get the reference of the object, if local.
def __drbref
@ref
end
undef :to_s
undef :to_a if respond_to?(:to_a)
# Routes respond_to? to the referenced remote object.
def respond_to?(msg_id, priv=false)
case msg_id
when :_dump
true
when :marshal_dump
false
else
method_missing(:respond_to?, msg_id, priv)
end
end
# Routes method calls to the referenced remote object.
ruby2_keywords def method_missing(msg_id, *a, &b)
if DRb.here?(@uri)
obj = DRb.to_obj(@ref)
DRb.current_server.check_insecure_method(obj, msg_id)
return obj.__send__(msg_id, *a, &b)
end
succ, result = self.class.with_friend(@uri) do
DRbConn.open(@uri) do |conn|
conn.send_message(self, msg_id, a, b)
end
end
if succ
return result
elsif DRbUnknown === result
raise result
else
bt = self.class.prepare_backtrace(@uri, result)
result.set_backtrace(bt + caller)
raise result
end
end
# Given the +uri+ of another host executes the block provided.
def self.with_friend(uri) # :nodoc:
friend = DRb.fetch_server(uri)
return yield() unless friend
save = Thread.current['DRb']
Thread.current['DRb'] = { 'server' => friend }
return yield
ensure
Thread.current['DRb'] = save if friend
end
# Returns a modified backtrace from +result+ with the +uri+ where each call
# in the backtrace came from.
def self.prepare_backtrace(uri, result) # :nodoc:
prefix = "(#{uri}) "
bt = []
result.backtrace.each do |x|
break if /`__send__'$/ =~ x
if /\A\(druby:\/\// =~ x
bt.push(x)
else
bt.push(prefix + x)
end
end
bt
end
def pretty_print(q) # :nodoc:
q.pp_object(self)
end
def pretty_print_cycle(q) # :nodoc:
q.object_address_group(self) {
q.breakable
q.text '...'
}
end
end
class ThreadObject
include MonitorMixin
def initialize(&blk)
super()
@wait_ev = new_cond
@req_ev = new_cond
@res_ev = new_cond
@status = :wait
@req = nil
@res = nil
@thread = Thread.new(self, &blk)
end
def alive?
@thread.alive?
end
def kill
@thread.kill
@thread.join
end
def method_missing(msg, *arg, &blk)
synchronize do
@wait_ev.wait_until { @status == :wait }
@req = [msg] + arg
@status = :req
@req_ev.broadcast
@res_ev.wait_until { @status == :res }
value = @res
@req = @res = nil
@status = :wait
@wait_ev.broadcast
return value
end
end
def _execute()
synchronize do
@req_ev.wait_until { @status == :req }
@res = yield(@req)
@status = :res
@res_ev.signal
end
end
end
# Class handling the connection between a DRbObject and the
# server the real object lives on.
#
# This class maintains a pool of connections, to reduce the
# overhead of starting and closing down connections for each
# method call.
#
# This class is used internally by DRbObject. The user does
# not normally need to deal with it directly.
class DRbConn
POOL_SIZE = 16 # :nodoc:
def self.make_pool
ThreadObject.new do |queue|
pool = []
while true
queue._execute do |message|
case(message[0])
when :take then
remote_uri = message[1]
conn = nil
new_pool = []
pool.each do |c|
if conn.nil? and c.uri == remote_uri
conn = c if c.alive?
else
new_pool.push c
end
end
pool = new_pool
conn
when :store then
conn = message[1]
pool.unshift(conn)
pool.pop.close while pool.size > POOL_SIZE
conn
else
nil
end
end
end
end
end
@pool_proxy = nil
def self.stop_pool
@pool_proxy&.kill
@pool_proxy = nil
end
def self.open(remote_uri) # :nodoc:
begin
@pool_proxy = make_pool unless @pool_proxy&.alive?
conn = @pool_proxy.take(remote_uri)
conn = self.new(remote_uri) unless conn
succ, result = yield(conn)
return succ, result
ensure
if conn
if succ
@pool_proxy.store(conn)
else
conn.close
end
end
end
end
def initialize(remote_uri) # :nodoc:
@uri = remote_uri
@protocol = DRbProtocol.open(remote_uri, DRb.config)
end
attr_reader :uri # :nodoc:
def send_message(ref, msg_id, arg, block) # :nodoc:
@protocol.send_request(ref, msg_id, arg, block)
@protocol.recv_reply
end
def close # :nodoc:
@protocol.close
@protocol = nil
end
def alive? # :nodoc:
return false unless @protocol
@protocol.alive?
end
end
# Class representing a drb server instance.
#
# A DRbServer must be running in the local process before any incoming
# dRuby calls can be accepted, or any local objects can be passed as
# dRuby references to remote processes, even if those local objects are
# never actually called remotely. You do not need to start a DRbServer
# in the local process if you are only making outgoing dRuby calls
# passing marshalled parameters.
#
# Unless multiple servers are being used, the local DRbServer is normally
# started by calling DRb.start_service.
class DRbServer
@@acl = nil
@@idconv = DRbIdConv.new
@@secondary_server = nil
@@argc_limit = 256
@@load_limit = 0xffffffff
@@verbose = false
# Set the default value for the :argc_limit option.
#
# See #new(). The initial default value is 256.
def self.default_argc_limit(argc)
@@argc_limit = argc
end
# Set the default value for the :load_limit option.
#
# See #new(). The initial default value is 25 MB.
def self.default_load_limit(sz)
@@load_limit = sz
end
# Set the default access control list to +acl+. The default ACL is +nil+.
#
# See also DRb::ACL and #new()
def self.default_acl(acl)
@@acl = acl
end
# Set the default value for the :id_conv option.
#
# See #new(). The initial default value is a DRbIdConv instance.
def self.default_id_conv(idconv)
@@idconv = idconv
end
# Set the default value of the :verbose option.
#
# See #new(). The initial default value is false.
def self.verbose=(on)
@@verbose = on
end
# Get the default value of the :verbose option.
def self.verbose
@@verbose
end
def self.make_config(hash={}) # :nodoc:
default_config = {
:idconv => @@idconv,
:verbose => @@verbose,
:tcp_acl => @@acl,
:load_limit => @@load_limit,
:argc_limit => @@argc_limit,
}
default_config.update(hash)
end
# Create a new DRbServer instance.
#
# +uri+ is the URI to bind to. This is normally of the form
# 'druby://<hostname>:<port>' where <hostname> is a hostname of
# the local machine. If nil, then the system's default hostname
# will be bound to, on a port selected by the system; these value
# can be retrieved from the +uri+ attribute. 'druby:' specifies
# the default dRuby transport protocol: another protocol, such
# as 'drbunix:', can be specified instead.
#
# +front+ is the front object for the server, that is, the object
# to which remote method calls on the server will be passed. If
# nil, then the server will not accept remote method calls.
#
# If +config_or_acl+ is a hash, it is the configuration to
# use for this server. The following options are recognised:
#
# :idconv :: an id-to-object conversion object. This defaults
# to an instance of the class DRb::DRbIdConv.
# :verbose :: if true, all unsuccessful remote calls on objects
# in the server will be logged to $stdout. false
# by default.
# :tcp_acl :: the access control list for this server. See
# the ACL class from the main dRuby distribution.
# :load_limit :: the maximum message size in bytes accepted by
# the server. Defaults to 25 MB (26214400).
# :argc_limit :: the maximum number of arguments to a remote
# method accepted by the server. Defaults to
# 256.
# The default values of these options can be modified on
# a class-wide basis by the class methods #default_argc_limit,
# #default_load_limit, #default_acl, #default_id_conv,
# and #verbose=
#
# If +config_or_acl+ is not a hash, but is not nil, it is
# assumed to be the access control list for this server.
# See the :tcp_acl option for more details.
#
# If no other server is currently set as the primary server,
# this will become the primary server.
#
# The server will immediately start running in its own thread.
def initialize(uri=nil, front=nil, config_or_acl=nil)
if Hash === config_or_acl
config = config_or_acl.dup
else
acl = config_or_acl || @@acl
config = {
:tcp_acl => acl
}
end
@config = self.class.make_config(config)
@protocol = DRbProtocol.open_server(uri, @config)
@uri = @protocol.uri
@exported_uri = [@uri]
@front = front
@idconv = @config[:idconv]
@grp = ThreadGroup.new
@thread = run
DRb.regist_server(self)
end
# The URI of this DRbServer.
attr_reader :uri
# The main thread of this DRbServer.
#
# This is the thread that listens for and accepts connections
# from clients, not that handles each client's request-response
# session.
attr_reader :thread
# The front object of the DRbServer.
#
# This object receives remote method calls made on the server's
# URI alone, with an object id.
attr_reader :front
# The configuration of this DRbServer
attr_reader :config
# Set whether to operate in verbose mode.
#
# In verbose mode, failed calls are logged to stdout.
def verbose=(v); @config[:verbose]=v; end
# Get whether the server is in verbose mode.
#
# In verbose mode, failed calls are logged to stdout.
def verbose; @config[:verbose]; end
# Is this server alive?
def alive?
@thread.alive?
end
# Is +uri+ the URI for this server?
def here?(uri)
@exported_uri.include?(uri)
end
# Stop this server.
def stop_service
DRb.remove_server(self)
if Thread.current['DRb'] && Thread.current['DRb']['server'] == self
Thread.current['DRb']['stop_service'] = true
else
shutdown
end
end
# Convert a dRuby reference to the local object it refers to.
def to_obj(ref)
return front if ref.nil?
return front[ref.to_s] if DRbURIOption === ref
@idconv.to_obj(ref)
end
# Convert a local object to a dRuby reference.
def to_id(obj)
return nil if obj.__id__ == front.__id__
@idconv.to_id(obj)
end
private
def shutdown
current = Thread.current
if @protocol.respond_to? :shutdown
@protocol.shutdown
else
[@thread, *@grp.list].each { |thread|
thread.kill unless thread == current # xxx: Thread#kill
}
end
@thread.join unless @thread == current
end
##
# Starts the DRb main loop in a new thread.
def run
Thread.start do
begin
while main_loop
end
ensure
@protocol.close if @protocol
end
end
end
# List of insecure methods.
#
# These methods are not callable via dRuby.
INSECURE_METHOD = [
:__send__
]
# Has a method been included in the list of insecure methods?
def insecure_method?(msg_id)
INSECURE_METHOD.include?(msg_id)
end
# Coerce an object to a string, providing our own representation if
# to_s is not defined for the object.
def any_to_s(obj)
"#{obj}:#{obj.class}"
rescue
Kernel.instance_method(:to_s).bind_call(obj)
end
# Check that a method is callable via dRuby.
#
# +obj+ is the object we want to invoke the method on. +msg_id+ is the
# method name, as a Symbol.
#
# If the method is an insecure method (see #insecure_method?) a
# SecurityError is thrown. If the method is private or undefined,
# a NameError is thrown.
def check_insecure_method(obj, msg_id)
return true if Proc === obj && msg_id == :__drb_yield
raise(ArgumentError, "#{any_to_s(msg_id)} is not a symbol") unless Symbol == msg_id.class
raise(SecurityError, "insecure method `#{msg_id}'") if insecure_method?(msg_id)
case obj
when Object
if obj.private_methods.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "private method `#{msg_id}' called for #{desc}"
elsif obj.protected_methods.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "protected method `#{msg_id}' called for #{desc}"
else
true
end
else
if Kernel.instance_method(:private_methods).bind(obj).call.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "private method `#{msg_id}' called for #{desc}"
elsif Kernel.instance_method(:protected_methods).bind(obj).call.include?(msg_id)
desc = any_to_s(obj)
raise NoMethodError, "protected method `#{msg_id}' called for #{desc}"
else
true
end
end
end
public :check_insecure_method
class InvokeMethod # :nodoc:
def initialize(drb_server, client)
@drb_server = drb_server
@client = client
end
def perform
begin
setup_message
ensure
@result = nil
@succ = false
end
if @block
@result = perform_with_block
else
@result = perform_without_block
end
@succ = true
case @result
when Array
if @msg_id == :to_ary
@result = DRbArray.new(@result)
end
end
return @succ, @result
rescue NoMemoryError, SystemExit, SystemStackError, SecurityError
raise
rescue Exception
@result = $!
return @succ, @result
end
private
def init_with_client
obj, msg, argv, block = @client.recv_request
@obj = obj
@msg_id = msg.intern
@argv = argv
@block = block
end
def check_insecure_method
@drb_server.check_insecure_method(@obj, @msg_id)
end
def setup_message
init_with_client
check_insecure_method
end
def perform_without_block
if Proc === @obj && @msg_id == :__drb_yield
if @argv.size == 1
ary = @argv
else
ary = [@argv]
end
ary.collect(&@obj)[0]
else
@obj.__send__(@msg_id, *@argv)
end
end
end
require_relative 'invokemethod'
class InvokeMethod
include InvokeMethod18Mixin
end
def error_print(exception)
exception.backtrace.inject(true) do |first, x|
if first
$stderr.puts "#{x}: #{exception} (#{exception.class})"
else
$stderr.puts "\tfrom #{x}"
end
false
end
end
# The main loop performed by a DRbServer's internal thread.
#
# Accepts a connection from a client, and starts up its own
# thread to handle it. This thread loops, receiving requests
# from the client, invoking them on a local object, and
# returning responses, until the client closes the connection
# or a local method call fails.
def main_loop
client0 = @protocol.accept
return nil if !client0
Thread.start(client0) do |client|
@grp.add Thread.current
Thread.current['DRb'] = { 'client' => client ,
'server' => self }
DRb.mutex.synchronize do
client_uri = client.uri
@exported_uri << client_uri unless @exported_uri.include?(client_uri)
end
_last_invoke_method = nil
loop do
begin
succ = false
invoke_method = InvokeMethod.new(self, client)
succ, result = invoke_method.perform
error_print(result) if !succ && verbose
unless DRbConnError === result && result.message == 'connection closed'
client.send_reply(succ, result)
end
rescue Exception => e
error_print(e) if verbose
ensure
_last_invoke_method = invoke_method
client.close unless succ
if Thread.current['DRb']['stop_service']
shutdown
break
end
break unless succ
end
end
end
end
end
@primary_server = nil
# Start a dRuby server locally.
#
# The new dRuby server will become the primary server, even
# if another server is currently the primary server.
#
# +uri+ is the URI for the server to bind to. If nil,
# the server will bind to random port on the default local host
# name and use the default dRuby protocol.
#
# +front+ is the server's front object. This may be nil.
#
# +config+ is the configuration for the new server. This may
# be nil.
#
# See DRbServer::new.
def start_service(uri=nil, front=nil, config=nil)
@primary_server = DRbServer.new(uri, front, config)
end
module_function :start_service
# The primary local dRuby server.
#
# This is the server created by the #start_service call.
attr_accessor :primary_server
module_function :primary_server=, :primary_server
# Get the 'current' server.
#
# In the context of execution taking place within the main
# thread of a dRuby server (typically, as a result of a remote
# call on the server or one of its objects), the current
# server is that server. Otherwise, the current server is
# the primary server.
#
# If the above rule fails to find a server, a DRbServerNotFound
# error is raised.
def current_server
drb = Thread.current['DRb']
server = (drb && drb['server']) ? drb['server'] : @primary_server
raise DRbServerNotFound unless server
return server
end
module_function :current_server
# Stop the local dRuby server.
#
# This operates on the primary server. If there is no primary
# server currently running, it is a noop.
def stop_service
@primary_server.stop_service if @primary_server
@primary_server = nil
end
module_function :stop_service
# Get the URI defining the local dRuby space.
#
# This is the URI of the current server. See #current_server.
def uri
drb = Thread.current['DRb']
client = (drb && drb['client'])
if client
uri = client.uri
return uri if uri
end
current_server.uri
end
module_function :uri
# Is +uri+ the URI for the current local server?
def here?(uri)
current_server.here?(uri) rescue false
# (current_server.uri rescue nil) == uri
end
module_function :here?
# Get the configuration of the current server.
#
# If there is no current server, this returns the default configuration.
# See #current_server and DRbServer::make_config.
def config
current_server.config
rescue
DRbServer.make_config
end
module_function :config
# Get the front object of the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def front
current_server.front
end
module_function :front
# Convert a reference into an object using the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def to_obj(ref)
current_server.to_obj(ref)
end
# Get a reference id for an object using the current server.
#
# This raises a DRbServerNotFound error if there is no current server.
# See #current_server.
def to_id(obj)
current_server.to_id(obj)
end
module_function :to_id
module_function :to_obj
# Get the thread of the primary server.
#
# This returns nil if there is no primary server. See #primary_server.
def thread
@primary_server ? @primary_server.thread : nil
end
module_function :thread
# Set the default id conversion object.
#
# This is expected to be an instance such as DRb::DRbIdConv that responds to
# #to_id and #to_obj that can convert objects to and from DRb references.
#
# See DRbServer#default_id_conv.
def install_id_conv(idconv)
DRbServer.default_id_conv(idconv)
end
module_function :install_id_conv
# Set the default ACL to +acl+.
#
# See DRb::DRbServer.default_acl.
def install_acl(acl)
DRbServer.default_acl(acl)
end
module_function :install_acl
@mutex = Thread::Mutex.new
def mutex # :nodoc:
@mutex
end
module_function :mutex
@server = {}
# Registers +server+ with DRb.
#
# This is called when a new DRb::DRbServer is created.
#
# If there is no primary server then +server+ becomes the primary server.
#
# Example:
#
# require 'drb'
#
# s = DRb::DRbServer.new # automatically calls regist_server
# DRb.fetch_server s.uri #=> #<DRb::DRbServer:0x...>
def regist_server(server)
@server[server.uri] = server
mutex.synchronize do
@primary_server = server unless @primary_server
end
end
module_function :regist_server
# Removes +server+ from the list of registered servers.
def remove_server(server)
@server.delete(server.uri)
mutex.synchronize do
if @primary_server == server
@primary_server = nil
end
end
end
module_function :remove_server
# Retrieves the server with the given +uri+.
#
# See also regist_server and remove_server.
def fetch_server(uri)
@server[uri]
end
module_function :fetch_server
end
# :stopdoc:
DRbObject = DRb::DRbObject
DRbUndumped = DRb::DRbUndumped
DRbIdConv = DRb::DRbIdConv
share/ruby/drb/acl.rb 0000644 00000011161 15173547334 0010476 0 ustar 00 # frozen_string_literal: false
# Copyright (c) 2000,2002,2003 Masatoshi SEKI
#
# acl.rb is copyrighted free software by Masatoshi SEKI.
# You can redistribute it and/or modify it under the same terms as Ruby.
require 'ipaddr'
##
# Simple Access Control Lists.
#
# Access control lists are composed of "allow" and "deny" halves to control
# access. Use "all" or "*" to match any address. To match a specific address
# use any address or address mask that IPAddr can understand.
#
# Example:
#
# list = %w[
# deny all
# allow 192.168.1.1
# allow ::ffff:192.168.1.2
# allow 192.168.1.3
# ]
#
# # From Socket#peeraddr, see also ACL#allow_socket?
# addr = ["AF_INET", 10, "lc630", "192.168.1.3"]
#
# acl = ACL.new
# p acl.allow_addr?(addr) # => true
#
# acl = ACL.new(list, ACL::DENY_ALLOW)
# p acl.allow_addr?(addr) # => true
class ACL
##
# The current version of ACL
VERSION=["2.0.0"]
##
# An entry in an ACL
class ACLEntry
##
# Creates a new entry using +str+.
#
# +str+ may be "*" or "all" to match any address, an IP address string
# to match a specific address, an IP address mask per IPAddr, or one
# containing "*" to match part of an IPv4 address.
#
# IPAddr::InvalidPrefixError may be raised when an IP network
# address with an invalid netmask/prefix is given.
def initialize(str)
if str == '*' or str == 'all'
@pat = [:all]
elsif str.include?('*')
@pat = [:name, dot_pat(str)]
else
begin
@pat = [:ip, IPAddr.new(str)]
rescue IPAddr::InvalidPrefixError
# In this case, `str` shouldn't be a host name pattern
# because it contains a slash.
raise
rescue ArgumentError
@pat = [:name, dot_pat(str)]
end
end
end
private
##
# Creates a regular expression to match IPv4 addresses
def dot_pat_str(str)
list = str.split('.').collect { |s|
(s == '*') ? '.+' : s
}
list.join("\\.")
end
private
##
# Creates a Regexp to match an address.
def dot_pat(str)
/\A#{dot_pat_str(str)}\z/
end
public
##
# Matches +addr+ against this entry.
def match(addr)
case @pat[0]
when :all
true
when :ip
begin
ipaddr = IPAddr.new(addr[3])
ipaddr = ipaddr.ipv4_mapped if @pat[1].ipv6? && ipaddr.ipv4?
rescue ArgumentError
return false
end
(@pat[1].include?(ipaddr)) ? true : false
when :name
(@pat[1] =~ addr[2]) ? true : false
else
false
end
end
end
##
# A list of ACLEntry objects. Used to implement the allow and deny halves
# of an ACL
class ACLList
##
# Creates an empty ACLList
def initialize
@list = []
end
public
##
# Matches +addr+ against each ACLEntry in this list.
def match(addr)
@list.each do |e|
return true if e.match(addr)
end
false
end
public
##
# Adds +str+ as an ACLEntry in this list
def add(str)
@list.push(ACLEntry.new(str))
end
end
##
# Default to deny
DENY_ALLOW = 0
##
# Default to allow
ALLOW_DENY = 1
##
# Creates a new ACL from +list+ with an evaluation +order+ of DENY_ALLOW or
# ALLOW_DENY.
#
# An ACL +list+ is an Array of "allow" or "deny" and an address or address
# mask or "all" or "*" to match any address:
#
# %w[
# deny all
# allow 192.0.2.2
# allow 192.0.2.128/26
# ]
def initialize(list=nil, order = DENY_ALLOW)
@order = order
@deny = ACLList.new
@allow = ACLList.new
install_list(list) if list
end
public
##
# Allow connections from Socket +soc+?
def allow_socket?(soc)
allow_addr?(soc.peeraddr)
end
public
##
# Allow connections from addrinfo +addr+? It must be formatted like
# Socket#peeraddr:
#
# ["AF_INET", 10, "lc630", "192.0.2.1"]
def allow_addr?(addr)
case @order
when DENY_ALLOW
return true if @allow.match(addr)
return false if @deny.match(addr)
return true
when ALLOW_DENY
return false if @deny.match(addr)
return true if @allow.match(addr)
return false
else
false
end
end
public
##
# Adds +list+ of ACL entries to this ACL.
def install_list(list)
i = 0
while i < list.size
permission, domain = list.slice(i,2)
case permission.downcase
when 'allow'
@allow.add(domain)
when 'deny'
@deny.add(domain)
else
raise "Invalid ACL entry #{list}"
end
i += 2
end
end
end
share/ruby/mutex_m.rb 0000644 00000004517 15173547334 0010655 0 ustar 00 # 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 --- remember to call super() in your class initialize method.
#
# class Foo
# include Mutex_m
# def initialize
# # ...
# super()
# end
# # ...
# end
# obj = Foo.new
# # this obj can be handled like Mutex
#
module Mutex_m
VERSION = "0.1.2"
Ractor.make_shareable(VERSION) if defined?(Ractor)
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 Thread::Mutex#synchronize
def mu_synchronize(&block)
@_mutex.synchronize(&block)
end
# See Thread::Mutex#locked?
def mu_locked?
@_mutex.locked?
end
# See Thread::Mutex#try_lock
def mu_try_lock
@_mutex.try_lock
end
# See Thread::Mutex#lock
def mu_lock
@_mutex.lock
end
# See Thread::Mutex#unlock
def mu_unlock
@_mutex.unlock
end
# See Thread::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
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
end
share/ruby/digest.rb 0000644 00000006465 15173547334 0010462 0 ustar 00 # frozen_string_literal: false
if defined?(Digest) &&
/\A(?:2\.|3\.0\.[0-2]\z)/.match?(RUBY_VERSION) &&
caller_locations.any? { |l|
%r{/(rubygems/gem_runner|bundler/cli)\.rb}.match?(l.path)
}
# Before Ruby 3.0.3/3.1.0, the gem and bundle commands used to load
# the digest library before loading additionally installed gems, so
# you will get constant redefinition warnings and unexpected
# implementation overwriting if we proceed here. Avoid that.
return
end
require 'digest/version'
require 'digest/loader'
module Digest
# A mutex for Digest().
REQUIRE_MUTEX = Thread::Mutex.new
def self.const_missing(name) # :nodoc:
case name
when :SHA256, :SHA384, :SHA512
lib = 'digest/sha2'
else
lib = File.join('digest', name.to_s.downcase)
end
begin
require lib
rescue LoadError
raise LoadError, "library not found for class Digest::#{name} -- #{lib}", caller(1)
end
unless Digest.const_defined?(name)
raise NameError, "uninitialized constant Digest::#{name}", caller(1)
end
Digest.const_get(name)
end
class ::Digest::Class
# Creates a digest object and reads a given file, _name_.
# Optional arguments are passed to the constructor of the digest
# class.
#
# p Digest::SHA256.file("X11R6.8.2-src.tar.bz2").hexdigest
# # => "f02e3c85572dc9ad7cb77c2a638e3be24cc1b5bea9fdbb0b0299c9668475c534"
def self.file(name, *args)
new(*args).file(name)
end
# Returns the base64 encoded hash value of a given _string_. The
# return value is properly padded with '=' and contains no line
# feeds.
def self.base64digest(str, *args)
[digest(str, *args)].pack('m0')
end
end
module Instance
# Updates the digest with the contents of a given file _name_ and
# returns self.
def file(name)
File.open(name, "rb") {|f|
buf = ""
while f.read(16384, buf)
update buf
end
}
self
end
# If none is given, returns the resulting hash value of the digest
# in a base64 encoded form, keeping the digest's state.
#
# If a +string+ is given, returns the hash value for the given
# +string+ in a base64 encoded form, resetting the digest to the
# initial state before and after the process.
#
# In either case, the return value is properly padded with '=' and
# contains no line feeds.
def base64digest(str = nil)
[str ? digest(str) : digest].pack('m0')
end
# Returns the resulting hash value and resets the digest to the
# initial state.
def base64digest!
[digest!].pack('m0')
end
end
end
# call-seq:
# Digest(name) -> digest_subclass
#
# Returns a Digest subclass by +name+ in a thread-safe manner even
# when on-demand loading is involved.
#
# require 'digest'
#
# Digest("MD5")
# # => Digest::MD5
#
# Digest(:SHA256)
# # => Digest::SHA256
#
# Digest(:Foo)
# # => LoadError: library not found for class Digest::Foo -- digest/foo
def Digest(name)
const = name.to_sym
Digest::REQUIRE_MUTEX.synchronize {
# Ignore autoload's because it is void when we have #const_missing
Digest.const_missing(const)
}
rescue LoadError
# Constants do not necessarily rely on digest/*.
if Digest.const_defined?(const)
Digest.const_get(const)
else
raise
end
end
share/ruby/fiddle.rb 0000644 00000005610 15173547334 0010421 0 ustar 00 # frozen_string_literal: true
require 'fiddle.so'
require 'fiddle/closure'
require 'fiddle/function'
require 'fiddle/version'
module Fiddle
if WINDOWS
# Returns the last win32 +Error+ of the current executing +Thread+ or nil
# if none
def self.win32_last_error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__]
end
# Sets the last win32 +Error+ of the current executing +Thread+ to +error+
def self.win32_last_error= error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error
end
# Returns the last win32 socket +Error+ of the current executing
# +Thread+ or nil if none
def self.win32_last_socket_error
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__]
end
# Sets the last win32 socket +Error+ of the current executing
# +Thread+ to +error+
def self.win32_last_socket_error= error
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__] = error
end
end
# Returns the last +Error+ of the current executing +Thread+ or nil if none
def self.last_error
Thread.current[:__FIDDLE_LAST_ERROR__]
end
# Sets the last +Error+ of the current executing +Thread+ to +error+
def self.last_error= error
Thread.current[:__DL2_LAST_ERROR__] = error
Thread.current[:__FIDDLE_LAST_ERROR__] = error
end
# call-seq: dlopen(library) => Fiddle::Handle
#
# Creates a new handler that opens +library+, and returns an instance of
# Fiddle::Handle.
#
# If +nil+ is given for the +library+, Fiddle::Handle::DEFAULT is used, which
# is the equivalent to RTLD_DEFAULT. See <code>man 3 dlopen</code> for more.
#
# lib = Fiddle.dlopen(nil)
#
# The default is dependent on OS, and provide a handle for all libraries
# already loaded. For example, in most cases you can use this to access
# +libc+ functions, or ruby functions like +rb_str_new+.
#
# See Fiddle::Handle.new for more.
def dlopen library
begin
Fiddle::Handle.new(library)
rescue DLError => error
case RUBY_PLATFORM
when /linux/
case error.message
when /\A(\/.+?): (?:invalid ELF header|file too short)/
# This may be a linker script:
# https://sourceware.org/binutils/docs/ld.html#Scripts
path = $1
else
raise
end
else
raise
end
File.open(path) do |input|
input.each_line do |line|
case line
when /\A\s*(?:INPUT|GROUP)\s*\(\s*([^\s,\)]+)/
# TODO: Should we support multiple files?
return dlopen($1)
end
end
end
# Not found
raise
end
end
module_function :dlopen
# Add constants for backwards compat
RTLD_GLOBAL = Handle::RTLD_GLOBAL # :nodoc:
RTLD_LAZY = Handle::RTLD_LAZY # :nodoc:
RTLD_NOW = Handle::RTLD_NOW # :nodoc:
Fiddle::Types.constants.each do |type|
const_set "TYPE_#{type}", Fiddle::Types.const_get(type)
end
end
share/ruby/open3.rb 0000644 00000054160 15173547334 0010222 0 ustar 00 # frozen_string_literal: true
#
# = open3.rb: Popen, but with stderr, too
#
# Author:: Yukihiro Matsumoto
# Documentation:: Konrad Meyer
#
# Open3 gives you access to stdin, stdout, and stderr when running other
# programs.
#
#
# Open3 grants you access to stdin, stdout, stderr and a thread to wait for the
# child process when running another program.
# You can specify various attributes, redirections, current directory, etc., of
# the program in the same way as for Process.spawn.
#
# - Open3.popen3 : pipes for stdin, stdout, stderr
# - Open3.popen2 : pipes for stdin, stdout
# - Open3.popen2e : pipes for stdin, merged stdout and stderr
# - Open3.capture3 : give a string for stdin; get strings for stdout, stderr
# - Open3.capture2 : give a string for stdin; get a string for stdout
# - Open3.capture2e : give a string for stdin; get a string for merged stdout and stderr
# - Open3.pipeline_rw : pipes for first stdin and last stdout of a pipeline
# - Open3.pipeline_r : pipe for last stdout of a pipeline
# - Open3.pipeline_w : pipe for first stdin of a pipeline
# - Open3.pipeline_start : run a pipeline without waiting
# - Open3.pipeline : run a pipeline and wait for its completion
#
require 'open3/version'
module Open3
# Open stdin, stdout, and stderr streams and start external executable.
# In addition, a thread to wait for the started process is created.
# The thread has a pid method and a thread variable :pid which is the pid of
# the started process.
#
# Block form:
#
# Open3.popen3([env,] cmd... [, opts]) {|stdin, stdout, stderr, wait_thr|
# pid = wait_thr.pid # pid of the started process.
# ...
# exit_status = wait_thr.value # Process::Status object returned.
# }
#
# Non-block form:
#
# stdin, stdout, stderr, wait_thr = Open3.popen3([env,] cmd... [, opts])
# pid = wait_thr[:pid] # pid of the started process
# ...
# stdin.close # stdin, stdout and stderr should be closed explicitly in this form.
# stdout.close
# stderr.close
# exit_status = wait_thr.value # Process::Status object returned.
#
# The parameters env, cmd, and opts are passed to Process.spawn.
# A commandline string and a list of argument strings can be accepted as follows:
#
# Open3.popen3("echo abc") {|i, o, e, t| ... }
# Open3.popen3("echo", "abc") {|i, o, e, t| ... }
# Open3.popen3(["echo", "argv0"], "abc") {|i, o, e, t| ... }
#
# If the last parameter, opts, is a Hash, it is recognized as an option for Process.spawn.
#
# Open3.popen3("pwd", :chdir=>"/") {|i,o,e,t|
# p o.read.chomp #=> "/"
# }
#
# wait_thr.value waits for the termination of the process.
# The block form also waits for the process when it returns.
#
# Closing stdin, stdout and stderr does not wait for the process to complete.
#
# You should be careful to avoid deadlocks.
# Since pipes are fixed length buffers,
# Open3.popen3("prog") {|i, o, e, t| o.read } deadlocks if
# the program generates too much output on stderr.
# You should read stdout and stderr simultaneously (using threads or IO.select).
# However, if you don't need stderr output, you can use Open3.popen2.
# If merged stdout and stderr output is not a problem, you can use Open3.popen2e.
# If you really need stdout and stderr output as separate strings, you can consider Open3.capture3.
#
def popen3(*cmd, &block)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
out_r, out_w = IO.pipe
opts[:out] = out_w
err_r, err_w = IO.pipe
opts[:err] = err_w
popen_run(cmd, opts, [in_r, out_w, err_w], [in_w, out_r, err_r], &block)
end
module_function :popen3
# Open3.popen2 is similar to Open3.popen3 except that it doesn't create a pipe for
# the standard error stream.
#
# Block form:
#
# Open3.popen2([env,] cmd... [, opts]) {|stdin, stdout, wait_thr|
# pid = wait_thr.pid # pid of the started process.
# ...
# exit_status = wait_thr.value # Process::Status object returned.
# }
#
# Non-block form:
#
# stdin, stdout, wait_thr = Open3.popen2([env,] cmd... [, opts])
# ...
# stdin.close # stdin and stdout should be closed explicitly in this form.
# stdout.close
#
# See Process.spawn for the optional hash arguments _env_ and _opts_.
#
# Example:
#
# Open3.popen2("wc -c") {|i,o,t|
# i.print "answer to life the universe and everything"
# i.close
# p o.gets #=> "42\n"
# }
#
# Open3.popen2("bc -q") {|i,o,t|
# i.puts "obase=13"
# i.puts "6 * 9"
# p o.gets #=> "42\n"
# }
#
# Open3.popen2("dc") {|i,o,t|
# i.print "42P"
# i.close
# p o.read #=> "*"
# }
#
def popen2(*cmd, &block)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
out_r, out_w = IO.pipe
opts[:out] = out_w
popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
end
module_function :popen2
# Open3.popen2e is similar to Open3.popen3 except that it merges
# the standard output stream and the standard error stream.
#
# Block form:
#
# Open3.popen2e([env,] cmd... [, opts]) {|stdin, stdout_and_stderr, wait_thr|
# pid = wait_thr.pid # pid of the started process.
# ...
# exit_status = wait_thr.value # Process::Status object returned.
# }
#
# Non-block form:
#
# stdin, stdout_and_stderr, wait_thr = Open3.popen2e([env,] cmd... [, opts])
# ...
# stdin.close # stdin and stdout_and_stderr should be closed explicitly in this form.
# stdout_and_stderr.close
#
# See Process.spawn for the optional hash arguments _env_ and _opts_.
#
# Example:
# # check gcc warnings
# source = "foo.c"
# Open3.popen2e("gcc", "-Wall", source) {|i,oe,t|
# oe.each {|line|
# if /warning/ =~ line
# ...
# end
# }
# }
#
def popen2e(*cmd, &block)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
out_r, out_w = IO.pipe
opts[[:out, :err]] = out_w
popen_run(cmd, opts, [in_r, out_w], [in_w, out_r], &block)
ensure
if block
in_r.close
in_w.close
out_r.close
out_w.close
end
end
module_function :popen2e
def popen_run(cmd, opts, child_io, parent_io) # :nodoc:
pid = spawn(*cmd, opts)
wait_thr = Process.detach(pid)
child_io.each(&:close)
result = [*parent_io, wait_thr]
if defined? yield
begin
return yield(*result)
ensure
parent_io.each(&:close)
wait_thr.join
end
end
result
end
module_function :popen_run
class << self
private :popen_run
end
# Open3.capture3 captures the standard output and the standard error of a command.
#
# stdout_str, stderr_str, status = Open3.capture3([env,] cmd... [, opts])
#
# The arguments env, cmd and opts are passed to Open3.popen3 except
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
#
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
#
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
#
# Examples:
#
# # dot is a command of graphviz.
# graph = <<'End'
# digraph g {
# a -> b
# }
# End
# drawn_graph, dot_log = Open3.capture3("dot -v", :stdin_data=>graph)
#
# o, e, s = Open3.capture3("echo abc; sort >&2", :stdin_data=>"foo\nbar\nbaz\n")
# p o #=> "abc\n"
# p e #=> "bar\nbaz\nfoo\n"
# p s #=> #<Process::Status: pid 32682 exit 0>
#
# # generate a thumbnail image using the convert command of ImageMagick.
# # However, if the image is really stored in a file,
# # system("convert", "-thumbnail", "80", "png:#{filename}", "png:-") is better
# # because of reduced memory consumption.
# # But if the image is stored in a DB or generated by the gnuplot Open3.capture2 example,
# # Open3.capture3 should be considered.
# #
# image = File.read("/usr/share/openclipart/png/animals/mammals/sheep-md-v0.1.png", :binmode=>true)
# thumbnail, err, s = Open3.capture3("convert -thumbnail 80 png:- png:-", :stdin_data=>image, :binmode=>true)
# if s.success?
# STDOUT.binmode; print thumbnail
# end
#
def capture3(*cmd)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
stdin_data = opts.delete(:stdin_data) || ''
binmode = opts.delete(:binmode)
popen3(*cmd, opts) {|i, o, e, t|
if binmode
i.binmode
o.binmode
e.binmode
end
out_reader = Thread.new { o.read }
err_reader = Thread.new { e.read }
begin
if stdin_data.respond_to? :readpartial
IO.copy_stream(stdin_data, i)
else
i.write stdin_data
end
rescue Errno::EPIPE
end
i.close
[out_reader.value, err_reader.value, t.value]
}
end
module_function :capture3
# Open3.capture2 captures the standard output of a command.
#
# stdout_str, status = Open3.capture2([env,] cmd... [, opts])
#
# The arguments env, cmd and opts are passed to Open3.popen3 except
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
#
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
#
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
#
# Example:
#
# # factor is a command for integer factorization.
# o, s = Open3.capture2("factor", :stdin_data=>"42")
# p o #=> "42: 2 3 7\n"
#
# # generate x**2 graph in png using gnuplot.
# gnuplot_commands = <<"End"
# set terminal png
# plot x**2, "-" with lines
# 1 14
# 2 1
# 3 8
# 4 5
# e
# End
# image, s = Open3.capture2("gnuplot", :stdin_data=>gnuplot_commands, :binmode=>true)
#
def capture2(*cmd)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
stdin_data = opts.delete(:stdin_data)
binmode = opts.delete(:binmode)
popen2(*cmd, opts) {|i, o, t|
if binmode
i.binmode
o.binmode
end
out_reader = Thread.new { o.read }
if stdin_data
begin
if stdin_data.respond_to? :readpartial
IO.copy_stream(stdin_data, i)
else
i.write stdin_data
end
rescue Errno::EPIPE
end
end
i.close
[out_reader.value, t.value]
}
end
module_function :capture2
# Open3.capture2e captures the standard output and the standard error of a command.
#
# stdout_and_stderr_str, status = Open3.capture2e([env,] cmd... [, opts])
#
# The arguments env, cmd and opts are passed to Open3.popen3 except
# <code>opts[:stdin_data]</code> and <code>opts[:binmode]</code>. See Process.spawn.
#
# If <code>opts[:stdin_data]</code> is specified, it is sent to the command's standard input.
#
# If <code>opts[:binmode]</code> is true, internal pipes are set to binary mode.
#
# Example:
#
# # capture make log
# make_log, s = Open3.capture2e("make")
#
def capture2e(*cmd)
if Hash === cmd.last
opts = cmd.pop.dup
else
opts = {}
end
stdin_data = opts.delete(:stdin_data)
binmode = opts.delete(:binmode)
popen2e(*cmd, opts) {|i, oe, t|
if binmode
i.binmode
oe.binmode
end
outerr_reader = Thread.new { oe.read }
if stdin_data
begin
if stdin_data.respond_to? :readpartial
IO.copy_stream(stdin_data, i)
else
i.write stdin_data
end
rescue Errno::EPIPE
end
end
i.close
[outerr_reader.value, t.value]
}
end
module_function :capture2e
# Open3.pipeline_rw starts a list of commands as a pipeline with pipes
# which connect to stdin of the first command and stdout of the last command.
#
# Open3.pipeline_rw(cmd1, cmd2, ... [, opts]) {|first_stdin, last_stdout, wait_threads|
# ...
# }
#
# first_stdin, last_stdout, wait_threads = Open3.pipeline_rw(cmd1, cmd2, ... [, opts])
# ...
# first_stdin.close
# last_stdout.close
#
# Each cmd is a string or an array.
# If it is an array, the elements are passed to Process.spawn.
#
# cmd:
# commandline command line string which is passed to a shell
# [env, commandline, opts] command line string which is passed to a shell
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
#
# Note that env and opts are optional, as for Process.spawn.
#
# The options to pass to Process.spawn are constructed by merging
# +opts+, the last hash element of the array, and
# specifications for the pipes between each of the commands.
#
# Example:
#
# Open3.pipeline_rw("tr -dc A-Za-z", "wc -c") {|i, o, ts|
# i.puts "All persons more than a mile high to leave the court."
# i.close
# p o.gets #=> "42\n"
# }
#
# Open3.pipeline_rw("sort", "cat -n") {|stdin, stdout, wait_thrs|
# stdin.puts "foo"
# stdin.puts "bar"
# stdin.puts "baz"
# stdin.close # send EOF to sort.
# p stdout.read #=> " 1\tbar\n 2\tbaz\n 3\tfoo\n"
# }
def pipeline_rw(*cmds, &block)
if Hash === cmds.last
opts = cmds.pop.dup
else
opts = {}
end
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
out_r, out_w = IO.pipe
opts[:out] = out_w
pipeline_run(cmds, opts, [in_r, out_w], [in_w, out_r], &block)
end
module_function :pipeline_rw
# Open3.pipeline_r starts a list of commands as a pipeline with a pipe
# which connects to stdout of the last command.
#
# Open3.pipeline_r(cmd1, cmd2, ... [, opts]) {|last_stdout, wait_threads|
# ...
# }
#
# last_stdout, wait_threads = Open3.pipeline_r(cmd1, cmd2, ... [, opts])
# ...
# last_stdout.close
#
# Each cmd is a string or an array.
# If it is an array, the elements are passed to Process.spawn.
#
# cmd:
# commandline command line string which is passed to a shell
# [env, commandline, opts] command line string which is passed to a shell
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
#
# Note that env and opts are optional, as for Process.spawn.
#
# Example:
#
# Open3.pipeline_r("zcat /var/log/apache2/access.log.*.gz",
# [{"LANG"=>"C"}, "grep", "GET /favicon.ico"],
# "logresolve") {|o, ts|
# o.each_line {|line|
# ...
# }
# }
#
# Open3.pipeline_r("yes", "head -10") {|o, ts|
# p o.read #=> "y\ny\ny\ny\ny\ny\ny\ny\ny\ny\n"
# p ts[0].value #=> #<Process::Status: pid 24910 SIGPIPE (signal 13)>
# p ts[1].value #=> #<Process::Status: pid 24913 exit 0>
# }
#
def pipeline_r(*cmds, &block)
if Hash === cmds.last
opts = cmds.pop.dup
else
opts = {}
end
out_r, out_w = IO.pipe
opts[:out] = out_w
pipeline_run(cmds, opts, [out_w], [out_r], &block)
end
module_function :pipeline_r
# Open3.pipeline_w starts a list of commands as a pipeline with a pipe
# which connects to stdin of the first command.
#
# Open3.pipeline_w(cmd1, cmd2, ... [, opts]) {|first_stdin, wait_threads|
# ...
# }
#
# first_stdin, wait_threads = Open3.pipeline_w(cmd1, cmd2, ... [, opts])
# ...
# first_stdin.close
#
# Each cmd is a string or an array.
# If it is an array, the elements are passed to Process.spawn.
#
# cmd:
# commandline command line string which is passed to a shell
# [env, commandline, opts] command line string which is passed to a shell
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
#
# Note that env and opts are optional, as for Process.spawn.
#
# Example:
#
# Open3.pipeline_w("bzip2 -c", :out=>"/tmp/hello.bz2") {|i, ts|
# i.puts "hello"
# }
#
def pipeline_w(*cmds, &block)
if Hash === cmds.last
opts = cmds.pop.dup
else
opts = {}
end
in_r, in_w = IO.pipe
opts[:in] = in_r
in_w.sync = true
pipeline_run(cmds, opts, [in_r], [in_w], &block)
end
module_function :pipeline_w
# Open3.pipeline_start starts a list of commands as a pipeline.
# No pipes are created for stdin of the first command and
# stdout of the last command.
#
# Open3.pipeline_start(cmd1, cmd2, ... [, opts]) {|wait_threads|
# ...
# }
#
# wait_threads = Open3.pipeline_start(cmd1, cmd2, ... [, opts])
# ...
#
# Each cmd is a string or an array.
# If it is an array, the elements are passed to Process.spawn.
#
# cmd:
# commandline command line string which is passed to a shell
# [env, commandline, opts] command line string which is passed to a shell
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
#
# Note that env and opts are optional, as for Process.spawn.
#
# Example:
#
# # Run xeyes in 10 seconds.
# Open3.pipeline_start("xeyes") {|ts|
# sleep 10
# t = ts[0]
# Process.kill("TERM", t.pid)
# p t.value #=> #<Process::Status: pid 911 SIGTERM (signal 15)>
# }
#
# # Convert pdf to ps and send it to a printer.
# # Collect error message of pdftops and lpr.
# pdf_file = "paper.pdf"
# printer = "printer-name"
# err_r, err_w = IO.pipe
# Open3.pipeline_start(["pdftops", pdf_file, "-"],
# ["lpr", "-P#{printer}"],
# :err=>err_w) {|ts|
# err_w.close
# p err_r.read # error messages of pdftops and lpr.
# }
#
def pipeline_start(*cmds, &block)
if Hash === cmds.last
opts = cmds.pop.dup
else
opts = {}
end
if block
pipeline_run(cmds, opts, [], [], &block)
else
ts, = pipeline_run(cmds, opts, [], [])
ts
end
end
module_function :pipeline_start
# Open3.pipeline starts a list of commands as a pipeline.
# It waits for the completion of the commands.
# No pipes are created for stdin of the first command and
# stdout of the last command.
#
# status_list = Open3.pipeline(cmd1, cmd2, ... [, opts])
#
# Each cmd is a string or an array.
# If it is an array, the elements are passed to Process.spawn.
#
# cmd:
# commandline command line string which is passed to a shell
# [env, commandline, opts] command line string which is passed to a shell
# [env, cmdname, arg1, ..., opts] command name and one or more arguments (no shell)
# [env, [cmdname, argv0], arg1, ..., opts] command name and arguments including argv[0] (no shell)
#
# Note that env and opts are optional, as Process.spawn.
#
# Example:
#
# fname = "/usr/share/man/man1/ruby.1.gz"
# p Open3.pipeline(["zcat", fname], "nroff -man", "less")
# #=> [#<Process::Status: pid 11817 exit 0>,
# # #<Process::Status: pid 11820 exit 0>,
# # #<Process::Status: pid 11828 exit 0>]
#
# fname = "/usr/share/man/man1/ls.1.gz"
# Open3.pipeline(["zcat", fname], "nroff -man", "colcrt")
#
# # convert PDF to PS and send to a printer by lpr
# pdf_file = "paper.pdf"
# printer = "printer-name"
# Open3.pipeline(["pdftops", pdf_file, "-"],
# ["lpr", "-P#{printer}"])
#
# # count lines
# Open3.pipeline("sort", "uniq -c", :in=>"names.txt", :out=>"count")
#
# # cyclic pipeline
# r,w = IO.pipe
# w.print "ibase=14\n10\n"
# Open3.pipeline("bc", "tee /dev/tty", :in=>r, :out=>w)
# #=> 14
# # 18
# # 22
# # 30
# # 42
# # 58
# # 78
# # 106
# # 202
#
def pipeline(*cmds)
if Hash === cmds.last
opts = cmds.pop.dup
else
opts = {}
end
pipeline_run(cmds, opts, [], []) {|ts|
ts.map(&:value)
}
end
module_function :pipeline
def pipeline_run(cmds, pipeline_opts, child_io, parent_io) # :nodoc:
if cmds.empty?
raise ArgumentError, "no commands"
end
opts_base = pipeline_opts.dup
opts_base.delete :in
opts_base.delete :out
wait_thrs = []
r = nil
cmds.each_with_index {|cmd, i|
cmd_opts = opts_base.dup
if String === cmd
cmd = [cmd]
else
cmd_opts.update cmd.pop if Hash === cmd.last
end
if i == 0
if !cmd_opts.include?(:in)
if pipeline_opts.include?(:in)
cmd_opts[:in] = pipeline_opts[:in]
end
end
else
cmd_opts[:in] = r
end
if i != cmds.length - 1
r2, w2 = IO.pipe
cmd_opts[:out] = w2
else
if !cmd_opts.include?(:out)
if pipeline_opts.include?(:out)
cmd_opts[:out] = pipeline_opts[:out]
end
end
end
pid = spawn(*cmd, cmd_opts)
wait_thrs << Process.detach(pid)
r&.close
w2&.close
r = r2
}
result = parent_io + [wait_thrs]
child_io.each(&:close)
if defined? yield
begin
return yield(*result)
ensure
parent_io.each(&:close)
wait_thrs.each(&:join)
end
end
result
end
module_function :pipeline_run
class << self
private :pipeline_run
end
end
# JRuby uses different popen logic on Windows, require it here to reuse wrapper methods above.
require 'open3/jruby_windows' if RUBY_ENGINE == 'jruby' && JRuby::Util::ON_WINDOWS
share/ruby/openssl/ssl.rb 0000644 00000042717 15173547334 0011467 0 ustar 00 # frozen_string_literal: true
=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"
if defined?(OpenSSL::SSL)
require "io/nonblock"
require "ipaddr"
require "socket"
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
# A callback invoked when DH parameters are required for ephemeral DH key
# exchange.
#
# The callback is invoked with the SSLSocket, a
# 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.
#
# <b>Deprecated in version 3.0.</b> Use #tmp_dh= instead.
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
self.verify_mode = OpenSSL::SSL::VERIFY_NONE
self.verify_hostname = false
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
# The file descriptor for the socket.
def fileno
to_io.fileno
end
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 session_new_cb
@context.session_new_cb
end
def session_get_cb
@context.session_get_cb
end
class << self
# call-seq:
# open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil)
#
# Creates a new instance of SSLSocket.
# _remote\_host_ and _remote\_port_ are used to open TCPSocket.
# If _local\_host_ and _local\_port_ are specified,
# then those parameters are used on the local end to establish the connection.
# If _context_ is provided,
# the SSL Sockets initial params will be taken from the context.
#
# === Examples
#
# sock = OpenSSL::SSL::SSLSocket.open('localhost', 443)
# sock.connect # Initiates a connection to localhost:443
#
# with SSLContext:
#
# ctx = OpenSSL::SSL::SSLContext.new
# sock = OpenSSL::SSL::SSLSocket.open('localhost', 443, context: ctx)
# sock.connect # Initiates a connection to localhost:443 with SSLContext
def open(remote_host, remote_port, local_host=nil, local_port=nil, context: nil)
sock = ::TCPSocket.open(remote_host, remote_port, local_host, local_port)
if context.nil?
return OpenSSL::SSL::SSLSocket.new(sock)
else
return OpenSSL::SSL::SSLSocket.new(sock, context)
end
end
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=Socket::SOMAXCONN)
@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
end
share/ruby/openssl/hmac.rb 0000644 00000004774 15173547334 0011577 0 ustar 00 # frozen_string_literal: true
module OpenSSL
class HMAC
# Securely compare with another HMAC instance in constant time.
def ==(other)
return false unless HMAC === other
return false unless self.digest.bytesize == other.digest.bytesize
OpenSSL.fixed_length_secure_compare(self.digest, other.digest)
end
# :call-seq:
# hmac.base64digest -> string
#
# Returns the authentication code an a Base64-encoded string.
def base64digest
[digest].pack("m0")
end
class << self
# :call-seq:
# HMAC.digest(digest, key, data) -> aString
#
# Returns the authentication code as a binary string. The _digest_ parameter
# specifies the digest algorithm to use. This may be a String representing
# the algorithm name or an instance of OpenSSL::Digest.
#
# === Example
# key = 'key'
# data = 'The quick brown fox jumps over the lazy dog'
#
# hmac = OpenSSL::HMAC.digest('SHA1', key, data)
# #=> "\xDE|\x9B\x85\xB8\xB7\x8A\xA6\xBC\x8Az6\xF7\n\x90p\x1C\x9D\xB4\xD9"
def digest(digest, key, data)
hmac = new(key, digest)
hmac << data
hmac.digest
end
# :call-seq:
# HMAC.hexdigest(digest, key, data) -> aString
#
# Returns the authentication code as a hex-encoded string. The _digest_
# parameter specifies the digest algorithm to use. This may be a String
# representing the algorithm name or an instance of OpenSSL::Digest.
#
# === Example
# key = 'key'
# data = 'The quick brown fox jumps over the lazy dog'
#
# hmac = OpenSSL::HMAC.hexdigest('SHA1', key, data)
# #=> "de7c9b85b8b78aa6bc8a7a36f70a90701c9db4d9"
def hexdigest(digest, key, data)
hmac = new(key, digest)
hmac << data
hmac.hexdigest
end
# :call-seq:
# HMAC.base64digest(digest, key, data) -> aString
#
# Returns the authentication code as a Base64-encoded string. The _digest_
# parameter specifies the digest algorithm to use. This may be a String
# representing the algorithm name or an instance of OpenSSL::Digest.
#
# === Example
# key = 'key'
# data = 'The quick brown fox jumps over the lazy dog'
#
# hmac = OpenSSL::HMAC.base64digest('SHA1', key, data)
# #=> "3nybhbi3iqa8ino29wqQcBydtNk="
def base64digest(digest, key, data)
[digest(digest, key, data)].pack("m0")
end
end
end
end
share/ruby/openssl/marshal.rb 0000644 00000001070 15173547334 0012300 0 ustar 00 # frozen_string_literal: true
#--
# = Ruby-space definitions to add DER (de)serialization to classes
#
# = 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 Marshal
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def _load(string)
new(string)
end
end
def _dump(_level)
to_der
end
end
end
share/ruby/openssl/pkcs5.rb 0000644 00000001145 15173547334 0011701 0 ustar 00 # frozen_string_literal: true
#--
# 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
share/ruby/openssl/buffering.rb 0000644 00000024344 15173547334 0012631 0 ustar 00 # coding: binary
# frozen_string_literal: true
#--
#= 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
# A buffer which will retain binary encoding.
class Buffer < String
BINARY = Encoding::BINARY
def initialize
super
force_encoding(BINARY)
end
def << string
if string.encoding == BINARY
super(string)
else
super(string.b)
end
return self
end
alias concat <<
end
##
# 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 = Buffer.new
@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
# call-seq:
# ssl.getbyte => 81
#
# Get the next 8bit byte from `ssl`. Returns `nil` on EOF
def getbyte
byte = read(1)
byte && byte.unpack1("C")
end
##
# 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 = Buffer.new unless defined? @wbuffer
@wbuffer << s
@wbuffer.force_encoding(Encoding::BINARY)
@sync ||= false
if @sync or @wbuffer.size > BLOCK_SIZE
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 = Buffer.new
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 = Buffer.new
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
share/ruby/openssl/digest.rb 0000644 00000003207 15173547334 0012134 0 ustar 00 # frozen_string_literal: true
#--
# = 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
# 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.digest('SHA256', "abc")
def self.digest(name, data)
super(data, name)
end
%w(MD4 MD5 RIPEMD160 SHA1 SHA224 SHA256 SHA384 SHA512).each do |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.tr('-', '_'), klass)
end
# 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
share/ruby/openssl/version.rb 0000644 00000000106 15173547334 0012335 0 ustar 00 # frozen_string_literal: true
module OpenSSL
VERSION = "3.1.0"
end
share/ruby/openssl/cipher.rb 0000644 00000003320 15173547334 0012123 0 ustar 00 # frozen_string_literal: true
#--
# = 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
share/ruby/openssl/pkey.rb 0000644 00000035416 15173547334 0011634 0 ustar 00 # frozen_string_literal: true
#--
# Ruby/OpenSSL Project
# Copyright (C) 2017 Ruby/OpenSSL Project Authors
#++
require_relative 'marshal'
module OpenSSL::PKey
class DH
include OpenSSL::Marshal
# :call-seq:
# dh.public_key -> dhnew
#
# Returns a new DH instance that carries just the \DH parameters.
#
# Contrary to the method name, the returned DH object contains only
# parameters and not the public key.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of re-generating the key pair while keeping the
# parameters, check OpenSSL::PKey.generate_key.
#
# Example:
# # OpenSSL::PKey::DH.generate by default generates a random key pair
# dh1 = OpenSSL::PKey::DH.generate(2048)
# p dh1.priv_key #=> #<OpenSSL::BN 1288347...>
# dhcopy = dh1.public_key
# p dhcopy.priv_key #=> nil
def public_key
DH.new(to_der)
end
# :call-seq:
# dh.compute_key(pub_bn) -> string
#
# Returns a String containing a shared secret computed from the other
# party's public value.
#
# This method is provided for backwards compatibility, and calls #derive
# internally.
#
# === Parameters
# * _pub_bn_ is a OpenSSL::BN, *not* the DH instance returned by
# DH#public_key as that contains the DH parameters only.
def compute_key(pub_bn)
# FIXME: This is constructing an X.509 SubjectPublicKeyInfo and is very
# inefficient
obj = OpenSSL::ASN1.Sequence([
OpenSSL::ASN1.Sequence([
OpenSSL::ASN1.ObjectId("dhKeyAgreement"),
OpenSSL::ASN1.Sequence([
OpenSSL::ASN1.Integer(p),
OpenSSL::ASN1.Integer(g),
]),
]),
OpenSSL::ASN1.BitString(OpenSSL::ASN1.Integer(pub_bn).to_der),
])
derive(OpenSSL::PKey.read(obj.to_der))
end
# :call-seq:
# dh.generate_key! -> self
#
# Generates a private and public key unless a private key already exists.
# If this DH instance was generated from public \DH parameters (e.g. by
# encoding the result of DH#public_key), then this method needs to be
# called first in order to generate the per-session keys before performing
# the actual key exchange.
#
# <b>Deprecated in version 3.0</b>. This method is incompatible with
# OpenSSL 3.0.0 or later.
#
# See also OpenSSL::PKey.generate_key.
#
# Example:
# # DEPRECATED USAGE: This will not work on OpenSSL 3.0 or later
# dh0 = OpenSSL::PKey::DH.new(2048)
# dh = dh0.public_key # #public_key only copies the DH parameters (contrary to the name)
# dh.generate_key!
# puts dh.private? # => true
# puts dh0.pub_key == dh.pub_key #=> false
#
# # With OpenSSL::PKey.generate_key
# dh0 = OpenSSL::PKey::DH.new(2048)
# dh = OpenSSL::PKey.generate_key(dh0)
# puts dh0.pub_key == dh.pub_key #=> false
def generate_key!
if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000
raise DHError, "OpenSSL::PKey::DH is immutable on OpenSSL 3.0; " \
"use OpenSSL::PKey.generate_key instead"
end
unless priv_key
tmp = OpenSSL::PKey.generate_key(self)
set_key(tmp.pub_key, tmp.priv_key)
end
self
end
class << self
# :call-seq:
# DH.generate(size, generator = 2) -> dh
#
# Creates a new DH instance from scratch by generating random parameters
# and a key pair.
#
# See also OpenSSL::PKey.generate_parameters and
# OpenSSL::PKey.generate_key.
#
# +size+::
# The desired key size in bits.
# +generator+::
# The generator.
def generate(size, generator = 2, &blk)
dhparams = OpenSSL::PKey.generate_parameters("DH", {
"dh_paramgen_prime_len" => size,
"dh_paramgen_generator" => generator,
}, &blk)
OpenSSL::PKey.generate_key(dhparams)
end
# Handle DH.new(size, generator) form here; new(str) and new() forms
# are handled by #initialize
def new(*args, &blk) # :nodoc:
if args[0].is_a?(Integer)
generate(*args, &blk)
else
super
end
end
end
end
class DSA
include OpenSSL::Marshal
# :call-seq:
# dsa.public_key -> dsanew
#
# Returns a new DSA instance that carries just the \DSA parameters and the
# public key.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of serializing the public key, to PEM or DER encoding of
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
# PKey#public_to_der.
def public_key
OpenSSL::PKey.read(public_to_der)
end
class << self
# :call-seq:
# DSA.generate(size) -> dsa
#
# Creates a new DSA instance by generating a private/public key pair
# from scratch.
#
# See also OpenSSL::PKey.generate_parameters and
# OpenSSL::PKey.generate_key.
#
# +size+::
# The desired key size in bits.
def generate(size, &blk)
# FIPS 186-4 specifies four (L,N) pairs: (1024,160), (2048,224),
# (2048,256), and (3072,256).
#
# q size is derived here with compatibility with
# DSA_generator_parameters_ex() which previous versions of ruby/openssl
# used to call.
qsize = size >= 2048 ? 256 : 160
dsaparams = OpenSSL::PKey.generate_parameters("DSA", {
"dsa_paramgen_bits" => size,
"dsa_paramgen_q_bits" => qsize,
}, &blk)
OpenSSL::PKey.generate_key(dsaparams)
end
# Handle DSA.new(size) form here; new(str) and new() forms
# are handled by #initialize
def new(*args, &blk) # :nodoc:
if args[0].is_a?(Integer)
generate(*args, &blk)
else
super
end
end
end
# :call-seq:
# dsa.syssign(string) -> string
#
# Computes and returns the \DSA signature of +string+, where +string+ is
# expected to be an already-computed message digest of the original input
# data. The signature is issued using the private key of this DSA instance.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
#
# +string+::
# A message digest of the original input data to be signed.
#
# Example:
# dsa = OpenSSL::PKey::DSA.new(2048)
# doc = "Sign me"
# digest = OpenSSL::Digest.digest('SHA1', doc)
#
# # With legacy #syssign and #sysverify:
# sig = dsa.syssign(digest)
# p dsa.sysverify(digest, sig) #=> true
#
# # With #sign_raw and #verify_raw:
# sig = dsa.sign_raw(nil, digest)
# p dsa.verify_raw(nil, sig, digest) #=> true
def syssign(string)
q or raise OpenSSL::PKey::DSAError, "incomplete DSA"
private? or raise OpenSSL::PKey::DSAError, "Private DSA key needed!"
begin
sign_raw(nil, string)
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::DSAError, $!.message
end
end
# :call-seq:
# dsa.sysverify(digest, sig) -> true | false
#
# Verifies whether the signature is valid given the message digest input.
# It does so by validating +sig+ using the public key of this DSA instance.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
#
# +digest+::
# A message digest of the original input data to be signed.
# +sig+::
# A \DSA signature value.
def sysverify(digest, sig)
verify_raw(nil, sig, digest)
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::DSAError, $!.message
end
end
if defined?(EC)
class EC
include OpenSSL::Marshal
# :call-seq:
# key.dsa_sign_asn1(data) -> String
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
def dsa_sign_asn1(data)
sign_raw(nil, data)
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::ECError, $!.message
end
# :call-seq:
# key.dsa_verify_asn1(data, sig) -> true | false
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw instead.
def dsa_verify_asn1(data, sig)
verify_raw(nil, sig, data)
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::ECError, $!.message
end
# :call-seq:
# ec.dh_compute_key(pubkey) -> string
#
# Derives a shared secret by ECDH. _pubkey_ must be an instance of
# OpenSSL::PKey::EC::Point and must belong to the same group.
#
# This method is provided for backwards compatibility, and calls #derive
# internally.
def dh_compute_key(pubkey)
obj = OpenSSL::ASN1.Sequence([
OpenSSL::ASN1.Sequence([
OpenSSL::ASN1.ObjectId("id-ecPublicKey"),
group.to_der,
]),
OpenSSL::ASN1.BitString(pubkey.to_octet_string(:uncompressed)),
])
derive(OpenSSL::PKey.read(obj.to_der))
end
end
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
class RSA
include OpenSSL::Marshal
# :call-seq:
# rsa.public_key -> rsanew
#
# Returns a new RSA instance that carries just the public key components.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of serializing the public key, to PEM or DER encoding of
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
# PKey#public_to_der.
def public_key
OpenSSL::PKey.read(public_to_der)
end
class << self
# :call-seq:
# RSA.generate(size, exponent = 65537) -> RSA
#
# Generates an \RSA keypair.
#
# See also OpenSSL::PKey.generate_key.
#
# +size+::
# The desired key size in bits.
# +exponent+::
# An odd Integer, normally 3, 17, or 65537.
def generate(size, exp = 0x10001, &blk)
OpenSSL::PKey.generate_key("RSA", {
"rsa_keygen_bits" => size,
"rsa_keygen_pubexp" => exp,
}, &blk)
end
# Handle RSA.new(size, exponent) form here; new(str) and new() forms
# are handled by #initialize
def new(*args, &blk) # :nodoc:
if args[0].is_a?(Integer)
generate(*args, &blk)
else
super
end
end
end
# :call-seq:
# rsa.private_encrypt(string) -> String
# rsa.private_encrypt(string, padding) -> String
#
# Encrypt +string+ with the private key. +padding+ defaults to
# PKCS1_PADDING, which is known to be insecure but is kept for backwards
# compatibility. The encrypted string output can be decrypted using
# #public_decrypt.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
# PKey::PKey#verify_recover instead.
def private_encrypt(string, padding = PKCS1_PADDING)
n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
private? or raise OpenSSL::PKey::RSAError, "private key needed."
begin
sign_raw(nil, string, {
"rsa_padding_mode" => translate_padding_mode(padding),
})
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::RSAError, $!.message
end
end
# :call-seq:
# rsa.public_decrypt(string) -> String
# rsa.public_decrypt(string, padding) -> String
#
# Decrypt +string+, which has been encrypted with the private key, with the
# public key. +padding+ defaults to PKCS1_PADDING which is known to be
# insecure but is kept for backwards compatibility.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#sign_raw and PKey::PKey#verify_raw, and
# PKey::PKey#verify_recover instead.
def public_decrypt(string, padding = PKCS1_PADDING)
n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
begin
verify_recover(nil, string, {
"rsa_padding_mode" => translate_padding_mode(padding),
})
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::RSAError, $!.message
end
end
# :call-seq:
# rsa.public_encrypt(string) -> String
# rsa.public_encrypt(string, padding) -> String
#
# Encrypt +string+ with the public key. +padding+ defaults to
# PKCS1_PADDING, which is known to be insecure but is kept for backwards
# compatibility. The encrypted string output can be decrypted using
# #private_decrypt.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
def public_encrypt(data, padding = PKCS1_PADDING)
n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
begin
encrypt(data, {
"rsa_padding_mode" => translate_padding_mode(padding),
})
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::RSAError, $!.message
end
end
# :call-seq:
# rsa.private_decrypt(string) -> String
# rsa.private_decrypt(string, padding) -> String
#
# Decrypt +string+, which has been encrypted with the public key, with the
# private key. +padding+ defaults to PKCS1_PADDING, which is known to be
# insecure but is kept for backwards compatibility.
#
# <b>Deprecated in version 3.0</b>.
# Consider using PKey::PKey#encrypt and PKey::PKey#decrypt instead.
def private_decrypt(data, padding = PKCS1_PADDING)
n or raise OpenSSL::PKey::RSAError, "incomplete RSA"
private? or raise OpenSSL::PKey::RSAError, "private key needed."
begin
decrypt(data, {
"rsa_padding_mode" => translate_padding_mode(padding),
})
rescue OpenSSL::PKey::PKeyError
raise OpenSSL::PKey::RSAError, $!.message
end
end
PKCS1_PADDING = 1
SSLV23_PADDING = 2
NO_PADDING = 3
PKCS1_OAEP_PADDING = 4
private def translate_padding_mode(num)
case num
when PKCS1_PADDING
"pkcs1"
when SSLV23_PADDING
"sslv23"
when NO_PADDING
"none"
when PKCS1_OAEP_PADDING
"oaep"
else
raise OpenSSL::PKey::PKeyError, "unsupported padding mode"
end
end
end
end
share/ruby/openssl/x509.rb 0000644 00000027147 15173547334 0011373 0 ustar 00 # frozen_string_literal: true
#--
# = 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'.)
#++
require_relative 'marshal'
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
include OpenSSL::Marshal
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
module Helpers
def find_extension(oid)
extensions.find { |e| e.oid == oid }
end
end
module SubjectKeyIdentifier
include Helpers
# Get the subject's key identifier from the subjectKeyIdentifier
# exteension, as described in RFC5280 Section 4.2.1.2.
#
# Returns the binary String key identifier or nil or raises
# ASN1::ASN1Error.
def subject_key_identifier
ext = find_extension("subjectKeyIdentifier")
return nil if ext.nil?
ski_asn1 = ASN1.decode(ext.value_der)
if ext.critical? || ski_asn1.tag_class != :UNIVERSAL || ski_asn1.tag != ASN1::OCTET_STRING
raise ASN1::ASN1Error, "invalid extension"
end
ski_asn1.value
end
end
module AuthorityKeyIdentifier
include Helpers
# Get the issuing certificate's key identifier from the
# authorityKeyIdentifier extension, as described in RFC5280
# Section 4.2.1.1
#
# Returns the binary String keyIdentifier or nil or raises
# ASN1::ASN1Error.
def authority_key_identifier
ext = find_extension("authorityKeyIdentifier")
return nil if ext.nil?
aki_asn1 = ASN1.decode(ext.value_der)
if ext.critical? || aki_asn1.tag_class != :UNIVERSAL || aki_asn1.tag != ASN1::SEQUENCE
raise ASN1::ASN1Error, "invalid extension"
end
key_id = aki_asn1.value.find do |v|
v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
end
key_id.nil? ? nil : key_id.value
end
end
module CRLDistributionPoints
include Helpers
# Get the distributionPoint fullName URI from the certificate's CRL
# distribution points extension, as described in RFC5280 Section
# 4.2.1.13
#
# Returns an array of strings or nil or raises ASN1::ASN1Error.
def crl_uris
ext = find_extension("crlDistributionPoints")
return nil if ext.nil?
cdp_asn1 = ASN1.decode(ext.value_der)
if cdp_asn1.tag_class != :UNIVERSAL || cdp_asn1.tag != ASN1::SEQUENCE
raise ASN1::ASN1Error, "invalid extension"
end
crl_uris = cdp_asn1.map do |crl_distribution_point|
distribution_point = crl_distribution_point.value.find do |v|
v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
end
full_name = distribution_point&.value&.find do |v|
v.tag_class == :CONTEXT_SPECIFIC && v.tag == 0
end
full_name&.value&.find do |v|
v.tag_class == :CONTEXT_SPECIFIC && v.tag == 6 # uniformResourceIdentifier
end
end
crl_uris&.map(&:value)
end
end
module AuthorityInfoAccess
include Helpers
# Get the information and services for the issuer from the certificate's
# authority information access extension exteension, as described in RFC5280
# Section 4.2.2.1.
#
# Returns an array of strings or nil or raises ASN1::ASN1Error.
def ca_issuer_uris
aia_asn1 = parse_aia_asn1
return nil if aia_asn1.nil?
ca_issuer = aia_asn1.value.select do |authority_info_access|
authority_info_access.value.first.value == "caIssuers"
end
ca_issuer&.map(&:value)&.map(&:last)&.map(&:value)
end
# Get the URIs for OCSP from the certificate's authority information access
# extension exteension, as described in RFC5280 Section 4.2.2.1.
#
# Returns an array of strings or nil or raises ASN1::ASN1Error.
def ocsp_uris
aia_asn1 = parse_aia_asn1
return nil if aia_asn1.nil?
ocsp = aia_asn1.value.select do |authority_info_access|
authority_info_access.value.first.value == "OCSP"
end
ocsp&.map(&:value)&.map(&:last)&.map(&:value)
end
private
def parse_aia_asn1
ext = find_extension("authorityInfoAccess")
return nil if ext.nil?
aia_asn1 = ASN1.decode(ext.value_der)
if ext.critical? || aia_asn1.tag_class != :UNIVERSAL || aia_asn1.tag != ASN1::SEQUENCE
raise ASN1::ASN1Error, "invalid extension"
end
aia_asn1
end
end
end
class Name
include OpenSSL::Marshal
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
# Parses the UTF-8 string representation of a distinguished name,
# according to RFC 2253.
#
# See also #to_utf8 for the opposite operation.
def parse_rfc2253(str, template=OBJECT_TYPE_TEMPLATE)
ary = OpenSSL::X509::Name::RFC2253DN.scan(str)
self.new(ary, template)
end
# Parses the string representation of a distinguished name. Two
# different forms are supported:
#
# - \OpenSSL format (<tt>X509_NAME_oneline()</tt>) used by
# <tt>#to_s</tt>. For example: <tt>/DC=com/DC=example/CN=nobody</tt>
# - \OpenSSL format (<tt>X509_NAME_print()</tt>)
# used by <tt>#to_s(OpenSSL::X509::Name::COMPAT)</tt>. For example:
# <tt>DC=com, DC=example, CN=nobody</tt>
#
# Neither of them is standardized and has quirks and inconsistencies
# in handling of escaped characters or multi-valued RDNs.
#
# Use of this method is discouraged in new applications. See
# Name.parse_rfc2253 and #to_utf8 for the alternative.
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
include OpenSSL::Marshal
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
include OpenSSL::Marshal
include Extension::SubjectKeyIdentifier
include Extension::AuthorityKeyIdentifier
include Extension::CRLDistributionPoints
include Extension::AuthorityInfoAccess
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
def self.load_file(path)
load(File.binread(path))
end
end
class CRL
include OpenSSL::Marshal
include Extension::AuthorityKeyIdentifier
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
include OpenSSL::Marshal
def ==(other)
return false unless Request === other
to_der == other.to_der
end
end
end
end
share/ruby/openssl/bn.rb 0000644 00000001303 15173547334 0011247 0 ustar 00 # frozen_string_literal: true
#--
#
# = 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
share/ruby/optparse.rb 0000644 00000172077 15173547334 0011043 0 ustar 00 # frozen_string_literal: true
#
# optparse.rb - command-line option analysis with the OptionParser class.
#
# Author:: Nobu Nakada
# Documentation:: Nobu Nakada and Gavin Sinclair.
#
# See OptionParser for documentation.
#
#--
# == Developer Documentation (not for RDoc output)
#
# === Class tree
#
# - OptionParser:: front end
# - OptionParser::Switch:: each switches
# - OptionParser::List:: options list
# - OptionParser::ParseError:: errors on parsing
# - OptionParser::AmbiguousOption
# - OptionParser::NeedlessArgument
# - OptionParser::MissingArgument
# - OptionParser::InvalidOption
# - OptionParser::InvalidArgument
# - OptionParser::AmbiguousArgument
#
# === Object relationship diagram
#
# +--------------+
# | OptionParser |<>-----+
# +--------------+ | +--------+
# | ,-| Switch |
# on_head -------->+---------------+ / +--------+
# accept/reject -->| List |<|>-
# | |<|>- +----------+
# on ------------->+---------------+ `-| argument |
# : : | class |
# +---------------+ |==========|
# on_tail -------->| | |pattern |
# +---------------+ |----------|
# OptionParser.accept ->| DefaultList | |converter |
# reject |(shared between| +----------+
# | all instances)|
# +---------------+
#
#++
#
# == OptionParser
#
# === New to \OptionParser?
#
# See the {Tutorial}[optparse/tutorial.rdoc].
#
# === Introduction
#
# OptionParser is a class for command-line option analysis. It is much more
# advanced, yet also easier to use, than GetoptLong, and is a more Ruby-oriented
# solution.
#
# === Features
#
# 1. The argument specification and the code to handle it are written in the
# same place.
# 2. It can output an option summary; you don't need to maintain this string
# separately.
# 3. Optional and mandatory arguments are specified very gracefully.
# 4. Arguments can be automatically converted to a specified class.
# 5. Arguments can be restricted to a certain set.
#
# All of these features are demonstrated in the examples below. See
# #make_switch for full documentation.
#
# === Minimal example
#
# require 'optparse'
#
# options = {}
# OptionParser.new do |parser|
# parser.banner = "Usage: example.rb [options]"
#
# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
# options[:verbose] = v
# end
# end.parse!
#
# p options
# p ARGV
#
# === Generating Help
#
# OptionParser can be used to automatically generate help for the commands you
# write:
#
# require 'optparse'
#
# Options = Struct.new(:name)
#
# class Parser
# def self.parse(options)
# args = Options.new("world")
#
# opt_parser = OptionParser.new do |parser|
# parser.banner = "Usage: example.rb [options]"
#
# parser.on("-nNAME", "--name=NAME", "Name to say hello to") do |n|
# args.name = n
# end
#
# parser.on("-h", "--help", "Prints this help") do
# puts parser
# exit
# end
# end
#
# opt_parser.parse!(options)
# return args
# end
# end
# options = Parser.parse %w[--help]
#
# #=>
# # Usage: example.rb [options]
# # -n, --name=NAME Name to say hello to
# # -h, --help Prints this help
#
# === Required Arguments
#
# For options that require an argument, option specification strings may include an
# option name in all caps. If an option is used without the required argument,
# an exception will be raised.
#
# require 'optparse'
#
# options = {}
# OptionParser.new do |parser|
# parser.on("-r", "--require LIBRARY",
# "Require the LIBRARY before executing your script") do |lib|
# puts "You required #{lib}!"
# end
# end.parse!
#
# Used:
#
# $ ruby optparse-test.rb -r
# optparse-test.rb:9:in `<main>': missing argument: -r (OptionParser::MissingArgument)
# $ ruby optparse-test.rb -r my-library
# You required my-library!
#
# === Type Coercion
#
# OptionParser supports the ability to coerce command line arguments
# into objects for us.
#
# OptionParser comes with a few ready-to-use kinds of type
# coercion. They are:
#
# - Date -- Anything accepted by +Date.parse+
# - DateTime -- Anything accepted by +DateTime.parse+
# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+
# - URI -- Anything accepted by +URI.parse+
# - Shellwords -- Anything accepted by +Shellwords.shellwords+
# - String -- Any non-empty string
# - Integer -- Any integer. Will convert octal. (e.g. 124, -3, 040)
# - Float -- Any float. (e.g. 10, 3.14, -100E+13)
# - Numeric -- Any integer, float, or rational (1, 3.4, 1/3)
# - DecimalInteger -- Like +Integer+, but no octal format.
# - OctalInteger -- Like +Integer+, but no decimal format.
# - DecimalNumeric -- Decimal integer or float.
# - TrueClass -- Accepts '+, yes, true, -, no, false' and
# defaults as +true+
# - FalseClass -- Same as +TrueClass+, but defaults to +false+
# - Array -- Strings separated by ',' (e.g. 1,2,3)
# - Regexp -- Regular expressions. Also includes options.
#
# We can also add our own coercions, which we will cover below.
#
# ==== Using Built-in Conversions
#
# As an example, the built-in +Time+ conversion is used. The other built-in
# conversions behave in the same way.
# OptionParser will attempt to parse the argument
# as a +Time+. If it succeeds, that time will be passed to the
# handler block. Otherwise, an exception will be raised.
#
# require 'optparse'
# require 'optparse/time'
# OptionParser.new do |parser|
# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
# p time
# end
# end.parse!
#
# Used:
#
# $ ruby optparse-test.rb -t nonsense
# ... invalid argument: -t nonsense (OptionParser::InvalidArgument)
# $ ruby optparse-test.rb -t 10-11-12
# 2010-11-12 00:00:00 -0500
# $ ruby optparse-test.rb -t 9:30
# 2014-08-13 09:30:00 -0400
#
# ==== Creating Custom Conversions
#
# The +accept+ method on OptionParser may be used to create converters.
# It specifies which conversion block to call whenever a class is specified.
# The example below uses it to fetch a +User+ object before the +on+ handler receives it.
#
# require 'optparse'
#
# User = Struct.new(:id, :name)
#
# def find_user id
# not_found = ->{ raise "No User Found for id #{id}" }
# [ User.new(1, "Sam"),
# User.new(2, "Gandalf") ].find(not_found) do |u|
# u.id == id
# end
# end
#
# op = OptionParser.new
# op.accept(User) do |user_id|
# find_user user_id.to_i
# end
#
# op.on("--user ID", User) do |user|
# puts user
# end
#
# op.parse!
#
# Used:
#
# $ ruby optparse-test.rb --user 1
# #<struct User id=1, name="Sam">
# $ ruby optparse-test.rb --user 2
# #<struct User id=2, name="Gandalf">
# $ ruby optparse-test.rb --user 3
# optparse-test.rb:15:in `block in find_user': No User Found for id 3 (RuntimeError)
#
# === Store options to a Hash
#
# The +into+ option of +order+, +parse+ and so on methods stores command line options into a Hash.
#
# require 'optparse'
#
# options = {}
# OptionParser.new do |parser|
# parser.on('-a')
# parser.on('-b NUM', Integer)
# parser.on('-v', '--verbose')
# end.parse!(into: options)
#
# p options
#
# Used:
#
# $ ruby optparse-test.rb -a
# {:a=>true}
# $ ruby optparse-test.rb -a -v
# {:a=>true, :verbose=>true}
# $ ruby optparse-test.rb -a -b 100
# {:a=>true, :b=>100}
#
# === Complete example
#
# The following example is a complete Ruby program. You can run it and see the
# effect of specifying various options. This is probably the best way to learn
# the features of +optparse+.
#
# require 'optparse'
# require 'optparse/time'
# require 'ostruct'
# require 'pp'
#
# class OptparseExample
# Version = '1.0.0'
#
# CODES = %w[iso-2022-jp shift_jis euc-jp utf8 binary]
# CODE_ALIASES = { "jis" => "iso-2022-jp", "sjis" => "shift_jis" }
#
# class ScriptOptions
# attr_accessor :library, :inplace, :encoding, :transfer_type,
# :verbose, :extension, :delay, :time, :record_separator,
# :list
#
# def initialize
# self.library = []
# self.inplace = false
# self.encoding = "utf8"
# self.transfer_type = :auto
# self.verbose = false
# end
#
# def define_options(parser)
# parser.banner = "Usage: example.rb [options]"
# parser.separator ""
# parser.separator "Specific options:"
#
# # add additional options
# perform_inplace_option(parser)
# delay_execution_option(parser)
# execute_at_time_option(parser)
# specify_record_separator_option(parser)
# list_example_option(parser)
# specify_encoding_option(parser)
# optional_option_argument_with_keyword_completion_option(parser)
# boolean_verbose_option(parser)
#
# parser.separator ""
# parser.separator "Common options:"
# # No argument, shows at tail. This will print an options summary.
# # Try it and see!
# parser.on_tail("-h", "--help", "Show this message") do
# puts parser
# exit
# end
# # Another typical switch to print the version.
# parser.on_tail("--version", "Show version") do
# puts Version
# exit
# end
# end
#
# def perform_inplace_option(parser)
# # Specifies an optional option argument
# parser.on("-i", "--inplace [EXTENSION]",
# "Edit ARGV files in place",
# "(make backup if EXTENSION supplied)") do |ext|
# self.inplace = true
# self.extension = ext || ''
# self.extension.sub!(/\A\.?(?=.)/, ".") # Ensure extension begins with dot.
# end
# end
#
# def delay_execution_option(parser)
# # Cast 'delay' argument to a Float.
# parser.on("--delay N", Float, "Delay N seconds before executing") do |n|
# self.delay = n
# end
# end
#
# def execute_at_time_option(parser)
# # Cast 'time' argument to a Time object.
# parser.on("-t", "--time [TIME]", Time, "Begin execution at given time") do |time|
# self.time = time
# end
# end
#
# def specify_record_separator_option(parser)
# # Cast to octal integer.
# parser.on("-F", "--irs [OCTAL]", OptionParser::OctalInteger,
# "Specify record separator (default \\0)") do |rs|
# self.record_separator = rs
# end
# end
#
# def list_example_option(parser)
# # List of arguments.
# parser.on("--list x,y,z", Array, "Example 'list' of arguments") do |list|
# self.list = list
# end
# end
#
# def specify_encoding_option(parser)
# # Keyword completion. We are specifying a specific set of arguments (CODES
# # and CODE_ALIASES - notice the latter is a Hash), and the user may provide
# # the shortest unambiguous text.
# code_list = (CODE_ALIASES.keys + CODES).join(', ')
# parser.on("--code CODE", CODES, CODE_ALIASES, "Select encoding",
# "(#{code_list})") do |encoding|
# self.encoding = encoding
# end
# end
#
# def optional_option_argument_with_keyword_completion_option(parser)
# # Optional '--type' option argument with keyword completion.
# parser.on("--type [TYPE]", [:text, :binary, :auto],
# "Select transfer type (text, binary, auto)") do |t|
# self.transfer_type = t
# end
# end
#
# def boolean_verbose_option(parser)
# # Boolean switch.
# parser.on("-v", "--[no-]verbose", "Run verbosely") do |v|
# self.verbose = v
# end
# end
# end
#
# #
# # Return a structure describing the options.
# #
# def parse(args)
# # The options specified on the command line will be collected in
# # *options*.
#
# @options = ScriptOptions.new
# @args = OptionParser.new do |parser|
# @options.define_options(parser)
# parser.parse!(args)
# end
# @options
# end
#
# attr_reader :parser, :options
# end # class OptparseExample
#
# example = OptparseExample.new
# options = example.parse(ARGV)
# pp options # example.options
# pp ARGV
#
# === Shell Completion
#
# For modern shells (e.g. bash, zsh, etc.), you can use shell
# completion for command line options.
#
# === Further documentation
#
# The above examples, along with the accompanying
# {Tutorial}[optparse/tutorial.rdoc],
# should be enough to learn how to use this class.
# If you have any questions, file a ticket at http://bugs.ruby-lang.org.
#
class OptionParser
OptionParser::Version = "0.3.1"
# :stopdoc:
NoArgument = [NO_ARGUMENT = :NONE, nil].freeze
RequiredArgument = [REQUIRED_ARGUMENT = :REQUIRED, true].freeze
OptionalArgument = [OPTIONAL_ARGUMENT = :OPTIONAL, false].freeze
# :startdoc:
#
# Keyword completion module. This allows partial arguments to be specified
# and resolved against a list of acceptable values.
#
module Completion
def self.regexp(key, icase)
Regexp.new('\A' + Regexp.quote(key).gsub(/\w+\b/, '\&\w*'), icase)
end
def self.candidate(key, icase = false, pat = nil, &block)
pat ||= Completion.regexp(key, icase)
candidates = []
block.call do |k, *v|
(if Regexp === k
kn = ""
k === key
else
kn = defined?(k.id2name) ? k.id2name : k
pat === kn
end) or next
v << k if v.empty?
candidates << [k, v, kn]
end
candidates
end
def candidate(key, icase = false, pat = nil)
Completion.candidate(key, icase, pat, &method(:each))
end
public
def complete(key, icase = false, pat = nil)
candidates = candidate(key, icase, pat, &method(:each)).sort_by {|k, v, kn| kn.size}
if candidates.size == 1
canon, sw, * = candidates[0]
elsif candidates.size > 1
canon, sw, cn = candidates.shift
candidates.each do |k, v, kn|
next if sw == v
if String === cn and String === kn
if cn.rindex(kn, 0)
canon, sw, cn = k, v, kn
next
elsif kn.rindex(cn, 0)
next
end
end
throw :ambiguous, key
end
end
if canon
block_given? or return key, *sw
yield(key, *sw)
end
end
def convert(opt = nil, val = nil, *)
val
end
end
#
# Map from option/keyword string to object with completion.
#
class OptionMap < Hash
include Completion
end
#
# Individual switch class. Not important to the user.
#
# Defined within Switch are several Switch-derived classes: NoArgument,
# RequiredArgument, etc.
#
class Switch
attr_reader :pattern, :conv, :short, :long, :arg, :desc, :block
#
# Guesses argument style from +arg+. Returns corresponding
# OptionParser::Switch class (OptionalArgument, etc.).
#
def self.guess(arg)
case arg
when ""
t = self
when /\A=?\[/
t = Switch::OptionalArgument
when /\A\s+\[/
t = Switch::PlacedArgument
else
t = Switch::RequiredArgument
end
self >= t or incompatible_argument_styles(arg, t)
t
end
def self.incompatible_argument_styles(arg, t)
raise(ArgumentError, "#{arg}: incompatible argument styles\n #{self}, #{t}",
ParseError.filter_backtrace(caller(2)))
end
def self.pattern
NilClass
end
def initialize(pattern = nil, conv = nil,
short = nil, long = nil, arg = nil,
desc = ([] if short or long), block = nil, &_block)
raise if Array === pattern
block ||= _block
@pattern, @conv, @short, @long, @arg, @desc, @block =
pattern, conv, short, long, arg, desc, block
end
#
# Parses +arg+ and returns rest of +arg+ and matched portion to the
# argument pattern. Yields when the pattern doesn't match substring.
#
def parse_arg(arg) # :nodoc:
pattern or return nil, [arg]
unless m = pattern.match(arg)
yield(InvalidArgument, arg)
return arg, []
end
if String === m
m = [s = m]
else
m = m.to_a
s = m[0]
return nil, m unless String === s
end
raise InvalidArgument, arg unless arg.rindex(s, 0)
return nil, m if s.length == arg.length
yield(InvalidArgument, arg) # didn't match whole arg
return arg[s.length..-1], m
end
private :parse_arg
#
# Parses argument, converts and returns +arg+, +block+ and result of
# conversion. Yields at semi-error condition instead of raising an
# exception.
#
def conv_arg(arg, val = []) # :nodoc:
if conv
val = conv.call(*val)
else
val = proc {|v| v}.call(*val)
end
return arg, block, val
end
private :conv_arg
#
# Produces the summary text. Each line of the summary is yielded to the
# block (without newline).
#
# +sdone+:: Already summarized short style options keyed hash.
# +ldone+:: Already summarized long style options keyed hash.
# +width+:: Width of left side (option part). In other words, the right
# side (description part) starts after +width+ columns.
# +max+:: Maximum width of left side -> the options are filled within
# +max+ columns.
# +indent+:: Prefix string indents all summarized lines.
#
def summarize(sdone = {}, ldone = {}, width = 1, max = width - 1, indent = "")
sopts, lopts = [], [], nil
@short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
@long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
return if sopts.empty? and lopts.empty? # completely hidden
left = [sopts.join(', ')]
right = desc.dup
while s = lopts.shift
l = left[-1].length + s.length
l += arg.length if left.size == 1 && arg
l < max or sopts.empty? or left << +''
left[-1] << (left[-1].empty? ? ' ' * 4 : ', ') << s
end
if arg
left[0] << (left[1] ? arg.sub(/\A(\[?)=/, '\1') + ',' : arg)
end
mlen = left.collect {|ss| ss.length}.max.to_i
while mlen > width and l = left.shift
mlen = left.collect {|ss| ss.length}.max.to_i if l.length == mlen
if l.length < width and (r = right[0]) and !r.empty?
l = l.to_s.ljust(width) + ' ' + r
right.shift
end
yield(indent + l)
end
while begin l = left.shift; r = right.shift; l or r end
l = l.to_s.ljust(width) + ' ' + r if r and !r.empty?
yield(indent + l)
end
self
end
def add_banner(to) # :nodoc:
unless @short or @long
s = desc.join
to << " [" + s + "]..." unless s.empty?
end
to
end
def match_nonswitch?(str) # :nodoc:
@pattern =~ str unless @short or @long
end
#
# Main name of the switch.
#
def switch_name
(long.first || short.first).sub(/\A-+(?:\[no-\])?/, '')
end
def compsys(sdone, ldone) # :nodoc:
sopts, lopts = [], []
@short.each {|s| sdone.fetch(s) {sopts << s}; sdone[s] = true} if @short
@long.each {|s| ldone.fetch(s) {lopts << s}; ldone[s] = true} if @long
return if sopts.empty? and lopts.empty? # completely hidden
(sopts+lopts).each do |opt|
# "(-x -c -r)-l[left justify]"
if /^--\[no-\](.+)$/ =~ opt
o = $1
yield("--#{o}", desc.join(""))
yield("--no-#{o}", desc.join(""))
else
yield("#{opt}", desc.join(""))
end
end
end
def pretty_print_contents(q) # :nodoc:
if @block
q.text ":" + @block.source_location.join(":") + ":"
first = false
else
first = true
end
[@short, @long].each do |list|
list.each do |opt|
if first
q.text ":"
first = false
end
q.breakable
q.text opt
end
end
end
def pretty_print(q) # :nodoc:
q.object_group(self) {pretty_print_contents(q)}
end
#
# Switch that takes no arguments.
#
class NoArgument < self
#
# Raises an exception if any arguments given.
#
def parse(arg, argv)
yield(NeedlessArgument, arg) if arg
conv_arg(arg)
end
def self.incompatible_argument_styles(*)
end
def self.pattern
Object
end
def pretty_head # :nodoc:
"NoArgument"
end
end
#
# Switch that takes an argument.
#
class RequiredArgument < self
#
# Raises an exception if argument is not present.
#
def parse(arg, argv)
unless arg
raise MissingArgument if argv.empty?
arg = argv.shift
end
conv_arg(*parse_arg(arg, &method(:raise)))
end
def pretty_head # :nodoc:
"Required"
end
end
#
# Switch that can omit argument.
#
class OptionalArgument < self
#
# Parses argument if given, or uses default value.
#
def parse(arg, argv, &error)
if arg
conv_arg(*parse_arg(arg, &error))
else
conv_arg(arg)
end
end
def pretty_head # :nodoc:
"Optional"
end
end
#
# Switch that takes an argument, which does not begin with '-' or is '-'.
#
class PlacedArgument < self
#
# Returns nil if argument is not present or begins with '-' and is not '-'.
#
def parse(arg, argv, &error)
if !(val = arg) and (argv.empty? or /\A-./ =~ (val = argv[0]))
return nil, block, nil
end
opt = (val = parse_arg(val, &error))[1]
val = conv_arg(*val)
if opt and !arg
argv.shift
else
val[0] = nil
end
val
end
def pretty_head # :nodoc:
"Placed"
end
end
end
#
# Simple option list providing mapping from short and/or long option
# string to OptionParser::Switch and mapping from acceptable argument to
# matching pattern and converter pair. Also provides summary feature.
#
class List
# Map from acceptable argument types to pattern and converter pairs.
attr_reader :atype
# Map from short style option switches to actual switch objects.
attr_reader :short
# Map from long style option switches to actual switch objects.
attr_reader :long
# List of all switches and summary string.
attr_reader :list
#
# Just initializes all instance variables.
#
def initialize
@atype = {}
@short = OptionMap.new
@long = OptionMap.new
@list = []
end
def pretty_print(q) # :nodoc:
q.group(1, "(", ")") do
@list.each do |sw|
next unless Switch === sw
q.group(1, "(" + sw.pretty_head, ")") do
sw.pretty_print_contents(q)
end
end
end
end
#
# See OptionParser.accept.
#
def accept(t, pat = /.*/m, &block)
if pat
pat.respond_to?(:match) or
raise TypeError, "has no `match'", ParseError.filter_backtrace(caller(2))
else
pat = t if t.respond_to?(:match)
end
unless block
block = pat.method(:convert).to_proc if pat.respond_to?(:convert)
end
@atype[t] = [pat, block]
end
#
# See OptionParser.reject.
#
def reject(t)
@atype.delete(t)
end
#
# Adds +sw+ according to +sopts+, +lopts+ and +nlopts+.
#
# +sw+:: OptionParser::Switch instance to be added.
# +sopts+:: Short style option list.
# +lopts+:: Long style option list.
# +nlopts+:: Negated long style options list.
#
def update(sw, sopts, lopts, nsw = nil, nlopts = nil) # :nodoc:
sopts.each {|o| @short[o] = sw} if sopts
lopts.each {|o| @long[o] = sw} if lopts
nlopts.each {|o| @long[o] = nsw} if nsw and nlopts
used = @short.invert.update(@long.invert)
@list.delete_if {|o| Switch === o and !used[o]}
end
private :update
#
# Inserts +switch+ at the head of the list, and associates short, long
# and negated long options. Arguments are:
#
# +switch+:: OptionParser::Switch instance to be inserted.
# +short_opts+:: List of short style options.
# +long_opts+:: List of long style options.
# +nolong_opts+:: List of long style options with "no-" prefix.
#
# prepend(switch, short_opts, long_opts, nolong_opts)
#
def prepend(*args)
update(*args)
@list.unshift(args[0])
end
#
# Appends +switch+ at the tail of the list, and associates short, long
# and negated long options. Arguments are:
#
# +switch+:: OptionParser::Switch instance to be inserted.
# +short_opts+:: List of short style options.
# +long_opts+:: List of long style options.
# +nolong_opts+:: List of long style options with "no-" prefix.
#
# append(switch, short_opts, long_opts, nolong_opts)
#
def append(*args)
update(*args)
@list.push(args[0])
end
#
# Searches +key+ in +id+ list. The result is returned or yielded if a
# block is given. If it isn't found, nil is returned.
#
def search(id, key)
if list = __send__(id)
val = list.fetch(key) {return nil}
block_given? ? yield(val) : val
end
end
#
# Searches list +id+ for +opt+ and the optional patterns for completion
# +pat+. If +icase+ is true, the search is case insensitive. The result
# is returned or yielded if a block is given. If it isn't found, nil is
# returned.
#
def complete(id, opt, icase = false, *pat, &block)
__send__(id).complete(opt, icase, *pat, &block)
end
def get_candidates(id)
yield __send__(id).keys
end
#
# Iterates over each option, passing the option to the +block+.
#
def each_option(&block)
list.each(&block)
end
#
# Creates the summary table, passing each line to the +block+ (without
# newline). The arguments +args+ are passed along to the summarize
# method which is called on every option.
#
def summarize(*args, &block)
sum = []
list.reverse_each do |opt|
if opt.respond_to?(:summarize) # perhaps OptionParser::Switch
s = []
opt.summarize(*args) {|l| s << l}
sum.concat(s.reverse)
elsif !opt or opt.empty?
sum << ""
elsif opt.respond_to?(:each_line)
sum.concat([*opt.each_line].reverse)
else
sum.concat([*opt.each].reverse)
end
end
sum.reverse_each(&block)
end
def add_banner(to) # :nodoc:
list.each do |opt|
if opt.respond_to?(:add_banner)
opt.add_banner(to)
end
end
to
end
def compsys(*args, &block) # :nodoc:
list.each do |opt|
if opt.respond_to?(:compsys)
opt.compsys(*args, &block)
end
end
end
end
#
# Hash with completion search feature. See OptionParser::Completion.
#
class CompletingHash < Hash
include Completion
#
# Completion for hash key.
#
def match(key)
*values = fetch(key) {
raise AmbiguousArgument, catch(:ambiguous) {return complete(key)}
}
return key, *values
end
end
# :stopdoc:
#
# Enumeration of acceptable argument styles. Possible values are:
#
# NO_ARGUMENT:: The switch takes no arguments. (:NONE)
# REQUIRED_ARGUMENT:: The switch requires an argument. (:REQUIRED)
# OPTIONAL_ARGUMENT:: The switch requires an optional argument. (:OPTIONAL)
#
# Use like --switch=argument (long style) or -Xargument (short style). For
# short style, only portion matched to argument pattern is treated as
# argument.
#
ArgumentStyle = {}
NoArgument.each {|el| ArgumentStyle[el] = Switch::NoArgument}
RequiredArgument.each {|el| ArgumentStyle[el] = Switch::RequiredArgument}
OptionalArgument.each {|el| ArgumentStyle[el] = Switch::OptionalArgument}
ArgumentStyle.freeze
#
# Switches common used such as '--', and also provides default
# argument classes
#
DefaultList = List.new
DefaultList.short['-'] = Switch::NoArgument.new {}
DefaultList.long[''] = Switch::NoArgument.new {throw :terminate}
COMPSYS_HEADER = <<'XXX' # :nodoc:
typeset -A opt_args
local context state line
_arguments -s -S \
XXX
def compsys(to, name = File.basename($0)) # :nodoc:
to << "#compdef #{name}\n"
to << COMPSYS_HEADER
visit(:compsys, {}, {}) {|o, d|
to << %Q[ "#{o}[#{d.gsub(/[\"\[\]]/, '\\\\\&')}]" \\\n]
}
to << " '*:file:_files' && return 0\n"
end
#
# Default options for ARGV, which never appear in option summary.
#
Officious = {}
#
# --help
# Shows option summary.
#
Officious['help'] = proc do |parser|
Switch::NoArgument.new do |arg|
puts parser.help
exit
end
end
#
# --*-completion-bash=WORD
# Shows candidates for command line completion.
#
Officious['*-completion-bash'] = proc do |parser|
Switch::RequiredArgument.new do |arg|
puts parser.candidate(arg)
exit
end
end
#
# --*-completion-zsh[=NAME:FILE]
# Creates zsh completion file.
#
Officious['*-completion-zsh'] = proc do |parser|
Switch::OptionalArgument.new do |arg|
parser.compsys(STDOUT, arg)
exit
end
end
#
# --version
# Shows version string if Version is defined.
#
Officious['version'] = proc do |parser|
Switch::OptionalArgument.new do |pkg|
if pkg
begin
require 'optparse/version'
rescue LoadError
else
show_version(*pkg.split(/,/)) or
abort("#{parser.program_name}: no version found in package #{pkg}")
exit
end
end
v = parser.ver or abort("#{parser.program_name}: version unknown")
puts v
exit
end
end
# :startdoc:
#
# Class methods
#
#
# Initializes a new instance and evaluates the optional block in context
# of the instance. Arguments +args+ are passed to #new, see there for
# description of parameters.
#
# This method is *deprecated*, its behavior corresponds to the older #new
# method.
#
def self.with(*args, &block)
opts = new(*args)
opts.instance_eval(&block)
opts
end
#
# Returns an incremented value of +default+ according to +arg+.
#
def self.inc(arg, default = nil)
case arg
when Integer
arg.nonzero?
when nil
default.to_i + 1
end
end
def inc(*args)
self.class.inc(*args)
end
#
# Initializes the instance and yields itself if called with a block.
#
# +banner+:: Banner message.
# +width+:: Summary width.
# +indent+:: Summary indent.
#
def initialize(banner = nil, width = 32, indent = ' ' * 4)
@stack = [DefaultList, List.new, List.new]
@program_name = nil
@banner = banner
@summary_width = width
@summary_indent = indent
@default_argv = ARGV
@require_exact = false
@raise_unknown = true
add_officious
yield self if block_given?
end
def add_officious # :nodoc:
list = base()
Officious.each do |opt, block|
list.long[opt] ||= block.call(self)
end
end
#
# Terminates option parsing. Optional parameter +arg+ is a string pushed
# back to be the first non-option argument.
#
def terminate(arg = nil)
self.class.terminate(arg)
end
def self.terminate(arg = nil)
throw :terminate, arg
end
@stack = [DefaultList]
def self.top() DefaultList end
#
# Directs to accept specified class +t+. The argument string is passed to
# the block in which it should be converted to the desired class.
#
# +t+:: Argument class specifier, any object including Class.
# +pat+:: Pattern for argument, defaults to +t+ if it responds to match.
#
# accept(t, pat, &block)
#
def accept(*args, &blk) top.accept(*args, &blk) end
#
# See #accept.
#
def self.accept(*args, &blk) top.accept(*args, &blk) end
#
# Directs to reject specified class argument.
#
# +t+:: Argument class specifier, any object including Class.
#
# reject(t)
#
def reject(*args, &blk) top.reject(*args, &blk) end
#
# See #reject.
#
def self.reject(*args, &blk) top.reject(*args, &blk) end
#
# Instance methods
#
# Heading banner preceding summary.
attr_writer :banner
# Program name to be emitted in error message and default banner,
# defaults to $0.
attr_writer :program_name
# Width for option list portion of summary. Must be Numeric.
attr_accessor :summary_width
# Indentation for summary. Must be String (or have + String method).
attr_accessor :summary_indent
# Strings to be parsed in default.
attr_accessor :default_argv
# Whether to require that options match exactly (disallows providing
# abbreviated long option as short option).
attr_accessor :require_exact
# Whether to raise at unknown option.
attr_accessor :raise_unknown
#
# Heading banner preceding summary.
#
def banner
unless @banner
@banner = +"Usage: #{program_name} [options]"
visit(:add_banner, @banner)
end
@banner
end
#
# Program name to be emitted in error message and default banner, defaults
# to $0.
#
def program_name
@program_name || File.basename($0, '.*')
end
# for experimental cascading :-)
alias set_banner banner=
alias set_program_name program_name=
alias set_summary_width summary_width=
alias set_summary_indent summary_indent=
# Version
attr_writer :version
# Release code
attr_writer :release
#
# Version
#
def version
(defined?(@version) && @version) || (defined?(::Version) && ::Version)
end
#
# Release code
#
def release
(defined?(@release) && @release) || (defined?(::Release) && ::Release) || (defined?(::RELEASE) && ::RELEASE)
end
#
# Returns version string from program_name, version and release.
#
def ver
if v = version
str = +"#{program_name} #{[v].join('.')}"
str << " (#{v})" if v = release
str
end
end
def warn(mesg = $!)
super("#{program_name}: #{mesg}")
end
def abort(mesg = $!)
super("#{program_name}: #{mesg}")
end
#
# Subject of #on / #on_head, #accept / #reject
#
def top
@stack[-1]
end
#
# Subject of #on_tail.
#
def base
@stack[1]
end
#
# Pushes a new List.
#
def new
@stack.push(List.new)
if block_given?
yield self
else
self
end
end
#
# Removes the last List.
#
def remove
@stack.pop
end
#
# Puts option summary into +to+ and returns +to+. Yields each line if
# a block is given.
#
# +to+:: Output destination, which must have method <<. Defaults to [].
# +width+:: Width of left side, defaults to @summary_width.
# +max+:: Maximum length allowed for left side, defaults to +width+ - 1.
# +indent+:: Indentation, defaults to @summary_indent.
#
def summarize(to = [], width = @summary_width, max = width - 1, indent = @summary_indent, &blk)
nl = "\n"
blk ||= proc {|l| to << (l.index(nl, -1) ? l : l + nl)}
visit(:summarize, {}, {}, width, max, indent, &blk)
to
end
#
# Returns option summary string.
#
def help; summarize("#{banner}".sub(/\n?\z/, "\n")) end
alias to_s help
def pretty_print(q) # :nodoc:
q.object_group(self) do
first = true
if @stack.size > 2
@stack.each_with_index do |s, i|
next if i < 2
next if s.list.empty?
if first
first = false
q.text ":"
end
q.breakable
s.pretty_print(q)
end
end
end
end
def inspect # :nodoc:
require 'pp'
pretty_print_inspect
end
#
# Returns option summary list.
#
def to_a; summarize("#{banner}".split(/^/)) end
#
# Checks if an argument is given twice, in which case an ArgumentError is
# raised. Called from OptionParser#switch only.
#
# +obj+:: New argument.
# +prv+:: Previously specified argument.
# +msg+:: Exception message.
#
def notwice(obj, prv, msg) # :nodoc:
unless !prv or prv == obj
raise(ArgumentError, "argument #{msg} given twice: #{obj}",
ParseError.filter_backtrace(caller(2)))
end
obj
end
private :notwice
SPLAT_PROC = proc {|*a| a.length <= 1 ? a.first : a} # :nodoc:
# :call-seq:
# make_switch(params, block = nil)
#
# :include: ../doc/optparse/creates_option.rdoc
#
def make_switch(opts, block = nil)
short, long, nolong, style, pattern, conv, not_pattern, not_conv, not_style = [], [], []
ldesc, sdesc, desc, arg = [], [], []
default_style = Switch::NoArgument
default_pattern = nil
klass = nil
q, a = nil
has_arg = false
opts.each do |o|
# argument class
next if search(:atype, o) do |pat, c|
klass = notwice(o, klass, 'type')
if not_style and not_style != Switch::NoArgument
not_pattern, not_conv = pat, c
else
default_pattern, conv = pat, c
end
end
# directly specified pattern(any object possible to match)
if (!(String === o || Symbol === o)) and o.respond_to?(:match)
pattern = notwice(o, pattern, 'pattern')
if pattern.respond_to?(:convert)
conv = pattern.method(:convert).to_proc
else
conv = SPLAT_PROC
end
next
end
# anything others
case o
when Proc, Method
block = notwice(o, block, 'block')
when Array, Hash
case pattern
when CompletingHash
when nil
pattern = CompletingHash.new
conv = pattern.method(:convert).to_proc if pattern.respond_to?(:convert)
else
raise ArgumentError, "argument pattern given twice"
end
o.each {|pat, *v| pattern[pat] = v.fetch(0) {pat}}
when Module
raise ArgumentError, "unsupported argument type: #{o}", ParseError.filter_backtrace(caller(4))
when *ArgumentStyle.keys
style = notwice(ArgumentStyle[o], style, 'style')
when /^--no-([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
not_pattern, not_conv = search(:atype, o) unless not_style
not_style = (not_style || default_style).guess(arg = a) if a
default_style = Switch::NoArgument
default_pattern, conv = search(:atype, FalseClass) unless default_pattern
ldesc << "--no-#{q}"
(q = q.downcase).tr!('_', '-')
long << "no-#{q}"
nolong << q
when /^--\[no-\]([^\[\]=\s]*)(.+)?/
q, a = $1, $2
o = notwice(a ? Object : TrueClass, klass, 'type')
if a
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
ldesc << "--[no-]#{q}"
(o = q.downcase).tr!('_', '-')
long << o
not_pattern, not_conv = search(:atype, FalseClass) unless not_style
not_style = Switch::NoArgument
nolong << "no-#{o}"
when /^--([^\[\]=\s]*)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
ldesc << "--#{q}"
(o = q.downcase).tr!('_', '-')
long << o
when /^-(\[\^?\]?(?:[^\\\]]|\\.)*\])(.+)?/
q, a = $1, $2
o = notwice(Object, klass, 'type')
if a
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
else
has_arg = true
end
sdesc << "-#{q}"
short << Regexp.new(q)
when /^-(.)(.+)?/
q, a = $1, $2
if a
o = notwice(NilClass, klass, 'type')
default_style = default_style.guess(arg = a)
default_pattern, conv = search(:atype, o) unless default_pattern
end
sdesc << "-#{q}"
short << q
when /^=/
style = notwice(default_style.guess(arg = o), style, 'style')
default_pattern, conv = search(:atype, Object) unless default_pattern
else
desc.push(o) if o && !o.empty?
end
end
default_pattern, conv = search(:atype, default_style.pattern) unless default_pattern
if !(short.empty? and long.empty?)
if has_arg and default_style == Switch::NoArgument
default_style = Switch::RequiredArgument
end
s = (style || default_style).new(pattern || default_pattern,
conv, sdesc, ldesc, arg, desc, block)
elsif !block
if style or pattern
raise ArgumentError, "no switch given", ParseError.filter_backtrace(caller)
end
s = desc
else
short << pattern
s = (style || default_style).new(pattern,
conv, nil, nil, arg, desc, block)
end
return s, short, long,
(not_style.new(not_pattern, not_conv, sdesc, ldesc, nil, desc, block) if not_style),
nolong
end
# :call-seq:
# define(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
def define(*opts, &block)
top.append(*(sw = make_switch(opts, block)))
sw[0]
end
# :call-seq:
# on(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
def on(*opts, &block)
define(*opts, &block)
self
end
alias def_option define
# :call-seq:
# define_head(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
def define_head(*opts, &block)
top.prepend(*(sw = make_switch(opts, block)))
sw[0]
end
# :call-seq:
# on_head(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
# The new option is added at the head of the summary.
#
def on_head(*opts, &block)
define_head(*opts, &block)
self
end
alias def_head_option define_head
# :call-seq:
# define_tail(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
def define_tail(*opts, &block)
base.append(*(sw = make_switch(opts, block)))
sw[0]
end
#
# :call-seq:
# on_tail(*params, &block)
#
# :include: ../doc/optparse/creates_option.rdoc
#
# The new option is added at the tail of the summary.
#
def on_tail(*opts, &block)
define_tail(*opts, &block)
self
end
alias def_tail_option define_tail
#
# Add separator in summary.
#
def separator(string)
top.append(string, nil, nil)
end
#
# Parses command line arguments +argv+ in order. When a block is given,
# each non-option argument is yielded. When optional +into+ keyword
# argument is provided, the parsed option values are stored there via
# <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
# similar object).
#
# Returns the rest of +argv+ left unparsed.
#
def order(*argv, into: nil, &nonopt)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
order!(argv, into: into, &nonopt)
end
#
# Same as #order, but removes switches destructively.
# Non-option arguments remain in +argv+.
#
def order!(argv = default_argv, into: nil, &nonopt)
setter = ->(name, val) {into[name.to_sym] = val} if into
parse_in_order(argv, setter, &nonopt)
end
def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
opt, arg, val, rest = nil
nonopt ||= proc {|a| throw :terminate, a}
argv.unshift(arg) if arg = catch(:terminate) {
while arg = argv.shift
case arg
# long option
when /\A--([^=]*)(?:=(.*))?/m
opt, rest = $1, $2
opt.tr!('_', '-')
begin
sw, = complete(:long, opt, true)
if require_exact && !sw.long.include?(arg)
throw :terminate, arg unless raise_unknown
raise InvalidOption, arg
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, rest)
end
# short option
when /\A-(.)((=).*|.+)?/m
eq, rest, opt = $3, $2, $1
has_arg, val = eq, rest
begin
sw, = search(:short, opt)
unless sw
begin
sw, = complete(:short, opt)
# short option matched.
val = arg.delete_prefix('-')
has_arg = true
rescue InvalidOption
raise if require_exact
# if no short options match, try completion with long
# options.
sw, = complete(:long, opt)
eq ||= !rest
end
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
else
raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
end
begin
argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
end
# non-option argument
else
catch(:prune) do
visit(:each_option) do |sw0|
sw = sw0
sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
end
nonopt.call(arg)
end
end
end
nil
}
visit(:search, :short, nil) {|sw| sw.block.call(*argv) if !sw.pattern}
argv
end
private :parse_in_order
#
# Parses command line arguments +argv+ in permutation mode and returns
# list of non-option arguments. When optional +into+ keyword
# argument is provided, the parsed option values are stored there via
# <code>[]=</code> method (so it can be Hash, or OpenStruct, or other
# similar object).
#
def permute(*argv, into: nil)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
permute!(argv, into: into)
end
#
# Same as #permute, but removes switches destructively.
# Non-option arguments remain in +argv+.
#
def permute!(argv = default_argv, into: nil)
nonopts = []
order!(argv, into: into, &nonopts.method(:<<))
argv[0, 0] = nonopts
argv
end
#
# Parses command line arguments +argv+ in order when environment variable
# POSIXLY_CORRECT is set, and in permutation mode otherwise.
# When optional +into+ keyword argument is provided, the parsed option
# values are stored there via <code>[]=</code> method (so it can be Hash,
# or OpenStruct, or other similar object).
#
def parse(*argv, into: nil)
argv = argv[0].dup if argv.size == 1 and Array === argv[0]
parse!(argv, into: into)
end
#
# Same as #parse, but removes switches destructively.
# Non-option arguments remain in +argv+.
#
def parse!(argv = default_argv, into: nil)
if ENV.include?('POSIXLY_CORRECT')
order!(argv, into: into)
else
permute!(argv, into: into)
end
end
#
# Wrapper method for getopts.rb.
#
# params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option")
# # params["a"] = true # -a
# # params["b"] = "1" # -b1
# # params["foo"] = "1" # --foo
# # params["bar"] = "x" # --bar x
# # params["zot"] = "z" # --zot Z
#
def getopts(*args)
argv = Array === args.first ? args.shift : default_argv
single_options, *long_options = *args
result = {}
single_options.scan(/(.)(:)?/) do |opt, val|
if val
result[opt] = nil
define("-#{opt} VAL")
else
result[opt] = false
define("-#{opt}")
end
end if single_options
long_options.each do |arg|
arg, desc = arg.split(';', 2)
opt, val = arg.split(':', 2)
if val
result[opt] = val.empty? ? nil : val
define("--#{opt}=#{result[opt] || "VAL"}", *[desc].compact)
else
result[opt] = false
define("--#{opt}", *[desc].compact)
end
end
parse_in_order(argv, result.method(:[]=))
result
end
#
# See #getopts.
#
def self.getopts(*args)
new.getopts(*args)
end
#
# Traverses @stack, sending each element method +id+ with +args+ and
# +block+.
#
def visit(id, *args, &block) # :nodoc:
@stack.reverse_each do |el|
el.__send__(id, *args, &block)
end
nil
end
private :visit
#
# Searches +key+ in @stack for +id+ hash and returns or yields the result.
#
def search(id, key) # :nodoc:
block_given = block_given?
visit(:search, id, key) do |k|
return block_given ? yield(k) : k
end
end
private :search
#
# Completes shortened long style option switch and returns pair of
# canonical switch and switch descriptor OptionParser::Switch.
#
# +typ+:: Searching table.
# +opt+:: Searching key.
# +icase+:: Search case insensitive if true.
# +pat+:: Optional pattern for completion.
#
def complete(typ, opt, icase = false, *pat) # :nodoc:
if pat.empty?
search(typ, opt) {|sw| return [sw, opt]} # exact match or...
end
ambiguous = catch(:ambiguous) {
visit(:complete, typ, opt, icase, *pat) {|o, *sw| return sw}
}
exc = ambiguous ? AmbiguousOption : InvalidOption
raise exc.new(opt, additional: self.method(:additional_message).curry[typ])
end
private :complete
#
# Returns additional info.
#
def additional_message(typ, opt)
return unless typ and opt and defined?(DidYouMean::SpellChecker)
all_candidates = []
visit(:get_candidates, typ) do |candidates|
all_candidates.concat(candidates)
end
all_candidates.select! {|cand| cand.is_a?(String) }
checker = DidYouMean::SpellChecker.new(dictionary: all_candidates)
DidYouMean.formatter.message_for(all_candidates & checker.correct(opt))
end
def candidate(word)
list = []
case word
when '-'
long = short = true
when /\A--/
word, arg = word.split(/=/, 2)
argpat = Completion.regexp(arg, false) if arg and !arg.empty?
long = true
when /\A-/
short = true
end
pat = Completion.regexp(word, long)
visit(:each_option) do |opt|
next unless Switch === opt
opts = (long ? opt.long : []) + (short ? opt.short : [])
opts = Completion.candidate(word, true, pat, &opts.method(:each)).map(&:first) if pat
if /\A=/ =~ opt.arg
opts.map! {|sw| sw + "="}
if arg and CompletingHash === opt.pattern
if opts = opt.pattern.candidate(arg, false, argpat)
opts.map!(&:last)
end
end
end
list.concat(opts)
end
list
end
#
# Loads options from file names as +filename+. Does nothing when the file
# is not present. Returns whether successfully loaded.
#
# +filename+ defaults to basename of the program without suffix in a
# directory ~/.options, then the basename with '.options' suffix
# under XDG and Haiku standard places.
#
# The optional +into+ keyword argument works exactly like that accepted in
# method #parse.
#
def load(filename = nil, into: nil)
unless filename
basename = File.basename($0, '.*')
return true if load(File.expand_path(basename, '~/.options'), into: into) rescue nil
basename << ".options"
return [
# XDG
ENV['XDG_CONFIG_HOME'],
'~/.config',
*ENV['XDG_CONFIG_DIRS']&.split(File::PATH_SEPARATOR),
# Haiku
'~/config/settings',
].any? {|dir|
next if !dir or dir.empty?
load(File.expand_path(basename, dir), into: into) rescue nil
}
end
begin
parse(*File.readlines(filename, chomp: true), into: into)
true
rescue Errno::ENOENT, Errno::ENOTDIR
false
end
end
#
# Parses environment variable +env+ or its uppercase with splitting like a
# shell.
#
# +env+ defaults to the basename of the program.
#
def environment(env = File.basename($0, '.*'))
env = ENV[env] || ENV[env.upcase] or return
require 'shellwords'
parse(*Shellwords.shellwords(env))
end
#
# Acceptable argument classes
#
#
# Any string and no conversion. This is fall-back.
#
accept(Object) {|s,|s or s.nil?}
accept(NilClass) {|s,|s}
#
# Any non-empty string, and no conversion.
#
accept(String, /.+/m) {|s,*|s}
#
# Ruby/C-like integer, octal for 0-7 sequence, binary for 0b, hexadecimal
# for 0x, and decimal for others; with optional sign prefix. Converts to
# Integer.
#
decimal = '\d+(?:_\d+)*'
binary = 'b[01]+(?:_[01]+)*'
hex = 'x[\da-f]+(?:_[\da-f]+)*'
octal = "0(?:[0-7]+(?:_[0-7]+)*|#{binary}|#{hex})?"
integer = "#{octal}|#{decimal}"
accept(Integer, %r"\A[-+]?(?:#{integer})\z"io) {|s,|
begin
Integer(s)
rescue ArgumentError
raise OptionParser::InvalidArgument, s
end if s
}
#
# Float number format, and converts to Float.
#
float = "(?:#{decimal}(?=(.)?)(?:\\.(?:#{decimal})?)?|\\.#{decimal})(?:E[-+]?#{decimal})?"
floatpat = %r"\A[-+]?#{float}\z"io
accept(Float, floatpat) {|s,| s.to_f if s}
#
# Generic numeric format, converts to Integer for integer format, Float
# for float format, and Rational for rational format.
#
real = "[-+]?(?:#{octal}|#{float})"
accept(Numeric, /\A(#{real})(?:\/(#{real}))?\z/io) {|s, d, f, n,|
if n
Rational(d, n)
elsif f
Float(s)
else
Integer(s)
end
}
#
# Decimal integer format, to be converted to Integer.
#
DecimalInteger = /\A[-+]?#{decimal}\z/io
accept(DecimalInteger, DecimalInteger) {|s,|
begin
Integer(s, 10)
rescue ArgumentError
raise OptionParser::InvalidArgument, s
end if s
}
#
# Ruby/C like octal/hexadecimal/binary integer format, to be converted to
# Integer.
#
OctalInteger = /\A[-+]?(?:[0-7]+(?:_[0-7]+)*|0(?:#{binary}|#{hex}))\z/io
accept(OctalInteger, OctalInteger) {|s,|
begin
Integer(s, 8)
rescue ArgumentError
raise OptionParser::InvalidArgument, s
end if s
}
#
# Decimal integer/float number format, to be converted to Integer for
# integer format, Float for float format.
#
DecimalNumeric = floatpat # decimal integer is allowed as float also.
accept(DecimalNumeric, floatpat) {|s, f|
begin
if f
Float(s)
else
Integer(s)
end
rescue ArgumentError
raise OptionParser::InvalidArgument, s
end if s
}
#
# Boolean switch, which means whether it is present or not, whether it is
# absent or not with prefix no-, or it takes an argument
# yes/no/true/false/+/-.
#
yesno = CompletingHash.new
%w[- no false].each {|el| yesno[el] = false}
%w[+ yes true].each {|el| yesno[el] = true}
yesno['nil'] = false # should be nil?
accept(TrueClass, yesno) {|arg, val| val == nil or val}
#
# Similar to TrueClass, but defaults to false.
#
accept(FalseClass, yesno) {|arg, val| val != nil and val}
#
# List of strings separated by ",".
#
accept(Array) do |s, |
if s
s = s.split(',').collect {|ss| ss unless ss.empty?}
end
s
end
#
# Regular expression with options.
#
accept(Regexp, %r"\A/((?:\\.|[^\\])*)/([[:alpha:]]+)?\z|.*") do |all, s, o|
f = 0
if o
f |= Regexp::IGNORECASE if /i/ =~ o
f |= Regexp::MULTILINE if /m/ =~ o
f |= Regexp::EXTENDED if /x/ =~ o
case o = o.delete("imx")
when ""
when "u"
s = s.encode(Encoding::UTF_8)
when "e"
s = s.encode(Encoding::EUC_JP)
when "s"
s = s.encode(Encoding::SJIS)
when "n"
f |= Regexp::NOENCODING
else
raise OptionParser::InvalidArgument, "unknown regexp option - #{o}"
end
else
s ||= all
end
Regexp.new(s, f)
end
#
# Exceptions
#
#
# Base class of exceptions from OptionParser.
#
class ParseError < RuntimeError
# Reason which caused the error.
Reason = 'parse error'
def initialize(*args, additional: nil)
@additional = additional
@arg0, = args
@args = args
@reason = nil
end
attr_reader :args
attr_writer :reason
attr_accessor :additional
#
# Pushes back erred argument(s) to +argv+.
#
def recover(argv)
argv[0, 0] = @args
argv
end
def self.filter_backtrace(array)
unless $DEBUG
array.delete_if(&%r"\A#{Regexp.quote(__FILE__)}:"o.method(:=~))
end
array
end
def set_backtrace(array)
super(self.class.filter_backtrace(array))
end
def set_option(opt, eq)
if eq
@args[0] = opt
else
@args.unshift(opt)
end
self
end
#
# Returns error reason. Override this for I18N.
#
def reason
@reason || self.class::Reason
end
def inspect
"#<#{self.class}: #{args.join(' ')}>"
end
#
# Default stringizing method to emit standard error message.
#
def message
"#{reason}: #{args.join(' ')}#{additional[@arg0] if additional}"
end
alias to_s message
end
#
# Raises when ambiguously completable string is encountered.
#
class AmbiguousOption < ParseError
const_set(:Reason, 'ambiguous option')
end
#
# Raises when there is an argument for a switch which takes no argument.
#
class NeedlessArgument < ParseError
const_set(:Reason, 'needless argument')
end
#
# Raises when a switch with mandatory argument has no argument.
#
class MissingArgument < ParseError
const_set(:Reason, 'missing argument')
end
#
# Raises when switch is undefined.
#
class InvalidOption < ParseError
const_set(:Reason, 'invalid option')
end
#
# Raises when the given argument does not match required format.
#
class InvalidArgument < ParseError
const_set(:Reason, 'invalid argument')
end
#
# Raises when the given argument word can't be completed uniquely.
#
class AmbiguousArgument < InvalidArgument
const_set(:Reason, 'ambiguous argument')
end
#
# Miscellaneous
#
#
# Extends command line arguments array (ARGV) to parse itself.
#
module Arguable
#
# Sets OptionParser object, when +opt+ is +false+ or +nil+, methods
# OptionParser::Arguable#options and OptionParser::Arguable#options= are
# undefined. Thus, there is no ways to access the OptionParser object
# via the receiver object.
#
def options=(opt)
unless @optparse = opt
class << self
undef_method(:options)
undef_method(:options=)
end
end
end
#
# Actual OptionParser object, automatically created if nonexistent.
#
# If called with a block, yields the OptionParser object and returns the
# result of the block. If an OptionParser::ParseError exception occurs
# in the block, it is rescued, a error message printed to STDERR and
# +nil+ returned.
#
def options
@optparse ||= OptionParser.new
@optparse.default_argv = self
block_given? or return @optparse
begin
yield @optparse
rescue ParseError
@optparse.warn $!
nil
end
end
#
# Parses +self+ destructively in order and returns +self+ containing the
# rest arguments left unparsed.
#
def order!(&blk) options.order!(self, &blk) end
#
# Parses +self+ destructively in permutation mode and returns +self+
# containing the rest arguments left unparsed.
#
def permute!() options.permute!(self) end
#
# Parses +self+ destructively and returns +self+ containing the
# rest arguments left unparsed.
#
def parse!() options.parse!(self) end
#
# Substitution of getopts is possible as follows. Also see
# OptionParser#getopts.
#
# def getopts(*args)
# ($OPT = ARGV.getopts(*args)).each do |opt, val|
# eval "$OPT_#{opt.gsub(/[^A-Za-z0-9_]/, '_')} = val"
# end
# rescue OptionParser::ParseError
# end
#
def getopts(*args)
options.getopts(self, *args)
end
#
# Initializes instance variable.
#
def self.extend_object(obj)
super
obj.instance_eval {@optparse = nil}
end
def initialize(*args)
super
@optparse = nil
end
end
#
# Acceptable argument classes. Now contains DecimalInteger, OctalInteger
# and DecimalNumeric. See Acceptable argument classes (in source code).
#
module Acceptables
const_set(:DecimalInteger, OptionParser::DecimalInteger)
const_set(:OctalInteger, OptionParser::OctalInteger)
const_set(:DecimalNumeric, OptionParser::DecimalNumeric)
end
end
# ARGV is arguable by OptionParser
ARGV.extend(OptionParser::Arguable)
# An alias for OptionParser.
OptParse = OptionParser # :nodoc:
share/ruby/optparse/time.rb 0000644 00000000347 15173547334 0011767 0 ustar 00 # frozen_string_literal: false
require_relative '../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
share/ruby/optparse/kwargs.rb 0000644 00000001042 15173547334 0012320 0 ustar 00 # frozen_string_literal: true
require_relative '../optparse'
class OptionParser
# :call-seq:
# define_by_keywords(options, method, **params)
#
# :include: ../../doc/optparse/creates_option.rdoc
#
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
share/ruby/optparse/version.rb 0000644 00000004015 15173547334 0012512 0 ustar 00 # 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
share/ruby/optparse/shellwords.rb 0000644 00000000244 15173547334 0013213 0 ustar 00 # frozen_string_literal: false
# -*- ruby -*-
require 'shellwords'
require_relative '../optparse'
OptionParser.accept(Shellwords) {|s,| Shellwords.shellwords(s)}
share/ruby/optparse/uri.rb 0000644 00000000217 15173547334 0011624 0 ustar 00 # frozen_string_literal: false
# -*- ruby -*-
require_relative '../optparse'
require 'uri'
OptionParser.accept(URI) {|s,| URI.parse(s) if s}
share/ruby/optparse/ac.rb 0000644 00000003032 15173547335 0011407 0 ustar 00 # frozen_string_literal: false
require_relative '../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
ARG_CONV = proc {|val| val.nil? ? true : val}
def _ac_arg_enable(prefix, name, help_string, block)
_check_ac_args(name, block)
sdesc = []
ldesc = ["--#{prefix}-#{name}"]
desc = [help_string]
q = name.downcase
ac_block = proc {|val| block.call(ARG_CONV.call(val))}
enable = Switch::PlacedArgument.new(nil, ARG_CONV, sdesc, ldesc, nil, desc, ac_block)
disable = Switch::NoArgument.new(nil, proc {false}, sdesc, ldesc, nil, desc, ac_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
share/ruby/optparse/date.rb 0000644 00000000560 15173547335 0011744 0 ustar 00 # frozen_string_literal: false
require_relative '../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
share/ruby/shellwords.rb 0000644 00000016133 15173547335 0011363 0 ustar 00 # 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"]
#
# They treat quotes as special characters, so an unmatched quote will
# cause an ArgumentError.
#
# argv = "they all ran after the farmer's wife".shellsplit
# #=> ArgumentError: Unmatched quote: ...
#
# Shellwords also provides methods that do the opposite.
# Shellwords.escape, or its alias, String#shellescape, escapes
# shell metacharacters in a string for use in a command line.
#
# filename = "special's.txt"
#
# system("cat -- #{filename.shellescape}")
# # runs "cat -- special\\'s.txt"
#
# Note the '--'. Without it, cat(1) will treat the following argument
# as a command line option if it starts with '-'. It is guaranteed
# that Shellwords.escape converts a string to a form that a Bourne
# shell will parse back to the original string, but it is the
# programmer's responsibility to make sure that passing an arbitrary
# argument to a command does no harm.
#
# Shellwords also comes with a core extension for Array, Array#shelljoin.
#
# dir = "Funny GIFs"
# argv = %W[ls -lta -- #{dir}]
# system(argv.shelljoin + " | less")
# # runs "ls -lta -- Funny\\ GIFs | less"
#
# You can use this method to build a complete command line out of an
# array of arguments.
#
# === 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 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 -e #{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]/, "\\\\\\&")
# 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
share/ruby/forwardable.rb 0000644 00000022035 15173547335 0011463 0 ustar 00 # frozen_string_literal: false
#
# forwardable.rb -
# $Release Version: 1.1$
# $Revision$
# by Keiju ISHITSUKA(keiju@ishitsuka.com)
# original definition by delegator.rb
# Revised by Daniel J. Berger with suggestions from Florian Gross.
#
# Documentation by James Edward Gray II and Gavin Sinclair
# The Forwardable module provides delegation of specified
# methods to a designated object, using the methods #def_delegator
# and #def_delegators.
#
# For example, say you have a class RecordCollection which
# contains an array <tt>@records</tt>. You could provide the lookup method
# #record_number(), which simply calls #[] on the <tt>@records</tt>
# array, like this:
#
# require 'forwardable'
#
# class RecordCollection
# attr_accessor :records
# extend Forwardable
# def_delegator :@records, :[], :record_number
# end
#
# We can use the lookup method like so:
#
# r = RecordCollection.new
# r.records = [4,5,6]
# r.record_number(0) # => 4
#
# Further, if you wish to provide the methods #size, #<<, and #map,
# all of which delegate to @records, this is how you can do it:
#
# class RecordCollection # re-open RecordCollection class
# def_delegators :@records, :size, :<<, :map
# end
#
# r = RecordCollection.new
# r.records = [1,2,3]
# r.record_number(0) # => 1
# r.size # => 3
# r << 4 # => [1, 2, 3, 4]
# r.map { |x| x * 2 } # => [2, 4, 6, 8]
#
# You can even extend regular objects with Forwardable.
#
# my_hash = Hash.new
# my_hash.extend Forwardable # prepare object for delegation
# my_hash.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts()
# my_hash.puts "Howdy!"
#
# == Another example
#
# You could use Forwardable as an alternative to inheritance, when you don't want
# to inherit all methods from the superclass. For instance, here is how you might
# add a range of +Array+ instance methods to a new class +Queue+:
#
# class Queue
# extend Forwardable
#
# def initialize
# @q = [ ] # prepare delegate object
# end
#
# # setup preferred interface, enq() and deq()...
# def_delegator :@q, :push, :enq
# def_delegator :@q, :shift, :deq
#
# # support some general Array methods that fit Queues well
# def_delegators :@q, :clear, :first, :push, :shift, :size
# end
#
# q = Thread::Queue.new
# q.enq 1, 2, 3, 4, 5
# q.push 6
#
# q.shift # => 1
# while q.size > 0
# puts q.deq
# end
#
# q.enq "Ruby", "Perl", "Python"
# puts q.first
# q.clear
# puts q.first
#
# This should output:
#
# 2
# 3
# 4
# 5
# 6
# Ruby
# nil
#
# == Notes
#
# Be advised, RDoc will not detect delegated methods.
#
# +forwardable.rb+ provides single-method delegation via the def_delegator and
# def_delegators methods. For full-class delegation via DelegateClass, see
# +delegate.rb+.
#
module Forwardable
require 'forwardable/impl'
# Version of +forwardable.rb+
VERSION = "1.3.3"
VERSION.freeze
FORWARDABLE_VERSION = VERSION
FORWARDABLE_VERSION.freeze
@debug = nil
class << self
# ignored
attr_accessor :debug
end
# Takes a hash as its argument. The key is a symbol or an array of
# symbols. These symbols correspond to method names, instance variable
# names, or constant names (see def_delegator). The value is
# the accessor to which the methods will be delegated.
#
# :call-seq:
# delegate method => accessor
# delegate [method, method, ...] => accessor
#
def instance_delegate(hash)
hash.each do |methods, accessor|
unless defined?(methods.each)
def_instance_delegator(accessor, methods)
else
methods.each {|method| def_instance_delegator(accessor, method)}
end
end
end
#
# Shortcut for defining multiple delegator methods, but with no
# provision for using a different name. The following two code
# samples have the same effect:
#
# def_delegators :@records, :size, :<<, :map
#
# def_delegator :@records, :size
# def_delegator :@records, :<<
# def_delegator :@records, :map
#
def def_instance_delegators(accessor, *methods)
methods.each do |method|
next if /\A__(?:send|id)__\z/ =~ method
def_instance_delegator(accessor, method)
end
end
# Define +method+ as delegator instance method with an optional
# alias name +ali+. Method calls to +ali+ will be delegated to
# +accessor.method+. +accessor+ should be a method name, instance
# variable name, or constant name. Use the full path to the
# constant if providing the constant name.
# Returns the name of the method defined.
#
# class MyQueue
# CONST = 1
# extend Forwardable
# attr_reader :queue
# def initialize
# @queue = []
# end
#
# def_delegator :@queue, :push, :mypush
# def_delegator 'MyQueue::CONST', :to_i
# end
#
# q = MyQueue.new
# q.mypush 42
# q.queue #=> [42]
# q.push 23 #=> NoMethodError
# q.to_i #=> 1
#
def def_instance_delegator(accessor, method, ali = method)
gen = Forwardable._delegator_method(self, accessor, method, ali)
# If it's not a class or module, it's an instance
mod = Module === self ? self : singleton_class
ret = mod.module_eval(&gen)
mod.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
ret
end
alias delegate instance_delegate
alias def_delegators def_instance_delegators
alias def_delegator def_instance_delegator
# :nodoc:
def self._delegator_method(obj, accessor, method, ali)
accessor = accessor.to_s unless Symbol === accessor
if Module === obj ?
obj.method_defined?(accessor) || obj.private_method_defined?(accessor) :
obj.respond_to?(accessor, true)
accessor = "#{accessor}()"
end
method_call = ".__send__(:#{method}, *args, &block)"
if _valid_method?(method)
loc, = caller_locations(2,1)
pre = "_ ="
mesg = "#{Module === obj ? obj : obj.class}\##{ali} at #{loc.path}:#{loc.lineno} forwarding to private method "
method_call = "#{<<-"begin;"}\n#{<<-"end;".chomp}"
begin;
unless defined? _.#{method}
::Kernel.warn #{mesg.dump}"\#{_.class}"'##{method}', uplevel: 1
_#{method_call}
else
_.#{method}(*args, &block)
end
end;
end
_compile_method("#{<<-"begin;"}\n#{<<-"end;"}", __FILE__, __LINE__+1)
begin;
proc do
def #{ali}(*args, &block)
#{pre}
begin
#{accessor}
end#{method_call}
end
end
end;
end
end
# SingleForwardable can be used to setup delegation at the object level as well.
#
# printer = String.new
# printer.extend SingleForwardable # prepare object for delegation
# printer.def_delegator "STDOUT", "puts" # add delegation for STDOUT.puts()
# printer.puts "Howdy!"
#
# Also, SingleForwardable can be used to set up delegation for a Class or Module.
#
# class Implementation
# def self.service
# puts "serviced!"
# end
# end
#
# module Facade
# extend SingleForwardable
# def_delegator :Implementation, :service
# end
#
# Facade.service #=> serviced!
#
# If you want to use both Forwardable and SingleForwardable, you can
# use methods def_instance_delegator and def_single_delegator, etc.
module SingleForwardable
# Takes a hash as its argument. The key is a symbol or an array of
# symbols. These symbols correspond to method names. The value is
# the accessor to which the methods will be delegated.
#
# :call-seq:
# delegate method => accessor
# delegate [method, method, ...] => accessor
#
def single_delegate(hash)
hash.each do |methods, accessor|
unless defined?(methods.each)
def_single_delegator(accessor, methods)
else
methods.each {|method| def_single_delegator(accessor, method)}
end
end
end
#
# Shortcut for defining multiple delegator methods, but with no
# provision for using a different name. The following two code
# samples have the same effect:
#
# def_delegators :@records, :size, :<<, :map
#
# def_delegator :@records, :size
# def_delegator :@records, :<<
# def_delegator :@records, :map
#
def def_single_delegators(accessor, *methods)
methods.each do |method|
next if /\A__(?:send|id)__\z/ =~ method
def_single_delegator(accessor, method)
end
end
# :call-seq:
# def_single_delegator(accessor, method, new_name=method)
#
# Defines a method _method_ which delegates to _accessor_ (i.e. it calls
# the method of the same name in _accessor_). If _new_name_ is
# provided, it is used as the name for the delegate method.
# Returns the name of the method defined.
def def_single_delegator(accessor, method, ali = method)
gen = Forwardable._delegator_method(self, accessor, method, ali)
ret = instance_eval(&gen)
singleton_class.__send__(:ruby2_keywords, ali) if RUBY_VERSION >= '2.7'
ret
end
alias delegate single_delegate
alias def_delegators def_single_delegators
alias def_delegator def_single_delegator
end
share/ruby/syntax_suggest.rb 0000644 00000000112 15173547335 0012252 0 ustar 00 # frozen_string_literal: true
require_relative "syntax_suggest/core_ext"
share/ruby/singleton.rb 0000644 00000010122 15173547335 0011167 0 ustar 00 # frozen_string_literal: false
# The Singleton module implements the Singleton pattern.
#
# == Usage
#
# To use Singleton, include the module in your class.
#
# class Klass
# include Singleton
# # ...
# end
#
# This ensures that only one instance of Klass can be created.
#
# a,b = Klass.instance, Klass.instance
#
# a == b
# # => true
#
# Klass.new
# # => NoMethodError - new is private ...
#
# The instance is created at upon the first call of Klass.instance().
#
# class OtherKlass
# include Singleton
# # ...
# end
#
# ObjectSpace.each_object(OtherKlass){}
# # => 0
#
# OtherKlass.instance
# ObjectSpace.each_object(OtherKlass){}
# # => 1
#
#
# This behavior is preserved under inheritance and cloning.
#
# == Implementation
#
# This above is achieved by:
#
# * Making Klass.new and Klass.allocate private.
#
# * Overriding Klass.inherited(sub_klass) and Klass.clone() to ensure that the
# Singleton properties are kept when inherited and cloned.
#
# * Providing the Klass.instance() method that returns the same object each
# time it is called.
#
# * Overriding Klass._load(str) to call Klass.instance().
#
# * Overriding Klass#clone and Klass#dup to raise TypeErrors to prevent
# cloning or duping.
#
# == Singleton and Marshal
#
# By default Singleton's #_dump(depth) returns the empty string. Marshalling by
# default will strip state information, e.g. instance variables from the instance.
# Classes using Singleton can provide custom _load(str) and _dump(depth) methods
# to retain some of the previous state of the instance.
#
# require 'singleton'
#
# class Example
# include Singleton
# attr_accessor :keep, :strip
# def _dump(depth)
# # this strips the @strip information from the instance
# Marshal.dump(@keep, depth)
# end
#
# def self._load(str)
# instance.keep = Marshal.load(str)
# instance
# end
# end
#
# a = Example.instance
# a.keep = "keep this"
# a.strip = "get rid of this"
#
# stored_state = Marshal.dump(a)
#
# a.keep = nil
# a.strip = nil
# b = Marshal.load(stored_state)
# p a == b # => true
# p a.keep # => "keep this"
# p a.strip # => nil
#
module Singleton
VERSION = "0.1.1"
# Raises a TypeError to prevent cloning.
def clone
raise TypeError, "can't clone instance of singleton #{self.class}"
end
# Raises a TypeError to prevent duping.
def dup
raise TypeError, "can't dup instance of singleton #{self.class}"
end
# By default, do not retain any state when marshalling.
def _dump(depth = -1)
''
end
module SingletonClassMethods # :nodoc:
def clone # :nodoc:
Singleton.__init__(super)
end
# By default calls instance(). Override to retain singleton state.
def _load(str)
instance
end
def instance # :nodoc:
return @singleton__instance__ if @singleton__instance__
@singleton__mutex__.synchronize {
return @singleton__instance__ if @singleton__instance__
@singleton__instance__ = new()
}
@singleton__instance__
end
private
def inherited(sub_klass)
super
Singleton.__init__(sub_klass)
end
end
class << Singleton # :nodoc:
def __init__(klass) # :nodoc:
klass.instance_eval {
@singleton__instance__ = nil
@singleton__mutex__ = Thread::Mutex.new
}
klass
end
private
# extending an object with Singleton is a bad idea
undef_method :extend_object
def append_features(mod)
# help out people counting on transitive mixins
unless mod.instance_of?(Class)
raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
end
super
end
def included(klass)
super
klass.private_class_method :new, :allocate
klass.extend SingletonClassMethods
Singleton.__init__(klass)
end
end
##
# :singleton-method: _load
# By default calls instance(). Override to retain singleton state.
##
# :singleton-method: instance
# Returns the singleton instance.
end
share/gems/gems/psych-5.0.1/lib/psych.rb 0000644 00000061170 15173547335 0013564 0 ustar 00 # frozen_string_literal: true
require_relative 'psych/versions'
case RUBY_ENGINE
when 'jruby'
require_relative 'psych_jars'
if JRuby::Util.respond_to?(:load_ext)
JRuby::Util.load_ext('org.jruby.ext.psych.PsychLibrary')
else
require 'java'; require 'jruby'
org.jruby.ext.psych.PsychLibrary.new.load(JRuby.runtime, false)
end
else
require 'psych.so'
end
require_relative 'psych/nodes'
require_relative 'psych/streaming'
require_relative 'psych/visitors'
require_relative 'psych/handler'
require_relative 'psych/tree_builder'
require_relative 'psych/parser'
require_relative 'psych/omap'
require_relative 'psych/set'
require_relative 'psych/coder'
require_relative 'psych/core_ext'
require_relative 'psych/stream'
require_relative 'psych/json/tree_builder'
require_relative 'psych/json/stream'
require_relative 'psych/handlers/document_stream'
require_relative 'psych/class_loader'
###
# = Overview
#
# Psych is a YAML parser and emitter.
# Psych leverages libyaml [Home page: https://pyyaml.org/wiki/LibYAML]
# or [git repo: https://github.com/yaml/libyaml] for its YAML parsing
# and emitting capabilities. In addition to wrapping libyaml, Psych also
# knows how to serialize and de-serialize most Ruby objects to and from
# the YAML format.
#
# = I NEED TO PARSE OR EMIT YAML RIGHT NOW!
#
# # Parse some YAML
# Psych.load("--- foo") # => "foo"
#
# # Emit some YAML
# Psych.dump("foo") # => "--- foo\n...\n"
# { :a => 'b'}.to_yaml # => "---\n:a: b\n"
#
# Got more time on your hands? Keep on reading!
#
# == YAML Parsing
#
# Psych provides a range of interfaces for parsing a YAML document ranging from
# low level to high level, depending on your parsing needs. At the lowest
# level, is an event based parser. Mid level is access to the raw YAML AST,
# and at the highest level is the ability to unmarshal YAML to Ruby objects.
#
# == YAML Emitting
#
# Psych provides a range of interfaces ranging from low to high level for
# producing YAML documents. Very similar to the YAML parsing interfaces, Psych
# provides at the lowest level, an event based system, mid-level is building
# a YAML AST, and the highest level is converting a Ruby object straight to
# a YAML document.
#
# == High-level API
#
# === Parsing
#
# The high level YAML parser provided by Psych simply takes YAML as input and
# returns a Ruby data structure. For information on using the high level parser
# see Psych.load
#
# ==== Reading from a string
#
# Psych.safe_load("--- a") # => 'a'
# Psych.safe_load("---\n - a\n - b") # => ['a', 'b']
# # From a trusted string:
# Psych.load("--- !ruby/range\nbegin: 0\nend: 42\nexcl: false\n") # => 0..42
#
# ==== Reading from a file
#
# Psych.safe_load_file("data.yml", permitted_classes: [Date])
# Psych.load_file("trusted_database.yml")
#
# ==== Exception handling
#
# begin
# # The second argument changes only the exception contents
# Psych.parse("--- `", "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# === Emitting
#
# The high level emitter has the easiest interface. Psych simply takes a Ruby
# data structure and converts it to a YAML document. See Psych.dump for more
# information on dumping a Ruby data structure.
#
# ==== Writing to a string
#
# # Dump an array, get back a YAML string
# Psych.dump(['a', 'b']) # => "---\n- a\n- b\n"
#
# # Dump an array to an IO object
# Psych.dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890>
#
# # Dump an array with indentation set
# Psych.dump(['a', ['b']], :indentation => 3) # => "---\n- a\n- - b\n"
#
# # Dump an array to an IO with indentation set
# Psych.dump(['a', ['b']], StringIO.new, :indentation => 3)
#
# ==== Writing to a file
#
# Currently there is no direct API for dumping Ruby structure to file:
#
# File.open('database.yml', 'w') do |file|
# file.write(Psych.dump(['a', 'b']))
# end
#
# == Mid-level API
#
# === Parsing
#
# Psych provides access to an AST produced from parsing a YAML document. This
# tree is built using the Psych::Parser and Psych::TreeBuilder. The AST can
# be examined and manipulated freely. Please see Psych::parse_stream,
# Psych::Nodes, and Psych::Nodes::Node for more information on dealing with
# YAML syntax trees.
#
# ==== Reading from a string
#
# # Returns Psych::Nodes::Stream
# Psych.parse_stream("---\n - a\n - b")
#
# # Returns Psych::Nodes::Document
# Psych.parse("---\n - a\n - b")
#
# ==== Reading from a file
#
# # Returns Psych::Nodes::Stream
# Psych.parse_stream(File.read('database.yml'))
#
# # Returns Psych::Nodes::Document
# Psych.parse_file('database.yml')
#
# ==== Exception handling
#
# begin
# # The second argument changes only the exception contents
# Psych.parse("--- `", "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# === Emitting
#
# At the mid level is building an AST. This AST is exactly the same as the AST
# used when parsing a YAML document. Users can build an AST by hand and the
# AST knows how to emit itself as a YAML document. See Psych::Nodes,
# Psych::Nodes::Node, and Psych::TreeBuilder for more information on building
# a YAML AST.
#
# ==== Writing to a string
#
# # We need Psych::Nodes::Stream (not Psych::Nodes::Document)
# stream = Psych.parse_stream("---\n - a\n - b")
#
# stream.to_yaml # => "---\n- a\n- b\n"
#
# ==== Writing to a file
#
# # We need Psych::Nodes::Stream (not Psych::Nodes::Document)
# stream = Psych.parse_stream(File.read('database.yml'))
#
# File.open('database.yml', 'w') do |file|
# file.write(stream.to_yaml)
# end
#
# == Low-level API
#
# === Parsing
#
# The lowest level parser should be used when the YAML input is already known,
# and the developer does not want to pay the price of building an AST or
# automatic detection and conversion to Ruby objects. See Psych::Parser for
# more information on using the event based parser.
#
# ==== Reading to Psych::Nodes::Stream structure
#
# parser = Psych::Parser.new(TreeBuilder.new) # => #<Psych::Parser>
# parser = Psych.parser # it's an alias for the above
#
# parser.parse("---\n - a\n - b") # => #<Psych::Parser>
# parser.handler # => #<Psych::TreeBuilder>
# parser.handler.root # => #<Psych::Nodes::Stream>
#
# ==== Receiving an events stream
#
# recorder = Psych::Handlers::Recorder.new
# parser = Psych::Parser.new(recorder)
#
# parser.parse("---\n - a\n - b")
# recorder.events # => [list of [event, args] lists]
# # event is one of: Psych::Handler::EVENTS
# # args are the arguments passed to the event
#
# === Emitting
#
# The lowest level emitter is an event based system. Events are sent to a
# Psych::Emitter object. That object knows how to convert the events to a YAML
# document. This interface should be used when document format is known in
# advance or speed is a concern. See Psych::Emitter for more information.
#
# ==== Writing to a Ruby structure
#
# Psych.parser.parse("--- a") # => #<Psych::Parser>
#
# parser.handler.first # => #<Psych::Nodes::Stream>
# parser.handler.first.to_ruby # => ["a"]
#
# parser.handler.root.first # => #<Psych::Nodes::Document>
# parser.handler.root.first.to_ruby # => "a"
#
# # You can instantiate an Emitter manually
# Psych::Visitors::ToRuby.new.accept(parser.handler.root.first)
# # => "a"
module Psych
# The version of libyaml Psych is using
LIBYAML_VERSION = Psych.libyaml_version.join('.').freeze
###
# Load +yaml+ in to a Ruby data structure. If multiple documents are
# provided, the object contained in the first document will be returned.
# +filename+ will be used in the exception message if any exception
# is raised while parsing. If +yaml+ is empty, it returns
# the specified +fallback+ return value, which defaults to +false+.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
#
# Example:
#
# Psych.unsafe_load("--- a") # => 'a'
# Psych.unsafe_load("---\n - a\n - b") # => ['a', 'b']
#
# begin
# Psych.unsafe_load("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# When the optional +symbolize_names+ keyword argument is set to a
# true value, returns symbols for keys in Hash objects (default: strings).
#
# Psych.unsafe_load("---\n foo: bar") # => {"foo"=>"bar"}
# Psych.unsafe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
#
# Raises a TypeError when `yaml` parameter is NilClass
#
# NOTE: This method *should not* be used to parse untrusted documents, such as
# YAML documents that are supplied via user input. Instead, please use the
# load method or the safe_load method.
#
def self.unsafe_load yaml, filename: nil, fallback: false, symbolize_names: false, freeze: false, strict_integer: false
result = parse(yaml, filename: filename)
return fallback unless result
result.to_ruby(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer)
end
###
# Safely load the yaml string in +yaml+. By default, only the following
# classes are allowed to be deserialized:
#
# * TrueClass
# * FalseClass
# * NilClass
# * Integer
# * Float
# * String
# * Array
# * Hash
#
# Recursive data structures are not allowed by default. Arbitrary classes
# can be allowed by adding those classes to the +permitted_classes+ keyword argument. They are
# additive. For example, to allow Date deserialization:
#
# Psych.safe_load(yaml, permitted_classes: [Date])
#
# Now the Date class can be loaded in addition to the classes listed above.
#
# Aliases can be explicitly allowed by changing the +aliases+ keyword argument.
# For example:
#
# x = []
# x << x
# yaml = Psych.dump x
# Psych.safe_load yaml # => raises an exception
# Psych.safe_load yaml, aliases: true # => loads the aliases
#
# A Psych::DisallowedClass exception will be raised if the yaml contains a
# class that isn't in the +permitted_classes+ list.
#
# A Psych::AliasesNotEnabled exception will be raised if the yaml contains aliases
# but the +aliases+ keyword argument is set to false.
#
# +filename+ will be used in the exception message if any exception is raised
# while parsing.
#
# When the optional +symbolize_names+ keyword argument is set to a
# true value, returns symbols for keys in Hash objects (default: strings).
#
# Psych.safe_load("---\n foo: bar") # => {"foo"=>"bar"}
# Psych.safe_load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
#
def self.safe_load yaml, permitted_classes: [], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false
result = parse(yaml, filename: filename)
return fallback unless result
class_loader = ClassLoader::Restricted.new(permitted_classes.map(&:to_s),
permitted_symbols.map(&:to_s))
scanner = ScalarScanner.new class_loader, strict_integer: strict_integer
visitor = if aliases
Visitors::ToRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
else
Visitors::NoAliasRuby.new scanner, class_loader, symbolize_names: symbolize_names, freeze: freeze
end
result = visitor.accept result
result
end
###
# Load +yaml+ in to a Ruby data structure. If multiple documents are
# provided, the object contained in the first document will be returned.
# +filename+ will be used in the exception message if any exception
# is raised while parsing. If +yaml+ is empty, it returns
# the specified +fallback+ return value, which defaults to +false+.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
#
# Example:
#
# Psych.load("--- a") # => 'a'
# Psych.load("---\n - a\n - b") # => ['a', 'b']
#
# begin
# Psych.load("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# When the optional +symbolize_names+ keyword argument is set to a
# true value, returns symbols for keys in Hash objects (default: strings).
#
# Psych.load("---\n foo: bar") # => {"foo"=>"bar"}
# Psych.load("---\n foo: bar", symbolize_names: true) # => {:foo=>"bar"}
#
# Raises a TypeError when `yaml` parameter is NilClass. This method is
# similar to `safe_load` except that `Symbol` objects are allowed by default.
#
def self.load yaml, permitted_classes: [Symbol], permitted_symbols: [], aliases: false, filename: nil, fallback: nil, symbolize_names: false, freeze: false, strict_integer: false
safe_load yaml, permitted_classes: permitted_classes,
permitted_symbols: permitted_symbols,
aliases: aliases,
filename: filename,
fallback: fallback,
symbolize_names: symbolize_names,
freeze: freeze,
strict_integer: strict_integer
end
###
# Parse a YAML string in +yaml+. Returns the Psych::Nodes::Document.
# +filename+ is used in the exception message if a Psych::SyntaxError is
# raised.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
#
# Example:
#
# Psych.parse("---\n - a\n - b") # => #<Psych::Nodes::Document:0x00>
#
# begin
# Psych.parse("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# See Psych::Nodes for more information about YAML AST.
def self.parse yaml, filename: nil
parse_stream(yaml, filename: filename) do |node|
return node
end
false
end
###
# Parse a file at +filename+. Returns the Psych::Nodes::Document.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
def self.parse_file filename, fallback: false
result = File.open filename, 'r:bom|utf-8' do |f|
parse f, filename: filename
end
result || fallback
end
###
# Returns a default parser
def self.parser
Psych::Parser.new(TreeBuilder.new)
end
###
# Parse a YAML string in +yaml+. Returns the Psych::Nodes::Stream.
# This method can handle multiple YAML documents contained in +yaml+.
# +filename+ is used in the exception message if a Psych::SyntaxError is
# raised.
#
# If a block is given, a Psych::Nodes::Document node will be yielded to the
# block as it's being parsed.
#
# Raises a Psych::SyntaxError when a YAML syntax error is detected.
#
# Example:
#
# Psych.parse_stream("---\n - a\n - b") # => #<Psych::Nodes::Stream:0x00>
#
# Psych.parse_stream("--- a\n--- b") do |node|
# node # => #<Psych::Nodes::Document:0x00>
# end
#
# begin
# Psych.parse_stream("--- `", filename: "file.txt")
# rescue Psych::SyntaxError => ex
# ex.file # => 'file.txt'
# ex.message # => "(file.txt): found character that cannot start any token"
# end
#
# Raises a TypeError when NilClass is passed.
#
# See Psych::Nodes for more information about YAML AST.
def self.parse_stream yaml, filename: nil, &block
if block_given?
parser = Psych::Parser.new(Handlers::DocumentStream.new(&block))
parser.parse yaml, filename
else
parser = self.parser
parser.parse yaml, filename
parser.handler.root
end
end
###
# call-seq:
# Psych.dump(o) -> string of yaml
# Psych.dump(o, options) -> string of yaml
# Psych.dump(o, io) -> io object passed in
# Psych.dump(o, io, options) -> io object passed in
#
# Dump Ruby object +o+ to a YAML string. Optional +options+ may be passed in
# to control the output format. If an IO object is passed in, the YAML will
# be dumped to that IO object.
#
# Currently supported options are:
#
# [<tt>:indentation</tt>] Number of space characters used to indent.
# Acceptable value should be in <tt>0..9</tt> range,
# otherwise option is ignored.
#
# Default: <tt>2</tt>.
# [<tt>:line_width</tt>] Max character to wrap line at.
#
# Default: <tt>0</tt> (meaning "wrap at 81").
# [<tt>:canonical</tt>] Write "canonical" YAML form (very verbose, yet
# strictly formal).
#
# Default: <tt>false</tt>.
# [<tt>:header</tt>] Write <tt>%YAML [version]</tt> at the beginning of document.
#
# Default: <tt>false</tt>.
#
# Example:
#
# # Dump an array, get back a YAML string
# Psych.dump(['a', 'b']) # => "---\n- a\n- b\n"
#
# # Dump an array to an IO object
# Psych.dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890>
#
# # Dump an array with indentation set
# Psych.dump(['a', ['b']], indentation: 3) # => "---\n- a\n- - b\n"
#
# # Dump an array to an IO with indentation set
# Psych.dump(['a', ['b']], StringIO.new, indentation: 3)
def self.dump o, io = nil, options = {}
if Hash === io
options = io
io = nil
end
visitor = Psych::Visitors::YAMLTree.create options
visitor << o
visitor.tree.yaml io, options
end
###
# call-seq:
# Psych.safe_dump(o) -> string of yaml
# Psych.safe_dump(o, options) -> string of yaml
# Psych.safe_dump(o, io) -> io object passed in
# Psych.safe_dump(o, io, options) -> io object passed in
#
# Safely dump Ruby object +o+ to a YAML string. Optional +options+ may be passed in
# to control the output format. If an IO object is passed in, the YAML will
# be dumped to that IO object. By default, only the following
# classes are allowed to be serialized:
#
# * TrueClass
# * FalseClass
# * NilClass
# * Integer
# * Float
# * String
# * Array
# * Hash
#
# Arbitrary classes can be allowed by adding those classes to the +permitted_classes+
# keyword argument. They are additive. For example, to allow Date serialization:
#
# Psych.safe_dump(yaml, permitted_classes: [Date])
#
# Now the Date class can be dumped in addition to the classes listed above.
#
# A Psych::DisallowedClass exception will be raised if the object contains a
# class that isn't in the +permitted_classes+ list.
#
# Currently supported options are:
#
# [<tt>:indentation</tt>] Number of space characters used to indent.
# Acceptable value should be in <tt>0..9</tt> range,
# otherwise option is ignored.
#
# Default: <tt>2</tt>.
# [<tt>:line_width</tt>] Max character to wrap line at.
#
# Default: <tt>0</tt> (meaning "wrap at 81").
# [<tt>:canonical</tt>] Write "canonical" YAML form (very verbose, yet
# strictly formal).
#
# Default: <tt>false</tt>.
# [<tt>:header</tt>] Write <tt>%YAML [version]</tt> at the beginning of document.
#
# Default: <tt>false</tt>.
#
# Example:
#
# # Dump an array, get back a YAML string
# Psych.safe_dump(['a', 'b']) # => "---\n- a\n- b\n"
#
# # Dump an array to an IO object
# Psych.safe_dump(['a', 'b'], StringIO.new) # => #<StringIO:0x000001009d0890>
#
# # Dump an array with indentation set
# Psych.safe_dump(['a', ['b']], indentation: 3) # => "---\n- a\n- - b\n"
#
# # Dump an array to an IO with indentation set
# Psych.safe_dump(['a', ['b']], StringIO.new, indentation: 3)
def self.safe_dump o, io = nil, options = {}
if Hash === io
options = io
io = nil
end
visitor = Psych::Visitors::RestrictedYAMLTree.create options
visitor << o
visitor.tree.yaml io, options
end
###
# Dump a list of objects as separate documents to a document stream.
#
# Example:
#
# Psych.dump_stream("foo\n ", {}) # => "--- ! \"foo\\n \"\n--- {}\n"
def self.dump_stream *objects
visitor = Psych::Visitors::YAMLTree.create({})
objects.each do |o|
visitor << o
end
visitor.tree.yaml
end
###
# Dump Ruby +object+ to a JSON string.
def self.to_json object
visitor = Psych::Visitors::JSONTree.create
visitor << object
visitor.tree.yaml
end
###
# Load multiple documents given in +yaml+. Returns the parsed documents
# as a list. If a block is given, each document will be converted to Ruby
# and passed to the block during parsing
#
# Example:
#
# Psych.load_stream("--- foo\n...\n--- bar\n...") # => ['foo', 'bar']
#
# list = []
# Psych.load_stream("--- foo\n...\n--- bar\n...") do |ruby|
# list << ruby
# end
# list # => ['foo', 'bar']
#
def self.load_stream yaml, filename: nil, fallback: [], **kwargs
result = if block_given?
parse_stream(yaml, filename: filename) do |node|
yield node.to_ruby(**kwargs)
end
else
parse_stream(yaml, filename: filename).children.map { |node| node.to_ruby(**kwargs) }
end
return fallback if result.is_a?(Array) && result.empty?
result
end
###
# Load the document contained in +filename+. Returns the yaml contained in
# +filename+ as a Ruby object, or if the file is empty, it returns
# the specified +fallback+ return value, which defaults to +false+.
#
# NOTE: This method *should not* be used to parse untrusted documents, such as
# YAML documents that are supplied via user input. Instead, please use the
# safe_load_file method.
def self.unsafe_load_file filename, **kwargs
File.open(filename, 'r:bom|utf-8') { |f|
self.unsafe_load f, filename: filename, **kwargs
}
end
###
# Safely loads the document contained in +filename+. Returns the yaml contained in
# +filename+ as a Ruby object, or if the file is empty, it returns
# the specified +fallback+ return value, which defaults to +false+.
# See safe_load for options.
def self.safe_load_file filename, **kwargs
File.open(filename, 'r:bom|utf-8') { |f|
self.safe_load f, filename: filename, **kwargs
}
end
###
# Loads the document contained in +filename+. Returns the yaml contained in
# +filename+ as a Ruby object, or if the file is empty, it returns
# the specified +fallback+ return value, which defaults to +false+.
# See load for options.
def self.load_file filename, **kwargs
File.open(filename, 'r:bom|utf-8') { |f|
self.load f, filename: filename, **kwargs
}
end
# :stopdoc:
def self.add_domain_type domain, type_tag, &block
key = ['tag', domain, type_tag].join ':'
domain_types[key] = [key, block]
domain_types["tag:#{type_tag}"] = [key, block]
end
def self.add_builtin_type type_tag, &block
domain = 'yaml.org,2002'
key = ['tag', domain, type_tag].join ':'
domain_types[key] = [key, block]
end
def self.remove_type type_tag
domain_types.delete type_tag
end
def self.add_tag tag, klass
load_tags[tag] = klass.name
dump_tags[klass] = tag
end
# Workaround for emulating `warn '...', uplevel: 1` in Ruby 2.4 or lower.
def self.warn_with_uplevel(message, uplevel: 1)
at = parse_caller(caller[uplevel]).join(':')
warn "#{at}: #{message}"
end
def self.parse_caller(at)
if /^(.+?):(\d+)(?::in `.*')?/ =~ at
file = $1
line = $2.to_i
[file, line]
end
end
private_class_method :warn_with_uplevel, :parse_caller
class << self
if defined?(Ractor)
require 'forwardable'
extend Forwardable
class Config
attr_accessor :load_tags, :dump_tags, :domain_types
def initialize
@load_tags = {}
@dump_tags = {}
@domain_types = {}
end
end
def config
Ractor.current[:PsychConfig] ||= Config.new
end
def_delegators :config, :load_tags, :dump_tags, :domain_types, :load_tags=, :dump_tags=, :domain_types=
else
attr_accessor :load_tags
attr_accessor :dump_tags
attr_accessor :domain_types
end
end
self.load_tags = {}
self.dump_tags = {}
self.domain_types = {}
# :startdoc:
end
share/ruby/ripper/sexp.rb 0000644 00000011054 15173547335 0011452 0 ustar 00 # frozen_string_literal: true
#
# $Id$
#
# Copyright (c) 2004,2005 Minero Aoki
#
# This program is free software.
# You can distribute and/or modify this program under the Ruby License.
# For details of Ruby License, see ruby/COPYING.
#
require 'ripper/core'
class Ripper
# [EXPERIMENTAL]
# Parses +src+ and create S-exp tree.
# Returns more readable tree rather than Ripper.sexp_raw.
# This method is mainly for developer use.
# The +filename+ argument is mostly ignored.
# By default, this method does not handle syntax errors in +src+,
# returning +nil+ in such cases. Use the +raise_errors+ keyword
# to raise a SyntaxError for an error in +src+.
#
# require 'ripper'
# require 'pp'
#
# pp Ripper.sexp("def m(a) nil end")
# #=> [:program,
# [[:def,
# [:@ident, "m", [1, 4]],
# [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil, nil, nil, nil]],
# [:bodystmt, [[:var_ref, [:@kw, "nil", [1, 9]]]], nil, nil, nil]]]]
#
def Ripper.sexp(src, filename = '-', lineno = 1, raise_errors: false)
builder = SexpBuilderPP.new(src, filename, lineno)
sexp = builder.parse
if builder.error?
if raise_errors
raise SyntaxError, builder.error
end
else
sexp
end
end
# [EXPERIMENTAL]
# Parses +src+ and create S-exp tree.
# This method is mainly for developer use.
# The +filename+ argument is mostly ignored.
# By default, this method does not handle syntax errors in +src+,
# returning +nil+ in such cases. Use the +raise_errors+ keyword
# to raise a SyntaxError for an error in +src+.
#
# require 'ripper'
# require 'pp'
#
# pp Ripper.sexp_raw("def m(a) nil end")
# #=> [:program,
# [:stmts_add,
# [:stmts_new],
# [:def,
# [:@ident, "m", [1, 4]],
# [:paren, [:params, [[:@ident, "a", [1, 6]]], nil, nil, nil]],
# [:bodystmt,
# [:stmts_add, [:stmts_new], [:var_ref, [:@kw, "nil", [1, 9]]]],
# nil,
# nil,
# nil]]]]
#
def Ripper.sexp_raw(src, filename = '-', lineno = 1, raise_errors: false)
builder = SexpBuilder.new(src, filename, lineno)
sexp = builder.parse
if builder.error?
if raise_errors
raise SyntaxError, builder.error
end
else
sexp
end
end
class SexpBuilder < ::Ripper #:nodoc:
attr_reader :error
private
def dedent_element(e, width)
if (n = dedent_string(e[1], width)) > 0
e[2][1] += n
end
e
end
def on_heredoc_dedent(val, width)
sub = proc do |cont|
cont.map! do |e|
if Array === e
case e[0]
when :@tstring_content
e = dedent_element(e, width)
when /_add\z/
e[1] = sub[e[1]]
end
elsif String === e
dedent_string(e, width)
end
e
end
end
sub[val]
val
end
events = private_instance_methods(false).grep(/\Aon_/) {$'.to_sym}
(PARSER_EVENTS - events).each do |event|
module_eval(<<-End, __FILE__, __LINE__ + 1)
def on_#{event}(*args)
args.unshift :#{event}
args
end
End
end
SCANNER_EVENTS.each do |event|
module_eval(<<-End, __FILE__, __LINE__ + 1)
def on_#{event}(tok)
[:@#{event}, tok, [lineno(), column()]]
end
End
end
def on_error(mesg)
@error = mesg
end
remove_method :on_parse_error
alias on_parse_error on_error
alias compile_error on_error
end
class SexpBuilderPP < SexpBuilder #:nodoc:
private
def on_heredoc_dedent(val, width)
val.map! do |e|
next e if Symbol === e and /_content\z/ =~ e
if Array === e and e[0] == :@tstring_content
e = dedent_element(e, width)
elsif String === e
dedent_string(e, width)
end
e
end
val
end
def _dispatch_event_new
[]
end
def _dispatch_event_push(list, item)
list.push item
list
end
def on_mlhs_paren(list)
[:mlhs, *list]
end
def on_mlhs_add_star(list, star)
list.push([:rest_param, star])
end
def on_mlhs_add_post(list, post)
list.concat(post)
end
PARSER_EVENT_TABLE.each do |event, arity|
if /_new\z/ =~ event and arity == 0
alias_method "on_#{event}", :_dispatch_event_new
elsif /_add\z/ =~ event
alias_method "on_#{event}", :_dispatch_event_push
end
end
end
end
share/ruby/ripper/lexer.rb 0000644 00000023270 15173547335 0011615 0 ustar 00 # frozen_string_literal: true
#
# $Id$
#
# Copyright (c) 2004,2005 Minero Aoki
#
# This program is free software.
# You can distribute and/or modify this program under the Ruby License.
# For details of Ruby License, see ruby/COPYING.
#
require 'ripper/core'
class Ripper
# Tokenizes the Ruby program and returns an array of strings.
# The +filename+ and +lineno+ arguments are mostly ignored, since the
# return value is just the tokenized input.
# By default, this method does not handle syntax errors in +src+,
# use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+.
#
# p Ripper.tokenize("def m(a) nil end")
# # => ["def", " ", "m", "(", "a", ")", " ", "nil", " ", "end"]
#
def Ripper.tokenize(src, filename = '-', lineno = 1, **kw)
Lexer.new(src, filename, lineno).tokenize(**kw)
end
# Tokenizes the Ruby program and returns an array of an array,
# which is formatted like
# <code>[[lineno, column], type, token, state]</code>.
# The +filename+ argument is mostly ignored.
# By default, this method does not handle syntax errors in +src+,
# use the +raise_errors+ keyword to raise a SyntaxError for an error in +src+.
#
# require 'ripper'
# require 'pp'
#
# pp Ripper.lex("def m(a) nil end")
# #=> [[[1, 0], :on_kw, "def", FNAME ],
# [[1, 3], :on_sp, " ", FNAME ],
# [[1, 4], :on_ident, "m", ENDFN ],
# [[1, 5], :on_lparen, "(", BEG|LABEL],
# [[1, 6], :on_ident, "a", ARG ],
# [[1, 7], :on_rparen, ")", ENDFN ],
# [[1, 8], :on_sp, " ", BEG ],
# [[1, 9], :on_kw, "nil", END ],
# [[1, 12], :on_sp, " ", END ],
# [[1, 13], :on_kw, "end", END ]]
#
def Ripper.lex(src, filename = '-', lineno = 1, **kw)
Lexer.new(src, filename, lineno).lex(**kw)
end
class Lexer < ::Ripper #:nodoc: internal use only
class State
attr_reader :to_int, :to_s
def initialize(i)
@to_int = i
@to_s = Ripper.lex_state_name(i)
freeze
end
def [](index)
case index
when 0, :to_int
@to_int
when 1, :to_s
@event
else
nil
end
end
alias to_i to_int
alias inspect to_s
def pretty_print(q) q.text(to_s) end
def ==(i) super or to_int == i end
def &(i) self.class.new(to_int & i) end
def |(i) self.class.new(to_int | i) end
def allbits?(i) to_int.allbits?(i) end
def anybits?(i) to_int.anybits?(i) end
def nobits?(i) to_int.nobits?(i) end
end
class Elem
attr_accessor :pos, :event, :tok, :state, :message
def initialize(pos, event, tok, state, message = nil)
@pos = pos
@event = event
@tok = tok
@state = State.new(state)
@message = message
end
def [](index)
case index
when 0, :pos
@pos
when 1, :event
@event
when 2, :tok
@tok
when 3, :state
@state
when 4, :message
@message
else
nil
end
end
def inspect
"#<#{self.class}: #{event}@#{pos[0]}:#{pos[1]}:#{state}: #{tok.inspect}#{": " if message}#{message}>"
end
alias to_s inspect
def pretty_print(q)
q.group(2, "#<#{self.class}:", ">") {
q.breakable
q.text("#{event}@#{pos[0]}:#{pos[1]}")
q.breakable
state.pretty_print(q)
q.breakable
q.text("token: ")
tok.pretty_print(q)
if message
q.breakable
q.text("message: ")
q.text(message)
end
}
end
def to_a
if @message
[@pos, @event, @tok, @state, @message]
else
[@pos, @event, @tok, @state]
end
end
end
attr_reader :errors
def tokenize(**kw)
parse(**kw).sort_by(&:pos).map(&:tok)
end
def lex(**kw)
parse(**kw).sort_by(&:pos).map(&:to_a)
end
# parse the code and returns elements including errors.
def scan(**kw)
result = (parse(**kw) + errors + @stack.flatten).uniq.sort_by {|e| [*e.pos, (e.message ? -1 : 0)]}
result.each_with_index do |e, i|
if e.event == :on_parse_error and e.tok.empty? and (pre = result[i-1]) and
pre.pos[0] == e.pos[0] and (pre.pos[1] + pre.tok.size) == e.pos[1]
e.tok = pre.tok
e.pos[1] = pre.pos[1]
result[i-1] = e
result[i] = pre
end
end
result
end
def parse(raise_errors: false)
@errors = []
@buf = []
@stack = []
super()
@buf = @stack.pop unless @stack.empty?
if raise_errors and !@errors.empty?
raise SyntaxError, @errors.map(&:message).join(' ;')
end
@buf.flatten!
unless (result = @buf).empty?
result.concat(@buf) until (@buf = []; super(); @buf.flatten!; @buf.empty?)
end
result
end
private
unless SCANNER_EVENT_TABLE.key?(:ignored_sp)
SCANNER_EVENT_TABLE[:ignored_sp] = 1
SCANNER_EVENTS << :ignored_sp
EVENTS << :ignored_sp
end
def on_heredoc_dedent(v, w)
ignored_sp = []
heredoc = @buf.last
if Array === heredoc
heredoc.each_with_index do |e, i|
if Elem === e and e.event == :on_tstring_content and e.pos[1].zero?
tok = e.tok.dup if w > 0 and /\A\s/ =~ e.tok
if (n = dedent_string(e.tok, w)) > 0
if e.tok.empty?
e.tok = tok[0, n]
e.event = :on_ignored_sp
next
end
ignored_sp << [i, Elem.new(e.pos.dup, :on_ignored_sp, tok[0, n], e.state)]
e.pos[1] += n
end
end
end
end
ignored_sp.reverse_each do |i, e|
heredoc[i, 0] = [e]
end
v
end
def on_heredoc_beg(tok)
@stack.push @buf
buf = []
@buf.push buf
@buf = buf
@buf.push Elem.new([lineno(), column()], __callee__, tok, state())
end
def on_heredoc_end(tok)
@buf.push Elem.new([lineno(), column()], __callee__, tok, state())
@buf = @stack.pop unless @stack.empty?
end
def _push_token(tok)
e = Elem.new([lineno(), column()], __callee__, tok, state())
@buf.push(e)
e
end
def on_error1(mesg)
@errors.push Elem.new([lineno(), column()], __callee__, token(), state(), mesg)
end
def on_error2(mesg, elem)
if elem
elem = Elem.new(elem.pos, __callee__, elem.tok, elem.state, mesg)
else
elem = Elem.new([lineno(), column()], __callee__, token(), state(), mesg)
end
@errors.push elem
end
PARSER_EVENTS.grep(/_error\z/) do |e|
arity = PARSER_EVENT_TABLE.fetch(e)
alias_method "on_#{e}", "on_error#{arity}"
end
alias compile_error on_error1
(SCANNER_EVENTS.map {|event|:"on_#{event}"} - private_instance_methods(false)).each do |event|
alias_method event, :_push_token
end
end
# [EXPERIMENTAL]
# Parses +src+ and return a string which was matched to +pattern+.
# +pattern+ should be described as Regexp.
#
# require 'ripper'
#
# p Ripper.slice('def m(a) nil end', 'ident') #=> "m"
# p Ripper.slice('def m(a) nil end', '[ident lparen rparen]+') #=> "m(a)"
# p Ripper.slice("<<EOS\nstring\nEOS",
# 'heredoc_beg nl $(tstring_content*) heredoc_end', 1)
# #=> "string\n"
#
def Ripper.slice(src, pattern, n = 0)
if m = token_match(src, pattern)
then m.string(n)
else nil
end
end
def Ripper.token_match(src, pattern) #:nodoc:
TokenPattern.compile(pattern).match(src)
end
class TokenPattern #:nodoc:
class Error < ::StandardError # :nodoc:
end
class CompileError < Error # :nodoc:
end
class MatchError < Error # :nodoc:
end
class << self
alias compile new
end
def initialize(pattern)
@source = pattern
@re = compile(pattern)
end
def match(str)
match_list(::Ripper.lex(str))
end
def match_list(tokens)
if m = @re.match(map_tokens(tokens))
then MatchData.new(tokens, m)
else nil
end
end
private
def compile(pattern)
if m = /[^\w\s$()\[\]{}?*+\.]/.match(pattern)
raise CompileError, "invalid char in pattern: #{m[0].inspect}"
end
buf = +''
pattern.scan(/(?:\w+|\$\(|[()\[\]\{\}?*+\.]+)/) do |tok|
case tok
when /\w/
buf.concat map_token(tok)
when '$('
buf.concat '('
when '('
buf.concat '(?:'
when /[?*\[\])\.]/
buf.concat tok
else
raise 'must not happen'
end
end
Regexp.compile(buf)
rescue RegexpError => err
raise CompileError, err.message
end
def map_tokens(tokens)
tokens.map {|pos,type,str| map_token(type.to_s.delete_prefix('on_')) }.join
end
MAP = {}
seed = ('a'..'z').to_a + ('A'..'Z').to_a + ('0'..'9').to_a
SCANNER_EVENT_TABLE.each do |ev, |
raise CompileError, "[RIPPER FATAL] too many system token" if seed.empty?
MAP[ev.to_s.delete_prefix('on_')] = seed.shift
end
def map_token(tok)
MAP[tok] or raise CompileError, "unknown token: #{tok}"
end
class MatchData # :nodoc:
def initialize(tokens, match)
@tokens = tokens
@match = match
end
def string(n = 0)
return nil unless @match
match(n).join
end
private
def match(n = 0)
return [] unless @match
@tokens[@match.begin(n)...@match.end(n)].map {|pos,type,str| str }
end
end
end
end
share/ruby/ripper/core.rb 0000644 00000003253 15173547335 0011425 0 ustar 00 # frozen_string_literal: true
#
# $Id$
#
# Copyright (c) 2003-2005 Minero Aoki
#
# This program is free software.
# You can distribute and/or modify this program under the Ruby License.
# For details of Ruby License, see ruby/COPYING.
#
require 'ripper.so'
class Ripper
# Parses the given Ruby program read from +src+.
# +src+ must be a String or an IO or a object with a #gets method.
def Ripper.parse(src, filename = '(ripper)', lineno = 1)
new(src, filename, lineno).parse
end
# This array contains name of parser events.
PARSER_EVENTS = PARSER_EVENT_TABLE.keys
# This array contains name of scanner events.
SCANNER_EVENTS = SCANNER_EVENT_TABLE.keys
# This array contains name of all ripper events.
EVENTS = PARSER_EVENTS + SCANNER_EVENTS
private
# :stopdoc:
def _dispatch_0() nil end
def _dispatch_1(a) a end
def _dispatch_2(a, b) a end
def _dispatch_3(a, b, c) a end
def _dispatch_4(a, b, c, d) a end
def _dispatch_5(a, b, c, d, e) a end
def _dispatch_6(a, b, c, d, e, f) a end
def _dispatch_7(a, b, c, d, e, f, g) a end
# :startdoc:
#
# Parser Events
#
PARSER_EVENT_TABLE.each do |id, arity|
alias_method "on_#{id}", "_dispatch_#{arity}"
end
# This method is called when weak warning is produced by the parser.
# +fmt+ and +args+ is printf style.
def warn(fmt, *args)
end
# This method is called when strong warning is produced by the parser.
# +fmt+ and +args+ is printf style.
def warning(fmt, *args)
end
# This method is called when the parser found syntax error.
def compile_error(msg)
end
#
# Scanner Events
#
SCANNER_EVENTS.each do |id|
alias_method "on_#{id}", :_dispatch_1
end
end
share/ruby/ripper/filter.rb 0000644 00000004160 15173547335 0011760 0 ustar 00 # frozen_string_literal: true
#
# $Id$
#
# Copyright (c) 2004,2005 Minero Aoki
#
# This program is free software.
# You can distribute and/or modify this program under the Ruby License.
# For details of Ruby License, see ruby/COPYING.
#
require 'ripper/lexer'
class Ripper
# This class handles only scanner events,
# which are dispatched in the 'right' order (same with input).
class Filter
# Creates a new Ripper::Filter instance, passes parameters +src+,
# +filename+, and +lineno+ to Ripper::Lexer.new
#
# The lexer is for internal use only.
def initialize(src, filename = '-', lineno = 1)
@__lexer = Lexer.new(src, filename, lineno)
@__line = nil
@__col = nil
@__state = nil
end
# The file name of the input.
def filename
@__lexer.filename
end
# The line number of the current token.
# This value starts from 1.
# This method is valid only in event handlers.
def lineno
@__line
end
# The column number of the current token.
# This value starts from 0.
# This method is valid only in event handlers.
def column
@__col
end
# The scanner's state of the current token.
# This value is the bitwise OR of zero or more of the +Ripper::EXPR_*+ constants.
def state
@__state
end
# Starts the parser.
# +init+ is a data accumulator and is passed to the next event handler (as
# of Enumerable#inject).
def parse(init = nil)
data = init
@__lexer.lex.each do |pos, event, tok, state|
@__line, @__col = *pos
@__state = state
data = if respond_to?(event, true)
then __send__(event, tok, data)
else on_default(event, tok, data)
end
end
data
end
private
# This method is called when some event handler is undefined.
# +event+ is :on_XXX, +token+ is the scanned token, and +data+ is a data
# accumulator.
#
# The return value of this method is passed to the next event handler (as
# of Enumerable#inject).
def on_default(event, token, data)
data
end
end
end
share/ruby/uri.rb 0000644 00000006073 15173547335 0007776 0 ustar 00 # frozen_string_literal: false
# URI is a module providing classes to handle Uniform Resource Identifiers
# (RFC2396[http://tools.ietf.org/html/rfc2396]).
#
# == Features
#
# * Uniform way of handling URIs.
# * Flexibility to introduce custom URI schemes.
# * Flexibility to have an alternate URI::Parser (or just different patterns
# and regexp's).
#
# == Basic example
#
# require 'uri'
#
# uri = URI("http://foo.com/posts?id=30&limit=5#time=1305298413")
# #=> #<URI::HTTP http://foo.com/posts?id=30&limit=5#time=1305298413>
#
# uri.scheme #=> "http"
# uri.host #=> "foo.com"
# uri.path #=> "/posts"
# uri.query #=> "id=30&limit=5"
# uri.fragment #=> "time=1305298413"
#
# uri.to_s #=> "http://foo.com/posts?id=30&limit=5#time=1305298413"
#
# == Adding custom URIs
#
# module URI
# class RSYNC < Generic
# DEFAULT_PORT = 873
# end
# register_scheme 'RSYNC', RSYNC
# end
# #=> URI::RSYNC
#
# URI.scheme_list
# #=> {"FILE"=>URI::File, "FTP"=>URI::FTP, "HTTP"=>URI::HTTP,
# # "HTTPS"=>URI::HTTPS, "LDAP"=>URI::LDAP, "LDAPS"=>URI::LDAPS,
# # "MAILTO"=>URI::MailTo, "RSYNC"=>URI::RSYNC}
#
# uri = URI("rsync://rsync.foo.com")
# #=> #<URI::RSYNC rsync://rsync.foo.com>
#
# == RFC References
#
# A good place to view an RFC spec is http://www.ietf.org/rfc.html.
#
# Here is a list of all related RFC's:
# - RFC822[http://tools.ietf.org/html/rfc822]
# - RFC1738[http://tools.ietf.org/html/rfc1738]
# - RFC2255[http://tools.ietf.org/html/rfc2255]
# - RFC2368[http://tools.ietf.org/html/rfc2368]
# - RFC2373[http://tools.ietf.org/html/rfc2373]
# - RFC2396[http://tools.ietf.org/html/rfc2396]
# - RFC2732[http://tools.ietf.org/html/rfc2732]
# - RFC3986[http://tools.ietf.org/html/rfc3986]
#
# == Class tree
#
# - URI::Generic (in uri/generic.rb)
# - URI::File - (in uri/file.rb)
# - URI::FTP - (in uri/ftp.rb)
# - URI::HTTP - (in uri/http.rb)
# - URI::HTTPS - (in uri/https.rb)
# - URI::LDAP - (in uri/ldap.rb)
# - URI::LDAPS - (in uri/ldaps.rb)
# - URI::MailTo - (in uri/mailto.rb)
# - URI::Parser - (in uri/common.rb)
# - URI::REGEXP - (in uri/common.rb)
# - URI::REGEXP::PATTERN - (in uri/common.rb)
# - URI::Util - (in uri/common.rb)
# - URI::Error - (in uri/common.rb)
# - URI::InvalidURIError - (in uri/common.rb)
# - URI::InvalidComponentError - (in uri/common.rb)
# - URI::BadURIError - (in uri/common.rb)
#
# == Copyright Info
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# Documentation::
# Akira Yamada <akira@ruby-lang.org>
# Dmitry V. Sabanin <sdmitry@lrn.ru>
# Vincent Batts <vbatts@hashbangbash.com>
# License::
# Copyright (c) 2001 akira yamada <akira@ruby-lang.org>
# You can redistribute it and/or modify it under the same term as Ruby.
#
module URI
end
require_relative 'uri/version'
require_relative 'uri/common'
require_relative 'uri/generic'
require_relative 'uri/file'
require_relative 'uri/ftp'
require_relative 'uri/http'
require_relative 'uri/https'
require_relative 'uri/ldap'
require_relative 'uri/ldaps'
require_relative 'uri/mailto'
require_relative 'uri/ws'
require_relative 'uri/wss'
share/ruby/csv.rb 0000644 00000270742 15173547335 0010000 0 ustar 00 # encoding: US-ASCII
# frozen_string_literal: true
# = csv.rb -- CSV Reading and Writing
#
# Created by James Edward Gray II on 2005-10-31.
#
# See CSV for documentation.
#
# == Description
#
# Welcome to the new and improved CSV.
#
# This version of the CSV library began its life as FasterCSV. FasterCSV was
# intended as a replacement to Ruby's then standard CSV library. It was
# designed to address concerns users of that library had and it had three
# primary goals:
#
# 1. Be significantly faster than CSV while remaining a pure Ruby library.
# 2. Use a smaller and easier to maintain code base. (FasterCSV eventually
# grew larger, was also but considerably richer in features. The parsing
# core remains quite small.)
# 3. Improve on the CSV interface.
#
# Obviously, the last one is subjective. I did try to defer to the original
# interface whenever I didn't have a compelling reason to change it though, so
# hopefully this won't be too radically different.
#
# We must have met our goals because FasterCSV was renamed to CSV and replaced
# the original library as of Ruby 1.9. If you are migrating code from 1.8 or
# earlier, you may have to change your code to comply with the new interface.
#
# == What's the Different From the Old CSV?
#
# I'm sure I'll miss something, but I'll try to mention most of the major
# differences I am aware of, to help others quickly get up to speed:
#
# === \CSV Parsing
#
# * This parser is m17n aware. See CSV for full details.
# * This library has a stricter parser and will throw MalformedCSVErrors on
# problematic data.
# * This library has a less liberal idea of a line ending than CSV. What you
# set as the <tt>:row_sep</tt> is law. It can auto-detect your line endings
# though.
# * The old library returned empty lines as <tt>[nil]</tt>. This library calls
# them <tt>[]</tt>.
# * This library has a much faster parser.
#
# === Interface
#
# * CSV now uses keyword parameters to set options.
# * CSV no longer has generate_row() or parse_row().
# * The old CSV's Reader and Writer classes have been dropped.
# * CSV::open() is now more like Ruby's open().
# * CSV objects now support most standard IO methods.
# * CSV now has a new() method used to wrap objects like String and IO for
# reading and writing.
# * CSV::generate() is different from the old method.
# * CSV no longer supports partial reads. It works line-by-line.
# * CSV no longer allows the instance methods to override the separators for
# performance reasons. They must be set in the constructor.
#
# If you use this library and find yourself missing any functionality I have
# trimmed, please {let me know}[mailto:james@grayproductions.net].
#
# == Documentation
#
# See CSV for documentation.
#
# == What is CSV, really?
#
# CSV maintains a pretty strict definition of CSV taken directly from
# {the RFC}[http://www.ietf.org/rfc/rfc4180.txt]. I relax the rules in only one
# place and that is to make using this library easier. CSV will parse all valid
# CSV.
#
# What you don't want to do is to feed CSV invalid data. Because of the way the
# CSV format works, it's common for a parser to need to read until the end of
# the file to be sure a field is invalid. This consumes a lot of time and memory.
#
# Luckily, when working with invalid CSV, Ruby's built-in methods will almost
# always be superior in every way. For example, parsing non-quoted fields is as
# easy as:
#
# data.split(",")
#
# == Questions and/or Comments
#
# Feel free to email {James Edward Gray II}[mailto:james@grayproductions.net]
# with any questions.
require "forwardable"
require "date"
require "stringio"
require_relative "csv/fields_converter"
require_relative "csv/input_record_separator"
require_relative "csv/parser"
require_relative "csv/row"
require_relative "csv/table"
require_relative "csv/writer"
# == \CSV
#
# === In a Hurry?
#
# If you are familiar with \CSV data and have a particular task in mind,
# you may want to go directly to the:
# - {Recipes for CSV}[doc/csv/recipes/recipes_rdoc.html].
#
# Otherwise, read on here, about the API: classes, methods, and constants.
#
# === \CSV Data
#
# \CSV (comma-separated values) data is a text representation of a table:
# - A _row_ _separator_ delimits table rows.
# A common row separator is the newline character <tt>"\n"</tt>.
# - A _column_ _separator_ delimits fields in a row.
# A common column separator is the comma character <tt>","</tt>.
#
# This \CSV \String, with row separator <tt>"\n"</tt>
# and column separator <tt>","</tt>,
# has three rows and two columns:
# "foo,0\nbar,1\nbaz,2\n"
#
# Despite the name \CSV, a \CSV representation can use different separators.
#
# For more about tables, see the Wikipedia article
# "{Table (information)}[https://en.wikipedia.org/wiki/Table_(information)]",
# especially its section
# "{Simple table}[https://en.wikipedia.org/wiki/Table_(information)#Simple_table]"
#
# == \Class \CSV
#
# Class \CSV provides methods for:
# - Parsing \CSV data from a \String object, a \File (via its file path), or an \IO object.
# - Generating \CSV data to a \String object.
#
# To make \CSV available:
# require 'csv'
#
# All examples here assume that this has been done.
#
# == Keeping It Simple
#
# A \CSV object has dozens of instance methods that offer fine-grained control
# of parsing and generating \CSV data.
# For many needs, though, simpler approaches will do.
#
# This section summarizes the singleton methods in \CSV
# that allow you to parse and generate without explicitly
# creating \CSV objects.
# For details, follow the links.
#
# === Simple Parsing
#
# Parsing methods commonly return either of:
# - An \Array of Arrays of Strings:
# - The outer \Array is the entire "table".
# - Each inner \Array is a row.
# - Each \String is a field.
# - A CSV::Table object. For details, see
# {\CSV with Headers}[#class-CSV-label-CSV+with+Headers].
#
# ==== Parsing a \String
#
# The input to be parsed can be a string:
# string = "foo,0\nbar,1\nbaz,2\n"
#
# \Method CSV.parse returns the entire \CSV data:
# CSV.parse(string) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# \Method CSV.parse_line returns only the first row:
# CSV.parse_line(string) # => ["foo", "0"]
#
# \CSV extends class \String with instance method String#parse_csv,
# which also returns only the first row:
# string.parse_csv # => ["foo", "0"]
#
# ==== Parsing Via a \File Path
#
# The input to be parsed can be in a file:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# \Method CSV.read returns the entire \CSV data:
# CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# \Method CSV.foreach iterates, passing each row to the given block:
# CSV.foreach(path) do |row|
# p row
# end
# Output:
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# \Method CSV.table returns the entire \CSV data as a CSV::Table object:
# CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:3>
#
# ==== Parsing from an Open \IO Stream
#
# The input to be parsed can be in an open \IO stream:
#
# \Method CSV.read returns the entire \CSV data:
# File.open(path) do |file|
# CSV.read(file)
# end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# As does method CSV.parse:
# File.open(path) do |file|
# CSV.parse(file)
# end # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# \Method CSV.parse_line returns only the first row:
# File.open(path) do |file|
# CSV.parse_line(file)
# end # => ["foo", "0"]
#
# \Method CSV.foreach iterates, passing each row to the given block:
# File.open(path) do |file|
# CSV.foreach(file) do |row|
# p row
# end
# end
# Output:
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# \Method CSV.table returns the entire \CSV data as a CSV::Table object:
# File.open(path) do |file|
# CSV.table(file)
# end # => #<CSV::Table mode:col_or_row row_count:3>
#
# === Simple Generating
#
# \Method CSV.generate returns a \String;
# this example uses method CSV#<< to append the rows
# that are to be generated:
# output_string = CSV.generate do |csv|
# csv << ['foo', 0]
# csv << ['bar', 1]
# csv << ['baz', 2]
# end
# output_string # => "foo,0\nbar,1\nbaz,2\n"
#
# \Method CSV.generate_line returns a \String containing the single row
# constructed from an \Array:
# CSV.generate_line(['foo', '0']) # => "foo,0\n"
#
# \CSV extends class \Array with instance method <tt>Array#to_csv</tt>,
# which forms an \Array into a \String:
# ['foo', '0'].to_csv # => "foo,0\n"
#
# === "Filtering" \CSV
#
# \Method CSV.filter provides a Unix-style filter for \CSV data.
# The input data is processed to form the output data:
# in_string = "foo,0\nbar,1\nbaz,2\n"
# out_string = ''
# CSV.filter(in_string, out_string) do |row|
# row[0] = row[0].upcase
# row[1] *= 4
# end
# out_string # => "FOO,0000\nBAR,1111\nBAZ,2222\n"
#
# == \CSV Objects
#
# There are three ways to create a \CSV object:
# - \Method CSV.new returns a new \CSV object.
# - \Method CSV.instance returns a new or cached \CSV object.
# - \Method \CSV() also returns a new or cached \CSV object.
#
# === Instance Methods
#
# \CSV has three groups of instance methods:
# - Its own internally defined instance methods.
# - Methods included by module Enumerable.
# - Methods delegated to class IO. See below.
#
# ==== Delegated Methods
#
# For convenience, a CSV object will delegate to many methods in class IO.
# (A few have wrapper "guard code" in \CSV.) You may call:
# * IO#binmode
# * #binmode?
# * IO#close
# * IO#close_read
# * IO#close_write
# * IO#closed?
# * #eof
# * #eof?
# * IO#external_encoding
# * IO#fcntl
# * IO#fileno
# * #flock
# * IO#flush
# * IO#fsync
# * IO#internal_encoding
# * #ioctl
# * IO#isatty
# * #path
# * IO#pid
# * IO#pos
# * IO#pos=
# * IO#reopen
# * #rewind
# * IO#seek
# * #stat
# * IO#string
# * IO#sync
# * IO#sync=
# * IO#tell
# * #to_i
# * #to_io
# * IO#truncate
# * IO#tty?
#
# === Options
#
# The default values for options are:
# DEFAULT_OPTIONS = {
# # For both parsing and generating.
# col_sep: ",",
# row_sep: :auto,
# quote_char: '"',
# # For parsing.
# field_size_limit: nil,
# converters: nil,
# unconverted_fields: nil,
# headers: false,
# return_headers: false,
# header_converters: nil,
# skip_blanks: false,
# skip_lines: nil,
# liberal_parsing: false,
# nil_value: nil,
# empty_value: "",
# strip: false,
# # For generating.
# write_headers: nil,
# quote_empty: true,
# force_quotes: false,
# write_converters: nil,
# write_nil_value: nil,
# write_empty_value: "",
# }
#
# ==== Options for Parsing
#
# Options for parsing, described in detail below, include:
# - +row_sep+: Specifies the row separator; used to delimit rows.
# - +col_sep+: Specifies the column separator; used to delimit fields.
# - +quote_char+: Specifies the quote character; used to quote fields.
# - +field_size_limit+: Specifies the maximum field size + 1 allowed.
# Deprecated since 3.2.3. Use +max_field_size+ instead.
# - +max_field_size+: Specifies the maximum field size allowed.
# - +converters+: Specifies the field converters to be used.
# - +unconverted_fields+: Specifies whether unconverted fields are to be available.
# - +headers+: Specifies whether data contains headers,
# or specifies the headers themselves.
# - +return_headers+: Specifies whether headers are to be returned.
# - +header_converters+: Specifies the header converters to be used.
# - +skip_blanks+: Specifies whether blanks lines are to be ignored.
# - +skip_lines+: Specifies how comments lines are to be recognized.
# - +strip+: Specifies whether leading and trailing whitespace are to be
# stripped from fields. This must be compatible with +col_sep+; if it is not,
# then an +ArgumentError+ exception will be raised.
# - +liberal_parsing+: Specifies whether \CSV should attempt to parse
# non-compliant data.
# - +nil_value+: Specifies the object that is to be substituted for each null (no-text) field.
# - +empty_value+: Specifies the object that is to be substituted for each empty field.
#
# :include: ../doc/csv/options/common/row_sep.rdoc
#
# :include: ../doc/csv/options/common/col_sep.rdoc
#
# :include: ../doc/csv/options/common/quote_char.rdoc
#
# :include: ../doc/csv/options/parsing/field_size_limit.rdoc
#
# :include: ../doc/csv/options/parsing/converters.rdoc
#
# :include: ../doc/csv/options/parsing/unconverted_fields.rdoc
#
# :include: ../doc/csv/options/parsing/headers.rdoc
#
# :include: ../doc/csv/options/parsing/return_headers.rdoc
#
# :include: ../doc/csv/options/parsing/header_converters.rdoc
#
# :include: ../doc/csv/options/parsing/skip_blanks.rdoc
#
# :include: ../doc/csv/options/parsing/skip_lines.rdoc
#
# :include: ../doc/csv/options/parsing/strip.rdoc
#
# :include: ../doc/csv/options/parsing/liberal_parsing.rdoc
#
# :include: ../doc/csv/options/parsing/nil_value.rdoc
#
# :include: ../doc/csv/options/parsing/empty_value.rdoc
#
# ==== Options for Generating
#
# Options for generating, described in detail below, include:
# - +row_sep+: Specifies the row separator; used to delimit rows.
# - +col_sep+: Specifies the column separator; used to delimit fields.
# - +quote_char+: Specifies the quote character; used to quote fields.
# - +write_headers+: Specifies whether headers are to be written.
# - +force_quotes+: Specifies whether each output field is to be quoted.
# - +quote_empty+: Specifies whether each empty output field is to be quoted.
# - +write_converters+: Specifies the field converters to be used in writing.
# - +write_nil_value+: Specifies the object that is to be substituted for each +nil+-valued field.
# - +write_empty_value+: Specifies the object that is to be substituted for each empty field.
#
# :include: ../doc/csv/options/common/row_sep.rdoc
#
# :include: ../doc/csv/options/common/col_sep.rdoc
#
# :include: ../doc/csv/options/common/quote_char.rdoc
#
# :include: ../doc/csv/options/generating/write_headers.rdoc
#
# :include: ../doc/csv/options/generating/force_quotes.rdoc
#
# :include: ../doc/csv/options/generating/quote_empty.rdoc
#
# :include: ../doc/csv/options/generating/write_converters.rdoc
#
# :include: ../doc/csv/options/generating/write_nil_value.rdoc
#
# :include: ../doc/csv/options/generating/write_empty_value.rdoc
#
# === \CSV with Headers
#
# CSV allows to specify column names of CSV file, whether they are in data, or
# provided separately. If headers are specified, reading methods return an instance
# of CSV::Table, consisting of CSV::Row.
#
# # Headers are part of data
# data = CSV.parse(<<~ROWS, headers: true)
# Name,Department,Salary
# Bob,Engineering,1000
# Jane,Sales,2000
# John,Management,5000
# ROWS
#
# data.class #=> CSV::Table
# data.first #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000">
# data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"}
#
# # Headers provided by developer
# data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
# data.first #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">
#
# === \Converters
#
# By default, each value (field or header) parsed by \CSV is formed into a \String.
# You can use a _field_ _converter_ or _header_ _converter_
# to intercept and modify the parsed values:
# - See {Field Converters}[#class-CSV-label-Field+Converters].
# - See {Header Converters}[#class-CSV-label-Header+Converters].
#
# Also by default, each value to be written during generation is written 'as-is'.
# You can use a _write_ _converter_ to modify values before writing.
# - See {Write Converters}[#class-CSV-label-Write+Converters].
#
# ==== Specifying \Converters
#
# You can specify converters for parsing or generating in the +options+
# argument to various \CSV methods:
# - Option +converters+ for converting parsed field values.
# - Option +header_converters+ for converting parsed header values.
# - Option +write_converters+ for converting values to be written (generated).
#
# There are three forms for specifying converters:
# - A converter proc: executable code to be used for conversion.
# - A converter name: the name of a stored converter.
# - A converter list: an array of converter procs, converter names, and converter lists.
#
# ===== Converter Procs
#
# This converter proc, +strip_converter+, accepts a value +field+
# and returns <tt>field.strip</tt>:
# strip_converter = proc {|field| field.strip }
# In this call to <tt>CSV.parse</tt>,
# the keyword argument <tt>converters: string_converter</tt>
# specifies that:
# - \Proc +string_converter+ is to be called for each parsed field.
# - The converter's return value is to replace the +field+ value.
# Example:
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# A converter proc can receive a second argument, +field_info+,
# that contains details about the field.
# This modified +strip_converter+ displays its arguments:
# strip_converter = proc do |field, field_info|
# p [field, field_info]
# field.strip
# end
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
# Output:
# [" foo ", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# [" 0 ", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# [" bar ", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
# [" 1 ", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
# [" baz ", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
# [" 2 ", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
# Each CSV::FieldInfo object shows:
# - The 0-based field index.
# - The 1-based line index.
# - The field header, if any.
#
# ===== Stored \Converters
#
# A converter may be given a name and stored in a structure where
# the parsing methods can find it by name.
#
# The storage structure for field converters is the \Hash CSV::Converters.
# It has several built-in converter procs:
# - <tt>:integer</tt>: converts each \String-embedded integer into a true \Integer.
# - <tt>:float</tt>: converts each \String-embedded float into a true \Float.
# - <tt>:date</tt>: converts each \String-embedded date into a true \Date.
# - <tt>:date_time</tt>: converts each \String-embedded date-time into a true \DateTime
# .
# This example creates a converter proc, then stores it:
# strip_converter = proc {|field| field.strip }
# CSV::Converters[:strip] = strip_converter
# Then the parsing method call can refer to the converter
# by its name, <tt>:strip</tt>:
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: :strip)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# The storage structure for header converters is the \Hash CSV::HeaderConverters,
# which works in the same way.
# It also has built-in converter procs:
# - <tt>:downcase</tt>: Downcases each header.
# - <tt>:symbol</tt>: Converts each header to a \Symbol.
#
# There is no such storage structure for write headers.
#
# In order for the parsing methods to access stored converters in non-main-Ractors, the
# storage structure must be made shareable first.
# Therefore, <tt>Ractor.make_shareable(CSV::Converters)</tt> and
# <tt>Ractor.make_shareable(CSV::HeaderConverters)</tt> must be called before the creation
# of Ractors that use the converters stored in these structures. (Since making the storage
# structures shareable involves freezing them, any custom converters that are to be used
# must be added first.)
#
# ===== Converter Lists
#
# A _converter_ _list_ is an \Array that may include any assortment of:
# - Converter procs.
# - Names of stored converters.
# - Nested converter lists.
#
# Examples:
# numeric_converters = [:integer, :float]
# date_converters = [:date, :date_time]
# [numeric_converters, strip_converter]
# [strip_converter, date_converters, :float]
#
# Like a converter proc, a converter list may be named and stored in either
# \CSV::Converters or CSV::HeaderConverters:
# CSV::Converters[:custom] = [strip_converter, date_converters, :float]
# CSV::HeaderConverters[:custom] = [:downcase, :symbol]
#
# There are two built-in converter lists:
# CSV::Converters[:numeric] # => [:integer, :float]
# CSV::Converters[:all] # => [:date_time, :numeric]
#
# ==== Field \Converters
#
# With no conversion, all parsed fields in all rows become Strings:
# string = "foo,0\nbar,1\nbaz,2\n"
# ary = CSV.parse(string)
# ary # => # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# When you specify a field converter, each parsed field is passed to the converter;
# its return value becomes the stored value for the field.
# A converter might, for example, convert an integer embedded in a \String
# into a true \Integer.
# (In fact, that's what built-in field converter +:integer+ does.)
#
# There are three ways to use field \converters.
#
# - Using option {converters}[#class-CSV-label-Option+converters] with a parsing method:
# ary = CSV.parse(string, converters: :integer)
# ary # => [0, 1, 2] # => [["foo", 0], ["bar", 1], ["baz", 2]]
# - Using option {converters}[#class-CSV-label-Option+converters] with a new \CSV instance:
# csv = CSV.new(string, converters: :integer)
# # Field converters in effect:
# csv.converters # => [:integer]
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
# - Using method #convert to add a field converter to a \CSV instance:
# csv = CSV.new(string)
# # Add a converter.
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
#
# Installing a field converter does not affect already-read rows:
# csv = CSV.new(string)
# csv.shift # => ["foo", "0"]
# # Add a converter.
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.read # => [["bar", 1], ["baz", 2]]
#
# There are additional built-in \converters, and custom \converters are also supported.
#
# ===== Built-In Field \Converters
#
# The built-in field converters are in \Hash CSV::Converters:
# - Each key is a field converter name.
# - Each value is one of:
# - A \Proc field converter.
# - An \Array of field converter names.
#
# Display:
# CSV::Converters.each_pair do |name, value|
# if value.kind_of?(Proc)
# p [name, value.class]
# else
# p [name, value]
# end
# end
# Output:
# [:integer, Proc]
# [:float, Proc]
# [:numeric, [:integer, :float]]
# [:date, Proc]
# [:date_time, Proc]
# [:all, [:date_time, :numeric]]
#
# Each of these converters transcodes values to UTF-8 before attempting conversion.
# If a value cannot be transcoded to UTF-8 the conversion will
# fail and the value will remain unconverted.
#
# Converter +:integer+ converts each field that Integer() accepts:
# data = '0,1,2,x'
# # Without the converter
# csv = CSV.parse_line(data)
# csv # => ["0", "1", "2", "x"]
# # With the converter
# csv = CSV.parse_line(data, converters: :integer)
# csv # => [0, 1, 2, "x"]
#
# Converter +:float+ converts each field that Float() accepts:
# data = '1.0,3.14159,x'
# # Without the converter
# csv = CSV.parse_line(data)
# csv # => ["1.0", "3.14159", "x"]
# # With the converter
# csv = CSV.parse_line(data, converters: :float)
# csv # => [1.0, 3.14159, "x"]
#
# Converter +:numeric+ converts with both +:integer+ and +:float+..
#
# Converter +:date+ converts each field that Date::parse accepts:
# data = '2001-02-03,x'
# # Without the converter
# csv = CSV.parse_line(data)
# csv # => ["2001-02-03", "x"]
# # With the converter
# csv = CSV.parse_line(data, converters: :date)
# csv # => [#<Date: 2001-02-03 ((2451944j,0s,0n),+0s,2299161j)>, "x"]
#
# Converter +:date_time+ converts each field that DateTime::parse accepts:
# data = '2020-05-07T14:59:00-05:00,x'
# # Without the converter
# csv = CSV.parse_line(data)
# csv # => ["2020-05-07T14:59:00-05:00", "x"]
# # With the converter
# csv = CSV.parse_line(data, converters: :date_time)
# csv # => [#<DateTime: 2020-05-07T14:59:00-05:00 ((2458977j,71940s,0n),-18000s,2299161j)>, "x"]
#
# Converter +:numeric+ converts with both +:date_time+ and +:numeric+..
#
# As seen above, method #convert adds \converters to a \CSV instance,
# and method #converters returns an \Array of the \converters in effect:
# csv = CSV.new('0,1,2')
# csv.converters # => []
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.convert(:date)
# csv.converters # => [:integer, :date]
#
# ===== Custom Field \Converters
#
# You can define a custom field converter:
# strip_converter = proc {|field| field.strip }
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: strip_converter)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
# You can register the converter in \Converters \Hash,
# which allows you to refer to it by name:
# CSV::Converters[:strip] = strip_converter
# string = " foo , 0 \n bar , 1 \n baz , 2 \n"
# array = CSV.parse(string, converters: :strip)
# array # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# ==== Header \Converters
#
# Header converters operate only on headers (and not on other rows).
#
# There are three ways to use header \converters;
# these examples use built-in header converter +:downcase+,
# which downcases each parsed header.
#
# - Option +header_converters+ with a singleton parsing method:
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :downcase)
# tbl.class # => CSV::Table
# tbl.headers # => ["name", "count"]
#
# - Option +header_converters+ with a new \CSV instance:
# csv = CSV.new(string, header_converters: :downcase)
# # Header converters in effect:
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(string, headers: true)
# tbl.headers # => ["Name", "Count"]
#
# - Method #header_convert adds a header converter to a \CSV instance:
# csv = CSV.new(string)
# # Add a header converter.
# csv.header_convert(:downcase)
# csv.header_converters # => [:downcase]
# tbl = CSV.parse(string, headers: true)
# tbl.headers # => ["Name", "Count"]
#
# ===== Built-In Header \Converters
#
# The built-in header \converters are in \Hash CSV::HeaderConverters.
# The keys there are the names of the \converters:
# CSV::HeaderConverters.keys # => [:downcase, :symbol]
#
# Converter +:downcase+ converts each header by downcasing it:
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :downcase)
# tbl.class # => CSV::Table
# tbl.headers # => ["name", "count"]
#
# Converter +:symbol+ converts each header by making it into a \Symbol:
# string = "Name,Count\nFoo,0\n,Bar,1\nBaz,2"
# tbl = CSV.parse(string, headers: true, header_converters: :symbol)
# tbl.headers # => [:name, :count]
# Details:
# - Strips leading and trailing whitespace.
# - Downcases the header.
# - Replaces embedded spaces with underscores.
# - Removes non-word characters.
# - Makes the string into a \Symbol.
#
# ===== Custom Header \Converters
#
# You can define a custom header converter:
# upcase_converter = proc {|header| header.upcase }
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(string, headers: true, header_converters: upcase_converter)
# table # => #<CSV::Table mode:col_or_row row_count:4>
# table.headers # => ["NAME", "VALUE"]
# You can register the converter in \HeaderConverters \Hash,
# which allows you to refer to it by name:
# CSV::HeaderConverters[:upcase] = upcase_converter
# table = CSV.parse(string, headers: true, header_converters: :upcase)
# table # => #<CSV::Table mode:col_or_row row_count:4>
# table.headers # => ["NAME", "VALUE"]
#
# ===== Write \Converters
#
# When you specify a write converter for generating \CSV,
# each field to be written is passed to the converter;
# its return value becomes the new value for the field.
# A converter might, for example, strip whitespace from a field.
#
# Using no write converter (all fields unmodified):
# output_string = CSV.generate do |csv|
# csv << [' foo ', 0]
# csv << [' bar ', 1]
# csv << [' baz ', 2]
# end
# output_string # => " foo ,0\n bar ,1\n baz ,2\n"
# Using option +write_converters+ with two custom write converters:
# strip_converter = proc {|field| field.respond_to?(:strip) ? field.strip : field }
# upcase_converter = proc {|field| field.respond_to?(:upcase) ? field.upcase : field }
# write_converters = [strip_converter, upcase_converter]
# output_string = CSV.generate(write_converters: write_converters) do |csv|
# csv << [' foo ', 0]
# csv << [' bar ', 1]
# csv << [' baz ', 2]
# end
# output_string # => "FOO,0\nBAR,1\nBAZ,2\n"
#
# === Character Encodings (M17n or Multilingualization)
#
# This new CSV parser is m17n savvy. The parser works in the Encoding of the IO
# or String object being read from or written to. Your data is never transcoded
# (unless you ask Ruby to transcode it for you) and will literally be parsed in
# the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
# Encoding of your data. This is accomplished by transcoding the parser itself
# into your Encoding.
#
# Some transcoding must take place, of course, to accomplish this multiencoding
# support. For example, <tt>:col_sep</tt>, <tt>:row_sep</tt>, and
# <tt>:quote_char</tt> must be transcoded to match your data. Hopefully this
# makes the entire process feel transparent, since CSV's defaults should just
# magically work for your data. However, you can set these values manually in
# the target Encoding to avoid the translation.
#
# It's also important to note that while all of CSV's core parser is now
# Encoding agnostic, some features are not. For example, the built-in
# converters will try to transcode data to UTF-8 before making conversions.
# Again, you can provide custom converters that are aware of your Encodings to
# avoid this translation. It's just too hard for me to support native
# conversions in all of Ruby's Encodings.
#
# Anyway, the practical side of this is simple: make sure IO and String objects
# passed into CSV have the proper Encoding set and everything should just work.
# CSV methods that allow you to open IO objects (CSV::foreach(), CSV::open(),
# CSV::read(), and CSV::readlines()) do allow you to specify the Encoding.
#
# One minor exception comes when generating CSV into a String with an Encoding
# that is not ASCII compatible. There's no existing data for CSV to use to
# prepare itself and thus you will probably need to manually specify the desired
# Encoding for most of those cases. It will try to guess using the fields in a
# row of output though, when using CSV::generate_line() or Array#to_csv().
#
# I try to point out any other Encoding issues in the documentation of methods
# as they come up.
#
# This has been tested to the best of my ability with all non-"dummy" Encodings
# Ruby ships with. However, it is brave new code and may have some bugs.
# Please feel free to {report}[mailto:james@grayproductions.net] any issues you
# find with it.
#
class CSV
# The error thrown when the parser encounters illegal CSV formatting.
class MalformedCSVError < RuntimeError
attr_reader :line_number
alias_method :lineno, :line_number
def initialize(message, line_number)
@line_number = line_number
super("#{message} in line #{line_number}.")
end
end
#
# A FieldInfo Struct contains details about a field's position in the data
# source it was read from. CSV will pass this Struct to some blocks that make
# decisions based on field structure. See CSV.convert_fields() for an
# example.
#
# <b><tt>index</tt></b>:: The zero-based index of the field in its row.
# <b><tt>line</tt></b>:: The line of the data source this row is from.
# <b><tt>header</tt></b>:: The header for the column, when available.
# <b><tt>quoted?</tt></b>:: True or false, whether the original value is quoted or not.
#
FieldInfo = Struct.new(:index, :line, :header, :quoted?)
# A Regexp used to find and convert some common Date formats.
DateMatcher = / \A(?: (\w+,?\s+)?\w+\s+\d{1,2},?\s+\d{2,4} |
\d{4}-\d{2}-\d{2} )\z /x
# A Regexp used to find and convert some common DateTime formats.
DateTimeMatcher =
/ \A(?: (\w+,?\s+)?\w+\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2},?\s+\d{2,4} |
# ISO-8601 and RFC-3339 (space instead of T) recognized by DateTime.parse
\d{4}-\d{2}-\d{2}
(?:[T\s]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?(?:[+-]\d{2}(?::\d{2})|Z)?)?)?
)\z /x
# The encoding used by all converters.
ConverterEncoding = Encoding.find("UTF-8")
# A \Hash containing the names and \Procs for the built-in field converters.
# See {Built-In Field Converters}[#class-CSV-label-Built-In+Field+Converters].
#
# This \Hash is intentionally left unfrozen, and may be extended with
# custom field converters.
# See {Custom Field Converters}[#class-CSV-label-Custom+Field+Converters].
Converters = {
integer: lambda { |f|
Integer(f.encode(ConverterEncoding)) rescue f
},
float: lambda { |f|
Float(f.encode(ConverterEncoding)) rescue f
},
numeric: [:integer, :float],
date: lambda { |f|
begin
e = f.encode(ConverterEncoding)
e.match?(DateMatcher) ? Date.parse(e) : f
rescue # encoding conversion or date parse errors
f
end
},
date_time: lambda { |f|
begin
e = f.encode(ConverterEncoding)
e.match?(DateTimeMatcher) ? DateTime.parse(e) : f
rescue # encoding conversion or date parse errors
f
end
},
all: [:date_time, :numeric],
}
# A \Hash containing the names and \Procs for the built-in header converters.
# See {Built-In Header Converters}[#class-CSV-label-Built-In+Header+Converters].
#
# This \Hash is intentionally left unfrozen, and may be extended with
# custom field converters.
# See {Custom Header Converters}[#class-CSV-label-Custom+Header+Converters].
HeaderConverters = {
downcase: lambda { |h| h.encode(ConverterEncoding).downcase },
symbol: lambda { |h|
h.encode(ConverterEncoding).downcase.gsub(/[^\s\w]+/, "").strip.
gsub(/\s+/, "_").to_sym
},
symbol_raw: lambda { |h| h.encode(ConverterEncoding).to_sym }
}
# Default values for method options.
DEFAULT_OPTIONS = {
# For both parsing and generating.
col_sep: ",",
row_sep: :auto,
quote_char: '"',
# For parsing.
field_size_limit: nil,
max_field_size: nil,
converters: nil,
unconverted_fields: nil,
headers: false,
return_headers: false,
header_converters: nil,
skip_blanks: false,
skip_lines: nil,
liberal_parsing: false,
nil_value: nil,
empty_value: "",
strip: false,
# For generating.
write_headers: nil,
quote_empty: true,
force_quotes: false,
write_converters: nil,
write_nil_value: nil,
write_empty_value: "",
}.freeze
class << self
# :call-seq:
# instance(string, **options)
# instance(io = $stdout, **options)
# instance(string, **options) {|csv| ... }
# instance(io = $stdout, **options) {|csv| ... }
#
# Creates or retrieves cached \CSV objects.
# For arguments and options, see CSV.new.
#
# This API is not Ractor-safe.
#
# ---
#
# With no block given, returns a \CSV object.
#
# The first call to +instance+ creates and caches a \CSV object:
# s0 = 's0'
# csv0 = CSV.instance(s0)
# csv0.class # => CSV
#
# Subsequent calls to +instance+ with that _same_ +string+ or +io+
# retrieve that same cached object:
# csv1 = CSV.instance(s0)
# csv1.class # => CSV
# csv1.equal?(csv0) # => true # Same CSV object
#
# A subsequent call to +instance+ with a _different_ +string+ or +io+
# creates and caches a _different_ \CSV object.
# s1 = 's1'
# csv2 = CSV.instance(s1)
# csv2.equal?(csv0) # => false # Different CSV object
#
# All the cached objects remains available:
# csv3 = CSV.instance(s0)
# csv3.equal?(csv0) # true # Same CSV object
# csv4 = CSV.instance(s1)
# csv4.equal?(csv2) # true # Same CSV object
#
# ---
#
# When a block is given, calls the block with the created or retrieved
# \CSV object; returns the block's return value:
# CSV.instance(s0) {|csv| :foo } # => :foo
def instance(data = $stdout, **options)
# create a _signature_ for this method call, data object and options
sig = [data.object_id] +
options.values_at(*DEFAULT_OPTIONS.keys)
# fetch or create the instance for this signature
@@instances ||= Hash.new
instance = (@@instances[sig] ||= new(data, **options))
if block_given?
yield instance # run block, if given, returning result
else
instance # or return the instance
end
end
# :call-seq:
# filter(in_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
# filter(in_string_or_io, out_string_or_io, **options) {|row| ... } -> array_of_arrays or csv_table
# filter(**options) {|row| ... } -> array_of_arrays or csv_table
#
# - Parses \CSV from a source (\String, \IO stream, or ARGF).
# - Calls the given block with each parsed row:
# - Without headers, each row is an \Array.
# - With headers, each row is a CSV::Row.
# - Generates \CSV to an output (\String, \IO stream, or STDOUT).
# - Returns the parsed source:
# - Without headers, an \Array of \Arrays.
# - With headers, a CSV::Table.
#
# When +in_string_or_io+ is given, but not +out_string_or_io+,
# parses from the given +in_string_or_io+
# and generates to STDOUT.
#
# \String input without headers:
#
# in_string = "foo,0\nbar,1\nbaz,2"
# CSV.filter(in_string) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
#
# Output (to STDOUT):
#
# FOO,0
# BAR,-1
# BAZ,-2
#
# \String input with headers:
#
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
# CSV.filter(in_string, headers: true) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end # => #<CSV::Table mode:col_or_row row_count:4>
#
# Output (to STDOUT):
#
# Name,Value
# FOO,0
# BAR,-1
# BAZ,-2
#
# \IO stream input without headers:
#
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
# File.open('t.csv') do |in_io|
# CSV.filter(in_io) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
#
# Output (to STDOUT):
#
# FOO,0
# BAR,-1
# BAZ,-2
#
# \IO stream input with headers:
#
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
# File.open('t.csv') do |in_io|
# CSV.filter(in_io, headers: true) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# end # => #<CSV::Table mode:col_or_row row_count:4>
#
# Output (to STDOUT):
#
# Name,Value
# FOO,0
# BAR,-1
# BAZ,-2
#
# When both +in_string_or_io+ and +out_string_or_io+ are given,
# parses from +in_string_or_io+ and generates to +out_string_or_io+.
#
# \String output without headers:
#
# in_string = "foo,0\nbar,1\nbaz,2"
# out_string = ''
# CSV.filter(in_string, out_string) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
# out_string # => "FOO,0\nBAR,-1\nBAZ,-2\n"
#
# \String output with headers:
#
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
# out_string = ''
# CSV.filter(in_string, out_string, headers: true) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end # => #<CSV::Table mode:col_or_row row_count:4>
# out_string # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
#
# \IO stream output without headers:
#
# in_string = "foo,0\nbar,1\nbaz,2"
# File.open('t.csv', 'w') do |out_io|
# CSV.filter(in_string, out_io) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# end # => [["FOO", 0], ["BAR", -1], ["BAZ", -2]]
# File.read('t.csv') # => "FOO,0\nBAR,-1\nBAZ,-2\n"
#
# \IO stream output with headers:
#
# in_string = "Name,Value\nfoo,0\nbar,1\nbaz,2"
# File.open('t.csv', 'w') do |out_io|
# CSV.filter(in_string, out_io, headers: true) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# end # => #<CSV::Table mode:col_or_row row_count:4>
# File.read('t.csv') # => "Name,Value\nFOO,0\nBAR,-1\nBAZ,-2\n"
#
# When neither +in_string_or_io+ nor +out_string_or_io+ given,
# parses from {ARGF}[rdoc-ref:ARGF]
# and generates to STDOUT.
#
# Without headers:
#
# # Put Ruby code into a file.
# ruby = <<-EOT
# require 'csv'
# CSV.filter do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# EOT
# File.write('t.rb', ruby)
# # Put some CSV into a file.
# File.write('t.csv', "foo,0\nbar,1\nbaz,2")
# # Run the Ruby code with CSV filename as argument.
# system(Gem.ruby, "t.rb", "t.csv")
#
# Output (to STDOUT):
#
# FOO,0
# BAR,-1
# BAZ,-2
#
# With headers:
#
# # Put Ruby code into a file.
# ruby = <<-EOT
# require 'csv'
# CSV.filter(headers: true) do |row|
# row[0].upcase!
# row[1] = - row[1].to_i
# end
# EOT
# File.write('t.rb', ruby)
# # Put some CSV into a file.
# File.write('t.csv', "Name,Value\nfoo,0\nbar,1\nbaz,2")
# # Run the Ruby code with CSV filename as argument.
# system(Gem.ruby, "t.rb", "t.csv")
#
# Output (to STDOUT):
#
# Name,Value
# FOO,0
# BAR,-1
# BAZ,-2
#
# Arguments:
#
# * Argument +in_string_or_io+ must be a \String or an \IO stream.
# * Argument +out_string_or_io+ must be a \String or an \IO stream.
# * Arguments <tt>**options</tt> must be keyword options.
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
def filter(input=nil, output=nil, **options)
# parse options for input, output, or both
in_options, out_options = Hash.new, {row_sep: InputRecordSeparator.value}
options.each do |key, value|
case key
when /\Ain(?:put)?_(.+)\Z/
in_options[$1.to_sym] = value
when /\Aout(?:put)?_(.+)\Z/
out_options[$1.to_sym] = value
else
in_options[key] = value
out_options[key] = value
end
end
# build input and output wrappers
input = new(input || ARGF, **in_options)
output = new(output || $stdout, **out_options)
# process headers
need_manual_header_output =
(in_options[:headers] and
out_options[:headers] == true and
out_options[:write_headers])
if need_manual_header_output
first_row = input.shift
if first_row
if first_row.is_a?(Row)
headers = first_row.headers
yield headers
output << headers
end
yield first_row
output << first_row
end
end
# read, yield, write
input.each do |row|
yield row
output << row
end
end
#
# :call-seq:
# foreach(path_or_io, mode='r', **options) {|row| ... )
# foreach(path_or_io, mode='r', **options) -> new_enumerator
#
# Calls the block with each row read from source +path_or_io+.
#
# \Path input without headers:
#
# string = "foo,0\nbar,1\nbaz,2\n"
# in_path = 't.csv'
# File.write(in_path, string)
# CSV.foreach(in_path) {|row| p row }
#
# Output:
#
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# \Path input with headers:
#
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# in_path = 't.csv'
# File.write(in_path, string)
# CSV.foreach(in_path, headers: true) {|row| p row }
#
# Output:
#
# <CSV::Row "Name":"foo" "Value":"0">
# <CSV::Row "Name":"bar" "Value":"1">
# <CSV::Row "Name":"baz" "Value":"2">
#
# \IO stream input without headers:
#
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# File.open('t.csv') do |in_io|
# CSV.foreach(in_io) {|row| p row }
# end
#
# Output:
#
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# \IO stream input with headers:
#
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# File.open('t.csv') do |in_io|
# CSV.foreach(in_io, headers: true) {|row| p row }
# end
#
# Output:
#
# <CSV::Row "Name":"foo" "Value":"0">
# <CSV::Row "Name":"bar" "Value":"1">
# <CSV::Row "Name":"baz" "Value":"2">
#
# With no block given, returns an \Enumerator:
#
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.foreach(path) # => #<Enumerator: CSV:foreach("t.csv", "r")>
#
# Arguments:
# * Argument +path_or_io+ must be a file path or an \IO stream.
# * Argument +mode+, if given, must be a \File mode
# See {Open Mode}[https://ruby-doc.org/core/IO.html#method-c-new-label-Open+Mode].
# * Arguments <tt>**options</tt> must be keyword options.
# See {Options for Parsing}[#class-CSV-label-Options+for+Parsing].
# * This method optionally accepts an additional <tt>:encoding</tt> option
# that you can use to specify the Encoding of the data read from +path+ or +io+.
# You must provide this unless your data is in the encoding
# given by <tt>Encoding::default_external</tt>.
# Parsing will use this to determine how to parse the data.
# You may provide a second Encoding to
# have the data transcoded as it is read. For example,
# encoding: 'UTF-32BE:UTF-8'
# would read +UTF-32BE+ data from the file
# but transcode it to +UTF-8+ before parsing.
def foreach(path, mode="r", **options, &block)
return to_enum(__method__, path, mode, **options) unless block_given?
open(path, mode, **options) do |csv|
csv.each(&block)
end
end
#
# :call-seq:
# generate(csv_string, **options) {|csv| ... }
# generate(**options) {|csv| ... }
#
# * Argument +csv_string+, if given, must be a \String object;
# defaults to a new empty \String.
# * Arguments +options+, if given, should be generating options.
# See {Options for Generating}[#class-CSV-label-Options+for+Generating].
#
# ---
#
# Creates a new \CSV object via <tt>CSV.new(csv_string, **options)</tt>;
# calls the block with the \CSV object, which the block may modify;
# returns the \String generated from the \CSV object.
#
# Note that a passed \String *is* modified by this method.
# Pass <tt>csv_string</tt>.dup if the \String must be preserved.
#
# This method has one additional option: <tt>:encoding</tt>,
# which sets the base Encoding for the output if no no +str+ is specified.
# CSV needs this hint if you plan to output non-ASCII compatible data.
#
# ---
#
# Add lines:
# input_string = "foo,0\nbar,1\nbaz,2\n"
# output_string = CSV.generate(input_string) do |csv|
# csv << ['bat', 3]
# csv << ['bam', 4]
# end
# output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
# input_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
# output_string.equal?(input_string) # => true # Same string, modified
#
# Add lines into new string, preserving old string:
# input_string = "foo,0\nbar,1\nbaz,2\n"
# output_string = CSV.generate(input_string.dup) do |csv|
# csv << ['bat', 3]
# csv << ['bam', 4]
# end
# output_string # => "foo,0\nbar,1\nbaz,2\nbat,3\nbam,4\n"
# input_string # => "foo,0\nbar,1\nbaz,2\n"
# output_string.equal?(input_string) # => false # Different strings
#
# Create lines from nothing:
# output_string = CSV.generate do |csv|
# csv << ['foo', 0]
# csv << ['bar', 1]
# csv << ['baz', 2]
# end
# output_string # => "foo,0\nbar,1\nbaz,2\n"
#
# ---
#
# Raises an exception if +csv_string+ is not a \String object:
# # Raises TypeError (no implicit conversion of Integer into String)
# CSV.generate(0)
#
def generate(str=nil, **options)
encoding = options[:encoding]
# add a default empty String, if none was given
if str
str = StringIO.new(str)
str.seek(0, IO::SEEK_END)
str.set_encoding(encoding) if encoding
else
str = +""
str.force_encoding(encoding) if encoding
end
csv = new(str, **options) # wrap
yield csv # yield for appending
csv.string # return final String
end
# :call-seq:
# CSV.generate_line(ary)
# CSV.generate_line(ary, **options)
#
# Returns the \String created by generating \CSV from +ary+
# using the specified +options+.
#
# Argument +ary+ must be an \Array.
#
# Special options:
# * Option <tt>:row_sep</tt> defaults to <tt>"\n"> on Ruby 3.0 or later
# and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
# $INPUT_RECORD_SEPARATOR # => "\n"
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
# Encoding for the output. This method will try to guess your Encoding from
# the first non-+nil+ field in +row+, if possible, but you may need to use
# this parameter as a backup plan.
#
# For other +options+,
# see {Options for Generating}[#class-CSV-label-Options+for+Generating].
#
# ---
#
# Returns the \String generated from an \Array:
# CSV.generate_line(['foo', '0']) # => "foo,0\n"
#
# ---
#
# Raises an exception if +ary+ is not an \Array:
# # Raises NoMethodError (undefined method `find' for :foo:Symbol)
# CSV.generate_line(:foo)
#
def generate_line(row, **options)
options = {row_sep: InputRecordSeparator.value}.merge(options)
str = +""
if options[:encoding]
str.force_encoding(options[:encoding])
else
fallback_encoding = nil
output_encoding = nil
row.each do |field|
next unless field.is_a?(String)
fallback_encoding ||= field.encoding
next if field.ascii_only?
output_encoding = field.encoding
break
end
output_encoding ||= fallback_encoding
if output_encoding
str.force_encoding(output_encoding)
end
end
(new(str, **options) << row).string
end
# :call-seq:
# CSV.generate_lines(rows)
# CSV.generate_lines(rows, **options)
#
# Returns the \String created by generating \CSV from
# using the specified +options+.
#
# Argument +rows+ must be an \Array of row. Row is \Array of \String or \CSV::Row.
#
# Special options:
# * Option <tt>:row_sep</tt> defaults to <tt>"\n"</tt> on Ruby 3.0 or later
# and <tt>$INPUT_RECORD_SEPARATOR</tt> (<tt>$/</tt>) otherwise.:
# $INPUT_RECORD_SEPARATOR # => "\n"
# * This method accepts an additional option, <tt>:encoding</tt>, which sets the base
# Encoding for the output. This method will try to guess your Encoding from
# the first non-+nil+ field in +row+, if possible, but you may need to use
# this parameter as a backup plan.
#
# For other +options+,
# see {Options for Generating}[#class-CSV-label-Options+for+Generating].
#
# ---
#
# Returns the \String generated from an
# CSV.generate_lines([['foo', '0'], ['bar', '1'], ['baz', '2']]) # => "foo,0\nbar,1\nbaz,2\n"
#
# ---
#
# Raises an exception
# # Raises NoMethodError (undefined method `each' for :foo:Symbol)
# CSV.generate_lines(:foo)
#
def generate_lines(rows, **options)
self.generate(**options) do |csv|
rows.each do |row|
csv << row
end
end
end
#
# :call-seq:
# open(file_path, mode = "rb", **options ) -> new_csv
# open(io, mode = "rb", **options ) -> new_csv
# open(file_path, mode = "rb", **options ) { |csv| ... } -> object
# open(io, mode = "rb", **options ) { |csv| ... } -> object
#
# possible options elements:
# keyword form:
# :invalid => nil # raise error on invalid byte sequence (default)
# :invalid => :replace # replace invalid byte sequence
# :undef => :replace # replace undefined conversion
# :replace => string # replacement string ("?" or "\uFFFD" if not specified)
#
# * Argument +path+, if given, must be the path to a file.
# :include: ../doc/csv/arguments/io.rdoc
# * Argument +mode+, if given, must be a \File mode
# See {Open Mode}[IO.html#method-c-new-label-Open+Mode].
# * Arguments <tt>**options</tt> must be keyword options.
# See {Options for Generating}[#class-CSV-label-Options+for+Generating].
# * This method optionally accepts an additional <tt>:encoding</tt> option
# that you can use to specify the Encoding of the data read from +path+ or +io+.
# You must provide this unless your data is in the encoding
# given by <tt>Encoding::default_external</tt>.
# Parsing will use this to determine how to parse the data.
# You may provide a second Encoding to
# have the data transcoded as it is read. For example,
# encoding: 'UTF-32BE:UTF-8'
# would read +UTF-32BE+ data from the file
# but transcode it to +UTF-8+ before parsing.
#
# ---
#
# These examples assume prior execution of:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# ---
#
# With no block given, returns a new \CSV object.
#
# Create a \CSV object using a file path:
# csv = CSV.open(path)
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# Create a \CSV object using an open \File:
# csv = CSV.open(File.open(path))
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# ---
#
# With a block given, calls the block with the created \CSV object;
# returns the block's return value:
#
# Using a file path:
# csv = CSV.open(path) {|csv| p csv}
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
# Output:
# #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# Using an open \File:
# csv = CSV.open(File.open(path)) {|csv| p csv}
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
# Output:
# #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# ---
#
# Raises an exception if the argument is not a \String object or \IO object:
# # Raises TypeError (no implicit conversion of Symbol into String)
# CSV.open(:foo)
def open(filename, mode="r", **options)
# wrap a File opened with the remaining +args+ with no newline
# decorator
file_opts = options.dup
unless file_opts.key?(:newline)
file_opts[:universal_newline] ||= false
end
options.delete(:invalid)
options.delete(:undef)
options.delete(:replace)
options.delete_if {|k, _| /newline\z/.match?(k)}
begin
f = File.open(filename, mode, **file_opts)
rescue ArgumentError => e
raise unless /needs binmode/.match?(e.message) and mode == "r"
mode = "rb"
file_opts = {encoding: Encoding.default_external}.merge(file_opts)
retry
end
begin
csv = new(f, **options)
rescue Exception
f.close
raise
end
# handle blocks like Ruby's open(), not like the CSV library
if block_given?
begin
yield csv
ensure
csv.close
end
else
csv
end
end
#
# :call-seq:
# parse(string) -> array_of_arrays
# parse(io) -> array_of_arrays
# parse(string, headers: ..., **options) -> csv_table
# parse(io, headers: ..., **options) -> csv_table
# parse(string, **options) {|row| ... }
# parse(io, **options) {|row| ... }
#
# Parses +string+ or +io+ using the specified +options+.
#
# - Argument +string+ should be a \String object;
# it will be put into a new StringIO object positioned at the beginning.
# :include: ../doc/csv/arguments/io.rdoc
# - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
#
# ====== Without Option +headers+
#
# Without {option +headers+}[#class-CSV-label-Option+headers] case.
#
# These examples assume prior execution of:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# ---
#
# With no block given, returns an \Array of Arrays formed from the source.
#
# Parse a \String:
# a_of_a = CSV.parse(string)
# a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# Parse an open \File:
# a_of_a = File.open(path) do |file|
# CSV.parse(file)
# end
# a_of_a # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# ---
#
# With a block given, calls the block with each parsed row:
#
# Parse a \String:
# CSV.parse(string) {|row| p row }
#
# Output:
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# Parse an open \File:
# File.open(path) do |file|
# CSV.parse(file) {|row| p row }
# end
#
# Output:
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# ====== With Option +headers+
#
# With {option +headers+}[#class-CSV-label-Option+headers] case.
#
# These examples assume prior execution of:
# string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# ---
#
# With no block given, returns a CSV::Table object formed from the source.
#
# Parse a \String:
# csv_table = CSV.parse(string, headers: ['Name', 'Count'])
# csv_table # => #<CSV::Table mode:col_or_row row_count:5>
#
# Parse an open \File:
# csv_table = File.open(path) do |file|
# CSV.parse(file, headers: ['Name', 'Count'])
# end
# csv_table # => #<CSV::Table mode:col_or_row row_count:4>
#
# ---
#
# With a block given, calls the block with each parsed row,
# which has been formed into a CSV::Row object:
#
# Parse a \String:
# CSV.parse(string, headers: ['Name', 'Count']) {|row| p row }
#
# Output:
# # <CSV::Row "Name":"foo" "Count":"0">
# # <CSV::Row "Name":"bar" "Count":"1">
# # <CSV::Row "Name":"baz" "Count":"2">
#
# Parse an open \File:
# File.open(path) do |file|
# CSV.parse(file, headers: ['Name', 'Count']) {|row| p row }
# end
#
# Output:
# # <CSV::Row "Name":"foo" "Count":"0">
# # <CSV::Row "Name":"bar" "Count":"1">
# # <CSV::Row "Name":"baz" "Count":"2">
#
# ---
#
# Raises an exception if the argument is not a \String object or \IO object:
# # Raises NoMethodError (undefined method `close' for :foo:Symbol)
# CSV.parse(:foo)
def parse(str, **options, &block)
csv = new(str, **options)
return csv.each(&block) if block_given?
# slurp contents, if no block is given
begin
csv.read
ensure
csv.close
end
end
# :call-seq:
# CSV.parse_line(string) -> new_array or nil
# CSV.parse_line(io) -> new_array or nil
# CSV.parse_line(string, **options) -> new_array or nil
# CSV.parse_line(io, **options) -> new_array or nil
# CSV.parse_line(string, headers: true, **options) -> csv_row or nil
# CSV.parse_line(io, headers: true, **options) -> csv_row or nil
#
# Returns the data created by parsing the first line of +string+ or +io+
# using the specified +options+.
#
# - Argument +string+ should be a \String object;
# it will be put into a new StringIO object positioned at the beginning.
# :include: ../doc/csv/arguments/io.rdoc
# - Argument +options+: see {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
#
# ====== Without Option +headers+
#
# Without option +headers+, returns the first row as a new \Array.
#
# These examples assume prior execution of:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Parse the first line from a \String object:
# CSV.parse_line(string) # => ["foo", "0"]
#
# Parse the first line from a File object:
# File.open(path) do |file|
# CSV.parse_line(file) # => ["foo", "0"]
# end # => ["foo", "0"]
#
# Returns +nil+ if the argument is an empty \String:
# CSV.parse_line('') # => nil
#
# ====== With Option +headers+
#
# With {option +headers+}[#class-CSV-label-Option+headers],
# returns the first row as a CSV::Row object.
#
# These examples assume prior execution of:
# string = "Name,Count\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Parse the first line from a \String object:
# CSV.parse_line(string, headers: true) # => #<CSV::Row "Name":"foo" "Count":"0">
#
# Parse the first line from a File object:
# File.open(path) do |file|
# CSV.parse_line(file, headers: true)
# end # => #<CSV::Row "Name":"foo" "Count":"0">
#
# ---
#
# Raises an exception if the argument is +nil+:
# # Raises ArgumentError (Cannot parse nil as CSV):
# CSV.parse_line(nil)
#
def parse_line(line, **options)
new(line, **options).each.first
end
#
# :call-seq:
# read(source, **options) -> array_of_arrays
# read(source, headers: true, **options) -> csv_table
#
# Opens the given +source+ with the given +options+ (see CSV.open),
# reads the source (see CSV#read), and returns the result,
# which will be either an \Array of Arrays or a CSV::Table.
#
# Without headers:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.read(path) # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# With headers:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.read(path, headers: true) # => #<CSV::Table mode:col_or_row row_count:4>
def read(path, **options)
open(path, **options) { |csv| csv.read }
end
# :call-seq:
# CSV.readlines(source, **options)
#
# Alias for CSV.read.
def readlines(path, **options)
read(path, **options)
end
# :call-seq:
# CSV.table(source, **options)
#
# Calls CSV.read with +source+, +options+, and certain default options:
# - +headers+: +true+
# - +converters+: +:numeric+
# - +header_converters+: +:symbol+
#
# Returns a CSV::Table object.
#
# Example:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.table(path) # => #<CSV::Table mode:col_or_row row_count:4>
def table(path, **options)
default_options = {
headers: true,
converters: :numeric,
header_converters: :symbol,
}
options = default_options.merge(options)
read(path, **options)
end
end
# :call-seq:
# CSV.new(string)
# CSV.new(io)
# CSV.new(string, **options)
# CSV.new(io, **options)
#
# Returns the new \CSV object created using +string+ or +io+
# and the specified +options+.
#
# - Argument +string+ should be a \String object;
# it will be put into a new StringIO object positioned at the beginning.
# :include: ../doc/csv/arguments/io.rdoc
# - Argument +options+: See:
# * {Options for Parsing}[#class-CSV-label-Options+for+Parsing]
# * {Options for Generating}[#class-CSV-label-Options+for+Generating]
# For performance reasons, the options cannot be overridden
# in a \CSV object, so those specified here will endure.
#
# In addition to the \CSV instance methods, several \IO methods are delegated.
# See {Delegated Methods}[#class-CSV-label-Delegated+Methods].
#
# ---
#
# Create a \CSV object from a \String object:
# csv = CSV.new('foo,0')
# csv # => #<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# Create a \CSV object from a \File object:
# File.write('t.csv', 'foo,0')
# csv = CSV.new(File.open('t.csv'))
# csv # => #<CSV io_type:File io_path:"t.csv" encoding:UTF-8 lineno:0 col_sep:"," row_sep:"\n" quote_char:"\"">
#
# ---
#
# Raises an exception if the argument is +nil+:
# # Raises ArgumentError (Cannot parse nil as CSV):
# CSV.new(nil)
#
def initialize(data,
col_sep: ",",
row_sep: :auto,
quote_char: '"',
field_size_limit: nil,
max_field_size: nil,
converters: nil,
unconverted_fields: nil,
headers: false,
return_headers: false,
write_headers: nil,
header_converters: nil,
skip_blanks: false,
force_quotes: false,
skip_lines: nil,
liberal_parsing: false,
internal_encoding: nil,
external_encoding: nil,
encoding: nil,
nil_value: nil,
empty_value: "",
strip: false,
quote_empty: true,
write_converters: nil,
write_nil_value: nil,
write_empty_value: "")
raise ArgumentError.new("Cannot parse nil as CSV") if data.nil?
if data.is_a?(String)
if encoding
if encoding.is_a?(String)
data_external_encoding, data_internal_encoding = encoding.split(":", 2)
if data_internal_encoding
data = data.encode(data_internal_encoding, data_external_encoding)
else
data = data.dup.force_encoding(data_external_encoding)
end
else
data = data.dup.force_encoding(encoding)
end
end
@io = StringIO.new(data)
else
@io = data
end
@encoding = determine_encoding(encoding, internal_encoding)
@base_fields_converter_options = {
nil_value: nil_value,
empty_value: empty_value,
}
@write_fields_converter_options = {
nil_value: write_nil_value,
empty_value: write_empty_value,
}
@initial_converters = converters
@initial_header_converters = header_converters
@initial_write_converters = write_converters
if max_field_size.nil? and field_size_limit
max_field_size = field_size_limit - 1
end
@parser_options = {
column_separator: col_sep,
row_separator: row_sep,
quote_character: quote_char,
max_field_size: max_field_size,
unconverted_fields: unconverted_fields,
headers: headers,
return_headers: return_headers,
skip_blanks: skip_blanks,
skip_lines: skip_lines,
liberal_parsing: liberal_parsing,
encoding: @encoding,
nil_value: nil_value,
empty_value: empty_value,
strip: strip,
}
@parser = nil
@parser_enumerator = nil
@eof_error = nil
@writer_options = {
encoding: @encoding,
force_encoding: (not encoding.nil?),
force_quotes: force_quotes,
headers: headers,
write_headers: write_headers,
column_separator: col_sep,
row_separator: row_sep,
quote_character: quote_char,
quote_empty: quote_empty,
}
@writer = nil
writer if @writer_options[:write_headers]
end
# :call-seq:
# csv.col_sep -> string
#
# Returns the encoded column separator; used for parsing and writing;
# see {Option +col_sep+}[#class-CSV-label-Option+col_sep]:
# CSV.new('').col_sep # => ","
def col_sep
parser.column_separator
end
# :call-seq:
# csv.row_sep -> string
#
# Returns the encoded row separator; used for parsing and writing;
# see {Option +row_sep+}[#class-CSV-label-Option+row_sep]:
# CSV.new('').row_sep # => "\n"
def row_sep
parser.row_separator
end
# :call-seq:
# csv.quote_char -> character
#
# Returns the encoded quote character; used for parsing and writing;
# see {Option +quote_char+}[#class-CSV-label-Option+quote_char]:
# CSV.new('').quote_char # => "\""
def quote_char
parser.quote_character
end
# :call-seq:
# csv.field_size_limit -> integer or nil
#
# Returns the limit for field size; used for parsing;
# see {Option +field_size_limit+}[#class-CSV-label-Option+field_size_limit]:
# CSV.new('').field_size_limit # => nil
#
# Deprecated since 3.2.3. Use +max_field_size+ instead.
def field_size_limit
parser.field_size_limit
end
# :call-seq:
# csv.max_field_size -> integer or nil
#
# Returns the limit for field size; used for parsing;
# see {Option +max_field_size+}[#class-CSV-label-Option+max_field_size]:
# CSV.new('').max_field_size # => nil
#
# Since 3.2.3.
def max_field_size
parser.max_field_size
end
# :call-seq:
# csv.skip_lines -> regexp or nil
#
# Returns the \Regexp used to identify comment lines; used for parsing;
# see {Option +skip_lines+}[#class-CSV-label-Option+skip_lines]:
# CSV.new('').skip_lines # => nil
def skip_lines
parser.skip_lines
end
# :call-seq:
# csv.converters -> array
#
# Returns an \Array containing field converters;
# see {Field Converters}[#class-CSV-label-Field+Converters]:
# csv = CSV.new('')
# csv.converters # => []
# csv.convert(:integer)
# csv.converters # => [:integer]
# csv.convert(proc {|x| x.to_s })
# csv.converters
#
# Notes that you need to call
# +Ractor.make_shareable(CSV::Converters)+ on the main Ractor to use
# this method.
def converters
parser_fields_converter.map do |converter|
name = Converters.rassoc(converter)
name ? name.first : converter
end
end
# :call-seq:
# csv.unconverted_fields? -> object
#
# Returns the value that determines whether unconverted fields are to be
# available; used for parsing;
# see {Option +unconverted_fields+}[#class-CSV-label-Option+unconverted_fields]:
# CSV.new('').unconverted_fields? # => nil
def unconverted_fields?
parser.unconverted_fields?
end
# :call-seq:
# csv.headers -> object
#
# Returns the value that determines whether headers are used; used for parsing;
# see {Option +headers+}[#class-CSV-label-Option+headers]:
# CSV.new('').headers # => nil
def headers
if @writer
@writer.headers
else
parsed_headers = parser.headers
return parsed_headers if parsed_headers
raw_headers = @parser_options[:headers]
raw_headers = nil if raw_headers == false
raw_headers
end
end
# :call-seq:
# csv.return_headers? -> true or false
#
# Returns the value that determines whether headers are to be returned; used for parsing;
# see {Option +return_headers+}[#class-CSV-label-Option+return_headers]:
# CSV.new('').return_headers? # => false
def return_headers?
parser.return_headers?
end
# :call-seq:
# csv.write_headers? -> true or false
#
# Returns the value that determines whether headers are to be written; used for generating;
# see {Option +write_headers+}[#class-CSV-label-Option+write_headers]:
# CSV.new('').write_headers? # => nil
def write_headers?
@writer_options[:write_headers]
end
# :call-seq:
# csv.header_converters -> array
#
# Returns an \Array containing header converters; used for parsing;
# see {Header Converters}[#class-CSV-label-Header+Converters]:
# CSV.new('').header_converters # => []
#
# Notes that you need to call
# +Ractor.make_shareable(CSV::HeaderConverters)+ on the main Ractor
# to use this method.
def header_converters
header_fields_converter.map do |converter|
name = HeaderConverters.rassoc(converter)
name ? name.first : converter
end
end
# :call-seq:
# csv.skip_blanks? -> true or false
#
# Returns the value that determines whether blank lines are to be ignored; used for parsing;
# see {Option +skip_blanks+}[#class-CSV-label-Option+skip_blanks]:
# CSV.new('').skip_blanks? # => false
def skip_blanks?
parser.skip_blanks?
end
# :call-seq:
# csv.force_quotes? -> true or false
#
# Returns the value that determines whether all output fields are to be quoted;
# used for generating;
# see {Option +force_quotes+}[#class-CSV-label-Option+force_quotes]:
# CSV.new('').force_quotes? # => false
def force_quotes?
@writer_options[:force_quotes]
end
# :call-seq:
# csv.liberal_parsing? -> true or false
#
# Returns the value that determines whether illegal input is to be handled; used for parsing;
# see {Option +liberal_parsing+}[#class-CSV-label-Option+liberal_parsing]:
# CSV.new('').liberal_parsing? # => false
def liberal_parsing?
parser.liberal_parsing?
end
# :call-seq:
# csv.encoding -> encoding
#
# Returns the encoding used for parsing and generating;
# see {Character Encodings (M17n or Multilingualization)}[#class-CSV-label-Character+Encodings+-28M17n+or+Multilingualization-29]:
# CSV.new('').encoding # => #<Encoding:UTF-8>
attr_reader :encoding
# :call-seq:
# csv.line_no -> integer
#
# Returns the count of the rows parsed or generated.
#
# Parsing:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.open(path) do |csv|
# csv.each do |row|
# p [csv.lineno, row]
# end
# end
# Output:
# [1, ["foo", "0"]]
# [2, ["bar", "1"]]
# [3, ["baz", "2"]]
#
# Generating:
# CSV.generate do |csv|
# p csv.lineno; csv << ['foo', 0]
# p csv.lineno; csv << ['bar', 1]
# p csv.lineno; csv << ['baz', 2]
# end
# Output:
# 0
# 1
# 2
def lineno
if @writer
@writer.lineno
else
parser.lineno
end
end
# :call-seq:
# csv.line -> array
#
# Returns the line most recently read:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# CSV.open(path) do |csv|
# csv.each do |row|
# p [csv.lineno, csv.line]
# end
# end
# Output:
# [1, "foo,0\n"]
# [2, "bar,1\n"]
# [3, "baz,2\n"]
def line
parser.line
end
### IO and StringIO Delegation ###
extend Forwardable
def_delegators :@io, :binmode, :close, :close_read, :close_write,
:closed?, :external_encoding, :fcntl,
:fileno, :flush, :fsync, :internal_encoding,
:isatty, :pid, :pos, :pos=, :reopen,
:seek, :string, :sync, :sync=, :tell,
:truncate, :tty?
def binmode?
if @io.respond_to?(:binmode?)
@io.binmode?
else
false
end
end
def flock(*args)
raise NotImplementedError unless @io.respond_to?(:flock)
@io.flock(*args)
end
def ioctl(*args)
raise NotImplementedError unless @io.respond_to?(:ioctl)
@io.ioctl(*args)
end
def path
@io.path if @io.respond_to?(:path)
end
def stat(*args)
raise NotImplementedError unless @io.respond_to?(:stat)
@io.stat(*args)
end
def to_i
raise NotImplementedError unless @io.respond_to?(:to_i)
@io.to_i
end
def to_io
@io.respond_to?(:to_io) ? @io.to_io : @io
end
def eof?
return false if @eof_error
begin
parser_enumerator.peek
false
rescue MalformedCSVError => error
@eof_error = error
false
rescue StopIteration
true
end
end
alias_method :eof, :eof?
# Rewinds the underlying IO object and resets CSV's lineno() counter.
def rewind
@parser = nil
@parser_enumerator = nil
@eof_error = nil
@writer.rewind if @writer
@io.rewind
end
### End Delegation ###
# :call-seq:
# csv << row -> self
#
# Appends a row to +self+.
#
# - Argument +row+ must be an \Array object or a CSV::Row object.
# - The output stream must be open for writing.
#
# ---
#
# Append Arrays:
# CSV.generate do |csv|
# csv << ['foo', 0]
# csv << ['bar', 1]
# csv << ['baz', 2]
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# Append CSV::Rows:
# headers = []
# CSV.generate do |csv|
# csv << CSV::Row.new(headers, ['foo', 0])
# csv << CSV::Row.new(headers, ['bar', 1])
# csv << CSV::Row.new(headers, ['baz', 2])
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# Headers in CSV::Row objects are not appended:
# headers = ['Name', 'Count']
# CSV.generate do |csv|
# csv << CSV::Row.new(headers, ['foo', 0])
# csv << CSV::Row.new(headers, ['bar', 1])
# csv << CSV::Row.new(headers, ['baz', 2])
# end # => "foo,0\nbar,1\nbaz,2\n"
#
# ---
#
# Raises an exception if +row+ is not an \Array or \CSV::Row:
# CSV.generate do |csv|
# # Raises NoMethodError (undefined method `collect' for :foo:Symbol)
# csv << :foo
# end
#
# Raises an exception if the output stream is not opened for writing:
# path = 't.csv'
# File.write(path, '')
# File.open(path) do |file|
# CSV.open(file) do |csv|
# # Raises IOError (not opened for writing)
# csv << ['foo', 0]
# end
# end
def <<(row)
writer << row
self
end
alias_method :add_row, :<<
alias_method :puts, :<<
# :call-seq:
# convert(converter_name) -> array_of_procs
# convert {|field, field_info| ... } -> array_of_procs
#
# - With no block, installs a field converter (a \Proc).
# - With a block, defines and installs a custom field converter.
# - Returns the \Array of installed field converters.
#
# - Argument +converter_name+, if given, should be the name
# of an existing field converter.
#
# See {Field Converters}[#class-CSV-label-Field+Converters].
# ---
#
# With no block, installs a field converter:
# csv = CSV.new('')
# csv.convert(:integer)
# csv.convert(:float)
# csv.convert(:date)
# csv.converters # => [:integer, :float, :date]
#
# ---
#
# The block, if given, is called for each field:
# - Argument +field+ is the field value.
# - Argument +field_info+ is a CSV::FieldInfo object
# containing details about the field.
#
# The examples here assume the prior execution of:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Example giving a block:
# csv = CSV.open(path)
# csv.convert {|field, field_info| p [field, field_info]; field.upcase }
# csv.read # => [["FOO", "0"], ["BAR", "1"], ["BAZ", "2"]]
#
# Output:
# ["foo", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# ["0", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# ["bar", #<struct CSV::FieldInfo index=0, line=2, header=nil>]
# ["1", #<struct CSV::FieldInfo index=1, line=2, header=nil>]
# ["baz", #<struct CSV::FieldInfo index=0, line=3, header=nil>]
# ["2", #<struct CSV::FieldInfo index=1, line=3, header=nil>]
#
# The block need not return a \String object:
# csv = CSV.open(path)
# csv.convert {|field, field_info| field.to_sym }
# csv.read # => [[:foo, :"0"], [:bar, :"1"], [:baz, :"2"]]
#
# If +converter_name+ is given, the block is not called:
# csv = CSV.open(path)
# csv.convert(:integer) {|field, field_info| fail 'Cannot happen' }
# csv.read # => [["foo", 0], ["bar", 1], ["baz", 2]]
#
# ---
#
# Raises a parse-time exception if +converter_name+ is not the name of a built-in
# field converter:
# csv = CSV.open(path)
# csv.convert(:nosuch) => [nil]
# # Raises NoMethodError (undefined method `arity' for nil:NilClass)
# csv.read
def convert(name = nil, &converter)
parser_fields_converter.add_converter(name, &converter)
end
# :call-seq:
# header_convert(converter_name) -> array_of_procs
# header_convert {|header, field_info| ... } -> array_of_procs
#
# - With no block, installs a header converter (a \Proc).
# - With a block, defines and installs a custom header converter.
# - Returns the \Array of installed header converters.
#
# - Argument +converter_name+, if given, should be the name
# of an existing header converter.
#
# See {Header Converters}[#class-CSV-label-Header+Converters].
# ---
#
# With no block, installs a header converter:
# csv = CSV.new('')
# csv.header_convert(:symbol)
# csv.header_convert(:downcase)
# csv.header_converters # => [:symbol, :downcase]
#
# ---
#
# The block, if given, is called for each header:
# - Argument +header+ is the header value.
# - Argument +field_info+ is a CSV::FieldInfo object
# containing details about the header.
#
# The examples here assume the prior execution of:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
#
# Example giving a block:
# csv = CSV.open(path, headers: true)
# csv.header_convert {|header, field_info| p [header, field_info]; header.upcase }
# table = csv.read
# table # => #<CSV::Table mode:col_or_row row_count:4>
# table.headers # => ["NAME", "VALUE"]
#
# Output:
# ["Name", #<struct CSV::FieldInfo index=0, line=1, header=nil>]
# ["Value", #<struct CSV::FieldInfo index=1, line=1, header=nil>]
# The block need not return a \String object:
# csv = CSV.open(path, headers: true)
# csv.header_convert {|header, field_info| header.to_sym }
# table = csv.read
# table.headers # => [:Name, :Value]
#
# If +converter_name+ is given, the block is not called:
# csv = CSV.open(path, headers: true)
# csv.header_convert(:downcase) {|header, field_info| fail 'Cannot happen' }
# table = csv.read
# table.headers # => ["name", "value"]
# ---
#
# Raises a parse-time exception if +converter_name+ is not the name of a built-in
# field converter:
# csv = CSV.open(path, headers: true)
# csv.header_convert(:nosuch)
# # Raises NoMethodError (undefined method `arity' for nil:NilClass)
# csv.read
def header_convert(name = nil, &converter)
header_fields_converter.add_converter(name, &converter)
end
include Enumerable
# :call-seq:
# csv.each -> enumerator
# csv.each {|row| ...}
#
# Calls the block with each successive row.
# The data source must be opened for reading.
#
# Without headers:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.each do |row|
# p row
# end
# Output:
# ["foo", "0"]
# ["bar", "1"]
# ["baz", "2"]
#
# With headers:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string, headers: true)
# csv.each do |row|
# p row
# end
# Output:
# <CSV::Row "Name":"foo" "Value":"0">
# <CSV::Row "Name":"bar" "Value":"1">
# <CSV::Row "Name":"baz" "Value":"2">
#
# ---
#
# Raises an exception if the source is not opened for reading:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.close
# # Raises IOError (not opened for reading)
# csv.each do |row|
# p row
# end
def each(&block)
return to_enum(__method__) unless block_given?
begin
while true
yield(parser_enumerator.next)
end
rescue StopIteration
end
end
# :call-seq:
# csv.read -> array or csv_table
#
# Forms the remaining rows from +self+ into:
# - A CSV::Table object, if headers are in use.
# - An \Array of Arrays, otherwise.
#
# The data source must be opened for reading.
#
# Without headers:
# string = "foo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# csv = CSV.open(path)
# csv.read # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# With headers:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# path = 't.csv'
# File.write(path, string)
# csv = CSV.open(path, headers: true)
# csv.read # => #<CSV::Table mode:col_or_row row_count:4>
#
# ---
#
# Raises an exception if the source is not opened for reading:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.close
# # Raises IOError (not opened for reading)
# csv.read
def read
rows = to_a
if parser.use_headers?
Table.new(rows, headers: parser.headers)
else
rows
end
end
alias_method :readlines, :read
# :call-seq:
# csv.header_row? -> true or false
#
# Returns +true+ if the next row to be read is a header row\;
# +false+ otherwise.
#
# Without headers:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.header_row? # => false
#
# With headers:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string, headers: true)
# csv.header_row? # => true
# csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
# csv.header_row? # => false
#
# ---
#
# Raises an exception if the source is not opened for reading:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.close
# # Raises IOError (not opened for reading)
# csv.header_row?
def header_row?
parser.header_row?
end
# :call-seq:
# csv.shift -> array, csv_row, or nil
#
# Returns the next row of data as:
# - An \Array if no headers are used.
# - A CSV::Row object if headers are used.
#
# The data source must be opened for reading.
#
# Without headers:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.shift # => ["foo", "0"]
# csv.shift # => ["bar", "1"]
# csv.shift # => ["baz", "2"]
# csv.shift # => nil
#
# With headers:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string, headers: true)
# csv.shift # => #<CSV::Row "Name":"foo" "Value":"0">
# csv.shift # => #<CSV::Row "Name":"bar" "Value":"1">
# csv.shift # => #<CSV::Row "Name":"baz" "Value":"2">
# csv.shift # => nil
#
# ---
#
# Raises an exception if the source is not opened for reading:
# string = "foo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string)
# csv.close
# # Raises IOError (not opened for reading)
# csv.shift
def shift
if @eof_error
eof_error, @eof_error = @eof_error, nil
raise eof_error
end
begin
parser_enumerator.next
rescue StopIteration
nil
end
end
alias_method :gets, :shift
alias_method :readline, :shift
# :call-seq:
# csv.inspect -> string
#
# Returns a \String showing certain properties of +self+:
# string = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# csv = CSV.new(string, headers: true)
# s = csv.inspect
# s # => "#<CSV io_type:StringIO encoding:UTF-8 lineno:0 col_sep:\",\" row_sep:\"\\n\" quote_char:\"\\\"\" headers:true>"
def inspect
str = ["#<", self.class.to_s, " io_type:"]
# show type of wrapped IO
if @io == $stdout then str << "$stdout"
elsif @io == $stdin then str << "$stdin"
elsif @io == $stderr then str << "$stderr"
else str << @io.class.to_s
end
# show IO.path(), if available
if @io.respond_to?(:path) and (p = @io.path)
str << " io_path:" << p.inspect
end
# show encoding
str << " encoding:" << @encoding.name
# show other attributes
["lineno", "col_sep", "row_sep", "quote_char"].each do |attr_name|
if a = __send__(attr_name)
str << " " << attr_name << ":" << a.inspect
end
end
["skip_blanks", "liberal_parsing"].each do |attr_name|
if a = __send__("#{attr_name}?")
str << " " << attr_name << ":" << a.inspect
end
end
_headers = headers
str << " headers:" << _headers.inspect if _headers
str << ">"
begin
str.join('')
rescue # any encoding error
str.map do |s|
e = Encoding::Converter.asciicompat_encoding(s.encoding)
e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
end.join('')
end
end
private
def determine_encoding(encoding, internal_encoding)
# honor the IO encoding if we can, otherwise default to ASCII-8BIT
io_encoding = raw_encoding
return io_encoding if io_encoding
return Encoding.find(internal_encoding) if internal_encoding
if encoding
encoding, = encoding.split(":", 2) if encoding.is_a?(String)
return Encoding.find(encoding)
end
Encoding.default_internal || Encoding.default_external
end
def normalize_converters(converters)
converters ||= []
unless converters.is_a?(Array)
converters = [converters]
end
converters.collect do |converter|
case converter
when Proc # custom code block
[nil, converter]
else # by name
[converter, nil]
end
end
end
#
# Processes +fields+ with <tt>@converters</tt>, or <tt>@header_converters</tt>
# if +headers+ is passed as +true+, returning the converted field set. Any
# converter that changes the field into something other than a String halts
# the pipeline of conversion for that field. This is primarily an efficiency
# shortcut.
#
def convert_fields(fields, headers = false)
if headers
header_fields_converter.convert(fields, nil, 0)
else
parser_fields_converter.convert(fields, @headers, lineno)
end
end
#
# Returns the encoding of the internal IO object.
#
def raw_encoding
if @io.respond_to? :internal_encoding
@io.internal_encoding || @io.external_encoding
elsif @io.respond_to? :encoding
@io.encoding
else
nil
end
end
def parser_fields_converter
@parser_fields_converter ||= build_parser_fields_converter
end
def build_parser_fields_converter
specific_options = {
builtin_converters_name: :Converters,
}
options = @base_fields_converter_options.merge(specific_options)
build_fields_converter(@initial_converters, options)
end
def header_fields_converter
@header_fields_converter ||= build_header_fields_converter
end
def build_header_fields_converter
specific_options = {
builtin_converters_name: :HeaderConverters,
accept_nil: true,
}
options = @base_fields_converter_options.merge(specific_options)
build_fields_converter(@initial_header_converters, options)
end
def writer_fields_converter
@writer_fields_converter ||= build_writer_fields_converter
end
def build_writer_fields_converter
build_fields_converter(@initial_write_converters,
@write_fields_converter_options)
end
def build_fields_converter(initial_converters, options)
fields_converter = FieldsConverter.new(options)
normalize_converters(initial_converters).each do |name, converter|
fields_converter.add_converter(name, &converter)
end
fields_converter
end
def parser
@parser ||= Parser.new(@io, parser_options)
end
def parser_options
@parser_options.merge(header_fields_converter: header_fields_converter,
fields_converter: parser_fields_converter)
end
def parser_enumerator
@parser_enumerator ||= parser.parse
end
def writer
@writer ||= Writer.new(@io, writer_options)
end
def writer_options
@writer_options.merge(header_fields_converter: header_fields_converter,
fields_converter: writer_fields_converter)
end
end
# Passes +args+ to CSV::instance.
#
# CSV("CSV,data").read
# #=> [["CSV", "data"]]
#
# If a block is given, the instance is passed the block and the return value
# becomes the return value of the block.
#
# CSV("CSV,data") { |c|
# c.read.any? { |a| a.include?("data") }
# } #=> true
#
# CSV("CSV,data") { |c|
# c.read.any? { |a| a.include?("zombies") }
# } #=> false
#
# CSV options may also be given.
#
# io = StringIO.new
# CSV(io, col_sep: ";") { |csv| csv << ["a", "b", "c"] }
#
# This API is not Ractor-safe.
#
def CSV(*args, **options, &block)
CSV.instance(*args, **options, &block)
end
require_relative "csv/version"
require_relative "csv/core_ext/array"
require_relative "csv/core_ext/string"
share/ruby/cgi.rb 0000644 00000023526 15173547335 0007743 0 ustar 00 # frozen_string_literal: true
#
# cgi.rb - cgi support library
#
# Copyright (C) 2000 Network Applied Communication Laboratory, Inc.
#
# Copyright (C) 2000 Information-technology Promotion Agency, Japan
#
# Author: Wakou Aoyama <wakou@ruby-lang.org>
#
# Documentation: Wakou Aoyama (RDoc'd and embellished by William Webber)
#
# == Overview
#
# The Common Gateway Interface (CGI) is a simple protocol for passing an HTTP
# request from a web server to a standalone program, and returning the output
# to the web browser. Basically, a CGI program is called with the parameters
# of the request passed in either in the environment (GET) or via $stdin
# (POST), and everything it prints to $stdout is returned to the client.
#
# This file holds the CGI class. This class provides functionality for
# retrieving HTTP request parameters, managing cookies, and generating HTML
# output.
#
# The file CGI::Session provides session management functionality; see that
# class for more details.
#
# See http://www.w3.org/CGI/ for more information on the CGI protocol.
#
# == Introduction
#
# CGI is a large class, providing several categories of methods, many of which
# are mixed in from other modules. Some of the documentation is in this class,
# some in the modules CGI::QueryExtension and CGI::HtmlExtension. See
# CGI::Cookie for specific information on handling cookies, and cgi/session.rb
# (CGI::Session) for information on sessions.
#
# For queries, CGI provides methods to get at environmental variables,
# parameters, cookies, and multipart request data. For responses, CGI provides
# methods for writing output and generating HTML.
#
# Read on for more details. Examples are provided at the bottom.
#
# == Queries
#
# The CGI class dynamically mixes in parameter and cookie-parsing
# functionality, environmental variable access, and support for
# parsing multipart requests (including uploaded files) from the
# CGI::QueryExtension module.
#
# === Environmental Variables
#
# The standard CGI environmental variables are available as read-only
# attributes of a CGI object. The following is a list of these variables:
#
#
# AUTH_TYPE HTTP_HOST REMOTE_IDENT
# CONTENT_LENGTH HTTP_NEGOTIATE REMOTE_USER
# CONTENT_TYPE HTTP_PRAGMA REQUEST_METHOD
# GATEWAY_INTERFACE HTTP_REFERER SCRIPT_NAME
# HTTP_ACCEPT HTTP_USER_AGENT SERVER_NAME
# HTTP_ACCEPT_CHARSET PATH_INFO SERVER_PORT
# HTTP_ACCEPT_ENCODING PATH_TRANSLATED SERVER_PROTOCOL
# HTTP_ACCEPT_LANGUAGE QUERY_STRING SERVER_SOFTWARE
# HTTP_CACHE_CONTROL REMOTE_ADDR
# HTTP_FROM REMOTE_HOST
#
#
# For each of these variables, there is a corresponding attribute with the
# same name, except all lower case and without a preceding HTTP_.
# +content_length+ and +server_port+ are integers; the rest are strings.
#
# === Parameters
#
# The method #params() returns a hash of all parameters in the request as
# name/value-list pairs, where the value-list is an Array of one or more
# values. The CGI object itself also behaves as a hash of parameter names
# to values, but only returns a single value (as a String) for each
# parameter name.
#
# For instance, suppose the request contains the parameter
# "favourite_colours" with the multiple values "blue" and "green". The
# following behavior would occur:
#
# cgi.params["favourite_colours"] # => ["blue", "green"]
# cgi["favourite_colours"] # => "blue"
#
# If a parameter does not exist, the former method will return an empty
# array, the latter an empty string. The simplest way to test for existence
# of a parameter is by the #has_key? method.
#
# === Cookies
#
# HTTP Cookies are automatically parsed from the request. They are available
# from the #cookies() accessor, which returns a hash from cookie name to
# CGI::Cookie object.
#
# === Multipart requests
#
# If a request's method is POST and its content type is multipart/form-data,
# then it may contain uploaded files. These are stored by the QueryExtension
# module in the parameters of the request. The parameter name is the name
# attribute of the file input field, as usual. However, the value is not
# a string, but an IO object, either an IOString for small files, or a
# Tempfile for larger ones. This object also has the additional singleton
# methods:
#
# #local_path():: the path of the uploaded file on the local filesystem
# #original_filename():: the name of the file on the client computer
# #content_type():: the content type of the file
#
# == Responses
#
# The CGI class provides methods for sending header and content output to
# the HTTP client, and mixes in methods for programmatic HTML generation
# from CGI::HtmlExtension and CGI::TagMaker modules. The precise version of HTML
# to use for HTML generation is specified at object creation time.
#
# === Writing output
#
# The simplest way to send output to the HTTP client is using the #out() method.
# This takes the HTTP headers as a hash parameter, and the body content
# via a block. The headers can be generated as a string using the #http_header()
# method. The output stream can be written directly to using the #print()
# method.
#
# === Generating HTML
#
# Each HTML element has a corresponding method for generating that
# element as a String. The name of this method is the same as that
# of the element, all lowercase. The attributes of the element are
# passed in as a hash, and the body as a no-argument block that evaluates
# to a String. The HTML generation module knows which elements are
# always empty, and silently drops any passed-in body. It also knows
# which elements require matching closing tags and which don't. However,
# it does not know what attributes are legal for which elements.
#
# There are also some additional HTML generation methods mixed in from
# the CGI::HtmlExtension module. These include individual methods for the
# different types of form inputs, and methods for elements that commonly
# take particular attributes where the attributes can be directly specified
# as arguments, rather than via a hash.
#
# === Utility HTML escape and other methods like a function.
#
# There are some utility tool defined in cgi/util.rb .
# And when include, you can use utility methods like a function.
#
# == Examples of use
#
# === Get form values
#
# require "cgi"
# cgi = CGI.new
# value = cgi['field_name'] # <== value string for 'field_name'
# # if not 'field_name' included, then return "".
# fields = cgi.keys # <== array of field names
#
# # returns true if form has 'field_name'
# cgi.has_key?('field_name')
# cgi.has_key?('field_name')
# cgi.include?('field_name')
#
# CAUTION! <code>cgi['field_name']</code> returned an Array with the old
# cgi.rb(included in Ruby 1.6)
#
# === Get form values as hash
#
# require "cgi"
# cgi = CGI.new
# params = cgi.params
#
# cgi.params is a hash.
#
# cgi.params['new_field_name'] = ["value"] # add new param
# cgi.params['field_name'] = ["new_value"] # change value
# cgi.params.delete('field_name') # delete param
# cgi.params.clear # delete all params
#
#
# === Save form values to file
#
# require "pstore"
# db = PStore.new("query.db")
# db.transaction do
# db["params"] = cgi.params
# end
#
#
# === Restore form values from file
#
# require "pstore"
# db = PStore.new("query.db")
# db.transaction do
# cgi.params = db["params"]
# end
#
#
# === Get multipart form values
#
# require "cgi"
# cgi = CGI.new
# value = cgi['field_name'] # <== value string for 'field_name'
# value.read # <== body of value
# value.local_path # <== path to local file of value
# value.original_filename # <== original filename of value
# value.content_type # <== content_type of value
#
# and value has StringIO or Tempfile class methods.
#
# === Get cookie values
#
# require "cgi"
# cgi = CGI.new
# values = cgi.cookies['name'] # <== array of 'name'
# # if not 'name' included, then return [].
# names = cgi.cookies.keys # <== array of cookie names
#
# and cgi.cookies is a hash.
#
# === Get cookie objects
#
# require "cgi"
# cgi = CGI.new
# for name, cookie in cgi.cookies
# cookie.expires = Time.now + 30
# end
# cgi.out("cookie" => cgi.cookies) {"string"}
#
# cgi.cookies # { "name1" => cookie1, "name2" => cookie2, ... }
#
# require "cgi"
# cgi = CGI.new
# cgi.cookies['name'].expires = Time.now + 30
# cgi.out("cookie" => cgi.cookies['name']) {"string"}
#
# === Print http header and html string to $DEFAULT_OUTPUT ($>)
#
# require "cgi"
# cgi = CGI.new("html4") # add HTML generation methods
# cgi.out do
# cgi.html do
# cgi.head do
# cgi.title { "TITLE" }
# end +
# cgi.body do
# cgi.form("ACTION" => "uri") do
# cgi.p do
# cgi.textarea("get_text") +
# cgi.br +
# cgi.submit
# end
# end +
# cgi.pre do
# CGI.escapeHTML(
# "params: #{cgi.params.inspect}\n" +
# "cookies: #{cgi.cookies.inspect}\n" +
# ENV.collect do |key, value|
# "#{key} --> #{value}\n"
# end.join("")
# )
# end
# end
# end
# end
#
# # add HTML generation methods
# CGI.new("html3") # html3.2
# CGI.new("html4") # html4.01 (Strict)
# CGI.new("html4Tr") # html4.01 Transitional
# CGI.new("html4Fr") # html4.01 Frameset
# CGI.new("html5") # html5
#
# === Some utility methods
#
# require 'cgi/util'
# CGI.escapeHTML('Usage: foo "bar" <baz>')
#
#
# === Some utility methods like a function
#
# require 'cgi/util'
# include CGI::Util
# escapeHTML('Usage: foo "bar" <baz>')
# h('Usage: foo "bar" <baz>') # alias
#
#
class CGI
VERSION = "0.3.7"
end
require 'cgi/core'
require 'cgi/cookie'
require 'cgi/util'
CGI.autoload(:HtmlExtension, 'cgi/html')
share/ruby/objspace.rb 0000644 00000011252 15173547335 0010760 0 ustar 00 # frozen_string_literal: true
require 'objspace.so'
module ObjectSpace
class << self
private :_dump
private :_dump_all
private :_dump_shapes
end
module_function
# call-seq:
# ObjectSpace.dump(obj[, output: :string]) -> "{ ... }"
# ObjectSpace.dump(obj, output: :file) -> #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json>
# ObjectSpace.dump(obj, output: :stdout) -> nil
#
# Dump the contents of a ruby object as JSON.
#
# This method is only expected to work with C Ruby.
# This is an experimental method and is subject to change.
# In particular, the function signature and output format are
# not guaranteed to be compatible in future versions of ruby.
def dump(obj, output: :string)
out = case output
when :file, nil
require 'tempfile'
Tempfile.create(%w(rubyobj .json))
when :stdout
STDOUT
when :string
+''
when IO
output
else
raise ArgumentError, "wrong output option: #{output.inspect}"
end
ret = _dump(obj, out)
return nil if output == :stdout
ret
end
# call-seq:
# ObjectSpace.dump_all([output: :file]) -> #<File:/tmp/rubyheap20131125-88469-laoj3v.json>
# ObjectSpace.dump_all(output: :stdout) -> nil
# ObjectSpace.dump_all(output: :string) -> "{...}\n{...}\n..."
# ObjectSpace.dump_all(output: File.open('heap.json','w')) -> #<File:heap.json>
# ObjectSpace.dump_all(output: :string, since: 42) -> "{...}\n{...}\n..."
#
# Dump the contents of the ruby heap as JSON.
#
# _full_ must be a boolean. If true all heap slots are dumped including the empty ones (T_NONE).
#
# _since_ must be a non-negative integer or +nil+.
#
# If _since_ is a positive integer, only objects of that generation and
# newer generations are dumped. The current generation can be accessed using
# GC::count. Objects that were allocated without object allocation tracing enabled
# are ignored. See ::trace_object_allocations for more information and
# examples.
#
# If _since_ is omitted or is +nil+, all objects are dumped.
#
# _shapes_ must be a boolean or a non-negative integer.
#
# If _shapes_ is a positive integer, only shapes newer than the provided
# shape id are dumped. The current shape_id can be accessed using <tt>RubyVM.stat(:next_shape_id)</tt>.
#
# If _shapes_ is +false+, no shapes are dumped.
#
# To only dump objects allocated past a certain point you can combine _since_ and _shapes_:
# ObjectSpace.trace_object_allocations
# GC.start
# gc_generation = GC.count
# shape_generation = RubyVM.stat(:next_shape_id)
# call_method_to_instrument
# ObjectSpace.dump_all(since: gc_generation, shapes: shape_generation)
#
# This method is only expected to work with C Ruby.
# This is an experimental method and is subject to change.
# In particular, the function signature and output format are
# not guaranteed to be compatible in future versions of ruby.
def dump_all(output: :file, full: false, since: nil, shapes: true)
out = case output
when :file, nil
require 'tempfile'
Tempfile.create(%w(rubyheap .json))
when :stdout
STDOUT
when :string
+''
when IO
output
else
raise ArgumentError, "wrong output option: #{output.inspect}"
end
shapes = 0 if shapes == true
ret = _dump_all(out, full, since, shapes)
return nil if output == :stdout
ret
end
# call-seq:
# ObjectSpace.dump_shapes([output: :file]) -> #<File:/tmp/rubyshapes20131125-88469-laoj3v.json>
# ObjectSpace.dump_shapes(output: :stdout) -> nil
# ObjectSpace.dump_shapes(output: :string) -> "{...}\n{...}\n..."
# ObjectSpace.dump_shapes(output: File.open('shapes.json','w')) -> #<File:shapes.json>
# ObjectSpace.dump_all(output: :string, since: 42) -> "{...}\n{...}\n..."
#
# Dump the contents of the ruby shape tree as JSON.
#
# If _shapes_ is a positive integer, only shapes newer than the provided
# shape id are dumped. The current shape_id can be accessed using <tt>RubyVM.stat(:next_shape_id)</tt>.
#
# This method is only expected to work with C Ruby.
# This is an experimental method and is subject to change.
# In particular, the function signature and output format are
# not guaranteed to be compatible in future versions of ruby.
def dump_shapes(output: :file, since: 0)
out = case output
when :file, nil
require 'tempfile'
Tempfile.create(%w(rubyshapes .json))
when :stdout
STDOUT
when :string
+''
when IO
output
else
raise ArgumentError, "wrong output option: #{output.inspect}"
end
ret = _dump_shapes(out, since)
return nil if output == :stdout
ret
end
end
share/gems/gems/psych-5.0.1/lib/psych 0000755 00000000000 15173547335 0013146 0 ustar 00 share/ruby/pp.rb 0000644 00000042343 15173547335 0007616 0 ustar 00 # frozen_string_literal: true
require 'prettyprint'
##
# A pretty-printer for Ruby objects.
#
##
# == What PP Does
#
# Standard output by #p returns this:
# #<PP:0x81fedf0 @genspace=#<Proc:0x81feda0>, @group_queue=#<PrettyPrint::GroupQueue:0x81fed3c @queue=[[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], []]>, @buffer=[], @newline="\n", @group_stack=[#<PrettyPrint::Group:0x81fed78 @breakables=[], @depth=0, @break=false>], @buffer_width=0, @indent=0, @maxwidth=79, @output_width=2, @output=#<IO:0x8114ee4>>
#
# Pretty-printed output returns this:
# #<PP:0x81fedf0
# @buffer=[],
# @buffer_width=0,
# @genspace=#<Proc:0x81feda0>,
# @group_queue=
# #<PrettyPrint::GroupQueue:0x81fed3c
# @queue=
# [[#<PrettyPrint::Group:0x81fed78 @break=false, @breakables=[], @depth=0>],
# []]>,
# @group_stack=
# [#<PrettyPrint::Group:0x81fed78 @break=false, @breakables=[], @depth=0>],
# @indent=0,
# @maxwidth=79,
# @newline="\n",
# @output=#<IO:0x8114ee4>,
# @output_width=2>
#
##
# == Usage
#
# pp(obj) #=> obj
# pp obj #=> obj
# pp(obj1, obj2, ...) #=> [obj1, obj2, ...]
# pp() #=> nil
#
# Output <tt>obj(s)</tt> to <tt>$></tt> in pretty printed format.
#
# It returns <tt>obj(s)</tt>.
#
##
# == Output Customization
#
# To define a customized pretty printing function for your classes,
# redefine method <code>#pretty_print(pp)</code> in the class.
#
# <code>#pretty_print</code> takes the +pp+ argument, which is an instance of the PP class.
# The method uses #text, #breakable, #nest, #group and #pp to print the
# object.
#
##
# == Pretty-Print JSON
#
# To pretty-print JSON refer to JSON#pretty_generate.
#
##
# == Author
# Tanaka Akira <akr@fsij.org>
class PP < PrettyPrint
# Returns the usable width for +out+.
# As the width of +out+:
# 1. If +out+ is assigned to a tty device, its width is used.
# 2. Otherwise, or it could not get the value, the +COLUMN+
# environment variable is assumed to be set to the width.
# 3. If +COLUMN+ is not set to a non-zero number, 80 is assumed.
#
# And finally, returns the above width value - 1.
# * This -1 is for Windows command prompt, which moves the cursor to
# the next line if it reaches the last column.
def PP.width_for(out)
begin
require 'io/console'
_, width = out.winsize
rescue LoadError, NoMethodError, SystemCallError
end
(width || ENV['COLUMNS']&.to_i&.nonzero? || 80) - 1
end
# Outputs +obj+ to +out+ in pretty printed format of
# +width+ columns in width.
#
# If +out+ is omitted, <code>$></code> is assumed.
# If +width+ is omitted, the width of +out+ is assumed (see
# width_for).
#
# PP.pp returns +out+.
def PP.pp(obj, out=$>, width=width_for(out))
q = PP.new(out, width)
q.guard_inspect_key {q.pp obj}
q.flush
#$pp = q
out << "\n"
end
# Outputs +obj+ to +out+ like PP.pp but with no indent and
# newline.
#
# PP.singleline_pp returns +out+.
def PP.singleline_pp(obj, out=$>)
q = SingleLine.new(out)
q.guard_inspect_key {q.pp obj}
q.flush
out
end
# :stopdoc:
def PP.mcall(obj, mod, meth, *args, &block)
mod.instance_method(meth).bind_call(obj, *args, &block)
end
# :startdoc:
if defined? ::Ractor
class << self
# Returns the sharing detection flag as a boolean value.
# It is false (nil) by default.
def sharing_detection
Ractor.current[:pp_sharing_detection]
end
# Sets the sharing detection flag to b.
def sharing_detection=(b)
Ractor.current[:pp_sharing_detection] = b
end
end
else
@sharing_detection = false
class << self
# Returns the sharing detection flag as a boolean value.
# It is false by default.
attr_accessor :sharing_detection
end
end
module PPMethods
# Yields to a block
# and preserves the previous set of objects being printed.
def guard_inspect_key
if Thread.current[:__recursive_key__] == nil
Thread.current[:__recursive_key__] = {}.compare_by_identity
end
if Thread.current[:__recursive_key__][:inspect] == nil
Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity
end
save = Thread.current[:__recursive_key__][:inspect]
begin
Thread.current[:__recursive_key__][:inspect] = {}.compare_by_identity
yield
ensure
Thread.current[:__recursive_key__][:inspect] = save
end
end
# Check whether the object_id +id+ is in the current buffer of objects
# to be pretty printed. Used to break cycles in chains of objects to be
# pretty printed.
def check_inspect_key(id)
Thread.current[:__recursive_key__] &&
Thread.current[:__recursive_key__][:inspect] &&
Thread.current[:__recursive_key__][:inspect].include?(id)
end
# Adds the object_id +id+ to the set of objects being pretty printed, so
# as to not repeat objects.
def push_inspect_key(id)
Thread.current[:__recursive_key__][:inspect][id] = true
end
# Removes an object from the set of objects being pretty printed.
def pop_inspect_key(id)
Thread.current[:__recursive_key__][:inspect].delete id
end
# Adds +obj+ to the pretty printing buffer
# using Object#pretty_print or Object#pretty_print_cycle.
#
# Object#pretty_print_cycle is used when +obj+ is already
# printed, a.k.a the object reference chain has a cycle.
def pp(obj)
# If obj is a Delegator then use the object being delegated to for cycle
# detection
obj = obj.__getobj__ if defined?(::Delegator) and obj.is_a?(::Delegator)
if check_inspect_key(obj)
group {obj.pretty_print_cycle self}
return
end
begin
push_inspect_key(obj)
group {obj.pretty_print self}
ensure
pop_inspect_key(obj) unless PP.sharing_detection
end
end
# A convenience method which is same as follows:
#
# group(1, '#<' + obj.class.name, '>') { ... }
def object_group(obj, &block) # :yield:
group(1, '#<' + obj.class.name, '>', &block)
end
# A convenience method, like object_group, but also reformats the Object's
# object_id.
def object_address_group(obj, &block)
str = Kernel.instance_method(:to_s).bind_call(obj)
str.chomp!('>')
group(1, str, '>', &block)
end
# A convenience method which is same as follows:
#
# text ','
# breakable
def comma_breakable
text ','
breakable
end
# Adds a separated list.
# The list is separated by comma with breakable space, by default.
#
# #seplist iterates the +list+ using +iter_method+.
# It yields each object to the block given for #seplist.
# The procedure +separator_proc+ is called between each yields.
#
# If the iteration is zero times, +separator_proc+ is not called at all.
#
# If +separator_proc+ is nil or not given,
# +lambda { comma_breakable }+ is used.
# If +iter_method+ is not given, :each is used.
#
# For example, following 3 code fragments has similar effect.
#
# q.seplist([1,2,3]) {|v| xxx v }
#
# q.seplist([1,2,3], lambda { q.comma_breakable }, :each) {|v| xxx v }
#
# xxx 1
# q.comma_breakable
# xxx 2
# q.comma_breakable
# xxx 3
def seplist(list, sep=nil, iter_method=:each) # :yield: element
sep ||= lambda { comma_breakable }
first = true
list.__send__(iter_method) {|*v|
if first
first = false
else
sep.call
end
RUBY_VERSION >= "3.0" ? yield(*v, **{}) : yield(*v)
}
end
# A present standard failsafe for pretty printing any given Object
def pp_object(obj)
object_address_group(obj) {
seplist(obj.pretty_print_instance_variables, lambda { text ',' }) {|v|
breakable
v = v.to_s if Symbol === v
text v
text '='
group(1) {
breakable ''
pp(obj.instance_eval(v))
}
}
}
end
# A pretty print for a Hash
def pp_hash(obj)
group(1, '{', '}') {
seplist(obj, nil, :each_pair) {|k, v|
group {
pp k
text '=>'
group(1) {
breakable ''
pp v
}
}
}
}
end
end
include PPMethods
class SingleLine < PrettyPrint::SingleLine # :nodoc:
include PPMethods
end
module ObjectMixin # :nodoc:
# 1. specific pretty_print
# 2. specific inspect
# 3. generic pretty_print
# A default pretty printing method for general objects.
# It calls #pretty_print_instance_variables to list instance variables.
#
# If +self+ has a customized (redefined) #inspect method,
# the result of self.inspect is used but it obviously has no
# line break hints.
#
# This module provides predefined #pretty_print methods for some of
# the most commonly used built-in classes for convenience.
def pretty_print(q)
umethod_method = Object.instance_method(:method)
begin
inspect_method = umethod_method.bind_call(self, :inspect)
rescue NameError
end
if inspect_method && inspect_method.owner != Kernel
q.text self.inspect
elsif !inspect_method && self.respond_to?(:inspect)
q.text self.inspect
else
q.pp_object(self)
end
end
# A default pretty printing method for general objects that are
# detected as part of a cycle.
def pretty_print_cycle(q)
q.object_address_group(self) {
q.breakable
q.text '...'
}
end
# Returns a sorted array of instance variable names.
#
# This method should return an array of names of instance variables as symbols or strings as:
# +[:@a, :@b]+.
def pretty_print_instance_variables
instance_variables.sort
end
# Is #inspect implementation using #pretty_print.
# If you implement #pretty_print, it can be used as follows.
#
# alias inspect pretty_print_inspect
#
# However, doing this requires that every class that #inspect is called on
# implement #pretty_print, or a RuntimeError will be raised.
def pretty_print_inspect
if Object.instance_method(:method).bind_call(self, :pretty_print).owner == PP::ObjectMixin
raise "pretty_print is not overridden for #{self.class}"
end
PP.singleline_pp(self, ''.dup)
end
end
end
class Array # :nodoc:
def pretty_print(q) # :nodoc:
q.group(1, '[', ']') {
q.seplist(self) {|v|
q.pp v
}
}
end
def pretty_print_cycle(q) # :nodoc:
q.text(empty? ? '[]' : '[...]')
end
end
class Hash # :nodoc:
def pretty_print(q) # :nodoc:
q.pp_hash self
end
def pretty_print_cycle(q) # :nodoc:
q.text(empty? ? '{}' : '{...}')
end
end
class << ENV # :nodoc:
def pretty_print(q) # :nodoc:
h = {}
ENV.keys.sort.each {|k|
h[k] = ENV[k]
}
q.pp_hash h
end
end
class Struct # :nodoc:
def pretty_print(q) # :nodoc:
q.group(1, sprintf("#<struct %s", PP.mcall(self, Kernel, :class).name), '>') {
q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member|
q.breakable
q.text member.to_s
q.text '='
q.group(1) {
q.breakable ''
q.pp self[member]
}
}
}
end
def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<struct %s:...>", PP.mcall(self, Kernel, :class).name)
end
end
class Data # :nodoc:
def pretty_print(q) # :nodoc:
q.group(1, sprintf("#<data %s", PP.mcall(self, Kernel, :class).name), '>') {
q.seplist(PP.mcall(self, Data, :members), lambda { q.text "," }) {|member|
q.breakable
q.text member.to_s
q.text '='
q.group(1) {
q.breakable ''
q.pp public_send(member)
}
}
}
end
def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<data %s:...>", PP.mcall(self, Kernel, :class).name)
end
end if "3.2" <= RUBY_VERSION
class Range # :nodoc:
def pretty_print(q) # :nodoc:
q.pp self.begin
q.breakable ''
q.text(self.exclude_end? ? '...' : '..')
q.breakable ''
q.pp self.end if self.end
end
end
class String # :nodoc:
def pretty_print(q) # :nodoc:
lines = self.lines
if lines.size > 1
q.group(0, '', '') do
q.seplist(lines, lambda { q.text ' +'; q.breakable }) do |v|
q.pp v
end
end
else
q.text inspect
end
end
end
class File < IO # :nodoc:
class Stat # :nodoc:
def pretty_print(q) # :nodoc:
require 'etc'
q.object_group(self) {
q.breakable
q.text sprintf("dev=0x%x", self.dev); q.comma_breakable
q.text "ino="; q.pp self.ino; q.comma_breakable
q.group {
m = self.mode
q.text sprintf("mode=0%o", m)
q.breakable
q.text sprintf("(%s %c%c%c%c%c%c%c%c%c)",
self.ftype,
(m & 0400 == 0 ? ?- : ?r),
(m & 0200 == 0 ? ?- : ?w),
(m & 0100 == 0 ? (m & 04000 == 0 ? ?- : ?S) :
(m & 04000 == 0 ? ?x : ?s)),
(m & 0040 == 0 ? ?- : ?r),
(m & 0020 == 0 ? ?- : ?w),
(m & 0010 == 0 ? (m & 02000 == 0 ? ?- : ?S) :
(m & 02000 == 0 ? ?x : ?s)),
(m & 0004 == 0 ? ?- : ?r),
(m & 0002 == 0 ? ?- : ?w),
(m & 0001 == 0 ? (m & 01000 == 0 ? ?- : ?T) :
(m & 01000 == 0 ? ?x : ?t)))
}
q.comma_breakable
q.text "nlink="; q.pp self.nlink; q.comma_breakable
q.group {
q.text "uid="; q.pp self.uid
begin
pw = Etc.getpwuid(self.uid)
rescue ArgumentError
end
if pw
q.breakable; q.text "(#{pw.name})"
end
}
q.comma_breakable
q.group {
q.text "gid="; q.pp self.gid
begin
gr = Etc.getgrgid(self.gid)
rescue ArgumentError
end
if gr
q.breakable; q.text "(#{gr.name})"
end
}
q.comma_breakable
q.group {
q.text sprintf("rdev=0x%x", self.rdev)
if self.rdev_major && self.rdev_minor
q.breakable
q.text sprintf('(%d, %d)', self.rdev_major, self.rdev_minor)
end
}
q.comma_breakable
q.text "size="; q.pp self.size; q.comma_breakable
q.text "blksize="; q.pp self.blksize; q.comma_breakable
q.text "blocks="; q.pp self.blocks; q.comma_breakable
q.group {
t = self.atime
q.text "atime="; q.pp t
q.breakable; q.text "(#{t.tv_sec})"
}
q.comma_breakable
q.group {
t = self.mtime
q.text "mtime="; q.pp t
q.breakable; q.text "(#{t.tv_sec})"
}
q.comma_breakable
q.group {
t = self.ctime
q.text "ctime="; q.pp t
q.breakable; q.text "(#{t.tv_sec})"
}
}
end
end
end
class MatchData # :nodoc:
def pretty_print(q) # :nodoc:
nc = []
self.regexp.named_captures.each {|name, indexes|
indexes.each {|i| nc[i] = name }
}
q.object_group(self) {
q.breakable
q.seplist(0...self.size, lambda { q.breakable }) {|i|
if i == 0
q.pp self[i]
else
if nc[i]
q.text nc[i]
else
q.pp i
end
q.text ':'
q.pp self[i]
end
}
}
end
end
if defined?(RubyVM::AbstractSyntaxTree)
class RubyVM::AbstractSyntaxTree::Node
def pretty_print_children(q, names = [])
children.zip(names) do |c, n|
if n
q.breakable
q.text "#{n}:"
end
q.group(2) do
q.breakable
q.pp c
end
end
end
def pretty_print(q)
q.group(1, "(#{type}@#{first_lineno}:#{first_column}-#{last_lineno}:#{last_column}", ")") {
case type
when :SCOPE
pretty_print_children(q, %w"tbl args body")
when :ARGS
pretty_print_children(q, %w[pre_num pre_init opt first_post post_num post_init rest kw kwrest block])
when :DEFN
pretty_print_children(q, %w[mid body])
when :ARYPTN
pretty_print_children(q, %w[const pre rest post])
when :HSHPTN
pretty_print_children(q, %w[const kw kwrest])
else
pretty_print_children(q)
end
}
end
end
end
class Object < BasicObject # :nodoc:
include PP::ObjectMixin
end
[Numeric, Symbol, FalseClass, TrueClass, NilClass, Module].each {|c|
c.class_eval {
def pretty_print_cycle(q)
q.text inspect
end
}
}
[Numeric, FalseClass, TrueClass, Module].each {|c|
c.class_eval {
def pretty_print(q)
q.text inspect
end
}
}
module Kernel
# Returns a pretty printed object as a string.
#
# In order to use this method you must first require the PP module:
#
# require 'pp'
#
# See the PP module for more information.
def pretty_inspect
PP.pp(self, ''.dup)
end
# prints arguments in pretty form.
#
# pp returns argument(s).
def pp(*objs)
objs.each {|obj|
PP.pp(obj)
}
objs.size <= 1 ? objs.first : objs
end
module_function :pp
end
share/ruby/erb.rb 0000644 00000035105 15173547335 0007745 0 ustar 00 # -*- coding: us-ascii -*-
# frozen_string_literal: true
# = ERB -- Ruby Templating
#
# Author:: Masatoshi SEKI
# Documentation:: James Edward Gray II, Gavin Sinclair, and Simon Chiang
#
# See ERB for primary documentation and ERB::Util for a couple of utility
# routines.
#
# Copyright (c) 1999-2000,2002,2003 Masatoshi SEKI
#
# You can redistribute it and/or modify it under the same terms as Ruby.
require 'cgi/util'
require 'erb/version'
require 'erb/compiler'
require 'erb/def_method'
require 'erb/util'
#
# = ERB -- Ruby Templating
#
# == Introduction
#
# ERB provides an easy to use but powerful templating system for Ruby. Using
# ERB, actual Ruby code can be added to any plain text document for the
# purposes of generating document information details and/or flow control.
#
# A very simple example is this:
#
# require 'erb'
#
# x = 42
# template = ERB.new <<-EOF
# The value of x is: <%= x %>
# EOF
# puts template.result(binding)
#
# <em>Prints:</em> The value of x is: 42
#
# More complex examples are given below.
#
#
# == Recognized Tags
#
# ERB recognizes certain tags in the provided template and converts them based
# on the rules below:
#
# <% Ruby code -- inline with output %>
# <%= Ruby expression -- replace with result %>
# <%# comment -- ignored -- useful in testing %> (`<% #` doesn't work. Don't use Ruby comments.)
# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new)
# %% replaced with % if first thing on a line and % processing is used
# <%% or %%> -- replace with <% or %> respectively
#
# All other text is passed through ERB filtering unchanged.
#
#
# == Options
#
# There are several settings you can change when you use ERB:
# * the nature of the tags that are recognized;
# * the binding used to resolve local variables in the template.
#
# See the ERB.new and ERB#result methods for more detail.
#
# == Character encodings
#
# ERB (or Ruby code generated by ERB) returns a string in the same
# character encoding as the input string. When the input string has
# a magic comment, however, it returns a string in the encoding specified
# by the magic comment.
#
# # -*- coding: utf-8 -*-
# require 'erb'
#
# template = ERB.new <<EOF
# <%#-*- coding: Big5 -*-%>
# \_\_ENCODING\_\_ is <%= \_\_ENCODING\_\_ %>.
# EOF
# puts template.result
#
# <em>Prints:</em> \_\_ENCODING\_\_ is Big5.
#
#
# == Examples
#
# === Plain Text
#
# ERB is useful for any generic templating situation. Note that in this example, we use the
# convenient "% at start of line" tag, and we quote the template literally with
# <tt>%q{...}</tt> to avoid trouble with the backslash.
#
# require "erb"
#
# # Create template.
# template = %q{
# From: James Edward Gray II <james@grayproductions.net>
# To: <%= to %>
# Subject: Addressing Needs
#
# <%= to[/\w+/] %>:
#
# Just wanted to send a quick note assuring that your needs are being
# addressed.
#
# I want you to know that my team will keep working on the issues,
# especially:
#
# <%# ignore numerous minor requests -- focus on priorities %>
# % priorities.each do |priority|
# * <%= priority %>
# % end
#
# Thanks for your patience.
#
# James Edward Gray II
# }.gsub(/^ /, '')
#
# message = ERB.new(template, trim_mode: "%<>")
#
# # Set up template data.
# to = "Community Spokesman <spokesman@ruby_community.org>"
# priorities = [ "Run Ruby Quiz",
# "Document Modules",
# "Answer Questions on Ruby Talk" ]
#
# # Produce result.
# email = message.result
# puts email
#
# <i>Generates:</i>
#
# From: James Edward Gray II <james@grayproductions.net>
# To: Community Spokesman <spokesman@ruby_community.org>
# Subject: Addressing Needs
#
# Community:
#
# Just wanted to send a quick note assuring that your needs are being addressed.
#
# I want you to know that my team will keep working on the issues, especially:
#
# * Run Ruby Quiz
# * Document Modules
# * Answer Questions on Ruby Talk
#
# Thanks for your patience.
#
# James Edward Gray II
#
# === Ruby in HTML
#
# ERB is often used in <tt>.rhtml</tt> files (HTML with embedded Ruby). Notice the need in
# this example to provide a special binding when the template is run, so that the instance
# variables in the Product object can be resolved.
#
# require "erb"
#
# # Build template data class.
# class Product
# def initialize( code, name, desc, cost )
# @code = code
# @name = name
# @desc = desc
# @cost = cost
#
# @features = [ ]
# end
#
# def add_feature( feature )
# @features << feature
# end
#
# # Support templating of member data.
# def get_binding
# binding
# end
#
# # ...
# end
#
# # Create template.
# template = %{
# <html>
# <head><title>Ruby Toys -- <%= @name %></title></head>
# <body>
#
# <h1><%= @name %> (<%= @code %>)</h1>
# <p><%= @desc %></p>
#
# <ul>
# <% @features.each do |f| %>
# <li><b><%= f %></b></li>
# <% end %>
# </ul>
#
# <p>
# <% if @cost < 10 %>
# <b>Only <%= @cost %>!!!</b>
# <% else %>
# Call for a price, today!
# <% end %>
# </p>
#
# </body>
# </html>
# }.gsub(/^ /, '')
#
# rhtml = ERB.new(template)
#
# # Set up template data.
# toy = Product.new( "TZ-1002",
# "Rubysapien",
# "Geek's Best Friend! Responds to Ruby commands...",
# 999.95 )
# toy.add_feature("Listens for verbal commands in the Ruby language!")
# toy.add_feature("Ignores Perl, Java, and all C variants.")
# toy.add_feature("Karate-Chop Action!!!")
# toy.add_feature("Matz signature on left leg.")
# toy.add_feature("Gem studded eyes... Rubies, of course!")
#
# # Produce result.
# rhtml.run(toy.get_binding)
#
# <i>Generates (some blank lines removed):</i>
#
# <html>
# <head><title>Ruby Toys -- Rubysapien</title></head>
# <body>
#
# <h1>Rubysapien (TZ-1002)</h1>
# <p>Geek's Best Friend! Responds to Ruby commands...</p>
#
# <ul>
# <li><b>Listens for verbal commands in the Ruby language!</b></li>
# <li><b>Ignores Perl, Java, and all C variants.</b></li>
# <li><b>Karate-Chop Action!!!</b></li>
# <li><b>Matz signature on left leg.</b></li>
# <li><b>Gem studded eyes... Rubies, of course!</b></li>
# </ul>
#
# <p>
# Call for a price, today!
# </p>
#
# </body>
# </html>
#
#
# == Notes
#
# There are a variety of templating solutions available in various Ruby projects.
# For example, RDoc, distributed with Ruby, uses its own template engine, which
# can be reused elsewhere.
#
# Other popular engines could be found in the corresponding
# {Category}[https://www.ruby-toolbox.com/categories/template_engines] of
# The Ruby Toolbox.
#
class ERB
Revision = '$Date:: $' # :nodoc: #'
deprecate_constant :Revision
# Returns revision information for the erb.rb module.
def self.version
VERSION
end
#
# Constructs a new ERB object with the template specified in _str_.
#
# An ERB object works by building a chunk of Ruby code that will output
# the completed template when run.
#
# If _trim_mode_ is passed a String containing one or more of the following
# modifiers, ERB will adjust its code generation as listed:
#
# % enables Ruby code processing for lines beginning with %
# <> omit newline for lines starting with <% and ending in %>
# > omit newline for lines ending in %>
# - omit blank lines ending in -%>
#
# _eoutvar_ can be used to set the name of the variable ERB will build up
# its output in. This is useful when you need to run multiple ERB
# templates through the same binding and/or when you want to control where
# output ends up. Pass the name of the variable to be used inside a String.
#
# === Example
#
# require "erb"
#
# # build data class
# class Listings
# PRODUCT = { :name => "Chicken Fried Steak",
# :desc => "A well messages pattie, breaded and fried.",
# :cost => 9.95 }
#
# attr_reader :product, :price
#
# def initialize( product = "", price = "" )
# @product = product
# @price = price
# end
#
# def build
# b = binding
# # create and run templates, filling member data variables
# ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@product").result b
# <%= PRODUCT[:name] %>
# <%= PRODUCT[:desc] %>
# END_PRODUCT
# ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), trim_mode: "", eoutvar: "@price").result b
# <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %>
# <%= PRODUCT[:desc] %>
# END_PRICE
# end
# end
#
# # setup template data
# listings = Listings.new
# listings.build
#
# puts listings.product + "\n" + listings.price
#
# _Generates_
#
# Chicken Fried Steak
# A well messages pattie, breaded and fried.
#
# Chicken Fried Steak -- 9.95
# A well messages pattie, breaded and fried.
#
def initialize(str, safe_level=NOT_GIVEN, legacy_trim_mode=NOT_GIVEN, legacy_eoutvar=NOT_GIVEN, trim_mode: nil, eoutvar: '_erbout')
# Complex initializer for $SAFE deprecation at [Feature #14256]. Use keyword arguments to pass trim_mode or eoutvar.
if safe_level != NOT_GIVEN
warn 'Passing safe_level with the 2nd argument of ERB.new is deprecated. Do not use it, and specify other arguments as keyword arguments.', uplevel: 1
end
if legacy_trim_mode != NOT_GIVEN
warn 'Passing trim_mode with the 3rd argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, trim_mode: ...) instead.', uplevel: 1
trim_mode = legacy_trim_mode
end
if legacy_eoutvar != NOT_GIVEN
warn 'Passing eoutvar with the 4th argument of ERB.new is deprecated. Use keyword argument like ERB.new(str, eoutvar: ...) instead.', uplevel: 1
eoutvar = legacy_eoutvar
end
compiler = make_compiler(trim_mode)
set_eoutvar(compiler, eoutvar)
@src, @encoding, @frozen_string = *compiler.compile(str)
@filename = nil
@lineno = 0
@_init = self.class.singleton_class
end
NOT_GIVEN = Object.new
private_constant :NOT_GIVEN
##
# Creates a new compiler for ERB. See ERB::Compiler.new for details
def make_compiler(trim_mode)
ERB::Compiler.new(trim_mode)
end
# The Ruby code generated by ERB
attr_reader :src
# The encoding to eval
attr_reader :encoding
# The optional _filename_ argument passed to Kernel#eval when the ERB code
# is run
attr_accessor :filename
# The optional _lineno_ argument passed to Kernel#eval when the ERB code
# is run
attr_accessor :lineno
#
# Sets optional filename and line number that will be used in ERB code
# evaluation and error reporting. See also #filename= and #lineno=
#
# erb = ERB.new('<%= some_x %>')
# erb.render
# # undefined local variable or method `some_x'
# # from (erb):1
#
# erb.location = ['file.erb', 3]
# # All subsequent error reporting would use new location
# erb.render
# # undefined local variable or method `some_x'
# # from file.erb:4
#
def location=((filename, lineno))
@filename = filename
@lineno = lineno if lineno
end
#
# Can be used to set _eoutvar_ as described in ERB::new. It's probably
# easier to just use the constructor though, since calling this method
# requires the setup of an ERB _compiler_ object.
#
def set_eoutvar(compiler, eoutvar = '_erbout')
compiler.put_cmd = "#{eoutvar}.<<"
compiler.insert_cmd = "#{eoutvar}.<<"
compiler.pre_cmd = ["#{eoutvar} = +''"]
compiler.post_cmd = [eoutvar]
end
# Generate results and print them. (see ERB#result)
def run(b=new_toplevel)
print self.result(b)
end
#
# Executes the generated ERB code to produce a completed template, returning
# the results of that code. (See ERB::new for details on how this process
# can be affected by _safe_level_.)
#
# _b_ accepts a Binding object which is used to set the context of
# code evaluation.
#
def result(b=new_toplevel)
unless @_init.equal?(self.class.singleton_class)
raise ArgumentError, "not initialized"
end
eval(@src, b, (@filename || '(erb)'), @lineno)
end
# Render a template on a new toplevel binding with local variables specified
# by a Hash object.
def result_with_hash(hash)
b = new_toplevel(hash.keys)
hash.each_pair do |key, value|
b.local_variable_set(key, value)
end
result(b)
end
##
# Returns a new binding each time *near* TOPLEVEL_BINDING for runs that do
# not specify a binding.
def new_toplevel(vars = nil)
b = TOPLEVEL_BINDING
if vars
vars = vars.select {|v| b.local_variable_defined?(v)}
unless vars.empty?
return b.eval("tap {|;#{vars.join(',')}| break binding}")
end
end
b.dup
end
private :new_toplevel
# Define _methodname_ as instance method of _mod_ from compiled Ruby source.
#
# example:
# filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml
# erb = ERB.new(File.read(filename))
# erb.def_method(MyClass, 'render(arg1, arg2)', filename)
# print MyClass.new.render('foo', 123)
def def_method(mod, methodname, fname='(ERB)')
src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
mod.module_eval do
eval(src, binding, fname, -1)
end
end
# Create unnamed module, define _methodname_ as instance method of it, and return it.
#
# example:
# filename = 'example.rhtml' # 'arg1' and 'arg2' are used in example.rhtml
# erb = ERB.new(File.read(filename))
# erb.filename = filename
# MyModule = erb.def_module('render(arg1, arg2)')
# class MyClass
# include MyModule
# end
def def_module(methodname='erb')
mod = Module.new
def_method(mod, methodname, @filename || '(ERB)')
mod
end
# Define unnamed class which has _methodname_ as instance method, and return it.
#
# example:
# class MyClass_
# def initialize(arg1, arg2)
# @arg1 = arg1; @arg2 = arg2
# end
# end
# filename = 'example.rhtml' # @arg1 and @arg2 are used in example.rhtml
# erb = ERB.new(File.read(filename))
# erb.filename = filename
# MyClass = erb.def_class(MyClass_, 'render()')
# print MyClass.new('foo', 123).render()
def def_class(superklass=Object, methodname='result')
cls = Class.new(superklass)
def_method(cls, methodname, @filename || '(ERB)')
cls
end
end
share/gems/gems/bigdecimal-3.1.3/lib/bigdecimal 0000755 00000000000 15173547335 0015033 0 ustar 00 share/ruby/yaml.rb 0000644 00000004155 15173547335 0010140 0 ustar 00 # frozen_string_literal: false
begin
require 'psych'
rescue LoadError
case RUBY_ENGINE
when 'jruby'
warn "The Psych YAML extension failed to load.\n" \
"Check your env for conflicting versions of SnakeYAML\n" \
"See https://github.com/jruby/jruby/wiki/FAQs#why-does-the-psych-yaml-extension-fail-to-load-in-my-environment",
uplevel: 1
else
warn "It seems your ruby installation is missing psych (for YAML output).\n" \
"To eliminate this warning, please install libyaml and reinstall your ruby.\n",
uplevel: 1
end
raise
end
YAML = Psych # :nodoc:
# YAML Ain't Markup Language
#
# This module provides a Ruby interface for data serialization in YAML format.
#
# The YAML module is an alias of Psych, the YAML engine for Ruby.
#
# == Usage
#
# Working with YAML can be very simple, for example:
#
# require 'yaml'
# # Parse a YAML string
# YAML.load("--- foo") #=> "foo"
#
# # Emit some YAML
# YAML.dump("foo") # => "--- foo\n...\n"
# { :a => 'b'}.to_yaml # => "---\n:a: b\n"
#
# As the implementation is provided by the Psych library, detailed documentation
# can be found in that library's docs (also part of standard library).
#
# == Security
#
# Do not use YAML to load untrusted data. Doing so is unsafe and could allow
# malicious input to execute arbitrary code inside your application. Please see
# doc/security.rdoc for more information.
#
# == History
#
# Syck was the original YAML implementation in Ruby's standard library
# developed by why the lucky stiff.
#
# You can still use Syck, if you prefer, for parsing and emitting YAML, but you
# must install the 'syck' gem now in order to use it.
#
# In older Ruby versions, ie. <= 1.9, Syck is still provided, however it was
# completely removed with the release of Ruby 2.0.0.
#
# == More info
#
# For more advanced details on the implementation see Psych, and also check out
# http://yaml.org for spec details and other helpful information.
#
# Psych is maintained by Aaron Patterson on github: https://github.com/ruby/psych
#
# Syck can also be found on github: https://github.com/ruby/syck
module YAML
end
share/ruby/bigdecimal.rb 0000644 00000000030 15173547335 0011242 0 ustar 00 require 'bigdecimal.so'
share/ruby/drb.rb 0000644 00000000062 15173547335 0007736 0 ustar 00 # frozen_string_literal: false
require 'drb/drb'
share/ruby/getoptlong.rb 0000644 00000050415 15173547335 0011360 0 ustar 00 # 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.
# \Class \GetoptLong provides parsing both for options
# and for regular arguments.
#
# Using \GetoptLong, you can define options for your program.
# The program can then capture and respond to whatever options
# are included in the command that executes the program.
#
# A simple example: file <tt>simple.rb</tt>:
#
# :include: ../sample/getoptlong/simple.rb
#
# If you are somewhat familiar with options,
# you may want to skip to this
# {full example}[#class-GetoptLong-label-Full+Example].
#
# == Options
#
# A \GetoptLong option has:
#
# - A string <em>option name</em>.
# - Zero or more string <em>aliases</em> for the name.
# - An <em>option type</em>.
#
# Options may be defined by calling singleton method GetoptLong.new,
# which returns a new \GetoptLong object.
# Options may then be processed by calling other methods
# such as GetoptLong#each.
#
# === Option Name and Aliases
#
# In the array that defines an option,
# the first element is the string option name.
# Often the name takes the 'long' form, beginning with two hyphens.
#
# The option name may have any number of aliases,
# which are defined by additional string elements.
#
# The name and each alias must be of one of two forms:
#
# - Two hyphens, followed by one or more letters.
# - One hyphen, followed by a single letter.
#
# File <tt>aliases.rb</tt>:
#
# :include: ../sample/getoptlong/aliases.rb
#
# An option may be cited by its name,
# or by any of its aliases;
# the parsed option always reports the name, not an alias:
#
# $ ruby aliases.rb -a -p --xxx --aaa -x
#
# Output:
#
# ["--xxx", ""]
# ["--xxx", ""]
# ["--xxx", ""]
# ["--xxx", ""]
# ["--xxx", ""]
#
#
# An option may also be cited by an abbreviation of its name or any alias,
# as long as that abbreviation is unique among the options.
#
# File <tt>abbrev.rb</tt>:
#
# :include: ../sample/getoptlong/abbrev.rb
#
# Command line:
#
# $ ruby abbrev.rb --xxx --xx --xyz --xy
#
# Output:
#
# ["--xxx", ""]
# ["--xxx", ""]
# ["--xyz", ""]
# ["--xyz", ""]
#
# This command line raises GetoptLong::AmbiguousOption:
#
# $ ruby abbrev.rb --x
#
# === Repetition
#
# An option may be cited more than once:
#
# $ ruby abbrev.rb --xxx --xyz --xxx --xyz
#
# Output:
#
# ["--xxx", ""]
# ["--xyz", ""]
# ["--xxx", ""]
# ["--xyz", ""]
#
# === Treating Remaining Options as Arguments
#
# A option-like token that appears
# anywhere after the token <tt>--</tt> is treated as an ordinary argument,
# and is not processed as an option:
#
# $ ruby abbrev.rb --xxx --xyz -- --xxx --xyz
#
# Output:
#
# ["--xxx", ""]
# ["--xyz", ""]
#
# === Option Types
#
# Each option definition includes an option type,
# which controls whether the option takes an argument.
#
# File <tt>types.rb</tt>:
#
# :include: ../sample/getoptlong/types.rb
#
# Note that an option type has to do with the <em>option argument</em>
# (whether it is required, optional, or forbidden),
# not with whether the option itself is required.
#
# ==== Option with Required Argument
#
# An option of type <tt>GetoptLong::REQUIRED_ARGUMENT</tt>
# must be followed by an argument, which is associated with that option:
#
# $ ruby types.rb --xxx foo
#
# Output:
#
# ["--xxx", "foo"]
#
# If the option is not last, its argument is whatever follows it
# (even if the argument looks like another option):
#
# $ ruby types.rb --xxx --yyy
#
# Output:
#
# ["--xxx", "--yyy"]
#
# If the option is last, an exception is raised:
#
# $ ruby types.rb
# # Raises GetoptLong::MissingArgument
#
# ==== Option with Optional Argument
#
# An option of type <tt>GetoptLong::OPTIONAL_ARGUMENT</tt>
# may be followed by an argument, which if given is associated with that option.
#
# If the option is last, it does not have an argument:
#
# $ ruby types.rb --yyy
#
# Output:
#
# ["--yyy", ""]
#
# If the option is followed by another option, it does not have an argument:
#
# $ ruby types.rb --yyy --zzz
#
# Output:
#
# ["--yyy", ""]
# ["--zzz", ""]
#
# Otherwise the option is followed by its argument, which is associated
# with that option:
#
# $ ruby types.rb --yyy foo
#
# Output:
#
# ["--yyy", "foo"]
#
# ==== Option with No Argument
#
# An option of type <tt>GetoptLong::NO_ARGUMENT</tt> takes no argument:
#
# ruby types.rb --zzz foo
#
# Output:
#
# ["--zzz", ""]
#
# === ARGV
#
# You can process options either with method #each and a block,
# or with method #get.
#
# During processing, each found option is removed, along with its argument
# if there is one.
# After processing, each remaining element was neither an option
# nor the argument for an option.
#
# File <tt>argv.rb</tt>:
#
# :include: ../sample/getoptlong/argv.rb
#
# Command line:
#
# $ ruby argv.rb --xxx Foo --yyy Bar Baz --zzz Bat Bam
#
# Output:
#
# Original ARGV: ["--xxx", "Foo", "--yyy", "Bar", "Baz", "--zzz", "Bat", "Bam"]
# ["--xxx", "Foo"]
# ["--yyy", "Bar"]
# ["--zzz", ""]
# Remaining ARGV: ["Baz", "Bat", "Bam"]
#
# === Ordering
#
# There are three settings that control the way the options
# are interpreted:
#
# - +PERMUTE+.
# - +REQUIRE_ORDER+.
# - +RETURN_IN_ORDER+.
#
# The initial setting for a new \GetoptLong object is +REQUIRE_ORDER+
# if environment variable +POSIXLY_CORRECT+ is defined, +PERMUTE+ otherwise.
#
# ==== PERMUTE Ordering
#
# In the +PERMUTE+ ordering, options and other, non-option,
# arguments may appear in any order and any mixture.
#
# File <tt>permute.rb</tt>:
#
# :include: ../sample/getoptlong/permute.rb
#
# Command line:
#
# $ ruby permute.rb Foo --zzz Bar --xxx Baz --yyy Bat Bam --xxx Bag Bah
#
# Output:
#
# Original ARGV: ["Foo", "--zzz", "Bar", "--xxx", "Baz", "--yyy", "Bat", "Bam", "--xxx", "Bag", "Bah"]
# ["--zzz", ""]
# ["--xxx", "Baz"]
# ["--yyy", "Bat"]
# ["--xxx", "Bag"]
# Remaining ARGV: ["Foo", "Bar", "Bam", "Bah"]
#
# ==== REQUIRE_ORDER Ordering
#
# In the +REQUIRE_ORDER+ ordering, all options precede all non-options;
# that is, each word after the first non-option word
# is treated as a non-option word (even if it begins with a hyphen).
#
# File <tt>require_order.rb</tt>:
#
# :include: ../sample/getoptlong/require_order.rb
#
# Command line:
#
# $ ruby require_order.rb --xxx Foo Bar --xxx Baz --yyy Bat -zzz
#
# Output:
#
# Original ARGV: ["--xxx", "Foo", "Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"]
# ["--xxx", "Foo"]
# Remaining ARGV: ["Bar", "--xxx", "Baz", "--yyy", "Bat", "-zzz"]
#
# ==== RETURN_IN_ORDER Ordering
#
# In the +RETURN_IN_ORDER+ ordering, every word is treated as an option.
# A word that begins with a hyphen (or two) is treated in the usual way;
# a word +word+ that does not so begin is treated as an option
# whose name is an empty string, and whose value is +word+.
#
# File <tt>return_in_order.rb</tt>:
#
# :include: ../sample/getoptlong/return_in_order.rb
#
# Command line:
#
# $ ruby return_in_order.rb Foo --xxx Bar Baz --zzz Bat Bam
#
# Output:
#
# Original ARGV: ["Foo", "--xxx", "Bar", "Baz", "--zzz", "Bat", "Bam"]
# ["", "Foo"]
# ["--xxx", "Bar"]
# ["", "Baz"]
# ["--zzz", ""]
# ["", "Bat"]
# ["", "Bam"]
# Remaining ARGV: []
#
# === Full Example
#
# File <tt>fibonacci.rb</tt>:
#
# :include: ../sample/getoptlong/fibonacci.rb
#
# Command line:
#
# $ ruby fibonacci.rb
#
# Output:
#
# Option --number is required.
# Usage:
#
# -n n, --number n:
# Compute Fibonacci number for n.
# -v [boolean], --verbose [boolean]:
# Show intermediate results; default is 'false'.
# -h, --help:
# Show this help.
#
# Command line:
#
# $ ruby fibonacci.rb --number
#
# Raises GetoptLong::MissingArgument:
#
# fibonacci.rb: option `--number' requires an argument
#
# Command line:
#
# $ ruby fibonacci.rb --number 6
#
# Output:
#
# 8
#
# Command line:
#
# $ ruby fibonacci.rb --number 6 --verbose
#
# Output:
# 1
# 2
# 3
# 5
# 8
#
# Command line:
#
# $ ruby fibonacci.rb --number 6 --verbose yes
#
# Output:
#
# --verbose argument must be true or false
# Usage:
#
# -n n, --number n:
# Compute Fibonacci number for n.
# -v [boolean], --verbose [boolean]:
# Show intermediate results; default is 'false'.
# -h, --help:
# Show this help.
#
class GetoptLong
# Version.
VERSION = "0.2.0"
#
# 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
#
# Returns a new \GetoptLong object based on the given +arguments+.
# See {Options}[#class-GetoptLong-label-Options].
#
# Example:
#
# :include: ../sample/getoptlong/simple.rb
#
# Raises an exception if:
#
# - Any of +arguments+ is not an array.
# - Any option name or alias is not a string.
# - Any option type is invalid.
#
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
# Sets the ordering; see {Ordering}[#class-GetoptLong-label-Ordering];
# returns the new ordering.
#
# If the given +ordering+ is +PERMUTE+ and environment variable
# +POSIXLY_CORRECT+ is defined, sets the ordering to +REQUIRE_ORDER+;
# otherwise sets the ordering to +ordering+:
#
# options = GetoptLong.new
# options.ordering == GetoptLong::PERMUTE # => true
# options.ordering = GetoptLong::RETURN_IN_ORDER
# options.ordering == GetoptLong::RETURN_IN_ORDER # => true
# ENV['POSIXLY_CORRECT'] = 'true'
# options.ordering = GetoptLong::PERMUTE
# options.ordering == GetoptLong::REQUIRE_ORDER # => true
#
# Raises an exception if +ordering+ is invalid.
#
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
#
# Returns the ordering setting.
#
attr_reader :ordering
#
# Replaces existing options with those given by +arguments+,
# which have the same form as the arguments to ::new;
# returns +self+.
#
# Raises an exception if option processing has begun.
#
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
#
# Sets quiet mode and returns the given argument:
#
# - When +false+ or +nil+, error messages are written to <tt>$stdout</tt>.
# - Otherwise, error messages are not written.
#
attr_writer :quiet
#
# Returns the quiet mode setting.
#
attr_reader :quiet
alias quiet? quiet
#
# Terminate option processing;
# returns +nil+ if processing has already terminated;
# otherwise returns +self+.
#
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
#
# Returns whether option processing has failed.
#
attr_reader :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
#
# Returns the next option as a 2-element array containing:
#
# - The option name (the name itself, not an alias).
# - The option value.
#
# Returns +nil+ if there are no more options.
#
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
alias get_option get
#
# Calls the given block with each option;
# each option is a 2-element array containing:
#
# - The option name (the name itself, not an alias).
# - The option value.
#
# Example:
#
# :include: ../sample/getoptlong/each.rb
#
# Command line:
#
# ruby each.rb -xxx Foo -x Bar --yyy Baz -y Bat --zzz
#
# Output:
#
# Original ARGV: ["-xxx", "Foo", "-x", "Bar", "--yyy", "Baz", "-y", "Bat", "--zzz"]
# ["--xxx", "xx"]
# ["--xxx", "Bar"]
# ["--yyy", "Baz"]
# ["--yyy", "Bat"]
# ["--zzz", ""]
# Remaining ARGV: ["Foo"]
#
def each
loop do
option_name, option_argument = get_option
break if option_name == nil
yield option_name, option_argument
end
end
alias each_option each
end
share/ruby/resolv-replace.rb 0000644 00000003415 15173547335 0012117 0 ustar 00 # 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
share/ruby/reline.rb 0000644 00000043374 15173547335 0010462 0 ustar 00 require 'io/console'
require 'timeout'
require 'forwardable'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'
require 'reline/history'
require 'reline/terminfo'
require 'rbconfig'
module Reline
FILENAME_COMPLETION_PROC = nil
USERNAME_COMPLETION_PROC = nil
class ConfigEncodingConversionError < StandardError; end
Key = Struct.new('Key', :char, :combined_char, :with_meta) do
def match?(other)
case other
when Reline::Key
(other.char.nil? or char.nil? or char == other.char) and
(other.combined_char.nil? or combined_char.nil? or combined_char == other.combined_char) and
(other.with_meta.nil? or with_meta.nil? or with_meta == other.with_meta)
when Integer, Symbol
(combined_char and combined_char == other) or
(combined_char.nil? and char and char == other)
else
false
end
end
alias_method :==, :match?
end
CursorPos = Struct.new(:x, :y)
DialogRenderInfo = Struct.new(
:pos,
:contents,
:bg_color,
:pointer_bg_color,
:fg_color,
:pointer_fg_color,
:width,
:height,
:scrollbar,
keyword_init: true
)
class Core
ATTR_READER_NAMES = %i(
completion_append_character
basic_word_break_characters
completer_word_break_characters
basic_quote_characters
completer_quote_characters
filename_quote_characters
special_prefixes
completion_proc
output_modifier_proc
prompt_proc
auto_indent_proc
pre_input_hook
dig_perfect_match_proc
).each(&method(:attr_reader))
attr_accessor :config
attr_accessor :key_stroke
attr_accessor :line_editor
attr_accessor :last_incremental_search
attr_reader :output
extend Forwardable
def_delegators :config,
:autocompletion,
:autocompletion=
def initialize
self.output = STDOUT
@dialog_proc_list = {}
yield self
@completion_quote_character = nil
@bracketed_paste_finished = false
end
def encoding
Reline::IOGate.encoding
end
def completion_append_character=(val)
if val.nil?
@completion_append_character = nil
elsif val.size == 1
@completion_append_character = val.encode(Reline::IOGate.encoding)
elsif val.size > 1
@completion_append_character = val[0].encode(Reline::IOGate.encoding)
else
@completion_append_character = nil
end
end
def basic_word_break_characters=(v)
@basic_word_break_characters = v.encode(Reline::IOGate.encoding)
end
def completer_word_break_characters=(v)
@completer_word_break_characters = v.encode(Reline::IOGate.encoding)
end
def basic_quote_characters=(v)
@basic_quote_characters = v.encode(Reline::IOGate.encoding)
end
def completer_quote_characters=(v)
@completer_quote_characters = v.encode(Reline::IOGate.encoding)
end
def filename_quote_characters=(v)
@filename_quote_characters = v.encode(Reline::IOGate.encoding)
end
def special_prefixes=(v)
@special_prefixes = v.encode(Reline::IOGate.encoding)
end
def completion_case_fold=(v)
@config.completion_ignore_case = v
end
def completion_case_fold
@config.completion_ignore_case
end
def completion_quote_character
@completion_quote_character
end
def completion_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@completion_proc = p
end
def output_modifier_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@output_modifier_proc = p
end
def prompt_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@prompt_proc = p
end
def auto_indent_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@auto_indent_proc = p
end
def pre_input_hook=(p)
@pre_input_hook = p
end
def dig_perfect_match_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@dig_perfect_match_proc = p
end
DialogProc = Struct.new(:dialog_proc, :context)
def add_dialog_proc(name_sym, p, context = nil)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
raise ArgumentError unless name_sym.instance_of?(Symbol)
@dialog_proc_list[name_sym] = DialogProc.new(p, context)
end
def dialog_proc(name_sym)
@dialog_proc_list[name_sym]
end
def input=(val)
raise TypeError unless val.respond_to?(:getc) or val.nil?
if val.respond_to?(:getc)
if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
Reline::ANSI.input = val
elsif Reline::IOGate == Reline::GeneralIO
Reline::GeneralIO.input = val
end
end
end
def output=(val)
raise TypeError unless val.respond_to?(:write) or val.nil?
@output = val
if defined?(Reline::ANSI) and Reline::IOGate == Reline::ANSI
Reline::ANSI.output = val
end
end
def vi_editing_mode
config.editing_mode = :vi_insert
nil
end
def emacs_editing_mode
config.editing_mode = :emacs
nil
end
def vi_editing_mode?
config.editing_mode_is?(:vi_insert, :vi_command)
end
def emacs_editing_mode?
config.editing_mode_is?(:emacs)
end
def get_screen_size
Reline::IOGate.get_screen_size
end
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
# autocomplete
return nil unless config.autocompletion
if just_cursor_moving and completion_journey_data.nil?
# Auto complete starts only when edited
return nil
end
pre, target, post = retrieve_completion_block(true)
if target.nil? or target.empty? or (completion_journey_data&.pointer == -1 and target.size <= 3)
return nil
end
if completion_journey_data and completion_journey_data.list
result = completion_journey_data.list.dup
result.shift
pointer = completion_journey_data.pointer - 1
else
result = call_completion_proc_with_checking_args(pre, target, post)
pointer = nil
end
if result and result.size == 1 and result[0] == target and pointer != 0
result = nil
end
target_width = Reline::Unicode.calculate_width(target)
x = cursor_pos.x - target_width
if x < 0
x = screen_width + x
y = -1
else
y = 0
end
cursor_pos_to_render = Reline::CursorPos.new(x, y)
if context and context.is_a?(Array)
context.clear
context.push(cursor_pos_to_render, result, pointer, dialog)
end
dialog.pointer = pointer
DialogRenderInfo.new(
pos: cursor_pos_to_render,
contents: result,
scrollbar: true,
height: 15,
bg_color: 46,
pointer_bg_color: 45,
fg_color: 37,
pointer_fg_color: 37
)
}
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7'
if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
Reline::HISTORY << whole_buffer
end
line_editor.reset_line if line_editor.whole_buffer.nil?
whole_buffer
end
def readline(prompt = '', add_hist = false)
inner_readline(prompt, add_hist, false)
line = line_editor.line.dup
line.taint if RUBY_VERSION < '2.7'
if add_hist and line and line.chomp("\n").size > 0
Reline::HISTORY << line.chomp("\n")
end
line_editor.reset_line if line_editor.line.nil?
line
end
private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
if ENV['RELINE_STDERR_TTY']
if Reline::IOGate.win?
$stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
else
$stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
end
$stderr.sync = true
$stderr.puts "Reline is used by #{Process.pid}"
end
otio = Reline::IOGate.prep
may_req_ambiguous_char_width
line_editor.reset(prompt, encoding: Reline::IOGate.encoding)
if multiline
line_editor.multiline_on
if block_given?
line_editor.confirm_multiline_termination_proc = confirm_multiline_termination
end
else
line_editor.multiline_off
end
line_editor.output = output
line_editor.completion_proc = completion_proc
line_editor.completion_append_character = completion_append_character
line_editor.output_modifier_proc = output_modifier_proc
line_editor.prompt_proc = prompt_proc
line_editor.auto_indent_proc = auto_indent_proc
line_editor.dig_perfect_match_proc = dig_perfect_match_proc
line_editor.pre_input_hook = pre_input_hook
@dialog_proc_list.each_pair do |name_sym, d|
line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
end
unless config.test_mode
config.read
config.reset_default_key_bindings
Reline::IOGate.set_default_key_bindings(config)
end
line_editor.rerender
begin
line_editor.set_signal_handlers
prev_pasting_state = false
loop do
prev_pasting_state = Reline::IOGate.in_pasting?
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(Reline::IOGate.in_pasting?)
inputs.each { |c|
line_editor.input_key(c)
line_editor.rerender
}
if @bracketed_paste_finished
line_editor.rerender_all
@bracketed_paste_finished = false
end
}
if prev_pasting_state == true and not Reline::IOGate.in_pasting? and not line_editor.finished?
line_editor.set_pasting_state(false)
prev_pasting_state = false
line_editor.rerender_all
end
break if line_editor.finished?
end
Reline::IOGate.move_cursor_column(0)
rescue Errno::EIO
# Maybe the I/O has been closed.
rescue StandardError => e
line_editor.finalize
Reline::IOGate.deprep(otio)
raise e
rescue Exception
# Including Interrupt
line_editor.finalize
Reline::IOGate.deprep(otio)
raise
end
line_editor.finalize
Reline::IOGate.deprep(otio)
end
# GNU Readline waits for "keyseq-timeout" milliseconds to see if the ESC
# is followed by a character, and times out and treats it as a standalone
# ESC if the second character does not arrive. If the second character
# comes before timed out, it is treated as a modifier key with the
# meta-property of meta-key, so that it can be distinguished from
# multibyte characters with the 8th bit turned on.
#
# GNU Readline will wait for the 2nd character with "keyseq-timeout"
# milli-seconds but wait forever after 3rd characters.
private def read_io(keyseq_timeout, &block)
buffer = []
loop do
c = Reline::IOGate.getc
if c == -1
result = :unmatched
@bracketed_paste_finished = true
else
buffer << c
result = key_stroke.match_status(buffer)
end
case result
when :matched
expanded = key_stroke.expand(buffer).map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
block.(expanded)
break
when :matching
if buffer.size == 1
case read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
when :break then break
when :next then next
end
end
when :unmatched
if buffer.size == 1 and c == "\e".ord
read_escaped_key(keyseq_timeout, c, block)
else
expanded = buffer.map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
block.(expanded)
end
break
end
end
end
private def read_2nd_character_of_key_sequence(keyseq_timeout, buffer, c, block)
begin
succ_c = nil
Timeout.timeout(keyseq_timeout / 1000.0) {
succ_c = Reline::IOGate.getc
}
rescue Timeout::Error # cancel matching only when first byte
block.([Reline::Key.new(c, c, false)])
return :break
else
case key_stroke.match_status(buffer.dup.push(succ_c))
when :unmatched
if c == "\e".ord
block.([Reline::Key.new(succ_c, succ_c | 0b10000000, true)])
else
block.([Reline::Key.new(c, c, false), Reline::Key.new(succ_c, succ_c, false)])
end
return :break
when :matching
Reline::IOGate.ungetc(succ_c)
return :next
when :matched
buffer << succ_c
expanded = key_stroke.expand(buffer).map{ |expanded_c|
Reline::Key.new(expanded_c, expanded_c, false)
}
block.(expanded)
return :break
end
end
end
private def read_escaped_key(keyseq_timeout, c, block)
begin
escaped_c = nil
Timeout.timeout(keyseq_timeout / 1000.0) {
escaped_c = Reline::IOGate.getc
}
rescue Timeout::Error # independent ESC
block.([Reline::Key.new(c, c, false)])
else
if escaped_c.nil?
block.([Reline::Key.new(c, c, false)])
elsif escaped_c >= 128 # maybe, first byte of multi byte
block.([Reline::Key.new(c, c, false), Reline::Key.new(escaped_c, escaped_c, false)])
elsif escaped_c == "\e".ord # escape twice
block.([Reline::Key.new(c, c, false), Reline::Key.new(c, c, false)])
else
block.([Reline::Key.new(escaped_c, escaped_c | 0b10000000, true)])
end
end
end
def ambiguous_width
may_req_ambiguous_char_width unless defined? @ambiguous_width
@ambiguous_width
end
private def may_req_ambiguous_char_width
@ambiguous_width = 2 if Reline::IOGate == Reline::GeneralIO or !STDOUT.tty?
return if defined? @ambiguous_width
Reline::IOGate.move_cursor_column(0)
begin
output.write "\u{25bd}"
rescue Encoding::UndefinedConversionError
# LANG=C
@ambiguous_width = 1
else
@ambiguous_width = Reline::IOGate.cursor_pos.x
end
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
end
end
extend Forwardable
extend SingleForwardable
#--------------------------------------------------------
# Documented API
#--------------------------------------------------------
(Core::ATTR_READER_NAMES).each { |name|
def_single_delegators :core, :"#{name}", :"#{name}="
}
def_single_delegators :core, :input=, :output=
def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
def_single_delegators :core, :readline
def_single_delegators :core, :completion_case_fold, :completion_case_fold=
def_single_delegators :core, :completion_quote_character
def_instance_delegators self, :readline
private :readline
#--------------------------------------------------------
# Undocumented API
#--------------------------------------------------------
# Testable in original
def_single_delegators :core, :get_screen_size
def_single_delegators :line_editor, :eof?
def_instance_delegators self, :eof?
def_single_delegators :line_editor, :delete_text
def_single_delegator :line_editor, :line, :line_buffer
def_single_delegator :line_editor, :byte_pointer, :point
def_single_delegator :line_editor, :byte_pointer=, :point=
def self.insert_text(*args, &block)
line_editor.insert_text(*args, &block)
self
end
# Untestable in original
def_single_delegator :line_editor, :rerender, :redisplay
def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode?
def_single_delegators :core, :ambiguous_width
def_single_delegators :core, :last_incremental_search
def_single_delegators :core, :last_incremental_search=
def_single_delegators :core, :add_dialog_proc
def_single_delegators :core, :dialog_proc
def_single_delegators :core, :autocompletion, :autocompletion=
def_single_delegators :core, :readmultiline
def_instance_delegators self, :readmultiline
private :readmultiline
def self.encoding_system_needs
self.core.encoding
end
def self.core
@core ||= Core.new { |core|
core.config = Reline::Config.new
core.key_stroke = Reline::KeyStroke.new(core.config)
core.line_editor = Reline::LineEditor.new(core.config, Reline::IOGate.encoding)
core.basic_word_break_characters = " \t\n`><=;|&{("
core.completer_word_break_characters = " \t\n`><=;|&{("
core.basic_quote_characters = '"\''
core.completer_quote_characters = '"\''
core.filename_quote_characters = ""
core.special_prefixes = ""
core.add_dialog_proc(:autocomplete, Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE, Reline::DEFAULT_DIALOG_CONTEXT)
}
end
def self.ungetc(c)
Reline::IOGate.ungetc(c)
end
def self.line_editor
core.line_editor
end
end
require 'reline/general_io'
io = Reline::GeneralIO
unless ENV['TERM'] == 'dumb'
case RbConfig::CONFIG['host_os']
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
require 'reline/windows'
tty = (io = Reline::Windows).msys_tty?
else
tty = $stdout.tty?
end
end
Reline::IOGate = if tty
require 'reline/ansi'
Reline::ANSI
else
io
end
Reline::HISTORY = Reline::History.new(Reline.core.config)
share/ruby/reline/kill_ring.rb 0000644 00000004613 15173547335 0012425 0 ustar 00 class Reline::KillRing
include Enumerable
module State
FRESH = :fresh
CONTINUED = :continued
PROCESSED = :processed
YANK = :yank
end
RingPoint = Struct.new(:backward, :forward, :str) do
def initialize(str)
super(nil, nil, str)
end
def ==(other)
object_id == other.object_id
end
end
class RingBuffer
attr_reader :size
attr_reader :head
def initialize(max = 1024)
@max = max
@size = 0
@head = nil # reading head of ring-shaped tape
end
def <<(point)
if @size.zero?
@head = point
@head.backward = @head
@head.forward = @head
@size = 1
elsif @size >= @max
tail = @head.forward
new_tail = tail.forward
@head.forward = point
point.backward = @head
new_tail.backward = point
point.forward = new_tail
@head = point
else
tail = @head.forward
@head.forward = point
point.backward = @head
tail.backward = point
point.forward = tail
@head = point
@size += 1
end
end
def empty?
@size.zero?
end
end
def initialize(max = 1024)
@ring = RingBuffer.new(max)
@ring_pointer = nil
@buffer = nil
@state = State::FRESH
end
def append(string, before_p = false)
case @state
when State::FRESH, State::YANK
@ring << RingPoint.new(string)
@state = State::CONTINUED
when State::CONTINUED, State::PROCESSED
if before_p
@ring.head.str.prepend(string)
else
@ring.head.str.concat(string)
end
@state = State::CONTINUED
end
end
def process
case @state
when State::FRESH
# nothing to do
when State::CONTINUED
@state = State::PROCESSED
when State::PROCESSED
@state = State::FRESH
when State::YANK
# nothing to do
end
end
def yank
unless @ring.empty?
@state = State::YANK
@ring_pointer = @ring.head
@ring_pointer.str
else
nil
end
end
def yank_pop
if @state == State::YANK
prev_yank = @ring_pointer.str
@ring_pointer = @ring_pointer.backward
[@ring_pointer.str, prev_yank]
else
nil
end
end
def each
start = head = @ring.head
loop do
break if head.nil?
yield head.str
head = head.backward
break if head == start
end
end
end
share/ruby/reline/unicode.rb 0000644 00000051536 15173547335 0012107 0 ustar 00 class Reline::Unicode
EscapedPairs = {
0x00 => '^@',
0x01 => '^A', # C-a
0x02 => '^B',
0x03 => '^C',
0x04 => '^D',
0x05 => '^E',
0x06 => '^F',
0x07 => '^G',
0x08 => '^H', # Backspace
0x09 => '^I',
0x0A => '^J',
0x0B => '^K',
0x0C => '^L',
0x0D => '^M', # Enter
0x0E => '^N',
0x0F => '^O',
0x10 => '^P',
0x11 => '^Q',
0x12 => '^R',
0x13 => '^S',
0x14 => '^T',
0x15 => '^U',
0x16 => '^V',
0x17 => '^W',
0x18 => '^X',
0x19 => '^Y',
0x1A => '^Z', # C-z
0x1B => '^[', # C-[ C-3
0x1D => '^]', # C-]
0x1E => '^^', # C-~ C-6
0x1F => '^_', # C-_ C-7
0x7F => '^?', # C-? C-8
}
EscapedChars = EscapedPairs.keys.map(&:chr)
NON_PRINTING_START = "\1"
NON_PRINTING_END = "\2"
CSI_REGEXP = /\e\[[\d;]*[ABCDEFGHJKSTfminsuhl]/
OSC_REGEXP = /\e\]\d+(?:;[^;]+)*\a/
WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
NON_PRINTING_START_INDEX = 0
NON_PRINTING_END_INDEX = 1
CSI_REGEXP_INDEX = 2
OSC_REGEXP_INDEX = 3
GRAPHEME_CLUSTER_INDEX = 4
def self.get_mbchar_byte_size_by_first_char(c)
# Checks UTF-8 character byte size
case c.ord
# 0b0xxxxxxx
when ->(code) { (code ^ 0b10000000).allbits?(0b10000000) } then 1
# 0b110xxxxx
when ->(code) { (code ^ 0b00100000).allbits?(0b11100000) } then 2
# 0b1110xxxx
when ->(code) { (code ^ 0b00010000).allbits?(0b11110000) } then 3
# 0b11110xxx
when ->(code) { (code ^ 0b00001000).allbits?(0b11111000) } then 4
# 0b111110xx
when ->(code) { (code ^ 0b00000100).allbits?(0b11111100) } then 5
# 0b1111110x
when ->(code) { (code ^ 0b00000010).allbits?(0b11111110) } then 6
# successor of mbchar
else 0
end
end
def self.escape_for_print(str)
str.chars.map! { |gr|
escaped = EscapedPairs[gr.ord]
if escaped && gr != -"\n" && gr != -"\t"
escaped
else
gr
end
}.join
end
require 'reline/unicode/east_asian_width'
HalfwidthDakutenHandakuten = /[\u{FF9E}\u{FF9F}]/
MBCharWidthRE = /
(?<width_2_1>
[#{ EscapedChars.map {|c| "\\x%02x" % c.ord }.join }] (?# ^ + char, such as ^M, ^H, ^[, ...)
)
| (?<width_3>^\u{2E3B}) (?# THREE-EM DASH)
| (?<width_0>^\p{M})
| (?<width_2_2>
#{ EastAsianWidth::TYPE_F }
| #{ EastAsianWidth::TYPE_W }
)
| (?<width_1>
#{ EastAsianWidth::TYPE_H }
| #{ EastAsianWidth::TYPE_NA }
| #{ EastAsianWidth::TYPE_N }
)(?!#{ HalfwidthDakutenHandakuten })
| (?<width_2_3>
(?: #{ EastAsianWidth::TYPE_H }
| #{ EastAsianWidth::TYPE_NA }
| #{ EastAsianWidth::TYPE_N })
#{ HalfwidthDakutenHandakuten }
)
| (?<ambiguous_width>
#{EastAsianWidth::TYPE_A}
)
/x
def self.get_mbchar_width(mbchar)
ord = mbchar.ord
if (0x00 <= ord and ord <= 0x1F) # in EscapedPairs
return 2
elsif (0x20 <= ord and ord <= 0x7E) # printable ASCII chars
return 1
end
m = mbchar.encode(Encoding::UTF_8).match(MBCharWidthRE)
case
when m.nil? then 1 # TODO should be U+FFFD � REPLACEMENT CHARACTER
when m[:width_2_1], m[:width_2_2], m[:width_2_3] then 2
when m[:width_3] then 3
when m[:width_0] then 0
when m[:width_1] then 1
when m[:ambiguous_width] then Reline.ambiguous_width
else
nil
end
end
def self.calculate_width(str, allow_escape_code = false)
if allow_escape_code
width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
rest.scan(WIDTH_SCANNER) do |gc|
case
when gc[NON_PRINTING_START_INDEX]
in_zero_width = true
when gc[NON_PRINTING_END_INDEX]
in_zero_width = false
when gc[CSI_REGEXP_INDEX], gc[OSC_REGEXP_INDEX]
when gc[GRAPHEME_CLUSTER_INDEX]
gc = gc[GRAPHEME_CLUSTER_INDEX]
unless in_zero_width
width += get_mbchar_width(gc)
end
end
end
width
else
str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
w + get_mbchar_width(gc)
}
end
end
def self.split_by_width(str, max_width, encoding = str.encoding)
lines = [String.new(encoding: encoding)]
height = 1
width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
rest.scan(WIDTH_SCANNER) do |gc|
case
when gc[NON_PRINTING_START_INDEX]
in_zero_width = true
when gc[NON_PRINTING_END_INDEX]
in_zero_width = false
when gc[CSI_REGEXP_INDEX]
lines.last << gc[CSI_REGEXP_INDEX]
when gc[OSC_REGEXP_INDEX]
lines.last << gc[OSC_REGEXP_INDEX]
when gc[GRAPHEME_CLUSTER_INDEX]
gc = gc[GRAPHEME_CLUSTER_INDEX]
unless in_zero_width
mbchar_width = get_mbchar_width(gc)
if (width += mbchar_width) > max_width
width = mbchar_width
lines << nil
lines << String.new(encoding: encoding)
height += 1
end
end
lines.last << gc
end
end
# The cursor moves to next line in first
if width == max_width
lines << nil
lines << String.new(encoding: encoding)
height += 1
end
[lines, height]
end
# Take a chunk of a String cut by width with escape sequences.
def self.take_range(str, start_col, max_width, encoding = str.encoding)
chunk = String.new(encoding: encoding)
total_width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
rest.scan(WIDTH_SCANNER) do |gc|
case
when gc[NON_PRINTING_START_INDEX]
in_zero_width = true
when gc[NON_PRINTING_END_INDEX]
in_zero_width = false
when gc[CSI_REGEXP_INDEX]
chunk << gc[CSI_REGEXP_INDEX]
when gc[OSC_REGEXP_INDEX]
chunk << gc[OSC_REGEXP_INDEX]
when gc[GRAPHEME_CLUSTER_INDEX]
gc = gc[GRAPHEME_CLUSTER_INDEX]
if in_zero_width
chunk << gc
else
mbchar_width = get_mbchar_width(gc)
total_width += mbchar_width
break if (start_col + max_width) < total_width
chunk << gc if start_col < total_width
end
end
end
chunk
end
def self.get_next_mbchar_size(line, byte_pointer)
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
grapheme ? grapheme.bytesize : 0
end
def self.get_prev_mbchar_size(line, byte_pointer)
if byte_pointer.zero?
0
else
grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last
grapheme ? grapheme.bytesize : 0
end
end
def self.em_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_forward_word_with_capitalization(line, byte_pointer)
width = 0
byte_size = 0
new_str = String.new
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
new_str += mbchar
width += get_mbchar_width(mbchar)
byte_size += size
end
first = true
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
if first
new_str += mbchar.upcase
first = false
else
new_str += mbchar.downcase
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width, new_str]
end
def self.em_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.ed_transpose_words(line, byte_pointer)
right_word_start = nil
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if size.zero?
# ' aaa bbb [cursor]'
byte_size = 0
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
# ' aaa bb[cursor]b'
byte_size = 0
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
else
byte_size = 0
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size += size
end
if (byte_pointer + byte_size) == (line.bytesize - 1)
# ' aaa bbb [cursor] '
after_start = line.bytesize
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
else
# ' aaa [cursor] bbb '
right_word_start = byte_pointer + byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
end
end
byte_size = right_word_start - byte_pointer
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
middle_start = byte_pointer + byte_size
byte_size = middle_start - byte_pointer
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
left_word_start = byte_pointer + byte_size
[left_word_start, middle_start, right_word_start, after_start]
end
def self.vi_big_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_big_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false)
if line.bytesize > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :space
break if mbchar =~ /\S/
when :non_word_printable
break if mbchar =~ /\w|\s/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
return [byte_size, width] if drop_terminate_spaces
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
if (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\w/
second = :word
elsif mbchar =~ /\s/
second = :space
else
second = :non_word_printable
end
second_width = get_mbchar_width(mbchar)
second_byte_size = size
else
return [byte_size, width]
end
if second == :space
width += second_width
byte_size += second_byte_size
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
else
case [started_by, second]
when [:word, :non_word_printable], [:non_word_printable, :word]
started_by = second
else
width += second_width
byte_size += second_byte_size
started_by = second
end
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_first_print(line)
width = 0
byte_size = 0
while (line.bytesize - 1) > byte_size
size = get_next_mbchar_size(line, byte_size)
mbchar = line.byteslice(byte_size, size)
if mbchar =~ /\S/
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
end
share/ruby/reline/unicode/east_asian_width.rb 0000644 00000056631 15173547335 0015416 0 ustar 00 class Reline::Unicode::EastAsianWidth
# This is based on EastAsianWidth.txt
# EastAsianWidth.txt
# Fullwidth
TYPE_F = /^[#{ %W(
\u{3000}
\u{FF01}-\u{FF60}
\u{FFE0}-\u{FFE6}
).join }]/
# Halfwidth
TYPE_H = /^[#{ %W(
\u{20A9}
\u{FF61}-\u{FFBE}
\u{FFC2}-\u{FFC7}
\u{FFCA}-\u{FFCF}
\u{FFD2}-\u{FFD7}
\u{FFDA}-\u{FFDC}
\u{FFE8}-\u{FFEE}
).join }]/
# Wide
TYPE_W = /^[#{ %W(
\u{1100}-\u{115F}
\u{231A}-\u{231B}
\u{2329}-\u{232A}
\u{23E9}-\u{23EC}
\u{23F0}
\u{23F3}
\u{25FD}-\u{25FE}
\u{2614}-\u{2615}
\u{2648}-\u{2653}
\u{267F}
\u{2693}
\u{26A1}
\u{26AA}-\u{26AB}
\u{26BD}-\u{26BE}
\u{26C4}-\u{26C5}
\u{26CE}
\u{26D4}
\u{26EA}
\u{26F2}-\u{26F3}
\u{26F5}
\u{26FA}
\u{26FD}
\u{2705}
\u{270A}-\u{270B}
\u{2728}
\u{274C}
\u{274E}
\u{2753}-\u{2755}
\u{2757}
\u{2795}-\u{2797}
\u{27B0}
\u{27BF}
\u{2B1B}-\u{2B1C}
\u{2B50}
\u{2B55}
\u{2E80}-\u{2E99}
\u{2E9B}-\u{2EF3}
\u{2F00}-\u{2FD5}
\u{2FF0}-\u{2FFB}
\u{3001}-\u{303E}
\u{3041}-\u{3096}
\u{3099}-\u{30FF}
\u{3105}-\u{312F}
\u{3131}-\u{318E}
\u{3190}-\u{31E3}
\u{31F0}-\u{321E}
\u{3220}-\u{3247}
\u{3250}-\u{4DBF}
\u{4E00}-\u{A48C}
\u{A490}-\u{A4C6}
\u{A960}-\u{A97C}
\u{AC00}-\u{D7A3}
\u{F900}-\u{FAFF}
\u{FE10}-\u{FE19}
\u{FE30}-\u{FE52}
\u{FE54}-\u{FE66}
\u{FE68}-\u{FE6B}
\u{16FE0}-\u{16FE4}
\u{16FF0}-\u{16FF1}
\u{17000}-\u{187F7}
\u{18800}-\u{18CD5}
\u{18D00}-\u{18D08}
\u{1B000}-\u{1B11E}
\u{1B150}-\u{1B152}
\u{1B164}-\u{1B167}
\u{1B170}-\u{1B2FB}
\u{1F004}
\u{1F0CF}
\u{1F18E}
\u{1F191}-\u{1F19A}
\u{1F200}-\u{1F202}
\u{1F210}-\u{1F23B}
\u{1F240}-\u{1F248}
\u{1F250}-\u{1F251}
\u{1F260}-\u{1F265}
\u{1F300}-\u{1F320}
\u{1F32D}-\u{1F335}
\u{1F337}-\u{1F37C}
\u{1F37E}-\u{1F393}
\u{1F3A0}-\u{1F3CA}
\u{1F3CF}-\u{1F3D3}
\u{1F3E0}-\u{1F3F0}
\u{1F3F4}
\u{1F3F8}-\u{1F43E}
\u{1F440}
\u{1F442}-\u{1F4FC}
\u{1F4FF}-\u{1F53D}
\u{1F54B}-\u{1F54E}
\u{1F550}-\u{1F567}
\u{1F57A}
\u{1F595}-\u{1F596}
\u{1F5A4}
\u{1F5FB}-\u{1F64F}
\u{1F680}-\u{1F6C5}
\u{1F6CC}
\u{1F6D0}-\u{1F6D2}
\u{1F6D5}-\u{1F6D7}
\u{1F6EB}-\u{1F6EC}
\u{1F6F4}-\u{1F6FC}
\u{1F7E0}-\u{1F7EB}
\u{1F90C}-\u{1F93A}
\u{1F93C}-\u{1F945}
\u{1F947}-\u{1F978}
\u{1F97A}-\u{1F9CB}
\u{1F9CD}-\u{1F9FF}
\u{1FA70}-\u{1FA74}
\u{1FA78}-\u{1FA7A}
\u{1FA80}-\u{1FA86}
\u{1FA90}-\u{1FAA8}
\u{1FAB0}-\u{1FAB6}
\u{1FAC0}-\u{1FAC2}
\u{1FAD0}-\u{1FAD6}
\u{20000}-\u{2FFFD}
\u{30000}-\u{3FFFD}
).join }]/
# Narrow
TYPE_NA = /^[#{ %W(
\u{0020}-\u{007E}
\u{00A2}-\u{00A3}
\u{00A5}-\u{00A6}
\u{00AC}
\u{00AF}
\u{27E6}-\u{27ED}
\u{2985}-\u{2986}
).join }]/
# Ambiguous
TYPE_A = /^[#{ %W(
\u{00A1}
\u{00A4}
\u{00A7}-\u{00A8}
\u{00AA}
\u{00AD}-\u{00AE}
\u{00B0}-\u{00B4}
\u{00B6}-\u{00BA}
\u{00BC}-\u{00BF}
\u{00C6}
\u{00D0}
\u{00D7}-\u{00D8}
\u{00DE}-\u{00E1}
\u{00E6}
\u{00E8}-\u{00EA}
\u{00EC}-\u{00ED}
\u{00F0}
\u{00F2}-\u{00F3}
\u{00F7}-\u{00FA}
\u{00FC}
\u{00FE}
\u{0101}
\u{0111}
\u{0113}
\u{011B}
\u{0126}-\u{0127}
\u{012B}
\u{0131}-\u{0133}
\u{0138}
\u{013F}-\u{0142}
\u{0144}
\u{0148}-\u{014B}
\u{014D}
\u{0152}-\u{0153}
\u{0166}-\u{0167}
\u{016B}
\u{01CE}
\u{01D0}
\u{01D2}
\u{01D4}
\u{01D6}
\u{01D8}
\u{01DA}
\u{01DC}
\u{0251}
\u{0261}
\u{02C4}
\u{02C7}
\u{02C9}-\u{02CB}
\u{02CD}
\u{02D0}
\u{02D8}-\u{02DB}
\u{02DD}
\u{02DF}
\u{0300}-\u{036F}
\u{0391}-\u{03A1}
\u{03A3}-\u{03A9}
\u{03B1}-\u{03C1}
\u{03C3}-\u{03C9}
\u{0401}
\u{0410}-\u{044F}
\u{0451}
\u{2010}
\u{2013}-\u{2016}
\u{2018}-\u{2019}
\u{201C}-\u{201D}
\u{2020}-\u{2022}
\u{2024}-\u{2027}
\u{2030}
\u{2032}-\u{2033}
\u{2035}
\u{203B}
\u{203E}
\u{2074}
\u{207F}
\u{2081}-\u{2084}
\u{20AC}
\u{2103}
\u{2105}
\u{2109}
\u{2113}
\u{2116}
\u{2121}-\u{2122}
\u{2126}
\u{212B}
\u{2153}-\u{2154}
\u{215B}-\u{215E}
\u{2160}-\u{216B}
\u{2170}-\u{2179}
\u{2189}
\u{2190}-\u{2199}
\u{21B8}-\u{21B9}
\u{21D2}
\u{21D4}
\u{21E7}
\u{2200}
\u{2202}-\u{2203}
\u{2207}-\u{2208}
\u{220B}
\u{220F}
\u{2211}
\u{2215}
\u{221A}
\u{221D}-\u{2220}
\u{2223}
\u{2225}
\u{2227}-\u{222C}
\u{222E}
\u{2234}-\u{2237}
\u{223C}-\u{223D}
\u{2248}
\u{224C}
\u{2252}
\u{2260}-\u{2261}
\u{2264}-\u{2267}
\u{226A}-\u{226B}
\u{226E}-\u{226F}
\u{2282}-\u{2283}
\u{2286}-\u{2287}
\u{2295}
\u{2299}
\u{22A5}
\u{22BF}
\u{2312}
\u{2460}-\u{24E9}
\u{24EB}-\u{254B}
\u{2550}-\u{2573}
\u{2580}-\u{258F}
\u{2592}-\u{2595}
\u{25A0}-\u{25A1}
\u{25A3}-\u{25A9}
\u{25B2}-\u{25B3}
\u{25B6}-\u{25B7}
\u{25BC}-\u{25BD}
\u{25C0}-\u{25C1}
\u{25C6}-\u{25C8}
\u{25CB}
\u{25CE}-\u{25D1}
\u{25E2}-\u{25E5}
\u{25EF}
\u{2605}-\u{2606}
\u{2609}
\u{260E}-\u{260F}
\u{261C}
\u{261E}
\u{2640}
\u{2642}
\u{2660}-\u{2661}
\u{2663}-\u{2665}
\u{2667}-\u{266A}
\u{266C}-\u{266D}
\u{266F}
\u{269E}-\u{269F}
\u{26BF}
\u{26C6}-\u{26CD}
\u{26CF}-\u{26D3}
\u{26D5}-\u{26E1}
\u{26E3}
\u{26E8}-\u{26E9}
\u{26EB}-\u{26F1}
\u{26F4}
\u{26F6}-\u{26F9}
\u{26FB}-\u{26FC}
\u{26FE}-\u{26FF}
\u{273D}
\u{2776}-\u{277F}
\u{2B56}-\u{2B59}
\u{3248}-\u{324F}
\u{E000}-\u{F8FF}
\u{FE00}-\u{FE0F}
\u{FFFD}
\u{1F100}-\u{1F10A}
\u{1F110}-\u{1F12D}
\u{1F130}-\u{1F169}
\u{1F170}-\u{1F18D}
\u{1F18F}-\u{1F190}
\u{1F19B}-\u{1F1AC}
\u{E0100}-\u{E01EF}
\u{F0000}-\u{FFFFD}
\u{100000}-\u{10FFFD}
).join }]/
# Neutral
TYPE_N = /^[#{ %W(
\u{0000}-\u{001F}
\u{007F}-\u{00A0}
\u{00A9}
\u{00AB}
\u{00B5}
\u{00BB}
\u{00C0}-\u{00C5}
\u{00C7}-\u{00CF}
\u{00D1}-\u{00D6}
\u{00D9}-\u{00DD}
\u{00E2}-\u{00E5}
\u{00E7}
\u{00EB}
\u{00EE}-\u{00EF}
\u{00F1}
\u{00F4}-\u{00F6}
\u{00FB}
\u{00FD}
\u{00FF}-\u{0100}
\u{0102}-\u{0110}
\u{0112}
\u{0114}-\u{011A}
\u{011C}-\u{0125}
\u{0128}-\u{012A}
\u{012C}-\u{0130}
\u{0134}-\u{0137}
\u{0139}-\u{013E}
\u{0143}
\u{0145}-\u{0147}
\u{014C}
\u{014E}-\u{0151}
\u{0154}-\u{0165}
\u{0168}-\u{016A}
\u{016C}-\u{01CD}
\u{01CF}
\u{01D1}
\u{01D3}
\u{01D5}
\u{01D7}
\u{01D9}
\u{01DB}
\u{01DD}-\u{0250}
\u{0252}-\u{0260}
\u{0262}-\u{02C3}
\u{02C5}-\u{02C6}
\u{02C8}
\u{02CC}
\u{02CE}-\u{02CF}
\u{02D1}-\u{02D7}
\u{02DC}
\u{02DE}
\u{02E0}-\u{02FF}
\u{0370}-\u{0377}
\u{037A}-\u{037F}
\u{0384}-\u{038A}
\u{038C}
\u{038E}-\u{0390}
\u{03AA}-\u{03B0}
\u{03C2}
\u{03CA}-\u{0400}
\u{0402}-\u{040F}
\u{0450}
\u{0452}-\u{052F}
\u{0531}-\u{0556}
\u{0559}-\u{058A}
\u{058D}-\u{058F}
\u{0591}-\u{05C7}
\u{05D0}-\u{05EA}
\u{05EF}-\u{05F4}
\u{0600}-\u{061C}
\u{061E}-\u{070D}
\u{070F}-\u{074A}
\u{074D}-\u{07B1}
\u{07C0}-\u{07FA}
\u{07FD}-\u{082D}
\u{0830}-\u{083E}
\u{0840}-\u{085B}
\u{085E}
\u{0860}-\u{086A}
\u{08A0}-\u{08B4}
\u{08B6}-\u{08C7}
\u{08D3}-\u{0983}
\u{0985}-\u{098C}
\u{098F}-\u{0990}
\u{0993}-\u{09A8}
\u{09AA}-\u{09B0}
\u{09B2}
\u{09B6}-\u{09B9}
\u{09BC}-\u{09C4}
\u{09C7}-\u{09C8}
\u{09CB}-\u{09CE}
\u{09D7}
\u{09DC}-\u{09DD}
\u{09DF}-\u{09E3}
\u{09E6}-\u{09FE}
\u{0A01}-\u{0A03}
\u{0A05}-\u{0A0A}
\u{0A0F}-\u{0A10}
\u{0A13}-\u{0A28}
\u{0A2A}-\u{0A30}
\u{0A32}-\u{0A33}
\u{0A35}-\u{0A36}
\u{0A38}-\u{0A39}
\u{0A3C}
\u{0A3E}-\u{0A42}
\u{0A47}-\u{0A48}
\u{0A4B}-\u{0A4D}
\u{0A51}
\u{0A59}-\u{0A5C}
\u{0A5E}
\u{0A66}-\u{0A76}
\u{0A81}-\u{0A83}
\u{0A85}-\u{0A8D}
\u{0A8F}-\u{0A91}
\u{0A93}-\u{0AA8}
\u{0AAA}-\u{0AB0}
\u{0AB2}-\u{0AB3}
\u{0AB5}-\u{0AB9}
\u{0ABC}-\u{0AC5}
\u{0AC7}-\u{0AC9}
\u{0ACB}-\u{0ACD}
\u{0AD0}
\u{0AE0}-\u{0AE3}
\u{0AE6}-\u{0AF1}
\u{0AF9}-\u{0AFF}
\u{0B01}-\u{0B03}
\u{0B05}-\u{0B0C}
\u{0B0F}-\u{0B10}
\u{0B13}-\u{0B28}
\u{0B2A}-\u{0B30}
\u{0B32}-\u{0B33}
\u{0B35}-\u{0B39}
\u{0B3C}-\u{0B44}
\u{0B47}-\u{0B48}
\u{0B4B}-\u{0B4D}
\u{0B55}-\u{0B57}
\u{0B5C}-\u{0B5D}
\u{0B5F}-\u{0B63}
\u{0B66}-\u{0B77}
\u{0B82}-\u{0B83}
\u{0B85}-\u{0B8A}
\u{0B8E}-\u{0B90}
\u{0B92}-\u{0B95}
\u{0B99}-\u{0B9A}
\u{0B9C}
\u{0B9E}-\u{0B9F}
\u{0BA3}-\u{0BA4}
\u{0BA8}-\u{0BAA}
\u{0BAE}-\u{0BB9}
\u{0BBE}-\u{0BC2}
\u{0BC6}-\u{0BC8}
\u{0BCA}-\u{0BCD}
\u{0BD0}
\u{0BD7}
\u{0BE6}-\u{0BFA}
\u{0C00}-\u{0C0C}
\u{0C0E}-\u{0C10}
\u{0C12}-\u{0C28}
\u{0C2A}-\u{0C39}
\u{0C3D}-\u{0C44}
\u{0C46}-\u{0C48}
\u{0C4A}-\u{0C4D}
\u{0C55}-\u{0C56}
\u{0C58}-\u{0C5A}
\u{0C60}-\u{0C63}
\u{0C66}-\u{0C6F}
\u{0C77}-\u{0C8C}
\u{0C8E}-\u{0C90}
\u{0C92}-\u{0CA8}
\u{0CAA}-\u{0CB3}
\u{0CB5}-\u{0CB9}
\u{0CBC}-\u{0CC4}
\u{0CC6}-\u{0CC8}
\u{0CCA}-\u{0CCD}
\u{0CD5}-\u{0CD6}
\u{0CDE}
\u{0CE0}-\u{0CE3}
\u{0CE6}-\u{0CEF}
\u{0CF1}-\u{0CF2}
\u{0D00}-\u{0D0C}
\u{0D0E}-\u{0D10}
\u{0D12}-\u{0D44}
\u{0D46}-\u{0D48}
\u{0D4A}-\u{0D4F}
\u{0D54}-\u{0D63}
\u{0D66}-\u{0D7F}
\u{0D81}-\u{0D83}
\u{0D85}-\u{0D96}
\u{0D9A}-\u{0DB1}
\u{0DB3}-\u{0DBB}
\u{0DBD}
\u{0DC0}-\u{0DC6}
\u{0DCA}
\u{0DCF}-\u{0DD4}
\u{0DD6}
\u{0DD8}-\u{0DDF}
\u{0DE6}-\u{0DEF}
\u{0DF2}-\u{0DF4}
\u{0E01}-\u{0E3A}
\u{0E3F}-\u{0E5B}
\u{0E81}-\u{0E82}
\u{0E84}
\u{0E86}-\u{0E8A}
\u{0E8C}-\u{0EA3}
\u{0EA5}
\u{0EA7}-\u{0EBD}
\u{0EC0}-\u{0EC4}
\u{0EC6}
\u{0EC8}-\u{0ECD}
\u{0ED0}-\u{0ED9}
\u{0EDC}-\u{0EDF}
\u{0F00}-\u{0F47}
\u{0F49}-\u{0F6C}
\u{0F71}-\u{0F97}
\u{0F99}-\u{0FBC}
\u{0FBE}-\u{0FCC}
\u{0FCE}-\u{0FDA}
\u{1000}-\u{10C5}
\u{10C7}
\u{10CD}
\u{10D0}-\u{10FF}
\u{1160}-\u{1248}
\u{124A}-\u{124D}
\u{1250}-\u{1256}
\u{1258}
\u{125A}-\u{125D}
\u{1260}-\u{1288}
\u{128A}-\u{128D}
\u{1290}-\u{12B0}
\u{12B2}-\u{12B5}
\u{12B8}-\u{12BE}
\u{12C0}
\u{12C2}-\u{12C5}
\u{12C8}-\u{12D6}
\u{12D8}-\u{1310}
\u{1312}-\u{1315}
\u{1318}-\u{135A}
\u{135D}-\u{137C}
\u{1380}-\u{1399}
\u{13A0}-\u{13F5}
\u{13F8}-\u{13FD}
\u{1400}-\u{169C}
\u{16A0}-\u{16F8}
\u{1700}-\u{170C}
\u{170E}-\u{1714}
\u{1720}-\u{1736}
\u{1740}-\u{1753}
\u{1760}-\u{176C}
\u{176E}-\u{1770}
\u{1772}-\u{1773}
\u{1780}-\u{17DD}
\u{17E0}-\u{17E9}
\u{17F0}-\u{17F9}
\u{1800}-\u{180E}
\u{1810}-\u{1819}
\u{1820}-\u{1878}
\u{1880}-\u{18AA}
\u{18B0}-\u{18F5}
\u{1900}-\u{191E}
\u{1920}-\u{192B}
\u{1930}-\u{193B}
\u{1940}
\u{1944}-\u{196D}
\u{1970}-\u{1974}
\u{1980}-\u{19AB}
\u{19B0}-\u{19C9}
\u{19D0}-\u{19DA}
\u{19DE}-\u{1A1B}
\u{1A1E}-\u{1A5E}
\u{1A60}-\u{1A7C}
\u{1A7F}-\u{1A89}
\u{1A90}-\u{1A99}
\u{1AA0}-\u{1AAD}
\u{1AB0}-\u{1AC0}
\u{1B00}-\u{1B4B}
\u{1B50}-\u{1B7C}
\u{1B80}-\u{1BF3}
\u{1BFC}-\u{1C37}
\u{1C3B}-\u{1C49}
\u{1C4D}-\u{1C88}
\u{1C90}-\u{1CBA}
\u{1CBD}-\u{1CC7}
\u{1CD0}-\u{1CFA}
\u{1D00}-\u{1DF9}
\u{1DFB}-\u{1F15}
\u{1F18}-\u{1F1D}
\u{1F20}-\u{1F45}
\u{1F48}-\u{1F4D}
\u{1F50}-\u{1F57}
\u{1F59}
\u{1F5B}
\u{1F5D}
\u{1F5F}-\u{1F7D}
\u{1F80}-\u{1FB4}
\u{1FB6}-\u{1FC4}
\u{1FC6}-\u{1FD3}
\u{1FD6}-\u{1FDB}
\u{1FDD}-\u{1FEF}
\u{1FF2}-\u{1FF4}
\u{1FF6}-\u{1FFE}
\u{2000}-\u{200F}
\u{2011}-\u{2012}
\u{2017}
\u{201A}-\u{201B}
\u{201E}-\u{201F}
\u{2023}
\u{2028}-\u{202F}
\u{2031}
\u{2034}
\u{2036}-\u{203A}
\u{203C}-\u{203D}
\u{203F}-\u{2064}
\u{2066}-\u{2071}
\u{2075}-\u{207E}
\u{2080}
\u{2085}-\u{208E}
\u{2090}-\u{209C}
\u{20A0}-\u{20A8}
\u{20AA}-\u{20AB}
\u{20AD}-\u{20BF}
\u{20D0}-\u{20F0}
\u{2100}-\u{2102}
\u{2104}
\u{2106}-\u{2108}
\u{210A}-\u{2112}
\u{2114}-\u{2115}
\u{2117}-\u{2120}
\u{2123}-\u{2125}
\u{2127}-\u{212A}
\u{212C}-\u{2152}
\u{2155}-\u{215A}
\u{215F}
\u{216C}-\u{216F}
\u{217A}-\u{2188}
\u{218A}-\u{218B}
\u{219A}-\u{21B7}
\u{21BA}-\u{21D1}
\u{21D3}
\u{21D5}-\u{21E6}
\u{21E8}-\u{21FF}
\u{2201}
\u{2204}-\u{2206}
\u{2209}-\u{220A}
\u{220C}-\u{220E}
\u{2210}
\u{2212}-\u{2214}
\u{2216}-\u{2219}
\u{221B}-\u{221C}
\u{2221}-\u{2222}
\u{2224}
\u{2226}
\u{222D}
\u{222F}-\u{2233}
\u{2238}-\u{223B}
\u{223E}-\u{2247}
\u{2249}-\u{224B}
\u{224D}-\u{2251}
\u{2253}-\u{225F}
\u{2262}-\u{2263}
\u{2268}-\u{2269}
\u{226C}-\u{226D}
\u{2270}-\u{2281}
\u{2284}-\u{2285}
\u{2288}-\u{2294}
\u{2296}-\u{2298}
\u{229A}-\u{22A4}
\u{22A6}-\u{22BE}
\u{22C0}-\u{2311}
\u{2313}-\u{2319}
\u{231C}-\u{2328}
\u{232B}-\u{23E8}
\u{23ED}-\u{23EF}
\u{23F1}-\u{23F2}
\u{23F4}-\u{2426}
\u{2440}-\u{244A}
\u{24EA}
\u{254C}-\u{254F}
\u{2574}-\u{257F}
\u{2590}-\u{2591}
\u{2596}-\u{259F}
\u{25A2}
\u{25AA}-\u{25B1}
\u{25B4}-\u{25B5}
\u{25B8}-\u{25BB}
\u{25BE}-\u{25BF}
\u{25C2}-\u{25C5}
\u{25C9}-\u{25CA}
\u{25CC}-\u{25CD}
\u{25D2}-\u{25E1}
\u{25E6}-\u{25EE}
\u{25F0}-\u{25FC}
\u{25FF}-\u{2604}
\u{2607}-\u{2608}
\u{260A}-\u{260D}
\u{2610}-\u{2613}
\u{2616}-\u{261B}
\u{261D}
\u{261F}-\u{263F}
\u{2641}
\u{2643}-\u{2647}
\u{2654}-\u{265F}
\u{2662}
\u{2666}
\u{266B}
\u{266E}
\u{2670}-\u{267E}
\u{2680}-\u{2692}
\u{2694}-\u{269D}
\u{26A0}
\u{26A2}-\u{26A9}
\u{26AC}-\u{26BC}
\u{26C0}-\u{26C3}
\u{26E2}
\u{26E4}-\u{26E7}
\u{2700}-\u{2704}
\u{2706}-\u{2709}
\u{270C}-\u{2727}
\u{2729}-\u{273C}
\u{273E}-\u{274B}
\u{274D}
\u{274F}-\u{2752}
\u{2756}
\u{2758}-\u{2775}
\u{2780}-\u{2794}
\u{2798}-\u{27AF}
\u{27B1}-\u{27BE}
\u{27C0}-\u{27E5}
\u{27EE}-\u{2984}
\u{2987}-\u{2B1A}
\u{2B1D}-\u{2B4F}
\u{2B51}-\u{2B54}
\u{2B5A}-\u{2B73}
\u{2B76}-\u{2B95}
\u{2B97}-\u{2C2E}
\u{2C30}-\u{2C5E}
\u{2C60}-\u{2CF3}
\u{2CF9}-\u{2D25}
\u{2D27}
\u{2D2D}
\u{2D30}-\u{2D67}
\u{2D6F}-\u{2D70}
\u{2D7F}-\u{2D96}
\u{2DA0}-\u{2DA6}
\u{2DA8}-\u{2DAE}
\u{2DB0}-\u{2DB6}
\u{2DB8}-\u{2DBE}
\u{2DC0}-\u{2DC6}
\u{2DC8}-\u{2DCE}
\u{2DD0}-\u{2DD6}
\u{2DD8}-\u{2DDE}
\u{2DE0}-\u{2E52}
\u{303F}
\u{4DC0}-\u{4DFF}
\u{A4D0}-\u{A62B}
\u{A640}-\u{A6F7}
\u{A700}-\u{A7BF}
\u{A7C2}-\u{A7CA}
\u{A7F5}-\u{A82C}
\u{A830}-\u{A839}
\u{A840}-\u{A877}
\u{A880}-\u{A8C5}
\u{A8CE}-\u{A8D9}
\u{A8E0}-\u{A953}
\u{A95F}
\u{A980}-\u{A9CD}
\u{A9CF}-\u{A9D9}
\u{A9DE}-\u{A9FE}
\u{AA00}-\u{AA36}
\u{AA40}-\u{AA4D}
\u{AA50}-\u{AA59}
\u{AA5C}-\u{AAC2}
\u{AADB}-\u{AAF6}
\u{AB01}-\u{AB06}
\u{AB09}-\u{AB0E}
\u{AB11}-\u{AB16}
\u{AB20}-\u{AB26}
\u{AB28}-\u{AB2E}
\u{AB30}-\u{AB6B}
\u{AB70}-\u{ABED}
\u{ABF0}-\u{ABF9}
\u{D7B0}-\u{D7C6}
\u{D7CB}-\u{D7FB}
\u{FB00}-\u{FB06}
\u{FB13}-\u{FB17}
\u{FB1D}-\u{FB36}
\u{FB38}-\u{FB3C}
\u{FB3E}
\u{FB40}-\u{FB41}
\u{FB43}-\u{FB44}
\u{FB46}-\u{FBC1}
\u{FBD3}-\u{FD3F}
\u{FD50}-\u{FD8F}
\u{FD92}-\u{FDC7}
\u{FDF0}-\u{FDFD}
\u{FE20}-\u{FE2F}
\u{FE70}-\u{FE74}
\u{FE76}-\u{FEFC}
\u{FEFF}
\u{FFF9}-\u{FFFC}
\u{10000}-\u{1000B}
\u{1000D}-\u{10026}
\u{10028}-\u{1003A}
\u{1003C}-\u{1003D}
\u{1003F}-\u{1004D}
\u{10050}-\u{1005D}
\u{10080}-\u{100FA}
\u{10100}-\u{10102}
\u{10107}-\u{10133}
\u{10137}-\u{1018E}
\u{10190}-\u{1019C}
\u{101A0}
\u{101D0}-\u{101FD}
\u{10280}-\u{1029C}
\u{102A0}-\u{102D0}
\u{102E0}-\u{102FB}
\u{10300}-\u{10323}
\u{1032D}-\u{1034A}
\u{10350}-\u{1037A}
\u{10380}-\u{1039D}
\u{1039F}-\u{103C3}
\u{103C8}-\u{103D5}
\u{10400}-\u{1049D}
\u{104A0}-\u{104A9}
\u{104B0}-\u{104D3}
\u{104D8}-\u{104FB}
\u{10500}-\u{10527}
\u{10530}-\u{10563}
\u{1056F}
\u{10600}-\u{10736}
\u{10740}-\u{10755}
\u{10760}-\u{10767}
\u{10800}-\u{10805}
\u{10808}
\u{1080A}-\u{10835}
\u{10837}-\u{10838}
\u{1083C}
\u{1083F}-\u{10855}
\u{10857}-\u{1089E}
\u{108A7}-\u{108AF}
\u{108E0}-\u{108F2}
\u{108F4}-\u{108F5}
\u{108FB}-\u{1091B}
\u{1091F}-\u{10939}
\u{1093F}
\u{10980}-\u{109B7}
\u{109BC}-\u{109CF}
\u{109D2}-\u{10A03}
\u{10A05}-\u{10A06}
\u{10A0C}-\u{10A13}
\u{10A15}-\u{10A17}
\u{10A19}-\u{10A35}
\u{10A38}-\u{10A3A}
\u{10A3F}-\u{10A48}
\u{10A50}-\u{10A58}
\u{10A60}-\u{10A9F}
\u{10AC0}-\u{10AE6}
\u{10AEB}-\u{10AF6}
\u{10B00}-\u{10B35}
\u{10B39}-\u{10B55}
\u{10B58}-\u{10B72}
\u{10B78}-\u{10B91}
\u{10B99}-\u{10B9C}
\u{10BA9}-\u{10BAF}
\u{10C00}-\u{10C48}
\u{10C80}-\u{10CB2}
\u{10CC0}-\u{10CF2}
\u{10CFA}-\u{10D27}
\u{10D30}-\u{10D39}
\u{10E60}-\u{10E7E}
\u{10E80}-\u{10EA9}
\u{10EAB}-\u{10EAD}
\u{10EB0}-\u{10EB1}
\u{10F00}-\u{10F27}
\u{10F30}-\u{10F59}
\u{10FB0}-\u{10FCB}
\u{10FE0}-\u{10FF6}
\u{11000}-\u{1104D}
\u{11052}-\u{1106F}
\u{1107F}-\u{110C1}
\u{110CD}
\u{110D0}-\u{110E8}
\u{110F0}-\u{110F9}
\u{11100}-\u{11134}
\u{11136}-\u{11147}
\u{11150}-\u{11176}
\u{11180}-\u{111DF}
\u{111E1}-\u{111F4}
\u{11200}-\u{11211}
\u{11213}-\u{1123E}
\u{11280}-\u{11286}
\u{11288}
\u{1128A}-\u{1128D}
\u{1128F}-\u{1129D}
\u{1129F}-\u{112A9}
\u{112B0}-\u{112EA}
\u{112F0}-\u{112F9}
\u{11300}-\u{11303}
\u{11305}-\u{1130C}
\u{1130F}-\u{11310}
\u{11313}-\u{11328}
\u{1132A}-\u{11330}
\u{11332}-\u{11333}
\u{11335}-\u{11339}
\u{1133B}-\u{11344}
\u{11347}-\u{11348}
\u{1134B}-\u{1134D}
\u{11350}
\u{11357}
\u{1135D}-\u{11363}
\u{11366}-\u{1136C}
\u{11370}-\u{11374}
\u{11400}-\u{1145B}
\u{1145D}-\u{11461}
\u{11480}-\u{114C7}
\u{114D0}-\u{114D9}
\u{11580}-\u{115B5}
\u{115B8}-\u{115DD}
\u{11600}-\u{11644}
\u{11650}-\u{11659}
\u{11660}-\u{1166C}
\u{11680}-\u{116B8}
\u{116C0}-\u{116C9}
\u{11700}-\u{1171A}
\u{1171D}-\u{1172B}
\u{11730}-\u{1173F}
\u{11800}-\u{1183B}
\u{118A0}-\u{118F2}
\u{118FF}-\u{11906}
\u{11909}
\u{1190C}-\u{11913}
\u{11915}-\u{11916}
\u{11918}-\u{11935}
\u{11937}-\u{11938}
\u{1193B}-\u{11946}
\u{11950}-\u{11959}
\u{119A0}-\u{119A7}
\u{119AA}-\u{119D7}
\u{119DA}-\u{119E4}
\u{11A00}-\u{11A47}
\u{11A50}-\u{11AA2}
\u{11AC0}-\u{11AF8}
\u{11C00}-\u{11C08}
\u{11C0A}-\u{11C36}
\u{11C38}-\u{11C45}
\u{11C50}-\u{11C6C}
\u{11C70}-\u{11C8F}
\u{11C92}-\u{11CA7}
\u{11CA9}-\u{11CB6}
\u{11D00}-\u{11D06}
\u{11D08}-\u{11D09}
\u{11D0B}-\u{11D36}
\u{11D3A}
\u{11D3C}-\u{11D3D}
\u{11D3F}-\u{11D47}
\u{11D50}-\u{11D59}
\u{11D60}-\u{11D65}
\u{11D67}-\u{11D68}
\u{11D6A}-\u{11D8E}
\u{11D90}-\u{11D91}
\u{11D93}-\u{11D98}
\u{11DA0}-\u{11DA9}
\u{11EE0}-\u{11EF8}
\u{11FB0}
\u{11FC0}-\u{11FF1}
\u{11FFF}-\u{12399}
\u{12400}-\u{1246E}
\u{12470}-\u{12474}
\u{12480}-\u{12543}
\u{13000}-\u{1342E}
\u{13430}-\u{13438}
\u{14400}-\u{14646}
\u{16800}-\u{16A38}
\u{16A40}-\u{16A5E}
\u{16A60}-\u{16A69}
\u{16A6E}-\u{16A6F}
\u{16AD0}-\u{16AED}
\u{16AF0}-\u{16AF5}
\u{16B00}-\u{16B45}
\u{16B50}-\u{16B59}
\u{16B5B}-\u{16B61}
\u{16B63}-\u{16B77}
\u{16B7D}-\u{16B8F}
\u{16E40}-\u{16E9A}
\u{16F00}-\u{16F4A}
\u{16F4F}-\u{16F87}
\u{16F8F}-\u{16F9F}
\u{1BC00}-\u{1BC6A}
\u{1BC70}-\u{1BC7C}
\u{1BC80}-\u{1BC88}
\u{1BC90}-\u{1BC99}
\u{1BC9C}-\u{1BCA3}
\u{1D000}-\u{1D0F5}
\u{1D100}-\u{1D126}
\u{1D129}-\u{1D1E8}
\u{1D200}-\u{1D245}
\u{1D2E0}-\u{1D2F3}
\u{1D300}-\u{1D356}
\u{1D360}-\u{1D378}
\u{1D400}-\u{1D454}
\u{1D456}-\u{1D49C}
\u{1D49E}-\u{1D49F}
\u{1D4A2}
\u{1D4A5}-\u{1D4A6}
\u{1D4A9}-\u{1D4AC}
\u{1D4AE}-\u{1D4B9}
\u{1D4BB}
\u{1D4BD}-\u{1D4C3}
\u{1D4C5}-\u{1D505}
\u{1D507}-\u{1D50A}
\u{1D50D}-\u{1D514}
\u{1D516}-\u{1D51C}
\u{1D51E}-\u{1D539}
\u{1D53B}-\u{1D53E}
\u{1D540}-\u{1D544}
\u{1D546}
\u{1D54A}-\u{1D550}
\u{1D552}-\u{1D6A5}
\u{1D6A8}-\u{1D7CB}
\u{1D7CE}-\u{1DA8B}
\u{1DA9B}-\u{1DA9F}
\u{1DAA1}-\u{1DAAF}
\u{1E000}-\u{1E006}
\u{1E008}-\u{1E018}
\u{1E01B}-\u{1E021}
\u{1E023}-\u{1E024}
\u{1E026}-\u{1E02A}
\u{1E100}-\u{1E12C}
\u{1E130}-\u{1E13D}
\u{1E140}-\u{1E149}
\u{1E14E}-\u{1E14F}
\u{1E2C0}-\u{1E2F9}
\u{1E2FF}
\u{1E800}-\u{1E8C4}
\u{1E8C7}-\u{1E8D6}
\u{1E900}-\u{1E94B}
\u{1E950}-\u{1E959}
\u{1E95E}-\u{1E95F}
\u{1EC71}-\u{1ECB4}
\u{1ED01}-\u{1ED3D}
\u{1EE00}-\u{1EE03}
\u{1EE05}-\u{1EE1F}
\u{1EE21}-\u{1EE22}
\u{1EE24}
\u{1EE27}
\u{1EE29}-\u{1EE32}
\u{1EE34}-\u{1EE37}
\u{1EE39}
\u{1EE3B}
\u{1EE42}
\u{1EE47}
\u{1EE49}
\u{1EE4B}
\u{1EE4D}-\u{1EE4F}
\u{1EE51}-\u{1EE52}
\u{1EE54}
\u{1EE57}
\u{1EE59}
\u{1EE5B}
\u{1EE5D}
\u{1EE5F}
\u{1EE61}-\u{1EE62}
\u{1EE64}
\u{1EE67}-\u{1EE6A}
\u{1EE6C}-\u{1EE72}
\u{1EE74}-\u{1EE77}
\u{1EE79}-\u{1EE7C}
\u{1EE7E}
\u{1EE80}-\u{1EE89}
\u{1EE8B}-\u{1EE9B}
\u{1EEA1}-\u{1EEA3}
\u{1EEA5}-\u{1EEA9}
\u{1EEAB}-\u{1EEBB}
\u{1EEF0}-\u{1EEF1}
\u{1F000}-\u{1F003}
\u{1F005}-\u{1F02B}
\u{1F030}-\u{1F093}
\u{1F0A0}-\u{1F0AE}
\u{1F0B1}-\u{1F0BF}
\u{1F0C1}-\u{1F0CE}
\u{1F0D1}-\u{1F0F5}
\u{1F10B}-\u{1F10F}
\u{1F12E}-\u{1F12F}
\u{1F16A}-\u{1F16F}
\u{1F1AD}
\u{1F1E6}-\u{1F1FF}
\u{1F321}-\u{1F32C}
\u{1F336}
\u{1F37D}
\u{1F394}-\u{1F39F}
\u{1F3CB}-\u{1F3CE}
\u{1F3D4}-\u{1F3DF}
\u{1F3F1}-\u{1F3F3}
\u{1F3F5}-\u{1F3F7}
\u{1F43F}
\u{1F441}
\u{1F4FD}-\u{1F4FE}
\u{1F53E}-\u{1F54A}
\u{1F54F}
\u{1F568}-\u{1F579}
\u{1F57B}-\u{1F594}
\u{1F597}-\u{1F5A3}
\u{1F5A5}-\u{1F5FA}
\u{1F650}-\u{1F67F}
\u{1F6C6}-\u{1F6CB}
\u{1F6CD}-\u{1F6CF}
\u{1F6D3}-\u{1F6D4}
\u{1F6E0}-\u{1F6EA}
\u{1F6F0}-\u{1F6F3}
\u{1F700}-\u{1F773}
\u{1F780}-\u{1F7D8}
\u{1F800}-\u{1F80B}
\u{1F810}-\u{1F847}
\u{1F850}-\u{1F859}
\u{1F860}-\u{1F887}
\u{1F890}-\u{1F8AD}
\u{1F8B0}-\u{1F8B1}
\u{1F900}-\u{1F90B}
\u{1F93B}
\u{1F946}
\u{1FA00}-\u{1FA53}
\u{1FA60}-\u{1FA6D}
\u{1FB00}-\u{1FB92}
\u{1FB94}-\u{1FBCA}
\u{1FBF0}-\u{1FBF9}
\u{E0001}
\u{E0020}-\u{E007F}
).join }]/
end
share/ruby/reline/key_actor.rb 0000644 00000000251 15173547335 0012425 0 ustar 00 module Reline::KeyActor
end
require 'reline/key_actor/base'
require 'reline/key_actor/emacs'
require 'reline/key_actor/vi_command'
require 'reline/key_actor/vi_insert'
share/ruby/reline/key_stroke.rb 0000644 00000005072 15173547335 0012632 0 ustar 00 class Reline::KeyStroke
def initialize(config)
@config = config
end
def compress_meta_key(ary)
return ary unless @config.convert_meta
ary.inject([]) { |result, key|
if result.size > 0 and result.last == "\e".ord
result[result.size - 1] = Reline::Key.new(key, key | 0b10000000, true)
else
result << key
end
result
}
end
def start_with?(me, other)
compressed_me = compress_meta_key(me)
compressed_other = compress_meta_key(other)
i = 0
loop do
my_c = compressed_me[i]
other_c = compressed_other[i]
other_is_last = (i + 1) == compressed_other.size
me_is_last = (i + 1) == compressed_me.size
if my_c != other_c
if other_c == "\e".ord and other_is_last and my_c.is_a?(Reline::Key) and my_c.with_meta
return true
else
return false
end
elsif other_is_last
return true
elsif me_is_last
return false
end
i += 1
end
end
def equal?(me, other)
case me
when Array
compressed_me = compress_meta_key(me)
compressed_other = compress_meta_key(other)
compressed_me.size == compressed_other.size and [compressed_me, compressed_other].transpose.all?{ |i| equal?(i[0], i[1]) }
when Integer
if other.is_a?(Reline::Key)
if other.combined_char == "\e".ord
false
else
other.combined_char == me
end
else
me == other
end
when Reline::Key
if other.is_a?(Integer)
me.combined_char == other
else
me == other
end
end
end
def match_status(input)
key_mapping.keys.select { |lhs|
start_with?(lhs, input)
}.tap { |it|
return :matched if it.size == 1 && equal?(it[0], input)
return :matching if it.size == 1 && !equal?(it[0], input)
return :matched if it.max_by(&:size)&.size&.< input.size
return :matching if it.size > 1
}
key_mapping.keys.select { |lhs|
start_with?(input, lhs)
}.tap { |it|
return it.size > 0 ? :matched : :unmatched
}
end
def expand(input)
input = compress_meta_key(input)
lhs = key_mapping.keys.select { |item| start_with?(input, item) }.sort_by(&:size).last
return input unless lhs
rhs = key_mapping[lhs]
case rhs
when String
rhs_bytes = rhs.bytes
expand(expand(rhs_bytes) + expand(input.drop(lhs.size)))
when Symbol
[rhs] + expand(input.drop(lhs.size))
when Array
rhs
end
end
private
def key_mapping
@config.key_bindings
end
end
share/ruby/reline/terminfo.rb 0000644 00000012564 15173547335 0012302 0 ustar 00 begin
require 'fiddle'
require 'fiddle/import'
rescue LoadError
module Reline::Terminfo
def self.curses_dl
false
end
end
end
module Reline::Terminfo
extend Fiddle::Importer
class TerminfoError < StandardError; end
def self.curses_dl_files
case RUBY_PLATFORM
when /mingw/, /mswin/
# aren't supported
[]
when /cygwin/
%w[cygncursesw-10.dll cygncurses-10.dll]
when /darwin/
%w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib]
else
%w[libncursesw.so libcursesw.so libncurses.so libcurses.so]
end
end
@curses_dl = false
def self.curses_dl
return @curses_dl unless @curses_dl == false
if RUBY_VERSION >= '3.0.0'
# Gem module isn't defined in test-all of the Ruby repository, and
# Fiddle in Ruby 3.0.0 or later supports Fiddle::TYPE_VARIADIC.
fiddle_supports_variadic = true
elsif Fiddle.const_defined?(:VERSION,false) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
# Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
fiddle_supports_variadic = true
else
fiddle_supports_variadic = false
end
if fiddle_supports_variadic and not Fiddle.const_defined?(:TYPE_VARIADIC)
# If the libffi version is not 3.0.5 or higher, there isn't TYPE_VARIADIC.
fiddle_supports_variadic = false
end
if fiddle_supports_variadic
curses_dl_files.each do |curses_name|
result = Fiddle::Handle.new(curses_name)
rescue Fiddle::DLError
next
else
@curses_dl = result
break
end
end
@curses_dl = nil if @curses_dl == false
@curses_dl
end
end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
module Reline::Terminfo
dlload curses_dl
#extern 'int setupterm(char *term, int fildes, int *errret)'
@setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
#extern 'char *tigetstr(char *capname)'
@tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
begin
#extern 'char *tiparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
rescue Fiddle::DLError
# OpenBSD lacks tiparm
#extern 'char *tparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
end
begin
#extern 'int tigetflag(char *str)'
@tigetflag = Fiddle::Function.new(curses_dl['tigetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
rescue Fiddle::DLError
# OpenBSD lacks tigetflag
#extern 'int tgetflag(char *str)'
@tigetflag = Fiddle::Function.new(curses_dl['tgetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
end
begin
#extern 'int tigetnum(char *str)'
@tigetnum = Fiddle::Function.new(curses_dl['tigetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
rescue Fiddle::DLError
# OpenBSD lacks tigetnum
#extern 'int tgetnum(char *str)'
@tigetnum = Fiddle::Function.new(curses_dl['tgetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
end
def self.setupterm(term, fildes)
errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT)
ret = @setupterm.(term, fildes, errret_int)
errret = errret_int[0, Fiddle::SIZEOF_INT].unpack1('i')
case ret
when 0 # OK
0
when -1 # ERR
case errret
when 1
raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
when 0
raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
when -1
raise TerminfoError.new('The terminfo database could not be found.')
else # unknown
-1
end
else # unknown
-2
end
end
class StringWithTiparm < String
def tiparm(*args) # for method chain
Reline::Terminfo.tiparm(self, *args)
end
end
def self.tigetstr(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
capability = @tigetstr.(capname)
case capability.to_i
when 0, -1
raise TerminfoError, "can't find capability: #{capname}"
end
StringWithTiparm.new(capability.to_s)
end
def self.tiparm(str, *args)
new_args = []
args.each do |a|
new_args << Fiddle::TYPE_INT << a
end
@tiparm.(str, *new_args).to_s
end
def self.tigetflag(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
flag = @tigetflag.(capname).to_i
case flag
when -1
raise TerminfoError, "not boolean capability: #{capname}"
when 0
raise TerminfoError, "can't find capability: #{capname}"
end
flag
end
def self.tigetnum(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
num = @tigetnum.(capname).to_i
case num
when -2
raise TerminfoError, "not numeric capability: #{capname}"
when -1
raise TerminfoError, "can't find capability: #{capname}"
end
num
end
def self.enabled?
true
end
end if Reline::Terminfo.curses_dl
module Reline::Terminfo
def self.enabled?
false
end
end unless Reline::Terminfo.curses_dl
share/ruby/reline/ansi.rb 0000644 00000022210 15173547335 0011376 0 ustar 00 require 'io/console'
require 'io/wait'
require 'timeout'
require_relative 'terminfo'
class Reline::ANSI
CAPNAME_KEY_BINDINGS = {
'khome' => :ed_move_to_beg,
'kend' => :ed_move_to_end,
'kcuu1' => :ed_prev_history,
'kcud1' => :ed_next_history,
'kcuf1' => :ed_next_char,
'kcub1' => :ed_prev_char,
'cuu' => :ed_prev_history,
'cud' => :ed_next_history,
'cuf' => :ed_next_char,
'cub' => :ed_prev_char,
}
if Reline::Terminfo.enabled?
Reline::Terminfo.setupterm(0, 2)
end
def self.encoding
Encoding.default_external
end
def self.win?
false
end
def self.set_default_key_bindings(config)
if Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
else
set_default_key_bindings_comprehensive_list(config)
end
{
# extended entries of terminfo
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
[27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
[27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
end
{
# default bindings
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
end
def self.set_default_key_bindings_terminfo(config)
key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
begin
key_code = Reline::Terminfo.tigetstr(capname)
case capname
# Escape sequences that omit the move distance and are set to defaults
# value 1 may be sometimes sent by pressing the arrow-key.
when 'cuu', 'cud', 'cuf', 'cub'
[ key_code.sub(/%p1%d/, '').bytes, key_binding ]
else
[ key_code.bytes, key_binding ]
end
rescue Reline::Terminfo::TerminfoError
# capname is undefined
end
end.compact.to_h
key_bindings.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end
def self.set_default_key_bindings_comprehensive_list(config)
{
# Console (80x25)
[27, 91, 49, 126] => :ed_move_to_beg, # Home
[27, 91, 52, 126] => :ed_move_to_end, # End
[27, 91, 51, 126] => :key_delete, # Del
[27, 91, 65] => :ed_prev_history, # ↑
[27, 91, 66] => :ed_next_history, # ↓
[27, 91, 67] => :ed_next_char, # →
[27, 91, 68] => :ed_prev_char, # ←
# KDE
[27, 91, 72] => :ed_move_to_beg, # Home
[27, 91, 70] => :ed_move_to_end, # End
# Del is 0x08
[27, 71, 65] => :ed_prev_history, # ↑
[27, 71, 66] => :ed_next_history, # ↓
[27, 71, 67] => :ed_next_char, # →
[27, 71, 68] => :ed_prev_char, # ←
# urxvt / exoterm
[27, 91, 55, 126] => :ed_move_to_beg, # Home
[27, 91, 56, 126] => :ed_move_to_end, # End
# GNOME
[27, 79, 72] => :ed_move_to_beg, # Home
[27, 79, 70] => :ed_move_to_end, # End
# Del is 0x08
# Arrow keys are the same of KDE
# iTerm2
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
[195, 166] => :em_next_word, # Option+f
[195, 162] => :ed_prev_word, # Option+b
[27, 79, 65] => :ed_prev_history, # ↑
[27, 79, 66] => :ed_next_history, # ↓
[27, 79, 67] => :ed_next_char, # →
[27, 79, 68] => :ed_prev_char, # ←
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end
@@input = STDIN
def self.input=(val)
@@input = val
end
@@output = STDOUT
def self.output=(val)
@@output = val
end
@@buf = []
def self.inner_getc
unless @@buf.empty?
return @@buf.shift
end
until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
Reline.core.line_editor.resize
end
(c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
# Maybe the I/O has been closed.
nil
rescue Errno::ENOTTY
nil
end
@@in_bracketed_paste_mode = false
START_BRACKETED_PASTE = String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE = String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
def self.getc_with_bracketed_paste
buffer = String.new(encoding: Encoding::ASCII_8BIT)
buffer << inner_getc
while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
if START_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = true
return inner_getc
elsif END_BRACKETED_PASTE == buffer
@@in_bracketed_paste_mode = false
ungetc(-1)
return inner_getc
end
begin
succ_c = nil
Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
succ_c = inner_getc
}
rescue Timeout::Error
break
else
buffer << succ_c
end
end
buffer.bytes.reverse_each do |ch|
ungetc ch
end
inner_getc
end
def self.getc
if Reline.core.config.enable_bracketed_paste
getc_with_bracketed_paste
else
inner_getc
end
end
def self.in_pasting?
@@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
end
def self.empty_buffer?
unless @@buf.empty?
return false
end
!@@input.wait_readable(0)
end
def self.ungetc(c)
@@buf.unshift(c)
end
def self.retrieve_keybuffer
begin
return unless @@input.wait_readable(0.001)
str = @@input.read_nonblock(1024)
str.bytes.each do |c|
@@buf.push(c)
end
rescue EOFError
end
end
def self.get_screen_size
s = @@input.winsize
return s if s[0] > 0 && s[1] > 0
s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
return s if s[0] > 0 && s[1] > 0
[24, 80]
rescue Errno::ENOTTY
[24, 80]
end
def self.set_screen_size(rows, columns)
@@input.winsize = [rows, columns]
self
rescue Errno::ENOTTY
self
end
def self.cursor_pos
begin
res = +''
m = nil
@@input.raw do |stdin|
@@output << "\e[6n"
@@output.flush
loop do
c = stdin.getc
next if c.nil?
res << c
m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
break if m
end
(m.pre_match + m.post_match).chars.reverse_each do |ch|
stdin.ungetc ch
end
end
column = m[:column].to_i - 1
row = m[:row].to_i - 1
rescue Errno::ENOTTY
begin
buf = @@output.pread(@@output.pos, 0)
row = buf.count("\n")
column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
rescue Errno::ESPIPE
# Just returns column 1 for ambiguous width because this I/O is not
# tty and can't seek.
row = 0
column = 1
end
end
Reline::CursorPos.new(column, row)
end
def self.move_cursor_column(x)
@@output.write "\e[#{x + 1}G"
end
def self.move_cursor_up(x)
if x > 0
@@output.write "\e[#{x}A"
elsif x < 0
move_cursor_down(-x)
end
end
def self.move_cursor_down(x)
if x > 0
@@output.write "\e[#{x}B"
elsif x < 0
move_cursor_up(-x)
end
end
def self.hide_cursor
if Reline::Terminfo.enabled?
begin
@@output.write Reline::Terminfo.tigetstr('civis')
rescue Reline::Terminfo::TerminfoError
# civis is undefined
end
else
# ignored
end
end
def self.show_cursor
if Reline::Terminfo.enabled?
begin
@@output.write Reline::Terminfo.tigetstr('cnorm')
rescue Reline::Terminfo::TerminfoError
# cnorm is undefined
end
else
# ignored
end
end
def self.erase_after_cursor
@@output.write "\e[K"
end
def self.scroll_down(x)
return if x.zero?
@@output.write "\e[#{x}S"
end
def self.clear_screen
@@output.write "\e[2J"
@@output.write "\e[1;1H"
end
@@old_winch_handler = nil
def self.set_winch_handler(&handler)
@@old_winch_handler = Signal.trap('WINCH', &handler)
end
def self.prep
retrieve_keybuffer
nil
end
def self.deprep(otio)
Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
end
end
share/ruby/reline/version.rb 0000644 00000000046 15173547335 0012134 0 ustar 00 module Reline
VERSION = '0.3.2'
end
share/ruby/reline/line_editor.rb 0000644 00000336315 15173547335 0012757 0 ustar 00 require 'reline/kill_ring'
require 'reline/unicode'
require 'tempfile'
class Reline::LineEditor
# TODO: undo
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
attr_reader :line
attr_reader :byte_pointer
attr_accessor :confirm_multiline_termination_proc
attr_accessor :completion_proc
attr_accessor :completion_append_character
attr_accessor :output_modifier_proc
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
attr_accessor :pre_input_hook
attr_accessor :dig_perfect_match_proc
attr_writer :output
VI_MOTIONS = %i{
ed_prev_char
ed_next_char
vi_zero
ed_move_to_beg
ed_move_to_end
vi_to_column
vi_next_char
vi_prev_char
vi_next_word
vi_prev_word
vi_to_next_char
vi_to_prev_char
vi_end_word
vi_next_big_word
vi_prev_big_word
vi_end_big_word
vi_repeat_next_char
vi_repeat_prev_char
}
module CompletionState
NORMAL = :normal
COMPLETION = :completion
MENU = :menu
JOURNEY = :journey
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
PERFECT_MATCH = :perfect_match
end
CompletionJourneyData = Struct.new('CompletionJourneyData', :preposing, :postposing, :list, :pointer)
MenuInfo = Struct.new('MenuInfo', :target, :list)
PROMPT_LIST_CACHE_TIMEOUT = 0.5
def initialize(config, encoding)
@config = config
@completion_append_character = ''
reset_variables(encoding: encoding)
end
def set_pasting_state(in_pasting)
@in_pasting = in_pasting
end
def simplified_rendering?
if finished?
false
elsif @just_cursor_moving and not @rerender_all
true
else
not @rerender_all and not finished? and @in_pasting
end
end
private def check_mode_string
mode_string = nil
if @config.show_mode_in_prompt
if @config.editing_mode_is?(:vi_command)
mode_string = @config.vi_cmd_mode_string
elsif @config.editing_mode_is?(:vi_insert)
mode_string = @config.vi_ins_mode_string
elsif @config.editing_mode_is?(:emacs)
mode_string = @config.emacs_mode_string
else
mode_string = '?'
end
end
if mode_string != @prev_mode_string
@rerender_all = true
end
@prev_mode_string = mode_string
mode_string
end
private def check_multiline_prompt(buffer)
if @vi_arg
prompt = "(arg: #{@vi_arg}) "
@rerender_all = true
elsif @searching_prompt
prompt = @searching_prompt
@rerender_all = true
else
prompt = @prompt
end
if simplified_rendering?
mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
return [prompt, calculate_width(prompt, true), [prompt] * buffer.size]
end
if @prompt_proc
use_cached_prompt_list = false
if @cached_prompt_list
if @just_cursor_moving
use_cached_prompt_list = true
elsif Time.now.to_f < (@prompt_cache_time + PROMPT_LIST_CACHE_TIMEOUT) and buffer.size == @cached_prompt_list.size
use_cached_prompt_list = true
end
end
use_cached_prompt_list = false if @rerender_all
if use_cached_prompt_list
prompt_list = @cached_prompt_list
else
prompt_list = @cached_prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
@prompt_cache_time = Time.now.to_f
end
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
prompt_list = [prompt] if prompt_list.empty?
mode_string = check_mode_string
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
prompt = prompt_list[@line_index]
prompt = prompt_list[0] if prompt.nil?
prompt = prompt_list.last if prompt.nil?
if buffer.size > prompt_list.size
(buffer.size - prompt_list.size).times do
prompt_list << prompt_list.last
end
end
prompt_width = calculate_width(prompt, true)
[prompt, prompt_width, prompt_list]
else
mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
prompt_width = calculate_width(prompt, true)
[prompt, prompt_width, nil]
end
end
def reset(prompt = '', encoding:)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
@screen_size = Reline::IOGate.get_screen_size
@screen_height = @screen_size.first
reset_variables(prompt, encoding: encoding)
Reline::IOGate.set_winch_handler do
@resized = true
end
if ENV.key?('RELINE_ALT_SCROLLBAR')
@full_block = '::'
@upper_half_block = "''"
@lower_half_block = '..'
@block_elem_width = 2
elsif Reline::IOGate.win?
@full_block = '█'
@upper_half_block = '▀'
@lower_half_block = '▄'
@block_elem_width = 1
elsif @encoding == Encoding::UTF_8
@full_block = '█'
@upper_half_block = '▀'
@lower_half_block = '▄'
@block_elem_width = Reline::Unicode.calculate_width('█')
else
@full_block = '::'
@upper_half_block = "''"
@lower_half_block = '..'
@block_elem_width = 2
end
end
def resize
return unless @resized
@resized = false
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
old_screen_size = @screen_size
@screen_size = Reline::IOGate.get_screen_size
@screen_height = @screen_size.first
if old_screen_size.last < @screen_size.last # columns increase
@rerender_all = true
rerender
else
back = 0
new_buffer = whole_lines
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
new_buffer.each_with_index do |line, index|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
width = prompt_width + calculate_width(line)
height = calculate_height_by_width(width)
back += height
end
@highest_in_all = back
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
@first_line_started_from =
if @line_index.zero?
0
else
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
if @prompt_proc
prompt = prompt_list[@line_index]
prompt_width = calculate_width(prompt, true)
end
calculate_nearest_cursor
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
@rerender_all = true
end
end
def set_signal_handlers
@old_trap = Signal.trap('INT') {
clear_dialog
if @scroll_partial_screen
move_cursor_down(@screen_height - (@line_index - @scroll_partial_screen) - 1)
else
move_cursor_down(@highest_in_all - @line_index - 1)
end
Reline::IOGate.move_cursor_column(0)
scroll_down(1)
case @old_trap
when 'DEFAULT', 'SYSTEM_DEFAULT'
raise Interrupt
when 'IGNORE'
# Do nothing
when 'EXIT'
exit
else
@old_trap.call if @old_trap.respond_to?(:call)
end
}
begin
@old_tstp_trap = Signal.trap('TSTP') {
Reline::IOGate.ungetc("\C-z".ord)
@old_tstp_trap.call if @old_tstp_trap.respond_to?(:call)
}
rescue ArgumentError
end
end
def finalize
Signal.trap('INT', @old_trap)
begin
Signal.trap('TSTP', @old_tstp_trap)
rescue ArgumentError
end
end
def eof?
@eof
end
def reset_variables(prompt = '', encoding:)
@prompt = prompt.gsub("\n", "\\n")
@mark_pointer = nil
@encoding = encoding
@is_multiline = false
@finished = false
@cleared = false
@rerender_all = false
@history_pointer = nil
@kill_ring ||= Reline::KillRing.new
@vi_clipboard = ''
@vi_arg = nil
@waiting_proc = nil
@waiting_operator_proc = nil
@waiting_operator_vi_arg = nil
@completion_journey_data = nil
@completion_state = CompletionState::NORMAL
@perfect_matched = nil
@menu_info = nil
@first_prompt = true
@searching_prompt = nil
@first_char = true
@add_newline_to_end_of_buffer = false
@just_cursor_moving = nil
@cached_prompt_list = nil
@prompt_cache_time = nil
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
@scroll_partial_screen = nil
@prev_mode_string = nil
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
@dialogs = []
@last_key = nil
@resized = false
reset_line
end
def reset_line
@cursor = 0
@cursor_max = 0
@byte_pointer = 0
@buffer_of_lines = [String.new(encoding: @encoding)]
@line_index = 0
@previous_line_index = nil
@line = @buffer_of_lines[0]
@first_line_started_from = 0
@move_up = 0
@started_from = 0
@highest_in_this = 1
@highest_in_all = 1
@line_backup_in_history = nil
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
@check_new_auto_indent = false
end
def multiline_on
@is_multiline = true
end
def multiline_off
@is_multiline = false
end
private def calculate_height_by_lines(lines, prompt)
result = 0
prompt_list = prompt.is_a?(Array) ? prompt : nil
lines.each_with_index { |line, i|
prompt = prompt_list[i] if prompt_list and prompt_list[i]
result += calculate_height_by_width(calculate_width(prompt, true) + calculate_width(line))
}
result
end
private def insert_new_line(cursor_line, next_line)
@line = cursor_line
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
@previous_line_index = @line_index
@line_index += 1
@just_cursor_moving = false
end
private def calculate_height_by_width(width)
width.div(@screen_size.last) + 1
end
private def split_by_width(str, max_width)
Reline::Unicode.split_by_width(str, max_width, @encoding)
end
private def scroll_down(val)
if val <= @rest_height
Reline::IOGate.move_cursor_down(val)
@rest_height -= val
else
Reline::IOGate.move_cursor_down(@rest_height)
Reline::IOGate.scroll_down(val - @rest_height)
@rest_height = 0
end
end
private def move_cursor_up(val)
if val > 0
Reline::IOGate.move_cursor_up(val)
@rest_height += val
elsif val < 0
move_cursor_down(-val)
end
end
private def move_cursor_down(val)
if val > 0
Reline::IOGate.move_cursor_down(val)
@rest_height -= val
@rest_height = 0 if @rest_height < 0
elsif val < 0
move_cursor_up(-val)
end
end
private def calculate_nearest_cursor(line_to_calc = @line, cursor = @cursor, started_from = @started_from, byte_pointer = @byte_pointer, update = true)
new_cursor_max = calculate_width(line_to_calc)
new_cursor = 0
new_byte_pointer = 0
height = 1
max_width = @screen_size.last
if @config.editing_mode_is?(:vi_command)
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
if last_byte_size > 0
last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
end_of_line_cursor = new_cursor_max - last_width
else
end_of_line_cursor = new_cursor_max
end
else
end_of_line_cursor = new_cursor_max
end
line_to_calc.grapheme_clusters.each do |gc|
mbchar = gc.encode(Encoding::UTF_8)
mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
now = new_cursor + mbchar_width
if now > end_of_line_cursor or now > cursor
break
end
new_cursor += mbchar_width
if new_cursor > max_width * height
height += 1
end
new_byte_pointer += gc.bytesize
end
new_started_from = height - 1
if update
@cursor = new_cursor
@cursor_max = new_cursor_max
@started_from = new_started_from
@byte_pointer = new_byte_pointer
else
[new_cursor, new_cursor_max, new_started_from, new_byte_pointer]
end
end
def rerender_all
@rerender_all = true
process_insert(force: true)
rerender
end
def rerender
return if @line.nil?
if @menu_info
scroll_down(@highest_in_all - @first_line_started_from)
@rerender_all = true
end
if @menu_info
show_menu
@menu_info = nil
end
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
if @cleared
clear_screen_buffer(prompt, prompt_list, prompt_width)
@cleared = false
return
end
if @is_multiline and finished? and @scroll_partial_screen
# Re-output all code higher than the screen when finished.
Reline::IOGate.move_cursor_up(@first_line_started_from + @started_from - @scroll_partial_screen)
Reline::IOGate.move_cursor_column(0)
@scroll_partial_screen = nil
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
modify_lines(new_lines).each_with_index do |line, index|
@output.write "#{prompt_list ? prompt_list[index] : prompt}#{line}\n"
Reline::IOGate.erase_after_cursor
end
@output.flush
clear_dialog
return
end
new_highest_in_this = calculate_height_by_width(prompt_width + calculate_width(@line.nil? ? '' : @line))
rendered = false
if @add_newline_to_end_of_buffer
clear_dialog_with_content
rerender_added_newline(prompt, prompt_width)
@add_newline_to_end_of_buffer = false
else
if @just_cursor_moving and not @rerender_all
clear_dialog_with_content
rendered = just_move_cursor
@just_cursor_moving = false
return
elsif @previous_line_index or new_highest_in_this != @highest_in_this
clear_dialog_with_content
rerender_changed_current_line
@previous_line_index = nil
rendered = true
elsif @rerender_all
rerender_all_lines
@rerender_all = false
rendered = true
else
end
end
if @is_multiline
if finished?
# Always rerender on finish because output_modifier_proc may return a different output.
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
line = modify_lines(new_lines)[@line_index]
clear_dialog
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
render_partial(prompt, prompt_width, line, @first_line_started_from)
move_cursor_down(@highest_in_all - (@first_line_started_from + @highest_in_this - 1) - 1)
scroll_down(1)
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
else
if not rendered and not @in_pasting
line = modify_lines(whole_lines)[@line_index]
prompt, prompt_width, prompt_list = check_multiline_prompt(whole_lines)
render_partial(prompt, prompt_width, line, @first_line_started_from)
end
render_dialog((prompt_width + @cursor) % @screen_size.last)
end
@buffer_of_lines[@line_index] = @line
@rest_height = 0 if @scroll_partial_screen
else
line = modify_lines(whole_lines)[@line_index]
render_partial(prompt, prompt_width, line, 0)
if finished?
scroll_down(1)
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
end
end
end
class DialogProcScope
def initialize(line_editor, config, proc_to_exec, context)
@line_editor = line_editor
@config = config
@proc_to_exec = proc_to_exec
@context = context
@cursor_pos = Reline::CursorPos.new
end
def context
@context
end
def retrieve_completion_block(set_completion_quote_character = false)
@line_editor.retrieve_completion_block(set_completion_quote_character)
end
def call_completion_proc_with_checking_args(pre, target, post)
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
end
def set_dialog(dialog)
@dialog = dialog
end
def dialog
@dialog
end
def set_cursor_pos(col, row)
@cursor_pos.x = col
@cursor_pos.y = row
end
def set_key(key)
@key = key
end
def key
@key
end
def cursor_pos
@cursor_pos
end
def just_cursor_moving
@line_editor.instance_variable_get(:@just_cursor_moving)
end
def screen_width
@line_editor.instance_variable_get(:@screen_size).last
end
def completion_journey_data
@line_editor.instance_variable_get(:@completion_journey_data)
end
def config
@config
end
def call
instance_exec(&@proc_to_exec)
end
end
class Dialog
attr_reader :name, :contents, :width
attr_accessor :scroll_top, :scrollbar_pos, :pointer, :column, :vertical_offset, :lines_backup, :trap_key
def initialize(name, config, proc_scope)
@name = name
@config = config
@proc_scope = proc_scope
@width = nil
@scroll_top = 0
@trap_key = nil
end
def set_cursor_pos(col, row)
@proc_scope.set_cursor_pos(col, row)
end
def width=(v)
@width = v
end
def contents=(contents)
@contents = contents
if contents and @width.nil?
@width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
end
end
def call(key)
@proc_scope.set_dialog(self)
@proc_scope.set_key(key)
dialog_render_info = @proc_scope.call
if @trap_key
if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
@trap_key.each do |t|
@config.add_oneshot_key_binding(t, @name)
end
elsif @trap_key.is_a?(Array)
@config.add_oneshot_key_binding(@trap_key, @name)
elsif @trap_key.is_a?(Integer) or @trap_key.is_a?(Reline::Key)
@config.add_oneshot_key_binding([@trap_key], @name)
end
end
dialog_render_info
end
end
def add_dialog_proc(name, p, context = nil)
dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
if index = @dialogs.find_index { |d| d.name == name }
@dialogs[index] = dialog
else
@dialogs << dialog
end
end
DIALOG_DEFAULT_HEIGHT = 20
private def render_dialog(cursor_column)
@dialogs.each do |dialog|
render_each_dialog(dialog, cursor_column)
end
end
private def padding_space_with_escape_sequences(str, width)
padding_width = width - calculate_width(str, true)
# padding_width should be only positive value. But macOS and Alacritty returns negative value.
padding_width = 0 if padding_width < 0
str + (' ' * padding_width)
end
private def render_each_dialog(dialog, cursor_column)
if @in_pasting
clear_each_dialog(dialog)
dialog.contents = nil
dialog.trap_key = nil
return
end
dialog.set_cursor_pos(cursor_column, @first_line_started_from + @started_from)
dialog_render_info = dialog.call(@last_key)
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
dialog.lines_backup = {
lines: modify_lines(whole_lines),
line_index: @line_index,
first_line_started_from: @first_line_started_from,
started_from: @started_from,
byte_pointer: @byte_pointer
}
clear_each_dialog(dialog)
dialog.contents = nil
dialog.trap_key = nil
return
end
old_dialog = dialog.clone
dialog.contents = dialog_render_info.contents
pointer = dialog.pointer
if dialog_render_info.width
dialog.width = dialog_render_info.width
else
dialog.width = dialog.contents.map { |l| calculate_width(l, true) }.max
end
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
height = dialog.contents.size if dialog.contents.size < height
if dialog.contents.size > height
if dialog.pointer
if dialog.pointer < 0
dialog.scroll_top = 0
elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
dialog.scroll_top = dialog.pointer - (height - 1)
elsif (dialog.pointer - dialog.scroll_top) < 0
dialog.scroll_top = dialog.pointer
end
pointer = dialog.pointer - dialog.scroll_top
end
dialog.contents = dialog.contents[dialog.scroll_top, height]
end
if dialog.contents and dialog.scroll_top >= dialog.contents.size
dialog.scroll_top = dialog.contents.size - height
end
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
bar_max_height = height * 2
moving_distance = (dialog_render_info.contents.size - height) * 2
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
bar_height = (bar_max_height * ((dialog.contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
dialog.scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
else
dialog.scrollbar_pos = nil
end
upper_space = @first_line_started_from - @started_from
dialog.column = dialog_render_info.pos.x
dialog.width += @block_elem_width if dialog.scrollbar_pos
diff = (dialog.column + dialog.width) - (@screen_size.last)
if diff > 0
dialog.column -= diff
end
if (@rest_height - dialog_render_info.pos.y) >= height
dialog.vertical_offset = dialog_render_info.pos.y + 1
elsif upper_space >= height
dialog.vertical_offset = dialog_render_info.pos.y - height
else
if (@rest_height - dialog_render_info.pos.y) < height
scroll_down(height + dialog_render_info.pos.y)
move_cursor_up(height + dialog_render_info.pos.y)
end
dialog.vertical_offset = dialog_render_info.pos.y + 1
end
Reline::IOGate.hide_cursor
if dialog.column < 0
dialog.column = 0
dialog.width = @screen_size.last
end
reset_dialog(dialog, old_dialog)
move_cursor_down(dialog.vertical_offset)
Reline::IOGate.move_cursor_column(dialog.column)
dialog.contents.each_with_index do |item, i|
if i == pointer
fg_color = dialog_render_info.pointer_fg_color
bg_color = dialog_render_info.pointer_bg_color
else
fg_color = dialog_render_info.fg_color
bg_color = dialog_render_info.bg_color
end
str_width = dialog.width - (dialog.scrollbar_pos.nil? ? 0 : @block_elem_width)
str = padding_space_with_escape_sequences(Reline::Unicode.take_range(item, 0, str_width), str_width)
@output.write "\e[#{bg_color}m\e[#{fg_color}m#{str}"
if dialog.scrollbar_pos and (dialog.scrollbar_pos != old_dialog.scrollbar_pos or dialog.column != old_dialog.column)
@output.write "\e[37m"
if dialog.scrollbar_pos <= (i * 2) and (i * 2 + 1) < (dialog.scrollbar_pos + bar_height)
@output.write @full_block
elsif dialog.scrollbar_pos <= (i * 2) and (i * 2) < (dialog.scrollbar_pos + bar_height)
@output.write @upper_half_block
elsif dialog.scrollbar_pos <= (i * 2 + 1) and (i * 2) < (dialog.scrollbar_pos + bar_height)
@output.write @lower_half_block
else
@output.write ' ' * @block_elem_width
end
end
@output.write "\e[0m"
Reline::IOGate.move_cursor_column(dialog.column)
move_cursor_down(1) if i < (dialog.contents.size - 1)
end
Reline::IOGate.move_cursor_column(cursor_column)
move_cursor_up(dialog.vertical_offset + dialog.contents.size - 1)
Reline::IOGate.show_cursor
dialog.lines_backup = {
lines: modify_lines(whole_lines),
line_index: @line_index,
first_line_started_from: @first_line_started_from,
started_from: @started_from,
byte_pointer: @byte_pointer
}
end
private def reset_dialog(dialog, old_dialog)
return if dialog.lines_backup.nil? or old_dialog.contents.nil?
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
visual_lines = []
visual_start = nil
dialog.lines_backup[:lines].each_with_index { |l, i|
pr = prompt_list ? prompt_list[i] : prompt
vl, _ = split_by_width(pr + l, @screen_size.last)
vl.compact!
if i == dialog.lines_backup[:line_index]
visual_start = visual_lines.size + dialog.lines_backup[:started_from]
end
visual_lines.concat(vl)
}
old_y = dialog.lines_backup[:first_line_started_from] + dialog.lines_backup[:started_from]
y = @first_line_started_from + @started_from
y_diff = y - old_y
if (old_y + old_dialog.vertical_offset) < (y + dialog.vertical_offset)
# rerender top
move_cursor_down(old_dialog.vertical_offset - y_diff)
start = visual_start + old_dialog.vertical_offset
line_num = dialog.vertical_offset - old_dialog.vertical_offset
line_num.times do |i|
Reline::IOGate.move_cursor_column(old_dialog.column)
if visual_lines[start + i].nil?
s = ' ' * old_dialog.width
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
s = padding_space_with_escape_sequences(s, old_dialog.width)
end
@output.write "\e[0m#{s}\e[0m"
move_cursor_down(1) if i < (line_num - 1)
end
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
end
if (old_y + old_dialog.vertical_offset + old_dialog.contents.size) > (y + dialog.vertical_offset + dialog.contents.size)
# rerender bottom
move_cursor_down(dialog.vertical_offset + dialog.contents.size - y_diff)
start = visual_start + dialog.vertical_offset + dialog.contents.size
line_num = (old_dialog.vertical_offset + old_dialog.contents.size) - (dialog.vertical_offset + dialog.contents.size)
line_num.times do |i|
Reline::IOGate.move_cursor_column(old_dialog.column)
if visual_lines[start + i].nil?
s = ' ' * old_dialog.width
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, old_dialog.width)
s = padding_space_with_escape_sequences(s, old_dialog.width)
end
@output.write "\e[0m#{s}\e[0m"
move_cursor_down(1) if i < (line_num - 1)
end
move_cursor_up(dialog.vertical_offset + dialog.contents.size + line_num - 1 - y_diff)
end
if old_dialog.column < dialog.column
# rerender left
move_cursor_down(old_dialog.vertical_offset - y_diff)
width = dialog.column - old_dialog.column
start = visual_start + old_dialog.vertical_offset
line_num = old_dialog.contents.size
line_num.times do |i|
Reline::IOGate.move_cursor_column(old_dialog.column)
if visual_lines[start + i].nil?
s = ' ' * width
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column, width)
s = padding_space_with_escape_sequences(s, dialog.width)
end
@output.write "\e[0m#{s}\e[0m"
move_cursor_down(1) if i < (line_num - 1)
end
move_cursor_up(old_dialog.vertical_offset + line_num - 1 - y_diff)
end
if (old_dialog.column + old_dialog.width) > (dialog.column + dialog.width)
# rerender right
move_cursor_down(old_dialog.vertical_offset + y_diff)
width = (old_dialog.column + old_dialog.width) - (dialog.column + dialog.width)
start = visual_start + old_dialog.vertical_offset
line_num = old_dialog.contents.size
line_num.times do |i|
Reline::IOGate.move_cursor_column(old_dialog.column + dialog.width)
if visual_lines[start + i].nil?
s = ' ' * width
else
s = Reline::Unicode.take_range(visual_lines[start + i], old_dialog.column + dialog.width, width)
rerender_width = old_dialog.width - dialog.width
s = padding_space_with_escape_sequences(s, rerender_width)
end
Reline::IOGate.move_cursor_column(dialog.column + dialog.width)
@output.write "\e[0m#{s}\e[0m"
move_cursor_down(1) if i < (line_num - 1)
end
move_cursor_up(old_dialog.vertical_offset + line_num - 1 + y_diff)
end
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
private def clear_dialog
@dialogs.each do |dialog|
clear_each_dialog(dialog)
end
end
private def clear_dialog_with_content
@dialogs.each do |dialog|
clear_each_dialog(dialog)
dialog.contents = nil
dialog.trap_key = nil
end
end
private def clear_each_dialog(dialog)
dialog.trap_key = nil
return unless dialog.contents
prompt, prompt_width, prompt_list = check_multiline_prompt(dialog.lines_backup[:lines])
visual_lines = []
visual_lines_under_dialog = []
visual_start = nil
dialog.lines_backup[:lines].each_with_index { |l, i|
pr = prompt_list ? prompt_list[i] : prompt
vl, _ = split_by_width(pr + l, @screen_size.last)
vl.compact!
if i == dialog.lines_backup[:line_index]
visual_start = visual_lines.size + dialog.lines_backup[:started_from] + dialog.vertical_offset
end
visual_lines.concat(vl)
}
visual_lines_under_dialog = visual_lines[visual_start, dialog.contents.size]
visual_lines_under_dialog = [] if visual_lines_under_dialog.nil?
Reline::IOGate.hide_cursor
move_cursor_down(dialog.vertical_offset)
dialog_vertical_size = dialog.contents.size
dialog_vertical_size.times do |i|
if i < visual_lines_under_dialog.size
Reline::IOGate.move_cursor_column(dialog.column)
str = Reline::Unicode.take_range(visual_lines_under_dialog[i], dialog.column, dialog.width)
str = padding_space_with_escape_sequences(str, dialog.width)
@output.write "\e[0m#{str}\e[0m"
else
Reline::IOGate.move_cursor_column(dialog.column)
@output.write "\e[0m#{' ' * dialog.width}\e[0m"
end
move_cursor_down(1) if i < (dialog_vertical_size - 1)
end
move_cursor_up(dialog_vertical_size - 1 + dialog.vertical_offset)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
Reline::IOGate.show_cursor
end
private def calculate_scroll_partial_screen(highest_in_all, cursor_y)
if @screen_height < highest_in_all
old_scroll_partial_screen = @scroll_partial_screen
if cursor_y == 0
@scroll_partial_screen = 0
elsif cursor_y == (highest_in_all - 1)
@scroll_partial_screen = highest_in_all - @screen_height
else
if @scroll_partial_screen
if cursor_y <= @scroll_partial_screen
@scroll_partial_screen = cursor_y
elsif (@scroll_partial_screen + @screen_height - 1) < cursor_y
@scroll_partial_screen = cursor_y - (@screen_height - 1)
end
else
if cursor_y > (@screen_height - 1)
@scroll_partial_screen = cursor_y - (@screen_height - 1)
else
@scroll_partial_screen = 0
end
end
end
if @scroll_partial_screen != old_scroll_partial_screen
@rerender_all = true
end
else
if @scroll_partial_screen
@rerender_all = true
end
@scroll_partial_screen = nil
end
end
private def rerender_added_newline(prompt, prompt_width)
scroll_down(1)
@buffer_of_lines[@previous_line_index] = @line
@line = @buffer_of_lines[@line_index]
unless @in_pasting
render_partial(prompt, prompt_width, @line, @first_line_started_from + @started_from + 1, with_control: false)
end
@cursor = @cursor_max = calculate_width(@line)
@byte_pointer = @line.bytesize
@highest_in_all += @highest_in_this
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
@first_line_started_from += @started_from + 1
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
@previous_line_index = nil
end
def just_move_cursor
prompt, prompt_width, prompt_list = check_multiline_prompt(@buffer_of_lines)
move_cursor_up(@started_from)
new_first_line_started_from =
if @line_index.zero?
0
else
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
first_line_diff = new_first_line_started_from - @first_line_started_from
new_cursor, new_cursor_max, new_started_from, new_byte_pointer = calculate_nearest_cursor(@buffer_of_lines[@line_index], @cursor, @started_from, @byte_pointer, false)
new_started_from = calculate_height_by_width(prompt_width + new_cursor) - 1
calculate_scroll_partial_screen(@highest_in_all, new_first_line_started_from + new_started_from)
@previous_line_index = nil
if @rerender_all
@line = @buffer_of_lines[@line_index]
rerender_all_lines
@rerender_all = false
true
else
@line = @buffer_of_lines[@line_index]
@first_line_started_from = new_first_line_started_from
@started_from = new_started_from
@cursor = new_cursor
@cursor_max = new_cursor_max
@byte_pointer = new_byte_pointer
move_cursor_down(first_line_diff + @started_from)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
false
end
end
private def rerender_changed_current_line
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
prompt, prompt_width, prompt_list = check_multiline_prompt(new_lines)
all_height = calculate_height_by_lines(new_lines, prompt_list || prompt)
diff = all_height - @highest_in_all
move_cursor_down(@highest_in_all - @first_line_started_from - @started_from - 1)
if diff > 0
scroll_down(diff)
move_cursor_up(all_height - 1)
elsif diff < 0
(-diff).times do
Reline::IOGate.move_cursor_column(0)
Reline::IOGate.erase_after_cursor
move_cursor_up(1)
end
move_cursor_up(all_height - 1)
else
move_cursor_up(all_height - 1)
end
@highest_in_all = all_height
back = render_whole_lines(new_lines, prompt_list || prompt, prompt_width)
move_cursor_up(back)
if @previous_line_index
@buffer_of_lines[@previous_line_index] = @line
@line = @buffer_of_lines[@line_index]
end
@first_line_started_from =
if @line_index.zero?
0
else
calculate_height_by_lines(@buffer_of_lines[0..(@line_index - 1)], prompt_list || prompt)
end
if @prompt_proc
prompt = prompt_list[@line_index]
prompt_width = calculate_width(prompt, true)
end
move_cursor_down(@first_line_started_from)
calculate_nearest_cursor
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
move_cursor_down(@started_from)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
end
private def rerender_all_lines
move_cursor_up(@first_line_started_from + @started_from)
Reline::IOGate.move_cursor_column(0)
back = 0
new_buffer = whole_lines
prompt, prompt_width, prompt_list = check_multiline_prompt(new_buffer)
new_buffer.each_with_index do |line, index|
prompt_width = calculate_width(prompt_list[index], true) if @prompt_proc
width = prompt_width + calculate_width(line)
height = calculate_height_by_width(width)
back += height
end
old_highest_in_all = @highest_in_all
if @line_index.zero?
new_first_line_started_from = 0
else
new_first_line_started_from = calculate_height_by_lines(new_buffer[0..(@line_index - 1)], prompt_list || prompt)
end
new_started_from = calculate_height_by_width(prompt_width + @cursor) - 1
calculate_scroll_partial_screen(back, new_first_line_started_from + new_started_from)
if @scroll_partial_screen
move_cursor_up(@first_line_started_from + @started_from)
scroll_down(@screen_height - 1)
move_cursor_up(@screen_height)
Reline::IOGate.move_cursor_column(0)
elsif back > old_highest_in_all
scroll_down(back - 1)
move_cursor_up(back - 1)
elsif back < old_highest_in_all
scroll_down(back)
Reline::IOGate.erase_after_cursor
(old_highest_in_all - back - 1).times do
scroll_down(1)
Reline::IOGate.erase_after_cursor
end
move_cursor_up(old_highest_in_all - 1)
end
render_whole_lines(new_buffer, prompt_list || prompt, prompt_width)
if @prompt_proc
prompt = prompt_list[@line_index]
prompt_width = calculate_width(prompt, true)
end
@highest_in_this = calculate_height_by_width(prompt_width + @cursor_max)
@highest_in_all = back
@first_line_started_from = new_first_line_started_from
@started_from = new_started_from
if @scroll_partial_screen
Reline::IOGate.move_cursor_up(@screen_height - (@first_line_started_from + @started_from - @scroll_partial_screen) - 1)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
else
move_cursor_down(@first_line_started_from + @started_from - back + 1)
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
end
private def render_whole_lines(lines, prompt, prompt_width)
rendered_height = 0
modify_lines(lines).each_with_index do |line, index|
if prompt.is_a?(Array)
line_prompt = prompt[index]
prompt_width = calculate_width(line_prompt, true)
else
line_prompt = prompt
end
height = render_partial(line_prompt, prompt_width, line, rendered_height, with_control: false)
if index < (lines.size - 1)
if @scroll_partial_screen
if (@scroll_partial_screen - height) < rendered_height and (@scroll_partial_screen + @screen_height - 1) >= (rendered_height + height)
move_cursor_down(1)
end
else
scroll_down(1)
end
rendered_height += height
else
rendered_height += height - 1
end
end
rendered_height
end
private def render_partial(prompt, prompt_width, line_to_render, this_started_from, with_control: true)
visual_lines, height = split_by_width(line_to_render.nil? ? prompt : prompt + line_to_render, @screen_size.last)
cursor_up_from_last_line = 0
if @scroll_partial_screen
last_visual_line = this_started_from + (height - 1)
last_screen_line = @scroll_partial_screen + (@screen_height - 1)
if (@scroll_partial_screen - this_started_from) >= height
# Render nothing because this line is before the screen.
visual_lines = []
elsif this_started_from > last_screen_line
# Render nothing because this line is after the screen.
visual_lines = []
else
deleted_lines_before_screen = []
if @scroll_partial_screen > this_started_from and last_visual_line >= @scroll_partial_screen
# A part of visual lines are before the screen.
deleted_lines_before_screen = visual_lines.shift((@scroll_partial_screen - this_started_from) * 2)
deleted_lines_before_screen.compact!
end
if this_started_from <= last_screen_line and last_screen_line < last_visual_line
# A part of visual lines are after the screen.
visual_lines.pop((last_visual_line - last_screen_line) * 2)
end
move_cursor_up(deleted_lines_before_screen.size - @started_from)
cursor_up_from_last_line = @started_from - deleted_lines_before_screen.size
end
end
if with_control
if height > @highest_in_this
diff = height - @highest_in_this
scroll_down(diff)
@highest_in_all += diff
@highest_in_this = height
move_cursor_up(diff)
elsif height < @highest_in_this
diff = @highest_in_this - height
@highest_in_all -= diff
@highest_in_this = height
end
move_cursor_up(@started_from)
@started_from = calculate_height_by_width(prompt_width + @cursor) - 1
cursor_up_from_last_line = height - 1 - @started_from
end
if Reline::Unicode::CSI_REGEXP.match?(prompt + line_to_render)
@output.write "\e[0m" # clear character decorations
end
visual_lines.each_with_index do |line, index|
Reline::IOGate.move_cursor_column(0)
if line.nil?
if calculate_width(visual_lines[index - 1], true) == Reline::IOGate.get_screen_size.last
# reaches the end of line
if Reline::IOGate.win? and Reline::IOGate.win_legacy_console?
# A newline is automatically inserted if a character is rendered at
# eol on command prompt.
else
# When the cursor is at the end of the line and erases characters
# after the cursor, some terminals delete the character at the
# cursor position.
move_cursor_down(1)
Reline::IOGate.move_cursor_column(0)
end
else
Reline::IOGate.erase_after_cursor
move_cursor_down(1)
Reline::IOGate.move_cursor_column(0)
end
next
end
@output.write line
if Reline::IOGate.win? and Reline::IOGate.win_legacy_console? and calculate_width(line, true) == Reline::IOGate.get_screen_size.last
# A newline is automatically inserted if a character is rendered at eol on command prompt.
@rest_height -= 1 if @rest_height > 0
end
@output.flush
if @first_prompt
@first_prompt = false
@pre_input_hook&.call
end
end
unless visual_lines.empty?
Reline::IOGate.erase_after_cursor
Reline::IOGate.move_cursor_column(0)
end
if with_control
# Just after rendring, so the cursor is on the last line.
if finished?
Reline::IOGate.move_cursor_column(0)
else
# Moves up from bottom of lines to the cursor position.
move_cursor_up(cursor_up_from_last_line)
# This logic is buggy if a fullwidth char is wrapped because there is only one halfwidth at end of a line.
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
end
height
end
private def modify_lines(before)
return before if before.nil? || before.empty? || simplified_rendering?
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: finished?)
after.lines("\n").map { |l| l.chomp('') }
else
before
end
end
private def show_menu
scroll_down(@highest_in_all - @first_line_started_from)
@rerender_all = true
@menu_info.list.sort!.each do |item|
Reline::IOGate.move_cursor_column(0)
@output.write item
@output.flush
scroll_down(1)
end
scroll_down(@highest_in_all - 1)
move_cursor_up(@highest_in_all - 1 - @first_line_started_from)
end
private def clear_screen_buffer(prompt, prompt_list, prompt_width)
Reline::IOGate.clear_screen
back = 0
modify_lines(whole_lines).each_with_index do |line, index|
if @prompt_proc
pr = prompt_list[index]
height = render_partial(pr, calculate_width(pr), line, back, with_control: false)
else
height = render_partial(prompt, prompt_width, line, back, with_control: false)
end
if index < (@buffer_of_lines.size - 1)
move_cursor_down(1)
back += height
end
end
move_cursor_up(back)
move_cursor_down(@first_line_started_from + @started_from)
@rest_height = (Reline::IOGate.get_screen_size.first - 1) - Reline::IOGate.cursor_pos.y
Reline::IOGate.move_cursor_column((prompt_width + @cursor) % @screen_size.last)
end
def editing_mode
@config.editing_mode
end
private def menu(target, list)
@menu_info = MenuInfo.new(target, list)
end
private def complete_internal_proc(list, is_menu)
preposing, target, postposing = retrieve_completion_block
list = list.select { |i|
if i and not Encoding.compatible?(target.encoding, i.encoding)
raise Encoding::CompatibilityError, "#{target.encoding.name} is not compatible with #{i.encoding.name}"
end
if @config.completion_ignore_case
i&.downcase&.start_with?(target.downcase)
else
i&.start_with?(target)
end
}.uniq
if is_menu
menu(target, list)
return nil
end
completed = list.inject { |memo, item|
begin
memo_mbchars = memo.unicode_normalize.grapheme_clusters
item_mbchars = item.unicode_normalize.grapheme_clusters
rescue Encoding::CompatibilityError
memo_mbchars = memo.grapheme_clusters
item_mbchars = item.grapheme_clusters
end
size = [memo_mbchars.size, item_mbchars.size].min
result = ''
size.times do |i|
if @config.completion_ignore_case
if memo_mbchars[i].casecmp?(item_mbchars[i])
result << memo_mbchars[i]
else
break
end
else
if memo_mbchars[i] == item_mbchars[i]
result << memo_mbchars[i]
else
break
end
end
end
result
}
[target, preposing, completed, postposing]
end
private def complete(list, just_show_list = false)
case @completion_state
when CompletionState::NORMAL, CompletionState::JOURNEY
@completion_state = CompletionState::COMPLETION
when CompletionState::PERFECT_MATCH
@dig_perfect_match_proc&.(@perfect_matched)
end
if just_show_list
is_menu = true
elsif @completion_state == CompletionState::MENU
is_menu = true
elsif @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
is_menu = true
else
is_menu = false
end
result = complete_internal_proc(list, is_menu)
if @completion_state == CompletionState::MENU_WITH_PERFECT_MATCH
@completion_state = CompletionState::PERFECT_MATCH
end
return if result.nil?
target, preposing, completed, postposing = result
return if completed.nil?
if target <= completed and (@completion_state == CompletionState::COMPLETION)
if list.include?(completed)
if list.one?
@completion_state = CompletionState::PERFECT_MATCH
else
@completion_state = CompletionState::MENU_WITH_PERFECT_MATCH
end
@perfect_matched = completed
else
@completion_state = CompletionState::MENU
end
if not just_show_list and target < completed
@line = preposing + completed + completion_append_character.to_s + postposing
line_to_pointer = preposing + completed + completion_append_character.to_s
@cursor_max = calculate_width(@line)
@cursor = calculate_width(line_to_pointer)
@byte_pointer = line_to_pointer.bytesize
end
end
end
private def move_completed_list(list, direction)
case @completion_state
when CompletionState::NORMAL, CompletionState::COMPLETION,
CompletionState::MENU, CompletionState::MENU_WITH_PERFECT_MATCH
@completion_state = CompletionState::JOURNEY
result = retrieve_completion_block
return if result.nil?
preposing, target, postposing = result
@completion_journey_data = CompletionJourneyData.new(
preposing, postposing,
[target] + list.select{ |item| item.start_with?(target) }, 0)
if @completion_journey_data.list.size == 1
@completion_journey_data.pointer = 0
else
case direction
when :up
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
when :down
@completion_journey_data.pointer = 1
end
end
@completion_state = CompletionState::JOURNEY
else
case direction
when :up
@completion_journey_data.pointer -= 1
if @completion_journey_data.pointer < 0
@completion_journey_data.pointer = @completion_journey_data.list.size - 1
end
when :down
@completion_journey_data.pointer += 1
if @completion_journey_data.pointer >= @completion_journey_data.list.size
@completion_journey_data.pointer = 0
end
end
end
completed = @completion_journey_data.list[@completion_journey_data.pointer]
new_line = (@completion_journey_data.preposing + completed + @completion_journey_data.postposing).split("\n")[@line_index]
@line = new_line.nil? ? String.new(encoding: @encoding) : new_line
line_to_pointer = (@completion_journey_data.preposing + completed).split("\n").last
line_to_pointer = String.new(encoding: @encoding) if line_to_pointer.nil?
@cursor_max = calculate_width(@line)
@cursor = calculate_width(line_to_pointer)
@byte_pointer = line_to_pointer.bytesize
end
private def run_for_operators(key, method_symbol, &block)
if @waiting_operator_proc
if VI_MOTIONS.include?(method_symbol)
old_cursor, old_byte_pointer = @cursor, @byte_pointer
@vi_arg = @waiting_operator_vi_arg if @waiting_operator_vi_arg&.> 1
block.(true)
unless @waiting_proc
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
@cursor, @byte_pointer = old_cursor, old_byte_pointer
@waiting_operator_proc.(cursor_diff, byte_pointer_diff)
else
old_waiting_proc = @waiting_proc
old_waiting_operator_proc = @waiting_operator_proc
current_waiting_operator_proc = @waiting_operator_proc
@waiting_proc = proc { |k|
old_cursor, old_byte_pointer = @cursor, @byte_pointer
old_waiting_proc.(k)
cursor_diff, byte_pointer_diff = @cursor - old_cursor, @byte_pointer - old_byte_pointer
@cursor, @byte_pointer = old_cursor, old_byte_pointer
current_waiting_operator_proc.(cursor_diff, byte_pointer_diff)
@waiting_operator_proc = old_waiting_operator_proc
}
end
else
# Ignores operator when not motion is given.
block.(false)
end
@waiting_operator_proc = nil
@waiting_operator_vi_arg = nil
if @vi_arg
@rerender_all = true
@vi_arg = nil
end
else
block.(false)
end
end
private def argumentable?(method_obj)
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
end
private def inclusive?(method_obj)
# If a motion method with the keyword argument "inclusive" follows the
# operator, it must contain the character at the cursor position.
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
end
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
if @config.editing_mode_is?(:emacs, :vi_insert) and @waiting_proc.nil? and @waiting_operator_proc.nil?
not_insertion = method_symbol != :ed_insert
process_insert(force: not_insertion)
end
if @vi_arg and argumentable?(method_obj)
if with_operator and inclusive?(method_obj)
method_obj.(key, arg: @vi_arg, inclusive: true)
else
method_obj.(key, arg: @vi_arg)
end
else
if with_operator and inclusive?(method_obj)
method_obj.(key, inclusive: true)
else
method_obj.(key)
end
end
end
private def process_key(key, method_symbol)
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
else
method_obj = nil
end
if method_symbol and key.is_a?(Symbol)
if @vi_arg and argumentable?(method_obj)
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
else
wrap_method_call(method_symbol, method_obj, key) if method_obj
end
@kill_ring.process
if @vi_arg
@rerender_al = true
@vi_arg = nil
end
elsif @vi_arg
if key.chr =~ /[0-9]/
ed_argument_digit(key)
else
if argumentable?(method_obj)
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
elsif @waiting_proc
@waiting_proc.(key)
elsif method_obj
wrap_method_call(method_symbol, method_obj, key)
else
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
end
@kill_ring.process
if @vi_arg
@rerender_all = true
@vi_arg = nil
end
end
elsif @waiting_proc
@waiting_proc.(key)
@kill_ring.process
elsif method_obj
if method_symbol == :ed_argument_digit
wrap_method_call(method_symbol, method_obj, key)
else
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
end
@kill_ring.process
else
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
end
end
private def normal_char(key)
method_symbol = method_obj = nil
if key.combined_char.is_a?(Symbol)
process_key(key.combined_char, key.combined_char)
return
end
@multibyte_buffer << key.combined_char
if @multibyte_buffer.size > 1
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
@multibyte_buffer.clear
else
# invalid
return
end
else # single byte
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
if key.with_meta and method_symbol == :ed_unassigned
# split ESC + key
method_symbol = @config.editing_mode.get_method("\e".ord)
process_key("\e".ord, method_symbol)
method_symbol = @config.editing_mode.get_method(key.char)
process_key(key.char, method_symbol)
else
process_key(key.combined_char, method_symbol)
end
@multibyte_buffer.clear
end
if @config.editing_mode_is?(:vi_command) and @cursor > 0 and @cursor == @cursor_max
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
mbchar = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor -= width
end
end
def input_key(key)
@last_key = key
@config.reset_oneshot_key_bindings
@dialogs.each do |dialog|
if key.char.instance_of?(Symbol) and key.char == dialog.name
return
end
end
@just_cursor_moving = nil
if key.char.nil?
if @first_char
@line = nil
end
finish
return
end
old_line = @line.dup
@first_char = false
completion_occurs = false
if @config.editing_mode_is?(:emacs, :vi_insert) and key.char == "\C-i".ord
unless @config.disable_completion
result = call_completion_proc
if result.is_a?(Array)
completion_occurs = true
process_insert
if @config.autocompletion
move_completed_list(result, :down)
else
complete(result)
end
end
end
elsif @config.editing_mode_is?(:emacs, :vi_insert) and key.char == :completion_journey_up
if not @config.disable_completion and @config.autocompletion
result = call_completion_proc
if result.is_a?(Array)
completion_occurs = true
process_insert
move_completed_list(result, :up)
end
end
elsif not @config.disable_completion and @config.editing_mode_is?(:vi_insert) and ["\C-p".ord, "\C-n".ord].include?(key.char)
unless @config.disable_completion
result = call_completion_proc
if result.is_a?(Array)
completion_occurs = true
process_insert
move_completed_list(result, "\C-p".ord == key.char ? :up : :down)
end
end
elsif Symbol === key.char and respond_to?(key.char, true)
process_key(key.char, key.char)
else
normal_char(key)
end
unless completion_occurs
@completion_state = CompletionState::NORMAL
@completion_journey_data = nil
end
if not @in_pasting and @just_cursor_moving.nil?
if @previous_line_index and @buffer_of_lines[@previous_line_index] == @line
@just_cursor_moving = true
elsif @previous_line_index.nil? and @buffer_of_lines[@line_index] == @line and old_line == @line
@just_cursor_moving = true
else
@just_cursor_moving = false
end
else
@just_cursor_moving = false
end
if @is_multiline and @auto_indent_proc and not simplified_rendering?
process_auto_indent
end
end
def call_completion_proc
result = retrieve_completion_block(true)
pre, target, post = result
result = call_completion_proc_with_checking_args(pre, target, post)
Reline.core.instance_variable_set(:@completion_quote_character, nil)
result
end
def call_completion_proc_with_checking_args(pre, target, post)
if @completion_proc and target
argnum = @completion_proc.parameters.inject(0) { |result, item|
case item.first
when :req, :opt
result + 1
when :rest
break 3
end
}
case argnum
when 1
result = @completion_proc.(target)
when 2
result = @completion_proc.(target, pre)
when 3..Float::INFINITY
result = @completion_proc.(target, pre, post)
end
end
result
end
private def process_auto_indent
return if not @check_new_auto_indent and @previous_line_index # move cursor up or down
if @check_new_auto_indent and @previous_line_index and @previous_line_index > 0 and @line_index > @previous_line_index
# Fix indent of a line when a newline is inserted to the next
new_lines = whole_lines(index: @previous_line_index, line: @line)
new_indent = @auto_indent_proc.(new_lines[0..-3].push(''), @line_index - 1, 0, true)
md = @line.match(/\A */)
prev_indent = md[0].count(' ')
@line = ' ' * new_indent + @line.lstrip
new_indent = nil
result = @auto_indent_proc.(new_lines[0..-2], @line_index - 1, (new_lines[-2].size + 1), false)
if result
new_indent = result
end
if new_indent&.>= 0
@line = ' ' * new_indent + @line.lstrip
end
end
if @previous_line_index
new_lines = whole_lines(index: @previous_line_index, line: @line)
else
new_lines = whole_lines
end
new_indent = @auto_indent_proc.(new_lines, @line_index, @byte_pointer, @check_new_auto_indent)
new_indent = @cursor_max if new_indent&.> @cursor_max
if new_indent&.>= 0
md = new_lines[@line_index].match(/\A */)
prev_indent = md[0].count(' ')
if @check_new_auto_indent
@buffer_of_lines[@line_index] = ' ' * new_indent + @buffer_of_lines[@line_index].lstrip
@cursor = new_indent
@byte_pointer = new_indent
else
@line = ' ' * new_indent + @line.lstrip
@cursor += new_indent - prev_indent
@byte_pointer += new_indent - prev_indent
end
end
@check_new_auto_indent = false
end
def retrieve_completion_block(set_completion_quote_character = false)
if Reline.completer_word_break_characters.empty?
word_break_regexp = nil
else
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
end
if Reline.completer_quote_characters.empty?
quote_characters_regexp = nil
else
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
end
before = @line.byteslice(0, @byte_pointer)
rest = nil
break_pointer = nil
quote = nil
closing_quote = nil
escaped_quote = nil
i = 0
while i < @byte_pointer do
slice = @line.byteslice(i, @byte_pointer - i)
unless slice.valid_encoding?
i += 1
next
end
if quote and slice.start_with?(closing_quote)
quote = nil
i += 1
rest = nil
elsif quote and slice.start_with?(escaped_quote)
# skip
i += 2
elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
rest = $'
quote = $&
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
escaped_quote = /\\#{Regexp.escape(quote)}/
i += 1
break_pointer = i - 1
elsif word_break_regexp and not quote and slice =~ word_break_regexp
rest = $'
i += 1
before = @line.byteslice(i, @byte_pointer - i)
break_pointer = i
else
i += 1
end
end
postposing = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
if rest
preposing = @line.byteslice(0, break_pointer)
target = rest
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
insert_text(quote)
end
end
else
preposing = ''
if break_pointer
preposing = @line.byteslice(0, break_pointer)
else
preposing = ''
end
target = before
end
if @is_multiline
if @previous_line_index
lines = whole_lines(index: @previous_line_index, line: @line)
else
lines = whole_lines
end
if @line_index > 0
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
end
if (lines.size - 1) > @line_index
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
end
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
end
def confirm_multiline_termination
temp_buffer = @buffer_of_lines.dup
if @previous_line_index and @line_index == (@buffer_of_lines.size - 1)
temp_buffer[@previous_line_index] = @line
else
temp_buffer[@line_index] = @line
end
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end
def insert_text(text)
width = calculate_width(text)
if @cursor == @cursor_max
@line += text
else
@line = byteinsert(@line, @byte_pointer, text)
end
@byte_pointer += text.bytesize
@cursor += width
@cursor_max += width
end
def delete_text(start = nil, length = nil)
if start.nil? and length.nil?
if @is_multiline
if @buffer_of_lines.size == 1
@line&.clear
@byte_pointer = 0
@cursor = 0
@cursor_max = 0
elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
@buffer_of_lines.pop
@line_index -= 1
@line = @buffer_of_lines[@line_index]
@byte_pointer = 0
@cursor = 0
@cursor_max = calculate_width(@line)
elsif @line_index < (@buffer_of_lines.size - 1)
@buffer_of_lines.delete_at(@line_index)
@line = @buffer_of_lines[@line_index]
@byte_pointer = 0
@cursor = 0
@cursor_max = calculate_width(@line)
end
else
@line&.clear
@byte_pointer = 0
@cursor = 0
@cursor_max = 0
end
elsif not start.nil? and not length.nil?
if @line
before = @line.byteslice(0, start)
after = @line.byteslice(start + length, @line.bytesize)
@line = before + after
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
str = @line.byteslice(0, @byte_pointer)
@cursor = calculate_width(str)
@cursor_max = calculate_width(@line)
end
elsif start.is_a?(Range)
range = start
first = range.first
last = range.last
last = @line.bytesize - 1 if last > @line.bytesize
last += @line.bytesize if last < 0
first += @line.bytesize if first < 0
range = range.exclude_end? ? first...last : first..last
@line = @line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
str = @line.byteslice(0, @byte_pointer)
@cursor = calculate_width(str)
@cursor_max = calculate_width(@line)
else
@line = @line.byteslice(0, start)
@byte_pointer = @line.bytesize if @byte_pointer > @line.bytesize
str = @line.byteslice(0, @byte_pointer)
@cursor = calculate_width(str)
@cursor_max = calculate_width(@line)
end
end
def byte_pointer=(val)
@byte_pointer = val
str = @line.byteslice(0, @byte_pointer)
@cursor = calculate_width(str)
@cursor_max = calculate_width(@line)
end
def whole_lines(index: @line_index, line: @line)
temp_lines = @buffer_of_lines.dup
temp_lines[index] = line
temp_lines
end
def whole_buffer
if @buffer_of_lines.size == 1 and @line.nil?
nil
else
if @previous_line_index
whole_lines(index: @previous_line_index, line: @line).join("\n")
else
whole_lines.join("\n")
end
end
end
def finished?
@finished
end
def finish
@finished = true
@rerender_all = true
@config.reset
end
private def byteslice!(str, byte_pointer, size)
new_str = str.byteslice(0, byte_pointer)
new_str << str.byteslice(byte_pointer + size, str.bytesize)
[new_str, str.byteslice(byte_pointer, size)]
end
private def byteinsert(str, byte_pointer, other)
new_str = str.byteslice(0, byte_pointer)
new_str << other
new_str << str.byteslice(byte_pointer, str.bytesize)
new_str
end
private def calculate_width(str, allow_escape_code = false)
Reline::Unicode.calculate_width(str, allow_escape_code)
end
private def key_delete(key)
if @config.editing_mode_is?(:vi_insert, :emacs)
ed_delete_next_char(key)
end
end
private def key_newline(key)
if @is_multiline
if (@buffer_of_lines.size - 1) == @line_index and @line.bytesize == @byte_pointer
@add_newline_to_end_of_buffer = true
end
next_line = @line.byteslice(@byte_pointer, @line.bytesize - @byte_pointer)
cursor_line = @line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
@cursor = 0
@check_new_auto_indent = true unless @in_pasting
end
end
# Editline:: +ed-unassigned+ This editor command always results in an error.
# GNU Readline:: There is no corresponding macro.
private def ed_unassigned(key) end # do nothing
private def process_insert(force: false)
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
width = Reline::Unicode.calculate_width(@continuous_insertion_buffer)
bytesize = @continuous_insertion_buffer.bytesize
if @cursor == @cursor_max
@line += @continuous_insertion_buffer
else
@line = byteinsert(@line, @byte_pointer, @continuous_insertion_buffer)
end
@byte_pointer += bytesize
@cursor += width
@cursor_max += width
@continuous_insertion_buffer.clear
end
# Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
# In insert mode, insert the input character left of the cursor
# position. In replace mode, overwrite the character at the
# cursor and move the cursor to the right by one character
# position. Accept an argument to do this repeatedly. It is an
# error if the input character is the NUL character (+Ctrl-@+).
# Failure to enlarge the edit buffer also results in an error.
# Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
# the input digit to the argument being read. Otherwise, call
# +ed-insert+. It is an error if the input character is not a
# digit or if the existing argument is already greater than a
# million.
# GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
private def ed_insert(key)
str = nil
width = nil
bytesize = nil
if key.instance_of?(String)
begin
key.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
str = key
bytesize = key.bytesize
else
begin
key.chr.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
str = key.chr
bytesize = 1
end
if @in_pasting
@continuous_insertion_buffer << str
return
elsif not @continuous_insertion_buffer.empty?
process_insert
end
width = Reline::Unicode.get_mbchar_width(str)
if @cursor == @cursor_max
@line += str
else
@line = byteinsert(@line, @byte_pointer, str)
end
last_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer += bytesize
last_mbchar = @line.byteslice((@byte_pointer - bytesize - last_byte_size), last_byte_size)
combined_char = last_mbchar + str
if last_byte_size != 0 and combined_char.grapheme_clusters.size == 1
# combined char
last_mbchar_width = Reline::Unicode.get_mbchar_width(last_mbchar)
combined_char_width = Reline::Unicode.get_mbchar_width(combined_char)
if combined_char_width > last_mbchar_width
width = combined_char_width - last_mbchar_width
else
width = 0
end
end
@cursor += width
@cursor_max += width
end
alias_method :ed_digit, :ed_insert
alias_method :self_insert, :ed_insert
private def ed_quoted_insert(str, arg: 1)
@waiting_proc = proc { |key|
arg.times do
if key == "\C-j".ord or key == "\C-m".ord
key_newline(key)
elsif key == 0
# Ignore NUL.
else
ed_insert(key)
end
end
@waiting_proc = nil
}
end
alias_method :quoted_insert, :ed_quoted_insert
private def ed_next_char(key, arg: 1)
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
if (@byte_pointer < @line.bytesize)
mbchar = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor += width if width
@byte_pointer += byte_size
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == @line.bytesize and @line_index < @buffer_of_lines.size - 1
next_line = @buffer_of_lines[@line_index + 1]
@cursor = 0
@byte_pointer = 0
@cursor_max = calculate_width(next_line)
@previous_line_index = @line_index
@line_index += 1
end
arg -= 1
ed_next_char(key, arg: arg) if arg > 0
end
alias_method :forward_char, :ed_next_char
private def ed_prev_char(key, arg: 1)
if @cursor > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
mbchar = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor -= width
elsif @is_multiline and @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
prev_line = @buffer_of_lines[@line_index - 1]
@cursor = calculate_width(prev_line)
@byte_pointer = prev_line.bytesize
@cursor_max = calculate_width(prev_line)
@previous_line_index = @line_index
@line_index -= 1
end
arg -= 1
ed_prev_char(key, arg: arg) if arg > 0
end
alias_method :backward_char, :ed_prev_char
private def vi_first_print(key)
@byte_pointer, @cursor = Reline::Unicode.vi_first_print(@line)
end
private def ed_move_to_beg(key)
@byte_pointer = @cursor = 0
end
alias_method :beginning_of_line, :ed_move_to_beg
private def ed_move_to_end(key)
@byte_pointer = 0
@cursor = 0
byte_size = 0
while @byte_pointer < @line.bytesize
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
if byte_size > 0
mbchar = @line.byteslice(@byte_pointer, byte_size)
@cursor += Reline::Unicode.get_mbchar_width(mbchar)
end
@byte_pointer += byte_size
end
end
alias_method :end_of_line, :ed_move_to_end
private def generate_searcher
Fiber.new do |first_key|
prev_search_key = first_key
search_word = String.new(encoding: @encoding)
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
last_hit = nil
case first_key
when "\C-r".ord
prompt_name = 'reverse-i-search'
when "\C-s".ord
prompt_name = 'i-search'
end
loop do
key = Fiber.yield(search_word)
search_again = false
case key
when -1 # determined
Reline.last_incremental_search = search_word
break
when "\C-h".ord, "\C-?".ord
grapheme_clusters = search_word.grapheme_clusters
if grapheme_clusters.size > 0
grapheme_clusters.pop
search_word = grapheme_clusters.join
end
when "\C-r".ord, "\C-s".ord
search_again = true if prev_search_key == key
prev_search_key = key
else
multibyte_buf << key
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
search_word << multibyte_buf.dup.force_encoding(@encoding)
multibyte_buf.clear
end
end
hit = nil
if not search_word.empty? and @line_backup_in_history&.include?(search_word)
@history_pointer = nil
hit = @line_backup_in_history
else
if search_again
if search_word.empty? and Reline.last_incremental_search
search_word = Reline.last_incremental_search
end
if @history_pointer
case prev_search_key
when "\C-r".ord
history_pointer_base = 0
history = Reline::HISTORY[0..(@history_pointer - 1)]
when "\C-s".ord
history_pointer_base = @history_pointer + 1
history = Reline::HISTORY[(@history_pointer + 1)..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
elsif @history_pointer
case prev_search_key
when "\C-r".ord
history_pointer_base = 0
history = Reline::HISTORY[0..@history_pointer]
when "\C-s".ord
history_pointer_base = @history_pointer
history = Reline::HISTORY[@history_pointer..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
case prev_search_key
when "\C-r".ord
hit_index = history.rindex { |item|
item.include?(search_word)
}
when "\C-s".ord
hit_index = history.index { |item|
item.include?(search_word)
}
end
if hit_index
@history_pointer = history_pointer_base + hit_index
hit = Reline::HISTORY[@history_pointer]
end
end
case prev_search_key
when "\C-r".ord
prompt_name = 'reverse-i-search'
when "\C-s".ord
prompt_name = 'i-search'
end
if hit
if @is_multiline
@buffer_of_lines = hit.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@byte_pointer = @line.bytesize
@cursor = @cursor_max = calculate_width(@line)
@rerender_all = true
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
else
@line = hit
@searching_prompt = "(%s)`%s': %s" % [prompt_name, search_word, hit]
end
last_hit = hit
else
if @is_multiline
@rerender_all = true
@searching_prompt = "(failed %s)`%s'" % [prompt_name, search_word]
else
@searching_prompt = "(failed %s)`%s': %s" % [prompt_name, search_word, last_hit]
end
end
end
end
end
private def incremental_search_history(key)
unless @history_pointer
if @is_multiline
@line_backup_in_history = whole_buffer
else
@line_backup_in_history = @line
end
end
searcher = generate_searcher
searcher.resume(key)
@searching_prompt = "(reverse-i-search)`': "
termination_keys = ["\C-j".ord]
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@waiting_proc = ->(k) {
case k
when *termination_keys
if @history_pointer
buffer = Reline::HISTORY[@history_pointer]
else
buffer = @line_backup_in_history
end
if @is_multiline
@buffer_of_lines = buffer.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@rerender_all = true
else
@line = buffer
end
@searching_prompt = nil
@waiting_proc = nil
@cursor_max = calculate_width(@line)
@cursor = @byte_pointer = 0
@rerender_all = true
@cached_prompt_list = nil
searcher.resume(-1)
when "\C-g".ord
if @is_multiline
@buffer_of_lines = @line_backup_in_history.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@rerender_all = true
else
@line = @line_backup_in_history
end
@history_pointer = nil
@searching_prompt = nil
@waiting_proc = nil
@line_backup_in_history = nil
@cursor_max = calculate_width(@line)
@cursor = @byte_pointer = 0
@rerender_all = true
else
chr = k.is_a?(String) ? k : k.chr(Encoding::ASCII_8BIT)
if chr.match?(/[[:print:]]/) or k == "\C-h".ord or k == "\C-?".ord or k == "\C-r".ord or k == "\C-s".ord
searcher.resume(k)
else
if @history_pointer
line = Reline::HISTORY[@history_pointer]
else
line = @line_backup_in_history
end
if @is_multiline
@line_backup_in_history = whole_buffer
@buffer_of_lines = line.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@rerender_all = true
else
@line_backup_in_history = @line
@line = line
end
@searching_prompt = nil
@waiting_proc = nil
@cursor_max = calculate_width(@line)
@cursor = @byte_pointer = 0
@rerender_all = true
@cached_prompt_list = nil
searcher.resume(-1)
end
end
}
end
private def vi_search_prev(key)
incremental_search_history(key)
end
alias_method :reverse_search_history, :vi_search_prev
private def vi_search_next(key)
incremental_search_history(key)
end
alias_method :forward_search_history, :vi_search_next
private def ed_search_prev_history(key, arg: 1)
history = nil
h_pointer = nil
line_no = nil
substr = @line.slice(0, @byte_pointer)
if @history_pointer.nil?
return if not @line.empty? and substr.empty?
history = Reline::HISTORY
elsif @history_pointer.zero?
history = nil
h_pointer = nil
else
history = Reline::HISTORY.slice(0, @history_pointer)
end
return if history.nil?
if @is_multiline
h_pointer = history.rindex { |h|
h.split("\n").each_with_index { |l, i|
if l.start_with?(substr)
line_no = i
break
end
}
not line_no.nil?
}
else
h_pointer = history.rindex { |l|
l.start_with?(substr)
}
end
return if h_pointer.nil?
@history_pointer = h_pointer
if @is_multiline
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = line_no
@line = @buffer_of_lines[@line_index]
@rerender_all = true
else
@line = Reline::HISTORY[@history_pointer]
end
@cursor_max = calculate_width(@line)
arg -= 1
ed_search_prev_history(key, arg: arg) if arg > 0
end
alias_method :history_search_backward, :ed_search_prev_history
private def ed_search_next_history(key, arg: 1)
substr = @line.slice(0, @byte_pointer)
if @history_pointer.nil?
return
elsif @history_pointer == (Reline::HISTORY.size - 1) and not substr.empty?
return
end
history = Reline::HISTORY.slice((@history_pointer + 1)..-1)
h_pointer = nil
line_no = nil
if @is_multiline
h_pointer = history.index { |h|
h.split("\n").each_with_index { |l, i|
if l.start_with?(substr)
line_no = i
break
end
}
not line_no.nil?
}
else
h_pointer = history.index { |l|
l.start_with?(substr)
}
end
h_pointer += @history_pointer + 1 if h_pointer and @history_pointer
return if h_pointer.nil? and not substr.empty?
@history_pointer = h_pointer
if @is_multiline
if @history_pointer.nil? and substr.empty?
@buffer_of_lines = []
@line_index = 0
else
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
@line_index = line_no
end
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line = @buffer_of_lines[@line_index]
@rerender_all = true
else
if @history_pointer.nil? and substr.empty?
@line = ''
else
@line = Reline::HISTORY[@history_pointer]
end
end
@cursor_max = calculate_width(@line)
arg -= 1
ed_search_next_history(key, arg: arg) if arg > 0
end
alias_method :history_search_forward, :ed_search_next_history
private def ed_prev_history(key, arg: 1)
if @is_multiline and @line_index > 0
@previous_line_index = @line_index
@line_index -= 1
return
end
if Reline::HISTORY.empty?
return
end
if @history_pointer.nil?
@history_pointer = Reline::HISTORY.size - 1
if @is_multiline
@line_backup_in_history = whole_buffer
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@rerender_all = true
else
@line_backup_in_history = @line
@line = Reline::HISTORY[@history_pointer]
end
elsif @history_pointer.zero?
return
else
if @is_multiline
Reline::HISTORY[@history_pointer] = whole_buffer
@history_pointer -= 1
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = @buffer_of_lines.size - 1
@line = @buffer_of_lines.last
@rerender_all = true
else
Reline::HISTORY[@history_pointer] = @line
@history_pointer -= 1
@line = Reline::HISTORY[@history_pointer]
end
end
if @config.editing_mode_is?(:emacs, :vi_insert)
@cursor_max = @cursor = calculate_width(@line)
@byte_pointer = @line.bytesize
elsif @config.editing_mode_is?(:vi_command)
@byte_pointer = @cursor = 0
@cursor_max = calculate_width(@line)
end
arg -= 1
ed_prev_history(key, arg: arg) if arg > 0
end
alias_method :previous_history, :ed_prev_history
private def ed_next_history(key, arg: 1)
if @is_multiline and @line_index < (@buffer_of_lines.size - 1)
@previous_line_index = @line_index
@line_index += 1
return
end
if @history_pointer.nil?
return
elsif @history_pointer == (Reline::HISTORY.size - 1)
if @is_multiline
@history_pointer = nil
@buffer_of_lines = @line_backup_in_history.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = 0
@line = @buffer_of_lines.first
@rerender_all = true
else
@history_pointer = nil
@line = @line_backup_in_history
end
else
if @is_multiline
Reline::HISTORY[@history_pointer] = whole_buffer
@history_pointer += 1
@buffer_of_lines = Reline::HISTORY[@history_pointer].split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = 0
@line = @buffer_of_lines.first
@rerender_all = true
else
Reline::HISTORY[@history_pointer] = @line
@history_pointer += 1
@line = Reline::HISTORY[@history_pointer]
end
end
@line = '' unless @line
if @config.editing_mode_is?(:emacs, :vi_insert)
@cursor_max = @cursor = calculate_width(@line)
@byte_pointer = @line.bytesize
elsif @config.editing_mode_is?(:vi_command)
@byte_pointer = @cursor = 0
@cursor_max = calculate_width(@line)
end
arg -= 1
ed_next_history(key, arg: arg) if arg > 0
end
alias_method :next_history, :ed_next_history
private def ed_newline(key)
process_insert(force: true)
if @is_multiline
if @config.editing_mode_is?(:vi_command)
if @line_index < (@buffer_of_lines.size - 1)
ed_next_history(key) # means cursor down
else
# should check confirm_multiline_termination to finish?
finish
end
else
if @line_index == (@buffer_of_lines.size - 1)
if confirm_multiline_termination
finish
else
key_newline(key)
end
else
# should check confirm_multiline_termination to finish?
@previous_line_index = @line_index
@line_index = @buffer_of_lines.size - 1
finish
end
end
else
if @history_pointer
Reline::HISTORY[@history_pointer] = @line
@history_pointer = nil
end
finish
end
end
private def em_delete_prev_char(key, arg: 1)
if @is_multiline and @cursor == 0 and @line_index > 0
@buffer_of_lines[@line_index] = @line
@cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
@line = @buffer_of_lines[@line_index]
@cursor_max = calculate_width(@line)
@rerender_all = true
elsif @cursor > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor -= width
@cursor_max -= width
end
arg -= 1
em_delete_prev_char(key, arg: arg) if arg > 0
end
alias_method :backward_delete_char, :em_delete_prev_char
# Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
# +Ctrl-U+) + Kill from the cursor to the end of the line.
# GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
# the line. With a negative numeric argument, kill backward
# from the cursor to the beginning of the current line.
private def ed_kill_line(key)
if @line.bytesize > @byte_pointer
@line, deleted = byteslice!(@line, @byte_pointer, @line.bytesize - @byte_pointer)
@byte_pointer = @line.bytesize
@cursor = @cursor_max = calculate_width(@line)
@kill_ring.append(deleted)
elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
@cursor = calculate_width(@line)
@byte_pointer = @line.bytesize
@line += @buffer_of_lines.delete_at(@line_index + 1)
@cursor_max = calculate_width(@line)
@buffer_of_lines[@line_index] = @line
@rerender_all = true
@rest_height += 1
end
end
alias_method :kill_line, :ed_kill_line
# Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
# beginning of the edit buffer to the cursor and save it to the
# cut buffer.
# GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
# to the beginning of the current line.
private def vi_kill_line_prev(key)
if @byte_pointer > 0
@line, deleted = byteslice!(@line, 0, @byte_pointer)
@byte_pointer = 0
@kill_ring.append(deleted, true)
@cursor_max = calculate_width(@line)
@cursor = 0
end
end
alias_method :unix_line_discard, :vi_kill_line_prev
# Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
# edit buffer and save it to the cut buffer. +vi-kill-line-prev+
# GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
# current line, no matter where point is.
private def em_kill_line(key)
if @line.size > 0
@kill_ring.append(@line.dup, true)
@line.clear
@byte_pointer = 0
@cursor_max = 0
@cursor = 0
end
end
alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@line = nil
if @buffer_of_lines.size > 1
scroll_down(@highest_in_all - @first_line_started_from)
end
Reline::IOGate.move_cursor_column(0)
@eof = true
finish
elsif @byte_pointer < @line.bytesize
splitted_last = @line.byteslice(@byte_pointer, @line.bytesize)
mbchar = splitted_last.grapheme_clusters.first
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor_max -= width
@line, = byteslice!(@line, @byte_pointer, mbchar.bytesize)
elsif @is_multiline and @byte_pointer == @line.bytesize and @buffer_of_lines.size > @line_index + 1
@cursor = calculate_width(@line)
@byte_pointer = @line.bytesize
@line += @buffer_of_lines.delete_at(@line_index + 1)
@cursor_max = calculate_width(@line)
@buffer_of_lines[@line_index] = @line
@rerender_all = true
@rest_height += 1
end
end
alias_method :delete_char, :em_delete
private def em_delete_or_list(key)
if @line.empty? or @byte_pointer < @line.bytesize
em_delete(key)
else # show completed list
result = call_completion_proc
if result.is_a?(Array)
complete(result, true)
end
end
end
alias_method :delete_char_or_list, :em_delete_or_list
private def em_yank(key)
yanked = @kill_ring.yank
if yanked
@line = byteinsert(@line, @byte_pointer, yanked)
yanked_width = calculate_width(yanked)
@cursor += yanked_width
@cursor_max += yanked_width
@byte_pointer += yanked.bytesize
end
end
alias_method :yank, :em_yank
private def em_yank_pop(key)
yanked, prev_yank = @kill_ring.yank_pop
if yanked
prev_yank_width = calculate_width(prev_yank)
@cursor -= prev_yank_width
@cursor_max -= prev_yank_width
@byte_pointer -= prev_yank.bytesize
@line, = byteslice!(@line, @byte_pointer, prev_yank.bytesize)
@line = byteinsert(@line, @byte_pointer, yanked)
yanked_width = calculate_width(yanked)
@cursor += yanked_width
@cursor_max += yanked_width
@byte_pointer += yanked.bytesize
end
end
alias_method :yank_pop, :em_yank_pop
private def ed_clear_screen(key)
@cleared = true
end
alias_method :clear_screen, :ed_clear_screen
private def em_next_word(key)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
@byte_pointer += byte_size
@cursor += width
end
end
alias_method :forward_word, :em_next_word
private def ed_prev_word(key)
if @byte_pointer > 0
byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
@byte_pointer -= byte_size
@cursor -= width
end
end
alias_method :backward_word, :ed_prev_word
private def em_delete_next_word(key)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.em_forward_word(@line, @byte_pointer)
@line, word = byteslice!(@line, @byte_pointer, byte_size)
@kill_ring.append(word)
@cursor_max -= width
end
end
private def ed_delete_prev_word(key)
if @byte_pointer > 0
byte_size, width = Reline::Unicode.em_backward_word(@line, @byte_pointer)
@line, word = byteslice!(@line, @byte_pointer - byte_size, byte_size)
@kill_ring.append(word, true)
@byte_pointer -= byte_size
@cursor -= width
@cursor_max -= width
end
end
private def ed_transpose_chars(key)
if @byte_pointer > 0
if @cursor_max > @cursor
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
mbchar = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor += width
@byte_pointer += byte_size
end
back1_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
if (@byte_pointer - back1_byte_size) > 0
back2_byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer - back1_byte_size)
back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
@line, back2_mbchar = byteslice!(@line, back2_pointer, back2_byte_size)
@line = byteinsert(@line, @byte_pointer - back2_byte_size, back2_mbchar)
end
end
end
alias_method :transpose_chars, :ed_transpose_chars
private def ed_transpose_words(key)
left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(@line, @byte_pointer)
before = @line.byteslice(0, left_word_start)
left_word = @line.byteslice(left_word_start, middle_start - left_word_start)
middle = @line.byteslice(middle_start, right_word_start - middle_start)
right_word = @line.byteslice(right_word_start, after_start - right_word_start)
after = @line.byteslice(after_start, @line.bytesize - after_start)
return if left_word.empty? or right_word.empty?
@line = before + right_word + middle + left_word + after
from_head_to_left_word = before + right_word + middle + left_word
@byte_pointer = from_head_to_left_word.bytesize
@cursor = calculate_width(from_head_to_left_word)
end
alias_method :transpose_words, :ed_transpose_words
private def em_capitol_case(key)
if @line.bytesize > @byte_pointer
byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(@line, @byte_pointer)
before = @line.byteslice(0, @byte_pointer)
after = @line.byteslice((@byte_pointer + byte_size)..-1)
@line = before + new_str + after
@byte_pointer += new_str.bytesize
@cursor += calculate_width(new_str)
end
end
alias_method :capitalize_word, :em_capitol_case
private def em_lower_case(key)
if @line.bytesize > @byte_pointer
byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
}.join
rest = @line.byteslice((@byte_pointer + byte_size)..-1)
@line = @line.byteslice(0, @byte_pointer) + part
@byte_pointer = @line.bytesize
@cursor = calculate_width(@line)
@cursor_max = @cursor + calculate_width(rest)
@line += rest
end
end
alias_method :downcase_word, :em_lower_case
private def em_upper_case(key)
if @line.bytesize > @byte_pointer
byte_size, = Reline::Unicode.em_forward_word(@line, @byte_pointer)
part = @line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
}.join
rest = @line.byteslice((@byte_pointer + byte_size)..-1)
@line = @line.byteslice(0, @byte_pointer) + part
@byte_pointer = @line.bytesize
@cursor = calculate_width(@line)
@cursor_max = @cursor + calculate_width(rest)
@line += rest
end
end
alias_method :upcase_word, :em_upper_case
private def em_kill_region(key)
if @byte_pointer > 0
byte_size, width = Reline::Unicode.em_big_backward_word(@line, @byte_pointer)
@line, deleted = byteslice!(@line, @byte_pointer - byte_size, byte_size)
@byte_pointer -= byte_size
@cursor -= width
@cursor_max -= width
@kill_ring.append(deleted, true)
end
end
alias_method :unix_word_rubout, :em_kill_region
private def copy_for_vi(text)
if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@vi_clipboard = text
end
end
private def vi_insert(key)
@config.editing_mode = :vi_insert
end
private def vi_add(key)
@config.editing_mode = :vi_insert
ed_next_char(key)
end
private def vi_command_mode(key)
ed_prev_char(key)
@config.editing_mode = :vi_command
end
alias_method :vi_movement_mode, :vi_command_mode
private def vi_next_word(key, arg: 1)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.vi_forward_word(@line, @byte_pointer, @drop_terminate_spaces)
@byte_pointer += byte_size
@cursor += width
end
arg -= 1
vi_next_word(key, arg: arg) if arg > 0
end
private def vi_prev_word(key, arg: 1)
if @byte_pointer > 0
byte_size, width = Reline::Unicode.vi_backward_word(@line, @byte_pointer)
@byte_pointer -= byte_size
@cursor -= width
end
arg -= 1
vi_prev_word(key, arg: arg) if arg > 0
end
private def vi_end_word(key, arg: 1, inclusive: false)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.vi_forward_end_word(@line, @byte_pointer)
@byte_pointer += byte_size
@cursor += width
end
arg -= 1
if inclusive and arg.zero?
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
if byte_size > 0
c = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
@cursor += width
end
end
vi_end_word(key, arg: arg) if arg > 0
end
private def vi_next_big_word(key, arg: 1)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.vi_big_forward_word(@line, @byte_pointer)
@byte_pointer += byte_size
@cursor += width
end
arg -= 1
vi_next_big_word(key, arg: arg) if arg > 0
end
private def vi_prev_big_word(key, arg: 1)
if @byte_pointer > 0
byte_size, width = Reline::Unicode.vi_big_backward_word(@line, @byte_pointer)
@byte_pointer -= byte_size
@cursor -= width
end
arg -= 1
vi_prev_big_word(key, arg: arg) if arg > 0
end
private def vi_end_big_word(key, arg: 1, inclusive: false)
if @line.bytesize > @byte_pointer
byte_size, width = Reline::Unicode.vi_big_forward_end_word(@line, @byte_pointer)
@byte_pointer += byte_size
@cursor += width
end
arg -= 1
if inclusive and arg.zero?
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
if byte_size > 0
c = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
@cursor += width
end
end
vi_end_big_word(key, arg: arg) if arg > 0
end
private def vi_delete_prev_char(key)
if @is_multiline and @cursor == 0 and @line_index > 0
@buffer_of_lines[@line_index] = @line
@cursor = calculate_width(@buffer_of_lines[@line_index - 1])
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
@line = @buffer_of_lines[@line_index]
@cursor_max = calculate_width(@line)
@rerender_all = true
elsif @cursor > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor -= width
@cursor_max -= width
end
end
private def vi_insert_at_bol(key)
ed_move_to_beg(key)
@config.editing_mode = :vi_insert
end
private def vi_add_at_eol(key)
ed_move_to_end(key)
@config.editing_mode = :vi_insert
end
private def ed_delete_prev_char(key, arg: 1)
deleted = ''
arg.times do
if @cursor > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
@byte_pointer -= byte_size
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
deleted.prepend(mbchar)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor -= width
@cursor_max -= width
end
end
copy_for_vi(deleted)
end
private def vi_zero(key)
@byte_pointer = 0
@cursor = 0
end
private def vi_change_meta(key, arg: 1)
@drop_terminate_spaces = true
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
if byte_pointer_diff > 0
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
end
copy_for_vi(cut)
@cursor += cursor_diff if cursor_diff < 0
@cursor_max -= cursor_diff.abs
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
@config.editing_mode = :vi_insert
@drop_terminate_spaces = false
}
@waiting_operator_vi_arg = arg
end
private def vi_delete_meta(key, arg: 1)
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
if byte_pointer_diff > 0
@line, cut = byteslice!(@line, @byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
@line, cut = byteslice!(@line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
end
copy_for_vi(cut)
@cursor += cursor_diff if cursor_diff < 0
@cursor_max -= cursor_diff.abs
@byte_pointer += byte_pointer_diff if byte_pointer_diff < 0
}
@waiting_operator_vi_arg = arg
end
private def vi_yank(key, arg: 1)
@waiting_operator_proc = proc { |cursor_diff, byte_pointer_diff|
if byte_pointer_diff > 0
cut = @line.byteslice(@byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
cut = @line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
end
copy_for_vi(cut)
}
@waiting_operator_vi_arg = arg
end
private def vi_list_or_eof(key)
if (not @is_multiline and @line.empty?) or (@is_multiline and @line.empty? and @buffer_of_lines.size == 1)
@line = nil
if @buffer_of_lines.size > 1
scroll_down(@highest_in_all - @first_line_started_from)
end
Reline::IOGate.move_cursor_column(0)
@eof = true
finish
else
ed_newline(key)
end
end
alias_method :vi_end_of_transmission, :vi_list_or_eof
alias_method :vi_eof_maybe, :vi_list_or_eof
private def ed_delete_next_char(key, arg: 1)
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
unless @line.empty? || byte_size == 0
@line, mbchar = byteslice!(@line, @byte_pointer, byte_size)
copy_for_vi(mbchar)
width = Reline::Unicode.get_mbchar_width(mbchar)
@cursor_max -= width
if @cursor > 0 and @cursor >= @cursor_max
byte_size = Reline::Unicode.get_prev_mbchar_size(@line, @byte_pointer)
mbchar = @line.byteslice(@byte_pointer - byte_size, byte_size)
width = Reline::Unicode.get_mbchar_width(mbchar)
@byte_pointer -= byte_size
@cursor -= width
end
end
arg -= 1
ed_delete_next_char(key, arg: arg) if arg > 0
end
private def vi_to_history_line(key)
if Reline::HISTORY.empty?
return
end
if @history_pointer.nil?
@history_pointer = 0
@line_backup_in_history = @line
@line = Reline::HISTORY[@history_pointer]
@cursor_max = calculate_width(@line)
@cursor = 0
@byte_pointer = 0
elsif @history_pointer.zero?
return
else
Reline::HISTORY[@history_pointer] = @line
@history_pointer = 0
@line = Reline::HISTORY[@history_pointer]
@cursor_max = calculate_width(@line)
@cursor = 0
@byte_pointer = 0
end
end
private def vi_histedit(key)
path = Tempfile.open { |fp|
if @is_multiline
fp.write whole_lines.join("\n")
else
fp.write @line
end
fp.path
}
system("#{ENV['EDITOR']} #{path}")
if @is_multiline
@buffer_of_lines = File.read(path).split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = 0
@line = @buffer_of_lines[@line_index]
@rerender_all = true
else
@line = File.read(path)
end
finish
end
private def vi_paste_prev(key, arg: 1)
if @vi_clipboard.size > 0
@line = byteinsert(@line, @byte_pointer, @vi_clipboard)
@cursor_max += calculate_width(@vi_clipboard)
cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
@cursor += calculate_width(cursor_point)
@byte_pointer += cursor_point.bytesize
end
arg -= 1
vi_paste_prev(key, arg: arg) if arg > 0
end
private def vi_paste_next(key, arg: 1)
if @vi_clipboard.size > 0
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
@line = byteinsert(@line, @byte_pointer + byte_size, @vi_clipboard)
@cursor_max += calculate_width(@vi_clipboard)
@cursor += calculate_width(@vi_clipboard)
@byte_pointer += @vi_clipboard.bytesize
end
arg -= 1
vi_paste_next(key, arg: arg) if arg > 0
end
private def ed_argument_digit(key)
if @vi_arg.nil?
if key.chr.to_i.zero?
if key.anybits?(0b10000000)
unescaped_key = key ^ 0b10000000
unless unescaped_key.chr.to_i.zero?
@vi_arg = unescaped_key.chr.to_i
end
end
else
@vi_arg = key.chr.to_i
end
else
@vi_arg = @vi_arg * 10 + key.chr.to_i
end
end
private def vi_to_column(key, arg: 0)
@byte_pointer, @cursor = @line.grapheme_clusters.inject([0, 0]) { |total, gc|
# total has [byte_size, cursor]
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
if (total.last + mbchar_width) >= arg
break total
elsif (total.last + mbchar_width) >= @cursor_max
break total
else
total = [total.first + gc.bytesize, total.last + mbchar_width]
total
end
}
end
private def vi_replace_char(key, arg: 1)
@waiting_proc = ->(k) {
if arg == 1
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
before = @line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
@line = before + k.chr + after
@cursor_max = calculate_width(@line)
@waiting_proc = nil
elsif arg > 1
byte_size = 0
arg.times do
byte_size += Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer + byte_size)
end
before = @line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
after = @line.byteslice(remaining_point, @line.bytesize - remaining_point)
replaced = k.chr * arg
@line = before + replaced + after
@byte_pointer += replaced.bytesize
@cursor += calculate_width(replaced)
@cursor_max = calculate_width(@line)
@waiting_proc = nil
end
}
end
private def vi_next_char(key, arg: 1, inclusive: false)
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
end
private def vi_to_next_char(key, arg: 1, inclusive: false)
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
end
private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
prev_total = nil
total = nil
found = false
@line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
width = Reline::Unicode.get_mbchar_width(mbchar)
total = [mbchar.bytesize, width]
else
if inputed_char == mbchar
arg -= 1
if arg.zero?
found = true
break
end
end
width = Reline::Unicode.get_mbchar_width(mbchar)
prev_total = total
total = [total.first + mbchar.bytesize, total.last + width]
end
end
if not need_prev_char and found and total
byte_size, width = total
@byte_pointer += byte_size
@cursor += width
elsif need_prev_char and found and prev_total
byte_size, width = prev_total
@byte_pointer += byte_size
@cursor += width
end
if inclusive
byte_size = Reline::Unicode.get_next_mbchar_size(@line, @byte_pointer)
if byte_size > 0
c = @line.byteslice(@byte_pointer, byte_size)
width = Reline::Unicode.get_mbchar_width(c)
@byte_pointer += byte_size
@cursor += width
end
end
@waiting_proc = nil
end
private def vi_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
end
private def vi_to_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
end
private def search_prev_char(key, arg, need_next_char = false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
prev_total = nil
total = nil
found = false
@line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
width = Reline::Unicode.get_mbchar_width(mbchar)
total = [mbchar.bytesize, width]
else
if inputed_char == mbchar
arg -= 1
if arg.zero?
found = true
break
end
end
width = Reline::Unicode.get_mbchar_width(mbchar)
prev_total = total
total = [total.first + mbchar.bytesize, total.last + width]
end
end
if not need_next_char and found and total
byte_size, width = total
@byte_pointer -= byte_size
@cursor -= width
elsif need_next_char and found and prev_total
byte_size, width = prev_total
@byte_pointer -= byte_size
@cursor -= width
end
@waiting_proc = nil
end
private def vi_join_lines(key, arg: 1)
if @is_multiline and @buffer_of_lines.size > @line_index + 1
@cursor = calculate_width(@line)
@byte_pointer = @line.bytesize
@line += ' ' + @buffer_of_lines.delete_at(@line_index + 1).lstrip
@cursor_max = calculate_width(@line)
@buffer_of_lines[@line_index] = @line
@rerender_all = true
@rest_height += 1
end
arg -= 1
vi_join_lines(key, arg: arg) if arg > 0
end
private def em_set_mark(key)
@mark_pointer = [@byte_pointer, @line_index]
end
alias_method :set_mark, :em_set_mark
private def em_exchange_mark(key)
return unless @mark_pointer
new_pointer = [@byte_pointer, @line_index]
@previous_line_index = @line_index
@byte_pointer, @line_index = @mark_pointer
@cursor = calculate_width(@line.byteslice(0, @byte_pointer))
@cursor_max = calculate_width(@line)
@mark_pointer = new_pointer
end
alias_method :exchange_point_and_mark, :em_exchange_mark
end
share/ruby/reline/config.rb 0000644 00000024726 15173547336 0011730 0 ustar 00 class Reline::Config
attr_reader :test_mode
KEYSEQ_PATTERN = /\\(?:C|Control)-[A-Za-z_]|\\(?:M|Meta)-[0-9A-Za-z_]|\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]|\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]|\\e|\\[\\\"\'abdfnrtv]|\\\d{1,3}|\\x\h{1,2}|./
class InvalidInputrc < RuntimeError
attr_accessor :file, :lineno
end
VARIABLE_NAMES = %w{
bind-tty-special-chars
blink-matching-paren
byte-oriented
completion-ignore-case
convert-meta
disable-completion
enable-keypad
expand-tilde
history-preserve-point
history-size
horizontal-scroll-mode
input-meta
keyseq-timeout
mark-directories
mark-modified-lines
mark-symlinked-directories
match-hidden-files
meta-flag
output-meta
page-completions
prefer-visible-bell
print-completions-horizontally
show-all-if-ambiguous
show-all-if-unmodified
visible-stats
show-mode-in-prompt
vi-cmd-mode-string
vi-ins-mode-string
emacs-mode-string
enable-bracketed-paste
isearch-terminators
}
VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
VARIABLE_NAME_SYMBOLS.each do |v|
attr_accessor v
end
attr_accessor :autocompletion
def initialize
@additional_key_bindings = {} # from inputrc
@additional_key_bindings[:emacs] = {}
@additional_key_bindings[:vi_insert] = {}
@additional_key_bindings[:vi_command] = {}
@oneshot_key_bindings = {}
@skip_section = nil
@if_stack = nil
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
@key_actors = {}
@key_actors[:emacs] = Reline::KeyActor::Emacs.new
@key_actors[:vi_insert] = Reline::KeyActor::ViInsert.new
@key_actors[:vi_command] = Reline::KeyActor::ViCommand.new
@vi_cmd_mode_string = '(cmd)'
@vi_ins_mode_string = '(ins)'
@emacs_mode_string = '@'
# https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
@history_size = -1 # unlimited
@keyseq_timeout = 500
@test_mode = false
@autocompletion = false
@convert_meta = true if seven_bit_encoding?(Reline::IOGate.encoding)
end
def reset
if editing_mode_is?(:vi_command)
@editing_mode_label = :vi_insert
end
@additional_key_bindings.keys.each do |key|
@additional_key_bindings[key].clear
end
@oneshot_key_bindings.clear
reset_default_key_bindings
end
def editing_mode
@key_actors[@editing_mode_label]
end
def editing_mode=(val)
@editing_mode_label = val
end
def editing_mode_is?(*val)
(val.respond_to?(:any?) ? val : [val]).any?(@editing_mode_label)
end
def keymap
@key_actors[@keymap_label]
end
def inputrc_path
case ENV['INPUTRC']
when nil, ''
else
return File.expand_path(ENV['INPUTRC'])
end
# In the XDG Specification, if ~/.config/readline/inputrc exists, then
# ~/.inputrc should not be read, but for compatibility with GNU Readline,
# if ~/.inputrc exists, then it is given priority.
home_rc_path = File.expand_path('~/.inputrc')
return home_rc_path if File.exist?(home_rc_path)
case path = ENV['XDG_CONFIG_HOME']
when nil, ''
else
path = File.join(path, 'readline/inputrc')
return path if File.exist?(path) and path == File.expand_path(path)
end
path = File.expand_path('~/.config/readline/inputrc')
return path if File.exist?(path)
return home_rc_path
end
private def default_inputrc_path
@default_inputrc_path ||= inputrc_path
end
def read(file = nil)
file ||= default_inputrc_path
begin
if file.respond_to?(:readlines)
lines = file.readlines
else
lines = File.readlines(file)
end
rescue Errno::ENOENT
return nil
end
read_lines(lines, file)
self
rescue InvalidInputrc => e
warn e.message
nil
end
def key_bindings
# The key bindings for each editing mode will be overwritten by the user-defined ones.
kb = @key_actors[@editing_mode_label].default_key_bindings.dup
kb.merge!(@additional_key_bindings[@editing_mode_label])
kb.merge!(@oneshot_key_bindings)
kb
end
def add_oneshot_key_binding(keystroke, target)
@oneshot_key_bindings[keystroke] = target
end
def reset_oneshot_key_bindings
@oneshot_key_bindings.clear
end
def add_default_key_binding_by_keymap(keymap, keystroke, target)
@key_actors[keymap].default_key_bindings[keystroke] = target
end
def add_default_key_binding(keystroke, target)
@key_actors[@keymap_label].default_key_bindings[keystroke] = target
end
def reset_default_key_bindings
@key_actors.values.each do |ka|
ka.reset_default_key_bindings
end
end
def read_lines(lines, file = nil)
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
begin
lines = lines.map do |l|
l.encode(Reline.encoding_system_needs)
rescue Encoding::UndefinedConversionError
mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
raise Reline::ConfigEncodingConversionError.new(mes)
end
end
end
conditions = [@skip_section, @if_stack]
@skip_section = nil
@if_stack = []
lines.each_with_index do |line, no|
next if line.match(/\A\s*#/)
no += 1
line = line.chomp.lstrip
if line.start_with?('$')
handle_directive(line[1..-1], file, no)
next
end
next if @skip_section
case line
when /^set +([^ ]+) +([^ ]+)/i
var, value = $1.downcase, $2
bind_variable(var, value)
next
when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
key, func_name = $1, $2
keystroke, func = bind_key(key, func_name)
next unless keystroke
@additional_key_bindings[@keymap_label][@keymap_prefix + keystroke] = func
end
end
unless @if_stack.empty?
raise InvalidInputrc, "#{file}:#{@if_stack.last[1]}: unclosed if"
end
ensure
@skip_section, @if_stack = conditions
end
def handle_directive(directive, file, no)
directive, args = directive.split(' ')
case directive
when 'if'
condition = false
case args
when 'mode'
when 'term'
when 'version'
else # application name
condition = true if args == 'Ruby'
condition = true if args == 'Reline'
end
@if_stack << [file, no, @skip_section]
@skip_section = !condition
when 'else'
if @if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched else"
end
@skip_section = !@skip_section
when 'endif'
if @if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
end
@skip_section = @if_stack.pop
when 'include'
read(args)
end
end
def bind_variable(name, value)
case name
when 'history-size'
begin
@history_size = Integer(value)
rescue ArgumentError
@history_size = 500
end
when 'bell-style'
@bell_style =
case value
when 'none', 'off'
:none
when 'audible', 'on'
:audible
when 'visible'
:visible
else
:audible
end
when 'comment-begin'
@comment_begin = value.dup
when 'completion-query-items'
@completion_query_items = value.to_i
when 'isearch-terminators'
@isearch_terminators = retrieve_string(value)
when 'editing-mode'
case value
when 'emacs'
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
when 'vi'
@editing_mode_label = :vi_insert
@keymap_label = :vi_insert
@keymap_prefix = []
end
when 'keymap'
case value
when 'emacs', 'emacs-standard'
@keymap_label = :emacs
@keymap_prefix = []
when 'emacs-ctlx'
@keymap_label = :emacs
@keymap_prefix = [?\C-x.ord]
when 'emacs-meta'
@keymap_label = :emacs
@keymap_prefix = [?\e.ord]
when 'vi', 'vi-move', 'vi-command'
@keymap_label = :vi_command
@keymap_prefix = []
when 'vi-insert'
@keymap_label = :vi_insert
@keymap_prefix = []
end
when 'keyseq-timeout'
@keyseq_timeout = value.to_i
when 'show-mode-in-prompt'
case value
when 'off'
@show_mode_in_prompt = false
when 'on'
@show_mode_in_prompt = true
else
@show_mode_in_prompt = false
end
when 'vi-cmd-mode-string'
@vi_cmd_mode_string = retrieve_string(value)
when 'vi-ins-mode-string'
@vi_ins_mode_string = retrieve_string(value)
when 'emacs-mode-string'
@emacs_mode_string = retrieve_string(value)
when *VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
end
end
def retrieve_string(str)
str = $1 if str =~ /\A"(.*)"\z/
parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
end
def bind_key(key, func_name)
if key =~ /\A"(.*)"\z/
keyseq = parse_keyseq($1)
else
keyseq = nil
end
if func_name =~ /"(.*)"/
func = parse_keyseq($1)
else
func = func_name.tr(?-, ?_).to_sym # It must be macro.
end
[keyseq, func]
end
def key_notation_to_code(notation)
case notation
when /\\(?:C|Control)-([A-Za-z_])/
(1 + $1.downcase.ord - ?a.ord)
when /\\(?:M|Meta)-([0-9A-Za-z_])/
modified_key = $1
case $1
when /[0-9]/
?\M-0.bytes.first + (modified_key.ord - ?0.ord)
when /[A-Z]/
?\M-A.bytes.first + (modified_key.ord - ?A.ord)
when /[a-z]/
?\M-a.bytes.first + (modified_key.ord - ?a.ord)
end
when /\\(?:C|Control)-(?:M|Meta)-[A-Za-z_]/, /\\(?:M|Meta)-(?:C|Control)-[A-Za-z_]/
# 129 M-^A
when /\\(\d{1,3})/ then $1.to_i(8) # octal
when /\\x(\h{1,2})/ then $1.to_i(16) # hexadecimal
when "\\e" then ?\e.ord
when "\\\\" then ?\\.ord
when "\\\"" then ?".ord
when "\\'" then ?'.ord
when "\\a" then ?\a.ord
when "\\b" then ?\b.ord
when "\\d" then ?\d.ord
when "\\f" then ?\f.ord
when "\\n" then ?\n.ord
when "\\r" then ?\r.ord
when "\\t" then ?\t.ord
when "\\v" then ?\v.ord
else notation.ord
end
end
def parse_keyseq(str)
ret = []
str.scan(KEYSEQ_PATTERN) do
ret << key_notation_to_code($&)
end
ret
end
private def seven_bit_encoding?(encoding)
encoding == Encoding::US_ASCII
end
end
share/ruby/reline/key_actor/emacs.rb 0000644 00000020155 15173547336 0013523 0 ustar 00 class Reline::KeyActor::Emacs < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:em_set_mark,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_prev_char,
# 3 ^C
:ed_ignore,
# 4 ^D
:em_delete,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_next_char,
# 7 ^G
:ed_unassigned,
# 8 ^H
:em_delete_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_quoted_insert,
# 18 ^R
:vi_search_prev,
# 19 ^S
:vi_search_next,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
:unix_line_discard,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:em_kill_region,
# 24 ^X
:ed_sequence_lead_in,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_ignore,
# 27 ^[
:em_meta_next,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_ignore,
# 30 ^^
:ed_unassigned,
# 31 ^_
:ed_unassigned,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_digit,
# 49 1
:ed_digit,
# 50 2
:ed_digit,
# 51 3
:ed_digit,
# 52 4
:ed_digit,
# 53 5
:ed_digit,
# 54 6
:ed_digit,
# 55 7
:ed_digit,
# 56 8
:ed_digit,
# 57 9
:ed_digit,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_delete_prev_word,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:key_newline,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_clear_screen,
# 141 M-^M
:key_newline,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:em_yank_pop,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:em_copy_prev_word,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_argument_digit,
# 177 M-1
:ed_argument_digit,
# 178 M-2
:ed_argument_digit,
# 179 M-3
:ed_argument_digit,
# 180 M-4
:ed_argument_digit,
# 181 M-5
:ed_argument_digit,
# 182 M-6
:ed_argument_digit,
# 183 M-7
:ed_argument_digit,
# 184 M-8
:ed_argument_digit,
# 185 M-9
:ed_argument_digit,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_prev_word,
# 195 M-C
:em_capitol_case,
# 196 M-D
:em_delete_next_word,
# 197 M-E
:ed_unassigned,
# 198 M-F
:em_next_word,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:em_lower_case,
# 205 M-M
:ed_unassigned,
# 206 M-N
:vi_search_next,
# 207 M-O
:ed_sequence_lead_in,
# 208 M-P
:vi_search_prev,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:em_upper_case,
# 214 M-V
:ed_unassigned,
# 215 M-W
:em_copy_region,
# 216 M-X
:ed_command,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_sequence_lead_in,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_prev_word,
# 227 M-c
:em_capitol_case,
# 228 M-d
:em_delete_next_word,
# 229 M-e
:ed_unassigned,
# 230 M-f
:em_next_word,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:em_lower_case,
# 237 M-m
:ed_unassigned,
# 238 M-n
:vi_search_next,
# 239 M-o
:ed_unassigned,
# 240 M-p
:vi_search_prev,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_transpose_words,
# 245 M-u
:em_upper_case,
# 246 M-v
:ed_unassigned,
# 247 M-w
:em_copy_region,
# 248 M-x
:ed_command,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_delete_prev_word
# EOF
]
end
share/ruby/reline/key_actor/vi_insert.rb 0000644 00000017747 15173547336 0014452 0 ustar 00 class Reline::KeyActor::ViInsert < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_insert,
# 2 ^B
:ed_insert,
# 3 ^C
:ed_insert,
# 4 ^D
:vi_list_or_eof,
# 5 ^E
:ed_insert,
# 6 ^F
:ed_insert,
# 7 ^G
:ed_insert,
# 8 ^H
:vi_delete_prev_char,
# 9 ^I
:ed_insert,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_insert,
# 12 ^L
:ed_insert,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_insert,
# 15 ^O
:ed_insert,
# 16 ^P
:ed_insert,
# 17 ^Q
:ed_ignore,
# 18 ^R
:vi_search_prev,
# 19 ^S
:vi_search_next,
# 20 ^T
:ed_insert,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_insert,
# 25 ^Y
:ed_insert,
# 26 ^Z
:ed_insert,
# 27 ^[
:vi_command_mode,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_insert,
# 30 ^^
:ed_insert,
# 31 ^_
:ed_insert,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_insert,
# 49 1
:ed_insert,
# 50 2
:ed_insert,
# 51 3
:ed_insert,
# 52 4
:ed_insert,
# 53 5
:ed_insert,
# 54 6
:ed_insert,
# 55 7
:ed_insert,
# 56 8
:ed_insert,
# 57 9
:ed_insert,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:vi_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_unassigned,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:key_newline,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_unassigned,
# 141 M-^M
:key_newline,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:ed_unassigned,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_unassigned,
# 177 M-1
:ed_unassigned,
# 178 M-2
:ed_unassigned,
# 179 M-3
:ed_unassigned,
# 180 M-4
:ed_unassigned,
# 181 M-5
:ed_unassigned,
# 182 M-6
:ed_unassigned,
# 183 M-7
:ed_unassigned,
# 184 M-8
:ed_unassigned,
# 185 M-9
:ed_unassigned,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_unassigned,
# 195 M-C
:ed_unassigned,
# 196 M-D
:ed_unassigned,
# 197 M-E
:ed_unassigned,
# 198 M-F
:ed_unassigned,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:ed_unassigned,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_unassigned,
# 207 M-O
:ed_unassigned,
# 208 M-P
:ed_unassigned,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:ed_unassigned,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_unassigned,
# 227 M-c
:ed_unassigned,
# 228 M-d
:ed_unassigned,
# 229 M-e
:ed_unassigned,
# 230 M-f
:ed_unassigned,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:ed_unassigned,
# 237 M-m
:ed_unassigned,
# 238 M-n
:ed_unassigned,
# 239 M-o
:ed_unassigned,
# 240 M-p
:ed_unassigned,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_unassigned,
# 245 M-u
:ed_unassigned,
# 246 M-v
:ed_unassigned,
# 247 M-w
:ed_unassigned,
# 248 M-x
:ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_unassigned
# EOF
]
end
share/ruby/reline/key_actor/vi_command.rb 0000644 00000021005 15173547336 0014542 0 ustar 00 class Reline::KeyActor::ViCommand < Reline::KeyActor::Base
MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_unassigned,
# 3 ^C
:ed_ignore,
# 4 ^D
:vi_end_of_transmission,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_unassigned,
# 7 ^G
:ed_unassigned,
# 8 ^H
:ed_unassigned,
# 9 ^I
:ed_unassigned,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_ignore,
# 18 ^R
:vi_search_prev,
# 19 ^S
:ed_ignore,
# 20 ^T
:ed_unassigned,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_unassigned,
# 25 ^Y
:ed_unassigned,
# 26 ^Z
:ed_unassigned,
# 27 ^[
:ed_unassigned,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_unassigned,
# 30 ^^
:ed_unassigned,
# 31 ^_
:ed_unassigned,
# 32 SPACE
:ed_next_char,
# 33 !
:ed_unassigned,
# 34 "
:ed_unassigned,
# 35 #
:vi_comment_out,
# 36 $
:ed_move_to_end,
# 37 %
:vi_match,
# 38 &
:ed_unassigned,
# 39 '
:ed_unassigned,
# 40 (
:ed_unassigned,
# 41 )
:ed_unassigned,
# 42 *
:ed_unassigned,
# 43 +
:ed_next_history,
# 44 ,
:vi_repeat_prev_char,
# 45 -
:ed_prev_history,
# 46 .
:vi_redo,
# 47 /
:vi_search_prev,
# 48 0
:vi_zero,
# 49 1
:ed_argument_digit,
# 50 2
:ed_argument_digit,
# 51 3
:ed_argument_digit,
# 52 4
:ed_argument_digit,
# 53 5
:ed_argument_digit,
# 54 6
:ed_argument_digit,
# 55 7
:ed_argument_digit,
# 56 8
:ed_argument_digit,
# 57 9
:ed_argument_digit,
# 58 :
:ed_command,
# 59 ;
:vi_repeat_next_char,
# 60 <
:ed_unassigned,
# 61 =
:ed_unassigned,
# 62 >
:ed_unassigned,
# 63 ?
:vi_search_next,
# 64 @
:vi_alias,
# 65 A
:vi_add_at_eol,
# 66 B
:vi_prev_big_word,
# 67 C
:vi_change_to_eol,
# 68 D
:ed_kill_line,
# 69 E
:vi_end_big_word,
# 70 F
:vi_prev_char,
# 71 G
:vi_to_history_line,
# 72 H
:ed_unassigned,
# 73 I
:vi_insert_at_bol,
# 74 J
:vi_join_lines,
# 75 K
:vi_search_prev,
# 76 L
:ed_unassigned,
# 77 M
:ed_unassigned,
# 78 N
:vi_repeat_search_prev,
# 79 O
:ed_sequence_lead_in,
# 80 P
:vi_paste_prev,
# 81 Q
:ed_unassigned,
# 82 R
:vi_replace_mode,
# 83 S
:vi_substitute_line,
# 84 T
:vi_to_prev_char,
# 85 U
:vi_undo_line,
# 86 V
:ed_unassigned,
# 87 W
:vi_next_big_word,
# 88 X
:ed_delete_prev_char,
# 89 Y
:vi_yank_end,
# 90 Z
:ed_unassigned,
# 91 [
:ed_sequence_lead_in,
# 92 \
:ed_unassigned,
# 93 ]
:ed_unassigned,
# 94 ^
:vi_first_print,
# 95 _
:vi_history_word,
# 96 `
:ed_unassigned,
# 97 a
:vi_add,
# 98 b
:vi_prev_word,
# 99 c
:vi_change_meta,
# 100 d
:vi_delete_meta,
# 101 e
:vi_end_word,
# 102 f
:vi_next_char,
# 103 g
:ed_unassigned,
# 104 h
:ed_prev_char,
# 105 i
:vi_insert,
# 106 j
:ed_next_history,
# 107 k
:ed_prev_history,
# 108 l
:ed_next_char,
# 109 m
:ed_unassigned,
# 110 n
:vi_repeat_search_next,
# 111 o
:ed_unassigned,
# 112 p
:vi_paste_next,
# 113 q
:ed_unassigned,
# 114 r
:vi_replace_char,
# 115 s
:vi_substitute_char,
# 116 t
:vi_to_next_char,
# 117 u
:vi_undo,
# 118 v
:vi_histedit,
# 119 w
:vi_next_word,
# 120 x
:ed_delete_next_char,
# 121 y
:vi_yank,
# 122 z
:ed_unassigned,
# 123 {
:ed_unassigned,
# 124 |
:vi_to_column,
# 125 }
:ed_unassigned,
# 126 ~
:vi_change_case,
# 127 ^?
:ed_unassigned,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_unassigned,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:ed_unassigned,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_unassigned,
# 141 M-^M
:ed_unassigned,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:ed_unassigned,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_unassigned,
# 177 M-1
:ed_unassigned,
# 178 M-2
:ed_unassigned,
# 179 M-3
:ed_unassigned,
# 180 M-4
:ed_unassigned,
# 181 M-5
:ed_unassigned,
# 182 M-6
:ed_unassigned,
# 183 M-7
:ed_unassigned,
# 184 M-8
:ed_unassigned,
# 185 M-9
:ed_unassigned,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_unassigned,
# 195 M-C
:ed_unassigned,
# 196 M-D
:ed_unassigned,
# 197 M-E
:ed_unassigned,
# 198 M-F
:ed_unassigned,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:ed_unassigned,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_unassigned,
# 207 M-O
:ed_sequence_lead_in,
# 208 M-P
:ed_unassigned,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:ed_unassigned,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_sequence_lead_in,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_unassigned,
# 227 M-c
:ed_unassigned,
# 228 M-d
:ed_unassigned,
# 229 M-e
:ed_unassigned,
# 230 M-f
:ed_unassigned,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:ed_unassigned,
# 237 M-m
:ed_unassigned,
# 238 M-n
:ed_unassigned,
# 239 M-o
:ed_unassigned,
# 240 M-p
:ed_unassigned,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_unassigned,
# 245 M-u
:ed_unassigned,
# 246 M-v
:ed_unassigned,
# 247 M-w
:ed_unassigned,
# 248 M-x
:ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_unassigned
# EOF
]
end
share/ruby/reline/key_actor/base.rb 0000644 00000000461 15173547336 0013343 0 ustar 00 class Reline::KeyActor::Base
MAPPING = Array.new(256)
def get_method(key)
self.class::MAPPING[key]
end
def initialize
@default_key_bindings = {}
end
def default_key_bindings
@default_key_bindings
end
def reset_default_key_bindings
@default_key_bindings.clear
end
end
share/ruby/reline/windows.rb 0000644 00000040752 15173547336 0012152 0 ustar 00 require 'fiddle/import'
class Reline::Windows
def self.encoding
Encoding::UTF_8
end
def self.win?
true
end
def self.win_legacy_console?
@@legacy_console
end
def self.set_default_key_bindings(config)
{
[224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓
[224, 77] => :ed_next_char, # →
[224, 75] => :ed_prev_char, # ←
[224, 83] => :key_delete, # Del
[224, 71] => :ed_move_to_beg, # Home
[224, 79] => :ed_move_to_end, # End
[ 0, 41] => :ed_unassigned, # input method on/off
[ 0, 72] => :ed_prev_history, # ↑
[ 0, 80] => :ed_next_history, # ↓
[ 0, 77] => :ed_next_char, # →
[ 0, 75] => :ed_prev_char, # ←
[ 0, 83] => :key_delete, # Del
[ 0, 71] => :ed_move_to_beg, # Home
[ 0, 79] => :ed_move_to_end # End
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
# Emulate ANSI key sequence.
{
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
end
end
if defined? JRUBY_VERSION
require 'win32api'
else
class Win32API
DLL = {}
TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
WIN32_TYPES = "VPpNnLlIi"
DL_TYPES = "0SSI"
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
@proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
handle = DLL[dllname] ||=
begin
Fiddle.dlopen(dllname)
rescue Fiddle::DLError
raise unless File.extname(dllname).empty?
Fiddle.dlopen(dllname + ".dll")
end
@func = Fiddle::Function.new(handle[func], import, export, calltype)
rescue Fiddle::DLError => e
raise LoadError, e.message, e.backtrace
end
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
args[i], = [x == 0 ? nil : x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
return ret || 0
end
end
end
VK_RETURN = 0x0D
VK_MENU = 0x12 # ALT key
VK_LMENU = 0xA4
VK_CONTROL = 0x11
VK_SHIFT = 0x10
VK_DIVIDE = 0x6F
KEY_EVENT = 0x01
WINDOW_BUFFER_SIZE_EVENT = 0x04
CAPSLOCK_ON = 0x0080
ENHANCED_KEY = 0x0100
LEFT_ALT_PRESSED = 0x0002
LEFT_CTRL_PRESSED = 0x0008
NUMLOCK_ON = 0x0020
RIGHT_ALT_PRESSED = 0x0001
RIGHT_CTRL_PRESSED = 0x0004
SCROLLLOCK_ON = 0x0040
SHIFT_PRESSED = 0x0010
VK_TAB = 0x09
VK_END = 0x23
VK_HOME = 0x24
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
VK_DELETE = 0x2E
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
FILE_TYPE_PIPE = 0x0003
FILE_NAME_INFO = 2
@@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
@@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
@@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
@@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
@@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
@@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
@@hConsoleHandle = @@GetStdHandle.call(STD_OUTPUT_HANDLE)
@@hConsoleInputHandle = @@GetStdHandle.call(STD_INPUT_HANDLE)
@@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
@@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
@@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
@@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
@@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
@@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
@@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
@@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
@@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
private_class_method def self.getconsolemode
mode = "\000\000\000\000"
@@GetConsoleMode.call(@@hConsoleHandle, mode)
mode.unpack1('L')
end
private_class_method def self.setconsolemode(mode)
@@SetConsoleMode.call(@@hConsoleHandle, mode)
end
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
#if @@legacy_console
# setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
# @@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
#end
@@input_buf = []
@@output_buf = []
@@output = STDOUT
def self.msys_tty?(io = @@hConsoleInputHandle)
# check if fd is a pipe
if @@GetFileType.call(io) != FILE_TYPE_PIPE
return false
end
bufsize = 1024
p_buffer = "\0" * bufsize
res = @@GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
return false if res == 0
# get pipe name: p_buffer layout is:
# struct _FILE_NAME_INFO {
# DWORD FileNameLength;
# WCHAR FileName[1];
# } FILE_NAME_INFO
len = p_buffer[0, 4].unpack1("L")
name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
# Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
# or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX')
name =~ /(msys-|cygwin-).*-pty/ ? true : false
end
KEY_MAP = [
# It's treated as Meta+Enter on Windows.
[ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ],
[ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ],
# It's treated as Meta+Space on Windows.
[ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ],
# Emulate getwch() key sequences.
[ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ],
[ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ],
[ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ],
[ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ],
[ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
[ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
[ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ],
# Emulate ANSI key sequence.
[ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
]
@@hsg = nil
def self.process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
# high-surrogate
if 0xD800 <= char_code and char_code <= 0xDBFF
@@hsg = char_code
return
end
# low-surrogate
if 0xDC00 <= char_code and char_code <= 0xDFFF
if @@hsg
char_code = 0x10000 + (@@hsg - 0xD800) * 0x400 + char_code - 0xDC00
@@hsg = nil
else
# no high-surrogate. ignored.
return
end
else
# ignore high-surrogate without low-surrogate if there
@@hsg = nil
end
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
match = KEY_MAP.find { |args,| key.matches?(**args) }
unless match.nil?
@@output_buf.concat(match.last)
return
end
# no char, only control keys
return if key.char_code == 0 and key.control_keys.any?
@@output_buf.push("\e".ord) if key.control_keys.include?(:ALT) and !key.control_keys.include?(:CTRL)
@@output_buf.concat(key.char.bytes)
end
def self.check_input_event
num_of_events = 0.chr * 8
while @@output_buf.empty?
Reline.core.line_editor.resize
if @@WaitForSingleObject.(@@hConsoleInputHandle, 100) != 0 # max 0.1 sec
# prevent for background consolemode change
@@legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
next
end
next if @@GetNumberOfConsoleInputEvents.(@@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
input_records = 0.chr * 20 * 80
read_event = 0.chr * 4
if @@ReadConsoleInputW.(@@hConsoleInputHandle, input_records, 80, read_event) != 0
read_events = read_event.unpack1('L')
0.upto(read_events) do |idx|
input_record = input_records[idx * 20, 20]
event = input_record[0, 2].unpack1('s*')
case event
when WINDOW_BUFFER_SIZE_EVENT
@@winch_handler.()
when KEY_EVENT
key_down = input_record[4, 4].unpack1('l*')
repeat_count = input_record[8, 2].unpack1('s*')
virtual_key_code = input_record[10, 2].unpack1('s*')
virtual_scan_code = input_record[12, 2].unpack1('s*')
char_code = input_record[14, 2].unpack1('S*')
control_key_state = input_record[16, 2].unpack1('S*')
is_key_down = key_down.zero? ? false : true
if is_key_down
process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
end
end
end
end
end
end
def self.getc
check_input_event
@@output_buf.shift
end
def self.ungetc(c)
@@output_buf.unshift(c)
end
def self.in_pasting?
not self.empty_buffer?
end
def self.empty_buffer?
if not @@output_buf.empty?
false
elsif @@kbhit.call == 0
true
else
false
end
end
def self.get_console_screen_buffer_info
# CONSOLE_SCREEN_BUFFER_INFO
# [ 0,2] dwSize.X
# [ 2,2] dwSize.Y
# [ 4,2] dwCursorPositions.X
# [ 6,2] dwCursorPositions.Y
# [ 8,2] wAttributes
# [10,2] srWindow.Left
# [12,2] srWindow.Top
# [14,2] srWindow.Right
# [16,2] srWindow.Bottom
# [18,2] dwMaximumWindowSize.X
# [20,2] dwMaximumWindowSize.Y
csbi = 0.chr * 22
return if @@GetConsoleScreenBufferInfo.call(@@hConsoleHandle, csbi) == 0
csbi
end
def self.get_screen_size
unless csbi = get_console_screen_buffer_info
return [1, 1]
end
csbi[0, 4].unpack('SS').reverse
end
def self.cursor_pos
unless csbi = get_console_screen_buffer_info
return Reline::CursorPos.new(0, 0)
end
x = csbi[4, 2].unpack1('s')
y = csbi[6, 2].unpack1('s')
Reline::CursorPos.new(x, y)
end
def self.move_cursor_column(val)
@@SetConsoleCursorPosition.call(@@hConsoleHandle, cursor_pos.y * 65536 + val)
end
def self.move_cursor_up(val)
if val > 0
y = cursor_pos.y - val
y = 0 if y < 0
@@SetConsoleCursorPosition.call(@@hConsoleHandle, y * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_down(-val)
end
end
def self.move_cursor_down(val)
if val > 0
return unless csbi = get_console_screen_buffer_info
screen_height = get_screen_size.first
y = cursor_pos.y + val
y = screen_height - 1 if y > (screen_height - 1)
@@SetConsoleCursorPosition.call(@@hConsoleHandle, (cursor_pos.y + val) * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_up(-val)
end
end
def self.erase_after_cursor
return unless csbi = get_console_screen_buffer_info
attributes = csbi[8, 2].unpack1('S')
cursor = csbi[4, 4].unpack1('L')
written = 0.chr * 4
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
end
def self.scroll_down(val)
return if val < 0
return unless csbi = get_console_screen_buffer_info
buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
screen_height = window_bottom - window_top + 1
val = screen_height if val > screen_height
if @@legacy_console || window_left != 0
# unless ENABLE_VIRTUAL_TERMINAL,
# if srWindow.Left != 0 then it's conhost.exe hosted console
# and puts "\n" causes horizontal scroll. its glitch.
# FYI irb write from culumn 1, so this gives no gain.
scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
destination_origin = 0 # y * 65536 + x
fill = [' '.ord, attributes].pack('SS')
@@ScrollConsoleScreenBuffer.call(@@hConsoleHandle, scroll_rectangle, nil, destination_origin, fill)
else
origin_x = x + 1
origin_y = y - window_top + 1
@@output.write [
(origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
"\n" * val,
(origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
].join
end
end
def self.clear_screen
if @@legacy_console
return unless csbi = get_console_screen_buffer_info
buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
fill_length = buffer_width * (window_bottom - window_top + 1)
screen_topleft = window_top * 65536
written = 0.chr * 4
@@FillConsoleOutputCharacter.call(@@hConsoleHandle, 0x20, fill_length, screen_topleft, written)
@@FillConsoleOutputAttribute.call(@@hConsoleHandle, attributes, fill_length, screen_topleft, written)
@@SetConsoleCursorPosition.call(@@hConsoleHandle, screen_topleft)
else
@@output.write "\e[2J" "\e[H"
end
end
def self.set_screen_size(rows, columns)
raise NotImplementedError
end
def self.hide_cursor
size = 100
visible = 0 # 0 means false
cursor_info = [size, visible].pack('Li')
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
end
def self.show_cursor
size = 100
visible = 1 # 1 means true
cursor_info = [size, visible].pack('Li')
@@SetConsoleCursorInfo.call(@@hConsoleHandle, cursor_info)
end
def self.set_winch_handler(&handler)
@@winch_handler = handler
end
def self.prep
# do nothing
nil
end
def self.deprep(otio)
# do nothing
end
class KeyEventRecord
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
def initialize(virtual_key_code, char_code, control_key_state)
@virtual_key_code = virtual_key_code
@char_code = char_code
@control_key_state = control_key_state
@enhanced = control_key_state & ENHANCED_KEY != 0
(@control_keys = []).tap do |control_keys|
# symbols must be sorted to make comparison is easier later on
control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0
end.freeze
end
def char
@char_code.chr(Encoding::UTF_8)
end
def enhanced?
@enhanced
end
# Verifies if the arguments match with this key event.
# Nil arguments are ignored, but at least one must be passed as non-nil.
# To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
def matches?(control_keys: nil, virtual_key_code: nil, char_code: nil)
raise ArgumentError, 'No argument was passed to match key event' if control_keys.nil? && virtual_key_code.nil? && char_code.nil?
(control_keys.nil? || [*control_keys].sort == @control_keys) &&
(virtual_key_code.nil? || @virtual_key_code == virtual_key_code) &&
(char_code.nil? || char_code == @char_code)
end
end
end
share/ruby/reline/general_io.rb 0000644 00000002714 15173547336 0012560 0 ustar 00 require 'timeout'
require 'io/wait'
class Reline::GeneralIO
def self.reset(encoding: nil)
@@pasting = false
@@encoding = encoding
end
def self.encoding
if defined?(@@encoding)
@@encoding
elsif RUBY_PLATFORM =~ /mswin|mingw/
Encoding::UTF_8
else
Encoding::default_external
end
end
def self.win?
false
end
def self.set_default_key_bindings(_)
end
@@buf = []
@@input = STDIN
def self.input=(val)
@@input = val
end
def self.getc
unless @@buf.empty?
return @@buf.shift
end
c = nil
loop do
result = @@input.wait_readable(0.1)
next if result.nil?
c = @@input.read(1)
break
end
c&.ord
end
def self.ungetc(c)
@@buf.unshift(c)
end
def self.get_screen_size
[1, 1]
end
def self.cursor_pos
Reline::CursorPos.new(1, 1)
end
def self.hide_cursor
end
def self.show_cursor
end
def self.move_cursor_column(val)
end
def self.move_cursor_up(val)
end
def self.move_cursor_down(val)
end
def self.erase_after_cursor
end
def self.scroll_down(val)
end
def self.clear_screen
end
def self.set_screen_size(rows, columns)
end
def self.set_winch_handler(&handler)
end
@@pasting = false
def self.in_pasting?
@@pasting
end
def self.start_pasting
@@pasting = true
end
def self.finish_pasting
@@pasting = false
end
def self.prep
end
def self.deprep(otio)
end
end
share/ruby/reline/history.rb 0000644 00000003572 15173547336 0012160 0 ustar 00 class Reline::History < Array
def initialize(config)
@config = config
end
def to_s
'HISTORY'
end
def delete_at(index)
index = check_index(index)
super(index)
end
def [](index)
index = check_index(index) unless index.is_a?(Range)
super(index)
end
def []=(index, val)
index = check_index(index)
super(index, String.new(val, encoding: Reline.encoding_system_needs))
end
def concat(*val)
val.each do |v|
push(*v)
end
end
def push(*val)
# If history_size is zero, all histories are dropped.
return self if @config.history_size.zero?
# If history_size is negative, history size is unlimited.
if @config.history_size.positive?
diff = size + val.size - @config.history_size
if diff > 0
if diff <= size
shift(diff)
else
diff -= size
clear
val.shift(diff)
end
end
end
super(*(val.map{ |v|
String.new(v, encoding: Reline.encoding_system_needs)
}))
end
def <<(val)
# If history_size is zero, all histories are dropped.
return self if @config.history_size.zero?
# If history_size is negative, history size is unlimited.
if @config.history_size.positive?
shift if size + 1 > @config.history_size
end
super(String.new(val, encoding: Reline.encoding_system_needs))
end
private def check_index(index)
index += size if index < 0
if index < -2147483648 or 2147483647 < index
raise RangeError.new("integer #{index} too big to convert to `int'")
end
# If history_size is negative, history size is unlimited.
if @config.history_size.positive?
if index < -@config.history_size or @config.history_size < index
raise RangeError.new("index=<#{index}>")
end
end
raise IndexError.new("index=<#{index}>") if index < 0 or size <= index
index
end
end
share/ruby/timeout.rb 0000644 00000013520 15173547336 0010661 0 ustar 00 # frozen_string_literal: true
# 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
VERSION = "0.3.1"
# 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)
exc.instance_variable_set(:@catch_value, exc)
::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(@catch_value, bt)
rescue UncaughtThrowError
end
end
super
end
end
# :stopdoc:
CONDVAR = ConditionVariable.new
QUEUE = Queue.new
QUEUE_MUTEX = Mutex.new
TIMEOUT_THREAD_MUTEX = Mutex.new
@timeout_thread = nil
private_constant :CONDVAR, :QUEUE, :QUEUE_MUTEX, :TIMEOUT_THREAD_MUTEX
class Request
attr_reader :deadline
def initialize(thread, timeout, exception_class, message)
@thread = thread
@deadline = GET_TIME.call(Process::CLOCK_MONOTONIC) + timeout
@exception_class = exception_class
@message = message
@mutex = Mutex.new
@done = false # protected by @mutex
end
def done?
@mutex.synchronize do
@done
end
end
def expired?(now)
now >= @deadline
end
def interrupt
@mutex.synchronize do
unless @done
@thread.raise @exception_class, @message
@done = true
end
end
end
def finished
@mutex.synchronize do
@done = true
end
end
end
private_constant :Request
def self.create_timeout_thread
watcher = Thread.new do
requests = []
while true
until QUEUE.empty? and !requests.empty? # wait to have at least one request
req = QUEUE.pop
requests << req unless req.done?
end
closest_deadline = requests.min_by(&:deadline).deadline
now = 0.0
QUEUE_MUTEX.synchronize do
while (now = GET_TIME.call(Process::CLOCK_MONOTONIC)) < closest_deadline and QUEUE.empty?
CONDVAR.wait(QUEUE_MUTEX, closest_deadline - now)
end
end
requests.each do |req|
req.interrupt if req.expired?(now)
end
requests.reject!(&:done?)
end
end
ThreadGroup::Default.add(watcher)
watcher.name = "Timeout stdlib thread"
watcher.thread_variable_set(:"\0__detached_thread__", true)
watcher
end
private_class_method :create_timeout_thread
def self.ensure_timeout_thread_created
unless @timeout_thread and @timeout_thread.alive?
TIMEOUT_THREAD_MUTEX.synchronize do
unless @timeout_thread and @timeout_thread.alive?
@timeout_thread = create_timeout_thread
end
end
end
end
# We keep a private reference so that time mocking libraries won't break
# Timeout.
GET_TIME = Process.method(:clock_gettime)
private_constant :GET_TIME
# :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. However, the block can use
# ensure to prevent the handling of the exception. For that reason, this
# method cannot be relied on to enforce timeouts for untrusted blocks.
#
# If a scheduler is defined, it will be used to handle the timeout by invoking
# Scheduler#timeout_after.
#
# Note that this is both a method of module Timeout, so you can <tt>include
# Timeout</tt> into your classes so they have a #timeout method, as well as
# a module method, so you can call it directly as Timeout.timeout().
def timeout(sec, klass = nil, message = nil, &block) #:yield: +sec+
return yield(sec) if sec == nil or sec.zero?
message ||= "execution expired"
if Fiber.respond_to?(:current_scheduler) && (scheduler = Fiber.current_scheduler)&.respond_to?(:timeout_after)
return scheduler.timeout_after(sec, klass || Error, message, &block)
end
Timeout.ensure_timeout_thread_created
perform = Proc.new do |exc|
request = Request.new(Thread.current, sec, exc, message)
QUEUE_MUTEX.synchronize do
QUEUE << request
CONDVAR.signal
end
begin
return yield(sec)
ensure
request.finished
end
end
if klass
perform.call(klass)
else
backtrace = Error.catch(&perform)
raise Error, message, backtrace
end
end
module_function :timeout
end
share/ruby/csv/table.rb 0000644 00000112420 15173547336 0011054 0 ustar 00 # frozen_string_literal: true
require "forwardable"
class CSV
# = \CSV::Table
# A \CSV::Table instance represents \CSV data.
# (see {class CSV}[../CSV.html]).
#
# The instance may have:
# - Rows: each is a Table::Row object.
# - Headers: names for the columns.
#
# === Instance Methods
#
# \CSV::Table has three groups of instance methods:
# - Its own internally defined instance methods.
# - Methods included by module Enumerable.
# - Methods delegated to class Array.:
# * Array#empty?
# * Array#length
# * Array#size
#
# == Creating a \CSV::Table Instance
#
# Commonly, a new \CSV::Table instance is created by parsing \CSV source
# using headers:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.class # => CSV::Table
#
# You can also create an instance directly. See ::new.
#
# == Headers
#
# If a table has headers, the headers serve as labels for the columns of data.
# Each header serves as the label for its column.
#
# The headers for a \CSV::Table object are stored as an \Array of Strings.
#
# Commonly, headers are defined in the first row of \CSV source:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.headers # => ["Name", "Value"]
#
# If no headers are defined, the \Array is empty:
# table = CSV::Table.new([])
# table.headers # => []
#
# == Access Modes
#
# \CSV::Table provides three modes for accessing table data:
# - \Row mode.
# - Column mode.
# - Mixed mode (the default for a new table).
#
# The access mode for a\CSV::Table instance affects the behavior
# of some of its instance methods:
# - #[]
# - #[]=
# - #delete
# - #delete_if
# - #each
# - #values_at
#
# === \Row Mode
#
# Set a table to row mode with method #by_row!:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_row! # => #<CSV::Table mode:row row_count:4>
#
# Specify a single row by an \Integer index:
# # Get a row.
# table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
# # Set a row, then get it.
# table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3])
# table[1] # => #<CSV::Row "Name":"bam" "Value":3>
#
# Specify a sequence of rows by a \Range:
# # Get rows.
# table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">]
# # Set rows, then get them.
# table[1..2] = [
# CSV::Row.new(['Name', 'Value'], ['bat', 4]),
# CSV::Row.new(['Name', 'Value'], ['bad', 5]),
# ]
# table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]]
#
# === Column Mode
#
# Set a table to column mode with method #by_col!:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col! # => #<CSV::Table mode:col row_count:4>
#
# Specify a column by an \Integer index:
# # Get a column.
# table[0]
# # Set a column, then get it.
# table[0] = ['FOO', 'BAR', 'BAZ']
# table[0] # => ["FOO", "BAR", "BAZ"]
#
# Specify a column by its \String header:
# # Get a column.
# table['Name'] # => ["FOO", "BAR", "BAZ"]
# # Set a column, then get it.
# table['Name'] = ['Foo', 'Bar', 'Baz']
# table['Name'] # => ["Foo", "Bar", "Baz"]
#
# === Mixed Mode
#
# In mixed mode, you can refer to either rows or columns:
# - An \Integer index refers to a row.
# - A \Range index refers to multiple rows.
# - A \String index refers to a column.
#
# Set a table to mixed mode with method #by_col_or_row!:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
#
# Specify a single row by an \Integer index:
# # Get a row.
# table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
# # Set a row, then get it.
# table[1] = CSV::Row.new(['Name', 'Value'], ['bam', 3])
# table[1] # => #<CSV::Row "Name":"bam" "Value":3>
#
# Specify a sequence of rows by a \Range:
# # Get rows.
# table[1..2] # => [#<CSV::Row "Name":"bam" "Value":3>, #<CSV::Row "Name":"baz" "Value":"2">]
# # Set rows, then get them.
# table[1] = CSV::Row.new(['Name', 'Value'], ['bat', 4])
# table[2] = CSV::Row.new(['Name', 'Value'], ['bad', 5])
# table[1..2] # => [["Name", #<CSV::Row "Name":"bat" "Value":4>], ["Value", #<CSV::Row "Name":"bad" "Value":5>]]
#
# Specify a column by its \String header:
# # Get a column.
# table['Name'] # => ["foo", "bat", "bad"]
# # Set a column, then get it.
# table['Name'] = ['Foo', 'Bar', 'Baz']
# table['Name'] # => ["Foo", "Bar", "Baz"]
class Table
# :call-seq:
# CSV::Table.new(array_of_rows, headers = nil) -> csv_table
#
# Returns a new \CSV::Table object.
#
# - Argument +array_of_rows+ must be an \Array of CSV::Row objects.
# - Argument +headers+, if given, may be an \Array of Strings.
#
# ---
#
# Create an empty \CSV::Table object:
# table = CSV::Table.new([])
# table # => #<CSV::Table mode:col_or_row row_count:1>
#
# Create a non-empty \CSV::Table object:
# rows = [
# CSV::Row.new([], []),
# CSV::Row.new([], []),
# CSV::Row.new([], []),
# ]
# table = CSV::Table.new(rows)
# table # => #<CSV::Table mode:col_or_row row_count:4>
#
# ---
#
# If argument +headers+ is an \Array of Strings,
# those Strings become the table's headers:
# table = CSV::Table.new([], headers: ['Name', 'Age'])
# table.headers # => ["Name", "Age"]
#
# If argument +headers+ is not given and the table has rows,
# the headers are taken from the first row:
# rows = [
# CSV::Row.new(['Foo', 'Bar'], []),
# CSV::Row.new(['foo', 'bar'], []),
# CSV::Row.new(['FOO', 'BAR'], []),
# ]
# table = CSV::Table.new(rows)
# table.headers # => ["Foo", "Bar"]
#
# If argument +headers+ is not given and the table is empty (has no rows),
# the headers are also empty:
# table = CSV::Table.new([])
# table.headers # => []
#
# ---
#
# Raises an exception if argument +array_of_rows+ is not an \Array object:
# # Raises NoMethodError (undefined method `first' for :foo:Symbol):
# CSV::Table.new(:foo)
#
# Raises an exception if an element of +array_of_rows+ is not a \CSV::Table object:
# # Raises NoMethodError (undefined method `headers' for :foo:Symbol):
# CSV::Table.new([:foo])
def initialize(array_of_rows, headers: nil)
@table = array_of_rows
@headers = headers
unless @headers
if @table.empty?
@headers = []
else
@headers = @table.first.headers
end
end
@mode = :col_or_row
end
# The current access mode for indexing and iteration.
attr_reader :mode
# Internal data format used to compare equality.
attr_reader :table
protected :table
### Array Delegation ###
extend Forwardable
def_delegators :@table, :empty?, :length, :size
# :call-seq:
# table.by_col -> table_dup
#
# Returns a duplicate of +self+, in column mode
# (see {Column Mode}[#class-CSV::Table-label-Column+Mode]):
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.mode # => :col_or_row
# dup_table = table.by_col
# dup_table.mode # => :col
# dup_table.equal?(table) # => false # It's a dup
#
# This may be used to chain method calls without changing the mode
# (but also will affect performance and memory usage):
# dup_table.by_col['Name']
#
# Also note that changes to the duplicate table will not affect the original.
def by_col
self.class.new(@table.dup).by_col!
end
# :call-seq:
# table.by_col! -> self
#
# Sets the mode for +self+ to column mode
# (see {Column Mode}[#class-CSV::Table-label-Column+Mode]); returns +self+:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.mode # => :col_or_row
# table1 = table.by_col!
# table.mode # => :col
# table1.equal?(table) # => true # Returned self
def by_col!
@mode = :col
self
end
# :call-seq:
# table.by_col_or_row -> table_dup
#
# Returns a duplicate of +self+, in mixed mode
# (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]):
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true).by_col!
# table.mode # => :col
# dup_table = table.by_col_or_row
# dup_table.mode # => :col_or_row
# dup_table.equal?(table) # => false # It's a dup
#
# This may be used to chain method calls without changing the mode
# (but also will affect performance and memory usage):
# dup_table.by_col_or_row['Name']
#
# Also note that changes to the duplicate table will not affect the original.
def by_col_or_row
self.class.new(@table.dup).by_col_or_row!
end
# :call-seq:
# table.by_col_or_row! -> self
#
# Sets the mode for +self+ to mixed mode
# (see {Mixed Mode}[#class-CSV::Table-label-Mixed+Mode]); returns +self+:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true).by_col!
# table.mode # => :col
# table1 = table.by_col_or_row!
# table.mode # => :col_or_row
# table1.equal?(table) # => true # Returned self
def by_col_or_row!
@mode = :col_or_row
self
end
# :call-seq:
# table.by_row -> table_dup
#
# Returns a duplicate of +self+, in row mode
# (see {Row Mode}[#class-CSV::Table-label-Row+Mode]):
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.mode # => :col_or_row
# dup_table = table.by_row
# dup_table.mode # => :row
# dup_table.equal?(table) # => false # It's a dup
#
# This may be used to chain method calls without changing the mode
# (but also will affect performance and memory usage):
# dup_table.by_row[1]
#
# Also note that changes to the duplicate table will not affect the original.
def by_row
self.class.new(@table.dup).by_row!
end
# :call-seq:
# table.by_row! -> self
#
# Sets the mode for +self+ to row mode
# (see {Row Mode}[#class-CSV::Table-label-Row+Mode]); returns +self+:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.mode # => :col_or_row
# table1 = table.by_row!
# table.mode # => :row
# table1.equal?(table) # => true # Returned self
def by_row!
@mode = :row
self
end
# :call-seq:
# table.headers -> array_of_headers
#
# Returns a new \Array containing the \String headers for the table.
#
# If the table is not empty, returns the headers from the first row:
# rows = [
# CSV::Row.new(['Foo', 'Bar'], []),
# CSV::Row.new(['FOO', 'BAR'], []),
# CSV::Row.new(['foo', 'bar'], []),
# ]
# table = CSV::Table.new(rows)
# table.headers # => ["Foo", "Bar"]
# table.delete(0)
# table.headers # => ["FOO", "BAR"]
# table.delete(0)
# table.headers # => ["foo", "bar"]
#
# If the table is empty, returns a copy of the headers in the table itself:
# table.delete(0)
# table.headers # => ["Foo", "Bar"]
def headers
if @table.empty?
@headers.dup
else
@table.first.headers
end
end
# :call-seq:
# table[n] -> row or column_data
# table[range] -> array_of_rows or array_of_column_data
# table[header] -> array_of_column_data
#
# Returns data from the table; does not modify the table.
#
# ---
#
# Fetch a \Row by Its \Integer Index::
# - Form: <tt>table[n]</tt>, +n+ an integer.
# - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
# - Return value: _nth_ row of the table, if that row exists;
# otherwise +nil+.
#
# Returns the _nth_ row of the table if that row exists:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
# table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
# table[1] # => #<CSV::Row "Name":"bar" "Value":"1">
#
# Counts backward from the last row if +n+ is negative:
# table[-1] # => #<CSV::Row "Name":"baz" "Value":"2">
#
# Returns +nil+ if +n+ is too large or too small:
# table[4] # => nil
# table[-4] # => nil
#
# Raises an exception if the access mode is <tt>:row</tt>
# and +n+ is not an \Integer:
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# # Raises TypeError (no implicit conversion of String into Integer):
# table['Name']
#
# ---
#
# Fetch a Column by Its \Integer Index::
# - Form: <tt>table[n]</tt>, +n+ an \Integer.
# - Access mode: <tt>:col</tt>.
# - Return value: _nth_ column of the table, if that column exists;
# otherwise an \Array of +nil+ fields of length <tt>self.size</tt>.
#
# Returns the _nth_ column of the table if that column exists:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# table[1] # => ["0", "1", "2"]
#
# Counts backward from the last column if +n+ is negative:
# table[-2] # => ["foo", "bar", "baz"]
#
# Returns an \Array of +nil+ fields if +n+ is too large or too small:
# table[4] # => [nil, nil, nil]
# table[-4] # => [nil, nil, nil]
#
# ---
#
# Fetch Rows by \Range::
# - Form: <tt>table[range]</tt>, +range+ a \Range object.
# - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
# - Return value: rows from the table, beginning at row <tt>range.start</tt>,
# if those rows exists.
#
# Returns rows from the table, beginning at row <tt>range.first</tt>,
# if those rows exist:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# rows = table[1..2] # => #<CSV::Row "Name":"bar" "Value":"1">
# rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
# table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
# rows = table[1..2] # => #<CSV::Row "Name":"bar" "Value":"1">
# rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
#
# If there are too few rows, returns all from <tt>range.start</tt> to the end:
# rows = table[1..50] # => #<CSV::Row "Name":"bar" "Value":"1">
# rows # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
#
# Special case: if <tt>range.start == table.size</tt>, returns an empty \Array:
# table[table.size..50] # => []
#
# If <tt>range.end</tt> is negative, calculates the ending index from the end:
# rows = table[0..-1]
# rows # => [#<CSV::Row "Name":"foo" "Value":"0">, #<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
#
# If <tt>range.start</tt> is negative, calculates the starting index from the end:
# rows = table[-1..2]
# rows # => [#<CSV::Row "Name":"baz" "Value":"2">]
#
# If <tt>range.start</tt> is larger than <tt>table.size</tt>, returns +nil+:
# table[4..4] # => nil
#
# ---
#
# Fetch Columns by \Range::
# - Form: <tt>table[range]</tt>, +range+ a \Range object.
# - Access mode: <tt>:col</tt>.
# - Return value: column data from the table, beginning at column <tt>range.start</tt>,
# if those columns exist.
#
# Returns column values from the table, if the column exists;
# the values are arranged by row:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col!
# table[0..1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# Special case: if <tt>range.start == headers.size</tt>,
# returns an \Array (size: <tt>table.size</tt>) of empty \Arrays:
# table[table.headers.size..50] # => [[], [], []]
#
# If <tt>range.end</tt> is negative, calculates the ending index from the end:
# table[0..-1] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# If <tt>range.start</tt> is negative, calculates the starting index from the end:
# table[-2..2] # => [["foo", "0"], ["bar", "1"], ["baz", "2"]]
#
# If <tt>range.start</tt> is larger than <tt>table.size</tt>,
# returns an \Array of +nil+ values:
# table[4..4] # => [nil, nil, nil]
#
# ---
#
# Fetch a Column by Its \String Header::
# - Form: <tt>table[header]</tt>, +header+ a \String header.
# - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt>
# - Return value: column data from the table, if that +header+ exists.
#
# Returns column values from the table, if the column exists:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# table['Name'] # => ["foo", "bar", "baz"]
# table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
# col = table['Name']
# col # => ["foo", "bar", "baz"]
#
# Modifying the returned column values does not modify the table:
# col[0] = 'bat'
# col # => ["bat", "bar", "baz"]
# table['Name'] # => ["foo", "bar", "baz"]
#
# Returns an \Array of +nil+ values if there is no such column:
# table['Nosuch'] # => [nil, nil, nil]
def [](index_or_header)
if @mode == :row or # by index
(@mode == :col_or_row and (index_or_header.is_a?(Integer) or index_or_header.is_a?(Range)))
@table[index_or_header]
else # by header
@table.map { |row| row[index_or_header] }
end
end
# :call-seq:
# table[n] = row -> row
# table[n] = field_or_array_of_fields -> field_or_array_of_fields
# table[header] = field_or_array_of_fields -> field_or_array_of_fields
#
# Puts data onto the table.
#
# ---
#
# Set a \Row by Its \Integer Index::
# - Form: <tt>table[n] = row</tt>, +n+ an \Integer,
# +row+ a \CSV::Row instance or an \Array of fields.
# - Access mode: <tt>:row</tt> or <tt>:col_or_row</tt>.
# - Return value: +row+.
#
# If the row exists, it is replaced:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# new_row = CSV::Row.new(['Name', 'Value'], ['bat', 3])
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# return_value = table[0] = new_row
# return_value.equal?(new_row) # => true # Returned the row
# table[0].to_h # => {"Name"=>"bat", "Value"=>3}
#
# With access mode <tt>:col_or_row</tt>:
# table.by_col_or_row! # => #<CSV::Table mode:col_or_row row_count:4>
# table[0] = CSV::Row.new(['Name', 'Value'], ['bam', 4])
# table[0].to_h # => {"Name"=>"bam", "Value"=>4}
#
# With an \Array instead of a \CSV::Row, inherits headers from the table:
# array = ['bad', 5]
# return_value = table[0] = array
# return_value.equal?(array) # => true # Returned the array
# table[0].to_h # => {"Name"=>"bad", "Value"=>5}
#
# If the row does not exist, extends the table by adding rows:
# assigns rows with +nil+ as needed:
# table.size # => 3
# table[5] = ['bag', 6]
# table.size # => 6
# table[3] # => nil
# table[4]# => nil
# table[5].to_h # => {"Name"=>"bag", "Value"=>6}
#
# Note that the +nil+ rows are actually +nil+, not a row of +nil+ fields.
#
# ---
#
# Set a Column by Its \Integer Index::
# - Form: <tt>table[n] = array_of_fields</tt>, +n+ an \Integer,
# +array_of_fields+ an \Array of \String fields.
# - Access mode: <tt>:col</tt>.
# - Return value: +array_of_fields+.
#
# If the column exists, it is replaced:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# new_col = [3, 4, 5]
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# return_value = table[1] = new_col
# return_value.equal?(new_col) # => true # Returned the column
# table[1] # => [3, 4, 5]
# # The rows, as revised:
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# table[0].to_h # => {"Name"=>"foo", "Value"=>3}
# table[1].to_h # => {"Name"=>"bar", "Value"=>4}
# table[2].to_h # => {"Name"=>"baz", "Value"=>5}
# table.by_col! # => #<CSV::Table mode:col row_count:4>
#
# If there are too few values, fills with +nil+ values:
# table[1] = [0]
# table[1] # => [0, nil, nil]
#
# If there are too many values, ignores the extra values:
# table[1] = [0, 1, 2, 3, 4]
# table[1] # => [0, 1, 2]
#
# If a single value is given, replaces all fields in the column with that value:
# table[1] = 'bat'
# table[1] # => ["bat", "bat", "bat"]
#
# ---
#
# Set a Column by Its \String Header::
# - Form: <tt>table[header] = field_or_array_of_fields</tt>,
# +header+ a \String header, +field_or_array_of_fields+ a field value
# or an \Array of \String fields.
# - Access mode: <tt>:col</tt> or <tt>:col_or_row</tt>.
# - Return value: +field_or_array_of_fields+.
#
# If the column exists, it is replaced:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# new_col = [3, 4, 5]
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# return_value = table['Value'] = new_col
# return_value.equal?(new_col) # => true # Returned the column
# table['Value'] # => [3, 4, 5]
# # The rows, as revised:
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# table[0].to_h # => {"Name"=>"foo", "Value"=>3}
# table[1].to_h # => {"Name"=>"bar", "Value"=>4}
# table[2].to_h # => {"Name"=>"baz", "Value"=>5}
# table.by_col! # => #<CSV::Table mode:col row_count:4>
#
# If there are too few values, fills with +nil+ values:
# table['Value'] = [0]
# table['Value'] # => [0, nil, nil]
#
# If there are too many values, ignores the extra values:
# table['Value'] = [0, 1, 2, 3, 4]
# table['Value'] # => [0, 1, 2]
#
# If the column does not exist, extends the table by adding columns:
# table['Note'] = ['x', 'y', 'z']
# table['Note'] # => ["x", "y", "z"]
# # The rows, as revised:
# table.by_row!
# table[0].to_h # => {"Name"=>"foo", "Value"=>0, "Note"=>"x"}
# table[1].to_h # => {"Name"=>"bar", "Value"=>1, "Note"=>"y"}
# table[2].to_h # => {"Name"=>"baz", "Value"=>2, "Note"=>"z"}
# table.by_col!
#
# If a single value is given, replaces all fields in the column with that value:
# table['Value'] = 'bat'
# table['Value'] # => ["bat", "bat", "bat"]
def []=(index_or_header, value)
if @mode == :row or # by index
(@mode == :col_or_row and index_or_header.is_a? Integer)
if value.is_a? Array
@table[index_or_header] = Row.new(headers, value)
else
@table[index_or_header] = value
end
else # set column
unless index_or_header.is_a? Integer
index = @headers.index(index_or_header) || @headers.size
@headers[index] = index_or_header
end
if value.is_a? Array # multiple values
@table.each_with_index do |row, i|
if row.header_row?
row[index_or_header] = index_or_header
else
row[index_or_header] = value[i]
end
end
else # repeated value
@table.each do |row|
if row.header_row?
row[index_or_header] = index_or_header
else
row[index_or_header] = value
end
end
end
end
end
# :call-seq:
# table.values_at(*indexes) -> array_of_rows
# table.values_at(*headers) -> array_of_columns_data
#
# If the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
# and each argument is either an \Integer or a \Range,
# returns rows.
# Otherwise, returns columns data.
#
# In either case, the returned values are in the order
# specified by the arguments. Arguments may be repeated.
#
# ---
#
# Returns rows as an \Array of \CSV::Row objects.
#
# No argument:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.values_at # => []
#
# One index:
# values = table.values_at(0)
# values # => [#<CSV::Row "Name":"foo" "Value":"0">]
#
# Two indexes:
# values = table.values_at(2, 0)
# values # => [#<CSV::Row "Name":"baz" "Value":"2">, #<CSV::Row "Name":"foo" "Value":"0">]
#
# One \Range:
# values = table.values_at(1..2)
# values # => [#<CSV::Row "Name":"bar" "Value":"1">, #<CSV::Row "Name":"baz" "Value":"2">]
#
# \Ranges and indexes:
# values = table.values_at(0..1, 1..2, 0, 2)
# pp values
# Output:
# [#<CSV::Row "Name":"foo" "Value":"0">,
# #<CSV::Row "Name":"bar" "Value":"1">,
# #<CSV::Row "Name":"bar" "Value":"1">,
# #<CSV::Row "Name":"baz" "Value":"2">,
# #<CSV::Row "Name":"foo" "Value":"0">,
# #<CSV::Row "Name":"baz" "Value":"2">]
#
# ---
#
# Returns columns data as row Arrays,
# each consisting of the specified columns data for that row:
# values = table.values_at('Name')
# values # => [["foo"], ["bar"], ["baz"]]
# values = table.values_at('Value', 'Name')
# values # => [["0", "foo"], ["1", "bar"], ["2", "baz"]]
def values_at(*indices_or_headers)
if @mode == :row or # by indices
( @mode == :col_or_row and indices_or_headers.all? do |index|
index.is_a?(Integer) or
( index.is_a?(Range) and
index.first.is_a?(Integer) and
index.last.is_a?(Integer) )
end )
@table.values_at(*indices_or_headers)
else # by headers
@table.map { |row| row.values_at(*indices_or_headers) }
end
end
# :call-seq:
# table << row_or_array -> self
#
# If +row_or_array+ is a \CSV::Row object,
# it is appended to the table:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table << CSV::Row.new(table.headers, ['bat', 3])
# table[3] # => #<CSV::Row "Name":"bat" "Value":3>
#
# If +row_or_array+ is an \Array, it is used to create a new
# \CSV::Row object which is then appended to the table:
# table << ['bam', 4]
# table[4] # => #<CSV::Row "Name":"bam" "Value":4>
def <<(row_or_array)
if row_or_array.is_a? Array # append Array
@table << Row.new(headers, row_or_array)
else # append Row
@table << row_or_array
end
self # for chaining
end
#
# :call-seq:
# table.push(*rows_or_arrays) -> self
#
# A shortcut for appending multiple rows. Equivalent to:
# rows.each {|row| self << row }
#
# Each argument may be either a \CSV::Row object or an \Array:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# rows = [
# CSV::Row.new(table.headers, ['bat', 3]),
# ['bam', 4]
# ]
# table.push(*rows)
# table[3..4] # => [#<CSV::Row "Name":"bat" "Value":3>, #<CSV::Row "Name":"bam" "Value":4>]
def push(*rows)
rows.each { |row| self << row }
self # for chaining
end
# :call-seq:
# table.delete(*indexes) -> deleted_values
# table.delete(*headers) -> deleted_values
#
# If the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
# and each argument is either an \Integer or a \Range,
# returns deleted rows.
# Otherwise, returns deleted columns data.
#
# In either case, the returned values are in the order
# specified by the arguments. Arguments may be repeated.
#
# ---
#
# Returns rows as an \Array of \CSV::Row objects.
#
# One index:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# deleted_values = table.delete(0)
# deleted_values # => [#<CSV::Row "Name":"foo" "Value":"0">]
#
# Two indexes:
# table = CSV.parse(source, headers: true)
# deleted_values = table.delete(2, 0)
# deleted_values # => [#<CSV::Row "Name":"baz" "Value":"2">, #<CSV::Row "Name":"foo" "Value":"0">]
#
# ---
#
# Returns columns data as column Arrays.
#
# One header:
# table = CSV.parse(source, headers: true)
# deleted_values = table.delete('Name')
# deleted_values # => ["foo", "bar", "baz"]
#
# Two headers:
# table = CSV.parse(source, headers: true)
# deleted_values = table.delete('Value', 'Name')
# deleted_values # => [["0", "1", "2"], ["foo", "bar", "baz"]]
def delete(*indexes_or_headers)
if indexes_or_headers.empty?
raise ArgumentError, "wrong number of arguments (given 0, expected 1+)"
end
deleted_values = indexes_or_headers.map do |index_or_header|
if @mode == :row or # by index
(@mode == :col_or_row and index_or_header.is_a? Integer)
@table.delete_at(index_or_header)
else # by header
if index_or_header.is_a? Integer
@headers.delete_at(index_or_header)
else
@headers.delete(index_or_header)
end
@table.map { |row| row.delete(index_or_header).last }
end
end
if indexes_or_headers.size == 1
deleted_values[0]
else
deleted_values
end
end
# :call-seq:
# table.delete_if {|row_or_column| ... } -> self
#
# Removes rows or columns for which the block returns a truthy value;
# returns +self+.
#
# Removes rows when the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>;
# calls the block with each \CSV::Row object:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# table.size # => 3
# table.delete_if {|row| row['Name'].start_with?('b') }
# table.size # => 1
#
# Removes columns when the access mode is <tt>:col</tt>;
# calls the block with each column as a 2-element array
# containing the header and an \Array of column fields:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# table.headers.size # => 2
# table.delete_if {|column_data| column_data[1].include?('2') }
# table.headers.size # => 1
#
# Returns a new \Enumerator if no block is given:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.delete_if # => #<Enumerator: #<CSV::Table mode:col_or_row row_count:4>:delete_if>
def delete_if(&block)
return enum_for(__method__) { @mode == :row or @mode == :col_or_row ? size : headers.size } unless block_given?
if @mode == :row or @mode == :col_or_row # by index
@table.delete_if(&block)
else # by header
headers.each do |header|
delete(header) if yield([header, self[header]])
end
end
self # for chaining
end
include Enumerable
# :call-seq:
# table.each {|row_or_column| ... ) -> self
#
# Calls the block with each row or column; returns +self+.
#
# When the access mode is <tt>:row</tt> or <tt>:col_or_row</tt>,
# calls the block with each \CSV::Row object:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.by_row! # => #<CSV::Table mode:row row_count:4>
# table.each {|row| p row }
# Output:
# #<CSV::Row "Name":"foo" "Value":"0">
# #<CSV::Row "Name":"bar" "Value":"1">
# #<CSV::Row "Name":"baz" "Value":"2">
#
# When the access mode is <tt>:col</tt>,
# calls the block with each column as a 2-element array
# containing the header and an \Array of column fields:
# table.by_col! # => #<CSV::Table mode:col row_count:4>
# table.each {|column_data| p column_data }
# Output:
# ["Name", ["foo", "bar", "baz"]]
# ["Value", ["0", "1", "2"]]
#
# Returns a new \Enumerator if no block is given:
# table.each # => #<Enumerator: #<CSV::Table mode:col row_count:4>:each>
def each(&block)
return enum_for(__method__) { @mode == :col ? headers.size : size } unless block_given?
if @mode == :col
headers.each.with_index do |header, i|
yield([header, @table.map {|row| row[header, i]}])
end
else
@table.each(&block)
end
self # for chaining
end
# :call-seq:
# table == other_table -> true or false
#
# Returns +true+ if all each row of +self+ <tt>==</tt>
# the corresponding row of +other_table+, otherwise, +false+.
#
# The access mode does no affect the result.
#
# Equal tables:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# other_table = CSV.parse(source, headers: true)
# table == other_table # => true
#
# Different row count:
# other_table.delete(2)
# table == other_table # => false
#
# Different last row:
# other_table << ['bat', 3]
# table == other_table # => false
def ==(other)
return @table == other.table if other.is_a? CSV::Table
@table == other
end
# :call-seq:
# table.to_a -> array_of_arrays
#
# Returns the table as an \Array of \Arrays;
# the headers are in the first row:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.to_a # => [["Name", "Value"], ["foo", "0"], ["bar", "1"], ["baz", "2"]]
def to_a
array = [headers]
@table.each do |row|
array.push(row.fields) unless row.header_row?
end
array
end
# :call-seq:
# table.to_csv(**options) -> csv_string
#
# Returns the table as \CSV string.
# See {Options for Generating}[../CSV.html#class-CSV-label-Options+for+Generating].
#
# Defaults option +write_headers+ to +true+:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.to_csv # => "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
#
# Omits the headers if option +write_headers+ is given as +false+
# (see {Option +write_headers+}[../CSV.html#class-CSV-label-Option+write_headers]):
# table.to_csv(write_headers: false) # => "foo,0\nbar,1\nbaz,2\n"
#
# Limit rows if option +limit+ is given like +2+:
# table.to_csv(limit: 2) # => "Name,Value\nfoo,0\nbar,1\n"
def to_csv(write_headers: true, limit: nil, **options)
array = write_headers ? [headers.to_csv(**options)] : []
limit ||= @table.size
limit = @table.size + 1 + limit if limit < 0
limit = 0 if limit < 0
@table.first(limit).each do |row|
array.push(row.fields.to_csv(**options)) unless row.header_row?
end
array.join("")
end
alias_method :to_s, :to_csv
#
# Extracts the nested value specified by the sequence of +index+ or +header+ objects by calling dig at each step,
# returning nil if any intermediate step is nil.
#
def dig(index_or_header, *index_or_headers)
value = self[index_or_header]
if value.nil?
nil
elsif index_or_headers.empty?
value
else
unless value.respond_to?(:dig)
raise TypeError, "#{value.class} does not have \#dig method"
end
value.dig(*index_or_headers)
end
end
# :call-seq:
# table.inspect => string
#
# Returns a <tt>US-ASCII</tt>-encoded \String showing table:
# - Class: <tt>CSV::Table</tt>.
# - Access mode: <tt>:row</tt>, <tt>:col</tt>, or <tt>:col_or_row</tt>.
# - Size: Row count, including the header row.
#
# Example:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.inspect # => "#<CSV::Table mode:col_or_row row_count:4>\nName,Value\nfoo,0\nbar,1\nbaz,2\n"
#
def inspect
inspected = +"#<#{self.class} mode:#{@mode} row_count:#{to_a.size}>"
summary = to_csv(limit: 5)
inspected << "\n" << summary if summary.encoding.ascii_compatible?
inspected
end
end
end
share/ruby/csv/writer.rb 0000644 00000013570 15173547336 0011307 0 ustar 00 # frozen_string_literal: true
require_relative "input_record_separator"
require_relative "row"
class CSV
# Note: Don't use this class directly. This is an internal class.
class Writer
#
# A CSV::Writer receives an output, prepares the header, format and output.
# It allows us to write new rows in the object and rewind it.
#
attr_reader :lineno
attr_reader :headers
def initialize(output, options)
@output = output
@options = options
@lineno = 0
@fields_converter = nil
prepare
if @options[:write_headers] and @headers
self << @headers
end
@fields_converter = @options[:fields_converter]
end
#
# Adds a new row
#
def <<(row)
case row
when Row
row = row.fields
when Hash
row = @headers.collect {|header| row[header]}
end
@headers ||= row if @use_headers
@lineno += 1
if @fields_converter
quoted_fields = [false] * row.size
row = @fields_converter.convert(row, nil, lineno, quoted_fields)
end
i = -1
converted_row = row.collect do |field|
i += 1
quote(field, i)
end
line = converted_row.join(@column_separator) + @row_separator
if @output_encoding
line = line.encode(@output_encoding)
end
@output << line
self
end
#
# Winds back to the beginning
#
def rewind
@lineno = 0
@headers = nil if @options[:headers].nil?
end
private
def prepare
@encoding = @options[:encoding]
prepare_header
prepare_format
prepare_output
end
def prepare_header
headers = @options[:headers]
case headers
when Array
@headers = headers
@use_headers = true
when String
@headers = CSV.parse_line(headers,
col_sep: @options[:column_separator],
row_sep: @options[:row_separator],
quote_char: @options[:quote_character])
@use_headers = true
when true
@headers = nil
@use_headers = true
else
@headers = nil
@use_headers = false
end
return unless @headers
converter = @options[:header_fields_converter]
@headers = converter.convert(@headers, nil, 0, [])
@headers.each do |header|
header.freeze if header.is_a?(String)
end
end
def prepare_force_quotes_fields(force_quotes)
@force_quotes_fields = {}
force_quotes.each do |name_or_index|
case name_or_index
when Integer
index = name_or_index
@force_quotes_fields[index] = true
when String, Symbol
name = name_or_index.to_s
if @headers.nil?
message = ":headers is required when you use field name " +
"in :force_quotes: " +
"#{name_or_index.inspect}: #{force_quotes.inspect}"
raise ArgumentError, message
end
index = @headers.index(name)
next if index.nil?
@force_quotes_fields[index] = true
else
message = ":force_quotes element must be " +
"field index or field name: " +
"#{name_or_index.inspect}: #{force_quotes.inspect}"
raise ArgumentError, message
end
end
end
def prepare_format
@column_separator = @options[:column_separator].to_s.encode(@encoding)
row_separator = @options[:row_separator]
if row_separator == :auto
@row_separator = InputRecordSeparator.value.encode(@encoding)
else
@row_separator = row_separator.to_s.encode(@encoding)
end
@quote_character = @options[:quote_character]
force_quotes = @options[:force_quotes]
if force_quotes.is_a?(Array)
prepare_force_quotes_fields(force_quotes)
@force_quotes = false
elsif force_quotes
@force_quotes_fields = nil
@force_quotes = true
else
@force_quotes_fields = nil
@force_quotes = false
end
unless @force_quotes
@quotable_pattern =
Regexp.new("[\r\n".encode(@encoding) +
Regexp.escape(@column_separator) +
Regexp.escape(@quote_character.encode(@encoding)) +
"]".encode(@encoding))
end
@quote_empty = @options.fetch(:quote_empty, true)
end
def prepare_output
@output_encoding = nil
return unless @output.is_a?(StringIO)
output_encoding = @output.internal_encoding || @output.external_encoding
if @encoding != output_encoding
if @options[:force_encoding]
@output_encoding = output_encoding
else
compatible_encoding = Encoding.compatible?(@encoding, output_encoding)
if compatible_encoding
@output.set_encoding(compatible_encoding)
@output.seek(0, IO::SEEK_END)
end
end
end
end
def quote_field(field)
field = String(field)
encoded_quote_character = @quote_character.encode(field.encoding)
encoded_quote_character +
field.gsub(encoded_quote_character,
encoded_quote_character * 2) +
encoded_quote_character
end
def quote(field, i)
if @force_quotes
quote_field(field)
elsif @force_quotes_fields and @force_quotes_fields[i]
quote_field(field)
else
if field.nil? # represent +nil+ fields as empty unquoted fields
""
else
field = String(field) # Stringify fields
# represent empty fields as empty quoted fields
if (@quote_empty and field.empty?) or (field.valid_encoding? and @quotable_pattern.match?(field))
quote_field(field)
else
field # unquoted field
end
end
end
end
end
end
share/ruby/csv/core_ext/string.rb 0000644 00000000314 15173547336 0013101 0 ustar 00 class String # :nodoc:
# Equivalent to CSV::parse_line(self, options)
#
# "CSV,data".parse_csv
# #=> ["CSV", "data"]
def parse_csv(**options)
CSV.parse_line(self, **options)
end
end
share/ruby/csv/core_ext/array.rb 0000644 00000000315 15173547336 0012712 0 ustar 00 class Array # :nodoc:
# Equivalent to CSV::generate_line(self, options)
#
# ["CSV", "data"].to_csv
# #=> "CSV,data\n"
def to_csv(**options)
CSV.generate_line(self, **options)
end
end
share/ruby/csv/row.rb 0000644 00000060277 15173547336 0010610 0 ustar 00 # frozen_string_literal: true
require "forwardable"
class CSV
# = \CSV::Row
# A \CSV::Row instance represents a \CSV table row.
# (see {class CSV}[../CSV.html]).
#
# The instance may have:
# - Fields: each is an object, not necessarily a \String.
# - Headers: each serves a key, and also need not be a \String.
#
# === Instance Methods
#
# \CSV::Row has three groups of instance methods:
# - Its own internally defined instance methods.
# - Methods included by module Enumerable.
# - Methods delegated to class Array.:
# * Array#empty?
# * Array#length
# * Array#size
#
# == Creating a \CSV::Row Instance
#
# Commonly, a new \CSV::Row instance is created by parsing \CSV source
# that has headers:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# table.each {|row| p row }
# Output:
# #<CSV::Row "Name":"foo" "Value":"0">
# #<CSV::Row "Name":"bar" "Value":"1">
# #<CSV::Row "Name":"baz" "Value":"2">
#
# You can also create a row directly. See ::new.
#
# == Headers
#
# Like a \CSV::Table, a \CSV::Row has headers.
#
# A \CSV::Row that was created by parsing \CSV source
# inherits its headers from the table:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table.first
# row.headers # => ["Name", "Value"]
#
# You can also create a new row with headers;
# like the keys in a \Hash, the headers need not be Strings:
# row = CSV::Row.new([:name, :value], ['foo', 0])
# row.headers # => [:name, :value]
#
# The new row retains its headers even if added to a table
# that has headers:
# table << row # => #<CSV::Table mode:col_or_row row_count:5>
# row.headers # => [:name, :value]
# row[:name] # => "foo"
# row['Name'] # => nil
#
#
#
# == Accessing Fields
#
# You may access a field in a \CSV::Row with either its \Integer index
# (\Array-style) or its header (\Hash-style).
#
# Fetch a field using method #[]:
# row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
# row[1] # => 0
# row['Value'] # => 0
#
# Set a field using method #[]=:
# row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
# row # => #<CSV::Row "Name":"foo" "Value":0>
# row[0] = 'bar'
# row['Value'] = 1
# row # => #<CSV::Row "Name":"bar" "Value":1>
#
class Row
# :call-seq:
# CSV::Row.new(headers, fields, header_row = false) -> csv_row
#
# Returns the new \CSV::Row instance constructed from
# arguments +headers+ and +fields+; both should be Arrays;
# note that the fields need not be Strings:
# row = CSV::Row.new(['Name', 'Value'], ['foo', 0])
# row # => #<CSV::Row "Name":"foo" "Value":0>
#
# If the \Array lengths are different, the shorter is +nil+-filled:
# row = CSV::Row.new(['Name', 'Value', 'Date', 'Size'], ['foo', 0])
# row # => #<CSV::Row "Name":"foo" "Value":0 "Date":nil "Size":nil>
#
# Each \CSV::Row object is either a <i>field row</i> or a <i>header row</i>;
# by default, a new row is a field row; for the row created above:
# row.field_row? # => true
# row.header_row? # => false
#
# If the optional argument +header_row+ is given as +true+,
# the created row is a header row:
# row = CSV::Row.new(['Name', 'Value'], ['foo', 0], header_row = true)
# row # => #<CSV::Row "Name":"foo" "Value":0>
# row.field_row? # => false
# row.header_row? # => true
def initialize(headers, fields, header_row = false)
@header_row = header_row
headers.each { |h| h.freeze if h.is_a? String }
# handle extra headers or fields
@row = if headers.size >= fields.size
headers.zip(fields)
else
fields.zip(headers).each(&:reverse!)
end
end
# Internal data format used to compare equality.
attr_reader :row
protected :row
### Array Delegation ###
extend Forwardable
def_delegators :@row, :empty?, :length, :size
# :call-seq:
# row.initialize_copy(other_row) -> self
#
# Calls superclass method.
def initialize_copy(other)
super_return_value = super
@row = @row.collect(&:dup)
super_return_value
end
# :call-seq:
# row.header_row? -> true or false
#
# Returns +true+ if this is a header row, +false+ otherwise.
def header_row?
@header_row
end
# :call-seq:
# row.field_row? -> true or false
#
# Returns +true+ if this is a field row, +false+ otherwise.
def field_row?
not header_row?
end
# :call-seq:
# row.headers -> array_of_headers
#
# Returns the headers for this row:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table.first
# row.headers # => ["Name", "Value"]
def headers
@row.map(&:first)
end
# :call-seq:
# field(index) -> value
# field(header) -> value
# field(header, offset) -> value
#
# Returns the field value for the given +index+ or +header+.
#
# ---
#
# Fetch field value by \Integer index:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.field(0) # => "foo"
# row.field(1) # => "bar"
#
# Counts backward from the last column if +index+ is negative:
# row.field(-1) # => "0"
# row.field(-2) # => "foo"
#
# Returns +nil+ if +index+ is out of range:
# row.field(2) # => nil
# row.field(-3) # => nil
#
# ---
#
# Fetch field value by header (first found):
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.field('Name') # => "Foo"
#
# Fetch field value by header, ignoring +offset+ leading fields:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.field('Name', 2) # => "Baz"
#
# Returns +nil+ if the header does not exist.
def field(header_or_index, minimum_index = 0)
# locate the pair
finder = (header_or_index.is_a?(Integer) || header_or_index.is_a?(Range)) ? :[] : :assoc
pair = @row[minimum_index..-1].public_send(finder, header_or_index)
# return the field if we have a pair
if pair.nil?
nil
else
header_or_index.is_a?(Range) ? pair.map(&:last) : pair.last
end
end
alias_method :[], :field
#
# :call-seq:
# fetch(header) -> value
# fetch(header, default) -> value
# fetch(header) {|row| ... } -> value
#
# Returns the field value as specified by +header+.
#
# ---
#
# With the single argument +header+, returns the field value
# for that header (first found):
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.fetch('Name') # => "Foo"
#
# Raises exception +KeyError+ if the header does not exist.
#
# ---
#
# With arguments +header+ and +default+ given,
# returns the field value for the header (first found)
# if the header exists, otherwise returns +default+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.fetch('Name', '') # => "Foo"
# row.fetch(:nosuch, '') # => ""
#
# ---
#
# With argument +header+ and a block given,
# returns the field value for the header (first found)
# if the header exists; otherwise calls the block
# and returns its return value:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.fetch('Name') {|header| fail 'Cannot happen' } # => "Foo"
# row.fetch(:nosuch) {|header| "Header '#{header} not found'" } # => "Header 'nosuch not found'"
def fetch(header, *varargs)
raise ArgumentError, "Too many arguments" if varargs.length > 1
pair = @row.assoc(header)
if pair
pair.last
else
if block_given?
yield header
elsif varargs.empty?
raise KeyError, "key not found: #{header}"
else
varargs.first
end
end
end
# :call-seq:
# row.has_key?(header) -> true or false
#
# Returns +true+ if there is a field with the given +header+,
# +false+ otherwise.
def has_key?(header)
!!@row.assoc(header)
end
alias_method :include?, :has_key?
alias_method :key?, :has_key?
alias_method :member?, :has_key?
alias_method :header?, :has_key?
#
# :call-seq:
# row[index] = value -> value
# row[header, offset] = value -> value
# row[header] = value -> value
#
# Assigns the field value for the given +index+ or +header+;
# returns +value+.
#
# ---
#
# Assign field value by \Integer index:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row[0] = 'Bat'
# row[1] = 3
# row # => #<CSV::Row "Name":"Bat" "Value":3>
#
# Counts backward from the last column if +index+ is negative:
# row[-1] = 4
# row[-2] = 'Bam'
# row # => #<CSV::Row "Name":"Bam" "Value":4>
#
# Extends the row with <tt>nil:nil</tt> if positive +index+ is not in the row:
# row[4] = 5
# row # => #<CSV::Row "Name":"bad" "Value":4 nil:nil nil:nil nil:5>
#
# Raises IndexError if negative +index+ is too small (too far from zero).
#
# ---
#
# Assign field value by header (first found):
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row['Name'] = 'Bat'
# row # => #<CSV::Row "Name":"Bat" "Name":"Bar" "Name":"Baz">
#
# Assign field value by header, ignoring +offset+ leading fields:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row['Name', 2] = 4
# row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":4>
#
# Append new field by (new) header:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row['New'] = 6
# row# => #<CSV::Row "Name":"foo" "Value":"0" "New":6>
def []=(*args)
value = args.pop
if args.first.is_a? Integer
if @row[args.first].nil? # extending past the end with index
@row[args.first] = [nil, value]
@row.map! { |pair| pair.nil? ? [nil, nil] : pair }
else # normal index assignment
@row[args.first][1] = value
end
else
index = index(*args)
if index.nil? # appending a field
self << [args.first, value]
else # normal header assignment
@row[index][1] = value
end
end
end
#
# :call-seq:
# row << [header, value] -> self
# row << hash -> self
# row << value -> self
#
# Adds a field to +self+; returns +self+:
#
# If the argument is a 2-element \Array <tt>[header, value]</tt>,
# a field is added with the given +header+ and +value+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row << ['NAME', 'Bat']
# row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" "NAME":"Bat">
#
# If the argument is a \Hash, each <tt>key-value</tt> pair is added
# as a field with header +key+ and value +value+.
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row << {NAME: 'Bat', name: 'Bam'}
# row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" NAME:"Bat" name:"Bam">
#
# Otherwise, the given +value+ is added as a field with no header.
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row << 'Bag'
# row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bag">
def <<(arg)
if arg.is_a?(Array) and arg.size == 2 # appending a header and name
@row << arg
elsif arg.is_a?(Hash) # append header and name pairs
arg.each { |pair| @row << pair }
else # append field value
@row << [nil, arg]
end
self # for chaining
end
# :call-seq:
# row.push(*values) -> self
#
# Appends each of the given +values+ to +self+ as a field; returns +self+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.push('Bat', 'Bam')
# row # => #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz" nil:"Bat" nil:"Bam">
def push(*args)
args.each { |arg| self << arg }
self # for chaining
end
#
# :call-seq:
# delete(index) -> [header, value] or nil
# delete(header) -> [header, value] or empty_array
# delete(header, offset) -> [header, value] or empty_array
#
# Removes a specified field from +self+; returns the 2-element \Array
# <tt>[header, value]</tt> if the field exists.
#
# If an \Integer argument +index+ is given,
# removes and returns the field at offset +index+,
# or returns +nil+ if the field does not exist:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.delete(1) # => ["Name", "Bar"]
# row.delete(50) # => nil
#
# Otherwise, if the single argument +header+ is given,
# removes and returns the first-found field with the given header,
# of returns a new empty \Array if the field does not exist:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.delete('Name') # => ["Name", "Foo"]
# row.delete('NAME') # => []
#
# If argument +header+ and \Integer argument +offset+ are given,
# removes and returns the first-found field with the given header
# whose +index+ is at least as large as +offset+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.delete('Name', 1) # => ["Name", "Bar"]
# row.delete('NAME', 1) # => []
def delete(header_or_index, minimum_index = 0)
if header_or_index.is_a? Integer # by index
@row.delete_at(header_or_index)
elsif i = index(header_or_index, minimum_index) # by header
@row.delete_at(i)
else
[ ]
end
end
# :call-seq:
# row.delete_if {|header, value| ... } -> self
#
# Removes fields from +self+ as selected by the block; returns +self+.
#
# Removes each field for which the block returns a truthy value:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.delete_if {|header, value| value.start_with?('B') } # => true
# row # => #<CSV::Row "Name":"Foo">
# row.delete_if {|header, value| header.start_with?('B') } # => false
#
# If no block is given, returns a new Enumerator:
# row.delete_if # => #<Enumerator: #<CSV::Row "Name":"Foo">:delete_if>
def delete_if(&block)
return enum_for(__method__) { size } unless block_given?
@row.delete_if(&block)
self # for chaining
end
# :call-seq:
# self.fields(*specifiers) -> array_of_fields
#
# Returns field values per the given +specifiers+, which may be any mixture of:
# - \Integer index.
# - \Range of \Integer indexes.
# - 2-element \Array containing a header and offset.
# - Header.
# - \Range of headers.
#
# For +specifier+ in one of the first four cases above,
# returns the result of <tt>self.field(specifier)</tt>; see #field.
#
# Although there may be any number of +specifiers+,
# the examples here will illustrate one at a time.
#
# When the specifier is an \Integer +index+,
# returns <tt>self.field(index)</tt>L
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.fields(1) # => ["Bar"]
#
# When the specifier is a \Range of \Integers +range+,
# returns <tt>self.field(range)</tt>:
# row.fields(1..2) # => ["Bar", "Baz"]
#
# When the specifier is a 2-element \Array +array+,
# returns <tt>self.field(array)</tt>L
# row.fields('Name', 1) # => ["Foo", "Bar"]
#
# When the specifier is a header +header+,
# returns <tt>self.field(header)</tt>L
# row.fields('Name') # => ["Foo"]
#
# When the specifier is a \Range of headers +range+,
# forms a new \Range +new_range+ from the indexes of
# <tt>range.start</tt> and <tt>range.end</tt>,
# and returns <tt>self.field(new_range)</tt>:
# source = "Name,NAME,name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.fields('Name'..'NAME') # => ["Foo", "Bar"]
#
# Returns all fields if no argument given:
# row.fields # => ["Foo", "Bar", "Baz"]
def fields(*headers_and_or_indices)
if headers_and_or_indices.empty? # return all fields--no arguments
@row.map(&:last)
else # or work like values_at()
all = []
headers_and_or_indices.each do |h_or_i|
if h_or_i.is_a? Range
index_begin = h_or_i.begin.is_a?(Integer) ? h_or_i.begin :
index(h_or_i.begin)
index_end = h_or_i.end.is_a?(Integer) ? h_or_i.end :
index(h_or_i.end)
new_range = h_or_i.exclude_end? ? (index_begin...index_end) :
(index_begin..index_end)
all.concat(fields.values_at(new_range))
else
all << field(*Array(h_or_i))
end
end
return all
end
end
alias_method :values_at, :fields
# :call-seq:
# index(header) -> index
# index(header, offset) -> index
#
# Returns the index for the given header, if it exists;
# otherwise returns +nil+.
#
# With the single argument +header+, returns the index
# of the first-found field with the given +header+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.index('Name') # => 0
# row.index('NAME') # => nil
#
# With arguments +header+ and +offset+,
# returns the index of the first-found field with given +header+,
# but ignoring the first +offset+ fields:
# row.index('Name', 1) # => 1
# row.index('Name', 3) # => nil
def index(header, minimum_index = 0)
# find the pair
index = headers[minimum_index..-1].index(header)
# return the index at the right offset, if we found one
index.nil? ? nil : index + minimum_index
end
# :call-seq:
# row.field?(value) -> true or false
#
# Returns +true+ if +value+ is a field in this row, +false+ otherwise:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.field?('Bar') # => true
# row.field?('BAR') # => false
def field?(data)
fields.include? data
end
include Enumerable
# :call-seq:
# row.each {|header, value| ... } -> self
#
# Calls the block with each header-value pair; returns +self+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.each {|header, value| p [header, value] }
# Output:
# ["Name", "Foo"]
# ["Name", "Bar"]
# ["Name", "Baz"]
#
# If no block is given, returns a new Enumerator:
# row.each # => #<Enumerator: #<CSV::Row "Name":"Foo" "Name":"Bar" "Name":"Baz">:each>
def each(&block)
return enum_for(__method__) { size } unless block_given?
@row.each(&block)
self # for chaining
end
alias_method :each_pair, :each
# :call-seq:
# row == other -> true or false
#
# Returns +true+ if +other+ is a /CSV::Row that has the same
# fields (headers and values) in the same order as +self+;
# otherwise returns +false+:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# other_row = table[0]
# row == other_row # => true
# other_row = table[1]
# row == other_row # => false
def ==(other)
return @row == other.row if other.is_a? CSV::Row
@row == other
end
# :call-seq:
# row.to_h -> hash
#
# Returns the new \Hash formed by adding each header-value pair in +self+
# as a key-value pair in the \Hash.
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.to_h # => {"Name"=>"foo", "Value"=>"0"}
#
# Header order is preserved, but repeated headers are ignored:
# source = "Name,Name,Name\nFoo,Bar,Baz\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.to_h # => {"Name"=>"Foo"}
def to_h
hash = {}
each do |key, _value|
hash[key] = self[key] unless hash.key?(key)
end
hash
end
alias_method :to_hash, :to_h
# :call-seq:
# row.deconstruct_keys(keys) -> hash
#
# Returns the new \Hash suitable for pattern matching containing only the
# keys specified as an argument.
def deconstruct_keys(keys)
if keys.nil?
to_h
else
keys.to_h { |key| [key, self[key]] }
end
end
alias_method :to_ary, :to_a
# :call-seq:
# row.deconstruct -> array
#
# Returns the new \Array suitable for pattern matching containing the values
# of the row.
def deconstruct
fields
end
# :call-seq:
# row.to_csv -> csv_string
#
# Returns the row as a \CSV String. Headers are not included:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.to_csv # => "foo,0\n"
def to_csv(**options)
fields.to_csv(**options)
end
alias_method :to_s, :to_csv
# :call-seq:
# row.dig(index_or_header, *identifiers) -> object
#
# Finds and returns the object in nested object that is specified
# by +index_or_header+ and +specifiers+.
#
# The nested objects may be instances of various classes.
# See {Dig Methods}[rdoc-ref:dig_methods.rdoc].
#
# Examples:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.dig(1) # => "0"
# row.dig('Value') # => "0"
# row.dig(5) # => nil
def dig(index_or_header, *indexes)
value = field(index_or_header)
if value.nil?
nil
elsif indexes.empty?
value
else
unless value.respond_to?(:dig)
raise TypeError, "#{value.class} does not have \#dig method"
end
value.dig(*indexes)
end
end
# :call-seq:
# row.inspect -> string
#
# Returns an ASCII-compatible \String showing:
# - Class \CSV::Row.
# - Header-value pairs.
# Example:
# source = "Name,Value\nfoo,0\nbar,1\nbaz,2\n"
# table = CSV.parse(source, headers: true)
# row = table[0]
# row.inspect # => "#<CSV::Row \"Name\":\"foo\" \"Value\":\"0\">"
def inspect
str = ["#<", self.class.to_s]
each do |header, field|
str << " " << (header.is_a?(Symbol) ? header.to_s : header.inspect) <<
":" << field.inspect
end
str << ">"
begin
str.join('')
rescue # any encoding error
str.map do |s|
e = Encoding::Converter.asciicompat_encoding(s.encoding)
e ? s.encode(e) : s.force_encoding("ASCII-8BIT")
end.join('')
end
end
end
end
share/ruby/csv/fields_converter.rb 0000644 00000005072 15173547336 0013326 0 ustar 00 # frozen_string_literal: true
class CSV
# Note: Don't use this class directly. This is an internal class.
class FieldsConverter
include Enumerable
#
# A CSV::FieldsConverter is a data structure for storing the
# fields converter properties to be passed as a parameter
# when parsing a new file (e.g. CSV::Parser.new(@io, parser_options))
#
def initialize(options={})
@converters = []
@nil_value = options[:nil_value]
@empty_value = options[:empty_value]
@empty_value_is_empty_string = (@empty_value == "")
@accept_nil = options[:accept_nil]
@builtin_converters_name = options[:builtin_converters_name]
@need_static_convert = need_static_convert?
end
def add_converter(name=nil, &converter)
if name.nil? # custom converter
@converters << converter
else # named converter
combo = builtin_converters[name]
case combo
when Array # combo converter
combo.each do |sub_name|
add_converter(sub_name)
end
else # individual named converter
@converters << combo
end
end
end
def each(&block)
@converters.each(&block)
end
def empty?
@converters.empty?
end
def convert(fields, headers, lineno, quoted_fields)
return fields unless need_convert?
fields.collect.with_index do |field, index|
if field.nil?
field = @nil_value
elsif field.is_a?(String) and field.empty?
field = @empty_value unless @empty_value_is_empty_string
end
@converters.each do |converter|
break if field.nil? and @accept_nil
if converter.arity == 1 # straight field converter
field = converter[field]
else # FieldInfo converter
if headers
header = headers[index]
else
header = nil
end
quoted = quoted_fields[index]
field = converter[field, FieldInfo.new(index, lineno, header, quoted)]
end
break unless field.is_a?(String) # short-circuit pipeline for speed
end
field # final state of each field, converted or original
end
end
private
def need_static_convert?
not (@nil_value.nil? and @empty_value_is_empty_string)
end
def need_convert?
@need_static_convert or
(not @converters.empty?)
end
def builtin_converters
@builtin_converters ||= ::CSV.const_get(@builtin_converters_name)
end
end
end
share/ruby/csv/input_record_separator.rb 0000644 00000000425 15173547336 0014543 0 ustar 00 require "English"
require "stringio"
class CSV
module InputRecordSeparator
class << self
if RUBY_VERSION >= "3.0.0"
def value
"\n"
end
else
def value
$INPUT_RECORD_SEPARATOR
end
end
end
end
end
share/ruby/csv/version.rb 0000644 00000000153 15173547336 0011451 0 ustar 00 # frozen_string_literal: true
class CSV
# The version of the installed library.
VERSION = "3.2.6"
end
share/ruby/csv/parser.rb 0000644 00000111310 15173547336 0011256 0 ustar 00 # frozen_string_literal: true
require "strscan"
require_relative "input_record_separator"
require_relative "row"
require_relative "table"
class CSV
# Note: Don't use this class directly. This is an internal class.
class Parser
#
# A CSV::Parser is m17n aware. The parser works in the Encoding of the IO
# or String object being read from or written to. Your data is never transcoded
# (unless you ask Ruby to transcode it for you) and will literally be parsed in
# the Encoding it is in. Thus CSV will return Arrays or Rows of Strings in the
# Encoding of your data. This is accomplished by transcoding the parser itself
# into your Encoding.
#
# Raised when encoding is invalid.
class InvalidEncoding < StandardError
end
# Raised when unexpected case is happen.
class UnexpectedError < StandardError
end
#
# CSV::Scanner receives a CSV output, scans it and return the content.
# It also controls the life cycle of the object with its methods +keep_start+,
# +keep_end+, +keep_back+, +keep_drop+.
#
# Uses StringScanner (the official strscan gem). Strscan provides lexical
# scanning operations on a String. We inherit its object and take advantage
# on the methods. For more information, please visit:
# https://ruby-doc.org/stdlib-2.6.1/libdoc/strscan/rdoc/StringScanner.html
#
class Scanner < StringScanner
alias_method :scan_all, :scan
def initialize(*args)
super
@keeps = []
end
def each_line(row_separator)
position = pos
rest.each_line(row_separator) do |line|
position += line.bytesize
self.pos = position
yield(line)
end
end
def keep_start
@keeps.push(pos)
end
def keep_end
start = @keeps.pop
string.byteslice(start, pos - start)
end
def keep_back
self.pos = @keeps.pop
end
def keep_drop
@keeps.pop
end
end
#
# CSV::InputsScanner receives IO inputs, encoding and the chunk_size.
# It also controls the life cycle of the object with its methods +keep_start+,
# +keep_end+, +keep_back+, +keep_drop+.
#
# CSV::InputsScanner.scan() tries to match with pattern at the current position.
# If there's a match, the scanner advances the "scan pointer" and returns the matched string.
# Otherwise, the scanner returns nil.
#
# CSV::InputsScanner.rest() returns the "rest" of the string (i.e. everything after the scan pointer).
# If there is no more data (eos? = true), it returns "".
#
class InputsScanner
def initialize(inputs, encoding, row_separator, chunk_size: 8192)
@inputs = inputs.dup
@encoding = encoding
@row_separator = row_separator
@chunk_size = chunk_size
@last_scanner = @inputs.empty?
@keeps = []
read_chunk
end
def each_line(row_separator)
return enum_for(__method__, row_separator) unless block_given?
buffer = nil
input = @scanner.rest
position = @scanner.pos
offset = 0
n_row_separator_chars = row_separator.size
# trace(__method__, :start, line, input)
while true
input.each_line(row_separator) do |line|
@scanner.pos += line.bytesize
if buffer
if n_row_separator_chars == 2 and
buffer.end_with?(row_separator[0]) and
line.start_with?(row_separator[1])
buffer << line[0]
line = line[1..-1]
position += buffer.bytesize + offset
@scanner.pos = position
offset = 0
yield(buffer)
buffer = nil
next if line.empty?
else
buffer << line
line = buffer
buffer = nil
end
end
if line.end_with?(row_separator)
position += line.bytesize + offset
@scanner.pos = position
offset = 0
yield(line)
else
buffer = line
end
end
break unless read_chunk
input = @scanner.rest
position = @scanner.pos
offset = -buffer.bytesize if buffer
end
yield(buffer) if buffer
end
def scan(pattern)
# trace(__method__, pattern, :start)
value = @scanner.scan(pattern)
# trace(__method__, pattern, :done, :last, value) if @last_scanner
return value if @last_scanner
read_chunk if value and @scanner.eos?
# trace(__method__, pattern, :done, value)
value
end
def scan_all(pattern)
# trace(__method__, pattern, :start)
value = @scanner.scan(pattern)
# trace(__method__, pattern, :done, :last, value) if @last_scanner
return value if @last_scanner
return nil if value.nil?
while @scanner.eos? and read_chunk and (sub_value = @scanner.scan(pattern))
# trace(__method__, pattern, :sub, sub_value)
value << sub_value
end
# trace(__method__, pattern, :done, value)
value
end
def eos?
@scanner.eos?
end
def keep_start
# trace(__method__, :start)
adjust_last_keep
@keeps.push([@scanner, @scanner.pos, nil])
# trace(__method__, :done)
end
def keep_end
# trace(__method__, :start)
scanner, start, buffer = @keeps.pop
if scanner == @scanner
keep = @scanner.string.byteslice(start, @scanner.pos - start)
else
keep = @scanner.string.byteslice(0, @scanner.pos)
end
if buffer
buffer << keep
keep = buffer
end
# trace(__method__, :done, keep)
keep
end
def keep_back
# trace(__method__, :start)
scanner, start, buffer = @keeps.pop
if buffer
# trace(__method__, :rescan, start, buffer)
string = @scanner.string
if scanner == @scanner
keep = string.byteslice(start, string.bytesize - start)
else
keep = string
end
if keep and not keep.empty?
@inputs.unshift(StringIO.new(keep))
@last_scanner = false
end
@scanner = StringScanner.new(buffer)
else
if @scanner != scanner
message = "scanners are different but no buffer: "
message += "#{@scanner.inspect}(#{@scanner.object_id}): "
message += "#{scanner.inspect}(#{scanner.object_id})"
raise UnexpectedError, message
end
# trace(__method__, :repos, start, buffer)
@scanner.pos = start
end
read_chunk if @scanner.eos?
end
def keep_drop
_, _, buffer = @keeps.pop
# trace(__method__, :done, :empty) unless buffer
return unless buffer
last_keep = @keeps.last
# trace(__method__, :done, :no_last_keep) unless last_keep
return unless last_keep
if last_keep[2]
last_keep[2] << buffer
else
last_keep[2] = buffer
end
# trace(__method__, :done)
end
def rest
@scanner.rest
end
def check(pattern)
@scanner.check(pattern)
end
private
def trace(*args)
pp([*args, @scanner, @scanner&.string, @scanner&.pos, @keeps])
end
def adjust_last_keep
# trace(__method__, :start)
keep = @keeps.last
# trace(__method__, :done, :empty) if keep.nil?
return if keep.nil?
scanner, start, buffer = keep
string = @scanner.string
if @scanner != scanner
start = 0
end
if start == 0 and @scanner.eos?
keep_data = string
else
keep_data = string.byteslice(start, @scanner.pos - start)
end
if keep_data
if buffer
buffer << keep_data
else
keep[2] = keep_data.dup
end
end
# trace(__method__, :done)
end
def read_chunk
return false if @last_scanner
adjust_last_keep
input = @inputs.first
case input
when StringIO
string = input.read
raise InvalidEncoding unless string.valid_encoding?
# trace(__method__, :stringio, string)
@scanner = StringScanner.new(string)
@inputs.shift
@last_scanner = @inputs.empty?
true
else
chunk = input.gets(@row_separator, @chunk_size)
if chunk
raise InvalidEncoding unless chunk.valid_encoding?
# trace(__method__, :chunk, chunk)
@scanner = StringScanner.new(chunk)
if input.respond_to?(:eof?) and input.eof?
@inputs.shift
@last_scanner = @inputs.empty?
end
true
else
# trace(__method__, :no_chunk)
@scanner = StringScanner.new("".encode(@encoding))
@inputs.shift
@last_scanner = @inputs.empty?
if @last_scanner
false
else
read_chunk
end
end
end
end
end
def initialize(input, options)
@input = input
@options = options
@samples = []
prepare
end
def column_separator
@column_separator
end
def row_separator
@row_separator
end
def quote_character
@quote_character
end
def field_size_limit
@max_field_size&.succ
end
def max_field_size
@max_field_size
end
def skip_lines
@skip_lines
end
def unconverted_fields?
@unconverted_fields
end
def headers
@headers
end
def header_row?
@use_headers and @headers.nil?
end
def return_headers?
@return_headers
end
def skip_blanks?
@skip_blanks
end
def liberal_parsing?
@liberal_parsing
end
def lineno
@lineno
end
def line
last_line
end
def parse(&block)
return to_enum(__method__) unless block_given?
if @return_headers and @headers and @raw_headers
headers = Row.new(@headers, @raw_headers, true)
if @unconverted_fields
headers = add_unconverted_fields(headers, [])
end
yield headers
end
begin
@scanner ||= build_scanner
if quote_character.nil?
parse_no_quote(&block)
elsif @need_robust_parsing
parse_quotable_robust(&block)
else
parse_quotable_loose(&block)
end
rescue InvalidEncoding
if @scanner
ignore_broken_line
lineno = @lineno
else
lineno = @lineno + 1
end
message = "Invalid byte sequence in #{@encoding}"
raise MalformedCSVError.new(message, lineno)
rescue UnexpectedError => error
if @scanner
ignore_broken_line
lineno = @lineno
else
lineno = @lineno + 1
end
message = "This should not be happen: #{error.message}: "
message += "Please report this to https://github.com/ruby/csv/issues"
raise MalformedCSVError.new(message, lineno)
end
end
def use_headers?
@use_headers
end
private
# A set of tasks to prepare the file in order to parse it
def prepare
prepare_variable
prepare_quote_character
prepare_backslash
prepare_skip_lines
prepare_strip
prepare_separators
validate_strip_and_col_sep_options
prepare_quoted
prepare_unquoted
prepare_line
prepare_header
prepare_parser
end
def prepare_variable
@need_robust_parsing = false
@encoding = @options[:encoding]
liberal_parsing = @options[:liberal_parsing]
if liberal_parsing
@liberal_parsing = true
if liberal_parsing.is_a?(Hash)
@double_quote_outside_quote =
liberal_parsing[:double_quote_outside_quote]
@backslash_quote = liberal_parsing[:backslash_quote]
else
@double_quote_outside_quote = false
@backslash_quote = false
end
@need_robust_parsing = true
else
@liberal_parsing = false
@backslash_quote = false
end
@unconverted_fields = @options[:unconverted_fields]
@max_field_size = @options[:max_field_size]
@skip_blanks = @options[:skip_blanks]
@fields_converter = @options[:fields_converter]
@header_fields_converter = @options[:header_fields_converter]
end
def prepare_quote_character
@quote_character = @options[:quote_character]
if @quote_character.nil?
@escaped_quote_character = nil
@escaped_quote = nil
else
@quote_character = @quote_character.to_s.encode(@encoding)
if @quote_character.length != 1
message = ":quote_char has to be nil or a single character String"
raise ArgumentError, message
end
@double_quote_character = @quote_character * 2
@escaped_quote_character = Regexp.escape(@quote_character)
@escaped_quote = Regexp.new(@escaped_quote_character)
end
end
def prepare_backslash
return unless @backslash_quote
@backslash_character = "\\".encode(@encoding)
@escaped_backslash_character = Regexp.escape(@backslash_character)
@escaped_backslash = Regexp.new(@escaped_backslash_character)
if @quote_character.nil?
@backslash_quote_character = nil
else
@backslash_quote_character =
@backslash_character + @escaped_quote_character
end
end
def prepare_skip_lines
skip_lines = @options[:skip_lines]
case skip_lines
when String
@skip_lines = skip_lines.encode(@encoding)
when Regexp, nil
@skip_lines = skip_lines
else
unless skip_lines.respond_to?(:match)
message =
":skip_lines has to respond to \#match: #{skip_lines.inspect}"
raise ArgumentError, message
end
@skip_lines = skip_lines
end
end
def prepare_strip
@strip = @options[:strip]
@escaped_strip = nil
@strip_value = nil
@rstrip_value = nil
if @strip.is_a?(String)
case @strip.length
when 0
raise ArgumentError, ":strip must not be an empty String"
when 1
# ok
else
raise ArgumentError, ":strip doesn't support 2 or more characters yet"
end
@strip = @strip.encode(@encoding)
@escaped_strip = Regexp.escape(@strip)
if @quote_character
@strip_value = Regexp.new(@escaped_strip +
"+".encode(@encoding))
@rstrip_value = Regexp.new(@escaped_strip +
"+\\z".encode(@encoding))
end
@need_robust_parsing = true
elsif @strip
strip_values = " \t\f\v"
@escaped_strip = strip_values.encode(@encoding)
if @quote_character
@strip_value = Regexp.new("[#{strip_values}]+".encode(@encoding))
@rstrip_value = Regexp.new("[#{strip_values}]+\\z".encode(@encoding))
end
@need_robust_parsing = true
end
end
begin
StringScanner.new("x").scan("x")
rescue TypeError
STRING_SCANNER_SCAN_ACCEPT_STRING = false
else
STRING_SCANNER_SCAN_ACCEPT_STRING = true
end
def prepare_separators
column_separator = @options[:column_separator]
@column_separator = column_separator.to_s.encode(@encoding)
if @column_separator.size < 1
message = ":col_sep must be 1 or more characters: "
message += column_separator.inspect
raise ArgumentError, message
end
@row_separator =
resolve_row_separator(@options[:row_separator]).encode(@encoding)
@escaped_column_separator = Regexp.escape(@column_separator)
@escaped_first_column_separator = Regexp.escape(@column_separator[0])
if @column_separator.size > 1
@column_end = Regexp.new(@escaped_column_separator)
@column_ends = @column_separator.each_char.collect do |char|
Regexp.new(Regexp.escape(char))
end
@first_column_separators = Regexp.new(@escaped_first_column_separator +
"+".encode(@encoding))
else
if STRING_SCANNER_SCAN_ACCEPT_STRING
@column_end = @column_separator
else
@column_end = Regexp.new(@escaped_column_separator)
end
@column_ends = nil
@first_column_separators = nil
end
escaped_row_separator = Regexp.escape(@row_separator)
@row_end = Regexp.new(escaped_row_separator)
if @row_separator.size > 1
@row_ends = @row_separator.each_char.collect do |char|
Regexp.new(Regexp.escape(char))
end
else
@row_ends = nil
end
@cr = "\r".encode(@encoding)
@lf = "\n".encode(@encoding)
@line_end = Regexp.new("\r\n|\n|\r".encode(@encoding))
@not_line_end = Regexp.new("[^\r\n]+".encode(@encoding))
end
# This method verifies that there are no (obvious) ambiguities with the
# provided +col_sep+ and +strip+ parsing options. For example, if +col_sep+
# and +strip+ were both equal to +\t+, then there would be no clear way to
# parse the input.
def validate_strip_and_col_sep_options
return unless @strip
if @strip.is_a?(String)
if @column_separator.start_with?(@strip) || @column_separator.end_with?(@strip)
raise ArgumentError,
"The provided strip (#{@escaped_strip}) and " \
"col_sep (#{@escaped_column_separator}) options are incompatible."
end
else
if Regexp.new("\\A[#{@escaped_strip}]|[#{@escaped_strip}]\\z").match?(@column_separator)
raise ArgumentError,
"The provided strip (true) and " \
"col_sep (#{@escaped_column_separator}) options are incompatible."
end
end
end
def prepare_quoted
if @quote_character
@quotes = Regexp.new(@escaped_quote_character +
"+".encode(@encoding))
no_quoted_values = @escaped_quote_character.dup
if @backslash_quote
no_quoted_values << @escaped_backslash_character
end
@quoted_value = Regexp.new("[^".encode(@encoding) +
no_quoted_values +
"]+".encode(@encoding))
end
if @escaped_strip
@split_column_separator = Regexp.new(@escaped_strip +
"*".encode(@encoding) +
@escaped_column_separator +
@escaped_strip +
"*".encode(@encoding))
else
if @column_separator == " ".encode(@encoding)
@split_column_separator = Regexp.new(@escaped_column_separator)
else
@split_column_separator = @column_separator
end
end
end
def prepare_unquoted
return if @quote_character.nil?
no_unquoted_values = "\r\n".encode(@encoding)
no_unquoted_values << @escaped_first_column_separator
unless @liberal_parsing
no_unquoted_values << @escaped_quote_character
end
@unquoted_value = Regexp.new("[^".encode(@encoding) +
no_unquoted_values +
"]+".encode(@encoding))
end
def resolve_row_separator(separator)
if separator == :auto
cr = "\r".encode(@encoding)
lf = "\n".encode(@encoding)
if @input.is_a?(StringIO)
pos = @input.pos
separator = detect_row_separator(@input.read, cr, lf)
@input.seek(pos)
elsif @input.respond_to?(:gets)
if @input.is_a?(File)
chunk_size = 32 * 1024
else
chunk_size = 1024
end
begin
while separator == :auto
#
# if we run out of data, it's probably a single line
# (ensure will set default value)
#
break unless sample = @input.gets(nil, chunk_size)
# extend sample if we're unsure of the line ending
if sample.end_with?(cr)
sample << (@input.gets(nil, 1) || "")
end
@samples << sample
separator = detect_row_separator(sample, cr, lf)
end
rescue IOError
# do nothing: ensure will set default
end
end
separator = InputRecordSeparator.value if separator == :auto
end
separator.to_s.encode(@encoding)
end
def detect_row_separator(sample, cr, lf)
lf_index = sample.index(lf)
if lf_index
cr_index = sample[0, lf_index].index(cr)
else
cr_index = sample.index(cr)
end
if cr_index and lf_index
if cr_index + 1 == lf_index
cr + lf
elsif cr_index < lf_index
cr
else
lf
end
elsif cr_index
cr
elsif lf_index
lf
else
:auto
end
end
def prepare_line
@lineno = 0
@last_line = nil
@scanner = nil
end
def last_line
if @scanner
@last_line ||= @scanner.keep_end
else
@last_line
end
end
def prepare_header
@return_headers = @options[:return_headers]
headers = @options[:headers]
case headers
when Array
@raw_headers = headers
quoted_fields = [false] * @raw_headers.size
@use_headers = true
when String
@raw_headers, quoted_fields = parse_headers(headers)
@use_headers = true
when nil, false
@raw_headers = nil
@use_headers = false
else
@raw_headers = nil
@use_headers = true
end
if @raw_headers
@headers = adjust_headers(@raw_headers, quoted_fields)
else
@headers = nil
end
end
def parse_headers(row)
quoted_fields = []
converter = lambda do |field, info|
quoted_fields << info.quoted?
field
end
headers = CSV.parse_line(row,
col_sep: @column_separator,
row_sep: @row_separator,
quote_char: @quote_character,
converters: [converter])
[headers, quoted_fields]
end
def adjust_headers(headers, quoted_fields)
adjusted_headers = @header_fields_converter.convert(headers, nil, @lineno, quoted_fields)
adjusted_headers.each {|h| h.freeze if h.is_a? String}
adjusted_headers
end
def prepare_parser
@may_quoted = may_quoted?
end
def may_quoted?
return false if @quote_character.nil?
if @input.is_a?(StringIO)
pos = @input.pos
sample = @input.read
@input.seek(pos)
else
return false if @samples.empty?
sample = @samples.first
end
sample[0, 128].index(@quote_character)
end
class UnoptimizedStringIO # :nodoc:
def initialize(string)
@io = StringIO.new(string, "rb:#{string.encoding}")
end
def gets(*args)
@io.gets(*args)
end
def each_line(*args, &block)
@io.each_line(*args, &block)
end
def eof?
@io.eof?
end
end
SCANNER_TEST = (ENV["CSV_PARSER_SCANNER_TEST"] == "yes")
if SCANNER_TEST
SCANNER_TEST_CHUNK_SIZE_NAME = "CSV_PARSER_SCANNER_TEST_CHUNK_SIZE"
SCANNER_TEST_CHUNK_SIZE_VALUE = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
def build_scanner
inputs = @samples.collect do |sample|
UnoptimizedStringIO.new(sample)
end
if @input.is_a?(StringIO)
inputs << UnoptimizedStringIO.new(@input.read)
else
inputs << @input
end
begin
chunk_size_value = ENV[SCANNER_TEST_CHUNK_SIZE_NAME]
rescue # Ractor::IsolationError
# Ractor on Ruby 3.0 can't read ENV value.
chunk_size_value = SCANNER_TEST_CHUNK_SIZE_VALUE
end
chunk_size = Integer((chunk_size_value || "1"), 10)
InputsScanner.new(inputs,
@encoding,
@row_separator,
chunk_size: chunk_size)
end
else
def build_scanner
string = nil
if @samples.empty? and @input.is_a?(StringIO)
string = @input.read
elsif @samples.size == 1 and
@input != ARGF and
@input.respond_to?(:eof?) and
@input.eof?
string = @samples[0]
end
if string
unless string.valid_encoding?
index = string.lines(@row_separator).index do |line|
!line.valid_encoding?
end
if index
message = "Invalid byte sequence in #{@encoding}"
raise MalformedCSVError.new(message, @lineno + index + 1)
end
end
Scanner.new(string)
else
inputs = @samples.collect do |sample|
StringIO.new(sample)
end
inputs << @input
InputsScanner.new(inputs, @encoding, @row_separator)
end
end
end
def skip_needless_lines
return unless @skip_lines
until @scanner.eos?
@scanner.keep_start
line = @scanner.scan_all(@not_line_end) || "".encode(@encoding)
line << @row_separator if parse_row_end
if skip_line?(line)
@lineno += 1
@scanner.keep_drop
else
@scanner.keep_back
return
end
end
end
def skip_line?(line)
line = line.delete_suffix(@row_separator)
case @skip_lines
when String
line.include?(@skip_lines)
when Regexp
@skip_lines.match?(line)
else
@skip_lines.match(line)
end
end
def validate_field_size(field)
return unless @max_field_size
return if field.size <= @max_field_size
ignore_broken_line
message = "Field size exceeded: #{field.size} > #{@max_field_size}"
raise MalformedCSVError.new(message, @lineno)
end
def parse_no_quote(&block)
@scanner.each_line(@row_separator) do |line|
next if @skip_lines and skip_line?(line)
original_line = line
line = line.delete_suffix(@row_separator)
if line.empty?
next if @skip_blanks
row = []
quoted_fields = []
else
line = strip_value(line)
row = line.split(@split_column_separator, -1)
quoted_fields = [false] * row.size
if @max_field_size
row.each do |column|
validate_field_size(column)
end
end
n_columns = row.size
i = 0
while i < n_columns
row[i] = nil if row[i].empty?
i += 1
end
end
@last_line = original_line
emit_row(row, quoted_fields, &block)
end
end
def parse_quotable_loose(&block)
@scanner.keep_start
@scanner.each_line(@row_separator) do |line|
if @skip_lines and skip_line?(line)
@scanner.keep_drop
@scanner.keep_start
next
end
original_line = line
line = line.delete_suffix(@row_separator)
if line.empty?
if @skip_blanks
@scanner.keep_drop
@scanner.keep_start
next
end
row = []
quoted_fields = []
elsif line.include?(@cr) or line.include?(@lf)
@scanner.keep_back
@need_robust_parsing = true
return parse_quotable_robust(&block)
else
row = line.split(@split_column_separator, -1)
quoted_fields = []
n_columns = row.size
i = 0
while i < n_columns
column = row[i]
if column.empty?
quoted_fields << false
row[i] = nil
else
n_quotes = column.count(@quote_character)
if n_quotes.zero?
quoted_fields << false
# no quote
elsif n_quotes == 2 and
column.start_with?(@quote_character) and
column.end_with?(@quote_character)
quoted_fields << true
row[i] = column[1..-2]
else
@scanner.keep_back
@need_robust_parsing = true
return parse_quotable_robust(&block)
end
validate_field_size(row[i])
end
i += 1
end
end
@scanner.keep_drop
@scanner.keep_start
@last_line = original_line
emit_row(row, quoted_fields, &block)
end
@scanner.keep_drop
end
def parse_quotable_robust(&block)
row = []
quoted_fields = []
skip_needless_lines
start_row
while true
@quoted_column_value = false
@unquoted_column_value = false
@scanner.scan_all(@strip_value) if @strip_value
value = parse_column_value
if value
@scanner.scan_all(@strip_value) if @strip_value
validate_field_size(value)
end
if parse_column_end
row << value
quoted_fields << @quoted_column_value
elsif parse_row_end
if row.empty? and value.nil?
emit_row([], [], &block) unless @skip_blanks
else
row << value
quoted_fields << @quoted_column_value
emit_row(row, quoted_fields, &block)
row = []
quoted_fields = []
end
skip_needless_lines
start_row
elsif @scanner.eos?
break if row.empty? and value.nil?
row << value
quoted_fields << @quoted_column_value
emit_row(row, quoted_fields, &block)
break
else
if @quoted_column_value
if liberal_parsing? and (new_line = @scanner.check(@line_end))
message =
"Illegal end-of-line sequence outside of a quoted field " +
"<#{new_line.inspect}>"
else
message = "Any value after quoted field isn't allowed"
end
ignore_broken_line
raise MalformedCSVError.new(message, @lineno)
elsif @unquoted_column_value and
(new_line = @scanner.scan(@line_end))
ignore_broken_line
message = "Unquoted fields do not allow new line " +
"<#{new_line.inspect}>"
raise MalformedCSVError.new(message, @lineno)
elsif @scanner.rest.start_with?(@quote_character)
ignore_broken_line
message = "Illegal quoting"
raise MalformedCSVError.new(message, @lineno)
elsif (new_line = @scanner.scan(@line_end))
ignore_broken_line
message = "New line must be <#{@row_separator.inspect}> " +
"not <#{new_line.inspect}>"
raise MalformedCSVError.new(message, @lineno)
else
ignore_broken_line
raise MalformedCSVError.new("TODO: Meaningful message",
@lineno)
end
end
end
end
def parse_column_value
if @liberal_parsing
quoted_value = parse_quoted_column_value
if quoted_value
@scanner.scan_all(@strip_value) if @strip_value
unquoted_value = parse_unquoted_column_value
if unquoted_value
if @double_quote_outside_quote
unquoted_value = unquoted_value.gsub(@quote_character * 2,
@quote_character)
if quoted_value.empty? # %Q{""...} case
return @quote_character + unquoted_value
end
end
@quote_character + quoted_value + @quote_character + unquoted_value
else
quoted_value
end
else
parse_unquoted_column_value
end
elsif @may_quoted
parse_quoted_column_value ||
parse_unquoted_column_value
else
parse_unquoted_column_value ||
parse_quoted_column_value
end
end
def parse_unquoted_column_value
value = @scanner.scan_all(@unquoted_value)
return nil unless value
@unquoted_column_value = true
if @first_column_separators
while true
@scanner.keep_start
is_column_end = @column_ends.all? do |column_end|
@scanner.scan(column_end)
end
@scanner.keep_back
break if is_column_end
sub_separator = @scanner.scan_all(@first_column_separators)
break if sub_separator.nil?
value << sub_separator
sub_value = @scanner.scan_all(@unquoted_value)
break if sub_value.nil?
value << sub_value
end
end
value.gsub!(@backslash_quote_character, @quote_character) if @backslash_quote
if @rstrip_value
value.gsub!(@rstrip_value, "")
end
value
end
def parse_quoted_column_value
quotes = @scanner.scan_all(@quotes)
return nil unless quotes
@quoted_column_value = true
n_quotes = quotes.size
if (n_quotes % 2).zero?
quotes[0, (n_quotes - 2) / 2]
else
value = quotes[0, n_quotes / 2]
while true
quoted_value = @scanner.scan_all(@quoted_value)
value << quoted_value if quoted_value
if @backslash_quote
if @scanner.scan(@escaped_backslash)
if @scanner.scan(@escaped_quote)
value << @quote_character
else
value << @backslash_character
end
next
end
end
quotes = @scanner.scan_all(@quotes)
unless quotes
ignore_broken_line
message = "Unclosed quoted field"
raise MalformedCSVError.new(message, @lineno)
end
n_quotes = quotes.size
if n_quotes == 1
break
else
value << quotes[0, n_quotes / 2]
break if (n_quotes % 2) == 1
end
end
value
end
end
def parse_column_end
return true if @scanner.scan(@column_end)
return false unless @column_ends
@scanner.keep_start
if @column_ends.all? {|column_end| @scanner.scan(column_end)}
@scanner.keep_drop
true
else
@scanner.keep_back
false
end
end
def parse_row_end
return true if @scanner.scan(@row_end)
return false unless @row_ends
@scanner.keep_start
if @row_ends.all? {|row_end| @scanner.scan(row_end)}
@scanner.keep_drop
true
else
@scanner.keep_back
false
end
end
def strip_value(value)
return value unless @strip
return value if value.nil?
case @strip
when String
while value.delete_prefix!(@strip)
# do nothing
end
while value.delete_suffix!(@strip)
# do nothing
end
else
value.strip!
end
value
end
def ignore_broken_line
@scanner.scan_all(@not_line_end)
@scanner.scan_all(@line_end)
@lineno += 1
end
def start_row
if @last_line
@last_line = nil
else
@scanner.keep_drop
end
@scanner.keep_start
end
def emit_row(row, quoted_fields, &block)
@lineno += 1
raw_row = row
if @use_headers
if @headers.nil?
@headers = adjust_headers(row, quoted_fields)
return unless @return_headers
row = Row.new(@headers, row, true)
else
row = Row.new(@headers,
@fields_converter.convert(raw_row, @headers, @lineno, quoted_fields))
end
else
# convert fields, if needed...
row = @fields_converter.convert(raw_row, nil, @lineno, quoted_fields)
end
# inject unconverted fields and accessor, if requested...
if @unconverted_fields and not row.respond_to?(:unconverted_fields)
add_unconverted_fields(row, raw_row)
end
yield(row)
end
# This method injects an instance variable <tt>unconverted_fields</tt> into
# +row+ and an accessor method for +row+ called unconverted_fields(). The
# variable is set to the contents of +fields+.
def add_unconverted_fields(row, fields)
class << row
attr_reader :unconverted_fields
end
row.instance_variable_set(:@unconverted_fields, fields)
row
end
end
end
share/ruby/csv/delete_suffix.rb 0000644 00000000566 15173547336 0012622 0 ustar 00 # frozen_string_literal: true
# This provides String#delete_suffix? for Ruby 2.4.
unless String.method_defined?(:delete_suffix)
class CSV
module DeleteSuffix
refine String do
def delete_suffix(suffix)
if end_with?(suffix)
self[0...-suffix.size]
else
self
end
end
end
end
end
end
share/ruby/csv/match_p.rb 0000644 00000000571 15173547336 0011403 0 ustar 00 # frozen_string_literal: true
# This provides String#match? and Regexp#match? for Ruby 2.3.
unless String.method_defined?(:match?)
class CSV
module MatchP
refine String do
def match?(pattern)
self =~ pattern
end
end
refine Regexp do
def match?(string)
self =~ string
end
end
end
end
end
share/ruby/rinda/ring.rb 0000644 00000031061 15173547336 0011227 0 ustar 00 # frozen_string_literal: false
#
# Note: Rinda::Ring API is unstable.
#
require 'drb/drb'
require_relative '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
share/ruby/rinda/tuplespace.rb 0000644 00000033610 15173547336 0012437 0 ustar 00 # frozen_string_literal: false
require 'monitor'
require 'drb/drb'
require_relative '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
share/ruby/rinda/rinda.rb 0000644 00000015123 15173547336 0011366 0 ustar 00 # 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
share/ruby/uri/generic.rb 0000644 00000112627 15173547336 0011416 0 ustar 00 # frozen_string_literal: true
# = uri/generic.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'common'
autoload :IPSocket, 'socket'
autoload :IPAddr, 'ipaddr'
module URI
#
# Base class for all URI classes.
# Implements generic URI syntax as per RFC 2396.
#
class Generic
include URI
#
# A Default port of nil for URI::Generic.
#
DEFAULT_PORT = nil
#
# Returns default port.
#
def self.default_port
self::DEFAULT_PORT
end
#
# Returns default port.
#
def default_port
self.class.default_port
end
#
# An Array of the available components for URI::Generic.
#
COMPONENT = [
:scheme,
:userinfo, :host, :port, :registry,
:path, :opaque,
:query,
:fragment
].freeze
#
# Components of the URI in the order.
#
def self.component
self::COMPONENT
end
USE_REGISTRY = false # :nodoc:
def self.use_registry # :nodoc:
self::USE_REGISTRY
end
#
# == Synopsis
#
# See ::new.
#
# == Description
#
# At first, tries to create a new URI::Generic instance using
# URI::Generic::build. But, if exception URI::InvalidComponentError is raised,
# then it does URI::Escape.escape all URI components and tries again.
#
def self.build2(args)
begin
return self.build(args)
rescue InvalidComponentError
if args.kind_of?(Array)
return self.build(args.collect{|x|
if x.is_a?(String)
DEFAULT_PARSER.escape(x)
else
x
end
})
elsif args.kind_of?(Hash)
tmp = {}
args.each do |key, value|
tmp[key] = if value
DEFAULT_PARSER.escape(value)
else
value
end
end
return self.build(tmp)
end
end
end
#
# == Synopsis
#
# See ::new.
#
# == Description
#
# Creates a new URI::Generic instance from components of URI::Generic
# with check. Components are: scheme, userinfo, host, port, registry, path,
# opaque, query, and fragment. You can provide arguments either by an Array or a Hash.
# See ::new for hash keys to use or for order of array items.
#
def self.build(args)
if args.kind_of?(Array) &&
args.size == ::URI::Generic::COMPONENT.size
tmp = args.dup
elsif args.kind_of?(Hash)
tmp = ::URI::Generic::COMPONENT.collect do |c|
if args.include?(c)
args[c]
else
nil
end
end
else
component = self.class.component rescue ::URI::Generic::COMPONENT
raise ArgumentError,
"expected Array of or Hash of components of #{self.class} (#{component.join(', ')})"
end
tmp << nil
tmp << true
return self.new(*tmp)
end
#
# == Args
#
# +scheme+::
# Protocol scheme, i.e. 'http','ftp','mailto' and so on.
# +userinfo+::
# User name and password, i.e. 'sdmitry:bla'.
# +host+::
# Server host name.
# +port+::
# Server port.
# +registry+::
# Registry of naming authorities.
# +path+::
# Path on server.
# +opaque+::
# Opaque part.
# +query+::
# Query data.
# +fragment+::
# Part of the URI after '#' character.
# +parser+::
# Parser for internal use [URI::DEFAULT_PARSER by default].
# +arg_check+::
# Check arguments [false by default].
#
# == Description
#
# Creates a new URI::Generic instance from ``generic'' components without check.
#
def initialize(scheme,
userinfo, host, port, registry,
path, opaque,
query,
fragment,
parser = DEFAULT_PARSER,
arg_check = false)
@scheme = nil
@user = nil
@password = nil
@host = nil
@port = nil
@path = nil
@query = nil
@opaque = nil
@fragment = nil
@parser = parser == DEFAULT_PARSER ? nil : parser
if arg_check
self.scheme = scheme
self.hostname = host
self.port = port
self.userinfo = userinfo
self.path = path
self.query = query
self.opaque = opaque
self.fragment = fragment
else
self.set_scheme(scheme)
self.set_host(host)
self.set_port(port)
self.set_userinfo(userinfo)
self.set_path(path)
self.query = query
self.set_opaque(opaque)
self.fragment=(fragment)
end
if registry
raise InvalidURIError,
"the scheme #{@scheme} does not accept registry part: #{registry} (or bad hostname?)"
end
@scheme&.freeze
self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2)
self.set_port(self.default_port) if self.default_port && !@port
end
#
# Returns the scheme component of the URI.
#
# URI("http://foo/bar/baz").scheme #=> "http"
#
attr_reader :scheme
# Returns the host component of the URI.
#
# URI("http://foo/bar/baz").host #=> "foo"
#
# It returns nil if no host component exists.
#
# URI("mailto:foo@example.org").host #=> nil
#
# The component does not contain the port number.
#
# URI("http://foo:8080/bar/baz").host #=> "foo"
#
# Since IPv6 addresses are wrapped with brackets in URIs,
# this method returns IPv6 addresses wrapped with brackets.
# This form is not appropriate to pass to socket methods such as TCPSocket.open.
# If unwrapped host names are required, use the #hostname method.
#
# URI("http://[::1]/bar/baz").host #=> "[::1]"
# URI("http://[::1]/bar/baz").hostname #=> "::1"
#
attr_reader :host
# Returns the port component of the URI.
#
# URI("http://foo/bar/baz").port #=> 80
# URI("http://foo:8080/bar/baz").port #=> 8080
#
attr_reader :port
def registry # :nodoc:
nil
end
# Returns the path component of the URI.
#
# URI("http://foo/bar/baz").path #=> "/bar/baz"
#
attr_reader :path
# Returns the query component of the URI.
#
# URI("http://foo/bar/baz?search=FooBar").query #=> "search=FooBar"
#
attr_reader :query
# Returns the opaque part of the URI.
#
# URI("mailto:foo@example.org").opaque #=> "foo@example.org"
# URI("http://foo/bar/baz").opaque #=> nil
#
# The portion of the path that does not make use of the slash '/'.
# The path typically refers to an absolute path or an opaque part.
# (See RFC2396 Section 3 and 5.2.)
#
attr_reader :opaque
# Returns the fragment component of the URI.
#
# URI("http://foo/bar/baz?search=FooBar#ponies").fragment #=> "ponies"
#
attr_reader :fragment
# Returns the parser to be used.
#
# Unless a URI::Parser is defined, DEFAULT_PARSER is used.
#
def parser
if !defined?(@parser) || !@parser
DEFAULT_PARSER
else
@parser || DEFAULT_PARSER
end
end
# Replaces self by other URI object.
#
def replace!(oth)
if self.class != oth.class
raise ArgumentError, "expected #{self.class} object"
end
component.each do |c|
self.__send__("#{c}=", oth.__send__(c))
end
end
private :replace!
#
# Components of the URI in the order.
#
def component
self.class.component
end
#
# Checks the scheme +v+ component against the URI::Parser Regexp for :SCHEME.
#
def check_scheme(v)
if v && parser.regexp[:SCHEME] !~ v
raise InvalidComponentError,
"bad component(expected scheme component): #{v}"
end
return true
end
private :check_scheme
# Protected setter for the scheme component +v+.
#
# See also URI::Generic.scheme=.
#
def set_scheme(v)
@scheme = v&.downcase
end
protected :set_scheme
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the scheme component +v+
# (with validation).
#
# See also URI::Generic.check_scheme.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.scheme = "https"
# uri.to_s #=> "https://my.example.com"
#
def scheme=(v)
check_scheme(v)
set_scheme(v)
v
end
#
# Checks the +user+ and +password+.
#
# If +password+ is not provided, then +user+ is
# split, using URI::Generic.split_userinfo, to
# pull +user+ and +password.
#
# See also URI::Generic.check_user, URI::Generic.check_password.
#
def check_userinfo(user, password = nil)
if !password
user, password = split_userinfo(user)
end
check_user(user)
check_password(password, user)
return true
end
private :check_userinfo
#
# Checks the user +v+ component for RFC2396 compliance
# and against the URI::Parser Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
#
def check_user(v)
if @opaque
raise InvalidURIError,
"can not set user with opaque"
end
return v unless v
if parser.regexp[:USERINFO] !~ v
raise InvalidComponentError,
"bad component(expected userinfo component or user component): #{v}"
end
return true
end
private :check_user
#
# Checks the password +v+ component for RFC2396 compliance
# and against the URI::Parser Regexp for :USERINFO.
#
# Can not have a registry or opaque component defined,
# with a user component defined.
#
def check_password(v, user = @user)
if @opaque
raise InvalidURIError,
"can not set password with opaque"
end
return v unless v
if !user
raise InvalidURIError,
"password component depends user component"
end
if parser.regexp[:USERINFO] !~ v
raise InvalidComponentError,
"bad password component"
end
return true
end
private :check_password
#
# Sets userinfo, argument is string like 'name:pass'.
#
def userinfo=(userinfo)
if userinfo.nil?
return nil
end
check_userinfo(*userinfo)
set_userinfo(*userinfo)
# returns userinfo
end
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the +user+ component
# (with validation).
#
# See also URI::Generic.check_user.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://john:S3nsit1ve@my.example.com")
# uri.user = "sam"
# uri.to_s #=> "http://sam:V3ry_S3nsit1ve@my.example.com"
#
def user=(user)
check_user(user)
set_user(user)
# returns user
end
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the +password+ component
# (with validation).
#
# See also URI::Generic.check_password.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://john:S3nsit1ve@my.example.com")
# uri.password = "V3ry_S3nsit1ve"
# uri.to_s #=> "http://john:V3ry_S3nsit1ve@my.example.com"
#
def password=(password)
check_password(password)
set_password(password)
# returns password
end
# Protected setter for the +user+ component, and +password+ if available
# (with validation).
#
# See also URI::Generic.userinfo=.
#
def set_userinfo(user, password = nil)
unless password
user, password = split_userinfo(user)
end
@user = user
@password = password
[@user, @password]
end
protected :set_userinfo
# Protected setter for the user component +v+.
#
# See also URI::Generic.user=.
#
def set_user(v)
set_userinfo(v, nil)
v
end
protected :set_user
# Protected setter for the password component +v+.
#
# See also URI::Generic.password=.
#
def set_password(v)
@password = v
# returns v
end
protected :set_password
# Returns the userinfo +ui+ as <code>[user, password]</code>
# if properly formatted as 'user:password'.
def split_userinfo(ui)
return nil, nil unless ui
user, password = ui.split(':', 2)
return user, password
end
private :split_userinfo
# Escapes 'user:password' +v+ based on RFC 1738 section 3.1.
def escape_userpass(v)
parser.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/
end
private :escape_userpass
# Returns the userinfo, either as 'user' or 'user:password'.
def userinfo
if @user.nil?
nil
elsif @password.nil?
@user
else
@user + ':' + @password
end
end
# Returns the user component (without URI decoding).
def user
@user
end
# Returns the password component (without URI decoding).
def password
@password
end
# Returns the authority info (array of user, password, host and
# port), if any is set. Or returns +nil+.
def authority
return @user, @password, @host, @port if @user || @password || @host || @port
end
# Returns the user component after URI decoding.
def decoded_user
URI.decode_uri_component(@user) if @user
end
# Returns the password component after URI decoding.
def decoded_password
URI.decode_uri_component(@password) if @password
end
#
# Checks the host +v+ component for RFC2396 compliance
# and against the URI::Parser Regexp for :HOST.
#
# Can not have a registry or opaque component defined,
# with a host component defined.
#
def check_host(v)
return v unless v
if @opaque
raise InvalidURIError,
"can not set host with registry or opaque"
elsif parser.regexp[:HOST] !~ v
raise InvalidComponentError,
"bad component(expected host component): #{v}"
end
return true
end
private :check_host
# Protected setter for the host component +v+.
#
# See also URI::Generic.host=.
#
def set_host(v)
@host = v
end
protected :set_host
# Protected setter for the authority info (+user+, +password+, +host+
# and +port+). If +port+ is +nil+, +default_port+ will be set.
#
protected def set_authority(user, password, host, port = nil)
@user, @password, @host, @port = user, password, host, port || self.default_port
end
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the host component +v+
# (with validation).
#
# See also URI::Generic.check_host.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.host = "foo.com"
# uri.to_s #=> "http://foo.com"
#
def host=(v)
check_host(v)
set_host(v)
set_userinfo(nil)
v
end
# Extract the host part of the URI and unwrap brackets for IPv6 addresses.
#
# This method is the same as URI::Generic#host except
# brackets for IPv6 (and future IP) addresses are removed.
#
# uri = URI("http://[::1]/bar")
# uri.hostname #=> "::1"
# uri.host #=> "[::1]"
#
def hostname
v = self.host
v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v
end
# Sets the host part of the URI as the argument with brackets for IPv6 addresses.
#
# This method is the same as URI::Generic#host= except
# the argument can be a bare IPv6 address.
#
# uri = URI("http://foo/bar")
# uri.hostname = "::1"
# uri.to_s #=> "http://[::1]/bar"
#
# If the argument seems to be an IPv6 address,
# it is wrapped with brackets.
#
def hostname=(v)
v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':')
self.host = v
end
#
# Checks the port +v+ component for RFC2396 compliance
# and against the URI::Parser Regexp for :PORT.
#
# Can not have a registry or opaque component defined,
# with a port component defined.
#
def check_port(v)
return v unless v
if @opaque
raise InvalidURIError,
"can not set port with registry or opaque"
elsif !v.kind_of?(Integer) && parser.regexp[:PORT] !~ v
raise InvalidComponentError,
"bad component(expected port component): #{v.inspect}"
end
return true
end
private :check_port
# Protected setter for the port component +v+.
#
# See also URI::Generic.port=.
#
def set_port(v)
v = v.empty? ? nil : v.to_i unless !v || v.kind_of?(Integer)
@port = v
end
protected :set_port
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the port component +v+
# (with validation).
#
# See also URI::Generic.check_port.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.port = 8080
# uri.to_s #=> "http://my.example.com:8080"
#
def port=(v)
check_port(v)
set_port(v)
set_userinfo(nil)
port
end
def check_registry(v) # :nodoc:
raise InvalidURIError, "can not set registry"
end
private :check_registry
def set_registry(v) #:nodoc:
raise InvalidURIError, "can not set registry"
end
protected :set_registry
def registry=(v)
raise InvalidURIError, "can not set registry"
end
#
# Checks the path +v+ component for RFC2396 compliance
# and against the URI::Parser Regexp
# for :ABS_PATH and :REL_PATH.
#
# Can not have a opaque component defined,
# with a path component defined.
#
def check_path(v)
# raise if both hier and opaque are not nil, because:
# absoluteURI = scheme ":" ( hier_part | opaque_part )
# hier_part = ( net_path | abs_path ) [ "?" query ]
if v && @opaque
raise InvalidURIError,
"path conflicts with opaque"
end
# If scheme is ftp, path may be relative.
# See RFC 1738 section 3.2.2, and RFC 2396.
if @scheme && @scheme != "ftp"
if v && v != '' && parser.regexp[:ABS_PATH] !~ v
raise InvalidComponentError,
"bad component(expected absolute path component): #{v}"
end
else
if v && v != '' && parser.regexp[:ABS_PATH] !~ v &&
parser.regexp[:REL_PATH] !~ v
raise InvalidComponentError,
"bad component(expected relative path component): #{v}"
end
end
return true
end
private :check_path
# Protected setter for the path component +v+.
#
# See also URI::Generic.path=.
#
def set_path(v)
@path = v
end
protected :set_path
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the path component +v+
# (with validation).
#
# See also URI::Generic.check_path.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com/pub/files")
# uri.path = "/faq/"
# uri.to_s #=> "http://my.example.com/faq/"
#
def path=(v)
check_path(v)
set_path(v)
v
end
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the query component +v+.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com/?id=25")
# uri.query = "id=1"
# uri.to_s #=> "http://my.example.com/?id=1"
#
def query=(v)
return @query = nil unless v
raise InvalidURIError, "query conflicts with opaque" if @opaque
x = v.to_str
v = x.dup if x.equal? v
v.encode!(Encoding::UTF_8) rescue nil
v.delete!("\t\r\n")
v.force_encoding(Encoding::ASCII_8BIT)
raise InvalidURIError, "invalid percent escape: #{$1}" if /(%\H\H)/n.match(v)
v.gsub!(/(?!%\h\h|[!$-&(-;=?-_a-~])./n.freeze){'%%%02X' % $&.ord}
v.force_encoding(Encoding::US_ASCII)
@query = v
end
#
# Checks the opaque +v+ component for RFC2396 compliance and
# against the URI::Parser Regexp for :OPAQUE.
#
# Can not have a host, port, user, or path component defined,
# with an opaque component defined.
#
def check_opaque(v)
return v unless v
# raise if both hier and opaque are not nil, because:
# absoluteURI = scheme ":" ( hier_part | opaque_part )
# hier_part = ( net_path | abs_path ) [ "?" query ]
if @host || @port || @user || @path # userinfo = @user + ':' + @password
raise InvalidURIError,
"can not set opaque with host, port, userinfo or path"
elsif v && parser.regexp[:OPAQUE] !~ v
raise InvalidComponentError,
"bad component(expected opaque component): #{v}"
end
return true
end
private :check_opaque
# Protected setter for the opaque component +v+.
#
# See also URI::Generic.opaque=.
#
def set_opaque(v)
@opaque = v
end
protected :set_opaque
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the opaque component +v+
# (with validation).
#
# See also URI::Generic.check_opaque.
#
def opaque=(v)
check_opaque(v)
set_opaque(v)
v
end
#
# Checks the fragment +v+ component against the URI::Parser Regexp for :FRAGMENT.
#
#
# == Args
#
# +v+::
# String
#
# == Description
#
# Public setter for the fragment component +v+
# (with validation).
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com/?id=25#time=1305212049")
# uri.fragment = "time=1305212086"
# uri.to_s #=> "http://my.example.com/?id=25#time=1305212086"
#
def fragment=(v)
return @fragment = nil unless v
x = v.to_str
v = x.dup if x.equal? v
v.encode!(Encoding::UTF_8) rescue nil
v.delete!("\t\r\n")
v.force_encoding(Encoding::ASCII_8BIT)
v.gsub!(/(?!%\h\h|[!-~])./n){'%%%02X' % $&.ord}
v.force_encoding(Encoding::US_ASCII)
@fragment = v
end
#
# Returns true if URI is hierarchical.
#
# == Description
#
# URI has components listed in order of decreasing significance from left to right,
# see RFC3986 https://tools.ietf.org/html/rfc3986 1.2.3.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com/")
# uri.hierarchical?
# #=> true
# uri = URI.parse("mailto:joe@example.com")
# uri.hierarchical?
# #=> false
#
def hierarchical?
if @path
true
else
false
end
end
#
# Returns true if URI has a scheme (e.g. http:// or https://) specified.
#
def absolute?
if @scheme
true
else
false
end
end
alias absolute absolute?
#
# Returns true if URI does not have a scheme (e.g. http:// or https://) specified.
#
def relative?
!absolute?
end
#
# Returns an Array of the path split on '/'.
#
def split_path(path)
path.split("/", -1)
end
private :split_path
#
# Merges a base path +base+, with relative path +rel+,
# returns a modified base path.
#
def merge_path(base, rel)
# RFC2396, Section 5.2, 5)
# RFC2396, Section 5.2, 6)
base_path = split_path(base)
rel_path = split_path(rel)
# RFC2396, Section 5.2, 6), a)
base_path << '' if base_path.last == '..'
while i = base_path.index('..')
base_path.slice!(i - 1, 2)
end
if (first = rel_path.first) and first.empty?
base_path.clear
rel_path.shift
end
# RFC2396, Section 5.2, 6), c)
# RFC2396, Section 5.2, 6), d)
rel_path.push('') if rel_path.last == '.' || rel_path.last == '..'
rel_path.delete('.')
# RFC2396, Section 5.2, 6), e)
tmp = []
rel_path.each do |x|
if x == '..' &&
!(tmp.empty? || tmp.last == '..')
tmp.pop
else
tmp << x
end
end
add_trailer_slash = !tmp.empty?
if base_path.empty?
base_path = [''] # keep '/' for root directory
elsif add_trailer_slash
base_path.pop
end
while x = tmp.shift
if x == '..'
# RFC2396, Section 4
# a .. or . in an absolute path has no special meaning
base_path.pop if base_path.size > 1
else
# if x == '..'
# valid absolute (but abnormal) path "/../..."
# else
# valid absolute path
# end
base_path << x
tmp.each {|t| base_path << t}
add_trailer_slash = false
break
end
end
base_path.push('') if add_trailer_slash
return base_path.join('/')
end
private :merge_path
#
# == Args
#
# +oth+::
# URI or String
#
# == Description
#
# Destructive form of #merge.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.merge!("/main.rbx?page=1")
# uri.to_s # => "http://my.example.com/main.rbx?page=1"
#
def merge!(oth)
t = merge(oth)
if self == t
nil
else
replace!(t)
self
end
end
#
# == Args
#
# +oth+::
# URI or String
#
# == Description
#
# Merges two URIs.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.merge("/main.rbx?page=1")
# # => "http://my.example.com/main.rbx?page=1"
#
def merge(oth)
rel = parser.__send__(:convert_to_uri, oth)
if rel.absolute?
#raise BadURIError, "both URI are absolute" if absolute?
# hmm... should return oth for usability?
return rel
end
unless self.absolute?
raise BadURIError, "both URI are relative"
end
base = self.dup
authority = rel.authority
# RFC2396, Section 5.2, 2)
if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
base.fragment=(rel.fragment) if rel.fragment
return base
end
base.query = nil
base.fragment=(nil)
# RFC2396, Section 5.2, 4)
if authority
base.set_authority(*authority)
base.set_path(rel.path)
elsif base.path && rel.path
base.set_path(merge_path(base.path, rel.path))
end
# RFC2396, Section 5.2, 7)
base.query = rel.query if rel.query
base.fragment=(rel.fragment) if rel.fragment
return base
end # merge
alias + merge
# :stopdoc:
def route_from_path(src, dst)
case dst
when src
# RFC2396, Section 4.2
return ''
when %r{(?:\A|/)\.\.?(?:/|\z)}
# dst has abnormal absolute path,
# like "/./", "/../", "/x/../", ...
return dst.dup
end
src_path = src.scan(%r{[^/]*/})
dst_path = dst.scan(%r{[^/]*/?})
# discard same parts
while !dst_path.empty? && dst_path.first == src_path.first
src_path.shift
dst_path.shift
end
tmp = dst_path.join
# calculate
if src_path.empty?
if tmp.empty?
return './'
elsif dst_path.first.include?(':') # (see RFC2396 Section 5)
return './' + tmp
else
return tmp
end
end
return '../' * src_path.size + tmp
end
private :route_from_path
# :startdoc:
# :stopdoc:
def route_from0(oth)
oth = parser.__send__(:convert_to_uri, oth)
if self.relative?
raise BadURIError,
"relative URI: #{self}"
end
if oth.relative?
raise BadURIError,
"relative URI: #{oth}"
end
if self.scheme != oth.scheme
return self, self.dup
end
rel = URI::Generic.new(nil, # it is relative URI
self.userinfo, self.host, self.port,
nil, self.path, self.opaque,
self.query, self.fragment, parser)
if rel.userinfo != oth.userinfo ||
rel.host.to_s.downcase != oth.host.to_s.downcase ||
rel.port != oth.port
if self.userinfo.nil? && self.host.nil?
return self, self.dup
end
rel.set_port(nil) if rel.port == oth.default_port
return rel, rel
end
rel.set_userinfo(nil)
rel.set_host(nil)
rel.set_port(nil)
if rel.path && rel.path == oth.path
rel.set_path('')
rel.query = nil if rel.query == oth.query
return rel, rel
elsif rel.opaque && rel.opaque == oth.opaque
rel.set_opaque('')
rel.query = nil if rel.query == oth.query
return rel, rel
end
# you can modify `rel', but can not `oth'.
return oth, rel
end
private :route_from0
# :startdoc:
#
# == Args
#
# +oth+::
# URI or String
#
# == Description
#
# Calculates relative path from oth to self.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse('http://my.example.com/main.rbx?page=1')
# uri.route_from('http://my.example.com')
# #=> #<URI::Generic /main.rbx?page=1>
#
def route_from(oth)
# you can modify `rel', but can not `oth'.
begin
oth, rel = route_from0(oth)
rescue
raise $!.class, $!.message
end
if oth == rel
return rel
end
rel.set_path(route_from_path(oth.path, self.path))
if rel.path == './' && self.query
# "./?foo" -> "?foo"
rel.set_path('')
end
return rel
end
alias - route_from
#
# == Args
#
# +oth+::
# URI or String
#
# == Description
#
# Calculates relative path to oth from self.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse('http://my.example.com')
# uri.route_to('http://my.example.com/main.rbx?page=1')
# #=> #<URI::Generic /main.rbx?page=1>
#
def route_to(oth)
parser.__send__(:convert_to_uri, oth).route_from(self)
end
#
# Returns normalized URI.
#
# require 'uri'
#
# URI("HTTP://my.EXAMPLE.com").normalize
# #=> #<URI::HTTP http://my.example.com/>
#
# Normalization here means:
#
# * scheme and host are converted to lowercase,
# * an empty path component is set to "/".
#
def normalize
uri = dup
uri.normalize!
uri
end
#
# Destructive version of #normalize.
#
def normalize!
if path&.empty?
set_path('/')
end
if scheme && scheme != scheme.downcase
set_scheme(self.scheme.downcase)
end
if host && host != host.downcase
set_host(self.host.downcase)
end
end
#
# Constructs String from URI.
#
def to_s
str = ''.dup
if @scheme
str << @scheme
str << ':'
end
if @opaque
str << @opaque
else
if @host || %w[file postgres].include?(@scheme)
str << '//'
end
if self.userinfo
str << self.userinfo
str << '@'
end
if @host
str << @host
end
if @port && @port != self.default_port
str << ':'
str << @port.to_s
end
str << @path
if @query
str << '?'
str << @query
end
end
if @fragment
str << '#'
str << @fragment
end
str
end
#
# Compares two URIs.
#
def ==(oth)
if self.class == oth.class
self.normalize.component_ary == oth.normalize.component_ary
else
false
end
end
def hash
self.component_ary.hash
end
def eql?(oth)
self.class == oth.class &&
parser == oth.parser &&
self.component_ary.eql?(oth.component_ary)
end
=begin
--- URI::Generic#===(oth)
=end
# def ===(oth)
# raise NotImplementedError
# end
=begin
=end
# Returns an Array of the components defined from the COMPONENT Array.
def component_ary
component.collect do |x|
self.__send__(x)
end
end
protected :component_ary
# == Args
#
# +components+::
# Multiple Symbol arguments defined in URI::HTTP.
#
# == Description
#
# Selects specified components from URI.
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse('http://myuser:mypass@my.example.com/test.rbx')
# uri.select(:userinfo, :host, :path)
# # => ["myuser:mypass", "my.example.com", "/test.rbx"]
#
def select(*components)
components.collect do |c|
if component.include?(c)
self.__send__(c)
else
raise ArgumentError,
"expected of components of #{self.class} (#{self.class.component.join(', ')})"
end
end
end
def inspect
"#<#{self.class} #{self}>"
end
#
# == Args
#
# +v+::
# URI or String
#
# == Description
#
# Attempts to parse other URI +oth+,
# returns [parsed_oth, self].
#
# == Usage
#
# require 'uri'
#
# uri = URI.parse("http://my.example.com")
# uri.coerce("http://foo.com")
# #=> [#<URI::HTTP http://foo.com>, #<URI::HTTP http://my.example.com>]
#
def coerce(oth)
case oth
when String
oth = parser.parse(oth)
else
super
end
return oth, self
end
# Returns a proxy URI.
# The proxy URI is obtained from environment variables such as http_proxy,
# ftp_proxy, no_proxy, etc.
# If there is no proper proxy, nil is returned.
#
# If the optional parameter +env+ is specified, it is used instead of ENV.
#
# Note that capitalized variables (HTTP_PROXY, FTP_PROXY, NO_PROXY, etc.)
# are examined, too.
#
# But http_proxy and HTTP_PROXY is treated specially under CGI environment.
# It's because HTTP_PROXY may be set by Proxy: header.
# So HTTP_PROXY is not used.
# http_proxy is not used too if the variable is case insensitive.
# CGI_HTTP_PROXY can be used instead.
def find_proxy(env=ENV)
raise BadURIError, "relative URI: #{self}" if self.relative?
name = self.scheme.downcase + '_proxy'
proxy_uri = nil
if name == 'http_proxy' && env.include?('REQUEST_METHOD') # CGI?
# HTTP_PROXY conflicts with *_proxy for proxy settings and
# HTTP_* for header information in CGI.
# So it should be careful to use it.
pairs = env.reject {|k, v| /\Ahttp_proxy\z/i !~ k }
case pairs.length
when 0 # no proxy setting anyway.
proxy_uri = nil
when 1
k, _ = pairs.shift
if k == 'http_proxy' && env[k.upcase] == nil
# http_proxy is safe to use because ENV is case sensitive.
proxy_uri = env[name]
else
proxy_uri = nil
end
else # http_proxy is safe to use because ENV is case sensitive.
proxy_uri = env.to_hash[name]
end
if !proxy_uri
# Use CGI_HTTP_PROXY. cf. libwww-perl.
proxy_uri = env["CGI_#{name.upcase}"]
end
elsif name == 'http_proxy'
if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost']
p_port = ENV_JAVA['http.proxyPort']
if p_user = ENV_JAVA['http.proxyUser']
p_pass = ENV_JAVA['http.proxyPass']
proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}"
else
proxy_uri = "http://#{p_addr}:#{p_port}"
end
else
unless proxy_uri = env[name]
if proxy_uri = env[name.upcase]
warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1
end
end
end
else
proxy_uri = env[name] || env[name.upcase]
end
if proxy_uri.nil? || proxy_uri.empty?
return nil
end
if self.hostname
begin
addr = IPSocket.getaddress(self.hostname)
return nil if /\A127\.|\A::1\z/ =~ addr
rescue SocketError
end
end
name = 'no_proxy'
if no_proxy = env[name] || env[name.upcase]
return nil unless URI::Generic.use_proxy?(self.hostname, addr, self.port, no_proxy)
end
URI.parse(proxy_uri)
end
def self.use_proxy?(hostname, addr, port, no_proxy) # :nodoc:
hostname = hostname.downcase
dothostname = ".#{hostname}"
no_proxy.scan(/([^:,\s]+)(?::(\d+))?/) {|p_host, p_port|
if !p_port || port == p_port.to_i
if p_host.start_with?('.')
return false if hostname.end_with?(p_host.downcase)
else
return false if dothostname.end_with?(".#{p_host.downcase}")
end
if addr
begin
return false if IPAddr.new(p_host).include?(addr)
rescue IPAddr::InvalidAddressError
next
end
end
end
}
true
end
end
end
share/ruby/uri/wss.rb 0000644 00000001023 15173547336 0010601 0 ustar 00 # frozen_string_literal: false
# = uri/wss.rb
#
# Author:: Matt Muller <mamuller@amazon.com>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'ws'
module URI
# The default port for WSS URIs is 443, and the scheme is 'wss:' rather
# than 'ws:'. Other than that, WSS URIs are identical to WS URIs;
# see URI::WS.
class WSS < WS
# A Default port of 443 for URI::WSS
DEFAULT_PORT = 443
end
register_scheme 'WSS', WSS
end
share/ruby/uri/https.rb 0000644 00000001056 15173547336 0011135 0 ustar 00 # 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.
#
# See URI for general documentation
#
require_relative '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
register_scheme 'HTTPS', HTTPS
end
share/ruby/uri/ftp.rb 0000644 00000016033 15173547336 0010565 0 ustar 00 # 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.
#
# See URI for general documentation
#
require_relative '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 <code>[userinfo, host, port, path, typecode]</code>.
#
# If the path supplied is absolute, it will be escaped in order to
# make it absolute in the URI.
#
# Examples:
#
# require 'uri'
#
# uri1 = URI::FTP.build(['user:password', 'ftp.example.com', nil,
# '/path/file.zip', 'i'])
# uri1.to_s # => "ftp://user:password@ftp.example.com/%2Fpath/file.zip;type=i"
#
# uri2 = URI::FTP.build({:host => 'ftp.example.com',
# :path => 'ruby/src'})
# 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 +true+ or +false+.
#
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 ftp://john@ftp.example.com/my_file.img>
# uri.typecode = "i"
# uri
# #=> #<URI::FTP 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:
#
# <code>ftp://ftp.example.com/pub/ruby</code>
#
# The above URI indicates that the client should connect to
# ftp.example.com then cd to 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:
#
# <code>ftp://ftp.example.com/%2Fpub/ruby</code>
#
# 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
register_scheme 'FTP', FTP
end
share/ruby/uri/rfc3986_parser.rb 0000644 00000013525 15173547336 0012457 0 ustar 00 # frozen_string_literal: false
module URI
class RFC3986_Parser # :nodoc:
# URI defined in RFC3986
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.inspect}"
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.inspect}"
end
end
def parse(uri) # :nodoc:
URI.for(*self.split(uri), self)
end
def join(*uris) # :nodoc:
uris[0] = convert_to_uri(uris[0])
uris.inject :merge
end
@@to_s = Kernel.instance_method(:to_s)
if @@to_s.respond_to?(:bind_call)
def inspect
@@to_s.bind_call(self)
end
else
def inspect
@@to_s.bind(self).call
end
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
share/ruby/uri/rfc2396_parser.rb 0000644 00000042046 15173547336 0012451 0 ustar 00 # frozen_string_literal: false
#--
# = uri/common.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# 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 http://example.jp/%uABCD>
# URI.parse(u.to_s) #=> raises URI::InvalidURIError
#
# s = "http://example.com/ABCD"
# u1 = p.parse(s) #=> #<URI::HTTP http://example.com/ABCD>
# u2 = URI.parse(s) #=> #<URI::HTTP 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
# (File, 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 ldap://ldap.example.com/dc=example?user=john>
#
def parse(uri)
URI.for(*self.split(uri), self)
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, escaped )
#
# == Args
#
# +str+::
# String to remove escapes from
# +escaped+::
# Regexp to apply. Defaults to +self.regexp[:ESCAPED]+
#
# == Description
#
# Removes escapes from +str+.
#
def unescape(str, escaped = @regexp[:ESCAPED])
enc = str.encoding
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
str.gsub(escaped) { [$&[1, 2]].pack('H2').force_encoding(enc) }
end
@@to_s = Kernel.instance_method(:to_s)
if @@to_s.respond_to?(:bind_call)
def inspect
@@to_s.bind_call(self)
end
else
def inspect
@@to_s.bind(self).call
end
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
share/ruby/uri/common.rb 0000644 00000047446 15173547336 0011300 0 ustar 00 # frozen_string_literal: true
#--
# = uri/common.rb
#
# Author:: Akira Yamada <akira@ruby-lang.org>
# License::
# You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative "rfc2396_parser"
require_relative "rfc3986_parser"
module URI
include RFC2396_REGEXP
REGEXP = RFC2396_REGEXP
Parser = RFC2396_Parser
RFC3986_PARSER = RFC3986_Parser.new
Ractor.make_shareable(RFC3986_PARSER) if defined?(Ractor)
RFC2396_PARSER = RFC2396_Parser.new
Ractor.make_shareable(RFC2396_PARSER) if defined?(Ractor)
# 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
Ractor.make_shareable(DEFAULT_PARSER) if defined?(Ractor)
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 Schemes
end
private_constant :Schemes
#
# Register the given +klass+ to be instantiated when parsing URLs with the given +scheme+.
# Note that currently only schemes which after .upcase are valid constant names
# can be registered (no -/+/. allowed).
#
def self.register_scheme(scheme, klass)
Schemes.const_set(scheme.to_s.upcase, klass)
end
# Returns a Hash of the defined schemes.
def self.scheme_list
Schemes.constants.map { |name|
[name.to_s.upcase, Schemes.const_get(name)]
}.to_h
end
INITIAL_SCHEMES = scheme_list
private_constant :INITIAL_SCHEMES
Ractor.make_shareable(INITIAL_SCHEMES) if defined?(Ractor)
#
# Construct a URI instance, using the scheme to detect the appropriate class
# from +URI.scheme_list+.
#
def self.for(scheme, *arguments, default: Generic)
const_name = scheme.to_s.upcase
uri_class = INITIAL_SCHEMES[const_name]
uri_class ||= if /\A[A-Z]\w*\z/.match?(const_name) && Schemes.const_defined?(const_name, false)
Schemes.const_get(const_name, false)
end
uri_class ||= default
return uri_class.new(scheme, *arguments)
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'
#
# 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/")
# # => #<URI::HTTP http://www.ruby-lang.org/>
# uri.scheme
# # => "http"
# 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'
#
# URI.join("http://example.com/","main.rbx")
# # => #<URI::HTTP http://example.com/main.rbx>
#
# URI.join('http://example.com', 'foo')
# # => #<URI::HTTP http://example.com/foo>
#
# URI.join('http://example.com', '/foo', '/bar')
# # => #<URI::HTTP http://example.com/bar>
#
# URI.join('http://example.com', '/foo', 'bar')
# # => #<URI::HTTP http://example.com/bar>
#
# URI.join('http://example.com', '/foo/', 'bar')
# # => #<URI::HTTP 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 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 its 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
TBLENCURICOMP_ = TBLENCWWWCOMP_.dup.freeze
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
# Encodes 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
# https://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)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
end
# Decodes 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)
_decode_uri_component(/\+|%\h\h/, str, enc)
end
# Encodes +str+ using URL encoding
#
# This encodes SP to %20 instead of +.
def self.encode_uri_component(str, enc=nil)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
end
# Decodes given +str+ of URL-encoded data.
#
# This does not decode + to SP.
def self.decode_uri_component(str, enc=Encoding::UTF_8)
_decode_uri_component(/%\h\h/, str, enc)
end
def self._encode_uri_component(regexp, table, str, enc)
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!(regexp, table)
str.force_encoding(Encoding::US_ASCII)
end
private_class_method :_encode_uri_component
def self._decode_uri_component(regexp, str, enc)
raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str)
str.b.gsub(regexp, TBLDECWWWCOMP_).force_encoding(enc)
end
private_class_method :_decode_uri_component
# Generates 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 calling 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 https://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
# Decodes URL-encoded form data from given +str+.
#
# This decodes application/x-www-form-urlencoded data
# and returns an array of key-value arrays.
#
# This refers http://url.spec.whatwg.org/#concept-urlencoded-parser,
# so this supports only &-separator, and doesn't support ;-separator.
#
# ary = URI.decode_www_form("a=1&a=2&b=3")
# ary #=> [['a', '1'], ['a', '2'], ['b', '3']]
# ary.assoc('a').last #=> '1'
# ary.assoc('b').last #=> '3'
# ary.rassoc('a').last #=> '2'
# 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:
Ractor.make_shareable(WEB_ENCODINGS_) if defined?(Ractor)
# :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 an 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
share/ruby/uri/version.rb 0000644 00000000226 15173547336 0011456 0 ustar 00 module URI
# :stopdoc:
VERSION_CODE = '001205'.freeze
VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
# :startdoc:
end
share/ruby/uri/ldap.rb 0000644 00000013437 15173547336 0010721 0 ustar 00 # 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.
#
# See URI for general documentation
#
require_relative '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 - subtrees, all entries at all levels
#
SCOPE = [
SCOPE_ONE = 'one',
SCOPE_SUB = 'sub',
SCOPE_BASE = 'base',
].freeze
#
# == Description
#
# Creates 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 <code>[host, port, dn, attributes, scope, filter, extensions]</code>.
#
# Example:
#
# uri = URI::LDAP.build({:host => 'ldap.example.com',
# :dn => '/dc=example'})
#
# uri = 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
#
# Creates 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, nil,
# "/dc=example;dc=com", nil, "query", 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
raise InvalidURIError, 'bad LDAP URL' unless @path
@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
register_scheme 'LDAP', LDAP
end
share/ruby/uri/ws.rb 0000644 00000004365 15173547336 0010432 0 ustar 00 # frozen_string_literal: false
# = uri/ws.rb
#
# Author:: Matt Muller <mamuller@amazon.com>
# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# The syntax of WS URIs is defined in RFC6455 section 3.
#
# Note that the Ruby URI library allows WS 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 WS < Generic
# A Default port of 80 for URI::WS.
DEFAULT_PORT = 80
# An Array of the available components for URI::WS.
COMPONENT = %i[
scheme
userinfo host port
path
query
].freeze
#
# == Description
#
# Creates a new URI::WS object from components, with syntax checking.
#
# The components accepted are userinfo, host, port, path, and query.
#
# 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 <code>[userinfo, host, port, path, query]</code>.
#
# Example:
#
# uri = URI::WS.build(host: 'www.example.com', path: '/foo/bar')
#
# uri = URI::WS.build([nil, "www.example.com", nil, "/path", "query"])
#
# Currently, if passed userinfo components this method generates
# invalid WS 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 a WS URI, 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:
#
# uri = URI::WS.build(path: '/foo/bar', query: 'test=true')
# uri.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
register_scheme 'WS', WS
end
share/ruby/uri/ldaps.rb 0000644 00000000777 15173547336 0011107 0 ustar 00 # 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_relative '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
register_scheme 'LDAPS', LDAPS
end
share/ruby/uri/mailto.rb 0000644 00000017521 15173547336 0011264 0 ustar 00 # 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.
#
# See URI for general documentation
#
require_relative 'generic'
module URI
#
# RFC6068, the mailto URL scheme.
#
class MailTo < Generic
include RFC2396_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
# https://html.spec.whatwg.org/multipage/input.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 <code>[to, headers]</code>.
#
# 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
# <code>"subject=subscribe&cc=address"</code>, or as an Array of Arrays
# like <code>[['subject', 'subscribe'], ['cc', 'address']]</code>.
#
# Examples:
#
# require 'uri'
#
# m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
# m1.to_s # => "mailto:joe@example.com?subject=Ruby"
#
# m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
# m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com"
#
# m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
# 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
# Checks 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
# Checks 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
register_scheme 'MAILTO', MailTo
end
share/ruby/uri/file.rb 0000644 00000004370 15173547336 0010714 0 ustar 00 # frozen_string_literal: true
require_relative 'generic'
module URI
#
# The "file" URI is defined by RFC8089.
#
class File < Generic
# A Default port of nil for URI::File.
DEFAULT_PORT = nil
#
# An Array of the available components for URI::File.
#
COMPONENT = [
:scheme,
:host,
:path
].freeze
#
# == Description
#
# Creates a new URI::File object from components, with syntax checking.
#
# The components accepted are +host+ and +path+.
#
# 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 <code>[host, path]</code>.
#
# A path from e.g. the File class should be escaped before
# being passed.
#
# Examples:
#
# require 'uri'
#
# uri1 = URI::File.build(['host.example.com', '/path/file.zip'])
# uri1.to_s # => "file://host.example.com/path/file.zip"
#
# uri2 = URI::File.build({:host => 'host.example.com',
# :path => '/ruby/src'})
# uri2.to_s # => "file://host.example.com/ruby/src"
#
# uri3 = URI::File.build({:path => URI::escape('/path/my file.txt')})
# uri3.to_s # => "file:///path/my%20file.txt"
#
def self.build(args)
tmp = Util::make_components_hash(self, args)
super(tmp)
end
# Protected setter for the host component +v+.
#
# See also URI::Generic.host=.
#
def set_host(v)
v = "" if v.nil? || v == "localhost"
@host = v
end
# do nothing
def set_port(v)
end
# raise InvalidURIError
def check_userinfo(user)
raise URI::InvalidURIError, "can not set userinfo for file URI"
end
# raise InvalidURIError
def check_user(user)
raise URI::InvalidURIError, "can not set user for file URI"
end
# raise InvalidURIError
def check_password(user)
raise URI::InvalidURIError, "can not set password for file URI"
end
# do nothing
def set_userinfo(v)
end
# do nothing
def set_user(v)
end
# do nothing
def set_password(v)
end
end
register_scheme 'FILE', File
end
share/ruby/uri/http.rb 0000644 00000007215 15173547336 0010755 0 ustar 00 # 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.
#
# See URI for general documentation
#
require_relative '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
#
# Creates 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 <code>[userinfo, host, port, path, query, fragment]</code>.
#
# Example:
#
# uri = URI::HTTP.build(host: 'www.example.com', path: '/foo/bar')
#
# uri = 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:
#
# uri = URI::HTTP.build(path: '/foo/bar', query: 'test=true')
# uri.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
#
# == Description
#
# Returns the authority for an HTTP uri, as defined in
# https://datatracker.ietf.org/doc/html/rfc3986/#section-3.2.
#
#
# Example:
#
# URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').authority #=> "www.example.com"
# URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').authority #=> "www.example.com:8000"
# URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').authority #=> "www.example.com"
#
def authority
if port == default_port
host
else
"#{host}:#{port}"
end
end
#
# == Description
#
# Returns the origin for an HTTP uri, as defined in
# https://datatracker.ietf.org/doc/html/rfc6454.
#
#
# Example:
#
# URI::HTTP.build(host: 'www.example.com', path: '/foo/bar').origin #=> "http://www.example.com"
# URI::HTTP.build(host: 'www.example.com', port: 8000, path: '/foo/bar').origin #=> "http://www.example.com:8000"
# URI::HTTP.build(host: 'www.example.com', port: 80, path: '/foo/bar').origin #=> "http://www.example.com"
# URI::HTTPS.build(host: 'www.example.com', path: '/foo/bar').origin #=> "https://www.example.com"
#
def origin
"#{scheme}://#{authority}"
end
end
register_scheme 'HTTP', HTTP
end
share/ruby/open3/version.rb 0000644 00000000045 15173547336 0011702 0 ustar 00 module Open3
VERSION = "0.1.2"
end
share/ruby/ripper.rb 0000644 00000004676 15173547336 0010510 0 ustar 00 # frozen_string_literal: true
require 'ripper/core'
require 'ripper/lexer'
require 'ripper/filter'
require 'ripper/sexp'
# Ripper is a Ruby script parser.
#
# You can get information from the parser with event-based style.
# Information such as abstract syntax trees or simple lexical analysis of the
# Ruby program.
#
# == Usage
#
# Ripper provides an easy interface for parsing your program into a symbolic
# expression tree (or S-expression).
#
# Understanding the output of the parser may come as a challenge, it's
# recommended you use PP to format the output for legibility.
#
# require 'ripper'
# require 'pp'
#
# pp Ripper.sexp('def hello(world) "Hello, #{world}!"; end')
# #=> [:program,
# [[:def,
# [:@ident, "hello", [1, 4]],
# [:paren,
# [:params, [[:@ident, "world", [1, 10]]], nil, nil, nil, nil, nil, nil]],
# [:bodystmt,
# [[:string_literal,
# [:string_content,
# [:@tstring_content, "Hello, ", [1, 18]],
# [:string_embexpr, [[:var_ref, [:@ident, "world", [1, 27]]]]],
# [:@tstring_content, "!", [1, 33]]]]],
# nil,
# nil,
# nil]]]]
#
# You can see in the example above, the expression starts with +:program+.
#
# From here, a method definition at +:def+, followed by the method's identifier
# <code>:@ident</code>. After the method's identifier comes the parentheses
# +:paren+ and the method parameters under +:params+.
#
# Next is the method body, starting at +:bodystmt+ (+stmt+ meaning statement),
# which contains the full definition of the method.
#
# In our case, we're simply returning a String, so next we have the
# +:string_literal+ expression.
#
# Within our +:string_literal+ you'll notice two <code>@tstring_content</code>,
# this is the literal part for <code>Hello, </code> and <code>!</code>. Between
# the two <code>@tstring_content</code> statements is a +:string_embexpr+,
# where _embexpr_ is an embedded expression. Our expression consists of a local
# variable, or +var_ref+, with the identifier (<code>@ident</code>) of +world+.
#
# == Resources
#
# * {Ruby Inside}[http://www.rubyinside.com/using-ripper-to-see-how-ruby-is-parsing-your-code-5270.html]
#
# == Requirements
#
# * ruby 1.9 (support CVS HEAD only)
# * bison 1.28 or later (Other yaccs do not work)
#
# == License
#
# Ruby License.
#
# - Minero Aoki
# - aamine@loveruby.net
# - http://i.loveruby.net
class Ripper; end
share/ruby/net/https.rb 0000644 00000001023 15173547336 0011116 0 ustar 00 # frozen_string_literal: true
=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_relative 'http'
require 'openssl'
share/ruby/net/http/requests.rb 0000644 00000031537 15173547337 0012624 0 ustar 00 # frozen_string_literal: true
# HTTP/1.1 methods --- RFC2616
# \Class for representing
# {HTTP method GET}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#GET_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: optional.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
#
# Related:
#
# - Net::HTTP.get: sends +GET+ request, returns response body.
# - Net::HTTP#get: sends +GET+ request, returns response object.
#
class Net::HTTP::Get < Net::HTTPRequest
METHOD = 'GET'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method HEAD}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#HEAD_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Head.new(uri) # => #<Net::HTTP::Head HEAD>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: optional.
# - Response body: no.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
#
# Related:
#
# - Net::HTTP#head: sends +HEAD+ request, returns response object.
#
class Net::HTTP::Head < Net::HTTPRequest
METHOD = 'HEAD'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = false
end
# \Class for representing
# {HTTP method POST}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#POST_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# uri.path = '/posts'
# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST>
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.content_type = 'application/json'
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: yes.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: yes.
#
# Related:
#
# - Net::HTTP.post: sends +POST+ request, returns response object.
# - Net::HTTP#post: sends +POST+ request, returns response object.
#
class Net::HTTP::Post < Net::HTTPRequest
METHOD = 'POST'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method PUT}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PUT_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# uri.path = '/posts'
# req = Net::HTTP::Put.new(uri) # => #<Net::HTTP::Put PUT>
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.content_type = 'application/json'
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: yes.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
#
class Net::HTTP::Put < Net::HTTPRequest
METHOD = 'PUT'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method DELETE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#DELETE_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# uri.path = '/posts/1'
# req = Net::HTTP::Delete.new(uri) # => #<Net::HTTP::Delete DELETE>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: optional.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
#
# Related:
#
# - Net::HTTP#delete: sends +DELETE+ request, returns response object.
#
class Net::HTTP::Delete < Net::HTTPRequest
METHOD = 'DELETE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method OPTIONS}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#OPTIONS_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Options.new(uri) # => #<Net::HTTP::Options OPTIONS>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: optional.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
#
# Related:
#
# - Net::HTTP#options: sends +OPTIONS+ request, returns response object.
#
class Net::HTTP::Options < Net::HTTPRequest
METHOD = 'OPTIONS'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method TRACE}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#TRACE_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Trace.new(uri) # => #<Net::HTTP::Trace TRACE>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: no.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: yes.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: yes.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
#
# Related:
#
# - Net::HTTP#trace: sends +TRACE+ request, returns response object.
#
class Net::HTTP::Trace < Net::HTTPRequest
METHOD = 'TRACE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {HTTP method PATCH}[https://en.wikipedia.org/w/index.php?title=Hypertext_Transfer_Protocol#PATCH_method]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# uri.path = '/posts'
# req = Net::HTTP::Patch.new(uri) # => #<Net::HTTP::Patch PATCH>
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.content_type = 'application/json'
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Properties:
#
# - Request body: yes.
# - Response body: yes.
# - {Safe}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Safe_methods]: no.
# - {Idempotent}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Idempotent_methods]: no.
# - {Cacheable}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Cacheable_methods]: no.
#
# Related:
#
# - Net::HTTP#patch: sends +PATCH+ request, returns response object.
#
class Net::HTTP::Patch < Net::HTTPRequest
METHOD = 'PATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
#
# WebDAV methods --- RFC2518
#
# \Class for representing
# {WebDAV method PROPFIND}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Propfind.new(uri) # => #<Net::HTTP::Propfind PROPFIND>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#propfind: sends +PROPFIND+ request, returns response object.
#
class Net::HTTP::Propfind < Net::HTTPRequest
METHOD = 'PROPFIND'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method PROPPATCH}[http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Proppatch.new(uri) # => #<Net::HTTP::Proppatch PROPPATCH>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#proppatch: sends +PROPPATCH+ request, returns response object.
#
class Net::HTTP::Proppatch < Net::HTTPRequest
METHOD = 'PROPPATCH'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method MKCOL}[http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Mkcol.new(uri) # => #<Net::HTTP::Mkcol MKCOL>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#mkcol: sends +MKCOL+ request, returns response object.
#
class Net::HTTP::Mkcol < Net::HTTPRequest
METHOD = 'MKCOL'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method COPY}[http://www.webdav.org/specs/rfc4918.html#METHOD_COPY]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Copy.new(uri) # => #<Net::HTTP::Copy COPY>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#copy: sends +COPY+ request, returns response object.
#
class Net::HTTP::Copy < Net::HTTPRequest
METHOD = 'COPY'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method MOVE}[http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Move.new(uri) # => #<Net::HTTP::Move MOVE>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#move: sends +MOVE+ request, returns response object.
#
class Net::HTTP::Move < Net::HTTPRequest
METHOD = 'MOVE'
REQUEST_HAS_BODY = false
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method LOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Lock.new(uri) # => #<Net::HTTP::Lock LOCK>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#lock: sends +LOCK+ request, returns response object.
#
class Net::HTTP::Lock < Net::HTTPRequest
METHOD = 'LOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
# \Class for representing
# {WebDAV method UNLOCK}[http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK]:
#
# require 'net/http'
# uri = URI('http://example.com')
# hostname = uri.hostname # => "example.com"
# req = Net::HTTP::Unlock.new(uri) # => #<Net::HTTP::Unlock UNLOCK>
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# See {Request Headers}[rdoc-ref:Net::HTTPRequest@Request+Headers].
#
# Related:
#
# - Net::HTTP#unlock: sends +UNLOCK+ request, returns response object.
#
class Net::HTTP::Unlock < Net::HTTPRequest
METHOD = 'UNLOCK'
REQUEST_HAS_BODY = true
RESPONSE_HAS_BODY = true
end
share/ruby/net/http/request.rb 0000644 00000005465 15173547337 0012442 0 ustar 00 # frozen_string_literal: true
# This class is the base class for \Net::HTTP request classes.
# The class should not be used directly;
# instead you should use its subclasses, listed below.
#
# == Creating a Request
#
# An request object may be created with either a URI or a string hostname:
#
# require 'net/http'
# uri = URI('https://jsonplaceholder.typicode.com/')
# req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET>
# req = Net::HTTP::Get.new(uri.hostname) # => #<Net::HTTP::Get GET>
#
# And with any of the subclasses:
#
# req = Net::HTTP::Head.new(uri) # => #<Net::HTTP::Head HEAD>
# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST>
# req = Net::HTTP::Put.new(uri) # => #<Net::HTTP::Put PUT>
# # ...
#
# The new instance is suitable for use as the argument to Net::HTTP#request.
#
# == Request Headers
#
# A new request object has these header fields by default:
#
# req.to_hash
# # =>
# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
# "accept"=>["*/*"],
# "user-agent"=>["Ruby"],
# "host"=>["jsonplaceholder.typicode.com"]}
#
# See:
#
# - {Request header Accept-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Accept-Encoding]
# and {Compression and Decompression}[rdoc-ref:Net::HTTP@Compression+and+Decompression].
# - {Request header Accept}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#accept-request-header].
# - {Request header User-Agent}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#user-agent-request-header].
# - {Request header Host}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#host-request-header].
#
# You can add headers or override default headers:
#
# # res = Net::HTTP::Get.new(uri, {'foo' => '0', 'bar' => '1'})
#
# This class (and therefore its subclasses) also includes (indirectly)
# module Net::HTTPHeader, which gives access to its
# {methods for setting headers}[rdoc-ref:Net::HTTPHeader@Setters].
#
# == Request Subclasses
#
# Subclasses for HTTP requests:
#
# - Net::HTTP::Get
# - Net::HTTP::Head
# - Net::HTTP::Post
# - Net::HTTP::Put
# - Net::HTTP::Delete
# - Net::HTTP::Options
# - Net::HTTP::Trace
# - Net::HTTP::Patch
#
# Subclasses for WebDAV requests:
#
# - Net::HTTP::Propfind
# - Net::HTTP::Proppatch
# - Net::HTTP::Mkcol
# - Net::HTTP::Copy
# - Net::HTTP::Move
# - Net::HTTP::Lock
# - Net::HTTP::Unlock
#
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
share/ruby/net/http/generic_request.rb 0000644 00000030160 15173547337 0014124 0 ustar 00 # frozen_string_literal: true
#
# \HTTPGenericRequest is the parent of the Net::HTTPRequest class.
#
# Do not use this directly; instead, use a subclass of Net::HTTPRequest.
#
# == About the Examples
#
# :include: doc/net-http/examples.rdoc
#
class Net::HTTPGenericRequest
include Net::HTTPHeader
def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) # :nodoc:
@method = m
@request_has_body = reqbody
@response_has_body = resbody
if URI === uri_or_path then
raise ArgumentError, "not an HTTP URI" unless URI::HTTP === uri_or_path
hostname = uri_or_path.hostname
raise ArgumentError, "no host component for URI" unless (hostname && hostname.length > 0)
@uri = uri_or_path.dup
host = @uri.hostname.dup
host << ":" << @uri.port.to_s if @uri.port != @uri.default_port
@path = uri_or_path.request_uri
raise ArgumentError, "no HTTP request path given" unless @path
else
@uri = nil
host = nil
raise ArgumentError, "no HTTP request path given" unless uri_or_path
raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty?
@path = uri_or_path.dup
end
@decode_content = false
if Net::HTTP::HAVE_ZLIB then
if !initheader ||
!initheader.keys.any? { |k|
%w[accept-encoding range].include? k.downcase
} then
@decode_content = true if @response_has_body
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
# Returns the string method name for the request:
#
# Net::HTTP::Get.new(uri).method # => "GET"
# Net::HTTP::Post.new(uri).method # => "POST"
#
attr_reader :method
# Returns the string path for the request:
#
# Net::HTTP::Get.new(uri).path # => "/"
# Net::HTTP::Post.new('example.com').path # => "example.com"
#
attr_reader :path
# Returns the URI object for the request, or +nil+ if none:
#
# Net::HTTP::Get.new(uri).uri
# # => #<URI::HTTPS https://jsonplaceholder.typicode.com/>
# Net::HTTP::Get.new('example.com').uri # => nil
#
attr_reader :uri
# Returns +false+ if the request's header <tt>'Accept-Encoding'</tt>
# has been set manually or deleted
# (indicating that the user intends to handle encoding in the response),
# +true+ otherwise:
#
# req = Net::HTTP::Get.new(uri) # => #<Net::HTTP::Get GET>
# req['Accept-Encoding'] # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
# req.decode_content # => true
# req['Accept-Encoding'] = 'foo'
# req.decode_content # => false
# req.delete('Accept-Encoding')
# req.decode_content # => false
#
attr_reader :decode_content
# Returns a string representation of the request:
#
# Net::HTTP::Post.new(uri).inspect # => "#<Net::HTTP::Post POST>"
#
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
# Returns whether the request may have a body:
#
# Net::HTTP::Post.new(uri).request_body_permitted? # => true
# Net::HTTP::Get.new(uri).request_body_permitted? # => false
#
def request_body_permitted?
@request_has_body
end
# Returns whether the response may have a body:
#
# Net::HTTP::Post.new(uri).response_body_permitted? # => true
# Net::HTTP::Head.new(uri).response_body_permitted? # => false
#
def response_body_permitted?
@response_has_body
end
def body_exist? # :nodoc:
warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?", uplevel: 1 if $VERBOSE
response_body_permitted?
end
# Returns the string body for the request, or +nil+ if there is none:
#
# req = Net::HTTP::Post.new(uri)
# req.body # => nil
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
#
attr_reader :body
# Sets the body for the request:
#
# req = Net::HTTP::Post.new(uri)
# req.body # => nil
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.body # => "{\"title\": \"foo\",\"body\": \"bar\",\"userId\": 1}"
#
def body=(str)
@body = str
@body_stream = nil
@body_data = nil
str
end
# Returns the body stream object for the request, or +nil+ if there is none:
#
# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST>
# req.body_stream # => nil
# require 'stringio'
# req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
# req.body_stream # => #<StringIO:0x0000027d1e5affa8>
#
attr_reader :body_stream
# Sets the body stream for the request:
#
# req = Net::HTTP::Post.new(uri) # => #<Net::HTTP::Post POST>
# req.body_stream # => nil
# require 'stringio'
# req.body_stream = StringIO.new('xyzzy') # => #<StringIO:0x0000027d1e5affa8>
# req.body_stream # => #<StringIO:0x0000027d1e5affa8>
#
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'
klass = URI::HTTPS
else
scheme = 'http'
klass = URI::HTTP
end
if host = self['host']
host.sub!(/:.*/m, '')
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
rv = buf.bytesize
@sock.write("#{rv.to_s(16)}\r\n", buf, "\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
IO.copy_stream(f, sock)
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
share/ruby/net/http/status.rb 0000644 00000004373 15173547337 0012272 0 ustar 00 # frozen_string_literal: true
require_relative '../http'
if $0 == __FILE__
require 'open-uri'
File.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 "} # :nodoc:"
end
Net::HTTP::STATUS_CODES = {
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
103 => 'Early Hints',
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 => 'Content Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
421 => 'Misdirected Request',
422 => 'Unprocessable Content',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Too Early',
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 (OBSOLETED)',
511 => 'Network Authentication Required',
} # :nodoc:
share/ruby/net/http/proxy_delta.rb 0000644 00000000417 15173547337 0013274 0 ustar 00 # frozen_string_literal: true
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
share/ruby/net/http/header.rb 0000644 00000100002 15173547337 0012161 0 ustar 00 # frozen_string_literal: true
#
# The \HTTPHeader module provides access to \HTTP headers.
#
# The module is included in:
#
# - Net::HTTPGenericRequest (and therefore Net::HTTPRequest).
# - Net::HTTPResponse.
#
# The headers are a hash-like collection of key/value pairs called _fields_.
#
# == Request and Response Fields
#
# Headers may be included in:
#
# - A Net::HTTPRequest object:
# the object's headers will be sent with the request.
# Any fields may be defined in the request;
# see {Setters}[rdoc-ref:Net::HTTPHeader@Setters].
# - A Net::HTTPResponse object:
# the objects headers are usually those returned from the host.
# Fields may be retrieved from the object;
# see {Getters}[rdoc-ref:Net::HTTPHeader@Getters]
# and {Iterators}[rdoc-ref:Net::HTTPHeader@Iterators].
#
# Exactly which fields should be sent or expected depends on the host;
# see:
#
# - {Request fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
# - {Response fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Response_fields].
#
# == About the Examples
#
# :include: doc/net-http/examples.rdoc
#
# == Fields
#
# A header field is a key/value pair.
#
# === Field Keys
#
# A field key may be:
#
# - A string: Key <tt>'Accept'</tt> is treated as if it were
# <tt>'Accept'.downcase</tt>; i.e., <tt>'accept'</tt>.
# - A symbol: Key <tt>:Accept</tt> is treated as if it were
# <tt>:Accept.to_s.downcase</tt>; i.e., <tt>'accept'</tt>.
#
# Examples:
#
# req = Net::HTTP::Get.new(uri)
# req[:accept] # => "*/*"
# req['Accept'] # => "*/*"
# req['ACCEPT'] # => "*/*"
#
# req['accept'] = 'text/html'
# req[:accept] = 'text/html'
# req['ACCEPT'] = 'text/html'
#
# === Field Values
#
# A field value may be returned as an array of strings or as a string:
#
# - These methods return field values as arrays:
#
# - #get_fields: Returns the array value for the given key,
# or +nil+ if it does not exist.
# - #to_hash: Returns a hash of all header fields:
# each key is a field name; its value is the array value for the field.
#
# - These methods return field values as string;
# the string value for a field is equivalent to
# <tt>self[key.downcase.to_s].join(', '))</tt>:
#
# - #[]: Returns the string value for the given key,
# or +nil+ if it does not exist.
# - #fetch: Like #[], but accepts a default value
# to be returned if the key does not exist.
#
# The field value may be set:
#
# - #[]=: Sets the value for the given key;
# the given value may be a string, a symbol, an array, or a hash.
# - #add_field: Adds a given value to a value for the given key
# (not overwriting the existing value).
# - #delete: Deletes the field for the given key.
#
# Example field values:
#
# - \String:
#
# req['Accept'] = 'text/html' # => "text/html"
# req['Accept'] # => "text/html"
# req.get_fields('Accept') # => ["text/html"]
#
# - \Symbol:
#
# req['Accept'] = :text # => :text
# req['Accept'] # => "text"
# req.get_fields('Accept') # => ["text"]
#
# - Simple array:
#
# req[:foo] = %w[bar baz bat]
# req[:foo] # => "bar, baz, bat"
# req.get_fields(:foo) # => ["bar", "baz", "bat"]
#
# - Simple hash:
#
# req[:foo] = {bar: 0, baz: 1, bat: 2}
# req[:foo] # => "bar, 0, baz, 1, bat, 2"
# req.get_fields(:foo) # => ["bar", "0", "baz", "1", "bat", "2"]
#
# - Nested:
#
# req[:foo] = [%w[bar baz], {bat: 0, bam: 1}]
# req[:foo] # => "bar, baz, bat, 0, bam, 1"
# req.get_fields(:foo) # => ["bar", "baz", "bat", "0", "bam", "1"]
#
# req[:foo] = {bar: %w[baz bat], bam: {bah: 0, bad: 1}}
# req[:foo] # => "bar, baz, bat, bam, bah, 0, bad, 1"
# req.get_fields(:foo) # => ["bar", "baz", "bat", "bam", "bah", "0", "bad", "1"]
#
# == Convenience Methods
#
# Various convenience methods retrieve values, set values, query values,
# set form values, or iterate over fields.
#
# === Setters
#
# \Method #[]= can set any field, but does little to validate the new value;
# some of the other setter methods provide some validation:
#
# - #[]=: Sets the string or array value for the given key.
# - #add_field: Creates or adds to the array value for the given key.
# - #basic_auth: Sets the string authorization header for <tt>'Authorization'</tt>.
# - #content_length=: Sets the integer length for field <tt>'Content-Length</tt>.
# - #content_type=: Sets the string value for field <tt>'Content-Type'</tt>.
# - #proxy_basic_auth: Sets the string authorization header for <tt>'Proxy-Authorization'</tt>.
# - #set_range: Sets the value for field <tt>'Range'</tt>.
#
# === Form Setters
#
# - #set_form: Sets an HTML form data set.
# - #set_form_data: Sets header fields and a body from HTML form data.
#
# === Getters
#
# \Method #[] can retrieve the value of any field that exists,
# but always as a string;
# some of the other getter methods return something different
# from the simple string value:
#
# - #[]: Returns the string field value for the given key.
# - #content_length: Returns the integer value of field <tt>'Content-Length'</tt>.
# - #content_range: Returns the Range value of field <tt>'Content-Range'</tt>.
# - #content_type: Returns the string value of field <tt>'Content-Type'</tt>.
# - #fetch: Returns the string field value for the given key.
# - #get_fields: Returns the array field value for the given +key+.
# - #main_type: Returns first part of the string value of field <tt>'Content-Type'</tt>.
# - #sub_type: Returns second part of the string value of field <tt>'Content-Type'</tt>.
# - #range: Returns an array of Range objects of field <tt>'Range'</tt>, or +nil+.
# - #range_length: Returns the integer length of the range given in field <tt>'Content-Range'</tt>.
# - #type_params: Returns the string parameters for <tt>'Content-Type'</tt>.
#
# === Queries
#
# - #chunked?: Returns whether field <tt>'Transfer-Encoding'</tt> is set to <tt>'chunked'</tt>.
# - #connection_close?: Returns whether field <tt>'Connection'</tt> is set to <tt>'close'</tt>.
# - #connection_keep_alive?: Returns whether field <tt>'Connection'</tt> is set to <tt>'keep-alive'</tt>.
# - #key?: Returns whether a given key exists.
#
# === Iterators
#
# - #each_capitalized: Passes each field capitalized-name/value pair to the block.
# - #each_capitalized_name: Passes each capitalized field name to the block.
# - #each_header: Passes each field name/value pair to the block.
# - #each_name: Passes each field name to the block.
# - #each_value: Passes each string field value to the block.
#
module Net::HTTPHeader
MAX_KEY_LENGTH = 1024
MAX_FIELD_LENGTH = 65536
def initialize_http_header(initheader) #:nodoc:
@header = {}
return unless initheader
initheader.each do |key, value|
warn "net/http: duplicated HTTP header: #{key}", uplevel: 3 if key?(key) and $VERBOSE
if value.nil?
warn "net/http: nil HTTP header: #{key}", uplevel: 3 if $VERBOSE
else
value = value.strip # raise error for invalid byte sequences
if key.to_s.bytesize > MAX_KEY_LENGTH
raise ArgumentError, "too long (#{key.bytesize} bytes) header: #{key[0, 30].inspect}..."
end
if value.to_s.bytesize > MAX_FIELD_LENGTH
raise ArgumentError, "header #{key} has too long field value: #{value.bytesize}"
end
if value.count("\r\n") > 0
raise ArgumentError, "header #{key} has field value #{value.inspect}, this cannot include CR/LF"
end
@header[key.downcase.to_s] = [value]
end
end
end
def size #:nodoc: obsolete
@header.size
end
alias length size #:nodoc: obsolete
# Returns the string field value for the case-insensitive field +key+,
# or +nil+ if there is no such key;
# see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['Connection'] # => "keep-alive"
# res['Nosuch'] # => nil
#
# Note that some field values may be retrieved via convenience methods;
# see {Getters}[rdoc-ref:Net::HTTPHeader@Getters].
def [](key)
a = @header[key.downcase.to_s] or return nil
a.join(', ')
end
# Sets the value for the case-insensitive +key+ to +val+,
# overwriting the previous value if the field exists;
# see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
#
# req = Net::HTTP::Get.new(uri)
# req['Accept'] # => "*/*"
# req['Accept'] = 'text/html'
# req['Accept'] # => "text/html"
#
# Note that some field values may be set via convenience methods;
# see {Setters}[rdoc-ref:Net::HTTPHeader@Setters].
def []=(key, val)
unless val
@header.delete key.downcase.to_s
return val
end
set_field(key, val)
end
# Adds value +val+ to the value array for field +key+ if the field exists;
# creates the field with the given +key+ and +val+ if it does not exist.
# see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
#
# req = Net::HTTP::Get.new(uri)
# req.add_field('Foo', 'bar')
# req['Foo'] # => "bar"
# req.add_field('Foo', 'baz')
# req['Foo'] # => "bar, baz"
# req.add_field('Foo', %w[baz bam])
# req['Foo'] # => "bar, baz, baz, bam"
# req.get_fields('Foo') # => ["bar", "baz", "baz", "bam"]
#
def add_field(key, val)
stringified_downcased_key = key.downcase.to_s
if @header.key?(stringified_downcased_key)
append_field_value(@header[stringified_downcased_key], 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.to_s] = 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.to_s] = [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
# Returns the array field value for the given +key+,
# or +nil+ if there is no such field;
# see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.get_fields('Connection') # => ["keep-alive"]
# res.get_fields('Nosuch') # => nil
#
def get_fields(key)
stringified_downcased_key = key.downcase.to_s
return nil unless @header[stringified_downcased_key]
@header[stringified_downcased_key].dup
end
# call-seq:
# fetch(key, default_val = nil) {|key| ... } -> object
# fetch(key, default_val = nil) -> value or default_val
#
# With a block, returns the string value for +key+ if it exists;
# otherwise returns the value of the block;
# ignores the +default_val+;
# see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
#
# # Field exists; block not called.
# res.fetch('Connection') do |value|
# fail 'Cannot happen'
# end # => "keep-alive"
#
# # Field does not exist; block called.
# res.fetch('Nosuch') do |value|
# value.downcase
# end # => "nosuch"
#
# With no block, returns the string value for +key+ if it exists;
# otherwise, returns +default_val+ if it was given;
# otherwise raises an exception:
#
# res.fetch('Connection', 'Foo') # => "keep-alive"
# res.fetch('Nosuch', 'Foo') # => "Foo"
# res.fetch('Nosuch') # Raises KeyError.
#
def fetch(key, *args, &block) #:yield: +key+
a = @header.fetch(key.downcase.to_s, *args, &block)
a.kind_of?(Array) ? a.join(', ') : a
end
# Calls the block with each key/value pair:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.each_header do |key, value|
# p [key, value] if key.start_with?('c')
# end
#
# Output:
#
# ["content-type", "application/json; charset=utf-8"]
# ["connection", "keep-alive"]
# ["cache-control", "max-age=43200"]
# ["cf-cache-status", "HIT"]
# ["cf-ray", "771d17e9bc542cf5-ORD"]
#
# Returns an enumerator if no block is given.
#
# Net::HTTPHeader#each is an alias for Net::HTTPHeader#each_header.
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
# Calls the block with each field key:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.each_key do |key|
# p key if key.start_with?('c')
# end
#
# Output:
#
# "content-type"
# "connection"
# "cache-control"
# "cf-cache-status"
# "cf-ray"
#
# Returns an enumerator if no block is given.
#
# Net::HTTPHeader#each_name is an alias for Net::HTTPHeader#each_key.
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
# Calls the block with each capitalized field name:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.each_capitalized_name do |key|
# p key if key.start_with?('C')
# end
#
# Output:
#
# "Content-Type"
# "Connection"
# "Cache-Control"
# "Cf-Cache-Status"
# "Cf-Ray"
#
# The capitalization is system-dependent;
# see {Case Mapping}[rdoc-ref:case_mapping.rdoc].
#
# 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
# Calls the block with each string field value:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.each_value do |value|
# p value if value.start_with?('c')
# end
#
# Output:
#
# "chunked"
# "cf-q-config;dur=6.0000002122251e-06"
# "cloudflare"
#
# 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 the header for the given case-insensitive +key+
# (see {Fields}[rdoc-ref:Net::HTTPHeader@Fields]);
# returns the deleted value, or +nil+ if no such field exists:
#
# req = Net::HTTP::Get.new(uri)
# req.delete('Accept') # => ["*/*"]
# req.delete('Nosuch') # => nil
#
def delete(key)
@header.delete(key.downcase.to_s)
end
# Returns +true+ if the field for the case-insensitive +key+ exists, +false+ otherwise:
#
# req = Net::HTTP::Get.new(uri)
# req.key?('Accept') # => true
# req.key?('Nosuch') # => false
#
def key?(key)
@header.key?(key.downcase.to_s)
end
# Returns a hash of the key/value pairs:
#
# req = Net::HTTP::Get.new(uri)
# req.to_hash
# # =>
# {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"],
# "accept"=>["*/*"],
# "user-agent"=>["Ruby"],
# "host"=>["jsonplaceholder.typicode.com"]}
#
def to_hash
@header.dup
end
# Like #each_header, but the keys are returned in capitalized form.
#
# Net::HTTPHeader#canonical_each is an alias for Net::HTTPHeader#each_capitalized.
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 that represent
# the value of field <tt>'Range'</tt>,
# or +nil+ if there is no such field;
# see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
#
# req = Net::HTTP::Get.new(uri)
# req['Range'] = 'bytes=0-99,200-299,400-499'
# req.range # => [0..99, 200..299, 400..499]
# req.delete('Range')
# req.range # # => nil
#
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
# call-seq:
# set_range(length) -> length
# set_range(offset, length) -> range
# set_range(begin..length) -> range
#
# Sets the value for field <tt>'Range'</tt>;
# see {Range request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#range-request-header]:
#
# With argument +length+:
#
# req = Net::HTTP::Get.new(uri)
# req.set_range(100) # => 100
# req['Range'] # => "bytes=0-99"
#
# With arguments +offset+ and +length+:
#
# req.set_range(100, 100) # => 100...200
# req['Range'] # => "bytes=100-199"
#
# With argument +range+:
#
# req.set_range(100..199) # => 100..199
# req['Range'] # => "bytes=100-199"
#
# Net::HTTPHeader#range= is an alias for Net::HTTPHeader#set_range.
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 the value of field <tt>'Content-Length'</tt> as an integer,
# or +nil+ if there is no such field;
# see {Content-Length request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-request-header]:
#
# res = Net::HTTP.get_response(hostname, '/nosuch/1')
# res.content_length # => 2
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res.content_length # => nil
#
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
# Sets the value of field <tt>'Content-Length'</tt> to the given numeric;
# see {Content-Length response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-length-response-header]:
#
# _uri = uri.dup
# hostname = _uri.hostname # => "jsonplaceholder.typicode.com"
# _uri.path = '/posts' # => "/posts"
# req = Net::HTTP::Post.new(_uri) # => #<Net::HTTP::Post POST>
# req.body = '{"title": "foo","body": "bar","userId": 1}'
# req.content_length = req.body.size # => 42
# req.content_type = 'application/json'
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end # => #<Net::HTTPCreated 201 Created readbody=true>
#
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 field <tt>'Transfer-Encoding'</tt>
# exists and has value <tt>'chunked'</tt>,
# +false+ otherwise;
# see {Transfer-Encoding response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#transfer-encoding-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['Transfer-Encoding'] # => "chunked"
# res.chunked? # => true
#
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 representing the value of field
# <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
# see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['Content-Range'] # => nil
# res['Content-Range'] = 'bytes 0-499/1000'
# res['Content-Range'] # => "bytes 0-499/1000"
# res.content_range # => 0..499
#
def content_range
return nil unless @header['content-range']
m = %r<\A\s*(\w+)\s+(\d+)-(\d+)/(\d+|\*)>.match(self['Content-Range']) or
raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format'
return unless m[1] == 'bytes'
m[2].to_i .. m[3].to_i
end
# Returns the integer representing length of the value of field
# <tt>'Content-Range'</tt>, or +nil+ if no such field exists;
# see {Content-Range response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-range-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['Content-Range'] # => nil
# res['Content-Range'] = 'bytes 0-499/1000'
# res.range_length # => 500
#
def range_length
r = content_range() or return nil
r.end - r.begin + 1
end
# Returns the {media type}[https://en.wikipedia.org/wiki/Media_type]
# from the value of field <tt>'Content-Type'</tt>,
# or +nil+ if no such field exists;
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['content-type'] # => "application/json; charset=utf-8"
# res.content_type # => "application/json"
#
def content_type
main = main_type()
return nil unless main
sub = sub_type()
if sub
"#{main}/#{sub}"
else
main
end
end
# Returns the leading ('type') part of the
# {media type}[https://en.wikipedia.org/wiki/Media_type]
# from the value of field <tt>'Content-Type'</tt>,
# or +nil+ if no such field exists;
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['content-type'] # => "application/json; charset=utf-8"
# res.main_type # => "application"
#
def main_type
return nil unless @header['content-type']
self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip
end
# Returns the trailing ('subtype') part of the
# {media type}[https://en.wikipedia.org/wiki/Media_type]
# from the value of field <tt>'Content-Type'</tt>,
# or +nil+ if no such field exists;
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['content-type'] # => "application/json; charset=utf-8"
# res.sub_type # => "json"
#
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
# Returns the trailing ('parameters') part of the value of field <tt>'Content-Type'</tt>,
# or +nil+ if no such field exists;
# see {Content-Type response header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-response-header]:
#
# res = Net::HTTP.get_response(hostname, '/todos/1')
# res['content-type'] # => "application/json; charset=utf-8"
# res.type_params # => {"charset"=>"utf-8"}
#
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 value of field <tt>'Content-Type'</tt>;
# returns the new value;
# see {Content-Type request header}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-type-request-header]:
#
# req = Net::HTTP::Get.new(uri)
# req.set_content_type('application/json') # => ["application/json"]
#
# Net::HTTPHeader#content_type= is an alias for Net::HTTPHeader#set_content_type.
def set_content_type(type, params = {})
@header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')]
end
alias content_type= set_content_type
# Sets the request body to a URL-encoded string derived from argument +params+,
# and sets request header field <tt>'Content-Type'</tt>
# to <tt>'application/x-www-form-urlencoded'</tt>.
#
# The resulting request is suitable for HTTP request +POST+ or +PUT+.
#
# Argument +params+ must be suitable for use as argument +enum+ to
# {URI.encode_www_form}[rdoc-ref:URI.encode_www_form].
#
# With only argument +params+ given,
# sets the body to a URL-encoded string with the default separator <tt>'&'</tt>:
#
# req = Net::HTTP::Post.new('example.com')
#
# req.set_form_data(q: 'ruby', lang: 'en')
# req.body # => "q=ruby&lang=en"
# req['Content-Type'] # => "application/x-www-form-urlencoded"
#
# req.set_form_data([['q', 'ruby'], ['lang', 'en']])
# req.body # => "q=ruby&lang=en"
#
# req.set_form_data(q: ['ruby', 'perl'], lang: 'en')
# req.body # => "q=ruby&q=perl&lang=en"
#
# req.set_form_data([['q', 'ruby'], ['q', 'perl'], ['lang', 'en']])
# req.body # => "q=ruby&q=perl&lang=en"
#
# With string argument +sep+ also given,
# uses that string as the separator:
#
# req.set_form_data({q: 'ruby', lang: 'en'}, '|')
# req.body # => "q=ruby|lang=en"
#
# Net::HTTPHeader#form_data= is an alias for Net::HTTPHeader#set_form_data.
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
# Stores form data to be used in a +POST+ or +PUT+ request.
#
# The form data given in +params+ consists of zero or more fields;
# each field is:
#
# - A scalar value.
# - A name/value pair.
# - An IO stream opened for reading.
#
# Argument +params+ should be an
# {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes]
# (method <tt>params.map</tt> will be called),
# and is often an array or hash.
#
# First, we set up a request:
#
# _uri = uri.dup
# _uri.path ='/posts'
# req = Net::HTTP::Post.new(_uri)
#
# <b>Argument +params+ As an Array</b>
#
# When +params+ is an array,
# each of its elements is a subarray that defines a field;
# the subarray may contain:
#
# - One string:
#
# req.set_form([['foo'], ['bar'], ['baz']])
#
# - Two strings:
#
# req.set_form([%w[foo 0], %w[bar 1], %w[baz 2]])
#
# - When argument +enctype+ (see below) is given as
# <tt>'multipart/form-data'</tt>:
#
# - A string name and an IO stream opened for reading:
#
# require 'stringio'
# req.set_form([['file', StringIO.new('Ruby is cool.')]])
#
# - A string name, an IO stream opened for reading,
# and an options hash, which may contain these entries:
#
# - +:filename+: The name of the file to use.
# - +:content_type+: The content type of the uploaded file.
#
# Example:
#
# req.set_form([['file', file, {filename: "other-filename.foo"}]]
#
# The various forms may be mixed:
#
# req.set_form(['foo', %w[bar 1], ['file', file]])
#
# <b>Argument +params+ As a Hash</b>
#
# When +params+ is a hash,
# each of its entries is a name/value pair that defines a field:
#
# - The name is a string.
# - The value may be:
#
# - +nil+.
# - Another string.
# - An IO stream opened for reading
# (only when argument +enctype+ -- see below -- is given as
# <tt>'multipart/form-data'</tt>).
#
# Examples:
#
# # Nil-valued fields.
# req.set_form({'foo' => nil, 'bar' => nil, 'baz' => nil})
#
# # String-valued fields.
# req.set_form({'foo' => 0, 'bar' => 1, 'baz' => 2})
#
# # IO-valued field.
# require 'stringio'
# req.set_form({'file' => StringIO.new('Ruby is cool.')})
#
# # Mixture of fields.
# req.set_form({'foo' => nil, 'bar' => 1, 'file' => file})
#
# Optional argument +enctype+ specifies the value to be given
# to field <tt>'Content-Type'</tt>, and must be one of:
#
# - <tt>'application/x-www-form-urlencoded'</tt> (the default).
# - <tt>'multipart/form-data'</tt>;
# see {RFC 7578}[https://www.rfc-editor.org/rfc/rfc7578].
#
# Optional argument +formopt+ is a hash of options
# (applicable only when argument +enctype+
# is <tt>'multipart/form-data'</tt>)
# that may include the following entries:
#
# - +:boundary+: The value is the boundary string for the multipart message.
# If not given, the boundary is a random string.
# See {Boundary}[https://www.rfc-editor.org/rfc/rfc7578#section-4.1].
# - +:charset+: Value is the character set for the form submission.
# Field names and values of non-file fields should be encoded with this charset.
#
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
# Sets header <tt>'Authorization'</tt> using the given
# +account+ and +password+ strings:
#
# req.basic_auth('my_account', 'my_password')
# req['Authorization']
# # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
#
def basic_auth(account, password)
@header['authorization'] = [basic_encode(account, password)]
end
# Sets header <tt>'Proxy-Authorization'</tt> using the given
# +account+ and +password+ strings:
#
# req.proxy_basic_auth('my_account', 'my_password')
# req['Proxy-Authorization']
# # => "Basic bXlfYWNjb3VudDpteV9wYXNzd29yZA=="
#
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
# Returns whether the HTTP session is to be closed.
def connection_close?
token = /(?:\A|,)\s*close\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
@header['proxy-connection']&.grep(token) {return true}
false
end
# Returns whether the HTTP session is to be kept alive.
def connection_keep_alive?
token = /(?:\A|,)\s*keep-alive\s*(?:\z|,)/i
@header['connection']&.grep(token) {return true}
@header['proxy-connection']&.grep(token) {return true}
false
end
end
share/ruby/net/http/exceptions.rb 0000644 00000001505 15173547337 0013122 0 ustar 00 # frozen_string_literal: true
module Net
# Net::HTTP exception class.
# You cannot use Net::HTTPExceptions directly; instead, you must use
# its subclasses.
module HTTPExceptions
def initialize(msg, res) #:nodoc:
super msg
@response = res
end
attr_reader :response
alias data response #:nodoc: obsolete
end
class HTTPError < ProtocolError
include HTTPExceptions
end
class HTTPRetriableError < ProtoRetriableError
include HTTPExceptions
end
class HTTPClientException < ProtoServerError
include HTTPExceptions
end
class HTTPFatalError < ProtoFatalError
include HTTPExceptions
end
# We cannot use the name "HTTPServerError", it is the name of the response.
HTTPServerException = HTTPClientException # :nodoc:
deprecate_constant(:HTTPServerException)
end
share/ruby/net/http/response.rb 0000644 00000046173 15173547337 0012611 0 ustar 00 # frozen_string_literal: true
# This class is the base class for \Net::HTTP response classes.
#
# == About the Examples
#
# :include: doc/net-http/examples.rdoc
#
# == Returned Responses
#
# \Method Net::HTTP.get_response returns
# an instance of one of the subclasses of \Net::HTTPResponse:
#
# Net::HTTP.get_response(uri)
# # => #<Net::HTTPOK 200 OK readbody=true>
# Net::HTTP.get_response(hostname, '/nosuch')
# # => #<Net::HTTPNotFound 404 Not Found readbody=true>
#
# As does method Net::HTTP#request:
#
# req = Net::HTTP::Get.new(uri)
# Net::HTTP.start(hostname) do |http|
# http.request(req)
# end # => #<Net::HTTPOK 200 OK readbody=true>
#
# \Class \Net::HTTPResponse includes module Net::HTTPHeader,
# which provides access to response header values via (among others):
#
# - \Hash-like method <tt>[]</tt>.
# - Specific reader methods, such as +content_type+.
#
# Examples:
#
# res = Net::HTTP.get_response(uri) # => #<Net::HTTPOK 200 OK readbody=true>
# res['Content-Type'] # => "text/html; charset=UTF-8"
# res.content_type # => "text/html"
#
# == Response Subclasses
#
# \Class \Net::HTTPResponse has a subclass for each
# {HTTP status code}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes].
# You can look up the response class for a given code:
#
# Net::HTTPResponse::CODE_TO_OBJ['200'] # => Net::HTTPOK
# Net::HTTPResponse::CODE_TO_OBJ['400'] # => Net::HTTPBadRequest
# Net::HTTPResponse::CODE_TO_OBJ['404'] # => Net::HTTPNotFound
#
# And you can retrieve the status code for a response object:
#
# Net::HTTP.get_response(uri).code # => "200"
# Net::HTTP.get_response(hostname, '/nosuch').code # => "404"
#
# The response subclasses (indentation shows class hierarchy):
#
# - Net::HTTPUnknownResponse (for unhandled \HTTP extensions).
#
# - Net::HTTPInformation:
#
# - Net::HTTPContinue (100)
# - Net::HTTPSwitchProtocol (101)
# - Net::HTTPProcessing (102)
# - Net::HTTPEarlyHints (103)
#
# - Net::HTTPSuccess:
#
# - Net::HTTPOK (200)
# - Net::HTTPCreated (201)
# - Net::HTTPAccepted (202)
# - Net::HTTPNonAuthoritativeInformation (203)
# - Net::HTTPNoContent (204)
# - Net::HTTPResetContent (205)
# - Net::HTTPPartialContent (206)
# - Net::HTTPMultiStatus (207)
# - Net::HTTPAlreadyReported (208)
# - Net::HTTPIMUsed (226)
#
# - Net::HTTPRedirection:
#
# - Net::HTTPMultipleChoices (300)
# - Net::HTTPMovedPermanently (301)
# - Net::HTTPFound (302)
# - Net::HTTPSeeOther (303)
# - Net::HTTPNotModified (304)
# - Net::HTTPUseProxy (305)
# - Net::HTTPTemporaryRedirect (307)
# - Net::HTTPPermanentRedirect (308)
#
# - Net::HTTPClientError:
#
# - Net::HTTPBadRequest (400)
# - Net::HTTPUnauthorized (401)
# - Net::HTTPPaymentRequired (402)
# - Net::HTTPForbidden (403)
# - Net::HTTPNotFound (404)
# - Net::HTTPMethodNotAllowed (405)
# - Net::HTTPNotAcceptable (406)
# - Net::HTTPProxyAuthenticationRequired (407)
# - Net::HTTPRequestTimeOut (408)
# - Net::HTTPConflict (409)
# - Net::HTTPGone (410)
# - Net::HTTPLengthRequired (411)
# - Net::HTTPPreconditionFailed (412)
# - Net::HTTPRequestEntityTooLarge (413)
# - Net::HTTPRequestURITooLong (414)
# - Net::HTTPUnsupportedMediaType (415)
# - Net::HTTPRequestedRangeNotSatisfiable (416)
# - Net::HTTPExpectationFailed (417)
# - Net::HTTPMisdirectedRequest (421)
# - Net::HTTPUnprocessableEntity (422)
# - Net::HTTPLocked (423)
# - Net::HTTPFailedDependency (424)
# - Net::HTTPUpgradeRequired (426)
# - Net::HTTPPreconditionRequired (428)
# - Net::HTTPTooManyRequests (429)
# - Net::HTTPRequestHeaderFieldsTooLarge (431)
# - Net::HTTPUnavailableForLegalReasons (451)
#
# - Net::HTTPServerError:
#
# - Net::HTTPInternalServerError (500)
# - Net::HTTPNotImplemented (501)
# - Net::HTTPBadGateway (502)
# - Net::HTTPServiceUnavailable (503)
# - Net::HTTPGatewayTimeOut (504)
# - Net::HTTPVersionNotSupported (505)
# - Net::HTTPVariantAlsoNegotiates (506)
# - Net::HTTPInsufficientStorage (507)
# - Net::HTTPLoopDetected (508)
# - Net::HTTPNotExtended (510)
# - Net::HTTPNetworkAuthenticationRequired (511)
#
# There is also the Net::HTTPBadResponse exception which is raised when
# there is a protocol error.
#
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
@body_encoding = false
@ignore_eof = true
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
# Returns the value set by body_encoding=, or +false+ if none;
# see #body_encoding=.
attr_reader :body_encoding
# Sets the encoding that should be used when reading the body:
#
# - If the given value is an Encoding object, that encoding will be used.
# - Otherwise if the value is a string, the value of
# {Encoding#find(value)}[rdoc-ref:Encoding.find]
# will be used.
# - Otherwise an encoding will be deduced from the body itself.
#
# Examples:
#
# http = Net::HTTP.new(hostname)
# req = Net::HTTP::Get.new('/')
#
# http.request(req) do |res|
# p res.body.encoding # => #<Encoding:ASCII-8BIT>
# end
#
# http.request(req) do |res|
# res.body_encoding = "UTF-8"
# p res.body.encoding # => #<Encoding:UTF-8>
# end
#
def body_encoding=(value)
value = Encoding.find(value) if value.is_a?(String)
@body_encoding = value
end
# Whether to ignore EOF when reading bodies with a specified Content-Length
# header.
attr_accessor :ignore_eof
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} #{@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.
#
# If +dest+ argument is given, response is read into that variable,
# with <code>dest#<<</code> method (it could be String or IO, or any
# other object responding to <code><<</code>).
#
# 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
return if @body.nil?
case enc = @body_encoding
when Encoding, false, nil
# Encoding: force given encoding
# false/nil: do not force encoding
else
# other value: detect encoding from body
enc = detect_encoding(@body)
end
@body.force_encoding(enc) if enc
@body
end
# Returns the string response body;
# note that repeated calls for the unmodified body return a cached string:
#
# path = '/todos/1'
# Net::HTTP.start(hostname) do |http|
# res = http.get(path)
# p res.body
# p http.head(path).body # No body.
# end
#
# Output:
#
# "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}"
# nil
#
def body
read_body()
end
# Sets the body of the response to the given value.
def body=(value)
@body = value
end
alias entity body #:nodoc: obsolete
private
# :nodoc:
def detect_encoding(str, encoding=nil)
if encoding
elsif encoding = type_params['charset']
elsif encoding = check_bom(str)
else
encoding = case content_type&.downcase
when %r{text/x(?:ht)?ml|application/(?:[^+]+\+)?xml}
/\A<xml[ \t\r\n]+
version[ \t\r\n]*=[ \t\r\n]*(?:"[0-9.]+"|'[0-9.]*')[ \t\r\n]+
encoding[ \t\r\n]*=[ \t\r\n]*
(?:"([A-Za-z][\-A-Za-z0-9._]*)"|'([A-Za-z][\-A-Za-z0-9._]*)')/x =~ str
encoding = $1 || $2 || Encoding::UTF_8
when %r{text/html.*}
sniff_encoding(str)
end
end
return encoding
end
# :nodoc:
def sniff_encoding(str, encoding=nil)
# the encoding sniffing algorithm
# http://www.w3.org/TR/html5/parsing.html#determining-the-character-encoding
if enc = scanning_meta(str)
enc
# 6. last visited page or something
# 7. frequency
elsif str.ascii_only?
Encoding::US_ASCII
elsif str.dup.force_encoding(Encoding::UTF_8).valid_encoding?
Encoding::UTF_8
end
# 8. implementation-defined or user-specified
end
# :nodoc:
def check_bom(str)
case str.byteslice(0, 2)
when "\xFE\xFF"
return Encoding::UTF_16BE
when "\xFF\xFE"
return Encoding::UTF_16LE
end
if "\xEF\xBB\xBF" == str.byteslice(0, 3)
return Encoding::UTF_8
end
nil
end
# :nodoc:
def scanning_meta(str)
require 'strscan'
ss = StringScanner.new(str)
if ss.scan_until(/<meta[\t\n\f\r ]*/)
attrs = {} # attribute_list
got_pragma = false
need_pragma = nil
charset = nil
# step: Attributes
while attr = get_attribute(ss)
name, value = *attr
next if attrs[name]
attrs[name] = true
case name
when 'http-equiv'
got_pragma = true if value == 'content-type'
when 'content'
encoding = extracting_encodings_from_meta_elements(value)
unless charset
charset = encoding
end
need_pragma = true
when 'charset'
need_pragma = false
charset = value
end
end
# step: Processing
return if need_pragma.nil?
return if need_pragma && !got_pragma
charset = Encoding.find(charset) rescue nil
return unless charset
charset = Encoding::UTF_8 if charset == Encoding::UTF_16
return charset # tentative
end
nil
end
def get_attribute(ss)
ss.scan(/[\t\n\f\r \/]*/)
if ss.peek(1) == '>'
ss.getch
return nil
end
name = ss.scan(/[^=\t\n\f\r \/>]*/)
name.downcase!
raise if name.empty?
ss.skip(/[\t\n\f\r ]*/)
if ss.getch != '='
value = ''
return [name, value]
end
ss.skip(/[\t\n\f\r ]*/)
case ss.peek(1)
when '"'
ss.getch
value = ss.scan(/[^"]+/)
value.downcase!
ss.getch
when "'"
ss.getch
value = ss.scan(/[^']+/)
value.downcase!
ss.getch
when '>'
value = ''
else
value = ss.scan(/[^\t\n\f\r >]+/)
value.downcase!
end
[name, value]
end
def extracting_encodings_from_meta_elements(value)
# http://dev.w3.org/html5/spec/fetching-resources.html#algorithm-for-extracting-an-encoding-from-a-meta-element
if /charset[\t\n\f\r ]*=(?:"([^"]*)"|'([^']*)'|["']|\z|([^\t\n\f\r ;]+))/i =~ value
return $1 || $2 || $3
end
return nil
end
##
# 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
success = true
ensure
begin
inflate_body_io.finish
if self['content-length']
self['content-length'] = inflate_body_io.bytes_inflated.to_s
end
rescue => err
# Ignore #finish's error if there is an exception from yield
raise err if success
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, @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.nil? || @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
##
# The number of bytes inflated, used to update the Content-Length of
# the response.
def bytes_inflated
@inflate.total_out
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|
compressed_chunk.clear
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
share/ruby/net/http/backward.rb 0000644 00000002065 15173547337 0012521 0 ustar 00 # frozen_string_literal: true
# for backward compatibility
# :enddoc:
class Net::HTTP
ProxyMod = ProxyDelta
deprecate_constant :ProxyMod
end
module Net::NetPrivate
HTTPRequest = ::Net::HTTPRequest
deprecate_constant :HTTPRequest
end
module Net
HTTPSession = HTTP
HTTPInformationCode = HTTPInformation
HTTPSuccessCode = HTTPSuccess
HTTPRedirectionCode = HTTPRedirection
HTTPRetriableCode = HTTPRedirection
HTTPClientErrorCode = HTTPClientError
HTTPFatalErrorCode = HTTPClientError
HTTPServerErrorCode = HTTPServerError
HTTPResponseReceiver = HTTPResponse
HTTPResponceReceiver = HTTPResponse # Typo since 2001
deprecate_constant :HTTPSession,
:HTTPInformationCode,
:HTTPSuccessCode,
:HTTPRedirectionCode,
:HTTPRetriableCode,
:HTTPClientErrorCode,
:HTTPFatalErrorCode,
:HTTPServerErrorCode,
:HTTPResponseReceiver,
:HTTPResponceReceiver
end
share/ruby/net/http/responses.rb 0000644 00000116775 15173547337 0013002 0 ustar 00 # frozen_string_literal: true
#--
# https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
module Net
class HTTPUnknownResponse < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
# Parent class for informational (1xx) HTTP response classes.
#
# An informational response indicates that the request was received and understood.
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.1xx].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#1xx_informational_response].
#
class HTTPInformation < HTTPResponse
HAS_BODY = false
EXCEPTION_TYPE = HTTPError #
end
# Parent class for success (2xx) HTTP response classes.
#
# A success response indicates the action requested by the client
# was received, understood, and accepted.
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.2xx].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#2xx_success].
#
class HTTPSuccess < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = HTTPError #
end
# Parent class for redirection (3xx) HTTP response classes.
#
# A redirection response indicates the client must take additional action
# to complete the request.
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.3xx].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#3xx_redirection].
#
class HTTPRedirection < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = HTTPRetriableError #
end
# Parent class for client error (4xx) HTTP response classes.
#
# A client error response indicates that the client may have caused an error.
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.4xx].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#4xx_client_errors].
#
class HTTPClientError < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = HTTPClientException #
end
# Parent class for server error (5xx) HTTP response classes.
#
# A server error response indicates that the server failed to fulfill a request.
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#status.5xx].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#5xx_server_errors].
#
class HTTPServerError < HTTPResponse
HAS_BODY = true
EXCEPTION_TYPE = HTTPFatalError #
end
# Response class for +Continue+ responses (status code 100).
#
# A +Continue+ response indicates that the server has received the request headers.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/100].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-100-continue].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#100].
#
class HTTPContinue < HTTPInformation
HAS_BODY = false
end
# Response class for <tt>Switching Protocol</tt> responses (status code 101).
#
# The <tt>Switching Protocol<tt> response indicates that the server has received
# a request to switch protocols, and has agreed to do so.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/101].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-101-switching-protocols].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#101].
#
class HTTPSwitchProtocol < HTTPInformation
HAS_BODY = false
end
# Response class for +Processing+ responses (status code 102).
#
# The +Processing+ response indicates that the server has received
# and is processing the request, but no response is available yet.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 2518}[https://www.rfc-editor.org/rfc/rfc2518#section-10.1].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#102].
#
class HTTPProcessing < HTTPInformation
HAS_BODY = false
end
# Response class for <tt>Early Hints</tt> responses (status code 103).
#
# The <tt>Early Hints</tt> indicates that the server has received
# and is processing the request, and contains certain headers;
# the final response is not available yet.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/103].
# - {RFC 8297}[https://www.rfc-editor.org/rfc/rfc8297.html#section-2].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#103].
#
class HTTPEarlyHints < HTTPInformation
HAS_BODY = false
end
# Response class for +OK+ responses (status code 200).
#
# The +OK+ response indicates that the server has received
# a request and has responded successfully.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-200-ok].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#200].
#
class HTTPOK < HTTPSuccess
HAS_BODY = true
end
# Response class for +Created+ responses (status code 201).
#
# The +Created+ response indicates that the server has received
# and has fulfilled a request to create a new resource.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-201-created].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#201].
#
class HTTPCreated < HTTPSuccess
HAS_BODY = true
end
# Response class for +Accepted+ responses (status code 202).
#
# The +Accepted+ response indicates that the server has received
# and is processing a request, but the processing has not yet been completed.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/202].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-202-accepted].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#202].
#
class HTTPAccepted < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>Non-Authoritative Information</tt> responses (status code 203).
#
# The <tt>Non-Authoritative Information</tt> response indicates that the server
# is a transforming proxy (such as a Web accelerator)
# that received a 200 OK response from its origin,
# and is returning a modified version of the origin's response.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/203].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-203-non-authoritative-infor].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#203].
#
class HTTPNonAuthoritativeInformation < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>No Content</tt> responses (status code 204).
#
# The <tt>No Content</tt> response indicates that the server
# successfully processed the request, and is not returning any content.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/204].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-204-no-content].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#204].
#
class HTTPNoContent < HTTPSuccess
HAS_BODY = false
end
# Response class for <tt>Reset Content</tt> responses (status code 205).
#
# The <tt>Reset Content</tt> response indicates that the server
# successfully processed the request,
# asks that the client reset its document view, and is not returning any content.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/205].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-205-reset-content].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#205].
#
class HTTPResetContent < HTTPSuccess
HAS_BODY = false
end
# Response class for <tt>Partial Content</tt> responses (status code 206).
#
# The <tt>Partial Content</tt> response indicates that the server is delivering
# only part of the resource (byte serving)
# due to a Range header in the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/206].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-206-partial-content].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#206].
#
class HTTPPartialContent < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>Multi-Status (WebDAV)</tt> responses (status code 207).
#
# The <tt>Multi-Status (WebDAV)</tt> response indicates that the server
# has received the request,
# and that the message body can contain a number of separate response codes.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 4818}[https://www.rfc-editor.org/rfc/rfc4918#section-11.1].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#207].
#
class HTTPMultiStatus < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>Already Reported (WebDAV)</tt> responses (status code 208).
#
# The <tt>Already Reported (WebDAV)</tt> response indicates that the server
# has received the request,
# and that the members of a DAV binding have already been enumerated
# in a preceding part of the (multi-status) response,
# and are not being included again.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 5842}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.1].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#208].
#
class HTTPAlreadyReported < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>IM Used</tt> responses (status code 226).
#
# The <tt>IM Used</tt> response indicates that the server has fulfilled a request
# for the resource, and the response is a representation of the result
# of one or more instance-manipulations applied to the current instance.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 3229}[https://www.rfc-editor.org/rfc/rfc3229.html#section-10.4.1].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#226].
#
class HTTPIMUsed < HTTPSuccess
HAS_BODY = true
end
# Response class for <tt>Multiple Choices</tt> responses (status code 300).
#
# The <tt>Multiple Choices</tt> response indicates that the server
# offers multiple options for the resource from which the client may choose.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/300].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-300-multiple-choices].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#300].
#
class HTTPMultipleChoices < HTTPRedirection
HAS_BODY = true
end
HTTPMultipleChoice = HTTPMultipleChoices
# Response class for <tt>Moved Permanently</tt> responses (status code 301).
#
# The <tt>Moved Permanently</tt> response indicates that links or records
# returning this response should be updated to use the given URL.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/301].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-301-moved-permanently].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#301].
#
class HTTPMovedPermanently < HTTPRedirection
HAS_BODY = true
end
# Response class for <tt>Found</tt> responses (status code 302).
#
# The <tt>Found</tt> response indicates that the client
# should look at (browse to) another URL.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/302].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-302-found].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#302].
#
class HTTPFound < HTTPRedirection
HAS_BODY = true
end
HTTPMovedTemporarily = HTTPFound
# Response class for <tt>See Other</tt> responses (status code 303).
#
# The response to the request can be found under another URI using the GET method.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/303].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-303-see-other].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#303].
#
class HTTPSeeOther < HTTPRedirection
HAS_BODY = true
end
# Response class for <tt>Not Modified</tt> responses (status code 304).
#
# Indicates that the resource has not been modified since the version
# specified by the request headers.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/304].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-304-not-modified].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#304].
#
class HTTPNotModified < HTTPRedirection
HAS_BODY = false
end
# Response class for <tt>Use Proxy</tt> responses (status code 305).
#
# The requested resource is available only through a proxy,
# whose address is provided in the response.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-305-use-proxy].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#305].
#
class HTTPUseProxy < HTTPRedirection
HAS_BODY = false
end
# Response class for <tt>Temporary Redirect</tt> responses (status code 307).
#
# The request should be repeated with another URI;
# however, future requests should still use the original URI.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/307].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-307-temporary-redirect].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#307].
#
class HTTPTemporaryRedirect < HTTPRedirection
HAS_BODY = true
end
# Response class for <tt>Permanent Redirect</tt> responses (status code 308).
#
# This and all future requests should be directed to the given URI.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/308].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-308-permanent-redirect].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#308].
#
class HTTPPermanentRedirect < HTTPRedirection
HAS_BODY = true
end
# Response class for <tt>Bad Request</tt> responses (status code 400).
#
# The server cannot or will not process the request due to an apparent client error.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-400-bad-request].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#400].
#
class HTTPBadRequest < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Unauthorized</tt> responses (status code 401).
#
# Authentication is required, but either was not provided or failed.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-401-unauthorized].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#401].
#
class HTTPUnauthorized < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Payment Required</tt> responses (status code 402).
#
# Reserved for future use.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/402].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-402-payment-required].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#402].
#
class HTTPPaymentRequired < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Forbidden</tt> responses (status code 403).
#
# The request contained valid data and was understood by the server,
# but the server is refusing action.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/403].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-403-forbidden].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#403].
#
class HTTPForbidden < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Not Found</tt> responses (status code 404).
#
# The requested resource could not be found but may be available in the future.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-404-not-found].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#404].
#
class HTTPNotFound < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Method Not Allowed</tt> responses (status code 405).
#
# The request method is not supported for the requested resource.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/405].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-405-method-not-allowed].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#405].
#
class HTTPMethodNotAllowed < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Not Acceptable</tt> responses (status code 406).
#
# The requested resource is capable of generating only content
# that not acceptable according to the Accept headers sent in the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-406-not-acceptable].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#406].
#
class HTTPNotAcceptable < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Proxy Authentication Required</tt> responses (status code 407).
#
# The client must first authenticate itself with the proxy.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/407].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-407-proxy-authentication-re].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#407].
#
class HTTPProxyAuthenticationRequired < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Request Timeout</tt> responses (status code 408).
#
# The server timed out waiting for the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/408].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-408-request-timeout].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#408].
#
class HTTPRequestTimeout < HTTPClientError
HAS_BODY = true
end
HTTPRequestTimeOut = HTTPRequestTimeout
# Response class for <tt>Conflict</tt> responses (status code 409).
#
# The request could not be processed because of conflict in the current state of the resource.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/409].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-409-conflict].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#409].
#
class HTTPConflict < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Gone</tt> responses (status code 410).
#
# The resource requested was previously in use but is no longer available
# and will not be available again.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/410].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-410-gone].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#410].
#
class HTTPGone < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Length Required</tt> responses (status code 411).
#
# The request did not specify the length of its content,
# which is required by the requested resource.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/411].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-411-length-required].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#411].
#
class HTTPLengthRequired < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Precondition Failed</tt> responses (status code 412).
#
# The server does not meet one of the preconditions
# specified in the request headers.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/412].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-412-precondition-failed].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#412].
#
class HTTPPreconditionFailed < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Payload Too Large</tt> responses (status code 413).
#
# The request is larger than the server is willing or able to process.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/413].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-413-content-too-large].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#413].
#
class HTTPPayloadTooLarge < HTTPClientError
HAS_BODY = true
end
HTTPRequestEntityTooLarge = HTTPPayloadTooLarge
# Response class for <tt>URI Too Long</tt> responses (status code 414).
#
# The URI provided was too long for the server to process.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/414].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-414-uri-too-long].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#414].
#
class HTTPURITooLong < HTTPClientError
HAS_BODY = true
end
HTTPRequestURITooLong = HTTPURITooLong
HTTPRequestURITooLarge = HTTPRequestURITooLong
# Response class for <tt>Unsupported Media Type</tt> responses (status code 415).
#
# The request entity has a media type which the server or resource does not support.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-415-unsupported-media-type].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#415].
#
class HTTPUnsupportedMediaType < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Range Not Satisfiable</tt> responses (status code 416).
#
# The request entity has a media type which the server or resource does not support.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/416].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-416-range-not-satisfiable].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#416].
#
class HTTPRangeNotSatisfiable < HTTPClientError
HAS_BODY = true
end
HTTPRequestedRangeNotSatisfiable = HTTPRangeNotSatisfiable
# Response class for <tt>Expectation Failed</tt> responses (status code 417).
#
# The server cannot meet the requirements of the Expect request-header field.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/417].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-417-expectation-failed].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#417].
#
class HTTPExpectationFailed < HTTPClientError
HAS_BODY = true
end
# 418 I'm a teapot - RFC 2324; a joke RFC
# See https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#418.
# 420 Enhance Your Calm - Twitter
# Response class for <tt>Misdirected Request</tt> responses (status code 421).
#
# The request was directed at a server that is not able to produce a response.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-421-misdirected-request].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#421].
#
class HTTPMisdirectedRequest < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Unprocessable Entity</tt> responses (status code 422).
#
# The request was well-formed but had semantic errors.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/422].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-422-unprocessable-content].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#422].
#
class HTTPUnprocessableEntity < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Locked (WebDAV)</tt> responses (status code 423).
#
# The requested resource is locked.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.3].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#423].
#
class HTTPLocked < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Failed Dependency (WebDAV)</tt> responses (status code 424).
#
# The request failed because it depended on another request and that request failed.
# See {424 Failed Dependency (WebDAV)}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.4].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#424].
#
class HTTPFailedDependency < HTTPClientError
HAS_BODY = true
end
# 425 Too Early
# https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#425.
# Response class for <tt>Upgrade Required</tt> responses (status code 426).
#
# The client should switch to the protocol given in the Upgrade header field.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/426].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-426-upgrade-required].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#426].
#
class HTTPUpgradeRequired < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Precondition Required</tt> responses (status code 428).
#
# The origin server requires the request to be conditional.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/428].
# - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-3].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#428].
#
class HTTPPreconditionRequired < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Too Many Requests</tt> responses (status code 429).
#
# The user has sent too many requests in a given amount of time.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429].
# - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-4].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#429].
#
class HTTPTooManyRequests < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Request Header Fields Too Large</tt> responses (status code 431).
#
# An individual header field is too large,
# or all the header fields collectively, are too large.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/431].
# - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-5].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#431].
#
class HTTPRequestHeaderFieldsTooLarge < HTTPClientError
HAS_BODY = true
end
# Response class for <tt>Unavailable For Legal Reasons</tt> responses (status code 451).
#
# A server operator has received a legal demand to deny access to a resource or to a set of resources
# that includes the requested resource.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/451].
# - {RFC 7725}[https://www.rfc-editor.org/rfc/rfc7725.html#section-3].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#451].
#
class HTTPUnavailableForLegalReasons < HTTPClientError
HAS_BODY = true
end
# 444 No Response - Nginx
# 449 Retry With - Microsoft
# 450 Blocked by Windows Parental Controls - Microsoft
# 499 Client Closed Request - Nginx
# Response class for <tt>Internal Server Error</tt> responses (status code 500).
#
# An unexpected condition was encountered and no more specific message is suitable.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/500].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-500-internal-server-error].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#500].
#
class HTTPInternalServerError < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Not Implemented</tt> responses (status code 501).
#
# The server either does not recognize the request method,
# or it lacks the ability to fulfil the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-501-not-implemented].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#501].
#
class HTTPNotImplemented < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Bad Gateway</tt> responses (status code 502).
#
# The server was acting as a gateway or proxy
# and received an invalid response from the upstream server.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-502-bad-gateway].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#502].
#
class HTTPBadGateway < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Service Unavailable</tt> responses (status code 503).
#
# The server cannot handle the request
# (because it is overloaded or down for maintenance).
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/503].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-503-service-unavailable].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#503].
#
class HTTPServiceUnavailable < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Gateway Timeout</tt> responses (status code 504).
#
# The server was acting as a gateway or proxy
# and did not receive a timely response from the upstream server.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/504].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-504-gateway-timeout].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#504].
#
class HTTPGatewayTimeout < HTTPServerError
HAS_BODY = true
end
HTTPGatewayTimeOut = HTTPGatewayTimeout
# Response class for <tt>HTTP Version Not Supported</tt> responses (status code 505).
#
# The server does not support the HTTP version used in the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/505].
# - {RFC 9110}[https://www.rfc-editor.org/rfc/rfc9110.html#name-505-http-version-not-suppor].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#505].
#
class HTTPVersionNotSupported < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Variant Also Negotiates</tt> responses (status code 506).
#
# Transparent content negotiation for the request results in a circular reference.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/506].
# - {RFC 2295}[https://www.rfc-editor.org/rfc/rfc2295#section-8.1].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#506].
#
class HTTPVariantAlsoNegotiates < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Insufficient Storage (WebDAV)</tt> responses (status code 507).
#
# The server is unable to store the representation needed to complete the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/507].
# - {RFC 4918}[https://www.rfc-editor.org/rfc/rfc4918#section-11.5].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#507].
#
class HTTPInsufficientStorage < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Loop Detected (WebDAV)</tt> responses (status code 508).
#
# The server detected an infinite loop while processing the request.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/508].
# - {RFC 5942}[https://www.rfc-editor.org/rfc/rfc5842.html#section-7.2].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#508].
#
class HTTPLoopDetected < HTTPServerError
HAS_BODY = true
end
# 509 Bandwidth Limit Exceeded - Apache bw/limited extension
# Response class for <tt>Not Extended</tt> responses (status code 510).
#
# Further extensions to the request are required for the server to fulfill it.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/510].
# - {RFC 2774}[https://www.rfc-editor.org/rfc/rfc2774.html#section-7].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#510].
#
class HTTPNotExtended < HTTPServerError
HAS_BODY = true
end
# Response class for <tt>Network Authentication Required</tt> responses (status code 511).
#
# The client needs to authenticate to gain network access.
#
# :include: doc/net-http/included_getters.rdoc
#
# References:
#
# - {Mozilla}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/511].
# - {RFC 6585}[https://www.rfc-editor.org/rfc/rfc6585#section-6].
# - {Wikipedia}[https://en.wikipedia.org/wiki/List_of_HTTP_status_codes#511].
#
class HTTPNetworkAuthenticationRequired < HTTPServerError
HAS_BODY = true
end
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,
'103' => Net::HTTPEarlyHints,
'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::HTTPPayloadTooLarge,
'414' => Net::HTTPURITooLong,
'415' => Net::HTTPUnsupportedMediaType,
'416' => Net::HTTPRangeNotSatisfiable,
'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
share/ruby/net/protocol.rb 0000644 00000026772 15173547337 0011640 0 ustar 00 # 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$
#++
#
# 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
VERSION = "0.2.1"
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
def initialize(io = nil)
@io = io
end
attr_reader :io
def message
msg = super
if @io
msg = "#{msg} with #{@io.inspect}"
end
msg
end
end
##
# WriteTimeout, a subclass of Timeout::Error, is raised if a chunk of the
# response cannot be written within the write_timeout. Not raised on Windows.
class WriteTimeout < Timeout::Error
def initialize(io = nil)
@io = io
end
attr_reader :io
def message
msg = super
if @io
msg = "#{msg} with #{@io.inspect}"
end
msg
end
end
class BufferedIO #:nodoc: internal use only
def initialize(io, read_timeout: 60, write_timeout: 60, continue_timeout: nil, debug_output: nil)
@io = io
@read_timeout = read_timeout
@write_timeout = write_timeout
@continue_timeout = continue_timeout
@debug_output = debug_output
@rbuf = ''.b
@rbuf_empty = true
@rbuf_offset = 0
end
attr_reader :io
attr_accessor :read_timeout
attr_accessor :write_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 = ''.b, ignore_eof = false)
LOG "reading #{len} bytes..."
read_bytes = 0
begin
while read_bytes + rbuf_size < len
if s = rbuf_consume_all
read_bytes += s.bytesize
dest << s
end
rbuf_fill
end
s = rbuf_consume(len - read_bytes)
read_bytes += s.bytesize
dest << s
rescue EOFError
raise unless ignore_eof
end
LOG "read #{read_bytes} bytes"
dest
end
def read_all(dest = ''.b)
LOG 'reading all...'
read_bytes = 0
begin
while true
if s = rbuf_consume_all
read_bytes += s.bytesize
dest << s
end
rbuf_fill
end
rescue EOFError
;
end
LOG "read #{read_bytes} bytes"
dest
end
def readuntil(terminator, ignore_eof = false)
offset = @rbuf_offset
begin
until idx = @rbuf.index(terminator, offset)
offset = @rbuf.bytesize
rbuf_fill
end
return rbuf_consume(idx + terminator.bytesize - @rbuf_offset)
rescue EOFError
raise unless ignore_eof
return rbuf_consume
end
end
def readline
readuntil("\n").chop
end
private
BUFSIZE = 1024 * 16
def rbuf_fill
tmp = @rbuf_empty ? @rbuf : nil
case rv = @io.read_nonblock(BUFSIZE, tmp, exception: false)
when String
@rbuf_empty = false
if rv.equal?(tmp)
@rbuf_offset = 0
else
@rbuf << rv
rv.clear
end
return
when :wait_readable
(io = @io.to_io).wait_readable(@read_timeout) or raise Net::ReadTimeout.new(io)
# continue looping
when :wait_writable
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
# http://www.openssl.org/support/faq.html#PROG10
(io = @io.to_io).wait_writable(@read_timeout) or raise Net::ReadTimeout.new(io)
# continue looping
when nil
raise EOFError, 'end of file reached'
end while true
end
def rbuf_flush
if @rbuf_empty
@rbuf.clear
@rbuf_offset = 0
end
nil
end
def rbuf_size
@rbuf.bytesize - @rbuf_offset
end
def rbuf_consume_all
rbuf_consume if rbuf_size > 0
end
def rbuf_consume(len = nil)
if @rbuf_offset == 0 && (len.nil? || len == @rbuf.bytesize)
s = @rbuf
@rbuf = ''.b
@rbuf_offset = 0
@rbuf_empty = true
elsif len.nil?
s = @rbuf.byteslice(@rbuf_offset..-1)
@rbuf = ''.b
@rbuf_offset = 0
@rbuf_empty = true
else
s = @rbuf.byteslice(@rbuf_offset, len)
@rbuf_offset += len
@rbuf_empty = @rbuf_offset == @rbuf.bytesize
rbuf_flush
end
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
s
end
#
# Write
#
public
def write(*strs)
writing {
write0(*strs)
}
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(*strs)
@debug_output << strs.map(&:dump).join if @debug_output
orig_written_bytes = @written_bytes
strs.each_with_index do |str, i|
need_retry = true
case len = @io.write_nonblock(str, exception: false)
when Integer
@written_bytes += len
len -= str.bytesize
if len == 0
if strs.size == i+1
return @written_bytes - orig_written_bytes
else
need_retry = false
# next string
end
elsif len < 0
str = str.byteslice(len, -len)
else # len > 0
need_retry = false
# next string
end
# continue looping
when :wait_writable
(io = @io.to_io).wait_writable(@write_timeout) or raise Net::WriteTimeout.new(io)
# continue looping
end while need_retry
end
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.delete_prefix('.')
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.method(: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 = ''.b
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(writer)
@writer = writer
end
def inspect
"#<#{self.class} writer=#{@writer.inspect}>"
end
def write(str)
@writer.call(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
share/ruby/net/http.rb 0000644 00000245707 15173547337 0010757 0 ustar 00 # frozen_string_literal: true
#
# = net/http.rb
#
# Copyright (c) 1999-2007 Yukihiro Matsumoto
# Copyright (c) 1999-2007 Minero Aoki
# Copyright (c) 2001 GOTOU Yuuzou
#
# Written and maintained by Minero Aoki <aamine@loveruby.net>.
# HTTPS support added by GOTOU Yuuzou <gotoyuzo@notwork.org>.
#
# This file is derived from "http-access.rb".
#
# Documented by Minero Aoki; converted to RDoc by William Webber.
#
# This program is free software. You can re-distribute and/or
# modify this program under the same terms of ruby itself ---
# Ruby Distribution License or GNU General Public License.
#
# See Net::HTTP for an overview and examples.
#
require 'net/protocol'
require 'uri'
require 'resolv'
autoload :OpenSSL, 'openssl'
module Net #:nodoc:
# :stopdoc:
class HTTPBadResponse < StandardError; end
class HTTPHeaderSyntaxError < StandardError; end
# :startdoc:
# \Class \Net::HTTP provides a rich library that implements the client
# in a client-server model that uses the \HTTP request-response protocol.
# For information about \HTTP, see:
#
# - {Hypertext Transfer Protocol}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol].
# - {Technical overview}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Technical_overview].
#
# == About the Examples
#
# :include: doc/net-http/examples.rdoc
#
# == Strategies
#
# - If you will make only a few GET requests,
# consider using {OpenURI}[rdoc-ref:OpenURI].
# - If you will make only a few requests of all kinds,
# consider using the various singleton convenience methods in this class.
# Each of the following methods automatically starts and finishes
# a {session}[rdoc-ref:Net::HTTP@Sessions] that sends a single request:
#
# # Return string response body.
# Net::HTTP.get(hostname, path)
# Net::HTTP.get(uri)
#
# # Write string response body to $stdout.
# Net::HTTP.get_print(hostname, path)
# Net::HTTP.get_print(uri)
#
# # Return response as Net::HTTPResponse object.
# Net::HTTP.get_response(hostname, path)
# Net::HTTP.get_response(uri)
# data = '{"title": "foo", "body": "bar", "userId": 1}'
# Net::HTTP.post(uri, data)
# params = {title: 'foo', body: 'bar', userId: 1}
# Net::HTTP.post_form(uri, params)
#
# - If performance is important, consider using sessions, which lower request overhead.
# This {session}[rdoc-ref:Net::HTTP@Sessions] has multiple requests for
# {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]
# and {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]:
#
# Net::HTTP.start(hostname) do |http|
# # Session started automatically before block execution.
# http.get(path)
# http.head(path)
# body = 'Some text'
# http.post(path, body) # Can also have a block.
# http.put(path, body)
# http.delete(path)
# http.options(path)
# http.trace(path)
# http.patch(path, body) # Can also have a block.
# http.copy(path)
# http.lock(path, body)
# http.mkcol(path, body)
# http.move(path)
# http.propfind(path, body)
# http.proppatch(path, body)
# http.unlock(path, body)
# # Session finished automatically at block exit.
# end
#
# The methods cited above are convenience methods that, via their few arguments,
# allow minimal control over the requests.
# For greater control, consider using {request objects}[rdoc-ref:Net::HTTPRequest].
#
# == URIs
#
# On the internet, a URI
# ({Universal Resource Identifier}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier])
# is a string that identifies a particular resource.
# It consists of some or all of: scheme, hostname, path, query, and fragment;
# see {URI syntax}[https://en.wikipedia.org/wiki/Uniform_Resource_Identifier#Syntax].
#
# A Ruby {URI::Generic}[rdoc-ref:URI::Generic] object
# represents an internet URI.
# It provides, among others, methods
# +scheme+, +hostname+, +path+, +query+, and +fragment+.
#
# === Schemes
#
# An internet \URI has
# a {scheme}[https://en.wikipedia.org/wiki/List_of_URI_schemes].
#
# The two schemes supported in \Net::HTTP are <tt>'https'</tt> and <tt>'http'</tt>:
#
# uri.scheme # => "https"
# URI('http://example.com').scheme # => "http"
#
# === Hostnames
#
# A hostname identifies a server (host) to which requests may be sent:
#
# hostname = uri.hostname # => "jsonplaceholder.typicode.com"
# Net::HTTP.start(hostname) do |http|
# # Some HTTP stuff.
# end
#
# === Paths
#
# A host-specific path identifies a resource on the host:
#
# _uri = uri.dup
# _uri.path = '/todos/1'
# hostname = _uri.hostname
# path = _uri.path
# Net::HTTP.get(hostname, path)
#
# === Queries
#
# A host-specific query adds name/value pairs to the URI:
#
# _uri = uri.dup
# params = {userId: 1, completed: false}
# _uri.query = URI.encode_www_form(params)
# _uri # => #<URI::HTTPS https://jsonplaceholder.typicode.com?userId=1&completed=false>
# Net::HTTP.get(_uri)
#
# === Fragments
#
# A {URI fragment}[https://en.wikipedia.org/wiki/URI_fragment] has no effect
# in \Net::HTTP;
# the same data is returned, regardless of whether a fragment is included.
#
# == Request Headers
#
# Request headers may be used to pass additional information to the host,
# similar to arguments passed in a method call;
# each header is a name/value pair.
#
# Each of the \Net::HTTP methods that sends a request to the host
# has optional argument +headers+,
# where the headers are expressed as a hash of field-name/value pairs:
#
# headers = {Accept: 'application/json', Connection: 'Keep-Alive'}
# Net::HTTP.get(uri, headers)
#
# See lists of both standard request fields and common request fields at
# {Request Fields}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#Request_fields].
# A host may also accept other custom fields.
#
# == \HTTP Sessions
#
# A _session_ is a connection between a server (host) and a client that:
#
# - Is begun by instance method Net::HTTP#start.
# - May contain any number of requests.
# - Is ended by instance method Net::HTTP#finish.
#
# See example sessions at {Strategies}[rdoc-ref:Net::HTTP@Strategies].
#
# === Session Using \Net::HTTP.start
#
# If you have many requests to make to a single host (and port),
# consider using singleton method Net::HTTP.start with a block;
# the method handles the session automatically by:
#
# - Calling #start before block execution.
# - Executing the block.
# - Calling #finish after block execution.
#
# In the block, you can use these instance methods,
# each of which that sends a single request:
#
# - {HTTP methods}[https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods]:
#
# - #get, #request_get: GET.
# - #head, #request_head: HEAD.
# - #post, #request_post: POST.
# - #delete: DELETE.
# - #options: OPTIONS.
# - #trace: TRACE.
# - #patch: PATCH.
#
# - {WebDAV methods}[https://en.wikipedia.org/wiki/WebDAV#Implementation]:
#
# - #copy: COPY.
# - #lock: LOCK.
# - #mkcol: MKCOL.
# - #move: MOVE.
# - #propfind: PROPFIND.
# - #proppatch: PROPPATCH.
# - #unlock: UNLOCK.
#
# === Session Using \Net::HTTP.start and \Net::HTTP.finish
#
# You can manage a session manually using methods #start and #finish:
#
# http = Net::HTTP.new(hostname)
# http.start
# http.get('/todos/1')
# http.get('/todos/2')
# http.delete('/posts/1')
# http.finish # Needed to free resources.
#
# === Single-Request Session
#
# Certain convenience methods automatically handle a session by:
#
# - Creating an \HTTP object
# - Starting a session.
# - Sending a single request.
# - Finishing the session.
# - Destroying the object.
#
# Such methods that send GET requests:
#
# - ::get: Returns the string response body.
# - ::get_print: Writes the string response body to $stdout.
# - ::get_response: Returns a Net::HTTPResponse object.
#
# Such methods that send POST requests:
#
# - ::post: Posts data to the host.
# - ::post_form: Posts form data to the host.
#
# == \HTTP Requests and Responses
#
# Many of the methods above are convenience methods,
# each of which sends a request and returns a string
# without directly using \Net::HTTPRequest and \Net::HTTPResponse objects.
#
# You can, however, directly create a request object, send the request,
# and retrieve the response object; see:
#
# - Net::HTTPRequest.
# - Net::HTTPResponse.
#
# == Following Redirection
#
# Each returned response is an instance of a subclass of Net::HTTPResponse.
# See the {response class hierarchy}[rdoc-ref:Net::HTTPResponse@Response+Subclasses].
#
# In particular, class Net::HTTPRedirection is the parent
# of all redirection classes.
# This allows you to craft a case statement to handle redirections properly:
#
# def fetch(uri, limit = 10)
# # You should choose a better exception.
# raise ArgumentError, 'Too many HTTP redirects' if limit == 0
#
# res = Net::HTTP.get_response(URI(uri))
# case res
# when Net::HTTPSuccess # Any success class.
# res
# when Net::HTTPRedirection # Any redirection class.
# location = res['Location']
# warn "Redirected to #{location}"
# fetch(location, limit - 1)
# else # Any other class.
# res.value
# end
# end
#
# fetch(uri)
#
# == Basic Authentication
#
# Basic authentication is performed according to
# {RFC2617}[http://www.ietf.org/rfc/rfc2617.txt]:
#
# req = Net::HTTP::Get.new(uri)
# req.basic_auth('user', 'pass')
# res = Net::HTTP.start(hostname) do |http|
# http.request(req)
# end
#
# == Streaming Response Bodies
#
# By default \Net::HTTP reads an entire response into memory. If you are
# handling large files or wish to implement a progress bar you can instead
# stream the body directly to an IO.
#
# Net::HTTP.start(hostname) do |http|
# req = Net::HTTP::Get.new(uri)
# http.request(req) do |res|
# open('t.tmp', 'w') do |f|
# res.read_body do |chunk|
# f.write chunk
# end
# end
# end
# end
#
# == HTTPS
#
# HTTPS is enabled for an \HTTP connection by Net::HTTP#use_ssl=:
#
# Net::HTTP.start(hostname, :use_ssl => true) do |http|
# req = Net::HTTP::Get.new(uri)
# res = http.request(req)
# end
#
# Or if you simply want to make a GET request, you may pass in a URI
# object that has an \HTTPS URL. \Net::HTTP automatically turns on TLS
# verification if the URI object has a 'https' URI scheme:
#
# uri # => #<URI::HTTPS https://jsonplaceholder.typicode.com/>
# Net::HTTP.get(uri)
#
# == Proxy Server
#
# An \HTTP object can have
# a {proxy server}[https://en.wikipedia.org/wiki/Proxy_server].
#
# You can create an \HTTP object with a proxy server
# using method Net::HTTP.new or method Net::HTTP.start.
#
# The proxy may be defined either by argument +p_addr+
# or by environment variable <tt>'http_proxy'</tt>.
#
# === Proxy Using Argument +p_addr+ as a \String
#
# When argument +p_addr+ is a string hostname,
# the returned +http+ has the given host as its proxy:
#
# http = Net::HTTP.new(hostname, nil, 'proxy.example')
# http.proxy? # => true
# http.proxy_from_env? # => false
# http.proxy_address # => "proxy.example"
# # These use default values.
# http.proxy_port # => 80
# http.proxy_user # => nil
# http.proxy_pass # => nil
#
# The port, username, and password for the proxy may also be given:
#
# http = Net::HTTP.new(hostname, nil, 'proxy.example', 8000, 'pname', 'ppass')
# # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false>
# http.proxy? # => true
# http.proxy_from_env? # => false
# http.proxy_address # => "proxy.example"
# http.proxy_port # => 8000
# http.proxy_user # => "pname"
# http.proxy_pass # => "ppass"
#
# === Proxy Using '<tt>ENV['http_proxy']</tt>'
#
# When environment variable <tt>'http_proxy'</tt>
# is set to a \URI string,
# the returned +http+ will have the server at that URI as its proxy;
# note that the \URI string must have a protocol
# such as <tt>'http'</tt> or <tt>'https'</tt>:
#
# ENV['http_proxy'] = 'http://example.com'
# http = Net::HTTP.new(hostname)
# http.proxy? # => true
# http.proxy_from_env? # => true
# http.proxy_address # => "example.com"
# # These use default values.
# http.proxy_port # => 80
# http.proxy_user # => nil
# http.proxy_pass # => nil
#
# The \URI string may include proxy username, password, and port number:
#
# ENV['http_proxy'] = 'http://pname:ppass@example.com:8000'
# http = Net::HTTP.new(hostname)
# http.proxy? # => true
# http.proxy_from_env? # => true
# http.proxy_address # => "example.com"
# http.proxy_port # => 8000
# http.proxy_user # => "pname"
# http.proxy_pass # => "ppass"
#
# === Filtering Proxies
#
# With method Net::HTTP.new (but not Net::HTTP.start),
# you can use argument +p_no_proxy+ to filter proxies:
#
# - Reject a certain address:
#
# http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example')
# http.proxy_address # => nil
#
# - Reject certain domains or subdomains:
#
# http = Net::HTTP.new('example.com', nil, 'my.proxy.example', 8000, 'pname', 'ppass', 'proxy.example')
# http.proxy_address # => nil
#
# - Reject certain addresses and port combinations:
#
# http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:1234')
# http.proxy_address # => "proxy.example"
#
# http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'proxy.example:8000')
# http.proxy_address # => nil
#
# - Reject a list of the types above delimited using a comma:
#
# http = Net::HTTP.new('example.com', nil, 'proxy.example', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000')
# http.proxy_address # => nil
#
# http = Net::HTTP.new('example.com', nil, 'my.proxy', 8000, 'pname', 'ppass', 'my.proxy,proxy.example:8000')
# http.proxy_address # => nil
#
# == Compression and Decompression
#
# \Net::HTTP does not compress the body of a request before sending.
#
# By default, \Net::HTTP adds header <tt>'Accept-Encoding'</tt>
# to a new {request object}[rdoc-ref:Net::HTTPRequest]:
#
# Net::HTTP::Get.new(uri)['Accept-Encoding']
# # => "gzip;q=1.0,deflate;q=0.6,identity;q=0.3"
#
# This requests the server to zip-encode the response body if there is one;
# the server is not required to do so.
#
# \Net::HTTP does not automatically decompress a response body
# if the response has header <tt>'Content-Range'</tt>.
#
# Otherwise decompression (or not) depends on the value of header
# {Content-Encoding}[https://en.wikipedia.org/wiki/List_of_HTTP_header_fields#content-encoding-response-header]:
#
# - <tt>'deflate'</tt>, <tt>'gzip'</tt>, or <tt>'x-gzip'</tt>:
# decompresses the body and deletes the header.
# - <tt>'none'</tt> or <tt>'identity'</tt>:
# does not decompress the body, but deletes the header.
# - Any other value:
# leaves the body and header unchanged.
#
# == What's Here
#
# This is a categorized summary of methods and attributes.
#
# === \Net::HTTP Objects
#
# - {::new}[rdoc-ref:Net::HTTP.new]:
# Creates a new instance.
# - {#inspect}[rdoc-ref:Net::HTTP#inspect]:
# Returns a string representation of +self+.
#
# === Sessions
#
# - {::start}[rdoc-ref:Net::HTTP.start]:
# Begins a new session in a new \Net::HTTP object.
# - {#started?}[rdoc-ref:Net::HTTP#started?]
# (aliased as {#active?}[rdoc-ref:Net::HTTP#active?]):
# Returns whether in a session.
# - {#finish}[rdoc-ref:Net::HTTP#finish]:
# Ends an active session.
# - {#start}[rdoc-ref:Net::HTTP#start]:
# Begins a new session in an existing \Net::HTTP object (+self+).
#
# === Connections
#
# - {:continue_timeout}[rdoc-ref:Net::HTTP#continue_timeout]:
# Returns the continue timeout.
# - {#continue_timeout=}[rdoc-ref:Net::HTTP#continue_timeout=]:
# Sets the continue timeout seconds.
# - {:keep_alive_timeout}[rdoc-ref:Net::HTTP#keep_alive_timeout]:
# Returns the keep-alive timeout.
# - {:keep_alive_timeout=}[rdoc-ref:Net::HTTP#keep_alive_timeout=]:
# Sets the keep-alive timeout.
# - {:max_retries}[rdoc-ref:Net::HTTP#max_retries]:
# Returns the maximum retries.
# - {#max_retries=}[rdoc-ref:Net::HTTP#max_retries=]:
# Sets the maximum retries.
# - {:open_timeout}[rdoc-ref:Net::HTTP#open_timeout]:
# Returns the open timeout.
# - {:open_timeout=}[rdoc-ref:Net::HTTP#open_timeout=]:
# Sets the open timeout.
# - {:read_timeout}[rdoc-ref:Net::HTTP#read_timeout]:
# Returns the open timeout.
# - {:read_timeout=}[rdoc-ref:Net::HTTP#read_timeout=]:
# Sets the read timeout.
# - {:ssl_timeout}[rdoc-ref:Net::HTTP#ssl_timeout]:
# Returns the ssl timeout.
# - {:ssl_timeout=}[rdoc-ref:Net::HTTP#ssl_timeout=]:
# Sets the ssl timeout.
# - {:write_timeout}[rdoc-ref:Net::HTTP#write_timeout]:
# Returns the write timeout.
# - {write_timeout=}[rdoc-ref:Net::HTTP#write_timeout=]:
# Sets the write timeout.
#
# === Requests
#
# - {::get}[rdoc-ref:Net::HTTP.get]:
# Sends a GET request and returns the string response body.
# - {::get_print}[rdoc-ref:Net::HTTP.get_print]:
# Sends a GET request and write the string response body to $stdout.
# - {::get_response}[rdoc-ref:Net::HTTP.get_response]:
# Sends a GET request and returns a response object.
# - {::post_form}[rdoc-ref:Net::HTTP.post_form]:
# Sends a POST request with form data and returns a response object.
# - {::post}[rdoc-ref:Net::HTTP.post]:
# Sends a POST request with data and returns a response object.
# - {#copy}[rdoc-ref:Net::HTTP#copy]:
# Sends a COPY request and returns a response object.
# - {#delete}[rdoc-ref:Net::HTTP#delete]:
# Sends a DELETE request and returns a response object.
# - {#get}[rdoc-ref:Net::HTTP#get]:
# Sends a GET request and returns a response object.
# - {#head}[rdoc-ref:Net::HTTP#head]:
# Sends a HEAD request and returns a response object.
# - {#lock}[rdoc-ref:Net::HTTP#lock]:
# Sends a LOCK request and returns a response object.
# - {#mkcol}[rdoc-ref:Net::HTTP#mkcol]:
# Sends a MKCOL request and returns a response object.
# - {#move}[rdoc-ref:Net::HTTP#move]:
# Sends a MOVE request and returns a response object.
# - {#options}[rdoc-ref:Net::HTTP#options]:
# Sends a OPTIONS request and returns a response object.
# - {#patch}[rdoc-ref:Net::HTTP#patch]:
# Sends a PATCH request and returns a response object.
# - {#post}[rdoc-ref:Net::HTTP#post]:
# Sends a POST request and returns a response object.
# - {#propfind}[rdoc-ref:Net::HTTP#propfind]:
# Sends a PROPFIND request and returns a response object.
# - {#proppatch}[rdoc-ref:Net::HTTP#proppatch]:
# Sends a PROPPATCH request and returns a response object.
# - {#put}[rdoc-ref:Net::HTTP#put]:
# Sends a PUT request and returns a response object.
# - {#request}[rdoc-ref:Net::HTTP#request]:
# Sends a request and returns a response object.
# - {#request_get}[rdoc-ref:Net::HTTP#request_get]
# (aliased as {#get2}[rdoc-ref:Net::HTTP#get2]):
# Sends a GET request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
# - {#request_head}[rdoc-ref:Net::HTTP#request_head]
# (aliased as {#head2}[rdoc-ref:Net::HTTP#head2]):
# Sends a HEAD request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
# - {#request_post}[rdoc-ref:Net::HTTP#request_post]
# (aliased as {#post2}[rdoc-ref:Net::HTTP#post2]):
# Sends a POST request and forms a response object;
# if a block given, calls the block with the object,
# otherwise returns the object.
# - {#send_request}[rdoc-ref:Net::HTTP#send_request]:
# Sends a request and returns a response object.
# - {#trace}[rdoc-ref:Net::HTTP#trace]:
# Sends a TRACE request and returns a response object.
# - {#unlock}[rdoc-ref:Net::HTTP#unlock]:
# Sends an UNLOCK request and returns a response object.
#
# === Responses
#
# - {:close_on_empty_response}[rdoc-ref:Net::HTTP#close_on_empty_response]:
# Returns whether to close connection on empty response.
# - {:close_on_empty_response=}[rdoc-ref:Net::HTTP#close_on_empty_response=]:
# Sets whether to close connection on empty response.
# - {:ignore_eof}[rdoc-ref:Net::HTTP#ignore_eof]:
# Returns whether to ignore end-of-file when reading a response body
# with <tt>Content-Length</tt> headers.
# - {:ignore_eof=}[rdoc-ref:Net::HTTP#ignore_eof=]:
# Sets whether to ignore end-of-file when reading a response body
# with <tt>Content-Length</tt> headers.
# - {:response_body_encoding}[rdoc-ref:Net::HTTP#response_body_encoding]:
# Returns the encoding to use for the response body.
# - {#response_body_encoding=}[rdoc-ref:Net::HTTP#response_body_encoding=]:
# Sets the response body encoding.
#
# === Proxies
#
# - {:proxy_address}[rdoc-ref:Net::HTTP#proxy_address]:
# Returns the proxy address.
# - {:proxy_address=}[rdoc-ref:Net::HTTP#proxy_address=]:
# Sets the proxy address.
# - {::proxy_class?}[rdoc-ref:Net::HTTP.proxy_class?]:
# Returns whether +self+ is a proxy class.
# - {#proxy?}[rdoc-ref:Net::HTTP#proxy?]:
# Returns whether +self+ has a proxy.
# - {#proxy_address}[rdoc-ref:Net::HTTP#proxy_address]
# (aliased as {#proxyaddr}[rdoc-ref:Net::HTTP#proxyaddr]):
# Returns the proxy address.
# - {#proxy_from_env?}[rdoc-ref:Net::HTTP#proxy_from_env?]:
# Returns whether the proxy is taken from an environment variable.
# - {:proxy_from_env=}[rdoc-ref:Net::HTTP#proxy_from_env=]:
# Sets whether the proxy is to be taken from an environment variable.
# - {:proxy_pass}[rdoc-ref:Net::HTTP#proxy_pass]:
# Returns the proxy password.
# - {:proxy_pass=}[rdoc-ref:Net::HTTP#proxy_pass=]:
# Sets the proxy password.
# - {:proxy_port}[rdoc-ref:Net::HTTP#proxy_port]:
# Returns the proxy port.
# - {:proxy_port=}[rdoc-ref:Net::HTTP#proxy_port=]:
# Sets the proxy port.
# - {#proxy_user}[rdoc-ref:Net::HTTP#proxy_user]:
# Returns the proxy user name.
# - {:proxy_user=}[rdoc-ref:Net::HTTP#proxy_user=]:
# Sets the proxy user.
#
# === Security
#
# - {:ca_file}[rdoc-ref:Net::HTTP#ca_file]:
# Returns the path to a CA certification file.
# - {:ca_file=}[rdoc-ref:Net::HTTP#ca_file=]:
# Sets the path to a CA certification file.
# - {:ca_path}[rdoc-ref:Net::HTTP#ca_path]:
# Returns the path of to CA directory containing certification files.
# - {:ca_path=}[rdoc-ref:Net::HTTP#ca_path=]:
# Sets the path of to CA directory containing certification files.
# - {:cert}[rdoc-ref:Net::HTTP#cert]:
# Returns the OpenSSL::X509::Certificate object to be used for client certification.
# - {:cert=}[rdoc-ref:Net::HTTP#cert=]:
# Sets the OpenSSL::X509::Certificate object to be used for client certification.
# - {:cert_store}[rdoc-ref:Net::HTTP#cert_store]:
# Returns the X509::Store to be used for verifying peer certificate.
# - {:cert_store=}[rdoc-ref:Net::HTTP#cert_store=]:
# Sets the X509::Store to be used for verifying peer certificate.
# - {:ciphers}[rdoc-ref:Net::HTTP#ciphers]:
# Returns the available SSL ciphers.
# - {:ciphers=}[rdoc-ref:Net::HTTP#ciphers=]:
# Sets the available SSL ciphers.
# - {:extra_chain_cert}[rdoc-ref:Net::HTTP#extra_chain_cert]:
# Returns the extra X509 certificates to be added to the certificate chain.
# - {:extra_chain_cert=}[rdoc-ref:Net::HTTP#extra_chain_cert=]:
# Sets the extra X509 certificates to be added to the certificate chain.
# - {:key}[rdoc-ref:Net::HTTP#key]:
# Returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
# - {:key=}[rdoc-ref:Net::HTTP#key=]:
# Sets the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
# - {:max_version}[rdoc-ref:Net::HTTP#max_version]:
# Returns the maximum SSL version.
# - {:max_version=}[rdoc-ref:Net::HTTP#max_version=]:
# Sets the maximum SSL version.
# - {:min_version}[rdoc-ref:Net::HTTP#min_version]:
# Returns the minimum SSL version.
# - {:min_version=}[rdoc-ref:Net::HTTP#min_version=]:
# Sets the minimum SSL version.
# - {#peer_cert}[rdoc-ref:Net::HTTP#peer_cert]:
# Returns the X509 certificate chain for the session's socket peer.
# - {:ssl_version}[rdoc-ref:Net::HTTP#ssl_version]:
# Returns the SSL version.
# - {:ssl_version=}[rdoc-ref:Net::HTTP#ssl_version=]:
# Sets the SSL version.
# - {#use_ssl=}[rdoc-ref:Net::HTTP#use_ssl=]:
# Sets whether a new session is to use Transport Layer Security.
# - {#use_ssl?}[rdoc-ref:Net::HTTP#use_ssl?]:
# Returns whether +self+ uses SSL.
# - {:verify_callback}[rdoc-ref:Net::HTTP#verify_callback]:
# Returns the callback for the server certification verification.
# - {:verify_callback=}[rdoc-ref:Net::HTTP#verify_callback=]:
# Sets the callback for the server certification verification.
# - {:verify_depth}[rdoc-ref:Net::HTTP#verify_depth]:
# Returns the maximum depth for the certificate chain verification.
# - {:verify_depth=}[rdoc-ref:Net::HTTP#verify_depth=]:
# Sets the maximum depth for the certificate chain verification.
# - {:verify_hostname}[rdoc-ref:Net::HTTP#verify_hostname]:
# Returns the flags for server the certification verification at the beginning of the SSL/TLS session.
# - {:verify_hostname=}[rdoc-ref:Net::HTTP#verify_hostname=]:
# Sets he flags for server the certification verification at the beginning of the SSL/TLS session.
# - {:verify_mode}[rdoc-ref:Net::HTTP#verify_mode]:
# Returns the flags for server the certification verification at the beginning of the SSL/TLS session.
# - {:verify_mode=}[rdoc-ref:Net::HTTP#verify_mode=]:
# Sets the flags for server the certification verification at the beginning of the SSL/TLS session.
#
# === Addresses and Ports
#
# - {:address}[rdoc-ref:Net::HTTP#address]:
# Returns the string host name or host IP.
# - {::default_port}[rdoc-ref:Net::HTTP.default_port]:
# Returns integer 80, the default port to use for HTTP requests.
# - {::http_default_port}[rdoc-ref:Net::HTTP.http_default_port]:
# Returns integer 80, the default port to use for HTTP requests.
# - {::https_default_port}[rdoc-ref:Net::HTTP.https_default_port]:
# Returns integer 443, the default port to use for HTTPS requests.
# - {#ipaddr}[rdoc-ref:Net::HTTP#ipaddr]:
# Returns the IP address for the connection.
# - {#ipaddr=}[rdoc-ref:Net::HTTP#ipaddr=]:
# Sets the IP address for the connection.
# - {:local_host}[rdoc-ref:Net::HTTP#local_host]:
# Returns the string local host used to establish the connection.
# - {:local_host=}[rdoc-ref:Net::HTTP#local_host=]:
# Sets the string local host used to establish the connection.
# - {:local_port}[rdoc-ref:Net::HTTP#local_port]:
# Returns the integer local port used to establish the connection.
# - {:local_port=}[rdoc-ref:Net::HTTP#local_port=]:
# Sets the integer local port used to establish the connection.
# - {:port}[rdoc-ref:Net::HTTP#port]:
# Returns the integer port number.
#
# === \HTTP Version
#
# - {::version_1_2?}[rdoc-ref:Net::HTTP.version_1_2?]
# (aliased as {::is_version_1_2?}[rdoc-ref:Net::HTTP.is_version_1_2?]
# and {::version_1_2}[rdoc-ref:Net::HTTP.version_1_2]):
# Returns true; retained for compatibility.
#
# === Debugging
#
# - {#set_debug_output}[rdoc-ref:Net::HTTP#set_debug_output]:
# Sets the output stream for debugging.
#
class HTTP < Protocol
# :stopdoc:
VERSION = "0.4.1"
HTTPVersion = '1.1'
begin
require 'zlib'
HAVE_ZLIB=true
rescue LoadError
HAVE_ZLIB=false
end
# :startdoc:
# Returns +true+; retained for compatibility.
def HTTP.version_1_2
true
end
# Returns +true+; retained for compatibility.
def HTTP.version_1_2?
true
end
# Returns +false+; retained for compatibility.
def HTTP.version_1_1? #:nodoc:
false
end
class << HTTP
alias is_version_1_1? version_1_1? #:nodoc:
alias is_version_1_2? version_1_2? #:nodoc:
end
# :call-seq:
# Net::HTTP.get_print(hostname, path, port = 80) -> nil
# Net::HTTP:get_print(uri, headers = {}, port = uri.port) -> nil
#
# Like Net::HTTP.get, but writes the returned body to $stdout;
# returns +nil+.
def HTTP.get_print(uri_or_host, path_or_headers = nil, port = nil)
get_response(uri_or_host, path_or_headers, port) {|res|
res.read_body do |chunk|
$stdout.print chunk
end
}
nil
end
# :call-seq:
# Net::HTTP.get(hostname, path, port = 80) -> body
# Net::HTTP:get(uri, headers = {}, port = uri.port) -> body
#
# Sends a GET request and returns the \HTTP response body as a string.
#
# With string arguments +hostname+ and +path+:
#
# hostname = 'jsonplaceholder.typicode.com'
# path = '/todos/1'
# puts Net::HTTP.get(hostname, path)
#
# Output:
#
# {
# "userId": 1,
# "id": 1,
# "title": "delectus aut autem",
# "completed": false
# }
#
# With URI object +uri+ and optional hash argument +headers+:
#
# uri = URI('https://jsonplaceholder.typicode.com/todos/1')
# headers = {'Content-type' => 'application/json; charset=UTF-8'}
# Net::HTTP.get(uri, headers)
#
# Related:
#
# - Net::HTTP::Get: request class for \HTTP method +GET+.
# - Net::HTTP#get: convenience method for \HTTP method +GET+.
#
def HTTP.get(uri_or_host, path_or_headers = nil, port = nil)
get_response(uri_or_host, path_or_headers, port).body
end
# :call-seq:
# Net::HTTP.get_response(hostname, path, port = 80) -> http_response
# Net::HTTP:get_response(uri, headers = {}, port = uri.port) -> http_response
#
# Like Net::HTTP.get, but returns a Net::HTTPResponse object
# instead of the body string.
def HTTP.get_response(uri_or_host, path_or_headers = nil, port = nil, &block)
if path_or_headers && !path_or_headers.is_a?(Hash)
host = uri_or_host
path = path_or_headers
new(host, port || HTTP.default_port).start {|http|
return http.request_get(path, &block)
}
else
uri = uri_or_host
headers = path_or_headers
start(uri.hostname, uri.port,
:use_ssl => uri.scheme == 'https') {|http|
return http.request_get(uri, headers, &block)
}
end
end
# Posts data to a host; returns a Net::HTTPResponse object.
#
# Argument +url+ must be a URL;
# argument +data+ must be a string:
#
# _uri = uri.dup
# _uri.path = '/posts'
# data = '{"title": "foo", "body": "bar", "userId": 1}'
# headers = {'content-type': 'application/json'}
# res = Net::HTTP.post(_uri, data, headers) # => #<Net::HTTPCreated 201 Created readbody=true>
# puts res.body
#
# Output:
#
# {
# "title": "foo",
# "body": "bar",
# "userId": 1,
# "id": 101
# }
#
# Related:
#
# - Net::HTTP::Post: request class for \HTTP method +POST+.
# - Net::HTTP#post: convenience method for \HTTP method +POST+.
#
def HTTP.post(url, data, header = nil)
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.post(url, data, header)
}
end
# Posts data to a host; returns a Net::HTTPResponse object.
#
# Argument +url+ must be a URI;
# argument +data+ must be a hash:
#
# _uri = uri.dup
# _uri.path = '/posts'
# data = {title: 'foo', body: 'bar', userId: 1}
# res = Net::HTTP.post_form(_uri, data) # => #<Net::HTTPCreated 201 Created readbody=true>
# puts res.body
#
# Output:
#
# {
# "title": "foo",
# "body": "bar",
# "userId": "1",
# "id": 101
# }
#
def HTTP.post_form(url, params)
req = Post.new(url)
req.form_data = params
req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.request(req)
}
end
#
# \HTTP session management
#
# Returns integer +80+, the default port to use for \HTTP requests:
#
# Net::HTTP.default_port # => 80
#
def HTTP.default_port
http_default_port()
end
# Returns integer +80+, the default port to use for \HTTP requests:
#
# Net::HTTP.http_default_port # => 80
#
def HTTP.http_default_port
80
end
# Returns integer +443+, the default port to use for HTTPS requests:
#
# Net::HTTP.https_default_port # => 443
#
def HTTP.https_default_port
443
end
def HTTP.socket_type #:nodoc: obsolete
BufferedIO
end
# :call-seq:
# HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) -> http
# HTTP.start(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, opts) {|http| ... } -> object
#
# Creates a new \Net::HTTP object, +http+, via \Net::HTTP.new:
#
# - For arguments +address+ and +port+, see Net::HTTP.new.
# - For proxy-defining arguments +p_addr+ through +p_pass+,
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
# - For argument +opts+, see below.
#
# With no block given:
#
# - Calls <tt>http.start</tt> with no block (see #start),
# which opens a TCP connection and \HTTP session.
# - Returns +http+.
# - The caller should call #finish to close the session:
#
# http = Net::HTTP.start(hostname)
# http.started? # => true
# http.finish
# http.started? # => false
#
# With a block given:
#
# - Calls <tt>http.start</tt> with the block (see #start), which:
#
# - Opens a TCP connection and \HTTP session.
# - Calls the block,
# which may make any number of requests to the host.
# - Closes the \HTTP session and TCP connection on block exit.
# - Returns the block's value +object+.
#
# - Returns +object+.
#
# Example:
#
# hostname = 'jsonplaceholder.typicode.com'
# Net::HTTP.start(hostname) do |http|
# puts http.get('/todos/1').body
# puts http.get('/todos/2').body
# end
#
# Output:
#
# {
# "userId": 1,
# "id": 1,
# "title": "delectus aut autem",
# "completed": false
# }
# {
# "userId": 1,
# "id": 2,
# "title": "quis ut nam facilis et officia qui",
# "completed": false
# }
#
# If the last argument given is a hash, it is the +opts+ hash,
# where each key is a method or accessor to be called,
# and its value is the value to be set.
#
# The keys may include:
#
# - #ca_file
# - #ca_path
# - #cert
# - #cert_store
# - #ciphers
# - #close_on_empty_response
# - +ipaddr+ (calls #ipaddr=)
# - #keep_alive_timeout
# - #key
# - #open_timeout
# - #read_timeout
# - #ssl_timeout
# - #ssl_version
# - +use_ssl+ (calls #use_ssl=)
# - #verify_callback
# - #verify_depth
# - #verify_mode
# - #write_timeout
#
# Note: If +port+ is +nil+ and <tt>opts[:use_ssl]</tt> is a truthy value,
# the value passed to +new+ is Net::HTTP.https_default_port, not +port+.
#
def HTTP.start(address, *arg, &block) # :yield: +http+
arg.pop if opt = Hash.try_convert(arg[-1])
port, p_addr, p_port, p_user, p_pass = *arg
p_addr = :ENV if arg.size < 2
port = https_default_port if !port && opt && opt[:use_ssl]
http = new(address, port, p_addr, p_port, p_user, p_pass)
http.ipaddr = opt[:ipaddr] if opt && opt[:ipaddr]
if opt
if opt[:use_ssl]
opt = {verify_mode: OpenSSL::SSL::VERIFY_PEER}.update(opt)
end
http.methods.grep(/\A(\w+)=\z/) do |meth|
key = $1.to_sym
opt.key?(key) or next
http.__send__(meth, opt[key])
end
end
http.start(&block)
end
class << HTTP
alias newobj new # :nodoc:
end
# Returns a new \Net::HTTP object +http+
# (but does not open a TCP connection or \HTTP session).
#
# With only string argument +address+ given
# (and <tt>ENV['http_proxy']</tt> undefined or +nil+),
# the returned +http+:
#
# - Has the given address.
# - Has the default port number, Net::HTTP.default_port (80).
# - Has no proxy.
#
# Example:
#
# http = Net::HTTP.new(hostname)
# # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false>
# http.address # => "jsonplaceholder.typicode.com"
# http.port # => 80
# http.proxy? # => false
#
# With integer argument +port+ also given,
# the returned +http+ has the given port:
#
# http = Net::HTTP.new(hostname, 8000)
# # => #<Net::HTTP jsonplaceholder.typicode.com:8000 open=false>
# http.port # => 8000
#
# For proxy-defining arguments +p_addr+ through +p_no_proxy+,
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
#
def HTTP.new(address, port = nil, p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil, p_no_proxy = nil)
http = super address, port
if proxy_class? then # from Net::HTTP::Proxy()
http.proxy_from_env = @proxy_from_env
http.proxy_address = @proxy_address
http.proxy_port = @proxy_port
http.proxy_user = @proxy_user
http.proxy_pass = @proxy_pass
elsif p_addr == :ENV then
http.proxy_from_env = true
else
if p_addr && p_no_proxy && !URI::Generic.use_proxy?(address, address, port, p_no_proxy)
p_addr = nil
p_port = nil
end
http.proxy_address = p_addr
http.proxy_port = p_port || default_port
http.proxy_user = p_user
http.proxy_pass = p_pass
end
http
end
# Creates a new \Net::HTTP object for the specified server address,
# without opening the TCP connection or initializing the \HTTP session.
# The +address+ should be a DNS hostname or IP address.
def initialize(address, port = nil) # :nodoc:
@address = address
@port = (port || HTTP.default_port)
@ipaddr = nil
@local_host = nil
@local_port = nil
@curr_http_version = HTTPVersion
@keep_alive_timeout = 2
@last_communicated = nil
@close_on_empty_response = false
@socket = nil
@started = false
@open_timeout = 60
@read_timeout = 60
@write_timeout = 60
@continue_timeout = nil
@max_retries = 1
@debug_output = nil
@response_body_encoding = false
@ignore_eof = true
@proxy_from_env = false
@proxy_uri = nil
@proxy_address = nil
@proxy_port = nil
@proxy_user = nil
@proxy_pass = nil
@use_ssl = false
@ssl_context = nil
@ssl_session = nil
@sspi_enabled = false
SSL_IVNAMES.each do |ivname|
instance_variable_set ivname, nil
end
end
# Returns a string representation of +self+:
#
# Net::HTTP.new(hostname).inspect
# # => "#<Net::HTTP jsonplaceholder.typicode.com:80 open=false>"
#
def inspect
"#<#{self.class} #{@address}:#{@port} open=#{started?}>"
end
# *WARNING* This method opens a serious security hole.
# Never use this method in production code.
#
# Sets the output stream for debugging:
#
# http = Net::HTTP.new(hostname)
# File.open('t.tmp', 'w') do |file|
# http.set_debug_output(file)
# http.start
# http.get('/nosuch/1')
# http.finish
# end
# puts File.read('t.tmp')
#
# Output:
#
# opening connection to jsonplaceholder.typicode.com:80...
# opened
# <- "GET /nosuch/1 HTTP/1.1\r\nAccept-Encoding: gzip;q=1.0,deflate;q=0.6,identity;q=0.3\r\nAccept: */*\r\nUser-Agent: Ruby\r\nHost: jsonplaceholder.typicode.com\r\n\r\n"
# -> "HTTP/1.1 404 Not Found\r\n"
# -> "Date: Mon, 12 Dec 2022 21:14:11 GMT\r\n"
# -> "Content-Type: application/json; charset=utf-8\r\n"
# -> "Content-Length: 2\r\n"
# -> "Connection: keep-alive\r\n"
# -> "X-Powered-By: Express\r\n"
# -> "X-Ratelimit-Limit: 1000\r\n"
# -> "X-Ratelimit-Remaining: 999\r\n"
# -> "X-Ratelimit-Reset: 1670879660\r\n"
# -> "Vary: Origin, Accept-Encoding\r\n"
# -> "Access-Control-Allow-Credentials: true\r\n"
# -> "Cache-Control: max-age=43200\r\n"
# -> "Pragma: no-cache\r\n"
# -> "Expires: -1\r\n"
# -> "X-Content-Type-Options: nosniff\r\n"
# -> "Etag: W/\"2-vyGp6PvFo4RvsFtPoIWeCReyIC8\"\r\n"
# -> "Via: 1.1 vegur\r\n"
# -> "CF-Cache-Status: MISS\r\n"
# -> "Server-Timing: cf-q-config;dur=1.3000000762986e-05\r\n"
# -> "Report-To: {\"endpoints\":[{\"url\":\"https:\\/\\/a.nel.cloudflare.com\\/report\\/v3?s=yOr40jo%2BwS1KHzhTlVpl54beJ5Wx2FcG4gGV0XVrh3X9OlR5q4drUn2dkt5DGO4GDcE%2BVXT7CNgJvGs%2BZleIyMu8CLieFiDIvOviOY3EhHg94m0ZNZgrEdpKD0S85S507l1vsEwEHkoTm%2Ff19SiO\"}],\"group\":\"cf-nel\",\"max_age\":604800}\r\n"
# -> "NEL: {\"success_fraction\":0,\"report_to\":\"cf-nel\",\"max_age\":604800}\r\n"
# -> "Server: cloudflare\r\n"
# -> "CF-RAY: 778977dc484ce591-DFW\r\n"
# -> "alt-svc: h3=\":443\"; ma=86400, h3-29=\":443\"; ma=86400\r\n"
# -> "\r\n"
# reading 2 bytes...
# -> "{}"
# read 2 bytes
# Conn keep-alive
#
def set_debug_output(output)
warn 'Net::HTTP#set_debug_output called after HTTP started', uplevel: 1 if started?
@debug_output = output
end
# Returns the string host name or host IP given as argument +address+ in ::new.
attr_reader :address
# Returns the integer port number given as argument +port+ in ::new.
attr_reader :port
# Sets or returns the string local host used to establish the connection;
# initially +nil+.
attr_accessor :local_host
# Sets or returns the integer local port used to establish the connection;
# initially +nil+.
attr_accessor :local_port
# Returns the encoding to use for the response body;
# see #response_body_encoding=.
attr_reader :response_body_encoding
# Sets the encoding to be used for the response body;
# returns the encoding.
#
# The given +value+ may be:
#
# - An Encoding object.
# - The name of an encoding.
# - An alias for an encoding name.
#
# See {Encoding}[rdoc-ref:Encoding].
#
# Examples:
#
# http = Net::HTTP.new(hostname)
# http.response_body_encoding = Encoding::US_ASCII # => #<Encoding:US-ASCII>
# http.response_body_encoding = 'US-ASCII' # => "US-ASCII"
# http.response_body_encoding = 'ASCII' # => "ASCII"
#
def response_body_encoding=(value)
value = Encoding.find(value) if value.is_a?(String)
@response_body_encoding = value
end
# Sets whether to determine the proxy from environment variable
# '<tt>ENV['http_proxy']</tt>';
# see {Proxy Using ENV['http_proxy']}[rdoc-ref:Net::HTTP@Proxy+Using+-27ENV-5B-27http_proxy-27-5D-27].
attr_writer :proxy_from_env
# Sets the proxy address;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_address
# Sets the proxy port;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_port
# Sets the proxy user;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_user
# Sets the proxy password;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
attr_writer :proxy_pass
# Returns the IP address for the connection.
#
# If the session has not been started,
# returns the value set by #ipaddr=,
# or +nil+ if it has not been set:
#
# http = Net::HTTP.new(hostname)
# http.ipaddr # => nil
# http.ipaddr = '172.67.155.76'
# http.ipaddr # => "172.67.155.76"
#
# If the session has been started,
# returns the IP address from the socket:
#
# http = Net::HTTP.new(hostname)
# http.start
# http.ipaddr # => "172.67.155.76"
# http.finish
#
def ipaddr
started? ? @socket.io.peeraddr[3] : @ipaddr
end
# Sets the IP address for the connection:
#
# http = Net::HTTP.new(hostname)
# http.ipaddr # => nil
# http.ipaddr = '172.67.155.76'
# http.ipaddr # => "172.67.155.76"
#
# The IP address may not be set if the session has been started.
def ipaddr=(addr)
raise IOError, "ipaddr value changed, but session already started" if started?
@ipaddr = addr
end
# Sets or returns the numeric (\Integer or \Float) number of seconds
# to wait for a connection to open;
# initially 60.
# If the connection is not made in the given interval,
# an exception is raised.
attr_accessor :open_timeout
# Returns the numeric (\Integer or \Float) number of seconds
# to wait for one block to be read (via one read(2) call);
# see #read_timeout=.
attr_reader :read_timeout
# Returns the numeric (\Integer or \Float) number of seconds
# to wait for one block to be written (via one write(2) call);
# see #write_timeout=.
attr_reader :write_timeout
# Sets the maximum number of times to retry an idempotent request in case of
# \Net::ReadTimeout, IOError, EOFError, Errno::ECONNRESET,
# Errno::ECONNABORTED, Errno::EPIPE, OpenSSL::SSL::SSLError,
# Timeout::Error.
# The initial value is 1.
#
# Argument +retries+ must be a non-negative numeric value:
#
# http = Net::HTTP.new(hostname)
# http.max_retries = 2 # => 2
# http.max_retries # => 2
#
def max_retries=(retries)
retries = retries.to_int
if retries < 0
raise ArgumentError, 'max_retries should be non-negative integer number'
end
@max_retries = retries
end
# Returns the maximum number of times to retry an idempotent request;
# see #max_retries=.
attr_reader :max_retries
# Sets the read timeout, in seconds, for +self+ to integer +sec+;
# the initial value is 60.
#
# Argument +sec+ must be a non-negative numeric value:
#
# http = Net::HTTP.new(hostname)
# http.read_timeout # => 60
# http.get('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true>
# http.read_timeout = 0
# http.get('/todos/1') # Raises Net::ReadTimeout.
#
def read_timeout=(sec)
@socket.read_timeout = sec if @socket
@read_timeout = sec
end
# Sets the write timeout, in seconds, for +self+ to integer +sec+;
# the initial value is 60.
#
# Argument +sec+ must be a non-negative numeric value:
#
# _uri = uri.dup
# _uri.path = '/posts'
# body = 'bar' * 200000
# data = <<EOF
# {"title": "foo", "body": "#{body}", "userId": "1"}
# EOF
# headers = {'content-type': 'application/json'}
# http = Net::HTTP.new(hostname)
# http.write_timeout # => 60
# http.post(_uri.path, data, headers)
# # => #<Net::HTTPCreated 201 Created readbody=true>
# http.write_timeout = 0
# http.post(_uri.path, data, headers) # Raises Net::WriteTimeout.
#
def write_timeout=(sec)
@socket.write_timeout = sec if @socket
@write_timeout = sec
end
# Returns the continue timeout value;
# see continue_timeout=.
attr_reader :continue_timeout
# Sets the continue timeout value,
# which is the number of seconds to wait for an expected 100 Continue response.
# If the \HTTP object does not receive a response in this many seconds
# it sends the request body.
def continue_timeout=(sec)
@socket.continue_timeout = sec if @socket
@continue_timeout = sec
end
# Sets or returns the numeric (\Integer or \Float) number of seconds
# to keep the connection open after a request is sent;
# initially 2.
# If a new request is made during the given interval,
# the still-open connection is used;
# otherwise the connection will have been closed
# and a new connection is opened.
attr_accessor :keep_alive_timeout
# Sets or returns whether to ignore end-of-file when reading a response body
# with <tt>Content-Length</tt> headers;
# initially +true+.
attr_accessor :ignore_eof
# Returns +true+ if the \HTTP session has been started:
#
# http = Net::HTTP.new(hostname)
# http.started? # => false
# http.start
# http.started? # => true
# http.finish # => nil
# http.started? # => false
#
# Net::HTTP.start(hostname) do |http|
# http.started?
# end # => true
# http.started? # => false
#
def started?
@started
end
alias active? started? #:nodoc: obsolete
# Sets or returns whether to close the connection when the response is empty;
# initially +false+.
attr_accessor :close_on_empty_response
# Returns +true+ if +self+ uses SSL, +false+ otherwise.
# See Net::HTTP#use_ssl=.
def use_ssl?
@use_ssl
end
# Sets whether a new session is to use
# {Transport Layer Security}[https://en.wikipedia.org/wiki/Transport_Layer_Security]:
#
# Raises IOError if attempting to change during a session.
#
# Raises OpenSSL::SSL::SSLError if the port is not an HTTPS port.
def use_ssl=(flag)
flag = flag ? true : false
if started? and @use_ssl != flag
raise IOError, "use_ssl value changed, but session already started"
end
@use_ssl = flag
end
SSL_IVNAMES = [
:@ca_file,
:@ca_path,
:@cert,
:@cert_store,
:@ciphers,
:@extra_chain_cert,
:@key,
:@ssl_timeout,
:@ssl_version,
:@min_version,
:@max_version,
:@verify_callback,
:@verify_depth,
:@verify_mode,
:@verify_hostname,
] # :nodoc:
SSL_ATTRIBUTES = [
:ca_file,
:ca_path,
:cert,
:cert_store,
:ciphers,
:extra_chain_cert,
:key,
:ssl_timeout,
:ssl_version,
:min_version,
:max_version,
:verify_callback,
:verify_depth,
:verify_mode,
:verify_hostname,
] # :nodoc:
# Sets or returns the path to a CA certification file in PEM format.
attr_accessor :ca_file
# Sets or returns the path of to CA directory
# containing certification files in PEM format.
attr_accessor :ca_path
# Sets or returns the OpenSSL::X509::Certificate object
# to be used for client certification.
attr_accessor :cert
# Sets or returns the X509::Store to be used for verifying peer certificate.
attr_accessor :cert_store
# Sets or returns the available SSL ciphers.
# See {OpenSSL::SSL::SSLContext#ciphers=}[rdoc-ref:OpenSSL::SSL::SSLContext#ciphers-3D].
attr_accessor :ciphers
# Sets or returns the extra X509 certificates to be added to the certificate chain.
# See {OpenSSL::SSL::SSLContext#add_certificate}[rdoc-ref:OpenSSL::SSL::SSLContext#add_certificate].
attr_accessor :extra_chain_cert
# Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
attr_accessor :key
# Sets or returns the SSL timeout seconds.
attr_accessor :ssl_timeout
# Sets or returns the SSL version.
# See {OpenSSL::SSL::SSLContext#ssl_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#ssl_version-3D].
attr_accessor :ssl_version
# Sets or returns the minimum SSL version.
# See {OpenSSL::SSL::SSLContext#min_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#min_version-3D].
attr_accessor :min_version
# Sets or returns the maximum SSL version.
# See {OpenSSL::SSL::SSLContext#max_version=}[rdoc-ref:OpenSSL::SSL::SSLContext#max_version-3D].
attr_accessor :max_version
# Sets or returns the callback for the server certification verification.
attr_accessor :verify_callback
# Sets or returns the maximum depth for the certificate chain verification.
attr_accessor :verify_depth
# Sets or returns the flags for server the certification verification
# at the beginning of the SSL/TLS session.
# OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable.
attr_accessor :verify_mode
# Sets or returns whether to verify that the server certificate is valid
# for the hostname.
# See {OpenSSL::SSL::SSLContext#verify_hostname=}[rdoc-ref:OpenSSL::SSL::SSLContext#attribute-i-verify_mode].
attr_accessor :verify_hostname
# Returns the X509 certificate chain (an array of strings)
# for the session's socket peer,
# or +nil+ if none.
def peer_cert
if not use_ssl? or not @socket
return nil
end
@socket.io.peer_cert
end
# Starts an \HTTP session.
#
# Without a block, returns +self+:
#
# http = Net::HTTP.new(hostname)
# # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false>
# http.start
# # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=true>
# http.started? # => true
# http.finish
#
# With a block, calls the block with +self+,
# finishes the session when the block exits,
# and returns the block's value:
#
# http.start do |http|
# http
# end
# # => #<Net::HTTP jsonplaceholder.typicode.com:80 open=false>
# http.started? # => false
#
def start # :yield: http
raise IOError, 'HTTP session already opened' if @started
if block_given?
begin
do_start
return yield(self)
ensure
do_finish
end
end
do_start
self
end
def do_start
connect
@started = true
end
private :do_start
def connect
if use_ssl?
# reference early to load OpenSSL before connecting,
# as OpenSSL may take time to load.
@ssl_context = OpenSSL::SSL::SSLContext.new
end
if proxy? then
conn_addr = proxy_address
conn_port = proxy_port
else
conn_addr = conn_address
conn_port = port
end
debug "opening connection to #{conn_addr}:#{conn_port}..."
s = Timeout.timeout(@open_timeout, Net::OpenTimeout) {
begin
TCPSocket.open(conn_addr, conn_port, @local_host, @local_port)
rescue => e
raise e, "Failed to open TCP connection to " +
"#{conn_addr}:#{conn_port} (#{e.message})"
end
}
s.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
debug "opened"
if use_ssl?
if proxy?
plain_sock = BufferedIO.new(s, read_timeout: @read_timeout,
write_timeout: @write_timeout,
continue_timeout: @continue_timeout,
debug_output: @debug_output)
buf = +"CONNECT #{conn_address}:#{@port} HTTP/#{HTTPVersion}\r\n" \
"Host: #{@address}:#{@port}\r\n"
if proxy_user
credential = ["#{proxy_user}:#{proxy_pass}"].pack('m0')
buf << "Proxy-Authorization: Basic #{credential}\r\n"
end
buf << "\r\n"
plain_sock.write(buf)
HTTPResponse.read_new(plain_sock).value
# assuming nothing left in buffers after successful CONNECT response
end
ssl_parameters = Hash.new
iv_list = instance_variables
SSL_IVNAMES.each_with_index do |ivname, i|
if iv_list.include?(ivname)
value = instance_variable_get(ivname)
unless value.nil?
ssl_parameters[SSL_ATTRIBUTES[i]] = value
end
end
end
@ssl_context.set_params(ssl_parameters)
unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby
@ssl_context.session_cache_mode =
OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT |
OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE
end
if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby
@ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess }
end
# Still do the post_connection_check below even if connecting
# to IP address
verify_hostname = @ssl_context.verify_hostname
# Server Name Indication (SNI) RFC 3546/6066
case @address
when Resolv::IPv4::Regex, Resolv::IPv6::Regex
# don't set SNI, as IP addresses in SNI is not valid
# per RFC 6066, section 3.
# Avoid openssl warning
@ssl_context.verify_hostname = false
else
ssl_host_address = @address
end
debug "starting SSL for #{conn_addr}:#{conn_port}..."
s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
s.sync_close = true
s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address
if @ssl_session and
Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout
s.session = @ssl_session
end
ssl_socket_connect(s, @open_timeout)
if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname
s.post_connection_check(@address)
end
debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}"
end
@socket = BufferedIO.new(s, read_timeout: @read_timeout,
write_timeout: @write_timeout,
continue_timeout: @continue_timeout,
debug_output: @debug_output)
@last_communicated = nil
on_connect
rescue => exception
if s
debug "Conn close because of connect error #{exception}"
s.close
end
raise
end
private :connect
def on_connect
end
private :on_connect
# Finishes the \HTTP session:
#
# http = Net::HTTP.new(hostname)
# http.start
# http.started? # => true
# http.finish # => nil
# http.started? # => false
#
# Raises IOError if not in a session.
def finish
raise IOError, 'HTTP session not yet started' unless started?
do_finish
end
def do_finish
@started = false
@socket.close if @socket
@socket = nil
end
private :do_finish
#
# proxy
#
public
# no proxy
@is_proxy_class = false
@proxy_from_env = false
@proxy_addr = nil
@proxy_port = nil
@proxy_user = nil
@proxy_pass = nil
# Creates an \HTTP proxy class which behaves like \Net::HTTP, but
# performs all access via the specified proxy.
#
# This class is obsolete. You may pass these same parameters directly to
# \Net::HTTP.new. See Net::HTTP.new for details of the arguments.
def HTTP.Proxy(p_addr = :ENV, p_port = nil, p_user = nil, p_pass = nil) #:nodoc:
return self unless p_addr
Class.new(self) {
@is_proxy_class = true
if p_addr == :ENV then
@proxy_from_env = true
@proxy_address = nil
@proxy_port = nil
else
@proxy_from_env = false
@proxy_address = p_addr
@proxy_port = p_port || default_port
end
@proxy_user = p_user
@proxy_pass = p_pass
}
end
class << HTTP
# Returns true if self is a class which was created by HTTP::Proxy.
def proxy_class?
defined?(@is_proxy_class) ? @is_proxy_class : false
end
# Returns the address of the proxy host, or +nil+ if none;
# see Net::HTTP@Proxy+Server.
attr_reader :proxy_address
# Returns the port number of the proxy host, or +nil+ if none;
# see Net::HTTP@Proxy+Server.
attr_reader :proxy_port
# Returns the user name for accessing the proxy, or +nil+ if none;
# see Net::HTTP@Proxy+Server.
attr_reader :proxy_user
# Returns the password for accessing the proxy, or +nil+ if none;
# see Net::HTTP@Proxy+Server.
attr_reader :proxy_pass
end
# Returns +true+ if a proxy server is defined, +false+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy?
!!(@proxy_from_env ? proxy_uri : @proxy_address)
end
# Returns +true+ if the proxy server is defined in the environment,
# +false+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy_from_env?
@proxy_from_env
end
# The proxy URI determined from the environment for this connection.
def proxy_uri # :nodoc:
return if @proxy_uri == false
@proxy_uri ||= URI::HTTP.new(
"http", nil, address, port, nil, nil, nil, nil, nil
).find_proxy || false
@proxy_uri || nil
end
# Returns the address of the proxy server, if defined, +nil+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy_address
if @proxy_from_env then
proxy_uri&.hostname
else
@proxy_address
end
end
# Returns the port number of the proxy server, if defined, +nil+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy_port
if @proxy_from_env then
proxy_uri&.port
else
@proxy_port
end
end
# Returns the user name of the proxy server, if defined, +nil+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy_user
if @proxy_from_env
user = proxy_uri&.user
unescape(user) if user
else
@proxy_user
end
end
# Returns the password of the proxy server, if defined, +nil+ otherwise;
# see {Proxy Server}[rdoc-ref:Net::HTTP@Proxy+Server].
def proxy_pass
if @proxy_from_env
pass = proxy_uri&.password
unescape(pass) if pass
else
@proxy_pass
end
end
alias proxyaddr proxy_address #:nodoc: obsolete
alias proxyport proxy_port #:nodoc: obsolete
private
def unescape(value)
require 'cgi/util'
CGI.unescape(value)
end
# without proxy, obsolete
def conn_address # :nodoc:
@ipaddr || address()
end
def conn_port # :nodoc:
port()
end
def edit_path(path)
if proxy?
if path.start_with?("ftp://") || use_ssl?
path
else
"http://#{addr_port}#{path}"
end
else
path
end
end
#
# HTTP operations
#
public
# :call-seq:
# get(path, initheader = nil) {|res| ... }
#
# Sends a GET request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Get object
# created from string +path+ and initial headers hash +initheader+.
#
# With a block given, calls the block with the response body:
#
# http = Net::HTTP.new(hostname)
# http.get('/todos/1') do |res|
# p res
# end # => #<Net::HTTPOK 200 OK readbody=true>
#
# Output:
#
# "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false\n}"
#
# With no block given, simply returns the response object:
#
# http.get('/') # => #<Net::HTTPOK 200 OK readbody=true>
#
# Related:
#
# - Net::HTTP::Get: request class for \HTTP method GET.
# - Net::HTTP.get: sends GET request, returns response body.
#
def get(path, initheader = nil, dest = nil, &block) # :yield: +body_segment+
res = nil
request(Get.new(path, initheader)) {|r|
r.read_body dest, &block
res = r
}
res
end
# Sends a HEAD request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Head object
# created from string +path+ and initial headers hash +initheader+:
#
# res = http.head('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true>
# res.body # => nil
# res.to_hash.take(3)
# # =>
# [["date", ["Wed, 15 Feb 2023 15:25:42 GMT"]],
# ["content-type", ["application/json; charset=utf-8"]],
# ["connection", ["close"]]]
#
def head(path, initheader = nil)
request(Head.new(path, initheader))
end
# :call-seq:
# post(path, data, initheader = nil) {|res| ... }
#
# Sends a POST request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Post object
# created from string +path+, string +data+, and initial headers hash +initheader+.
#
# With a block given, calls the block with the response body:
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.post('/todos', data) do |res|
# p res
# end # => #<Net::HTTPCreated 201 Created readbody=true>
#
# Output:
#
# "{\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\",\n \"id\": 201\n}"
#
# With no block given, simply returns the response object:
#
# http.post('/todos', data) # => #<Net::HTTPCreated 201 Created readbody=true>
#
# Related:
#
# - Net::HTTP::Post: request class for \HTTP method POST.
# - Net::HTTP.post: sends POST request, returns response body.
#
def post(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
send_entity(path, data, initheader, dest, Post, &block)
end
# :call-seq:
# patch(path, data, initheader = nil) {|res| ... }
#
# Sends a PATCH request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Patch object
# created from string +path+, string +data+, and initial headers hash +initheader+.
#
# With a block given, calls the block with the response body:
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.patch('/todos/1', data) do |res|
# p res
# end # => #<Net::HTTPOK 200 OK readbody=true>
#
# Output:
#
# "{\n \"userId\": 1,\n \"id\": 1,\n \"title\": \"delectus aut autem\",\n \"completed\": false,\n \"{\\\"userId\\\": 1, \\\"id\\\": 1, \\\"title\\\": \\\"delectus aut autem\\\", \\\"completed\\\": false}\": \"\"\n}"
#
# With no block given, simply returns the response object:
#
# http.patch('/todos/1', data) # => #<Net::HTTPCreated 201 Created readbody=true>
#
def patch(path, data, initheader = nil, dest = nil, &block) # :yield: +body_segment+
send_entity(path, data, initheader, dest, Patch, &block)
end
# Sends a PUT request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Put object
# created from string +path+, string +data+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.put('/todos/1', data) # => #<Net::HTTPOK 200 OK readbody=true>
#
def put(path, data, initheader = nil)
request(Put.new(path, initheader), data)
end
# Sends a PROPPATCH request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Proppatch object
# created from string +path+, string +body+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.proppatch('/todos/1', data)
#
def proppatch(path, body, initheader = nil)
request(Proppatch.new(path, initheader), body)
end
# Sends a LOCK request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Lock object
# created from string +path+, string +body+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.lock('/todos/1', data)
#
def lock(path, body, initheader = nil)
request(Lock.new(path, initheader), body)
end
# Sends an UNLOCK request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Unlock object
# created from string +path+, string +body+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.unlock('/todos/1', data)
#
def unlock(path, body, initheader = nil)
request(Unlock.new(path, initheader), body)
end
# Sends an Options request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Options object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.options('/')
#
def options(path, initheader = nil)
request(Options.new(path, initheader))
end
# Sends a PROPFIND request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Propfind object
# created from string +path+, string +body+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http = Net::HTTP.new(hostname)
# http.propfind('/todos/1', data)
#
def propfind(path, body = nil, initheader = {'Depth' => '0'})
request(Propfind.new(path, initheader), body)
end
# Sends a DELETE request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Delete object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.delete('/todos/1')
#
def delete(path, initheader = {'Depth' => 'Infinity'})
request(Delete.new(path, initheader))
end
# Sends a MOVE request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Move object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.move('/todos/1')
#
def move(path, initheader = nil)
request(Move.new(path, initheader))
end
# Sends a COPY request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Copy object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.copy('/todos/1')
#
def copy(path, initheader = nil)
request(Copy.new(path, initheader))
end
# Sends a MKCOL request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Mkcol object
# created from string +path+, string +body+, and initial headers hash +initheader+.
#
# data = '{"userId": 1, "id": 1, "title": "delectus aut autem", "completed": false}'
# http.mkcol('/todos/1', data)
# http = Net::HTTP.new(hostname)
#
def mkcol(path, body = nil, initheader = nil)
request(Mkcol.new(path, initheader), body)
end
# Sends a TRACE request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Trace object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.trace('/todos/1')
#
def trace(path, initheader = nil)
request(Trace.new(path, initheader))
end
# Sends a GET request to the server;
# forms the response into a Net::HTTPResponse object.
#
# The request is based on the Net::HTTP::Get object
# created from string +path+ and initial headers hash +initheader+.
#
# With no block given, returns the response object:
#
# http = Net::HTTP.new(hostname)
# http.request_get('/todos') # => #<Net::HTTPOK 200 OK readbody=true>
#
# With a block given, calls the block with the response object
# and returns the response object:
#
# http.request_get('/todos') do |res|
# p res
# end # => #<Net::HTTPOK 200 OK readbody=true>
#
# Output:
#
# #<Net::HTTPOK 200 OK readbody=false>
#
def request_get(path, initheader = nil, &block) # :yield: +response+
request(Get.new(path, initheader), &block)
end
# Sends a HEAD request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Head object
# created from string +path+ and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.head('/todos/1') # => #<Net::HTTPOK 200 OK readbody=true>
#
def request_head(path, initheader = nil, &block)
request(Head.new(path, initheader), &block)
end
# Sends a POST request to the server;
# forms the response into a Net::HTTPResponse object.
#
# The request is based on the Net::HTTP::Post object
# created from string +path+, string +data+, and initial headers hash +initheader+.
#
# With no block given, returns the response object:
#
# http = Net::HTTP.new(hostname)
# http.post('/todos', 'xyzzy')
# # => #<Net::HTTPCreated 201 Created readbody=true>
#
# With a block given, calls the block with the response body
# and returns the response object:
#
# http.post('/todos', 'xyzzy') do |res|
# p res
# end # => #<Net::HTTPCreated 201 Created readbody=true>
#
# Output:
#
# "{\n \"xyzzy\": \"\",\n \"id\": 201\n}"
#
def request_post(path, data, initheader = nil, &block) # :yield: +response+
request Post.new(path, initheader), data, &block
end
# Sends a PUT request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTP::Put object
# created from string +path+, string +data+, and initial headers hash +initheader+.
#
# http = Net::HTTP.new(hostname)
# http.put('/todos/1', 'xyzzy')
# # => #<Net::HTTPOK 200 OK readbody=true>
#
def request_put(path, data, initheader = nil, &block) #:nodoc:
request Put.new(path, initheader), data, &block
end
alias get2 request_get #:nodoc: obsolete
alias head2 request_head #:nodoc: obsolete
alias post2 request_post #:nodoc: obsolete
alias put2 request_put #:nodoc: obsolete
# Sends an \HTTP request to the server;
# returns an instance of a subclass of Net::HTTPResponse.
#
# The request is based on the Net::HTTPRequest object
# created from string +path+, string +data+, and initial headers hash +header+.
# That object is an instance of the
# {subclass of Net::HTTPRequest}[rdoc-ref:Net::HTTPRequest@Request+Subclasses],
# that corresponds to the given uppercase string +name+,
# which must be
# an {HTTP request method}[https://en.wikipedia.org/wiki/HTTP#Request_methods]
# or a {WebDAV request method}[https://en.wikipedia.org/wiki/WebDAV#Implementation].
#
# Examples:
#
# http = Net::HTTP.new(hostname)
# http.send_request('GET', '/todos/1')
# # => #<Net::HTTPOK 200 OK readbody=true>
# http.send_request('POST', '/todos', 'xyzzy')
# # => #<Net::HTTPCreated 201 Created readbody=true>
#
def send_request(name, path, data = nil, header = nil)
has_response_body = name != 'HEAD'
r = HTTPGenericRequest.new(name,(data ? true : false),has_response_body,path,header)
request r, data
end
# Sends the given request +req+ to the server;
# forms the response into a Net::HTTPResponse object.
#
# The given +req+ must be an instance of a
# {subclass of Net::HTTPRequest}[rdoc-ref:Net::HTTPRequest@Request+Subclasses].
# Argument +body+ should be given only if needed for the request.
#
# With no block given, returns the response object:
#
# http = Net::HTTP.new(hostname)
#
# req = Net::HTTP::Get.new('/todos/1')
# http.request(req)
# # => #<Net::HTTPOK 200 OK readbody=true>
#
# req = Net::HTTP::Post.new('/todos')
# http.request(req, 'xyzzy')
# # => #<Net::HTTPCreated 201 Created readbody=true>
#
# With a block given, calls the block with the response and returns the response:
#
# req = Net::HTTP::Get.new('/todos/1')
# http.request(req) do |res|
# p res
# end # => #<Net::HTTPOK 200 OK readbody=true>
#
# Output:
#
# #<Net::HTTPOK 200 OK readbody=false>
#
def request(req, body = nil, &block) # :yield: +response+
unless started?
start {
req['connection'] ||= 'close'
return request(req, body, &block)
}
end
if proxy_user()
req.proxy_basic_auth proxy_user(), proxy_pass() unless use_ssl?
end
req.set_body_internal body
res = transport_request(req, &block)
if sspi_auth?(res)
sspi_auth(req)
res = transport_request(req, &block)
end
res
end
private
# Executes a request which uses a representation
# and returns its body.
def send_entity(path, data, initheader, dest, type, &block)
res = nil
request(type.new(path, initheader), data) {|r|
r.read_body dest, &block
res = r
}
res
end
IDEMPOTENT_METHODS_ = %w/GET HEAD PUT DELETE OPTIONS TRACE/ # :nodoc:
def transport_request(req)
count = 0
begin
begin_transport req
res = catch(:response) {
begin
req.exec @socket, @curr_http_version, edit_path(req.path)
rescue Errno::EPIPE
# Failure when writing full request, but we can probably
# still read the received response.
end
begin
res = HTTPResponse.read_new(@socket)
res.decode_content = req.decode_content
res.body_encoding = @response_body_encoding
res.ignore_eof = @ignore_eof
end while res.kind_of?(HTTPInformation)
res.uri = req.uri
res
}
res.reading_body(@socket, req.response_body_permitted?) {
yield res if block_given?
}
rescue Net::OpenTimeout
raise
rescue Net::ReadTimeout, IOError, EOFError,
Errno::ECONNRESET, Errno::ECONNABORTED, Errno::EPIPE, Errno::ETIMEDOUT,
# avoid a dependency on OpenSSL
defined?(OpenSSL::SSL) ? OpenSSL::SSL::SSLError : IOError,
Timeout::Error => exception
if count < max_retries && IDEMPOTENT_METHODS_.include?(req.method)
count += 1
@socket.close if @socket
debug "Conn close because of error #{exception}, and retry"
retry
end
debug "Conn close because of error #{exception}"
@socket.close if @socket
raise
end
end_transport req, res
res
rescue => exception
debug "Conn close because of error #{exception}"
@socket.close if @socket
raise exception
end
def begin_transport(req)
if @socket.closed?
connect
elsif @last_communicated
if @last_communicated + @keep_alive_timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC)
debug 'Conn close because of keep_alive_timeout'
@socket.close
connect
elsif @socket.io.to_io.wait_readable(0) && @socket.eof?
debug "Conn close because of EOF"
@socket.close
connect
end
end
if not req.response_body_permitted? and @close_on_empty_response
req['connection'] ||= 'close'
end
req.update_uri address, port, use_ssl?
req['host'] ||= addr_port()
end
def end_transport(req, res)
@curr_http_version = res.http_version
@last_communicated = nil
if @socket.closed?
debug 'Conn socket closed'
elsif not res.body and @close_on_empty_response
debug 'Conn close'
@socket.close
elsif keep_alive?(req, res)
debug 'Conn keep-alive'
@last_communicated = Process.clock_gettime(Process::CLOCK_MONOTONIC)
else
debug 'Conn close'
@socket.close
end
end
def keep_alive?(req, res)
return false if req.connection_close?
if @curr_http_version <= '1.0'
res.connection_keep_alive?
else # HTTP/1.1 or later
not res.connection_close?
end
end
def sspi_auth?(res)
return false unless @sspi_enabled
if res.kind_of?(HTTPProxyAuthenticationRequired) and
proxy? and res["Proxy-Authenticate"].include?("Negotiate")
begin
require 'win32/sspi'
true
rescue LoadError
false
end
else
false
end
end
def sspi_auth(req)
n = Win32::SSPI::NegotiateAuth.new
req["Proxy-Authorization"] = "Negotiate #{n.get_initial_token}"
# Some versions of ISA will close the connection if this isn't present.
req["Connection"] = "Keep-Alive"
req["Proxy-Connection"] = "Keep-Alive"
res = transport_request(req)
authphrase = res["Proxy-Authenticate"] or return res
req["Proxy-Authorization"] = "Negotiate #{n.complete_authentication(authphrase)}"
rescue => err
raise HTTPAuthenticationError.new('HTTP authentication failed', err)
end
#
# utils
#
private
def addr_port
addr = address
addr = "[#{addr}]" if addr.include?(":")
default_port = use_ssl? ? HTTP.https_default_port : HTTP.http_default_port
default_port == port ? addr : "#{addr}:#{port}"
end
# Adds a message to debugging output
def debug(msg)
return unless @debug_output
@debug_output << msg
@debug_output << "\n"
end
alias_method :D, :debug
end
end
require_relative 'http/exceptions'
require_relative 'http/header'
require_relative 'http/generic_request'
require_relative 'http/request'
require_relative 'http/requests'
require_relative 'http/response'
require_relative 'http/responses'
require_relative 'http/proxy_delta'
require_relative 'http/backward'
share/ruby/mkmf.rb 0000644 00000260633 15173547337 0010137 0 ustar 00 # -*- coding: us-ascii -*-
# frozen-string-literal: false
# module to create Makefile for extension modules
# invoke like: ruby -r mkmf extconf.rb
require 'rbconfig'
require 'fileutils'
require 'shellwords'
class String # :nodoc:
# Wraps a string in escaped quotes if it contains whitespace.
def quote
/\s/ =~ self ? "\"#{self}\"" : "#{self}"
end
# Escape whitespaces for Makefile.
def unspace
gsub(/\s/, '\\\\\\&')
end
# Generates a string used as cpp macro name.
def tr_cpp
strip.upcase.tr_s("^A-Z0-9_*", "_").tr_s("*", "P")
end
def funcall_style
/\)\z/ =~ self ? dup : "#{self}()"
end
def sans_arguments
self[/\A[^()]+/]
end
end
class Array # :nodoc:
# Wraps all strings in escaped quotes if they contain whitespace.
def quote
map {|s| s.quote}
end
end
##
# mkmf.rb is used by Ruby C extensions to generate a Makefile which will
# correctly compile and link the C extension to Ruby and a third-party
# library.
module MakeMakefile
#### defer until this module become global-state free.
# def self.extended(obj)
# obj.init_mkmf
# super
# end
#
# def initialize(*args, rbconfig: RbConfig, **rest)
# init_mkmf(rbconfig::MAKEFILE_CONFIG, rbconfig::CONFIG)
# super(*args, **rest)
# end
##
# The makefile configuration using the defaults from when Ruby was built.
CONFIG = RbConfig::MAKEFILE_CONFIG
ORIG_LIBPATH = ENV['LIB']
##
# Extensions for files compiled with a C compiler
C_EXT = %w[c m]
##
# Extensions for files compiled with a C++ compiler
CXX_EXT = %w[cc mm cxx cpp]
unless File.exist?(File.join(*File.split(__FILE__).tap {|d, b| b.swapcase}))
CXX_EXT.concat(%w[C])
end
##
# Extensions for source files
SRC_EXT = C_EXT + CXX_EXT
##
# Extensions for header files
HDR_EXT = %w[h hpp]
$static = nil
$config_h = '$(arch_hdrdir)/ruby/config.h'
$default_static = $static
unless defined? $configure_args
$configure_args = {}
args = CONFIG["configure_args"]
if ENV["CONFIGURE_ARGS"]
args << " " << ENV["CONFIGURE_ARGS"]
end
for arg in Shellwords::shellwords(args)
arg, val = arg.split('=', 2)
next unless arg
arg.tr!('_', '-')
if arg.sub!(/^(?!--)/, '--')
val or next
arg.downcase!
end
next if /^--(?:top|topsrc|src|cur)dir$/ =~ arg
$configure_args[arg] = val || true
end
for arg in ARGV
arg, val = arg.split('=', 2)
next unless arg
arg.tr!('_', '-')
if arg.sub!(/^(?!--)/, '--')
val or next
arg.downcase!
end
$configure_args[arg] = val || true
end
end
$libdir = CONFIG["libdir"]
$rubylibdir = CONFIG["rubylibdir"]
$archdir = CONFIG["archdir"]
$sitedir = CONFIG["sitedir"]
$sitelibdir = CONFIG["sitelibdir"]
$sitearchdir = CONFIG["sitearchdir"]
$vendordir = CONFIG["vendordir"]
$vendorlibdir = CONFIG["vendorlibdir"]
$vendorarchdir = CONFIG["vendorarchdir"]
$mswin = /mswin/ =~ RUBY_PLATFORM
$mingw = /mingw/ =~ RUBY_PLATFORM
$cygwin = /cygwin/ =~ RUBY_PLATFORM
$netbsd = /netbsd/ =~ RUBY_PLATFORM
$haiku = /haiku/ =~ RUBY_PLATFORM
$solaris = /solaris/ =~ RUBY_PLATFORM
$universal = /universal/ =~ RUBY_PLATFORM
$dest_prefix_pattern = (File::PATH_SEPARATOR == ';' ? /\A([[:alpha:]]:)?/ : /\A/)
# :stopdoc:
def config_string(key, config = CONFIG)
s = config[key] and !s.empty? and block_given? ? yield(s) : s
end
module_function :config_string
def dir_re(dir)
Regexp.new('\$(?:\('+dir+'\)|\{'+dir+'\})(?:\$(?:\(target_prefix\)|\{target_prefix\}))?')
end
module_function :dir_re
def relative_from(path, base)
dir = File.join(path, "")
if File.expand_path(dir) == File.expand_path(dir, base)
path
else
File.join(base, path)
end
end
INSTALL_DIRS = [
[dir_re('commondir'), "$(RUBYCOMMONDIR)"],
[dir_re('sitedir'), "$(RUBYCOMMONDIR)"],
[dir_re('vendordir'), "$(RUBYCOMMONDIR)"],
[dir_re('rubylibdir'), "$(RUBYLIBDIR)"],
[dir_re('archdir'), "$(RUBYARCHDIR)"],
[dir_re('sitelibdir'), "$(RUBYLIBDIR)"],
[dir_re('vendorlibdir'), "$(RUBYLIBDIR)"],
[dir_re('sitearchdir'), "$(RUBYARCHDIR)"],
[dir_re('vendorarchdir'), "$(RUBYARCHDIR)"],
[dir_re('rubyhdrdir'), "$(RUBYHDRDIR)"],
[dir_re('sitehdrdir'), "$(SITEHDRDIR)"],
[dir_re('vendorhdrdir'), "$(VENDORHDRDIR)"],
[dir_re('bindir'), "$(BINDIR)"],
]
def install_dirs(target_prefix = nil)
if $extout and $extmk
dirs = [
['BINDIR', '$(extout)/bin'],
['RUBYCOMMONDIR', '$(extout)/common'],
['RUBYLIBDIR', '$(RUBYCOMMONDIR)$(target_prefix)'],
['RUBYARCHDIR', '$(extout)/$(arch)$(target_prefix)'],
['HDRDIR', '$(extout)/include/ruby$(target_prefix)'],
['ARCHHDRDIR', '$(extout)/include/$(arch)/ruby$(target_prefix)'],
['extout', "#$extout"],
['extout_prefix', "#$extout_prefix"],
]
elsif $extmk
dirs = [
['BINDIR', '$(bindir)'],
['RUBYCOMMONDIR', '$(rubylibdir)'],
['RUBYLIBDIR', '$(rubylibdir)$(target_prefix)'],
['RUBYARCHDIR', '$(archdir)$(target_prefix)'],
['HDRDIR', '$(rubyhdrdir)/ruby$(target_prefix)'],
['ARCHHDRDIR', '$(rubyhdrdir)/$(arch)/ruby$(target_prefix)'],
]
elsif $configure_args.has_key?('--vendor')
dirs = [
['BINDIR', '$(bindir)'],
['RUBYCOMMONDIR', '$(vendordir)$(target_prefix)'],
['RUBYLIBDIR', '$(vendorlibdir)$(target_prefix)'],
['RUBYARCHDIR', '$(vendorarchdir)$(target_prefix)'],
['HDRDIR', '$(vendorhdrdir)$(target_prefix)'],
['ARCHHDRDIR', '$(vendorarchhdrdir)$(target_prefix)'],
]
else
dirs = [
['BINDIR', '$(bindir)'],
['RUBYCOMMONDIR', '$(sitedir)$(target_prefix)'],
['RUBYLIBDIR', '$(sitelibdir)$(target_prefix)'],
['RUBYARCHDIR', '$(sitearchdir)$(target_prefix)'],
['HDRDIR', '$(sitehdrdir)$(target_prefix)'],
['ARCHHDRDIR', '$(sitearchhdrdir)$(target_prefix)'],
]
end
dirs << ['target_prefix', (target_prefix ? "/#{target_prefix}" : "")]
dirs
end
def map_dir(dir, map = nil)
map ||= INSTALL_DIRS
map.inject(dir) {|d, (orig, new)| d.gsub(orig, new)}
end
topdir = File.dirname(File.dirname(__FILE__))
path = File.expand_path($0)
until (dir = File.dirname(path)) == path
if File.identical?(dir, topdir)
$extmk = true if %r"\A(?:ext|enc|tool|test)\z" =~ File.basename(path)
break
end
path = dir
end
$extmk ||= false
if not $extmk and File.exist?(($hdrdir = RbConfig::CONFIG["rubyhdrdir"]) + "/ruby/ruby.h")
$topdir = $hdrdir
$top_srcdir = $hdrdir
$arch_hdrdir = RbConfig::CONFIG["rubyarchhdrdir"]
elsif File.exist?(($hdrdir = ($top_srcdir ||= topdir) + "/include") + "/ruby.h")
$topdir ||= RbConfig::CONFIG["topdir"]
$arch_hdrdir = "$(extout)/include/$(arch)"
else
abort <<MESSAGE
mkmf.rb can't find header files for ruby at #{$hdrdir}/ruby.h
You might have to install separate package for the ruby development
environment, ruby-dev or ruby-devel for example.
MESSAGE
end
CONFTEST = "conftest".freeze
CONFTEST_C = "#{CONFTEST}.c"
OUTFLAG = CONFIG['OUTFLAG']
COUTFLAG = CONFIG['COUTFLAG']
CSRCFLAG = CONFIG['CSRCFLAG']
CPPOUTFILE = config_string('CPPOUTFILE') {|str| str.sub(/\bconftest\b/, CONFTEST)}
def rm_f(*files)
opt = (Hash === files.last ? [files.pop] : [])
FileUtils.rm_f(Dir[*files.flatten], *opt)
end
module_function :rm_f
def rm_rf(*files)
opt = (Hash === files.last ? [files.pop] : [])
FileUtils.rm_rf(Dir[*files.flatten], *opt)
end
module_function :rm_rf
# Returns time stamp of the +target+ file if it exists and is newer than or
# equal to all of +times+.
def modified?(target, times)
(t = File.mtime(target)) rescue return nil
Array === times or times = [times]
t if times.all? {|n| n <= t}
end
def split_libs(*strs)
sep = $mswin ? /\s+/ : /\s+(?=-|\z)/
strs.flat_map {|s| s.lstrip.split(sep)}
end
def merge_libs(*libs)
libs.inject([]) do |x, y|
y = y.inject([]) {|ary, e| ary.last == e ? ary : ary << e}
y.each_with_index do |v, yi|
if xi = x.rindex(v)
x[(xi+1)..-1] = merge_libs(y[(yi+1)..-1], x[(xi+1)..-1])
x[xi, 0] = y[0...yi]
break
end
end and x.concat(y)
x
end
end
# This is a custom logging module. It generates an mkmf.log file when you
# run your extconf.rb script. This can be useful for debugging unexpected
# failures.
#
# This module and its associated methods are meant for internal use only.
#
module Logging
@log = nil
@logfile = 'mkmf.log'
@orgerr = $stderr.dup
@orgout = $stdout.dup
@postpone = 0
@quiet = $extmk
def self::log_open
@log ||= File::open(@logfile, 'wb')
@log.sync = true
end
def self::log_opened?
@log and not @log.closed?
end
def self::open
log_open
$stderr.reopen(@log)
$stdout.reopen(@log)
yield
ensure
$stderr.reopen(@orgerr)
$stdout.reopen(@orgout)
end
def self::message(*s)
log_open
@log.printf(*s)
end
def self::logfile file
@logfile = file
log_close
end
def self::log_close
if @log and not @log.closed?
@log.flush
@log.close
@log = nil
end
end
def self::postpone
tmplog = "mkmftmp#{@postpone += 1}.log"
open do
log, *save = @log, @logfile, @orgout, @orgerr
@log, @logfile, @orgout, @orgerr = nil, tmplog, log, log
begin
log.print(open {yield @log})
ensure
@log.close if @log and not @log.closed?
File::open(tmplog) {|t| FileUtils.copy_stream(t, log)} if File.exist?(tmplog)
@log, @logfile, @orgout, @orgerr = log, *save
@postpone -= 1
MakeMakefile.rm_f tmplog
end
end
end
class << self
attr_accessor :quiet
end
end
def libpath_env
# used only if native compiling
if libpathenv = config_string("LIBPATHENV")
pathenv = ENV[libpathenv]
libpath = RbConfig.expand($DEFLIBPATH.join(File::PATH_SEPARATOR))
{libpathenv => [libpath, pathenv].compact.join(File::PATH_SEPARATOR)}
else
{}
end
end
def expand_command(commands, envs = libpath_env)
varpat = /\$\((\w+)\)|\$\{(\w+)\}/
vars = nil
expand = proc do |command|
case command
when Array
command.map(&expand)
when String
if varpat =~ command
vars ||= Hash.new {|h, k| h[k] = ENV[k]}
command = command.dup
nil while command.gsub!(varpat) {vars[$1||$2]}
end
command
else
command
end
end
if Array === commands
env, *commands = commands if Hash === commands.first
envs.merge!(env) if env
end
return envs, expand[commands]
end
def env_quote(envs)
envs.map {|e, v| "#{e}=#{v.quote}"}
end
def xsystem command, opts = nil
env, command = expand_command(command)
Logging::open do
puts [env_quote(env), command.quote].join(' ')
if opts and opts[:werror]
result = nil
Logging.postpone do |log|
output = IO.popen(env, command, &:read)
result = ($?.success? and File.zero?(log.path))
output
end
result
else
system(env, *command)
end
end
end
def xpopen command, *mode, &block
env, commands = expand_command(command)
command = [env_quote(env), command].join(' ')
Logging::open do
case mode[0]
when nil, Hash, /^r/
puts "#{command} |"
else
puts "| #{command}"
end
IO.popen(env, commands, *mode, &block)
end
end
def log_src(src, heading="checked program was")
src = src.split(/^/)
fmt = "%#{src.size.to_s.size}d: %s"
Logging::message <<"EOM"
#{heading}:
/* begin */
EOM
src.each_with_index {|line, no| Logging::message fmt, no+1, line}
Logging::message <<"EOM"
/* end */
EOM
end
def conftest_source
CONFTEST_C
end
def create_tmpsrc(src)
src = "#{COMMON_HEADERS}\n#{src}"
src = yield(src) if block_given?
src.gsub!(/[ \t]+$/, '')
src.gsub!(/\A\n+|^\n+$/, '')
src.sub!(/[^\n]\z/, "\\&\n")
count = 0
begin
File.open(conftest_source, "wb") do |cfile|
cfile.print src
end
rescue Errno::EACCES
if (count += 1) < 5
sleep 0.2
retry
end
end
src
end
def have_devel?
unless defined? $have_devel
$have_devel = true
$have_devel = try_link(MAIN_DOES_NOTHING)
end
$have_devel
end
def try_do(src, command, *opts, &b)
unless have_devel?
raise <<MSG
The compiler failed to generate an executable file.
You have to install development tools first.
MSG
end
begin
src = create_tmpsrc(src, &b)
xsystem(command, *opts)
ensure
log_src(src)
end
end
def link_config(ldflags, opt="", libpath=$DEFLIBPATH|$LIBPATH)
librubyarg = $extmk ? $LIBRUBYARG_STATIC : "$(LIBRUBYARG)"
conf = RbConfig::CONFIG.merge('hdrdir' => $hdrdir.quote,
'src' => "#{conftest_source}",
'arch_hdrdir' => $arch_hdrdir.quote,
'top_srcdir' => $top_srcdir.quote,
'INCFLAGS' => "#$INCFLAGS",
'CPPFLAGS' => "#$CPPFLAGS",
'CFLAGS' => "#$CFLAGS",
'ARCH_FLAG' => "#$ARCH_FLAG",
'LDFLAGS' => "#$LDFLAGS #{ldflags}",
'LOCAL_LIBS' => "#$LOCAL_LIBS #$libs",
'LIBS' => "#{librubyarg} #{opt} #$LIBS")
conf['LIBPATH'] = libpathflag(libpath.map {|s| RbConfig::expand(s.dup, conf)})
conf
end
def link_command(ldflags, *opts)
conf = link_config(ldflags, *opts)
RbConfig::expand(TRY_LINK.dup, conf)
end
def cc_config(opt="")
conf = RbConfig::CONFIG.merge('hdrdir' => $hdrdir.quote, 'srcdir' => $srcdir.quote,
'arch_hdrdir' => $arch_hdrdir.quote,
'top_srcdir' => $top_srcdir.quote)
conf
end
def cc_command(opt="")
conf = cc_config(opt)
RbConfig::expand("$(CC) #$INCFLAGS #$CPPFLAGS #$CFLAGS #$ARCH_FLAG #{opt} -c #{CONFTEST_C}",
conf)
end
def cpp_command(outfile, opt="")
conf = cc_config(opt)
if $universal and (arch_flag = conf['ARCH_FLAG']) and !arch_flag.empty?
conf['ARCH_FLAG'] = arch_flag.gsub(/(?:\G|\s)-arch\s+\S+/, '')
end
RbConfig::expand("$(CPP) #$INCFLAGS #$CPPFLAGS #$CFLAGS #{opt} #{CONFTEST_C} #{outfile}",
conf)
end
def libpathflag(libpath=$DEFLIBPATH|$LIBPATH)
libpath.map{|x|
case x
when "$(topdir)", /\A\./
LIBPATHFLAG
else
LIBPATHFLAG+RPATHFLAG
end % x.quote
}.join
end
def with_werror(opt, opts = nil)
if opts
if opts[:werror] and config_string("WERRORFLAG") {|flag| opt = opt ? "#{opt} #{flag}" : flag}
(opts = opts.dup).delete(:werror)
end
yield(opt, opts)
else
yield(opt)
end
end
def try_link0(src, opt="", *opts, &b) # :nodoc:
exe = CONFTEST+$EXEEXT
cmd = link_command("", opt)
if $universal
require 'tmpdir'
Dir.mktmpdir("mkmf_", oldtmpdir = ENV["TMPDIR"]) do |tmpdir|
begin
ENV["TMPDIR"] = tmpdir
try_do(src, cmd, *opts, &b)
ensure
ENV["TMPDIR"] = oldtmpdir
end
end
else
try_do(src, cmd, *opts, &b)
end and File.executable?(exe) or return nil
exe
ensure
MakeMakefile.rm_rf(*Dir["#{CONFTEST}*"]-[exe])
end
# Returns whether or not the +src+ can be compiled as a C source and linked
# with its depending libraries successfully. +opt+ is passed to the linker
# as options. Note that +$CFLAGS+ and +$LDFLAGS+ are also passed to the
# linker.
#
# If a block given, it is called with the source before compilation. You can
# modify the source in the block.
#
# [+src+] a String which contains a C source
# [+opt+] a String which contains linker options
def try_link(src, opt="", *opts, &b)
exe = try_link0(src, opt, *opts, &b) or return false
MakeMakefile.rm_f exe
true
end
# Returns whether or not the +src+ can be compiled as a C source. +opt+ is
# passed to the C compiler as options. Note that +$CFLAGS+ is also passed to
# the compiler.
#
# If a block given, it is called with the source before compilation. You can
# modify the source in the block.
#
# [+src+] a String which contains a C source
# [+opt+] a String which contains compiler options
def try_compile(src, opt="", *opts, &b)
with_werror(opt, *opts) {|_opt, *| try_do(src, cc_command(_opt), *opts, &b)} and
File.file?("#{CONFTEST}.#{$OBJEXT}")
ensure
MakeMakefile.rm_f "#{CONFTEST}*"
end
# Returns whether or not the +src+ can be preprocessed with the C
# preprocessor. +opt+ is passed to the preprocessor as options. Note that
# +$CFLAGS+ is also passed to the preprocessor.
#
# If a block given, it is called with the source before preprocessing. You
# can modify the source in the block.
#
# [+src+] a String which contains a C source
# [+opt+] a String which contains preprocessor options
def try_cpp(src, opt="", *opts, &b)
try_do(src, cpp_command(CPPOUTFILE, opt), *opts, &b) and
File.file?("#{CONFTEST}.i")
ensure
MakeMakefile.rm_f "#{CONFTEST}*"
end
alias try_header try_compile
def cpp_include(header)
if header
header = [header] unless header.kind_of? Array
header.map {|h| String === h ? "#include <#{h}>\n" : h}.join
else
""
end
end
def with_cppflags(flags)
cppflags = $CPPFLAGS
$CPPFLAGS = flags.dup
ret = yield
ensure
$CPPFLAGS = cppflags unless ret
end
def try_cppflags(flags, opts = {})
try_header(MAIN_DOES_NOTHING, flags, {:werror => true}.update(opts))
end
def append_cppflags(flags, *opts)
Array(flags).each do |flag|
if checking_for("whether #{flag} is accepted as CPPFLAGS") {
try_cppflags(flag, *opts)
}
$CPPFLAGS << " " << flag
end
end
end
def with_cflags(flags)
cflags = $CFLAGS
$CFLAGS = flags.dup
ret = yield
ensure
$CFLAGS = cflags unless ret
end
def try_cflags(flags, opts = {})
try_compile(MAIN_DOES_NOTHING, flags, {:werror => true}.update(opts))
end
def with_ldflags(flags)
ldflags = $LDFLAGS
$LDFLAGS = flags.dup
ret = yield
ensure
$LDFLAGS = ldflags unless ret
end
def try_ldflags(flags, opts = {})
opts = {:werror => true}.update(opts) if $mswin
try_link(MAIN_DOES_NOTHING, flags, opts)
end
def append_ldflags(flags, *opts)
Array(flags).each do |flag|
if checking_for("whether #{flag} is accepted as LDFLAGS") {
try_ldflags(flag, *opts)
}
$LDFLAGS << " " << flag
end
end
end
def try_static_assert(expr, headers = nil, opt = "", &b)
headers = cpp_include(headers)
try_compile(<<SRC, opt, &b)
#{headers}
/*top*/
int conftest_const[(#{expr}) ? 1 : -1];
SRC
end
def try_constant(const, headers = nil, opt = "", &b)
includes = cpp_include(headers)
neg = try_static_assert("#{const} < 0", headers, opt)
if CROSS_COMPILING
if neg
const = "-(#{const})"
elsif try_static_assert("#{const} > 0", headers, opt)
# positive constant
elsif try_static_assert("#{const} == 0", headers, opt)
return 0
else
# not a constant
return nil
end
upper = 1
until try_static_assert("#{const} <= #{upper}", headers, opt)
lower = upper
upper <<= 1
end
return nil unless lower
while upper > lower + 1
mid = (upper + lower) / 2
if try_static_assert("#{const} > #{mid}", headers, opt)
lower = mid
else
upper = mid
end
end
upper = -upper if neg
return upper
else
src = %{#{includes}
#include <stdio.h>
/*top*/
typedef#{neg ? '' : ' unsigned'}
#ifdef PRI_LL_PREFIX
#define PRI_CONFTEST_PREFIX PRI_LL_PREFIX
LONG_LONG
#else
#define PRI_CONFTEST_PREFIX "l"
long
#endif
conftest_type;
conftest_type conftest_const = (conftest_type)(#{const});
int main() {printf("%"PRI_CONFTEST_PREFIX"#{neg ? 'd' : 'u'}\\n", conftest_const); return 0;}
}
begin
if try_link0(src, opt, &b)
xpopen("./#{CONFTEST}") do |f|
return Integer(f.gets)
end
end
ensure
MakeMakefile.rm_f "#{CONFTEST}#{$EXEEXT}"
end
end
nil
end
# You should use +have_func+ rather than +try_func+.
#
# [+func+] a String which contains a symbol name
# [+libs+] a String which contains library names.
# [+headers+] a String or an Array of strings which contains names of header
# files.
def try_func(func, libs, headers = nil, opt = "", &b)
headers = cpp_include(headers)
prepare = String.new
case func
when /^&/
decltype = proc {|x|"const volatile void *#{x}"}
when /\)$/
strvars = []
call = func.gsub(/""/) {
v = "s#{strvars.size + 1}"
strvars << v
v
}
unless strvars.empty?
prepare << "char " << strvars.map {|v| "#{v}[1024]"}.join(", ") << "; "
end
when nil
call = ""
else
call = "#{func}()"
decltype = proc {|x| "void ((*#{x})())"}
end
if opt and !opt.empty?
[[:to_str], [:join, " "], [:to_s]].each do |meth, *args|
if opt.respond_to?(meth)
break opt = opt.__send__(meth, *args)
end
end
opt = "#{opt} #{libs}"
else
opt = libs
end
decltype && try_link(<<"SRC", opt, &b) or
#{headers}
/*top*/
extern int t(void);
#{MAIN_DOES_NOTHING 't'}
int t(void) { #{decltype["volatile p"]}; p = (#{decltype[]})#{func}; return !p; }
SRC
call && try_link(<<"SRC", opt, &b)
#{headers}
/*top*/
extern int t(void);
#{MAIN_DOES_NOTHING 't'}
#{"extern void #{call};" if decltype}
int t(void) { #{prepare}#{call}; return 0; }
SRC
end
# You should use +have_var+ rather than +try_var+.
def try_var(var, headers = nil, opt = "", &b)
headers = cpp_include(headers)
try_compile(<<"SRC", opt, &b)
#{headers}
/*top*/
extern int t(void);
#{MAIN_DOES_NOTHING 't'}
int t(void) { const volatile void *volatile p; p = &(&#{var})[0]; return !p; }
SRC
end
# Returns whether or not the +src+ can be preprocessed with the C
# preprocessor and matches with +pat+.
#
# If a block given, it is called with the source before compilation. You can
# modify the source in the block.
#
# [+pat+] a Regexp or a String
# [+src+] a String which contains a C source
# [+opt+] a String which contains preprocessor options
#
# NOTE: When pat is a Regexp the matching will be checked in process,
# otherwise egrep(1) will be invoked to check it.
def egrep_cpp(pat, src, opt = "", &b)
src = create_tmpsrc(src, &b)
xpopen(cpp_command('', opt)) do |f|
if Regexp === pat
puts(" ruby -ne 'print if #{pat.inspect}'")
f.grep(pat) {|l|
puts "#{f.lineno}: #{l}"
return true
}
false
else
puts(" egrep '#{pat}'")
begin
stdin = $stdin.dup
$stdin.reopen(f)
system("egrep", pat)
ensure
$stdin.reopen(stdin)
end
end
end
ensure
MakeMakefile.rm_f "#{CONFTEST}*"
log_src(src)
end
# This is used internally by the have_macro? method.
def macro_defined?(macro, src, opt = "", &b)
src = src.sub(/[^\n]\z/, "\\&\n")
try_compile(src + <<"SRC", opt, &b)
/*top*/
#ifndef #{macro}
# error
|:/ === #{macro} undefined === /:|
#endif
SRC
end
# Returns whether or not:
# * the +src+ can be compiled as a C source,
# * the result object can be linked with its depending libraries
# successfully,
# * the linked file can be invoked as an executable
# * and the executable exits successfully
#
# +opt+ is passed to the linker as options. Note that +$CFLAGS+ and
# +$LDFLAGS+ are also passed to the linker.
#
# If a block given, it is called with the source before compilation. You can
# modify the source in the block.
#
# [+src+] a String which contains a C source
# [+opt+] a String which contains linker options
#
# Returns true when the executable exits successfully, false when it fails,
# or nil when preprocessing, compilation or link fails.
def try_run(src, opt = "", &b)
raise "cannot run test program while cross compiling" if CROSS_COMPILING
if try_link0(src, opt, &b)
xsystem("./#{CONFTEST}")
else
nil
end
ensure
MakeMakefile.rm_f "#{CONFTEST}*"
end
def install_files(mfile, ifiles, map = nil, srcprefix = nil)
ifiles or return
ifiles.empty? and return
srcprefix ||= "$(srcdir)/#{srcprefix}".chomp('/')
RbConfig::expand(srcdir = srcprefix.dup)
dirs = []
path = Hash.new {|h, i| h[i] = dirs.push([i])[-1]}
ifiles.each do |files, dir, prefix|
dir = map_dir(dir, map)
prefix &&= %r|\A#{Regexp.quote(prefix)}/?|
if /\A\.\// =~ files
# install files which are in current working directory.
files = files[2..-1]
len = nil
else
# install files which are under the $(srcdir).
files = File.join(srcdir, files)
len = srcdir.size
end
f = nil
Dir.glob(files) do |fx|
f = fx
f[0..len] = "" if len
case File.basename(f)
when *$NONINSTALLFILES
next
end
d = File.dirname(f)
d.sub!(prefix, "") if prefix
d = (d.empty? || d == ".") ? dir : File.join(dir, d)
f = File.join(srcprefix, f) if len
path[d] << f
end
unless len or f
d = File.dirname(files)
d.sub!(prefix, "") if prefix
d = (d.empty? || d == ".") ? dir : File.join(dir, d)
path[d] << files
end
end
dirs
end
def install_rb(mfile, dest, srcdir = nil)
install_files(mfile, [["lib/**/*.rb", dest, "lib"]], nil, srcdir)
end
def append_library(libs, lib) # :no-doc:
format(LIBARG, lib) + " " + libs
end
def message(*s)
unless Logging.quiet and not $VERBOSE
printf(*s)
$stdout.flush
end
end
# This emits a string to stdout that allows users to see the results of the
# various have* and find* methods as they are tested.
#
# Internal use only.
#
def checking_for(m, fmt = nil)
f = caller[0][/in `([^<].*)'$/, 1] and f << ": " #` for vim #'
m = "checking #{/\Acheck/ =~ f ? '' : 'for '}#{m}... "
message "%s", m
a = r = nil
Logging::postpone do
r = yield
a = (fmt ? "#{fmt % r}" : r ? "yes" : "no")
"#{f}#{m}-------------------- #{a}\n\n"
end
message "%s\n", a
Logging::message "--------------------\n\n"
r
end
def checking_message(target, place = nil, opt = nil)
[["in", place], ["with", opt]].inject("#{target}") do |msg, (pre, noun)|
if noun
[[:to_str], [:join, ","], [:to_s]].each do |meth, *args|
if noun.respond_to?(meth)
break noun = noun.__send__(meth, *args)
end
end
unless noun.empty?
msg << " #{pre} " unless msg.empty?
msg << noun
end
end
msg
end
end
# :startdoc:
# Check whether each given C compiler flag is acceptable and append it
# to <tt>$CFLAGS</tt> if so.
#
# [+flags+] a C compiler flag as a +String+ or an +Array+ of them
#
def append_cflags(flags, *opts)
Array(flags).each do |flag|
if checking_for("whether #{flag} is accepted as CFLAGS") {
try_cflags(flag, *opts)
}
$CFLAGS << " " << flag
end
end
end
# Returns whether or not +macro+ is defined either in the common header
# files or within any +headers+ you provide.
#
# Any options you pass to +opt+ are passed along to the compiler.
#
def have_macro(macro, headers = nil, opt = "", &b)
checking_for checking_message(macro, headers, opt) do
macro_defined?(macro, cpp_include(headers), opt, &b)
end
end
# Returns whether or not the given entry point +func+ can be found within
# +lib+. If +func+ is +nil+, the <code>main()</code> entry point is used by
# default. If found, it adds the library to list of libraries to be used
# when linking your extension.
#
# If +headers+ are provided, it will include those header files as the
# header files it looks in when searching for +func+.
#
# The real name of the library to be linked can be altered by
# <code>--with-FOOlib</code> configuration option.
#
def have_library(lib, func = nil, headers = nil, opt = "", &b)
dir_config(lib)
lib = with_config(lib+'lib', lib)
checking_for checking_message(func && func.funcall_style, LIBARG%lib, opt) do
if COMMON_LIBS.include?(lib)
true
else
libs = append_library($libs, lib)
if try_func(func, libs, headers, opt, &b)
$libs = libs
true
else
false
end
end
end
end
# Returns whether or not the entry point +func+ can be found within the
# library +lib+ in one of the +paths+ specified, where +paths+ is an array
# of strings. If +func+ is +nil+ , then the <code>main()</code> function is
# used as the entry point.
#
# If +lib+ is found, then the path it was found on is added to the list of
# library paths searched and linked against.
#
def find_library(lib, func, *paths, &b)
dir_config(lib)
lib = with_config(lib+'lib', lib)
paths = paths.flat_map {|path| path.split(File::PATH_SEPARATOR)}
checking_for checking_message(func && func.funcall_style, LIBARG%lib) do
libpath = $LIBPATH
libs = append_library($libs, lib)
begin
until r = try_func(func, libs, &b) or paths.empty?
$LIBPATH = libpath | [paths.shift]
end
if r
$libs = libs
libpath = nil
end
ensure
$LIBPATH = libpath if libpath
end
r
end
end
# Returns whether or not the function +func+ can be found in the common
# header files, or within any +headers+ that you provide. If found, a macro
# is passed as a preprocessor constant to the compiler using the function
# name, in uppercase, prepended with +HAVE_+.
#
# To check functions in an additional library, you need to check that
# library first using <code>have_library()</code>. The +func+ shall be
# either mere function name or function name with arguments.
#
# For example, if <code>have_func('foo')</code> returned +true+, then the
# +HAVE_FOO+ preprocessor macro would be passed to the compiler.
#
def have_func(func, headers = nil, opt = "", &b)
checking_for checking_message(func.funcall_style, headers, opt) do
if try_func(func, $libs, headers, opt, &b)
$defs << "-DHAVE_#{func.sans_arguments.tr_cpp}"
true
else
false
end
end
end
# Returns whether or not the variable +var+ can be found in the common
# header files, or within any +headers+ that you provide. If found, a macro
# is passed as a preprocessor constant to the compiler using the variable
# name, in uppercase, prepended with +HAVE_+.
#
# To check variables in an additional library, you need to check that
# library first using <code>have_library()</code>.
#
# For example, if <code>have_var('foo')</code> returned true, then the
# +HAVE_FOO+ preprocessor macro would be passed to the compiler.
#
def have_var(var, headers = nil, opt = "", &b)
checking_for checking_message(var, headers, opt) do
if try_var(var, headers, opt, &b)
$defs.push(format("-DHAVE_%s", var.tr_cpp))
true
else
false
end
end
end
# Returns whether or not the given +header+ file can be found on your system.
# If found, a macro is passed as a preprocessor constant to the compiler
# using the header file name, in uppercase, prepended with +HAVE_+.
#
# For example, if <code>have_header('foo.h')</code> returned true, then the
# +HAVE_FOO_H+ preprocessor macro would be passed to the compiler.
#
def have_header(header, preheaders = nil, opt = "", &b)
dir_config(header[/.*?(?=\/)|.*?(?=\.)/])
checking_for header do
if try_header(cpp_include(preheaders)+cpp_include(header), opt, &b)
$defs.push(format("-DHAVE_%s", header.tr_cpp))
true
else
false
end
end
end
# Returns whether or not the given +framework+ can be found on your system.
# If found, a macro is passed as a preprocessor constant to the compiler
# using the framework name, in uppercase, prepended with +HAVE_FRAMEWORK_+.
#
# For example, if <code>have_framework('Ruby')</code> returned true, then
# the +HAVE_FRAMEWORK_RUBY+ preprocessor macro would be passed to the
# compiler.
#
# If +fw+ is a pair of the framework name and its header file name
# that header file is checked, instead of the normally used header
# file which is named same as the framework.
def have_framework(fw, &b)
if Array === fw
fw, header = *fw
else
header = "#{fw}.h"
end
checking_for fw do
src = cpp_include("#{fw}/#{header}") << "\n" "int main(void){return 0;}"
opt = " -framework #{fw}"
if try_link(src, opt, &b) or (objc = try_link(src, "-ObjC#{opt}", &b))
$defs.push(format("-DHAVE_FRAMEWORK_%s", fw.tr_cpp))
# TODO: non-worse way than this hack, to get rid of separating
# option and its argument.
$LDFLAGS << " -ObjC" if objc and /(\A|\s)-ObjC(\s|\z)/ !~ $LDFLAGS
$LIBS << opt
true
else
false
end
end
end
# Instructs mkmf to search for the given +header+ in any of the +paths+
# provided, and returns whether or not it was found in those paths.
#
# If the header is found then the path it was found on is added to the list
# of included directories that are sent to the compiler (via the
# <code>-I</code> switch).
#
def find_header(header, *paths)
message = checking_message(header, paths)
header = cpp_include(header)
checking_for message do
if try_header(header)
true
else
found = false
paths.each do |dir|
opt = "-I#{dir}".quote
if try_header(header, opt)
$INCFLAGS << " " << opt
found = true
break
end
end
found
end
end
end
# Returns whether or not the struct of type +type+ contains +member+. If
# it does not, or the struct type can't be found, then false is returned.
# You may optionally specify additional +headers+ in which to look for the
# struct (in addition to the common header files).
#
# If found, a macro is passed as a preprocessor constant to the compiler
# using the type name and the member name, in uppercase, prepended with
# +HAVE_+.
#
# For example, if <code>have_struct_member('struct foo', 'bar')</code>
# returned true, then the +HAVE_STRUCT_FOO_BAR+ preprocessor macro would be
# passed to the compiler.
#
# +HAVE_ST_BAR+ is also defined for backward compatibility.
#
def have_struct_member(type, member, headers = nil, opt = "", &b)
checking_for checking_message("#{type}.#{member}", headers) do
if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
int s = (char *)&((#{type}*)0)->#{member} - (char *)0;
#{MAIN_DOES_NOTHING}
SRC
$defs.push(format("-DHAVE_%s_%s", type.tr_cpp, member.tr_cpp))
$defs.push(format("-DHAVE_ST_%s", member.tr_cpp)) # backward compatibility
true
else
false
end
end
end
# Returns whether or not the static type +type+ is defined.
#
# See also +have_type+
#
def try_type(type, headers = nil, opt = "", &b)
if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
typedef #{type} conftest_type;
int conftestval[sizeof(conftest_type)?1:-1];
SRC
$defs.push(format("-DHAVE_TYPE_%s", type.tr_cpp))
true
else
false
end
end
# Returns whether or not the static type +type+ is defined. You may
# optionally pass additional +headers+ to check against in addition to the
# common header files.
#
# You may also pass additional flags to +opt+ which are then passed along to
# the compiler.
#
# If found, a macro is passed as a preprocessor constant to the compiler
# using the type name, in uppercase, prepended with +HAVE_TYPE_+.
#
# For example, if <code>have_type('foo')</code> returned true, then the
# +HAVE_TYPE_FOO+ preprocessor macro would be passed to the compiler.
#
def have_type(type, headers = nil, opt = "", &b)
checking_for checking_message(type, headers, opt) do
try_type(type, headers, opt, &b)
end
end
# Returns where the static type +type+ is defined.
#
# You may also pass additional flags to +opt+ which are then passed along to
# the compiler.
#
# See also +have_type+.
#
def find_type(type, opt, *headers, &b)
opt ||= ""
fmt = "not found"
def fmt.%(x)
x ? x.respond_to?(:join) ? x.join(",") : x : self
end
checking_for checking_message(type, nil, opt), fmt do
headers.find do |h|
try_type(type, h, opt, &b)
end
end
end
# Returns whether or not the constant +const+ is defined.
#
# See also +have_const+
#
def try_const(const, headers = nil, opt = "", &b)
const, type = *const
if try_compile(<<"SRC", opt, &b)
#{cpp_include(headers)}
/*top*/
typedef #{type || 'int'} conftest_type;
conftest_type conftestval = #{type ? '' : '(int)'}#{const};
SRC
$defs.push(format("-DHAVE_CONST_%s", const.tr_cpp))
true
else
false
end
end
# Returns whether or not the constant +const+ is defined. You may
# optionally pass the +type+ of +const+ as <code>[const, type]</code>,
# such as:
#
# have_const(%w[PTHREAD_MUTEX_INITIALIZER pthread_mutex_t], "pthread.h")
#
# You may also pass additional +headers+ to check against in addition to the
# common header files, and additional flags to +opt+ which are then passed
# along to the compiler.
#
# If found, a macro is passed as a preprocessor constant to the compiler
# using the type name, in uppercase, prepended with +HAVE_CONST_+.
#
# For example, if <code>have_const('foo')</code> returned true, then the
# +HAVE_CONST_FOO+ preprocessor macro would be passed to the compiler.
#
def have_const(const, headers = nil, opt = "", &b)
checking_for checking_message([*const].compact.join(' '), headers, opt) do
try_const(const, headers, opt, &b)
end
end
# :stopdoc:
STRING_OR_FAILED_FORMAT = "%s"
class << STRING_OR_FAILED_FORMAT # :nodoc:
def %(x)
x ? super : "failed"
end
end
def typedef_expr(type, headers)
typename, member = type.split('.', 2)
prelude = cpp_include(headers).split(/$/)
prelude << "typedef #{typename} rbcv_typedef_;\n"
return "rbcv_typedef_", member, prelude
end
def try_signedness(type, member, headers = nil, opts = nil)
raise ArgumentError, "don't know how to tell signedness of members" if member
if try_static_assert("(#{type})-1 < 0", headers, opts)
return -1
elsif try_static_assert("(#{type})-1 > 0", headers, opts)
return +1
end
end
# :startdoc:
# Returns the size of the given +type+. You may optionally specify
# additional +headers+ to search in for the +type+.
#
# If found, a macro is passed as a preprocessor constant to the compiler
# using the type name, in uppercase, prepended with +SIZEOF_+, followed by
# the type name, followed by <code>=X</code> where "X" is the actual size.
#
# For example, if <code>check_sizeof('mystruct')</code> returned 12, then
# the <code>SIZEOF_MYSTRUCT=12</code> preprocessor macro would be passed to
# the compiler.
#
def check_sizeof(type, headers = nil, opts = "", &b)
typedef, member, prelude = typedef_expr(type, headers)
prelude << "#{typedef} *rbcv_ptr_;\n"
prelude = [prelude]
expr = "sizeof((*rbcv_ptr_)#{"." << member if member})"
fmt = STRING_OR_FAILED_FORMAT
checking_for checking_message("size of #{type}", headers), fmt do
if size = try_constant(expr, prelude, opts, &b)
$defs.push(format("-DSIZEOF_%s=%s", type.tr_cpp, size))
size
end
end
end
# Returns the signedness of the given +type+. You may optionally specify
# additional +headers+ to search in for the +type+.
#
# If the +type+ is found and is a numeric type, a macro is passed as a
# preprocessor constant to the compiler using the +type+ name, in uppercase,
# prepended with +SIGNEDNESS_OF_+, followed by the +type+ name, followed by
# <code>=X</code> where "X" is positive integer if the +type+ is unsigned
# and a negative integer if the +type+ is signed.
#
# For example, if +size_t+ is defined as unsigned, then
# <code>check_signedness('size_t')</code> would return +1 and the
# <code>SIGNEDNESS_OF_SIZE_T=+1</code> preprocessor macro would be passed to
# the compiler. The <code>SIGNEDNESS_OF_INT=-1</code> macro would be set
# for <code>check_signedness('int')</code>
#
def check_signedness(type, headers = nil, opts = nil, &b)
typedef, member, prelude = typedef_expr(type, headers)
signed = nil
checking_for("signedness of #{type}", STRING_OR_FAILED_FORMAT) do
signed = try_signedness(typedef, member, [prelude], opts, &b) or next nil
$defs.push("-DSIGNEDNESS_OF_%s=%+d" % [type.tr_cpp, signed])
signed < 0 ? "signed" : "unsigned"
end
signed
end
# Returns the convertible integer type of the given +type+. You may
# optionally specify additional +headers+ to search in for the +type+.
# _convertible_ means actually the same type, or typedef'd from the same
# type.
#
# If the +type+ is an integer type and the _convertible_ type is found,
# the following macros are passed as preprocessor constants to the compiler
# using the +type+ name, in uppercase.
#
# * +TYPEOF_+, followed by the +type+ name, followed by <code>=X</code>
# where "X" is the found _convertible_ type name.
# * +TYP2NUM+ and +NUM2TYP+,
# where +TYP+ is the +type+ name in uppercase with replacing an +_t+
# suffix with "T", followed by <code>=X</code> where "X" is the macro name
# to convert +type+ to an Integer object, and vice versa.
#
# For example, if +foobar_t+ is defined as unsigned long, then
# <code>convertible_int("foobar_t")</code> would return "unsigned long", and
# define these macros:
#
# #define TYPEOF_FOOBAR_T unsigned long
# #define FOOBART2NUM ULONG2NUM
# #define NUM2FOOBART NUM2ULONG
#
def convertible_int(type, headers = nil, opts = nil, &b)
type, macname = *type
checking_for("convertible type of #{type}", STRING_OR_FAILED_FORMAT) do
if UNIVERSAL_INTS.include?(type)
type
else
typedef, member, prelude = typedef_expr(type, headers, &b)
if member
prelude << "static rbcv_typedef_ rbcv_var;"
compat = UNIVERSAL_INTS.find {|t|
try_static_assert("sizeof(rbcv_var.#{member}) == sizeof(#{t})", [prelude], opts, &b)
}
else
next unless signed = try_signedness(typedef, member, [prelude])
u = "unsigned " if signed > 0
prelude << "extern rbcv_typedef_ foo();"
compat = UNIVERSAL_INTS.find {|t|
try_compile([prelude, "extern #{u}#{t} foo();"].join("\n"), opts, :werror=>true, &b)
}
end
if compat
macname ||= type.sub(/_(?=t\z)/, '').tr_cpp
conv = (compat == "long long" ? "LL" : compat.upcase)
compat = "#{u}#{compat}"
typename = type.tr_cpp
$defs.push(format("-DSIZEOF_%s=SIZEOF_%s", typename, compat.tr_cpp))
$defs.push(format("-DTYPEOF_%s=%s", typename, compat.quote))
$defs.push(format("-DPRI_%s_PREFIX=PRI_%s_PREFIX", macname, conv))
conv = (u ? "U" : "") + conv
$defs.push(format("-D%s2NUM=%s2NUM", macname, conv))
$defs.push(format("-DNUM2%s=NUM2%s", macname, conv))
compat
end
end
end
end
# :stopdoc:
# Used internally by the what_type? method to determine if +type+ is a scalar
# pointer.
def scalar_ptr_type?(type, member = nil, headers = nil, &b)
try_compile(<<"SRC", &b) # pointer
#{cpp_include(headers)}
/*top*/
volatile #{type} conftestval;
extern int t(void);
#{MAIN_DOES_NOTHING 't'}
int t(void) {return (int)(1-*(conftestval#{member ? ".#{member}" : ""}));}
SRC
end
# Used internally by the what_type? method to determine if +type+ is a scalar
# pointer.
def scalar_type?(type, member = nil, headers = nil, &b)
try_compile(<<"SRC", &b) # pointer
#{cpp_include(headers)}
/*top*/
volatile #{type} conftestval;
extern int t(void);
#{MAIN_DOES_NOTHING 't'}
int t(void) {return (int)(1-(conftestval#{member ? ".#{member}" : ""}));}
SRC
end
# Used internally by the what_type? method to check if the _typeof_ GCC
# extension is available.
def have_typeof?
return $typeof if defined?($typeof)
$typeof = %w[__typeof__ typeof].find do |t|
try_compile(<<SRC)
int rbcv_foo;
#{t}(rbcv_foo) rbcv_bar;
SRC
end
end
def what_type?(type, member = nil, headers = nil, &b)
m = "#{type}"
var = val = "*rbcv_var_"
func = "rbcv_func_(void)"
if member
m << "." << member
else
type, member = type.split('.', 2)
end
if member
val = "(#{var}).#{member}"
end
prelude = [cpp_include(headers).split(/^/)]
prelude << ["typedef #{type} rbcv_typedef_;\n",
"extern rbcv_typedef_ *#{func};\n",
"rbcv_typedef_ #{var};\n",
]
type = "rbcv_typedef_"
fmt = member && !(typeof = have_typeof?) ? "seems %s" : "%s"
if typeof
var = "*rbcv_member_"
func = "rbcv_mem_func_(void)"
member = nil
type = "rbcv_mem_typedef_"
prelude[-1] << "typedef #{typeof}(#{val}) #{type};\n"
prelude[-1] << "extern #{type} *#{func};\n"
prelude[-1] << "#{type} #{var};\n"
val = var
end
def fmt.%(x)
x ? super : "unknown"
end
checking_for checking_message(m, headers), fmt do
if scalar_ptr_type?(type, member, prelude, &b)
if try_static_assert("sizeof(*#{var}) == 1", prelude)
return "string"
end
ptr = "*"
elsif scalar_type?(type, member, prelude, &b)
unless member and !typeof or try_static_assert("(#{type})-1 < 0", prelude)
unsigned = "unsigned"
end
ptr = ""
else
next
end
type = UNIVERSAL_INTS.find do |t|
pre = prelude
unless member
pre += [["#{unsigned} #{t} #{ptr}#{var};\n",
"extern #{unsigned} #{t} #{ptr}*#{func};\n"]]
end
try_static_assert("sizeof(#{ptr}#{val}) == sizeof(#{unsigned} #{t})", pre)
end
type or next
[unsigned, type, ptr].join(" ").strip
end
end
# This method is used internally by the find_executable method.
#
# Internal use only.
#
def find_executable0(bin, path = nil)
executable_file = proc do |name|
begin
stat = File.stat(name)
rescue SystemCallError
else
next name if stat.file? and stat.executable?
end
end
exts = config_string('EXECUTABLE_EXTS') {|s| s.split} || config_string('EXEEXT') {|s| [s]}
if File.expand_path(bin) == bin
return bin if executable_file.call(bin)
if exts
exts.each {|ext| executable_file.call(file = bin + ext) and return file}
end
return nil
end
if path ||= ENV['PATH']
path = path.split(File::PATH_SEPARATOR)
else
path = %w[/usr/local/bin /usr/ucb /usr/bin /bin]
end
file = nil
path.each do |dir|
dir.sub!(/\A"(.*)"\z/m, '\1') if $mswin or $mingw
return file if executable_file.call(file = File.join(dir, bin))
if exts
exts.each {|ext| executable_file.call(ext = file + ext) and return ext}
end
end
nil
end
# :startdoc:
# Searches for the executable +bin+ on +path+. The default path is your
# +PATH+ environment variable. If that isn't defined, it will resort to
# searching /usr/local/bin, /usr/ucb, /usr/bin and /bin.
#
# If found, it will return the full path, including the executable name, of
# where it was found.
#
# Note that this method does not actually affect the generated Makefile.
#
def find_executable(bin, path = nil)
checking_for checking_message(bin, path) do
find_executable0(bin, path)
end
end
# :stopdoc:
def arg_config(config, default=nil, &block)
$arg_config << [config, default]
defaults = []
if default
defaults << default
elsif !block
defaults << nil
end
$configure_args.fetch(config.tr('_', '-'), *defaults, &block)
end
# :startdoc:
# Tests for the presence of a <tt>--with-</tt>_config_ or
# <tt>--without-</tt>_config_ option. Returns +true+ if the with option is
# given, +false+ if the without option is given, and the default value
# otherwise.
#
# This can be useful for adding custom definitions, such as debug
# information.
#
# Example:
#
# if with_config("debug")
# $defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG"
# end
#
def with_config(config, default=nil)
config = config.sub(/^--with[-_]/, '')
val = arg_config("--with-"+config) do
if arg_config("--without-"+config)
false
elsif block_given?
yield(config, default)
else
break default
end
end
case val
when "yes"
true
when "no"
false
else
val
end
end
# Tests for the presence of an <tt>--enable-</tt>_config_ or
# <tt>--disable-</tt>_config_ option. Returns +true+ if the enable option is
# given, +false+ if the disable option is given, and the default value
# otherwise.
#
# This can be useful for adding custom definitions, such as debug
# information.
#
# Example:
#
# if enable_config("debug")
# $defs.push("-DOSSL_DEBUG") unless $defs.include? "-DOSSL_DEBUG"
# end
#
def enable_config(config, default=nil)
if arg_config("--enable-"+config)
true
elsif arg_config("--disable-"+config)
false
elsif block_given?
yield(config, default)
else
return default
end
end
# Generates a header file consisting of the various macro definitions
# generated by other methods such as have_func and have_header. These are
# then wrapped in a custom <code>#ifndef</code> based on the +header+ file
# name, which defaults to "extconf.h".
#
# For example:
#
# # extconf.rb
# require 'mkmf'
# have_func('realpath')
# have_header('sys/utime.h')
# create_header
# create_makefile('foo')
#
# The above script would generate the following extconf.h file:
#
# #ifndef EXTCONF_H
# #define EXTCONF_H
# #define HAVE_REALPATH 1
# #define HAVE_SYS_UTIME_H 1
# #endif
#
# Given that the create_header method generates a file based on definitions
# set earlier in your extconf.rb file, you will probably want to make this
# one of the last methods you call in your script.
#
def create_header(header = "extconf.h")
message "creating %s\n", header
sym = header.tr_cpp
hdr = ["#ifndef #{sym}\n#define #{sym}\n"]
for line in $defs
case line
when /^-D([^=]+)(?:=(.*))?/
hdr << "#define #$1 #{$2 ? Shellwords.shellwords($2)[0].gsub(/(?=\t+)/, "\\\n") : 1}\n"
when /^-U(.*)/
hdr << "#undef #$1\n"
end
end
hdr << "#endif\n"
hdr = hdr.join("")
log_src(hdr, "#{header} is")
unless (File.read(header) == hdr rescue false)
File.open(header, "wb") do |hfile|
hfile.write(hdr)
end
end
$extconf_h = header
end
# call-seq:
# dir_config(target)
# dir_config(target, prefix)
# dir_config(target, idefault, ldefault)
#
# Sets a +target+ name that the user can then use to configure
# various "with" options with on the command line by using that
# name. For example, if the target is set to "foo", then the user
# could use the <code>--with-foo-dir=prefix</code>,
# <code>--with-foo-include=dir</code> and
# <code>--with-foo-lib=dir</code> command line options to tell where
# to search for header/library files.
#
# You may pass along additional parameters to specify default
# values. If one is given it is taken as default +prefix+, and if
# two are given they are taken as "include" and "lib" defaults in
# that order.
#
# In any case, the return value will be an array of determined
# "include" and "lib" directories, either of which can be nil if no
# corresponding command line option is given when no default value
# is specified.
#
# Note that dir_config only adds to the list of places to search for
# libraries and include files. It does not link the libraries into your
# application.
#
def dir_config(target, idefault=nil, ldefault=nil)
if conf = $config_dirs[target]
return conf
end
if dir = with_config(target + "-dir", (idefault unless ldefault))
defaults = Array === dir ? dir : dir.split(File::PATH_SEPARATOR)
idefault = ldefault = nil
end
idir = with_config(target + "-include", idefault)
$arg_config.last[1] ||= "${#{target}-dir}/include"
ldir = with_config(target + "-lib", ldefault)
$arg_config.last[1] ||= "${#{target}-dir}/#{_libdir_basename}"
idirs = idir ? Array === idir ? idir.dup : idir.split(File::PATH_SEPARATOR) : []
if defaults
idirs.concat(defaults.collect {|d| d + "/include"})
idir = ([idir] + idirs).compact.join(File::PATH_SEPARATOR)
end
unless idirs.empty?
idirs.collect! {|d| "-I" + d}
idirs -= Shellwords.shellwords($CPPFLAGS)
unless idirs.empty?
$CPPFLAGS = (idirs.quote << $CPPFLAGS).join(" ")
end
end
ldirs = ldir ? Array === ldir ? ldir.dup : ldir.split(File::PATH_SEPARATOR) : []
if defaults
ldirs.concat(defaults.collect {|d| "#{d}/#{_libdir_basename}"})
ldir = ([ldir] + ldirs).compact.join(File::PATH_SEPARATOR)
end
$LIBPATH = ldirs | $LIBPATH
$config_dirs[target] = [idir, ldir]
end
# Returns compile/link information about an installed library in a tuple of <code>[cflags,
# ldflags, libs]</code>, by using the command found first in the following commands:
#
# 1. If <code>--with-{pkg}-config={command}</code> is given via
# command line option: <code>{command} {options}</code>
#
# 2. <code>{pkg}-config {options}</code>
#
# 3. <code>pkg-config {options} {pkg}</code>
#
# Where +options+ is the option name without dashes, for instance <code>"cflags"</code> for the
# <code>--cflags</code> flag.
#
# The values obtained are appended to <code>$INCFLAGS</code>, <code>$CFLAGS</code>,
# <code>$LDFLAGS</code> and <code>$libs</code>.
#
# If one or more <code>options</code> argument is given, the config command is
# invoked with the options and a stripped output string is returned without
# modifying any of the global values mentioned above.
def pkg_config(pkg, *options)
_, ldir = dir_config(pkg)
if ldir
pkg_config_path = "#{ldir}/pkgconfig"
if File.directory?(pkg_config_path)
Logging.message("PKG_CONFIG_PATH = %s\n", pkg_config_path)
envs = ["PKG_CONFIG_PATH"=>[pkg_config_path, ENV["PKG_CONFIG_PATH"]].compact.join(File::PATH_SEPARATOR)]
end
end
if pkgconfig = with_config("#{pkg}-config") and find_executable0(pkgconfig)
# if and only if package specific config command is given
elsif ($PKGCONFIG ||=
(pkgconfig = with_config("pkg-config") {config_string("PKG_CONFIG") || "pkg-config"}) &&
find_executable0(pkgconfig) && pkgconfig) and
xsystem([*envs, $PKGCONFIG, "--exists", pkg])
# default to pkg-config command
pkgconfig = $PKGCONFIG
args = [pkg]
elsif find_executable0(pkgconfig = "#{pkg}-config")
# default to package specific config command, as a last resort.
else
pkgconfig = nil
end
if pkgconfig
get = proc {|opts|
opts = Array(opts).map { |o| "--#{o}" }
opts = xpopen([*envs, pkgconfig, *opts, *args], err:[:child, :out], &:read)
Logging.open {puts opts.each_line.map{|s|"=> #{s.inspect}"}}
opts.strip if $?.success?
}
end
orig_ldflags = $LDFLAGS
if get and !options.empty?
get[options]
elsif get and try_ldflags(ldflags = get['libs'])
if incflags = get['cflags-only-I']
$INCFLAGS << " " << incflags
cflags = get['cflags-only-other']
else
cflags = get['cflags']
end
libs = get['libs-only-l']
if cflags
$CFLAGS += " " << cflags
$CXXFLAGS += " " << cflags
end
if libs
ldflags = (Shellwords.shellwords(ldflags) - Shellwords.shellwords(libs)).quote.join(" ")
else
libs, ldflags = Shellwords.shellwords(ldflags).partition {|s| s =~ /-l([^ ]+)/ }.map {|l|l.quote.join(" ")}
end
$libs += " " << libs
$LDFLAGS = [orig_ldflags, ldflags].join(' ')
Logging::message "package configuration for %s\n", pkg
Logging::message "incflags: %s\ncflags: %s\nldflags: %s\nlibs: %s\n\n",
incflags, cflags, ldflags, libs
[[incflags, cflags].join(' '), ldflags, libs]
else
Logging::message "package configuration for %s is not found\n", pkg
nil
end
end
# :stopdoc:
def with_destdir(dir)
dir = dir.sub($dest_prefix_pattern, '')
/\A\$[\(\{]/ =~ dir ? dir : "$(DESTDIR)"+dir
end
# Converts forward slashes to backslashes. Aimed at MS Windows.
#
# Internal use only.
#
def winsep(s)
s.tr('/', '\\')
end
# Converts native path to format acceptable in Makefile
#
# Internal use only.
#
if !CROSS_COMPILING
case CONFIG['build_os']
when 'mingw32'
def mkintpath(path)
# mingw uses make from msys and it needs special care
# converts from C:\some\path to /C/some/path
path = path.dup
path.tr!('\\', '/')
path.sub!(/\A([A-Za-z]):(?=\/)/, '/\1')
path
end
when 'cygwin', 'msys'
if CONFIG['target_os'] != 'cygwin'
def mkintpath(path)
IO.popen(["cygpath", "-u", path], &:read).chomp
end
end
end
end
unless method_defined?(:mkintpath)
def mkintpath(path)
path
end
end
def configuration(srcdir)
mk = []
CONFIG['MKMF_VERBOSE'] ||= "0"
vpath = $VPATH.dup
CONFIG["hdrdir"] ||= $hdrdir
mk << %{
SHELL = /bin/sh
# V=0 quiet, V=1 verbose. other values don't work.
V = 1
V0 = $(V:0=)
Q1 = $(V:1=)
Q = $(Q1:0=@)
ECHO1 = $(V:1=@ #{CONFIG['NULLCMD']})
ECHO = $(ECHO1:0=@ echo)
NULLCMD = #{CONFIG['NULLCMD']}
#### Start of system configuration section. ####
#{"top_srcdir = " + $top_srcdir.sub(%r"\A#{Regexp.quote($topdir)}/", "$(topdir)/") if $extmk}
srcdir = #{srcdir.gsub(/\$\((srcdir)\)|\$\{(srcdir)\}/) {mkintpath(CONFIG[$1||$2]).unspace}}
topdir = #{mkintpath(topdir = $extmk ? CONFIG["topdir"] : $topdir).unspace}
hdrdir = #{(hdrdir = CONFIG["hdrdir"]) == topdir ? "$(topdir)" : mkintpath(hdrdir).unspace}
arch_hdrdir = #{mkintpath($arch_hdrdir).unspace}
PATH_SEPARATOR = #{CONFIG['PATH_SEPARATOR']}
VPATH = #{vpath.join(CONFIG['PATH_SEPARATOR'])}
}
if $extmk
mk << "RUBYLIB =\n""RUBYOPT = -\n"
end
prefix = mkintpath(CONFIG["prefix"])
if destdir = prefix[$dest_prefix_pattern, 1]
mk << "\nDESTDIR = #{destdir}\n"
prefix = prefix[destdir.size..-1]
end
mk << "prefix = #{with_destdir(prefix).unspace}\n"
CONFIG.each do |key, var|
mk << "#{key} = #{with_destdir(mkintpath(var)).unspace}\n" if /.prefix$/ =~ key
end
CONFIG.each do |key, var|
next if /^abs_/ =~ key
next if /^(?:src|top(?:_src)?|build|hdr)dir$/ =~ key
next unless /dir$/ =~ key
mk << "#{key} = #{with_destdir(var)}\n"
end
if !$extmk and !$configure_args.has_key?('--ruby') and
sep = config_string('BUILD_FILE_SEPARATOR')
sep = ":/=#{sep}"
else
sep = ""
end
possible_command = (proc {|s| s if /top_srcdir|tooldir/ !~ s} unless $extmk)
extconf_h = $extconf_h ? "-DRUBY_EXTCONF_H=\\\"$(RUBY_EXTCONF_H)\\\" " : $defs.join(" ") << " "
headers = %w[
$(hdrdir)/ruby.h
$(hdrdir)/ruby/backward.h
$(hdrdir)/ruby/ruby.h
$(hdrdir)/ruby/defines.h
$(hdrdir)/ruby/missing.h
$(hdrdir)/ruby/intern.h
$(hdrdir)/ruby/st.h
$(hdrdir)/ruby/subst.h
]
headers += $headers
if RULE_SUBST
headers.each {|h| h.sub!(/.*/, &RULE_SUBST.method(:%))}
end
headers << $config_h
headers << '$(RUBY_EXTCONF_H)' if $extconf_h
mk << %{
CC_WRAPPER = #{CONFIG['CC_WRAPPER']}
CC = #{CONFIG['CC']}
CXX = #{CONFIG['CXX']}
LIBRUBY = #{CONFIG['LIBRUBY']}
LIBRUBY_A = #{CONFIG['LIBRUBY_A']}
LIBRUBYARG_SHARED = #$LIBRUBYARG_SHARED
LIBRUBYARG_STATIC = #$LIBRUBYARG_STATIC
empty =
OUTFLAG = #{OUTFLAG}$(empty)
COUTFLAG = #{COUTFLAG}$(empty)
CSRCFLAG = #{CSRCFLAG}$(empty)
RUBY_EXTCONF_H = #{$extconf_h}
cflags = #{CONFIG['cflags']}
cxxflags = #{CONFIG['cxxflags']}
optflags = #{CONFIG['optflags']}
debugflags = #{CONFIG['debugflags']}
warnflags = #{$warnflags}
cppflags = #{CONFIG['cppflags']}
CCDLFLAGS = #{$static ? '' : CONFIG['CCDLFLAGS']}
CFLAGS = $(CCDLFLAGS) #$CFLAGS $(ARCH_FLAG)
INCFLAGS = -I. #$INCFLAGS
DEFS = #{CONFIG['DEFS']}
CPPFLAGS = #{extconf_h}#{$CPPFLAGS}
CXXFLAGS = $(CCDLFLAGS) #$CXXFLAGS $(ARCH_FLAG)
ldflags = #{$LDFLAGS}
dldflags = #{$DLDFLAGS} #{CONFIG['EXTDLDFLAGS']}
ARCH_FLAG = #{$ARCH_FLAG}
DLDFLAGS = $(ldflags) $(dldflags) $(ARCH_FLAG)
LDSHARED = #{CONFIG['LDSHARED']}
LDSHAREDXX = #{config_string('LDSHAREDXX') || '$(LDSHARED)'}
AR = #{CONFIG['AR']}
EXEEXT = #{CONFIG['EXEEXT']}
}
CONFIG.each do |key, val|
mk << "#{key} = #{val}\n" if /^RUBY.*NAME/ =~ key
end
mk << %{
arch = #{CONFIG['arch']}
sitearch = #{CONFIG['sitearch']}
ruby_version = #{RbConfig::CONFIG['ruby_version']}
ruby = #{$ruby.sub(%r[\A#{Regexp.quote(RbConfig::CONFIG['bindir'])}(?=/|\z)]) {'$(bindir)'}}
RUBY = $(ruby#{sep})
BUILTRUBY = #{if defined?($builtruby) && $builtruby
$builtruby
else
File.join('$(bindir)', CONFIG["RUBY_INSTALL_NAME"] + CONFIG['EXEEXT'])
end}
ruby_headers = #{headers.join(' ')}
RM = #{config_string('RM', &possible_command) || '$(RUBY) -run -e rm -- -f'}
RM_RF = #{config_string('RMALL', &possible_command) || '$(RUBY) -run -e rm -- -rf'}
RMDIRS = #{config_string('RMDIRS', &possible_command) || '$(RUBY) -run -e rmdir -- -p'}
MAKEDIRS = #{config_string('MAKEDIRS', &possible_command) || '@$(RUBY) -run -e mkdir -- -p'}
INSTALL = #{config_string('INSTALL', &possible_command) || '@$(RUBY) -run -e install -- -vp'}
INSTALL_PROG = #{config_string('INSTALL_PROG') || '$(INSTALL) -m 0755'}
INSTALL_DATA = #{config_string('INSTALL_DATA') || '$(INSTALL) -m 0644'}
COPY = #{config_string('CP', &possible_command) || '@$(RUBY) -run -e cp -- -v'}
TOUCH = exit >
#### End of system configuration section. ####
preload = #{defined?($preload) && $preload ? $preload.join(' ') : ''}
}
mk
end
def timestamp_file(name, target_prefix = nil)
pat = {}
name = '$(RUBYARCHDIR)' if name == '$(TARGET_SO_DIR)'
install_dirs.each do |n, d|
pat[n] = $` if /\$\(target_prefix\)\z/ =~ d
end
name = name.gsub(/\$\((#{pat.keys.join("|")})\)/) {pat[$1]+target_prefix}
name.sub!(/(\$\((?:site)?arch\))\/*/, '')
arch = $1 || ''
name.chomp!('/')
name = name.gsub(/(\$[({]|[})])|(\/+)|[^-.\w]+/) {$1 ? "" : $2 ? ".-." : "_"}
File.join("$(TIMESTAMP_DIR)", arch, "#{name.sub(/\A(?=.)/, '.')}.time")
end
# :startdoc:
# Creates a stub Makefile.
#
def dummy_makefile(srcdir)
configuration(srcdir) << <<RULES << CLEANINGS
CLEANFILES = #{$cleanfiles.join(' ')}
DISTCLEANFILES = #{$distcleanfiles.join(' ')}
all install static install-so install-rb: Makefile
@$(NULLCMD)
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
RULES
end
def each_compile_rules # :nodoc:
vpath_splat = /\$\(\*VPATH\*\)/
COMPILE_RULES.each do |rule|
if vpath_splat =~ rule
$VPATH.each do |path|
yield rule.sub(vpath_splat) {path}
end
else
yield rule
end
end
end
# Processes the data contents of the "depend" file. Each line of this file
# is expected to be a file name.
#
# Returns the output of findings, in Makefile format.
#
def depend_rules(depend)
suffixes = []
depout = []
cont = implicit = nil
impconv = proc do
each_compile_rules {|rule| depout << (rule % implicit[0]) << implicit[1]}
implicit = nil
end
ruleconv = proc do |line|
if implicit
if /\A\t/ =~ line
implicit[1] << line
next
else
impconv[]
end
end
if m = /\A\.(\w+)\.(\w+)(?:\s*:)/.match(line)
suffixes << m[1] << m[2]
implicit = [[m[1], m[2]], [m.post_match]]
next
elsif RULE_SUBST and /\A(?!\s*\w+\s*=)[$\w][^#]*:/ =~ line
line.sub!(/\s*\#.*$/, '')
comment = $&
line.gsub!(%r"(\s)(?!\.)([^$(){}+=:\s\\,]+)(?=\s|\z)") {$1 + RULE_SUBST % $2}
line = line.chomp + comment + "\n" if comment
end
depout << line
end
depend.each_line do |line|
line.gsub!(/\.o\b/, ".#{$OBJEXT}")
line.gsub!(/\{\$\(VPATH\)\}/, "") unless $nmake
line.gsub!(/\$\((?:hdr|top)dir\)\/config.h/, $config_h)
if $nmake && /\A\s*\$\(RM|COPY\)/ =~ line
line.gsub!(%r"[-\w\./]{2,}"){$&.tr("/", "\\")}
line.gsub!(/(\$\((?!RM|COPY)[^:)]+)(?=\))/, '\1:/=\\')
end
if /(?:^|[^\\])(?:\\\\)*\\$/ =~ line
(cont ||= []) << line
next
elsif cont
line = (cont << line).join
cont = nil
end
ruleconv.call(line)
end
if cont
ruleconv.call(cont.join)
elsif implicit
impconv.call
end
unless suffixes.empty?
depout.unshift(".SUFFIXES: ." + suffixes.uniq.join(" .") + "\n\n")
end
if $extconf_h
depout.unshift("$(OBJS): $(RUBY_EXTCONF_H)\n\n")
depout.unshift("$(OBJS): $(hdrdir)/ruby/win32.h\n\n") if $mswin or $mingw
end
depout.flatten!
depout
end
# Generates the Makefile for your extension, passing along any options and
# preprocessor constants that you may have generated through other methods.
#
# The +target+ name should correspond the name of the global function name
# defined within your C extension, minus the +Init_+. For example, if your
# C extension is defined as +Init_foo+, then your target would simply be
# "foo".
#
# If any "/" characters are present in the target name, only the last name
# is interpreted as the target name, and the rest are considered toplevel
# directory names, and the generated Makefile will be altered accordingly to
# follow that directory structure.
#
# For example, if you pass "test/foo" as a target name, your extension will
# be installed under the "test" directory. This means that in order to
# load the file within a Ruby program later, that directory structure will
# have to be followed, e.g. <code>require 'test/foo'</code>.
#
# The +srcprefix+ should be used when your source files are not in the same
# directory as your build script. This will not only eliminate the need for
# you to manually copy the source files into the same directory as your
# build script, but it also sets the proper +target_prefix+ in the generated
# Makefile.
#
# Setting the +target_prefix+ will, in turn, install the generated binary in
# a directory under your <code>RbConfig::CONFIG['sitearchdir']</code> that
# mimics your local filesystem when you run <code>make install</code>.
#
# For example, given the following file tree:
#
# ext/
# extconf.rb
# test/
# foo.c
#
# And given the following code:
#
# create_makefile('test/foo', 'test')
#
# That will set the +target_prefix+ in the generated Makefile to "test".
# That, in turn, will create the following file tree when installed via the
# <code>make install</code> command:
#
# /path/to/ruby/sitearchdir/test/foo.so
#
# It is recommended that you use this approach to generate your makefiles,
# instead of copying files around manually, because some third party
# libraries may depend on the +target_prefix+ being set properly.
#
# The +srcprefix+ argument can be used to override the default source
# directory, i.e. the current directory. It is included as part of the
# +VPATH+ and added to the list of +INCFLAGS+.
#
def create_makefile(target, srcprefix = nil)
$target = target
libpath = $DEFLIBPATH|$LIBPATH
message "creating Makefile\n"
MakeMakefile.rm_f "#{CONFTEST}*"
if CONFIG["DLEXT"] == $OBJEXT
for lib in libs = $libs.split(' ')
lib.sub!(/-l(.*)/, %%"lib\\1.#{$LIBEXT}"%)
end
$defs.push(format("-DEXTLIB='%s'", libs.join(",")))
end
if target.include?('/')
target_prefix, target = File.split(target)
target_prefix[0,0] = '/'
else
target_prefix = ""
end
srcprefix ||= "$(srcdir)/#{srcprefix}".chomp('/')
RbConfig.expand(srcdir = srcprefix.dup)
ext = ".#{$OBJEXT}"
orig_srcs = Dir[File.join(srcdir, "*.{#{SRC_EXT.join(%q{,})}}")]
if not $objs
srcs = $srcs || orig_srcs
$objs = []
objs = srcs.inject(Hash.new {[]}) {|h, f|
h.key?(o = File.basename(f, ".*") << ext) or $objs << o
h[o] <<= f
h
}
unless objs.delete_if {|b, f| f.size == 1}.empty?
dups = objs.map {|b, f|
"#{b[/.*\./]}{#{f.collect {|n| n[/([^.]+)\z/]}.join(',')}}"
}
abort "source files duplication - #{dups.join(", ")}"
end
else
$objs.collect! {|o| File.basename(o, ".*") << ext} unless $OBJEXT == "o"
srcs = $srcs || $objs.collect {|o| o.chomp(ext) << ".c"}
end
$srcs = srcs
hdrs = Dir[File.join(srcdir, "*.{#{HDR_EXT.join(%q{,})}}")]
target = nil if $objs.empty?
if target and EXPORT_PREFIX
if File.exist?(File.join(srcdir, target + '.def'))
deffile = "$(srcdir)/$(TARGET).def"
unless EXPORT_PREFIX.empty?
makedef = %{$(RUBY) -pe "$$_.sub!(/^(?=\\w)/,'#{EXPORT_PREFIX}') unless 1../^EXPORTS$/i" #{deffile}}
end
else
makedef = %{(echo EXPORTS && echo $(TARGET_ENTRY))}
end
if makedef
$cleanfiles << '$(DEFFILE)'
origdef = deffile
deffile = "$(TARGET)-$(arch).def"
end
end
origdef ||= ''
if $extout and $INSTALLFILES
$cleanfiles.concat($INSTALLFILES.collect {|files, dir|File.join(dir, files.delete_prefix('./'))})
$distcleandirs.concat($INSTALLFILES.collect {|files, dir| dir})
end
if $extmk and $static
$defs << "-DRUBY_EXPORT=1"
end
if $extmk and not $extconf_h
create_header
end
libpath = libpathflag(libpath)
dllib = target ? "$(TARGET).#{CONFIG['DLEXT']}" : ""
staticlib = target ? "$(TARGET).#$LIBEXT" : ""
conf = configuration(srcprefix)
conf << "\
libpath = #{($DEFLIBPATH|$LIBPATH).join(" ")}
LIBPATH = #{libpath}
DEFFILE = #{deffile}
CLEANFILES = #{$cleanfiles.join(' ')}
DISTCLEANFILES = #{$distcleanfiles.join(' ')}
DISTCLEANDIRS = #{$distcleandirs.join(' ')}
extout = #{$extout && $extout.quote}
extout_prefix = #{$extout_prefix}
target_prefix = #{target_prefix}
LOCAL_LIBS = #{$LOCAL_LIBS}
LIBS = #{$LIBRUBYARG} #{$libs} #{$LIBS}
ORIG_SRCS = #{orig_srcs.collect(&File.method(:basename)).join(' ')}
SRCS = $(ORIG_SRCS) #{(srcs - orig_srcs).collect(&File.method(:basename)).join(' ')}
OBJS = #{$objs.join(" ")}
HDRS = #{hdrs.map{|h| '$(srcdir)/' + File.basename(h)}.join(' ')}
LOCAL_HDRS = #{$headers.join(' ')}
TARGET = #{target}
TARGET_NAME = #{target && target[/\A\w+/]}
TARGET_ENTRY = #{EXPORT_PREFIX || ''}Init_$(TARGET_NAME)
DLLIB = #{dllib}
EXTSTATIC = #{$static || ""}
STATIC_LIB = #{staticlib unless $static.nil?}
#{!$extout && defined?($installed_list) ? "INSTALLED_LIST = #{$installed_list}\n" : ""}
TIMESTAMP_DIR = #{$extout && $extmk ? '$(extout)/.timestamp' : '.'}
" #"
# TODO: fixme
install_dirs.each {|d| conf << ("%-14s= %s\n" % d) if /^[[:upper:]]/ =~ d[0]}
sodir = $extout ? '$(TARGET_SO_DIR)' : '$(RUBYARCHDIR)'
n = '$(TARGET_SO_DIR)$(TARGET)'
cleanobjs = ["$(OBJS)"]
if $extmk
%w[bc i s].each {|ex| cleanobjs << "$(OBJS:.#{$OBJEXT}=.#{ex})"}
end
if target
config_string('cleanobjs') {|t| cleanobjs << t.gsub(/\$\*/, "$(TARGET)#{deffile ? '-$(arch)': ''}")}
end
conf << "\
TARGET_SO_DIR =#{$extout ? " $(RUBYARCHDIR)/" : ''}
TARGET_SO = $(TARGET_SO_DIR)$(DLLIB)
CLEANLIBS = #{'$(TARGET_SO) ' if target}#{config_string('cleanlibs') {|t| t.gsub(/\$\*/) {n}}}
CLEANOBJS = #{cleanobjs.join(' ')} *.bak
TARGET_SO_DIR_TIMESTAMP = #{timestamp_file(sodir, target_prefix)}
" #"
conf = yield(conf) if block_given?
mfile = File.open("Makefile", "wb")
mfile.puts(conf)
mfile.print "
all: #{$extout ? "install" : target ? "$(DLLIB)" : "Makefile"}
static: #{$extmk && !$static ? "all" : "$(STATIC_LIB)#{$extout ? " install-rb" : ""}"}
.PHONY: all install static install-so install-rb
.PHONY: clean clean-so clean-static clean-rb
" #"
mfile.print CLEANINGS
fsep = config_string('BUILD_FILE_SEPARATOR') {|s| s unless s == "/"}
if fsep
sep = ":/=#{fsep}"
fseprepl = proc {|s|
s = s.gsub("/", fsep)
s = s.gsub(/(\$\(\w+)(\))/) {$1+sep+$2}
s.gsub(/(\$\{\w+)(\})/) {$1+sep+$2}
}
rsep = ":#{fsep}=/"
else
fseprepl = proc {|s| s}
sep = ""
rsep = ""
end
dirs = []
mfile.print "install: install-so install-rb\n\n"
dir = sodir.dup
mfile.print("install-so: ")
if target
f = "$(DLLIB)"
dest = "$(TARGET_SO)"
stamp = '$(TARGET_SO_DIR_TIMESTAMP)'
if $extout
mfile.puts dest
mfile.print "clean-so::\n"
mfile.print "\t-$(Q)$(RM) #{fseprepl[dest]} #{fseprepl[stamp]}\n"
mfile.print "\t-$(Q)$(RMDIRS) #{fseprepl[dir]}#{$ignore_error}\n"
else
mfile.print "#{f} #{stamp}\n"
mfile.print "\t$(INSTALL_PROG) #{fseprepl[f]} #{dir}\n"
if defined?($installed_list)
mfile.print "\t@echo #{dir}/#{File.basename(f)}>>$(INSTALLED_LIST)\n"
end
end
mfile.print "clean-static::\n"
mfile.print "\t-$(Q)$(RM) $(STATIC_LIB)\n"
else
mfile.puts "Makefile"
end
mfile.print("install-rb: pre-install-rb do-install-rb install-rb-default\n")
mfile.print("install-rb-default: pre-install-rb-default do-install-rb-default\n")
mfile.print("pre-install-rb: Makefile\n")
mfile.print("pre-install-rb-default: Makefile\n")
mfile.print("do-install-rb:\n")
mfile.print("do-install-rb-default:\n")
for sfx, i in [["-default", [["lib/**/*.rb", "$(RUBYLIBDIR)", "lib"]]], ["", $INSTALLFILES]]
files = install_files(mfile, i, nil, srcprefix) or next
for dir, *files in files
unless dirs.include?(dir)
dirs << dir
mfile.print "pre-install-rb#{sfx}: #{timestamp_file(dir, target_prefix)}\n"
end
for f in files
dest = "#{dir}/#{File.basename(f)}"
mfile.print("do-install-rb#{sfx}: #{dest}\n")
mfile.print("#{dest}: #{f} #{timestamp_file(dir, target_prefix)}\n")
mfile.print("\t$(Q) $(#{$extout ? 'COPY' : 'INSTALL_DATA'}) #{f} $(@D)\n")
if defined?($installed_list) and !$extout
mfile.print("\t@echo #{dest}>>$(INSTALLED_LIST)\n")
end
if $extout
mfile.print("clean-rb#{sfx}::\n")
mfile.print("\t-$(Q)$(RM) #{fseprepl[dest]}\n")
end
end
end
mfile.print "pre-install-rb#{sfx}:\n"
if files.empty?
mfile.print("\t@$(NULLCMD)\n")
else
q = "$(MAKE) -q do-install-rb#{sfx}"
if $nmake
mfile.print "!if \"$(Q)\" == \"@\"\n\t@#{q} || \\\n!endif\n\t"
else
mfile.print "\t$(Q1:0=@#{q} || )"
end
mfile.print "$(ECHO1:0=echo) installing#{sfx.sub(/^-/, " ")} #{target} libraries\n"
end
if $extout
dirs.uniq!
unless dirs.empty?
mfile.print("clean-rb#{sfx}::\n")
for dir in dirs.sort_by {|d| -d.count('/')}
stamp = timestamp_file(dir, target_prefix)
mfile.print("\t-$(Q)$(RM) #{fseprepl[stamp]}\n")
mfile.print("\t-$(Q)$(RMDIRS) #{fseprepl[dir]}#{$ignore_error}\n")
end
end
end
end
if target and !dirs.include?(sodir)
mfile.print "$(TARGET_SO_DIR_TIMESTAMP):\n\t$(Q) $(MAKEDIRS) $(@D) #{sodir}\n\t$(Q) $(TOUCH) $@\n"
end
dirs.each do |d|
t = timestamp_file(d, target_prefix)
mfile.print "#{t}:\n\t$(Q) $(MAKEDIRS) $(@D) #{d}\n\t$(Q) $(TOUCH) $@\n"
end
mfile.print <<-SITEINSTALL
site-install: site-install-so site-install-rb
site-install-so: install-so
site-install-rb: install-rb
SITEINSTALL
return unless target
mfile.print ".SUFFIXES: .#{(SRC_EXT + [$OBJEXT, $ASMEXT]).compact.join(' .')}\n"
mfile.print "\n"
compile_command = "\n\t$(ECHO) compiling $(<#{rsep})\n\t$(Q) %s\n\n"
command = compile_command % COMPILE_CXX
asm_command = compile_command.sub(/compiling/, 'translating') % ASSEMBLE_CXX
CXX_EXT.each do |e|
each_compile_rules do |rule|
mfile.printf(rule, e, $OBJEXT)
mfile.print(command)
mfile.printf(rule, e, $ASMEXT)
mfile.print(asm_command)
end
end
command = compile_command % COMPILE_C
asm_command = compile_command.sub(/compiling/, 'translating') % ASSEMBLE_C
C_EXT.each do |e|
each_compile_rules do |rule|
mfile.printf(rule, e, $OBJEXT)
mfile.print(command)
mfile.printf(rule, e, $ASMEXT)
mfile.print(asm_command)
end
end
mfile.print "$(TARGET_SO): "
mfile.print "$(DEFFILE) " if makedef
mfile.print "$(OBJS) Makefile"
mfile.print " $(TARGET_SO_DIR_TIMESTAMP)" if $extout
mfile.print "\n"
mfile.print "\t$(ECHO) linking shared-object #{target_prefix.sub(/\A\/(.*)/, '\1/')}$(DLLIB)\n"
mfile.print "\t-$(Q)$(RM) $(@#{sep})\n"
link_so = LINK_SO.gsub(/^/, "\t$(Q) ")
if srcs.any?(&%r"\.(?:#{CXX_EXT.join('|')})\z".method(:===))
link_so = link_so.sub(/\bLDSHARED\b/, '\&XX')
end
mfile.print link_so, "\n\n"
unless $static.nil?
mfile.print "$(STATIC_LIB): $(OBJS)\n\t-$(Q)$(RM) $(@#{sep})\n\t"
mfile.print "$(ECHO) linking static-library $(@#{rsep})\n\t$(Q) "
mfile.print "$(AR) #{config_string('ARFLAGS') || 'cru '}$@ $(OBJS)"
config_string('RANLIB') do |ranlib|
mfile.print "\n\t-$(Q)#{ranlib} $(@)#{$ignore_error}"
end
end
mfile.print "\n\n"
if makedef
mfile.print "$(DEFFILE): #{origdef}\n"
mfile.print "\t$(ECHO) generating $(@#{rsep})\n"
mfile.print "\t$(Q) #{makedef} > $@\n\n"
end
depend = File.join(srcdir, "depend")
if File.exist?(depend)
mfile.print("###\n", *depend_rules(File.read(depend)))
else
mfile.print "$(OBJS): $(HDRS) $(ruby_headers)\n"
end
$makefile_created = true
ensure
mfile.close if mfile
end
# :stopdoc:
def init_mkmf(config = CONFIG, rbconfig = RbConfig::CONFIG)
$makefile_created = false
$arg_config = []
$enable_shared = config['ENABLE_SHARED'] == 'yes'
$defs = []
$extconf_h = nil
$config_dirs = {}
if $warnflags = CONFIG['warnflags'] and CONFIG['GCC'] == 'yes'
# turn warnings into errors only for bundled extensions.
config['warnflags'] = $warnflags.gsub(/(?:\A|\s)-W\Kerror[-=](?!implicit-function-declaration)/, '')
if /icc\z/ =~ config['CC']
config['warnflags'].gsub!(/(\A|\s)-W(?:division-by-zero|deprecated-declarations)/, '\1')
end
RbConfig.expand(rbconfig['warnflags'] = config['warnflags'].dup)
config.each do |key, val|
RbConfig.expand(rbconfig[key] = val.dup) if /warnflags/ =~ val
end
$warnflags = config['warnflags'] unless $extmk
end
if (w = rbconfig['CC_WRAPPER']) and !w.empty? and !File.executable?(w)
rbconfig['CC_WRAPPER'] = config['CC_WRAPPER'] = ''
end
$CFLAGS = with_config("cflags", arg_config("CFLAGS", config["CFLAGS"])).dup
$CXXFLAGS = (with_config("cxxflags", arg_config("CXXFLAGS", config["CXXFLAGS"]))||'').dup
$ARCH_FLAG = with_config("arch_flag", arg_config("ARCH_FLAG", config["ARCH_FLAG"])).dup
$CPPFLAGS = with_config("cppflags", arg_config("CPPFLAGS", config["CPPFLAGS"])).dup
$LDFLAGS = with_config("ldflags", arg_config("LDFLAGS", config["LDFLAGS"])).dup
$INCFLAGS = "-I$(arch_hdrdir)"
$INCFLAGS << " -I$(hdrdir)/ruby/backward" unless $extmk
$INCFLAGS << " -I$(hdrdir) -I$(srcdir)"
$DLDFLAGS = with_config("dldflags", arg_config("DLDFLAGS", config["DLDFLAGS"])).dup
config_string("ADDITIONAL_DLDFLAGS") {|flags| $DLDFLAGS << " " << flags} unless $extmk
$LIBEXT = config['LIBEXT'].dup
$OBJEXT = config["OBJEXT"].dup
$EXEEXT = config["EXEEXT"].dup
$ASMEXT = config_string('ASMEXT', &:dup) || 'S'
$LIBS = "#{config['LIBS']} #{config['DLDLIBS']}"
$LIBRUBYARG = ""
$LIBRUBYARG_STATIC = config['LIBRUBYARG_STATIC']
$LIBRUBYARG_SHARED = config['LIBRUBYARG_SHARED']
$DEFLIBPATH = [$extmk ? "$(topdir)" : "$(#{config["libdirname"] || "libdir"})"]
$DEFLIBPATH.unshift(".")
$LIBPATH = []
$INSTALLFILES = []
$NONINSTALLFILES = [/~\z/, /\A#.*#\z/, /\A\.#/, /\.bak\z/i, /\.orig\z/, /\.rej\z/, /\.l[ao]\z/, /\.o\z/]
$VPATH = %w[$(srcdir) $(arch_hdrdir)/ruby $(hdrdir)/ruby]
$objs = nil
$srcs = nil
$headers = []
$libs = ""
if $enable_shared or RbConfig.expand(config["LIBRUBY"].dup) != RbConfig.expand(config["LIBRUBY_A"].dup)
$LIBRUBYARG = config['LIBRUBYARG']
end
$LOCAL_LIBS = ""
$cleanfiles = config_string('CLEANFILES') {|s| Shellwords.shellwords(s)} || []
$cleanfiles << "mkmf.log"
$distcleanfiles = config_string('DISTCLEANFILES') {|s| Shellwords.shellwords(s)} || []
$distcleandirs = config_string('DISTCLEANDIRS') {|s| Shellwords.shellwords(s)} || []
$extout ||= nil
$extout_prefix ||= nil
$arg_config.clear
$config_dirs.clear
dir_config("opt")
end
FailedMessage = <<MESSAGE
Could not create Makefile due to some reason, probably lack of necessary
libraries and/or headers. Check the mkmf.log file for more details. You may
need configuration options.
Provided configuration options:
MESSAGE
# Returns whether or not the Makefile was successfully generated. If not,
# the script will abort with an error message.
#
# Internal use only.
#
def mkmf_failed(path)
unless $makefile_created or File.exist?("Makefile")
opts = $arg_config.collect {|t, n| "\t#{t}#{n ? "=#{n}" : ""}\n"}
abort "*** #{path} failed ***\n" + FailedMessage + opts.join
end
end
private
def _libdir_basename
@libdir_basename ||= config_string("libdir") {|name| name[/\A\$\(exec_prefix\)\/(.*)/, 1]} || "lib"
end
def MAIN_DOES_NOTHING(*refs)
src = MAIN_DOES_NOTHING
unless refs.empty?
src = src.sub(/\{/) do
$& +
"\n if (argc > 1000000) {\n" +
refs.map {|n|" int (* volatile #{n}p)(void)=(int (*)(void))&#{n};\n"}.join("") +
refs.map {|n|" printf(\"%d\", (*#{n}p)());\n"}.join("") +
" }\n"
end
end
src
end
extend self
init_mkmf
$make = with_config("make-prog", ENV["MAKE"] || "make")
make, = Shellwords.shellwords($make)
$nmake = nil
case
when $mswin
$nmake = ?m if /nmake/i =~ make
end
$ignore_error = $nmake ? '' : ' 2> /dev/null || true'
RbConfig::CONFIG["srcdir"] = CONFIG["srcdir"] =
$srcdir = arg_config("--srcdir", File.dirname($0))
$configure_args["--topsrcdir"] ||= $srcdir
if $curdir = arg_config("--curdir")
RbConfig.expand(curdir = $curdir.dup)
else
curdir = $curdir = "."
end
unless File.expand_path(RbConfig::CONFIG["topdir"]) == File.expand_path(curdir)
CONFIG["topdir"] = $curdir
RbConfig::CONFIG["topdir"] = curdir
end
$configure_args["--topdir"] ||= $curdir
$ruby = arg_config("--ruby", File.join(RbConfig::CONFIG["bindir"], CONFIG["ruby_install_name"]))
RbConfig.expand(CONFIG["RUBY_SO_NAME"])
# :startdoc:
split = Shellwords.method(:shellwords).to_proc
EXPORT_PREFIX = config_string('EXPORT_PREFIX') {|s| s.strip}
hdr = ['#include "ruby.h"' "\n"]
config_string('COMMON_MACROS') do |s|
Shellwords.shellwords(s).each do |w|
w, v = w.split(/=/, 2)
hdr << "#ifndef #{w}"
hdr << "#define #{[w, v].compact.join(" ")}"
hdr << "#endif /* #{w} */"
end
end
config_string('COMMON_HEADERS') do |s|
Shellwords.shellwords(s).each {|w| hdr << "#include <#{w}>"}
end
##
# Common headers for Ruby C extensions
COMMON_HEADERS = hdr.join("\n")
##
# Common libraries for Ruby C extensions
COMMON_LIBS = config_string('COMMON_LIBS', &split) || []
##
# make compile rules
COMPILE_RULES = config_string('COMPILE_RULES', &split) || %w[.%s.%s:]
RULE_SUBST = config_string('RULE_SUBST')
##
# Command which will compile C files in the generated Makefile
COMPILE_C = config_string('COMPILE_C') || '$(CC) $(INCFLAGS) $(CPPFLAGS) $(CFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<'
##
# Command which will compile C++ files in the generated Makefile
COMPILE_CXX = config_string('COMPILE_CXX') || '$(CXX) $(INCFLAGS) $(CPPFLAGS) $(CXXFLAGS) $(COUTFLAG)$@ -c $(CSRCFLAG)$<'
##
# Command which will translate C files to assembler sources in the generated Makefile
ASSEMBLE_C = config_string('ASSEMBLE_C') || COMPILE_C.sub(/(?<=\s)-c(?=\s)/, '-S')
##
# Command which will translate C++ files to assembler sources in the generated Makefile
ASSEMBLE_CXX = config_string('ASSEMBLE_CXX') || COMPILE_CXX.sub(/(?<=\s)-c(?=\s)/, '-S')
##
# Command which will compile a program in order to test linking a library
TRY_LINK = config_string('TRY_LINK') ||
"$(CC) #{OUTFLAG}#{CONFTEST}#{$EXEEXT} $(INCFLAGS) $(CPPFLAGS) " \
"$(CFLAGS) $(src) $(LIBPATH) $(LDFLAGS) $(ARCH_FLAG) $(LOCAL_LIBS) $(LIBS)"
##
# Command which will link a shared library
LINK_SO = (config_string('LINK_SO') || "").sub(/^$/) do
if CONFIG["DLEXT"] == $OBJEXT
"ld $(DLDFLAGS) -r -o $@ $(OBJS)\n"
else
"$(LDSHARED) #{OUTFLAG}$@ $(OBJS) " \
"$(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)"
end
end
##
# Argument which will add a library path to the linker
LIBPATHFLAG = config_string('LIBPATHFLAG') || ' -L%s'
RPATHFLAG = config_string('RPATHFLAG') || ''
##
# Argument which will add a library to the linker
LIBARG = config_string('LIBARG') || '-l%s'
##
# A C main function which does no work
MAIN_DOES_NOTHING = config_string('MAIN_DOES_NOTHING') || "int main(int argc, char **argv)\n{\n return !!argv[argc];\n}"
UNIVERSAL_INTS = config_string('UNIVERSAL_INTS') {|s| Shellwords.shellwords(s)} ||
%w[int short long long\ long]
sep = config_string('BUILD_FILE_SEPARATOR') {|s| ":/=#{s}" if s != "/"} || ""
##
# Makefile rules that will clean the extension build directory
CLEANINGS = "
clean-static::
clean-rb-default::
clean-rb::
clean-so::
clean: clean-so clean-static clean-rb-default clean-rb
\t\t-$(Q)$(RM_RF) $(CLEANLIBS#{sep}) $(CLEANOBJS#{sep}) $(CLEANFILES#{sep}) .*.time
distclean-rb-default::
distclean-rb::
distclean-so::
distclean-static::
distclean: clean distclean-so distclean-static distclean-rb-default distclean-rb
\t\t-$(Q)$(RM) Makefile $(RUBY_EXTCONF_H) #{CONFTEST}.* mkmf.log#{' exts.mk' if $extmk}
\t\t-$(Q)$(RM) core ruby$(EXEEXT) *~ $(DISTCLEANFILES#{sep})
\t\t-$(Q)$(RMDIRS) $(DISTCLEANDIRS#{sep})#{$ignore_error}
realclean: distclean
"
@lang = Hash.new(self)
def self.[](name)
@lang.fetch(name)
end
def self.[]=(name, mod)
@lang[name] = mod
end
self["C++"] = Module.new do
include MakeMakefile
extend self
CONFTEST_CXX = "#{CONFTEST}.#{config_string('CXX_EXT') || CXX_EXT[0]}"
TRY_LINK_CXX = config_string('TRY_LINK_CXX') ||
((cmd = TRY_LINK.gsub(/\$\(C(?:C|(FLAGS))\)/, '$(CXX\1)')) != TRY_LINK && cmd) ||
"$(CXX) #{OUTFLAG}#{CONFTEST}#{$EXEEXT} $(INCFLAGS) $(CPPFLAGS) " \
"$(CXXFLAGS) $(src) $(LIBPATH) $(LDFLAGS) $(ARCH_FLAG) $(LOCAL_LIBS) $(LIBS)"
def have_devel?
unless defined? @have_devel
@have_devel = true
@have_devel = try_link(MAIN_DOES_NOTHING)
end
@have_devel
end
def conftest_source
CONFTEST_CXX
end
def cc_command(opt="")
conf = cc_config(opt)
RbConfig::expand("$(CXX) #$INCFLAGS #$CPPFLAGS #$CXXFLAGS #$ARCH_FLAG #{opt} -c #{CONFTEST_CXX}",
conf)
end
def link_command(ldflags, *opts)
conf = link_config(ldflags, *opts)
RbConfig::expand(TRY_LINK_CXX.dup, conf)
end
end
end
# MakeMakefile::Global = #
m = Module.new {
include(MakeMakefile)
private(*MakeMakefile.public_instance_methods(false))
}
include m
if not $extmk and /\A(extconf|makefile).rb\z/ =~ File.basename($0)
END {mkmf_failed($0)}
end
share/ruby/yaml/dbm.rb 0000644 00000015451 15173547337 0010705 0 ustar 00 # frozen_string_literal: false
require 'yaml'
require 'dbm'
module YAML
# YAML + DBM = YDBM
#
# YAML::DBM provides the same interface as ::DBM.
#
# However, while DBM only allows strings for both keys and values,
# this library allows one to use most Ruby objects for values
# by first converting them to YAML. Keys must be strings.
#
# Conversion to and from YAML is performed automatically.
#
# See the documentation for ::DBM and ::YAML for more information.
class DBM < ::DBM
VERSION = "0.1" # :nodoc:
# :call-seq:
# ydbm[key] -> value
#
# Return value associated with +key+ from database.
#
# Returns +nil+ if there is no such +key+.
#
# See #fetch for more information.
def []( key )
fetch( key )
end
# :call-seq:
# ydbm[key] = value
#
# Set +key+ to +value+ in database.
#
# +value+ will be converted to YAML before storage.
#
# See #store for more information.
def []=( key, val )
store( key, val )
end
# :call-seq:
# ydbm.fetch( key, ifnone = nil )
# ydbm.fetch( key ) { |key| ... }
#
# Return value associated with +key+.
#
# If there is no value for +key+ and no block is given, returns +ifnone+.
#
# Otherwise, calls block passing in the given +key+.
#
# See ::DBM#fetch for more information.
def fetch( keystr, ifnone = nil )
begin
val = super( keystr )
return YAML.load( val ) if String === val
rescue IndexError
end
if block_given?
yield keystr
else
ifnone
end
end
# Deprecated, used YAML::DBM#key instead.
# ----
# Note:
# YAML::DBM#index makes warning from internal of ::DBM#index.
# It says 'DBM#index is deprecated; use DBM#key', but DBM#key
# behaves not same as DBM#index.
#
def index( keystr )
super( keystr.to_yaml )
end
# :call-seq:
# ydbm.key(value) -> string
#
# Returns the key for the specified value.
def key( keystr )
invert[keystr]
end
# :call-seq:
# ydbm.values_at(*keys)
#
# Returns an array containing the values associated with the given keys.
def values_at( *keys )
keys.collect { |k| fetch( k ) }
end
# :call-seq:
# ydbm.delete(key)
#
# Deletes value from database associated with +key+.
#
# Returns value or +nil+.
def delete( key )
v = super( key )
if String === v
v = YAML.load( v )
end
v
end
# :call-seq:
# ydbm.delete_if { |key, value| ... }
#
# Calls the given block once for each +key+, +value+ pair in the database.
# Deletes all entries for which the block returns true.
#
# Returns +self+.
def delete_if # :yields: [key, value]
del_keys = keys.dup
del_keys.delete_if { |k| yield( k, fetch( k ) ) == false }
del_keys.each { |k| delete( k ) }
self
end
# :call-seq:
# ydbm.reject { |key, value| ... }
#
# Converts the contents of the database to an in-memory Hash, then calls
# Hash#reject with the specified code block, returning a new Hash.
def reject
hsh = self.to_hash
hsh.reject { |k,v| yield k, v }
end
# :call-seq:
# ydbm.each_pair { |key, value| ... }
#
# Calls the given block once for each +key+, +value+ pair in the database.
#
# Returns +self+.
def each_pair # :yields: [key, value]
keys.each { |k| yield k, fetch( k ) }
self
end
# :call-seq:
# ydbm.each_value { |value| ... }
#
# Calls the given block for each value in database.
#
# Returns +self+.
def each_value # :yields: value
super { |v| yield YAML.load( v ) }
self
end
# :call-seq:
# ydbm.values
#
# Returns an array of values from the database.
def values
super.collect { |v| YAML.load( v ) }
end
# :call-seq:
# ydbm.has_value?(value)
#
# Returns true if specified +value+ is found in the database.
def has_value?( val )
each_value { |v| return true if v == val }
return false
end
# :call-seq:
# ydbm.invert -> hash
#
# Returns a Hash (not a DBM database) created by using each value in the
# database as a key, with the corresponding key as its value.
#
# Note that all values in the hash will be Strings, but the keys will be
# actual objects.
def invert
h = {}
keys.each { |k| h[ self.fetch( k ) ] = k }
h
end
# :call-seq:
# ydbm.replace(hash) -> ydbm
#
# Replaces the contents of the database with the contents of the specified
# object. Takes any object which implements the each_pair method, including
# Hash and DBM objects.
def replace( hsh )
clear
update( hsh )
end
# :call-seq:
# ydbm.shift -> [key, value]
#
# Removes a [key, value] pair from the database, and returns it.
# If the database is empty, returns +nil+.
#
# The order in which values are removed/returned is not guaranteed.
def shift
a = super
a[1] = YAML.load( a[1] ) if a
a
end
# :call-seq:
# ydbm.select { |key, value| ... }
# ydbm.select(*keys)
#
# If a block is provided, returns a new array containing [key, value] pairs
# for which the block returns true.
#
# Otherwise, same as #values_at
def select( *keys )
if block_given?
self.keys.collect { |k| v = self[k]; [k, v] if yield k, v }.compact
else
values_at( *keys )
end
end
# :call-seq:
# ydbm.store(key, value) -> value
#
# Stores +value+ in database with +key+ as the index. +value+ is converted
# to YAML before being stored.
#
# Returns +value+
def store( key, val )
super( key, val.to_yaml )
val
end
# :call-seq:
# ydbm.update(hash) -> ydbm
#
# Updates the database with multiple values from the specified object.
# Takes any object which implements the each_pair method, including
# Hash and DBM objects.
#
# Returns +self+.
def update( hsh )
hsh.each_pair do |k,v|
self.store( k, v )
end
self
end
# :call-seq:
# ydbm.to_a -> array
#
# Converts the contents of the database to an array of [key, value] arrays,
# and returns it.
def to_a
a = []
keys.each { |k| a.push [ k, self.fetch( k ) ] }
a
end
# :call-seq:
# ydbm.to_hash -> hash
#
# Converts the contents of the database to an in-memory Hash object, and
# returns it.
def to_hash
h = {}
keys.each { |k| h[ k ] = self.fetch( k ) }
h
end
alias :each :each_pair
end
end
share/ruby/yaml/store.rb 0000644 00000003523 15173547337 0011274 0 ustar 00 # frozen_string_literal: false
#
# YAML::Store
#
require 'yaml'
require 'pstore'
# YAML::Store provides the same functionality as PStore, except it uses YAML
# to dump objects instead of Marshal.
#
# == Example
#
# require 'yaml/store'
#
# Person = Struct.new :first_name, :last_name
#
# people = [Person.new("Bob", "Smith"), Person.new("Mary", "Johnson")]
#
# store = YAML::Store.new "test.store"
#
# store.transaction do
# store["people"] = people
# store["greeting"] = { "hello" => "world" }
# end
#
# After running the above code, the contents of "test.store" will be:
#
# ---
# people:
# - !ruby/struct:Person
# first_name: Bob
# last_name: Smith
# - !ruby/struct:Person
# first_name: Mary
# last_name: Johnson
# greeting:
# hello: world
class YAML::Store < PStore
# :call-seq:
# initialize( file_name, yaml_opts = {} )
# initialize( file_name, thread_safe = false, yaml_opts = {} )
#
# Creates a new YAML::Store object, which will store data in +file_name+.
# If the file does not already exist, it will be created.
#
# YAML::Store objects are always reentrant. But if _thread_safe_ is set to true,
# then it will become thread-safe at the cost of a minor performance hit.
#
# Options passed in through +yaml_opts+ will be used when converting the
# store to YAML via Hash#to_yaml().
def initialize( *o )
@opt = {}
if o.last.is_a? Hash
@opt.update(o.pop)
end
super(*o)
end
# :stopdoc:
def dump(table)
table.to_yaml(@opt)
end
def load(content)
table = YAML.unsafe_load(content)
if table == false
{}
else
table
end
end
def marshal_dump_supports_canonical_option?
false
end
def empty_marshal_data
{}.to_yaml(@opt)
end
def empty_marshal_checksum
CHECKSUM_ALGO.digest(empty_marshal_data)
end
end
share/ruby/fiddle/struct.rb 0000644 00000034320 15173547337 0011750 0 ustar 00 # frozen_string_literal: true
require 'fiddle'
require 'fiddle/value'
require 'fiddle/pack'
module Fiddle
# A base class for objects representing a C structure
class CStruct
include Enumerable
# accessor to Fiddle::CStructEntity
def CStruct.entity_class
CStructEntity
end
def self.offsetof(name, members, types) # :nodoc:
offset = 0
worklist = name.split('.')
this_type = self
while search_name = worklist.shift
index = 0
member_index = members.index(search_name)
unless member_index
# Possibly a sub-structure
member_index = members.index { |member_name, _|
member_name == search_name
}
return unless member_index
end
types.each { |type, count = 1|
orig_offset = offset
if type.respond_to?(:entity_class)
align = type.alignment
type_size = type.size
else
align = PackInfo::ALIGN_MAP[type]
type_size = PackInfo::SIZE_MAP[type]
end
# Unions shouldn't advance the offset
if this_type.entity_class == CUnionEntity
type_size = 0
end
offset = PackInfo.align(orig_offset, align)
if worklist.empty?
return offset if index == member_index
else
if index == member_index
subtype = types[member_index]
members = subtype.members
types = subtype.types
this_type = subtype
break
end
end
offset += (type_size * count)
index += 1
}
end
nil
end
def each
return enum_for(__function__) unless block_given?
self.class.members.each do |name,|
yield(self[name])
end
end
def each_pair
return enum_for(__function__) unless block_given?
self.class.members.each do |name,|
yield(name, self[name])
end
end
def to_h
hash = {}
each_pair do |name, value|
hash[name] = unstruct(value)
end
hash
end
def replace(another)
if another.nil?
self.class.members.each do |name,|
self[name] = nil
end
elsif another.respond_to?(:each_pair)
another.each_pair do |name, value|
self[name] = value
end
else
another.each do |name, value|
self[name] = value
end
end
self
end
private
def unstruct(value)
case value
when CStruct
value.to_h
when Array
value.collect do |v|
unstruct(v)
end
else
value
end
end
end
# A base class for objects representing a C union
class CUnion
# accessor to Fiddle::CUnionEntity
def CUnion.entity_class
CUnionEntity
end
def self.offsetof(name, members, types) # :nodoc:
0
end
end
# Wrapper for arrays within a struct
class StructArray < Array
include ValueUtil
def initialize(ptr, type, initial_values)
@ptr = ptr
@type = type
@is_struct = @type.respond_to?(:entity_class)
if @is_struct
super(initial_values)
else
@size = Fiddle::PackInfo::SIZE_MAP[type]
@pack_format = Fiddle::PackInfo::PACK_MAP[type]
super(initial_values.collect { |v| unsigned_value(v, type) })
end
end
def to_ptr
@ptr
end
def []=(index, value)
if index < 0 || index >= size
raise IndexError, 'index %d outside of array bounds 0...%d' % [index, size]
end
if @is_struct
self[index].replace(value)
else
to_ptr[index * @size, @size] = [value].pack(@pack_format)
super(index, value)
end
end
end
# Used to construct C classes (CUnion, CStruct, etc)
#
# Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
# easy-to-use manner.
module CStructBuilder
# Construct a new class given a C:
# * class +klass+ (CUnion, CStruct, or other that provide an
# #entity_class)
# * +types+ (Fiddle::TYPE_INT, Fiddle::TYPE_SIZE_T, etc., see the C types
# constants)
# * corresponding +members+
#
# Fiddle::Importer#struct and Fiddle::Importer#union wrap this functionality in an
# easy-to-use manner.
#
# Examples:
#
# require 'fiddle/struct'
# require 'fiddle/cparser'
#
# include Fiddle::CParser
#
# types, members = parse_struct_signature(['int i','char c'])
#
# MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
#
# MyStruct.malloc(Fiddle::RUBY_FREE) do |obj|
# ...
# end
#
# obj = MyStruct.malloc(Fiddle::RUBY_FREE)
# begin
# ...
# ensure
# obj.call_free
# end
#
# obj = MyStruct.malloc
# begin
# ...
# ensure
# Fiddle.free obj.to_ptr
# end
#
def create(klass, types, members)
new_class = Class.new(klass){
define_method(:initialize){|addr, func = nil|
if addr.is_a?(self.class.entity_class)
@entity = addr
else
@entity = self.class.entity_class.new(addr, types, func)
end
@entity.assign_names(members)
}
define_method(:[]) { |*args| @entity.send(:[], *args) }
define_method(:[]=) { |*args| @entity.send(:[]=, *args) }
define_method(:to_ptr){ @entity }
define_method(:to_i){ @entity.to_i }
define_singleton_method(:types) { types }
define_singleton_method(:members) { members }
# Return the offset of a struct member given its name.
# For example:
#
# MyStruct = struct [
# "int64_t i",
# "char c",
# ]
#
# MyStruct.offsetof("i") # => 0
# MyStruct.offsetof("c") # => 8
#
define_singleton_method(:offsetof) { |name|
klass.offsetof(name, members, types)
}
members.each{|name|
name = name[0] if name.is_a?(Array) # name is a nested struct
next if method_defined?(name)
define_method(name){ @entity[name] }
define_method(name + "="){|val| @entity[name] = val }
}
entity_class = klass.entity_class
alignment = entity_class.alignment(types)
size = entity_class.size(types)
define_singleton_method(:alignment) { alignment }
define_singleton_method(:size) { size }
define_singleton_method(:malloc) do |func=nil, &block|
if block
entity_class.malloc(types, func, size) do |entity|
block.call(new(entity))
end
else
new(entity_class.malloc(types, func, size))
end
end
}
return new_class
end
module_function :create
end
# A pointer to a C structure
class CStructEntity < Fiddle::Pointer
include PackInfo
include ValueUtil
def CStructEntity.alignment(types)
max = 1
types.each do |type, count = 1|
if type.respond_to?(:entity_class)
n = type.alignment
else
n = ALIGN_MAP[type]
end
max = n if n > max
end
max
end
# Allocates a C struct with the +types+ provided.
#
# See Fiddle::Pointer.malloc for memory management issues.
def CStructEntity.malloc(types, func = nil, size = size(types), &block)
if block_given?
super(size, func) do |struct|
struct.set_ctypes types
yield struct
end
else
struct = super(size, func)
struct.set_ctypes types
struct
end
end
# Returns the offset for the packed sizes for the given +types+.
#
# Fiddle::CStructEntity.size(
# [ Fiddle::TYPE_DOUBLE,
# Fiddle::TYPE_INT,
# Fiddle::TYPE_CHAR,
# Fiddle::TYPE_VOIDP ]) #=> 24
def CStructEntity.size(types)
offset = 0
max_align = types.map { |type, count = 1|
last_offset = offset
if type.respond_to?(:entity_class)
align = type.alignment
type_size = type.size
else
align = PackInfo::ALIGN_MAP[type]
type_size = PackInfo::SIZE_MAP[type]
end
offset = PackInfo.align(last_offset, align) +
(type_size * count)
align
}.max
PackInfo.align(offset, max_align)
end
# Wraps the C pointer +addr+ as a C struct with the given +types+.
#
# When the instance is garbage collected, the C function +func+ is called.
#
# See also Fiddle::Pointer.new
def initialize(addr, types, func = nil)
if func && addr.is_a?(Pointer) && addr.free
raise ArgumentError, 'free function specified on both underlying struct Pointer and when creating a CStructEntity - who do you want to free this?'
end
set_ctypes(types)
super(addr, @size, func)
end
# Set the names of the +members+ in this C struct
def assign_names(members)
@members = []
@nested_structs = {}
members.each_with_index do |member, index|
if member.is_a?(Array) # nested struct
member_name = member[0]
struct_type, struct_count = @ctypes[index]
if struct_count.nil?
struct = struct_type.new(to_i + @offset[index])
else
structs = struct_count.times.map do |i|
struct_type.new(to_i + @offset[index] + i * struct_type.size)
end
struct = StructArray.new(to_i + @offset[index],
struct_type,
structs)
end
@nested_structs[member_name] = struct
else
member_name = member
end
@members << member_name
end
end
# Calculates the offsets and sizes for the given +types+ in the struct.
def set_ctypes(types)
@ctypes = types
@offset = []
offset = 0
max_align = types.map { |type, count = 1|
orig_offset = offset
if type.respond_to?(:entity_class)
align = type.alignment
type_size = type.size
else
align = ALIGN_MAP[type]
type_size = SIZE_MAP[type]
end
offset = PackInfo.align(orig_offset, align)
@offset << offset
offset += (type_size * count)
align
}.max
@size = PackInfo.align(offset, max_align)
end
# Fetch struct member +name+ if only one argument is specified. If two
# arguments are specified, the first is an offset and the second is a
# length and this method returns the string of +length+ bytes beginning at
# +offset+.
#
# Examples:
#
# my_struct = struct(['int id']).malloc
# my_struct.id = 1
# my_struct['id'] # => 1
# my_struct[0, 4] # => "\x01\x00\x00\x00".b
#
def [](*args)
return super(*args) if args.size > 1
name = args[0]
idx = @members.index(name)
if( idx.nil? )
raise(ArgumentError, "no such member: #{name}")
end
ty = @ctypes[idx]
if( ty.is_a?(Array) )
if ty.first.respond_to?(:entity_class)
return @nested_structs[name]
else
r = super(@offset[idx], SIZE_MAP[ty[0]] * ty[1])
end
elsif ty.respond_to?(:entity_class)
return @nested_structs[name]
else
r = super(@offset[idx], SIZE_MAP[ty.abs])
end
packer = Packer.new([ty])
val = packer.unpack([r])
case ty
when Array
case ty[0]
when TYPE_VOIDP
val = val.collect{|v| Pointer.new(v)}
end
when TYPE_VOIDP
val = Pointer.new(val[0])
else
val = val[0]
end
if( ty.is_a?(Integer) && (ty < 0) )
return unsigned_value(val, ty)
elsif( ty.is_a?(Array) && (ty[0] < 0) )
return StructArray.new(self + @offset[idx], ty[0], val)
else
return val
end
end
# Set struct member +name+, to value +val+. If more arguments are
# specified, writes the string of bytes to the memory at the given
# +offset+ and +length+.
#
# Examples:
#
# my_struct = struct(['int id']).malloc
# my_struct['id'] = 1
# my_struct[0, 4] = "\x01\x00\x00\x00".b
# my_struct.id # => 1
#
def []=(*args)
return super(*args) if args.size > 2
name, val = *args
name = name.to_s if name.is_a?(Symbol)
nested_struct = @nested_structs[name]
if nested_struct
if nested_struct.is_a?(StructArray)
if val.nil?
nested_struct.each do |s|
s.replace(nil)
end
else
val.each_with_index do |v, i|
nested_struct[i] = v
end
end
else
nested_struct.replace(val)
end
return val
end
idx = @members.index(name)
if( idx.nil? )
raise(ArgumentError, "no such member: #{name}")
end
ty = @ctypes[idx]
packer = Packer.new([ty])
val = wrap_arg(val, ty, [])
buff = packer.pack([val].flatten())
super(@offset[idx], buff.size, buff)
if( ty.is_a?(Integer) && (ty < 0) )
return unsigned_value(val, ty)
elsif( ty.is_a?(Array) && (ty[0] < 0) )
return val.collect{|v| unsigned_value(v,ty[0])}
else
return val
end
end
undef_method :size=
def to_s() # :nodoc:
super(@size)
end
end
# A pointer to a C union
class CUnionEntity < CStructEntity
include PackInfo
# Returns the size needed for the union with the given +types+.
#
# Fiddle::CUnionEntity.size(
# [ Fiddle::TYPE_DOUBLE,
# Fiddle::TYPE_INT,
# Fiddle::TYPE_CHAR,
# Fiddle::TYPE_VOIDP ]) #=> 8
def CUnionEntity.size(types)
types.map { |type, count = 1|
if type.respond_to?(:entity_class)
type.size * count
else
PackInfo::SIZE_MAP[type] * count
end
}.max
end
# Calculate the necessary offset and for each union member with the given
# +types+
def set_ctypes(types)
@ctypes = types
@offset = Array.new(types.length, 0)
@size = self.class.size types
end
end
end
share/ruby/fiddle/closure.rb 0000644 00000003672 15173547337 0012106 0 ustar 00 # frozen_string_literal: true
module Fiddle
class Closure
class << self
# Create a new closure. If a block is given, the created closure
# is automatically freed after the given block is executed.
#
# The all given arguments are passed to Fiddle::Closure.new. So
# using this method without block equals to Fiddle::Closure.new.
#
# == Example
#
# Fiddle::Closure.create(TYPE_INT, [TYPE_INT]) do |closure|
# # closure is freed automatically when this block is finished.
# end
def create(*args)
if block_given?
closure = new(*args)
begin
yield(closure)
ensure
closure.free
end
else
new(*args)
end
end
end
# the C type of the return of the FFI closure
attr_reader :ctype
# arguments of the FFI closure
attr_reader :args
# Extends Fiddle::Closure to allow for building the closure in a block
class BlockCaller < Fiddle::Closure
# == Description
#
# Construct a new BlockCaller object.
#
# * +ctype+ is the C type to be returned
# * +args+ are passed the callback
# * +abi+ is the abi of the closure
#
# If there is an error in preparing the +ffi_cif+ or +ffi_prep_closure+,
# then a RuntimeError will be raised.
#
# == Example
#
# include Fiddle
#
# cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
# one
# end
#
# func = Function.new(cb, [TYPE_INT], TYPE_INT)
#
def initialize ctype, args, abi = Fiddle::Function::DEFAULT, &block
super(ctype, args, abi)
@block = block
end
# Calls the constructed BlockCaller, with +args+
#
# For an example see Fiddle::Closure::BlockCaller.new
#
def call *args
@block.call(*args)
end
end
end
end
share/ruby/fiddle/function.rb 0000644 00000001033 15173547337 0012244 0 ustar 00 # frozen_string_literal: true
module Fiddle
class Function
# The ABI of the Function.
attr_reader :abi
# The address of this function
attr_reader :ptr
# The name of this function
attr_reader :name
# Whether GVL is needed to call this function
def need_gvl?
@need_gvl
end
# The integer memory location of this function
def to_i
ptr.to_i
end
# Turn this function in to a proc
def to_proc
this = self
lambda { |*args| this.call(*args) }
end
end
end
share/ruby/fiddle/import.rb 0000644 00000021434 15173547337 0011740 0 ustar 00 # frozen_string_literal: true
require 'fiddle'
require 'fiddle/struct'
require 'fiddle/cparser'
module Fiddle
# Used internally by Fiddle::Importer
class CompositeHandler
# Create a new handler with the open +handlers+
#
# Used internally by Fiddle::Importer.dlload
def initialize(handlers)
@handlers = handlers
end
# Array of the currently loaded libraries.
def handlers()
@handlers
end
# Returns the address as an Integer from any handlers with the function
# named +symbol+.
#
# Raises a DLError if the handle is closed.
def sym(symbol)
@handlers.each{|handle|
if( handle )
begin
addr = handle.sym(symbol)
return addr
rescue DLError
end
end
}
return nil
end
# See Fiddle::CompositeHandler.sym
def [](symbol)
sym(symbol)
end
end
# A DSL that provides the means to dynamically load libraries and build
# modules around them including calling extern functions within the C
# library that has been loaded.
#
# == Example
#
# require 'fiddle'
# require 'fiddle/import'
#
# module LibSum
# extend Fiddle::Importer
# dlload './libsum.so'
# extern 'double sum(double*, int)'
# extern 'double split(double)'
# end
#
module Importer
include Fiddle
include CParser
extend Importer
attr_reader :type_alias
private :type_alias
# Creates an array of handlers for the given +libs+, can be an instance of
# Fiddle::Handle, Fiddle::Importer, or will create a new instance of
# Fiddle::Handle using Fiddle.dlopen
#
# Raises a DLError if the library cannot be loaded.
#
# See Fiddle.dlopen
def dlload(*libs)
handles = libs.collect{|lib|
case lib
when nil
nil
when Handle
lib
when Importer
lib.handlers
else
Fiddle.dlopen(lib)
end
}.flatten()
@handler = CompositeHandler.new(handles)
@func_map = {}
@type_alias = {}
end
# Sets the type alias for +alias_type+ as +orig_type+
def typealias(alias_type, orig_type)
@type_alias[alias_type] = orig_type
end
# Returns the sizeof +ty+, using Fiddle::Importer.parse_ctype to determine
# the C type and the appropriate Fiddle constant.
def sizeof(ty)
case ty
when String
ty = parse_ctype(ty, type_alias).abs()
case ty
when TYPE_CHAR
return SIZEOF_CHAR
when TYPE_SHORT
return SIZEOF_SHORT
when TYPE_INT
return SIZEOF_INT
when TYPE_LONG
return SIZEOF_LONG
when TYPE_FLOAT
return SIZEOF_FLOAT
when TYPE_DOUBLE
return SIZEOF_DOUBLE
when TYPE_VOIDP
return SIZEOF_VOIDP
when TYPE_CONST_STRING
return SIZEOF_CONST_STRING
else
if defined?(TYPE_LONG_LONG) and
ty == TYPE_LONG_LONG
return SIZEOF_LONG_LONG
else
raise(DLError, "unknown type: #{ty}")
end
end
when Class
if( ty.instance_methods().include?(:to_ptr) )
return ty.size()
end
end
return Pointer[ty].size()
end
def parse_bind_options(opts)
h = {}
while( opt = opts.shift() )
case opt
when :stdcall, :cdecl
h[:call_type] = opt
when :carried, :temp, :temporal, :bind
h[:callback_type] = opt
h[:carrier] = opts.shift()
else
h[opt] = true
end
end
h
end
private :parse_bind_options
# :stopdoc:
CALL_TYPE_TO_ABI = Hash.new { |h, k|
raise RuntimeError, "unsupported call type: #{k}"
}.merge({ :stdcall => Function.const_defined?(:STDCALL) ? Function::STDCALL :
Function::DEFAULT,
:cdecl => Function::DEFAULT,
nil => Function::DEFAULT
}).freeze
private_constant :CALL_TYPE_TO_ABI
# :startdoc:
# Creates a global method from the given C +signature+.
def extern(signature, *opts)
symname, ctype, argtype = parse_signature(signature, type_alias)
opt = parse_bind_options(opts)
f = import_function(symname, ctype, argtype, opt[:call_type])
name = symname.gsub(/@.+/,'')
@func_map[name] = f
# define_method(name){|*args,&block| f.call(*args,&block)}
begin
/^(.+?):(\d+)/ =~ caller.first
file, line = $1, $2.to_i
rescue
file, line = __FILE__, __LINE__+3
end
module_eval(<<-EOS, file, line)
def #{name}(*args, &block)
@func_map['#{name}'].call(*args,&block)
end
EOS
module_function(name)
f
end
# Creates a global method from the given C +signature+ using the given
# +opts+ as bind parameters with the given block.
def bind(signature, *opts, &blk)
name, ctype, argtype = parse_signature(signature, type_alias)
h = parse_bind_options(opts)
case h[:callback_type]
when :bind, nil
f = bind_function(name, ctype, argtype, h[:call_type], &blk)
else
raise(RuntimeError, "unknown callback type: #{h[:callback_type]}")
end
@func_map[name] = f
#define_method(name){|*args,&block| f.call(*args,&block)}
begin
/^(.+?):(\d+)/ =~ caller.first
file, line = $1, $2.to_i
rescue
file, line = __FILE__, __LINE__+3
end
module_eval(<<-EOS, file, line)
def #{name}(*args,&block)
@func_map['#{name}'].call(*args,&block)
end
EOS
module_function(name)
f
end
# Creates a class to wrap the C struct described by +signature+.
#
# MyStruct = struct ['int i', 'char c']
def struct(signature)
tys, mems = parse_struct_signature(signature, type_alias)
Fiddle::CStructBuilder.create(CStruct, tys, mems)
end
# Creates a class to wrap the C union described by +signature+.
#
# MyUnion = union ['int i', 'char c']
def union(signature)
tys, mems = parse_struct_signature(signature, type_alias)
Fiddle::CStructBuilder.create(CUnion, tys, mems)
end
# Returns the function mapped to +name+, that was created by either
# Fiddle::Importer.extern or Fiddle::Importer.bind
def [](name)
@func_map[name]
end
# Creates a class to wrap the C struct with the value +ty+
#
# See also Fiddle::Importer.struct
def create_value(ty, val=nil)
s = struct([ty + " value"])
ptr = s.malloc()
if( val )
ptr.value = val
end
return ptr
end
alias value create_value
# Returns a new instance of the C struct with the value +ty+ at the +addr+
# address.
def import_value(ty, addr)
s = struct([ty + " value"])
ptr = s.new(addr)
return ptr
end
# The Fiddle::CompositeHandler instance
#
# Will raise an error if no handlers are open.
def handler
(@handler ||= nil) or raise "call dlload before importing symbols and functions"
end
# Returns a new Fiddle::Pointer instance at the memory address of the given
# +name+ symbol.
#
# Raises a DLError if the +name+ doesn't exist.
#
# See Fiddle::CompositeHandler.sym and Fiddle::Handle.sym
def import_symbol(name)
addr = handler.sym(name)
if( !addr )
raise(DLError, "cannot find the symbol: #{name}")
end
Pointer.new(addr)
end
# Returns a new Fiddle::Function instance at the memory address of the given
# +name+ function.
#
# Raises a DLError if the +name+ doesn't exist.
#
# * +argtype+ is an Array of arguments, passed to the +name+ function.
# * +ctype+ is the return type of the function
# * +call_type+ is the ABI of the function
#
# See also Fiddle:Function.new
#
# See Fiddle::CompositeHandler.sym and Fiddle::Handler.sym
def import_function(name, ctype, argtype, call_type = nil)
addr = handler.sym(name)
if( !addr )
raise(DLError, "cannot find the function: #{name}()")
end
Function.new(addr, argtype, ctype, CALL_TYPE_TO_ABI[call_type],
name: name)
end
# Returns a new closure wrapper for the +name+ function.
#
# * +ctype+ is the return type of the function
# * +argtype+ is an Array of arguments, passed to the callback function
# * +call_type+ is the abi of the closure
# * +block+ is passed to the callback
#
# See Fiddle::Closure
def bind_function(name, ctype, argtype, call_type = nil, &block)
abi = CALL_TYPE_TO_ABI[call_type]
closure = Class.new(Fiddle::Closure) {
define_method(:call, block)
}.new(ctype, argtype, abi)
Function.new(closure, argtype, ctype, abi, name: name)
end
end
end
share/ruby/fiddle/types.rb 0000644 00000003626 15173547337 0011575 0 ustar 00 # frozen_string_literal: true
module Fiddle
# Adds Windows type aliases to the including class for use with
# Fiddle::Importer.
#
# The aliases added are:
# * ATOM
# * BOOL
# * BYTE
# * DWORD
# * DWORD32
# * DWORD64
# * HANDLE
# * HDC
# * HINSTANCE
# * HWND
# * LPCSTR
# * LPSTR
# * PBYTE
# * PDWORD
# * PHANDLE
# * PVOID
# * PWORD
# * UCHAR
# * UINT
# * ULONG
# * WORD
module Win32Types
def included(m) # :nodoc:
# https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types
m.module_eval{
typealias "ATOM", "WORD"
typealias "BOOL", "int"
typealias "BYTE", "unsigned char"
typealias "DWORD", "unsigned long"
typealias "DWORD32", "uint32_t"
typealias "DWORD64", "uint64_t"
typealias "HANDLE", "PVOID"
typealias "HDC", "HANDLE"
typealias "HINSTANCE", "HANDLE"
typealias "HWND", "HANDLE"
typealias "LPCSTR", "const char *"
typealias "LPSTR", "char *"
typealias "PBYTE", "BYTE *"
typealias "PDWORD", "DWORD *"
typealias "PHANDLE", "HANDLE *"
typealias "PVOID", "void *"
typealias "PWORD", "WORD *"
typealias "UCHAR", "unsigned char"
typealias "UINT", "unsigned int"
typealias "ULONG", "unsigned long"
typealias "WORD", "unsigned short"
}
end
module_function :included
end
# Adds basic type aliases to the including class for use with Fiddle::Importer.
#
# The aliases added are +uint+ and +u_int+ (<tt>unsigned int</tt>) and
# +ulong+ and +u_long+ (<tt>unsigned long</tt>)
module BasicTypes
def included(m) # :nodoc:
m.module_eval{
typealias "uint", "unsigned int"
typealias "u_int", "unsigned int"
typealias "ulong", "unsigned long"
typealias "u_long", "unsigned long"
}
end
module_function :included
end
end
share/ruby/fiddle/version.rb 0000644 00000000046 15173547337 0012107 0 ustar 00 module Fiddle
VERSION = "1.1.1"
end
share/ruby/fiddle/cparser.rb 0000644 00000021141 15173547337 0012060 0 ustar 00 # frozen_string_literal: true
module Fiddle
# A mixin that provides methods for parsing C struct and prototype signatures.
#
# == Example
# require 'fiddle/import'
#
# include Fiddle::CParser
# #=> Object
#
# parse_ctype('int')
# #=> Fiddle::TYPE_INT
#
# parse_struct_signature(['int i', 'char c'])
# #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
#
# parse_signature('double sum(double, double)')
# #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
#
module CParser
# Parses a C struct's members
#
# Example:
# require 'fiddle/import'
#
# include Fiddle::CParser
# #=> Object
#
# parse_struct_signature(['int i', 'char c'])
# #=> [[Fiddle::TYPE_INT, Fiddle::TYPE_CHAR], ["i", "c"]]
#
# parse_struct_signature(['char buffer[80]'])
# #=> [[[Fiddle::TYPE_CHAR, 80]], ["buffer"]]
#
def parse_struct_signature(signature, tymap=nil)
if signature.is_a?(String)
signature = split_arguments(signature, /[,;]/)
elsif signature.is_a?(Hash)
signature = [signature]
end
mems = []
tys = []
signature.each{|msig|
msig = compact(msig) if msig.is_a?(String)
case msig
when Hash
msig.each do |struct_name, struct_signature|
struct_name = struct_name.to_s if struct_name.is_a?(Symbol)
struct_name = compact(struct_name)
struct_count = nil
if struct_name =~ /^([\w\*\s]+)\[(\d+)\]$/
struct_count = $2.to_i
struct_name = $1
end
if struct_signature.respond_to?(:entity_class)
struct_type = struct_signature
else
parsed_struct = parse_struct_signature(struct_signature, tymap)
struct_type = CStructBuilder.create(CStruct, *parsed_struct)
end
if struct_count
ty = [struct_type, struct_count]
else
ty = struct_type
end
mems.push([struct_name, struct_type.members])
tys.push(ty)
end
when /^[\w\*\s]+[\*\s](\w+)$/
mems.push($1)
tys.push(parse_ctype(msig, tymap))
when /^[\w\*\s]+\(\*(\w+)\)\(.*?\)$/
mems.push($1)
tys.push(parse_ctype(msig, tymap))
when /^([\w\*\s]+[\*\s])(\w+)\[(\d+)\]$/
mems.push($2)
tys.push([parse_ctype($1.strip, tymap), $3.to_i])
when /^([\w\*\s]+)\[(\d+)\](\w+)$/
mems.push($3)
tys.push([parse_ctype($1.strip, tymap), $2.to_i])
else
raise(RuntimeError,"can't parse the struct member: #{msig}")
end
}
return tys, mems
end
# Parses a C prototype signature
#
# If Hash +tymap+ is provided, the return value and the arguments from the
# +signature+ are expected to be keys, and the value will be the C type to
# be looked up.
#
# Example:
# require 'fiddle/import'
#
# include Fiddle::CParser
# #=> Object
#
# parse_signature('double sum(double, double)')
# #=> ["sum", Fiddle::TYPE_DOUBLE, [Fiddle::TYPE_DOUBLE, Fiddle::TYPE_DOUBLE]]
#
# parse_signature('void update(void (*cb)(int code))')
# #=> ["update", Fiddle::TYPE_VOID, [Fiddle::TYPE_VOIDP]]
#
# parse_signature('char (*getbuffer(void))[80]')
# #=> ["getbuffer", Fiddle::TYPE_VOIDP, []]
#
def parse_signature(signature, tymap=nil)
tymap ||= {}
case compact(signature)
when /^(?:[\w\*\s]+)\(\*(\w+)\((.*?)\)\)(?:\[\w*\]|\(.*?\));?$/
func, args = $1, $2
return [func, TYPE_VOIDP, split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
when /^([\w\*\s]+[\*\s])(\w+)\((.*?)\);?$/
ret, func, args = $1.strip, $2, $3
return [func, parse_ctype(ret, tymap), split_arguments(args).collect {|arg| parse_ctype(arg, tymap)}]
else
raise(RuntimeError,"can't parse the function prototype: #{signature}")
end
end
# Given a String of C type +ty+, returns the corresponding Fiddle constant.
#
# +ty+ can also accept an Array of C type Strings, and will be returned in
# a corresponding Array.
#
# If Hash +tymap+ is provided, +ty+ is expected to be the key, and the
# value will be the C type to be looked up.
#
# Example:
# require 'fiddle/import'
#
# include Fiddle::CParser
# #=> Object
#
# parse_ctype('int')
# #=> Fiddle::TYPE_INT
#
# parse_ctype('double diff')
# #=> Fiddle::TYPE_DOUBLE
#
# parse_ctype('unsigned char byte')
# #=> -Fiddle::TYPE_CHAR
#
# parse_ctype('const char* const argv[]')
# #=> -Fiddle::TYPE_VOIDP
#
def parse_ctype(ty, tymap=nil)
tymap ||= {}
if ty.is_a?(Array)
return [parse_ctype(ty[0], tymap), ty[1]]
end
ty = ty.gsub(/\Aconst\s+/, "")
case ty
when 'void'
return TYPE_VOID
when /\A(?:(?:signed\s+)?long\s+long(?:\s+int\s+)?|int64_t)(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_LONG_LONG)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_LONG_LONG
when /\A(?:unsigned\s+long\s+long(?:\s+int\s+)?|uint64_t)(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_LONG_LONG)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_ULONG_LONG
when /\A(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?\z/
return TYPE_LONG
when /\Aunsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?\z/
return TYPE_ULONG
when /\A(?:signed\s+)?int(?:\s+\w+)?\z/
return TYPE_INT
when /\A(?:unsigned\s+int|uint)(?:\s+\w+)?\z/
return TYPE_UINT
when /\A(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?\z/
return TYPE_SHORT
when /\Aunsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?\z/
return TYPE_USHORT
when /\A(?:signed\s+)?char(?:\s+\w+)?\z/
return TYPE_CHAR
when /\Aunsigned\s+char(?:\s+\w+)?\z/
return TYPE_UCHAR
when /\Aint8_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT8_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_INT8_T
when /\Auint8_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT8_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_UINT8_T
when /\Aint16_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT16_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_INT16_T
when /\Auint16_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT16_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_UINT16_T
when /\Aint32_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT32_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_INT32_T
when /\Auint32_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT32_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_UINT32_T
when /\Aint64_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT64_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_INT64_T
when /\Auint64_t(?:\s+\w+)?\z/
unless Fiddle.const_defined?(:TYPE_INT64_T)
raise(RuntimeError, "unsupported type: #{ty}")
end
return TYPE_UINT64_T
when /\Afloat(?:\s+\w+)?\z/
return TYPE_FLOAT
when /\Adouble(?:\s+\w+)?\z/
return TYPE_DOUBLE
when /\Asize_t(?:\s+\w+)?\z/
return TYPE_SIZE_T
when /\Assize_t(?:\s+\w+)?\z/
return TYPE_SSIZE_T
when /\Aptrdiff_t(?:\s+\w+)?\z/
return TYPE_PTRDIFF_T
when /\Aintptr_t(?:\s+\w+)?\z/
return TYPE_INTPTR_T
when /\Auintptr_t(?:\s+\w+)?\z/
return TYPE_UINTPTR_T
when /\*/, /\[[\s\d]*\]/
return TYPE_VOIDP
when "..."
return TYPE_VARIADIC
else
ty = ty.split(' ', 2)[0]
if( tymap[ty] )
return parse_ctype(tymap[ty], tymap)
else
raise(DLError, "unknown type: #{ty}")
end
end
end
private
def split_arguments(arguments, sep=',')
return [] if arguments.strip == 'void'
arguments.scan(/([\w\*\s]+\(\*\w*\)\(.*?\)|[\w\*\s\[\]]+|\.\.\.)(?:#{sep}\s*|\z)/).collect {|m| m[0]}
end
def compact(signature)
signature.gsub(/\s+/, ' ').gsub(/\s*([\(\)\[\]\*,;])\s*/, '\1').strip
end
end
end
share/ruby/fiddle/pack.rb 0000644 00000006221 15173547337 0011341 0 ustar 00 # frozen_string_literal: true
require 'fiddle'
module Fiddle
module PackInfo # :nodoc: all
ALIGN_MAP = {
TYPE_VOIDP => ALIGN_VOIDP,
TYPE_CHAR => ALIGN_CHAR,
TYPE_SHORT => ALIGN_SHORT,
TYPE_INT => ALIGN_INT,
TYPE_LONG => ALIGN_LONG,
TYPE_FLOAT => ALIGN_FLOAT,
TYPE_DOUBLE => ALIGN_DOUBLE,
TYPE_UCHAR => ALIGN_CHAR,
TYPE_USHORT => ALIGN_SHORT,
TYPE_UINT => ALIGN_INT,
TYPE_ULONG => ALIGN_LONG,
}
PACK_MAP = {
TYPE_VOIDP => "L!",
TYPE_CHAR => "c",
TYPE_SHORT => "s!",
TYPE_INT => "i!",
TYPE_LONG => "l!",
TYPE_FLOAT => "f",
TYPE_DOUBLE => "d",
TYPE_UCHAR => "C",
TYPE_USHORT => "S!",
TYPE_UINT => "I!",
TYPE_ULONG => "L!",
}
SIZE_MAP = {
TYPE_VOIDP => SIZEOF_VOIDP,
TYPE_CHAR => SIZEOF_CHAR,
TYPE_SHORT => SIZEOF_SHORT,
TYPE_INT => SIZEOF_INT,
TYPE_LONG => SIZEOF_LONG,
TYPE_FLOAT => SIZEOF_FLOAT,
TYPE_DOUBLE => SIZEOF_DOUBLE,
TYPE_UCHAR => SIZEOF_CHAR,
TYPE_USHORT => SIZEOF_SHORT,
TYPE_UINT => SIZEOF_INT,
TYPE_ULONG => SIZEOF_LONG,
}
if defined?(TYPE_LONG_LONG)
ALIGN_MAP[TYPE_LONG_LONG] = ALIGN_MAP[TYPE_ULONG_LONG] = ALIGN_LONG_LONG
PACK_MAP[TYPE_LONG_LONG] = "q"
PACK_MAP[TYPE_ULONG_LONG] = "Q"
SIZE_MAP[TYPE_LONG_LONG] = SIZE_MAP[TYPE_ULONG_LONG] = SIZEOF_LONG_LONG
PACK_MAP[TYPE_VOIDP] = "Q" if SIZEOF_LONG_LONG == SIZEOF_VOIDP
end
def align(addr, align)
d = addr % align
if( d == 0 )
addr
else
addr + (align - d)
end
end
module_function :align
end
class Packer # :nodoc: all
include PackInfo
def self.[](*types)
new(types)
end
def initialize(types)
parse_types(types)
end
def size()
@size
end
def pack(ary)
case SIZEOF_VOIDP
when SIZEOF_LONG
ary.pack(@template)
else
if defined?(TYPE_LONG_LONG) and
SIZEOF_VOIDP == SIZEOF_LONG_LONG
ary.pack(@template)
else
raise(RuntimeError, "sizeof(void*)?")
end
end
end
def unpack(ary)
case SIZEOF_VOIDP
when SIZEOF_LONG
ary.join().unpack(@template)
else
if defined?(TYPE_LONG_LONG) and
SIZEOF_VOIDP == SIZEOF_LONG_LONG
ary.join().unpack(@template)
else
raise(RuntimeError, "sizeof(void*)?")
end
end
end
private
def parse_types(types)
@template = "".dup
addr = 0
types.each{|t|
orig_addr = addr
if( t.is_a?(Array) )
addr = align(orig_addr, ALIGN_MAP[TYPE_VOIDP])
else
addr = align(orig_addr, ALIGN_MAP[t])
end
d = addr - orig_addr
if( d > 0 )
@template << "x#{d}"
end
if( t.is_a?(Array) )
@template << (PACK_MAP[t[0]] * t[1])
addr += (SIZE_MAP[t[0]] * t[1])
else
@template << PACK_MAP[t]
addr += SIZE_MAP[t]
end
}
addr = align(addr, ALIGN_MAP[TYPE_VOIDP])
@size = addr
end
end
end
share/ruby/fiddle/value.rb 0000644 00000005570 15173547337 0011545 0 ustar 00 # frozen_string_literal: true
require 'fiddle'
module Fiddle
module ValueUtil #:nodoc: all
def unsigned_value(val, ty)
case ty.abs
when TYPE_CHAR
[val].pack("c").unpack("C")[0]
when TYPE_SHORT
[val].pack("s!").unpack("S!")[0]
when TYPE_INT
[val].pack("i!").unpack("I!")[0]
when TYPE_LONG
[val].pack("l!").unpack("L!")[0]
else
if defined?(TYPE_LONG_LONG) and
ty.abs == TYPE_LONG_LONG
[val].pack("q").unpack("Q")[0]
else
val
end
end
end
def signed_value(val, ty)
case ty.abs
when TYPE_CHAR
[val].pack("C").unpack("c")[0]
when TYPE_SHORT
[val].pack("S!").unpack("s!")[0]
when TYPE_INT
[val].pack("I!").unpack("i!")[0]
when TYPE_LONG
[val].pack("L!").unpack("l!")[0]
else
if defined?(TYPE_LONG_LONG) and
ty.abs == TYPE_LONG_LONG
[val].pack("Q").unpack("q")[0]
else
val
end
end
end
def wrap_args(args, tys, funcs, &block)
result = []
tys ||= []
args.each_with_index{|arg, idx|
result.push(wrap_arg(arg, tys[idx], funcs, &block))
}
result
end
def wrap_arg(arg, ty, funcs = [], &block)
funcs ||= []
case arg
when nil
return 0
when Pointer
return arg.to_i
when IO
case ty
when TYPE_VOIDP
return Pointer[arg].to_i
else
return arg.to_i
end
when Function
if( block )
arg.bind_at_call(&block)
funcs.push(arg)
elsif !arg.bound?
raise(RuntimeError, "block must be given.")
end
return arg.to_i
when String
if( ty.is_a?(Array) )
return arg.unpack('C*')
else
case SIZEOF_VOIDP
when SIZEOF_LONG
return [arg].pack("p").unpack("l!")[0]
else
if defined?(SIZEOF_LONG_LONG) and
SIZEOF_VOIDP == SIZEOF_LONG_LONG
return [arg].pack("p").unpack("q")[0]
else
raise(RuntimeError, "sizeof(void*)?")
end
end
end
when Float, Integer
return arg
when Array
if( ty.is_a?(Array) ) # used only by struct
case ty[0]
when TYPE_VOIDP
return arg.collect{|v| Integer(v)}
when TYPE_CHAR
if( arg.is_a?(String) )
return val.unpack('C*')
end
end
return arg
else
return arg
end
else
if( arg.respond_to?(:to_ptr) )
return arg.to_ptr.to_i
else
begin
return Integer(arg)
rescue
raise(ArgumentError, "unknown argument type: #{arg.class}")
end
end
end
end
end
end
share/ruby/ruby_vm/mjit/c_pointer.rb 0000644 00000022031 15173547337 0013601 0 ustar 00 module RubyVM::MJIT # :nodoc: all
# Every class under this namespace is a pointer. Even if the type is
# immediate, it shouldn't be dereferenced until `*` is called.
module CPointer
# Note: We'd like to avoid alphabetic method names to avoid a conflict
# with member methods. to_i and to_s are considered an exception.
class Struct
# @param name [String]
# @param sizeof [Integer]
# @param members [Hash{ Symbol => [RubyVM::MJIT::CType::*, Integer, TrueClass] }]
def initialize(addr, sizeof, members)
@addr = addr
@sizeof = sizeof
@members = members
end
# Get a raw address
def to_i
@addr
end
# Serialized address for generated code
def to_s
"0x#{@addr.to_s(16)}"
end
# Pointer diff
def -(struct)
raise ArgumentError if self.class != struct.class
(@addr - struct.to_i) / @sizeof
end
# Primitive API that does no automatic dereference
# TODO: remove this?
# @param member [Symbol]
def [](member)
type, offset = @members.fetch(member)
type.new(@addr + offset / 8)
end
private
# @param member [Symbol]
# @param value [Object]
def []=(member, value)
type, offset = @members.fetch(member)
type[@addr + offset / 8] = value
end
# @param sizeof [Integer]
# @param members [Hash{ Symbol => [Integer, RubyVM::MJIT::CType::*] }]
def self.define(sizeof, members)
Class.new(self) do
# Return the size of this type
define_singleton_method(:sizeof) { sizeof }
define_method(:initialize) do |addr = nil|
if addr.nil? # TODO: get rid of this feature later
addr = Fiddle.malloc(sizeof)
end
super(addr, sizeof, members)
end
members.each do |member, (type, offset, to_ruby)|
# Intelligent API that does automatic dereference
define_method(member) do
value = self[member]
if value.respond_to?(:*)
value = value.*
end
if to_ruby
value = C.to_ruby(value)
end
value
end
define_method("#{member}=") do |value|
self[member] = value
end
end
end
end
end
# Note: We'd like to avoid alphabetic method names to avoid a conflict
# with member methods. to_i is considered an exception.
class Union
# @param _name [String] To be used when it starts defining a union pointer class
# @param sizeof [Integer]
# @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
def initialize(addr, sizeof, members)
@addr = addr
@sizeof = sizeof
@members = members
end
# Get a raw address
def to_i
@addr
end
# Move addr to access this pointer like an array
def +(index)
raise ArgumentError unless index.is_a?(Integer)
self.class.new(@addr + index * @sizeof)
end
# Pointer diff
def -(union)
raise ArgumentError if self.class != union.class
(@addr - union.instance_variable_get(:@addr)) / @sizeof
end
# @param sizeof [Integer]
# @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
def self.define(sizeof, members)
Class.new(self) do
# Return the size of this type
define_singleton_method(:sizeof) { sizeof }
define_method(:initialize) do |addr|
super(addr, sizeof, members)
end
members.each do |member, type|
# Intelligent API that does automatic dereference
define_method(member) do
value = type.new(@addr)
if value.respond_to?(:*)
value = value.*
end
value
end
end
end
end
end
class Immediate
# @param addr [Integer]
# @param size [Integer]
# @param pack [String]
def initialize(addr, size, pack)
@addr = addr
@size = size
@pack = pack
end
# Get a raw address
def to_i
@addr
end
# Move addr to addess this pointer like an array
def +(index)
Immediate.new(@addr + index * @size, @size, @pack)
end
# Dereference
def *
self[0]
end
# Array access
def [](index)
return nil if @addr == 0
Fiddle::Pointer.new(@addr + index * @size)[0, @size].unpack1(@pack)
end
# Array set
def []=(index, value)
Fiddle::Pointer.new(@addr + index * @size)[0, @size] = [value].pack(@pack)
end
# Serialized address for generated code. Used for embedding things like body->iseq_encoded.
def to_s
"0x#{Integer(@addr).to_s(16)}"
end
# @param fiddle_type [Integer] Fiddle::TYPE_*
def self.define(fiddle_type)
size = Fiddle::PackInfo::SIZE_MAP.fetch(fiddle_type)
pack = Fiddle::PackInfo::PACK_MAP.fetch(fiddle_type)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, size, pack)
end
define_singleton_method(:size) do
size
end
# Type-level []=: Used by struct fields
define_singleton_method(:[]=) do |addr, value|
Fiddle::Pointer.new(addr)[0, size] = [value].pack(pack)
end
end
end
end
# -Fiddle::TYPE_CHAR Immediate with special handling of true/false
class Bool < Immediate.define(-Fiddle::TYPE_CHAR)
# Dereference
def *
return nil if @addr == 0
super != 0
end
def self.[]=(addr, value)
super(addr, value ? 1 : 0)
end
end
class Pointer
attr_reader :type
# @param addr [Integer]
# @param type [Class] RubyVM::MJIT::CType::*
def initialize(addr, type)
@addr = addr
@type = type
end
# Move addr to addess this pointer like an array
def +(index)
raise ArgumentError unless index.is_a?(Integer)
Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP, @type)
end
# Dereference
def *
return nil if dest_addr == 0
@type.new(dest_addr)
end
# Array access
def [](index)
(self + index).*
end
# Array set
# @param index [Integer]
# @param value [Integer, RubyVM::MJIT::CPointer::Struct] an address itself or an object that return an address with to_i
def []=(index, value)
Fiddle::Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP)[0, Fiddle::SIZEOF_VOIDP] =
[value.to_i].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
private
def dest_addr
Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_VOIDP].unpack1(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
def self.define(block)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, block.call)
end
# Type-level []=: Used by struct fields
# @param addr [Integer]
# @param value [Integer, RubyVM::MJIT::CPointer::Struct] an address itself, or an object that return an address with to_i
define_singleton_method(:[]=) do |addr, value|
value = value.to_i
Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP] = [value].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
end
end
end
class BitField
# @param addr [Integer]
# @param width [Integer]
# @param offset [Integer]
def initialize(addr, width, offset)
@addr = addr
@width = width
@offset = offset
end
# Dereference
def *
byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack1('c')
if @width == 1
bit = (1 & (byte >> @offset))
bit == 1
elsif @width <= 8 && @offset == 0
bitmask = @width.times.sum { |i| 1 << i }
byte & bitmask
else
raise NotImplementedError.new("not-implemented bit field access: width=#{@width} offset=#{@offset}")
end
end
# @param width [Integer]
# @param offset [Integer]
def self.define(width, offset)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, width, offset)
end
end
end
end
# Give a name to a dynamic CPointer class to see it on inspect
def self.with_class_name(prefix, name, cache: false, &block)
return block.call if name.empty?
# Use a cached result only if cache: true
class_name = "#{prefix}_#{name}"
klass =
if cache && self.const_defined?(class_name)
self.const_get(class_name)
else
block.call
end
# Give it a name unless it's already defined
unless self.const_defined?(class_name)
self.const_set(class_name, klass)
end
klass
end
end
end
share/ruby/ruby_vm/mjit/c_type.rb 0000644 00000004766 15173547337 0013121 0 ustar 00 require 'fiddle'
require 'fiddle/pack'
require_relative 'c_pointer'
module RubyVM::MJIT # :nodoc: all
module CType
module Struct
# @param name [String]
# @param members [Hash{ Symbol => [Integer, RubyVM::MJIT::CType::*] }]
def self.new(name, sizeof, **members)
name = members.keys.join('_') if name.empty?
CPointer.with_class_name('Struct', name) do
CPointer::Struct.define(sizeof, members)
end
end
end
module Union
# @param name [String]
# @param members [Hash{ Symbol => RubyVM::MJIT::CType::* }]
def self.new(name, sizeof, **members)
name = members.keys.join('_') if name.empty?
CPointer.with_class_name('Union', name) do
CPointer::Union.define(sizeof, members)
end
end
end
module Immediate
# @param fiddle_type [Integer]
def self.new(fiddle_type)
name = Fiddle.constants.find do |const|
const.start_with?('TYPE_') && Fiddle.const_get(const) == fiddle_type.abs
end&.to_s
name.delete_prefix!('TYPE_')
if fiddle_type.negative?
name.prepend('U')
end
CPointer.with_class_name('Immediate', name, cache: true) do
CPointer::Immediate.define(fiddle_type)
end
end
# @param type [String]
def self.parse(ctype)
new(Fiddle::Importer.parse_ctype(ctype))
end
def self.find(size, signed)
fiddle_type = TYPE_MAP.fetch(size)
fiddle_type = -fiddle_type unless signed
new(fiddle_type)
end
TYPE_MAP = Fiddle::PackInfo::SIZE_MAP.map { |type, size| [size, type.abs] }.to_h
private_constant :TYPE_MAP
end
module Bool
def self.new
CPointer::Bool
end
end
class Pointer
# This takes a block to avoid "stack level too deep" on a cyclic reference
# @param block [Proc]
def self.new(&block)
CPointer.with_class_name('Pointer', block.object_id.to_s) do
CPointer::Pointer.define(block)
end
end
end
module BitField
# @param width [Integer]
# @param offset [Integer]
def self.new(width, offset)
CPointer.with_class_name('BitField', "#{offset}_#{width}") do
CPointer::BitField.define(width, offset)
end
end
end
# Types that are referenced but not part of code generation targets
Stub = ::Struct.new(:name)
# Types that it failed to figure out from the header
Unknown = Module.new
end
end
share/ruby/ruby_vm/mjit/hooks.rb 0000644 00000001623 15173547340 0012740 0 ustar 00 module RubyVM::MJIT::Hooks # :nodoc: all
C = RubyVM::MJIT.const_get(:C, false)
def self.on_bop_redefined(_redefined_flag, _bop)
C.mjit_cancel_all("BOP is redefined")
end
def self.on_cme_invalidate(_cme)
# to be used later
end
def self.on_ractor_spawn
C.mjit_cancel_all("Ractor is spawned")
end
def self.on_constant_state_changed(_id)
# to be used later
end
def self.on_constant_ic_update(_iseq, _ic, _insn_idx)
# to be used later
end
def self.on_tracing_invalidate_all(new_iseq_events)
# Stop calling all JIT-ed code. We can't rewrite existing JIT-ed code to trace_ insns for now.
# :class events are triggered only in ISEQ_TYPE_CLASS, but mjit_target_iseq_p ignores such iseqs.
# Thus we don't need to cancel JIT-ed code for :class events.
if new_iseq_events != C.RUBY_EVENT_CLASS
C.mjit_cancel_all("TracePoint is enabled")
end
end
end
share/ruby/ruby_vm/mjit/compiler.rb 0000644 00000116651 15173547340 0013437 0 ustar 00 # Available variables and macros in JIT-ed function:
# ec: the first argument of _mjitXXX
# reg_cfp: the second argument of _mjitXXX
# GET_CFP(): refers to `reg_cfp`
# GET_EP(): refers to `reg_cfp->ep`
# GET_SP(): refers to `reg_cfp->sp`, or `(stack + stack_size)` if local_stack_p
# GET_SELF(): refers to `cfp_self`
# GET_LEP(): refers to `VM_EP_LEP(reg_cfp->ep)`
# EXEC_EC_CFP(): refers to `val = vm_exec(ec, true)` with frame setup
# CALL_METHOD(): using `GET_CFP()` and `EXEC_EC_CFP()`
# TOPN(): refers to `reg_cfp->sp`, or `*(stack + (stack_size - num - 1))` if local_stack_p
# STACK_ADDR_FROM_TOP(): refers to `reg_cfp->sp`, or `stack + (stack_size - num)` if local_stack_p
# DISPATCH_ORIGINAL_INSN(): expanded in _mjit_compile_insn.erb
# THROW_EXCEPTION(): specially defined for JIT
# RESTORE_REGS(): specially defined for `leave`
class RubyVM::MJIT::Compiler # :nodoc: all
C = RubyVM::MJIT.const_get(:C, false)
INSNS = RubyVM::MJIT.const_get(:INSNS, false)
UNSUPPORTED_INSNS = [
:defineclass, # low priority
]
def initialize = freeze
# @param iseq [RubyVM::MJIT::CPointer::Struct]
# @param funcname [String]
# @param id [Integer]
# @return [String,NilClass]
def compile(iseq, funcname, id)
status = C.compile_status.new # not freed for now
status.compiled_iseq = iseq.body
status.compiled_id = id
init_compile_status(status, iseq.body, true) # not freed for now
if iseq.body.ci_size > 0 && status.cc_entries_index == -1
return nil
end
src = +''
if !status.compile_info.disable_send_cache && !status.compile_info.disable_inlining
unless precompile_inlinable_iseqs(src, iseq, status)
return nil
end
end
src << "VALUE\n#{funcname}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp)\n{\n"
success = compile_body(src, iseq, status)
src << "\n} // end of #{funcname}\n"
return success ? src : nil
rescue Exception => e # should we use rb_rescue in C instead?
if C.mjit_opts.warnings || C.mjit_opts.verbose > 0
$stderr.puts "MJIT error: #{e.full_message}"
end
return nil
end
private
def compile_body(src, iseq, status)
status.success = true
status.local_stack_p = !iseq.body.catch_except_p
if status.local_stack_p
src << " VALUE stack[#{iseq.body.stack_max}];\n"
else
src << " VALUE *stack = reg_cfp->sp;\n"
end
unless status.inlined_iseqs.nil? # i.e. compile root
src << " static const rb_iseq_t *original_iseq = (const rb_iseq_t *)#{iseq};\n"
end
src << " static const VALUE *const original_body_iseq = (VALUE *)#{iseq.body.iseq_encoded};\n"
src << " VALUE cfp_self = reg_cfp->self;\n" # cache self across the method
src << "#undef GET_SELF\n"
src << "#define GET_SELF() cfp_self\n"
# Generate merged ivar guards first if needed
if !status.compile_info.disable_ivar_cache && using_ivar?(iseq.body)
src << " if (UNLIKELY(!RB_TYPE_P(GET_SELF(), T_OBJECT))) {"
src << " goto ivar_cancel;\n"
src << " }\n"
end
# Simulate `opt_pc` in setup_parameters_complex. Other PCs which may be passed by catch tables
# are not considered since vm_exec doesn't call jit_exec for catch tables.
if iseq.body.param.flags.has_opt
src << "\n"
src << " switch (reg_cfp->pc - ISEQ_BODY(reg_cfp->iseq)->iseq_encoded) {\n"
(0..iseq.body.param.opt_num).each do |i|
pc_offset = iseq.body.param.opt_table[i]
src << " case #{pc_offset}:\n"
src << " goto label_#{pc_offset};\n"
end
src << " }\n"
end
compile_insns(0, 0, status, iseq.body, src)
compile_cancel_handler(src, iseq.body, status)
src << "#undef GET_SELF\n"
return status.success
end
# Compile one conditional branch. If it has branchXXX insn, this should be
# called multiple times for each branch.
def compile_insns(stack_size, pos, status, body, src)
branch = C.compile_branch.new # not freed for now
branch.stack_size = stack_size
branch.finish_p = false
while pos < body.iseq_size && !already_compiled?(status, pos) && !branch.finish_p
insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
status.stack_size_for_pos[pos] = branch.stack_size
src << "\nlabel_#{pos}: /* #{insn.name} */\n"
pos = compile_insn(insn, pos, status, body.iseq_encoded + (pos+1), body, branch, src)
if status.success && branch.stack_size > body.stack_max
if mjit_opts.warnings || mjit_opts.verbose > 0
$stderr.puts "MJIT warning: JIT stack size (#{branch.stack_size}) exceeded its max size (#{body.stack_max})"
end
status.success = false
end
break unless status.success
end
end
# Main function of JIT compilation, vm_exec_core counterpart for JIT. Compile one insn to `f`, may modify
# b->stack_size and return next position.
#
# When you add a new instruction to insns.def, it would be nice to have JIT compilation support here but
# it's optional. This JIT compiler just ignores ISeq which includes unknown instruction, and ISeq which
# does not have it can be compiled as usual.
def compile_insn(insn, pos, status, operands, body, b, src)
sp_inc = C.mjit_call_attribute_sp_inc(insn.bin, operands)
next_pos = pos + insn.len
result = compile_insn_entry(insn, b.stack_size, sp_inc, status.local_stack_p, pos, next_pos, insn.len,
status.inlined_iseqs.nil?, status, operands, body)
if result.nil?
if C.mjit_opts.warnings || C.mjit_opts.verbose > 0
$stderr.puts "MJIT warning: Skipped to compile unsupported instruction: #{insn.name}"
end
status.success = false
else
result_src, next_pos, finish_p, compile_insns_p = result
src << result_src
b.stack_size += sp_inc
if finish_p
b.finish_p = true
end
if compile_insns_p
if already_compiled?(status, pos + insn.len)
src << "goto label_#{pos + insn.len};\n"
else
compile_insns(b.stack_size, pos + insn.len, status, body, src)
end
end
end
# If next_pos is already compiled and this branch is not finished yet,
# next instruction won't be compiled in C code next and will need `goto`.
if !b.finish_p && next_pos < body.iseq_size && already_compiled?(status, next_pos)
src << "goto label_#{next_pos};\n"
# Verify stack size assumption is the same among multiple branches
if status.stack_size_for_pos[next_pos] != b.stack_size
if mjit_opts.warnings || mjit_opts.verbose > 0
$stderr.puts "MJIT warning: JIT stack assumption is not the same between branches (#{status.stack_size_for_pos[next_pos]} != #{b.stack_size})\n"
end
status.success = false
end
end
return next_pos
end
def compile_insn_entry(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, status, operands, body)
finish_p = false
compile_insns = false
# TODO: define this outside this method, or at least cache it
opt_send_without_block = INSNS.values.find { |i| i.name == :opt_send_without_block }
if opt_send_without_block.nil?
raise 'opt_send_without_block not found'
end
send_compatible_opt_insns = INSNS.values.select do |insn|
insn.name.start_with?('opt_') && opt_send_without_block.opes == insn.opes &&
insn.expr.lines.any? { |l| l.match(/\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/) }
end.map(&:name)
case insn.name
when *UNSUPPORTED_INSNS
return nil
when :opt_send_without_block, :send
if src = compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
return src, next_pos, finish_p, compile_insns
end
when *send_compatible_opt_insns
if C.has_cache_for_send(captured_cc_entries(status)[call_data_index(C.CALL_DATA.new(operands[0]), body)], insn.bin) &&
src = compile_send(opt_send_without_block, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
return src, next_pos, finish_p, compile_insns
end
when :getinstancevariable, :setinstancevariable
if src = compile_ivar(insn.name, stack_size, pos, status, operands, body)
return src, next_pos, finish_p, compile_insns
end
when :opt_getconstant_path
if src = compile_getconstant_path(stack_size, pos, insn_len, operands, status)
return src, next_pos, finish_p, compile_insns
end
when :invokebuiltin, :opt_invokebuiltin_delegate, :opt_invokebuiltin_delegate_leave
if src = compile_invokebuiltin(insn, stack_size, sp_inc, body, operands)
if insn.name == :opt_invokebuiltin_delegate_leave
src << compile_leave(stack_size, pos, inlined_iseq_p)
finish_p = true
end
return src, next_pos, finish_p, compile_insns
end
when :leave
if stack_size != 1
raise "Unexpected JIT stack_size on leave: #{stack_size}"
end
src = compile_leave(stack_size, pos, inlined_iseq_p)
finish_p = true
return src, next_pos, finish_p, compile_insns
end
return compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands)
end
# Optimized case of send / opt_send_without_block instructions.
def compile_send(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, status, operands, body)
# compiler: Use captured cc to avoid race condition
cd = C.CALL_DATA.new(operands[0])
cd_index = call_data_index(cd, body)
captured_cc = captured_cc_entries(status)[cd_index]
# compiler: Inline send insn where some supported fastpath is used.
ci = cd.ci
kw_splat = (C.vm_ci_flag(ci) & C.VM_CALL_KW_SPLAT) > 0
if !status.compile_info.disable_send_cache && has_valid_method_type?(captured_cc) && (
# `CC_SET_FASTPATH(cd->cc, vm_call_cfunc_with_frame, ...)` in `vm_call_cfunc`
(vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC && !C.rb_splat_or_kwargs_p(ci) && !kw_splat) ||
# `CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(...), vm_call_iseq_optimizable_p(...))` in `vm_callee_setup_arg`,
# and support only non-VM_CALL_TAILCALL path inside it
(vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_ISEQ &&
C.fastpath_applied_iseq_p(ci, captured_cc, iseq = def_iseq_ptr(vm_cc_cme(captured_cc).def)) &&
(C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL) == 0)
)
src = +"{\n"
# JIT: Invalidate call cache if it requires vm_search_method. This allows to inline some of following things.
src << " const struct rb_callcache *cc = (struct rb_callcache *)#{captured_cc};\n"
src << " const rb_callable_method_entry_t *cc_cme = (rb_callable_method_entry_t *)#{vm_cc_cme(captured_cc)};\n"
src << " const VALUE recv = stack[#{stack_size + sp_inc - 1}];\n"
# If opt_class_of is true, use RBASIC_CLASS instead of CLASS_OF to reduce code size
opt_class_of = !maybe_special_const?(captured_cc.klass)
src << " if (UNLIKELY(#{opt_class_of ? 'RB_SPECIAL_CONST_P(recv)' : 'false'} || !vm_cc_valid_p(cc, cc_cme, #{opt_class_of ? 'RBASIC_CLASS' : 'CLASS_OF'}(recv)))) {\n"
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " goto send_cancel;\n"
src << " }\n"
# JIT: move sp and pc if necessary
pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
# JIT: If ISeq is inlinable, call the inlined method without pushing a frame.
if iseq && status.inlined_iseqs && iseq.body.to_i == status.inlined_iseqs[pos]&.to_i
src << " {\n"
src << " VALUE orig_self = reg_cfp->self;\n"
src << " reg_cfp->self = stack[#{stack_size + sp_inc - 1}];\n"
src << " stack[#{stack_size + sp_inc - 1}] = _mjit#{status.compiled_id}_inlined_#{pos}(ec, reg_cfp, orig_self, original_iseq);\n"
src << " reg_cfp->self = orig_self;\n"
src << " }\n"
else
# JIT: Forked `vm_sendish` (except method_explorer = vm_search_method_wrap) to inline various things
src << " {\n"
src << " VALUE val;\n"
src << " struct rb_calling_info calling;\n"
if insn.name == :send
src << " calling.block_handler = vm_caller_setup_arg_block(ec, reg_cfp, (const struct rb_callinfo *)#{ci}, (rb_iseq_t *)0x#{operands[1].to_s(16)}, FALSE);\n"
else
src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n"
end
src << " calling.kw_splat = #{kw_splat ? 1 : 0};\n"
src << " calling.recv = stack[#{stack_size + sp_inc - 1}];\n"
src << " calling.argc = #{C.vm_ci_argc(ci)};\n"
if vm_cc_cme(captured_cc).def.type == C.VM_METHOD_TYPE_CFUNC
# TODO: optimize this more
src << " calling.ci = (const struct rb_callinfo *)#{ci};\n" # creating local cd here because operand's cd->cc may not be the same as inlined cc.
src << " calling.cc = cc;"
src << " val = vm_call_cfunc_with_frame(ec, reg_cfp, &calling);\n"
else # :iseq
# fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, cc_cme, 0, #{iseq.body.param.size}, #{iseq.body.local_table_size});\n"
if iseq.body.catch_except_p
src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n"
src << " val = vm_exec(ec, true);\n"
else
src << " if ((val = jit_exec(ec)) == Qundef) {\n"
src << " VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);\n" # This is vm_call0_body's code after vm_call_iseq_setup
src << " val = vm_exec(ec, false);\n"
src << " }\n"
end
end
src << " stack[#{stack_size + sp_inc - 1}] = val;\n"
src << " }\n"
# JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow.
src << " if (UNLIKELY(!mjit_call_p)) {\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n"
if !pc_moved_p
src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n"
end
src << " goto cancel;\n"
src << " }\n"
end
src << "}\n"
return src
else
return nil
end
end
def compile_ivar(insn_name, stack_size, pos, status, operands, body)
iv_cache = C.iseq_inline_storage_entry.new(operands[1]).iv_cache
dest_shape_id = iv_cache.value >> C.SHAPE_FLAG_SHIFT
source_shape_id = parent_shape_id(dest_shape_id)
attr_index = iv_cache.value & ((1 << C.SHAPE_FLAG_SHIFT) - 1)
src = +''
if !status.compile_info.disable_ivar_cache && source_shape_id != C.INVALID_SHAPE_ID
# JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`.
# compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
# JIT: prepare vm_getivar/vm_setivar arguments and variables
src << "{\n"
src << " VALUE obj = GET_SELF();\n" # T_OBJECT guaranteed by compile_body
# JIT: cache hit path of vm_getivar/vm_setivar, or cancel JIT (recompile it with exivar)
if insn_name == :setinstancevariable
src << " const uint32_t index = #{attr_index - 1};\n"
src << " const shape_id_t dest_shape_id = (shape_id_t)#{dest_shape_id};\n"
src << " if (dest_shape_id == ROBJECT_SHAPE_ID(obj)) {\n"
src << " VALUE *ptr = ROBJECT_IVPTR(obj);\n"
src << " RB_OBJ_WRITE(obj, &ptr[index], stack[#{stack_size - 1}]);\n"
src << " }\n"
else
src << " const shape_id_t source_shape_id = (shape_id_t)#{dest_shape_id};\n"
if attr_index == 0 # cache hit, but uninitialized iv
src << " /* Uninitialized instance variable */\n"
src << " if (source_shape_id == ROBJECT_SHAPE_ID(obj)) {\n"
src << " stack[#{stack_size}] = Qnil;\n"
src << " }\n"
else
src << " const uint32_t index = #{attr_index - 1};\n"
src << " if (source_shape_id == ROBJECT_SHAPE_ID(obj)) {\n"
src << " stack[#{stack_size}] = ROBJECT_IVPTR(obj)[index];\n"
src << " }\n"
end
end
src << " else {\n"
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " goto ivar_cancel;\n"
src << " }\n"
src << "}\n"
return src
elsif insn_name == :getinstancevariable && !status.compile_info.disable_exivar_cache && source_shape_id != C.INVALID_SHAPE_ID
# JIT: optimize away motion of sp and pc. This path does not call rb_warning() and so it's always leaf and not `handles_sp`.
# compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
# JIT: prepare vm_getivar's arguments and variables
src << "{\n"
src << " VALUE obj = GET_SELF();\n"
src << " const shape_id_t source_shape_id = (shape_id_t)#{dest_shape_id};\n"
src << " const uint32_t index = #{attr_index - 1};\n"
# JIT: cache hit path of vm_getivar, or cancel JIT (recompile it without any ivar optimization)
src << " struct gen_ivtbl *ivtbl;\n"
src << " if (LIKELY(FL_TEST_RAW(GET_SELF(), FL_EXIVAR) && source_shape_id == rb_shape_get_shape_id(obj) && rb_ivar_generic_ivtbl_lookup(obj, &ivtbl))) {\n"
src << " stack[#{stack_size}] = ivtbl->ivptr[index];\n"
src << " }\n"
src << " else {\n"
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " goto exivar_cancel;\n"
src << " }\n"
src << "}\n"
return src
else
return nil
end
end
def compile_invokebuiltin(insn, stack_size, sp_inc, body, operands)
bf = C.RB_BUILTIN.new(operands[0])
if bf.compiler > 0
index = (insn.name == :invokebuiltin ? -1 : operands[1])
src = +"{\n"
src << " VALUE val;\n"
C.builtin_compiler(src, bf, index, stack_size, body.builtin_inline_p)
src << " stack[#{stack_size + sp_inc - 1}] = val;\n"
src << "}\n"
return src
else
return nil
end
end
def compile_leave(stack_size, pos, inlined_iseq_p)
src = +''
# Skip vm_pop_frame for inlined call
unless inlined_iseq_p
# Cancel on interrupts to make leave insn leaf
src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n"
src << " }\n"
src << " ec->cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(reg_cfp);\n" # vm_pop_frame
end
src << " return stack[0];\n"
end
def compile_getconstant_path(stack_size, pos, insn_len, operands, status)
ice = C.IC.new(operands[0]).entry
if !status.compile_info.disable_const_cache && ice
# JIT: Inline everything in IC, and cancel the slow path
src = +" if (vm_inlined_ic_hit_p(#{ice.flags}, #{ice.value}, (const rb_cref_t *)#{to_addr(ice.ic_cref)}, reg_cfp->ep)) {\n"
src << " stack[#{stack_size}] = #{ice.value};\n"
src << " }\n"
src << " else {\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " goto const_cancel;\n"
src << " }\n"
return src
else
return nil
end
end
def compile_insn_default(insn, stack_size, sp_inc, local_stack_p, pos, next_pos, insn_len, inlined_iseq_p, operands)
src = +''
finish_p = false
compile_insns = false
# JIT: Declare stack_size to be used in some macro of _mjit_compile_insn_body.erb
src << "{\n"
if local_stack_p
src << " MAYBE_UNUSED(unsigned int) stack_size = #{stack_size};\n"
end
# JIT: Declare variables for operands, popped values and return values
insn.declarations.each do |decl|
src << " #{decl};\n"
end
# JIT: Set const expressions for `RubyVM::OperandsUnifications` insn
insn.preamble.each do |amble|
src << "#{amble.sub(/const \S+\s+/, '')}\n"
end
# JIT: Initialize operands
insn.opes.each_with_index do |ope, i|
src << " #{ope.fetch(:name)} = (#{ope.fetch(:type)})#{operands[i]};\n"
# TODO: resurrect comment_id
end
# JIT: Initialize popped values
insn.pops.reverse_each.with_index.reverse_each do |pop, i|
src << " #{pop.fetch(:name)} = stack[#{stack_size - (i + 1)}];\n"
end
# JIT: move sp and pc if necessary
pc_moved_p = compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
# JIT: Print insn body in insns.def
next_pos = compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands)
# JIT: Set return values
insn.rets.reverse_each.with_index do |ret, i|
# TOPN(n) = ...
src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n"
end
# JIT: We should evaluate ISeq modified for TracePoint if it's enabled. Note: This is slow.
# leaf insn may not cancel JIT. leaf_without_check_ints is covered in RUBY_VM_CHECK_INTS of _mjit_compile_insn_body.erb.
unless insn.always_leaf? || insn.leaf_without_check_ints?
src << " if (UNLIKELY(!mjit_call_p)) {\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size + sp_inc};\n"
if !pc_moved_p
src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n"
end
src << " goto cancel;\n"
src << " }\n"
end
src << "}\n"
# compiler: If insn has conditional JUMP, the code should go to the branch not targeted by JUMP next.
if insn.expr.match?(/if\s+\([^{}]+\)\s+\{[^{}]+JUMP\([^)]+\);[^{}]+\}/)
compile_insns = true
end
# compiler: If insn returns (leave) or does longjmp (throw), the branch should no longer be compiled. TODO: create attr for it?
if insn.expr.match?(/\sTHROW_EXCEPTION\([^)]+\);/) || insn.expr.match?(/\bvm_pop_frame\(/)
finish_p = true
end
return src, next_pos, finish_p, compile_insns
end
def compile_insn_body(src, insn, pos, next_pos, insn_len, local_stack_p, stack_size, sp_inc, operands)
# Print a body of insn, but with macro expansion.
expand_simple_macros(insn.expr).each_line do |line|
# Expand dynamic macro here
# TODO: support combination of following macros in the same line
case line
when /\A\s+RUBY_VM_CHECK_INTS\(ec\);\s+\z/
if insn.leaf_without_check_ints? # lazily move PC and optionalize mjit_call_p here
src << " if (UNLIKELY(RUBY_VM_INTERRUPTED_ANY(ec))) {\n"
src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width));
src << " rb_threadptr_execute_interrupts(rb_ec_thread_ptr(ec), 0);\n"
src << " if (UNLIKELY(!mjit_call_p)) {\n"
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
src << " goto cancel;\n"
src << " }\n"
src << " }\n"
else
src << to_cstr(line)
end
when /\A\s+JUMP\((?<dest>[^)]+)\);\s+\z/
dest = Regexp.last_match[:dest]
if insn.name == :opt_case_dispatch # special case... TODO: use another macro to avoid checking name
hash_offsets = C.rb_hash_values(operands[0]).uniq
else_offset = cast_offset(operands[1])
base_pos = pos + insn_len
src << " switch (#{dest}) {\n"
hash_offsets.each do |offset|
src << " case #{offset}:\n"
src << " goto label_#{base_pos + offset};\n"
end
src << " case #{else_offset}:\n"
src << " goto label_#{base_pos + else_offset};\n"
src << " }\n"
else
# Before we `goto` next insn, we need to set return values, especially for getinlinecache
insn.rets.reverse_each.with_index do |ret, i|
# TOPN(n) = ...
src << " stack[#{stack_size + sp_inc - (i + 1)}] = #{ret.fetch(:name)};\n"
end
next_pos = pos + insn_len + cast_offset(operands[0]) # workaround: assuming dest == operands[0]. TODO: avoid relying on it
src << " goto label_#{next_pos};\n"
end
when /\A\s+CALL_SIMPLE_METHOD\(\);\s+\z/
# For `opt_xxx`'s fallbacks.
if local_stack_p
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
end
src << " reg_cfp->pc = original_body_iseq + #{pos};\n"
src << " goto cancel;\n"
when /\A(?<prefix>.+\b)INSN_LABEL\((?<name>[^)]+)\)(?<suffix>.+)\z/m
prefix, name, suffix = Regexp.last_match[:prefix], Regexp.last_match[:name], Regexp.last_match[:suffix]
src << "#{prefix}INSN_LABEL(#{name}_#{pos})#{suffix}"
else
if insn.handles_sp?
# If insn.handles_sp? is true, cfp->sp might be changed inside insns (like vm_caller_setup_arg_block)
# and thus we need to use cfp->sp, even when local_stack_p is TRUE. When insn.handles_sp? is true,
# cfp->sp should be available too because _mjit_compile_pc_and_sp.erb sets it.
src << to_cstr(line)
else
# If local_stack_p is TRUE and insn.handles_sp? is false, stack values are only available in local variables
# for stack. So we need to replace those macros if local_stack_p is TRUE here.
case line
when /\bGET_SP\(\)/
# reg_cfp->sp
src << to_cstr(line.sub(/\bGET_SP\(\)/, local_stack_p ? '(stack + stack_size)' : 'GET_SP()'))
when /\bSTACK_ADDR_FROM_TOP\((?<num>[^)]+)\)/
# #define STACK_ADDR_FROM_TOP(n) (GET_SP()-(n))
num = Regexp.last_match[:num]
src << to_cstr(line.sub(/\bSTACK_ADDR_FROM_TOP\(([^)]+)\)/, local_stack_p ? "(stack + (stack_size - (#{num})))" : "STACK_ADDR_FROM_TOP(#{num})"))
when /\bTOPN\((?<num>[^)]+)\)/
# #define TOPN(n) (*(GET_SP()-(n)-1))
num = Regexp.last_match[:num]
src << to_cstr(line.sub(/\bTOPN\(([^)]+)\)/, local_stack_p ? "*(stack + (stack_size - (#{num}) - 1))" : "TOPN(#{num})"))
else
src << to_cstr(line)
end
end
end
end
return next_pos
end
def compile_pc_and_sp(src, insn, stack_size, sp_inc, local_stack_p, next_pos)
# JIT: When an insn is leaf, we don't need to Move pc for a catch table on catch_except_p, #caller_locations,
# and rb_profile_frames. For check_ints, we lazily move PC when we have interruptions.
pc_moved_p = false
unless insn.always_leaf? || insn.leaf_without_check_ints?
src << " reg_cfp->pc = original_body_iseq + #{next_pos};\n" # ADD_PC(INSN_ATTR(width));
pc_moved_p = true
end
# JIT: move sp to use or preserve stack variables
if local_stack_p
# sp motion is optimized away for `handles_sp? #=> false` case.
# Thus sp should be set properly before `goto cancel`.
if insn.handles_sp?
# JIT-only behavior (pushing JIT's local variables to VM's stack):
push_size = -sp_inc + insn.rets.size - insn.pops.size
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{push_size};\n"
push_size.times do |i|
src << " *(reg_cfp->sp + #{i - push_size}) = stack[#{stack_size - push_size + i}];\n"
end
end
else
if insn.handles_sp?
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size - insn.pops.size};\n" # POPN(INSN_ATTR(popn));
else
src << " reg_cfp->sp = vm_base_ptr(reg_cfp) + #{stack_size};\n"
end
end
return pc_moved_p
end
# Print the block to cancel inlined method call. It's supporting only `opt_send_without_block` for now.
def compile_inlined_cancel_handler(src, body, inline_context)
src << "\ncancel:\n"
src << " rb_mjit_recompile_inlining(original_iseq);\n"
# Swap pc/sp set on cancel with original pc/sp.
src << " const VALUE *current_pc = reg_cfp->pc;\n"
src << " VALUE *current_sp = reg_cfp->sp;\n"
src << " reg_cfp->pc = orig_pc;\n"
src << " reg_cfp->sp = orig_sp;\n\n"
# Lazily push the current call frame.
src << " struct rb_calling_info calling;\n"
src << " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n" # assumes `opt_send_without_block`
src << " calling.argc = #{inline_context.orig_argc};\n"
src << " calling.recv = reg_cfp->self;\n"
src << " reg_cfp->self = orig_self;\n"
# fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE
src << " vm_call_iseq_setup_normal(ec, reg_cfp, &calling, (const rb_callable_method_entry_t *)#{inline_context.me}, 0, #{inline_context.param_size}, #{inline_context.local_size});\n\n"
# Start usual cancel from here.
src << " reg_cfp = ec->cfp;\n" # work on the new frame
src << " reg_cfp->pc = current_pc;\n"
src << " reg_cfp->sp = current_sp;\n"
(0...body.stack_max).each do |i| # should be always `status->local_stack_p`
src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n"
end
# We're not just returning Qundef here so that caller's normal cancel handler can
# push back `stack` to `cfp->sp`.
src << " return vm_exec(ec, false);\n"
end
# Print the block to cancel JIT execution.
def compile_cancel_handler(src, body, status)
if status.inlined_iseqs.nil? # the current ISeq is being inlined
compile_inlined_cancel_handler(src, body, status.inline_context)
return
end
src << "\nsend_cancel:\n"
src << " rb_mjit_recompile_send(original_iseq);\n"
src << " goto cancel;\n"
src << "\nivar_cancel:\n"
src << " rb_mjit_recompile_ivar(original_iseq);\n"
src << " goto cancel;\n"
src << "\nexivar_cancel:\n"
src << " rb_mjit_recompile_exivar(original_iseq);\n"
src << " goto cancel;\n"
src << "\nconst_cancel:\n"
src << " rb_mjit_recompile_const(original_iseq);\n"
src << " goto cancel;\n"
src << "\ncancel:\n"
if status.local_stack_p
(0...body.stack_max).each do |i|
src << " *(vm_base_ptr(reg_cfp) + #{i}) = stack[#{i}];\n"
end
end
src << " return Qundef;\n"
end
def precompile_inlinable_child_iseq(src, child_iseq, status, ci, cc, pos)
child_status = C.compile_status.new # not freed for now
child_status.compiled_iseq = status.compiled_iseq
child_status.compiled_id = status.compiled_id
init_compile_status(child_status, child_iseq.body, false) # not freed for now
child_status.inline_context.orig_argc = C.vm_ci_argc(ci)
child_status.inline_context.me = vm_cc_cme(cc).to_i
child_status.inline_context.param_size = child_iseq.body.param.size
child_status.inline_context.local_size = child_iseq.body.local_table_size
if child_iseq.body.ci_size > 0 && child_status.cc_entries_index == -1
return false
end
src << "ALWAYS_INLINE(static VALUE _mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq));\n"
src << "static inline VALUE\n_mjit#{status.compiled_id}_inlined_#{pos}(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, const VALUE orig_self, const rb_iseq_t *original_iseq)\n{\n"
src << " const VALUE *orig_pc = reg_cfp->pc;\n"
src << " VALUE *orig_sp = reg_cfp->sp;\n"
success = compile_body(src, child_iseq, child_status)
src << "\n} /* end of _mjit#{status.compiled_id}_inlined_#{pos} */\n\n"
return success;
end
def precompile_inlinable_iseqs(src, iseq, status)
body = iseq.body
pos = 0
while pos < body.iseq_size
insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
if insn.name == :opt_send_without_block || insn.name == :opt_size # `compile_inlined_cancel_handler` supports only `opt_send_without_block`
cd = C.CALL_DATA.new(body.iseq_encoded[pos + 1])
ci = cd.ci
cc = captured_cc_entries(status)[call_data_index(cd, body)] # use copy to avoid race condition
if (child_iseq = rb_mjit_inlinable_iseq(ci, cc)) != nil
status.inlined_iseqs[pos] = child_iseq.body
if C.mjit_opts.verbose >= 1 # print beforehand because ISeq may be GCed during copy job.
child_location = child_iseq.body.location
$stderr.puts "JIT inline: #{child_location.label}@#{C.rb_iseq_path(child_iseq)}:#{C.rb_iseq_first_lineno(child_iseq)} " \
"=> #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{C.rb_iseq_first_lineno(iseq)}"
end
if !precompile_inlinable_child_iseq(src, child_iseq, status, ci, cc, pos)
return false
end
end
end
pos += insn.len
end
return true
end
def init_compile_status(status, body, compile_root_p)
status.stack_size_for_pos = Fiddle.malloc(Fiddle::SIZEOF_INT * body.iseq_size)
body.iseq_size.times do |i|
status.stack_size_for_pos[i] = C.NOT_COMPILED_STACK_SIZE
end
if compile_root_p
status.inlined_iseqs = Fiddle.malloc(Fiddle::SIZEOF_VOIDP * body.iseq_size)
body.iseq_size.times do |i|
status.inlined_iseqs[i] = nil
end
end
if body.ci_size > 0
status.cc_entries_index = C.mjit_capture_cc_entries(status.compiled_iseq, body)
else
status.cc_entries_index = -1
end
if compile_root_p
status.compile_info = rb_mjit_iseq_compile_info(body)
else
status.compile_info = Fiddle.malloc(C.rb_mjit_compile_info.sizeof)
status.compile_info.disable_ivar_cache = false
status.compile_info.disable_exivar_cache = false
status.compile_info.disable_send_cache = false
status.compile_info.disable_inlining = false
status.compile_info.disable_const_cache = false
end
end
def using_ivar?(body)
pos = 0
while pos < body.iseq_size
insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
case insn.name
when :getinstancevariable, :setinstancevariable
return true
end
pos += insn.len
end
return false
end
# Expand simple macro that doesn't require dynamic C code.
def expand_simple_macros(arg_expr)
arg_expr.dup.tap do |expr|
# For `leave`. We can't proceed next ISeq in the same JIT function.
expr.gsub!(/^(?<indent>\s*)RESTORE_REGS\(\);\n/) do
indent = Regexp.last_match[:indent]
<<-end.gsub(/^ {12}/, '')
#if OPT_CALL_THREADED_CODE
#{indent}rb_ec_thread_ptr(ec)->retval = val;
#{indent}return 0;
#else
#{indent}return val;
#endif
end
end
expr.gsub!(/^(?<indent>\s*)NEXT_INSN\(\);\n/) do
indent = Regexp.last_match[:indent]
<<-end.gsub(/^ {12}/, '')
#{indent}UNREACHABLE_RETURN(Qundef);
end
end
end
end
def to_cstr(expr)
expr.gsub(/^(?!#)/, ' ') # indent everything but preprocessor lines
end
# Interpret unsigned long as signed long (VALUE -> OFFSET)
def cast_offset(offset)
if offset >= 1 << 8 * Fiddle::SIZEOF_VOIDP - 1 # negative
offset -= 1 << 8 * Fiddle::SIZEOF_VOIDP
end
offset
end
def captured_cc_entries(status)
status.compiled_iseq.mjit_unit.cc_entries + status.cc_entries_index
end
def call_data_index(cd, body)
cd - body.call_data
end
def vm_cc_cme(cc)
# TODO: add VM_ASSERT like actual vm_cc_cme
cc.cme_
end
def def_iseq_ptr(method_def)
C.rb_iseq_check(method_def.body.iseq.iseqptr)
end
def rb_mjit_iseq_compile_info(body)
body.mjit_unit.compile_info
end
def ISEQ_IS_SIZE(body)
body.ic_size + body.ivc_size + body.ise_size + body.icvarc_size
end
# Return true if an object of the class may be a special const (immediate).
# It's "maybe" because Integer and Float are not guaranteed to be an immediate.
# If this returns false, rb_class_of could be optimzied to RBASIC_CLASS.
def maybe_special_const?(klass)
[
C.rb_cFalseClass,
C.rb_cNilClass,
C.rb_cTrueClass,
C.rb_cInteger,
C.rb_cSymbol,
C.rb_cFloat,
].include?(klass)
end
def has_valid_method_type?(cc)
vm_cc_cme(cc) != nil
end
def already_compiled?(status, pos)
status.stack_size_for_pos[pos] != C.NOT_COMPILED_STACK_SIZE
end
# Return an iseq pointer if cc has inlinable iseq.
def rb_mjit_inlinable_iseq(ci, cc)
if has_valid_method_type?(cc) &&
C.vm_ci_flag(ci) & C.VM_CALL_TAILCALL == 0 && # inlining only non-tailcall path
vm_cc_cme(cc).def.type == C.VM_METHOD_TYPE_ISEQ &&
C.fastpath_applied_iseq_p(ci, cc, iseq = def_iseq_ptr(vm_cc_cme(cc).def)) &&
inlinable_iseq_p(iseq.body) # CC_SET_FASTPATH in vm_callee_setup_arg
return iseq
end
return nil
end
# Return true if the ISeq can be inlined without pushing a new control frame.
def inlinable_iseq_p(body)
# 1) If catch_except_p, caller frame should be preserved when callee catches an exception.
# Then we need to wrap `vm_exec()` but then we can't inline the call inside it.
#
# 2) If `body->catch_except_p` is false and `handles_sp?` of an insn is false,
# sp is not moved as we assume `status->local_stack_p = !body->catch_except_p`.
#
# 3) If `body->catch_except_p` is false and `always_leaf?` of an insn is true,
# pc is not moved.
if body.catch_except_p
return false
end
pos = 0
while pos < body.iseq_size
insn = INSNS.fetch(C.rb_vm_insn_decode(body.iseq_encoded[pos]))
# All insns in the ISeq except `leave` (to be overridden in the inlined code)
# should meet following strong assumptions:
# * Do not require `cfp->sp` motion
# * Do not move `cfp->pc`
# * Do not read any `cfp->pc`
if insn.name == :invokebuiltin || insn.name == :opt_invokebuiltin_delegate || insn.name == :opt_invokebuiltin_delegate_leave
# builtin insn's inlinability is handled by `Primitive.attr! 'inline'` per iseq
if !body.builtin_inline_p
return false;
end
elsif insn.name != :leave && C.insn_may_depend_on_sp_or_pc(insn.bin, body.iseq_encoded + (pos + 1))
return false
end
# At this moment, `cfp->ep` in an inlined method is not working.
case insn.name
when :getlocal,
:getlocal_WC_0,
:getlocal_WC_1,
:setlocal,
:setlocal_WC_0,
:setlocal_WC_1,
:getblockparam,
:getblockparamproxy,
:setblockparam
return false
end
pos += insn.len
end
return true
end
# CPointer::Struct could be nil on field reference, and this is a helper to
# handle that case while using CPointer::Struct#to_s in most cases.
# @param struct [RubyVM::MJIT::CPointer::Struct]
def to_addr(struct)
struct&.to_s || 'NULL'
end
def parent_shape_id(shape_id)
return shape_id if shape_id == C.INVALID_SHAPE_ID
parent_id = C.rb_shape_get_shape_by_id(shape_id).parent_id
parent = C.rb_shape_get_shape_by_id(parent_id)
if parent.type == C.SHAPE_CAPACITY_CHANGE
parent.parent_id
else
parent_id
end
end
end
share/ruby/ruby_vm/mjit/instruction.rb 0000644 00000204503 15173547340 0014200 0 ustar 00 module RubyVM::MJIT # :nodoc: all
Instruction = Struct.new(
:name,
:bin,
:len,
:expr,
:declarations,
:preamble,
:opes,
:pops,
:rets,
:always_leaf?,
:leaf_without_check_ints?,
:handles_sp?,
)
INSNS = {
0 => Instruction.new(
name: :nop,
bin: 0, # BIN(nop)
len: 1, # insn_len
expr: <<-EXPR,
{
/* none */
}
EXPR
declarations: [],
preamble: [],
opes: [],
pops: [],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
1 => Instruction.new(
name: :getlocal,
bin: 1, # BIN(getlocal)
len: 3, # insn_len
expr: <<-EXPR,
{
val = *(vm_get_ep(GET_EP(), level) - idx);
RB_DEBUG_COUNTER_INC(lvar_get);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
2 => Instruction.new(
name: :setlocal,
bin: 2, # BIN(setlocal)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
RB_DEBUG_COUNTER_INC(lvar_set);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
3 => Instruction.new(
name: :getblockparam,
bin: 3, # BIN(getblockparam)
len: 3, # insn_len
expr: <<-EXPR,
{
const VALUE *ep = vm_get_ep(GET_EP(), level);
VM_ASSERT(VM_ENV_LOCAL_P(ep));
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
val = rb_vm_bh_to_procval(ec, VM_ENV_BLOCK_HANDLER(ep));
vm_env_write(ep, -(int)idx, val);
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
}
else {
val = *(ep - idx);
RB_DEBUG_COUNTER_INC(lvar_get);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
4 => Instruction.new(
name: :setblockparam,
bin: 4, # BIN(setblockparam)
len: 3, # insn_len
expr: <<-EXPR,
{
const VALUE *ep = vm_get_ep(GET_EP(), level);
VM_ASSERT(VM_ENV_LOCAL_P(ep));
vm_env_write(ep, -(int)idx, val);
RB_DEBUG_COUNTER_INC(lvar_set);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
5 => Instruction.new(
name: :getblockparamproxy,
bin: 5, # BIN(getblockparamproxy)
len: 3, # insn_len
expr: <<-EXPR,
{
const VALUE *ep = vm_get_ep(GET_EP(), level);
VM_ASSERT(VM_ENV_LOCAL_P(ep));
if (!VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)) {
VALUE block_handler = VM_ENV_BLOCK_HANDLER(ep);
if (block_handler) {
switch (vm_block_handler_type(block_handler)) {
case block_handler_type_iseq:
case block_handler_type_ifunc:
val = rb_block_param_proxy;
break;
case block_handler_type_symbol:
val = rb_sym_to_proc(VM_BH_TO_SYMBOL(block_handler));
goto INSN_LABEL(set);
case block_handler_type_proc:
val = VM_BH_TO_PROC(block_handler);
goto INSN_LABEL(set);
default:
VM_UNREACHABLE(getblockparamproxy);
}
}
else {
val = Qnil;
INSN_LABEL(set):
vm_env_write(ep, -(int)idx, val);
VM_ENV_FLAGS_SET(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM);
}
}
else {
val = *(ep - idx);
RB_DEBUG_COUNTER_INC(lvar_get);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
6 => Instruction.new(
name: :getspecial,
bin: 6, # BIN(getspecial)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_getspecial(ec, GET_LEP(), key, type);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) key, type"],
preamble: [],
opes: [{:decl=>"rb_num_t key", :type=>"rb_num_t", :name=>"key"}, {:decl=>"rb_num_t type", :type=>"rb_num_t", :name=>"type"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
7 => Instruction.new(
name: :setspecial,
bin: 7, # BIN(setspecial)
len: 2, # insn_len
expr: <<-EXPR,
{
lep_svar_set(ec, GET_LEP(), key, obj);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) obj", "MAYBE_UNUSED(rb_num_t) key"],
preamble: [],
opes: [{:decl=>"rb_num_t key", :type=>"rb_num_t", :name=>"key"}],
pops: [{:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
8 => Instruction.new(
name: :getinstancevariable,
bin: 8, # BIN(getinstancevariable)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_getinstancevariable(GET_ISEQ(), GET_SELF(), id, ic);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(IVC) ic", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"IVC ic", :type=>"IVC", :name=>"ic"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
9 => Instruction.new(
name: :setinstancevariable,
bin: 9, # BIN(setinstancevariable)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_setinstancevariable(GET_ISEQ(), GET_SELF(), id, val, ic);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(IVC) ic", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"IVC ic", :type=>"IVC", :name=>"ic"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
10 => Instruction.new(
name: :getclassvariable,
bin: 10, # BIN(getclassvariable)
len: 3, # insn_len
expr: <<-EXPR,
{
rb_control_frame_t *cfp = GET_CFP();
val = vm_getclassvariable(GET_ISEQ(), cfp, id, ic);
}
EXPR
declarations: ["MAYBE_UNUSED(ICVARC) ic", "MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ICVARC ic", :type=>"ICVARC", :name=>"ic"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
11 => Instruction.new(
name: :setclassvariable,
bin: 11, # BIN(setclassvariable)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_ensure_not_refinement_module(GET_SELF());
vm_setclassvariable(GET_ISEQ(), GET_CFP(), id, val, ic);
}
EXPR
declarations: ["MAYBE_UNUSED(ICVARC) ic", "MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ICVARC ic", :type=>"ICVARC", :name=>"ic"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
12 => Instruction.new(
name: :opt_getconstant_path,
bin: 12, # BIN(opt_getconstant_path)
len: 2, # insn_len
expr: <<-EXPR,
{
const ID *segments = ic->segments;
struct iseq_inline_constant_cache_entry *ice = ic->entry;
if (ice && vm_ic_hit_p(ice, GET_EP())) {
val = ice->value;
VM_ASSERT(val == vm_get_ev_const_chain(ec, segments));
} else {
ruby_vm_constant_cache_misses++;
val = vm_get_ev_const_chain(ec, segments);
vm_ic_track_const_chain(GET_CFP(), ic, segments);
// Because leaf=false, we need to undo the PC increment to get the address to this instruction
// INSN_ATTR(width) == 2
vm_ic_update(GET_ISEQ(), ic, val, GET_EP(), GET_PC() - 2);
}
}
EXPR
declarations: ["MAYBE_UNUSED(IC) ic", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"IC ic", :type=>"IC", :name=>"ic"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
13 => Instruction.new(
name: :getconstant,
bin: 13, # BIN(getconstant)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_get_ev_const(ec, klass, id, allow_nil == Qtrue, 0);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(VALUE) allow_nil, klass, val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}],
pops: [{:decl=>"VALUE klass", :type=>"VALUE", :name=>"klass"}, {:decl=>"VALUE allow_nil", :type=>"VALUE", :name=>"allow_nil"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
14 => Instruction.new(
name: :setconstant,
bin: 14, # BIN(setconstant)
len: 2, # insn_len
expr: <<-EXPR,
{
vm_check_if_namespace(cbase);
vm_ensure_not_refinement_module(GET_SELF());
rb_const_set(cbase, id, val);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(VALUE) cbase, val"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}, {:decl=>"VALUE cbase", :type=>"VALUE", :name=>"cbase"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
15 => Instruction.new(
name: :getglobal,
bin: 15, # BIN(getglobal)
len: 2, # insn_len
expr: <<-EXPR,
{
val = rb_gvar_get(gid);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) gid", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID gid", :type=>"ID", :name=>"gid"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
16 => Instruction.new(
name: :setglobal,
bin: 16, # BIN(setglobal)
len: 2, # insn_len
expr: <<-EXPR,
{
rb_gvar_set(gid, val);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) gid", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ID gid", :type=>"ID", :name=>"gid"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
17 => Instruction.new(
name: :putnil,
bin: 17, # BIN(putnil)
len: 1, # insn_len
expr: <<-EXPR,
{
val = Qnil;
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
18 => Instruction.new(
name: :putself,
bin: 18, # BIN(putself)
len: 1, # insn_len
expr: <<-EXPR,
{
val = GET_SELF();
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
19 => Instruction.new(
name: :putobject,
bin: 19, # BIN(putobject)
len: 2, # insn_len
expr: <<-EXPR,
{
/* */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
20 => Instruction.new(
name: :putspecialobject,
bin: 20, # BIN(putspecialobject)
len: 2, # insn_len
expr: <<-EXPR,
{
enum vm_special_object_type type;
type = (enum vm_special_object_type)value_type;
val = vm_get_special_object(GET_EP(), type);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) value_type"],
preamble: [],
opes: [{:decl=>"rb_num_t value_type", :type=>"rb_num_t", :name=>"value_type"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
21 => Instruction.new(
name: :putstring,
bin: 21, # BIN(putstring)
len: 2, # insn_len
expr: <<-EXPR,
{
val = rb_ec_str_resurrect(ec, str);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) str, val"],
preamble: [],
opes: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
22 => Instruction.new(
name: :concatstrings,
bin: 22, # BIN(concatstrings)
len: 2, # insn_len
expr: <<-EXPR,
{
val = rb_str_concat_literals(num, STACK_ADDR_FROM_TOP(num));
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
23 => Instruction.new(
name: :anytostring,
bin: 23, # BIN(anytostring)
len: 1, # insn_len
expr: <<-EXPR,
{
val = rb_obj_as_string_result(str, val);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) str, val"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}, {:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
24 => Instruction.new(
name: :toregexp,
bin: 24, # BIN(toregexp)
len: 3, # insn_len
expr: <<-EXPR,
{
const VALUE ary = rb_ary_tmp_new_from_values(0, cnt, STACK_ADDR_FROM_TOP(cnt));
val = rb_reg_new_ary(ary, (int)opt);
rb_ary_clear(ary);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) cnt, opt"],
preamble: [],
opes: [{:decl=>"rb_num_t opt", :type=>"rb_num_t", :name=>"opt"}, {:decl=>"rb_num_t cnt", :type=>"rb_num_t", :name=>"cnt"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
25 => Instruction.new(
name: :intern,
bin: 25, # BIN(intern)
len: 1, # insn_len
expr: <<-EXPR,
{
sym = rb_str_intern(str);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) str, sym"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}],
rets: [{:decl=>"VALUE sym", :type=>"VALUE", :name=>"sym"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
26 => Instruction.new(
name: :newarray,
bin: 26, # BIN(newarray)
len: 2, # insn_len
expr: <<-EXPR,
{
val = rb_ec_ary_new_from_values(ec, num, STACK_ADDR_FROM_TOP(num));
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
27 => Instruction.new(
name: :newarraykwsplat,
bin: 27, # BIN(newarraykwsplat)
len: 2, # insn_len
expr: <<-EXPR,
{
if (RHASH_EMPTY_P(*STACK_ADDR_FROM_TOP(1))) {
val = rb_ary_new4(num-1, STACK_ADDR_FROM_TOP(num));
}
else {
val = rb_ary_new4(num, STACK_ADDR_FROM_TOP(num));
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
28 => Instruction.new(
name: :duparray,
bin: 28, # BIN(duparray)
len: 2, # insn_len
expr: <<-EXPR,
{
RUBY_DTRACE_CREATE_HOOK(ARRAY, RARRAY_LEN(ary));
val = rb_ary_resurrect(ary);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ary, val"],
preamble: [],
opes: [{:decl=>"VALUE ary", :type=>"VALUE", :name=>"ary"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
29 => Instruction.new(
name: :duphash,
bin: 29, # BIN(duphash)
len: 2, # insn_len
expr: <<-EXPR,
{
RUBY_DTRACE_CREATE_HOOK(HASH, RHASH_SIZE(hash) << 1);
val = rb_hash_resurrect(hash);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) hash, val"],
preamble: [],
opes: [{:decl=>"VALUE hash", :type=>"VALUE", :name=>"hash"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
30 => Instruction.new(
name: :expandarray,
bin: 30, # BIN(expandarray)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_expandarray(GET_SP(), ary, num, (int)flag);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ary", "MAYBE_UNUSED(rb_num_t) flag, num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}, {:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
pops: [{:decl=>"VALUE ary", :type=>"VALUE", :name=>"ary"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
31 => Instruction.new(
name: :concatarray,
bin: 31, # BIN(concatarray)
len: 1, # insn_len
expr: <<-EXPR,
{
ary = vm_concat_array(ary1, ary2);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ary, ary1, ary2"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE ary1", :type=>"VALUE", :name=>"ary1"}, {:decl=>"VALUE ary2", :type=>"VALUE", :name=>"ary2"}],
rets: [{:decl=>"VALUE ary", :type=>"VALUE", :name=>"ary"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
32 => Instruction.new(
name: :splatarray,
bin: 32, # BIN(splatarray)
len: 2, # insn_len
expr: <<-EXPR,
{
obj = vm_splat_array(flag, ary);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ary, flag, obj"],
preamble: [],
opes: [{:decl=>"VALUE flag", :type=>"VALUE", :name=>"flag"}],
pops: [{:decl=>"VALUE ary", :type=>"VALUE", :name=>"ary"}],
rets: [{:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
33 => Instruction.new(
name: :newhash,
bin: 33, # BIN(newhash)
len: 2, # insn_len
expr: <<-EXPR,
{
RUBY_DTRACE_CREATE_HOOK(HASH, num);
if (num) {
val = rb_hash_new_with_size(num / 2);
rb_hash_bulk_insert(num, STACK_ADDR_FROM_TOP(num), val);
}
else {
val = rb_hash_new();
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
34 => Instruction.new(
name: :newrange,
bin: 34, # BIN(newrange)
len: 2, # insn_len
expr: <<-EXPR,
{
val = rb_range_new(low, high, (int)flag);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) high, low, val", "MAYBE_UNUSED(rb_num_t) flag"],
preamble: [],
opes: [{:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
pops: [{:decl=>"VALUE low", :type=>"VALUE", :name=>"low"}, {:decl=>"VALUE high", :type=>"VALUE", :name=>"high"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
35 => Instruction.new(
name: :pop,
bin: 35, # BIN(pop)
len: 1, # insn_len
expr: <<-EXPR,
{
(void)val;
/* none */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
36 => Instruction.new(
name: :dup,
bin: 36, # BIN(dup)
len: 1, # insn_len
expr: <<-EXPR,
{
val1 = val2 = val;
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val, val1, val2"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [{:decl=>"VALUE val1", :type=>"VALUE", :name=>"val1"}, {:decl=>"VALUE val2", :type=>"VALUE", :name=>"val2"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
37 => Instruction.new(
name: :dupn,
bin: 37, # BIN(dupn)
len: 2, # insn_len
expr: <<-EXPR,
{
void *dst = GET_SP();
void *src = STACK_ADDR_FROM_TOP(n);
MEMCPY(dst, src, VALUE, n);
}
EXPR
declarations: ["MAYBE_UNUSED(rb_num_t) n"],
preamble: [],
opes: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
pops: [],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
38 => Instruction.new(
name: :swap,
bin: 38, # BIN(swap)
len: 1, # insn_len
expr: <<-EXPR,
{
/* none */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) obj, val"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}, {:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
39 => Instruction.new(
name: :opt_reverse,
bin: 39, # BIN(opt_reverse)
len: 2, # insn_len
expr: <<-EXPR,
{
rb_num_t i;
VALUE *sp = STACK_ADDR_FROM_TOP(n);
for (i=0; i<n/2; i++) {
VALUE v0 = sp[i];
VALUE v1 = TOPN(i);
sp[i] = v1;
TOPN(i) = v0;
}
}
EXPR
declarations: ["MAYBE_UNUSED(rb_num_t) n"],
preamble: [],
opes: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
pops: [],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
40 => Instruction.new(
name: :topn,
bin: 40, # BIN(topn)
len: 2, # insn_len
expr: <<-EXPR,
{
val = TOPN(n);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) n"],
preamble: [],
opes: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
41 => Instruction.new(
name: :setn,
bin: 41, # BIN(setn)
len: 2, # insn_len
expr: <<-EXPR,
{
TOPN(n) = val;
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) n"],
preamble: [],
opes: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
42 => Instruction.new(
name: :adjuststack,
bin: 42, # BIN(adjuststack)
len: 2, # insn_len
expr: <<-EXPR,
{
/* none */
}
EXPR
declarations: ["MAYBE_UNUSED(rb_num_t) n"],
preamble: [],
opes: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
pops: [],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
43 => Instruction.new(
name: :defined,
bin: 43, # BIN(defined)
len: 4, # insn_len
expr: <<-EXPR,
{
val = Qnil;
if (vm_defined(ec, GET_CFP(), op_type, obj, v)) {
val = pushval;
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) obj, pushval, v, val", "MAYBE_UNUSED(rb_num_t) op_type"],
preamble: [],
opes: [{:decl=>"rb_num_t op_type", :type=>"rb_num_t", :name=>"op_type"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}, {:decl=>"VALUE pushval", :type=>"VALUE", :name=>"pushval"}],
pops: [{:decl=>"VALUE v", :type=>"VALUE", :name=>"v"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
44 => Instruction.new(
name: :checkmatch,
bin: 44, # BIN(checkmatch)
len: 2, # insn_len
expr: <<-EXPR,
{
result = vm_check_match(ec, target, pattern, flag);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) pattern, result, target", "MAYBE_UNUSED(rb_num_t) flag"],
preamble: [],
opes: [{:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
pops: [{:decl=>"VALUE target", :type=>"VALUE", :name=>"target"}, {:decl=>"VALUE pattern", :type=>"VALUE", :name=>"pattern"}],
rets: [{:decl=>"VALUE result", :type=>"VALUE", :name=>"result"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
45 => Instruction.new(
name: :checkkeyword,
bin: 45, # BIN(checkkeyword)
len: 3, # insn_len
expr: <<-EXPR,
{
ret = vm_check_keyword(kw_bits_index, keyword_index, GET_EP());
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ret", "MAYBE_UNUSED(lindex_t) keyword_index, kw_bits_index"],
preamble: [],
opes: [{:decl=>"lindex_t kw_bits_index", :type=>"lindex_t", :name=>"kw_bits_index"}, {:decl=>"lindex_t keyword_index", :type=>"lindex_t", :name=>"keyword_index"}],
pops: [],
rets: [{:decl=>"VALUE ret", :type=>"VALUE", :name=>"ret"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
46 => Instruction.new(
name: :checktype,
bin: 46, # BIN(checktype)
len: 2, # insn_len
expr: <<-EXPR,
{
ret = RBOOL(TYPE(val) == (int)type);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) ret, val", "MAYBE_UNUSED(rb_num_t) type"],
preamble: [],
opes: [{:decl=>"rb_num_t type", :type=>"rb_num_t", :name=>"type"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [{:decl=>"VALUE ret", :type=>"VALUE", :name=>"ret"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
47 => Instruction.new(
name: :defineclass,
bin: 47, # BIN(defineclass)
len: 4, # insn_len
expr: <<-EXPR,
{
VALUE klass = vm_find_or_create_class_by_id(id, flags, cbase, super);
rb_iseq_check(class_iseq);
/* enter scope */
vm_push_frame(ec, class_iseq, VM_FRAME_MAGIC_CLASS | VM_ENV_FLAG_LOCAL, klass,
GET_BLOCK_HANDLER(),
(VALUE)vm_cref_push(ec, klass, NULL, FALSE, FALSE),
ISEQ_BODY(class_iseq)->iseq_encoded, GET_SP(),
ISEQ_BODY(class_iseq)->local_table_size,
ISEQ_BODY(class_iseq)->stack_max);
RESTORE_REGS();
NEXT_INSN();
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(ISEQ) class_iseq", "MAYBE_UNUSED(VALUE) cbase, super, val", "MAYBE_UNUSED(rb_num_t) flags"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ISEQ class_iseq", :type=>"ISEQ", :name=>"class_iseq"}, {:decl=>"rb_num_t flags", :type=>"rb_num_t", :name=>"flags"}],
pops: [{:decl=>"VALUE cbase", :type=>"VALUE", :name=>"cbase"}, {:decl=>"VALUE super", :type=>"VALUE", :name=>"super"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
48 => Instruction.new(
name: :definemethod,
bin: 48, # BIN(definemethod)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_define_method(ec, Qnil, id, (VALUE)iseq, FALSE);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(ISEQ) iseq"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}],
pops: [],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
49 => Instruction.new(
name: :definesmethod,
bin: 49, # BIN(definesmethod)
len: 3, # insn_len
expr: <<-EXPR,
{
vm_define_method(ec, obj, id, (VALUE)iseq, TRUE);
}
EXPR
declarations: ["MAYBE_UNUSED(ID) id", "MAYBE_UNUSED(ISEQ) iseq", "MAYBE_UNUSED(VALUE) obj"],
preamble: [],
opes: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}],
pops: [{:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
50 => Instruction.new(
name: :send,
bin: 50, # BIN(send)
len: 3, # insn_len
expr: <<-EXPR,
{
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, false);
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
if (val == Qundef) {
RESTORE_REGS();
NEXT_INSN();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(ISEQ) blockiseq", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}, {:decl=>"ISEQ blockiseq", :type=>"ISEQ", :name=>"blockiseq"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
51 => Instruction.new(
name: :opt_send_without_block,
bin: 51, # BIN(opt_send_without_block)
len: 2, # insn_len
expr: <<-EXPR,
{
VALUE bh = VM_BLOCK_HANDLER_NONE;
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_method);
if (val == Qundef) {
RESTORE_REGS();
NEXT_INSN();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
52 => Instruction.new(
name: :objtostring,
bin: 52, # BIN(objtostring)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_objtostring(GET_ISEQ(), recv, cd);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
53 => Instruction.new(
name: :opt_str_freeze,
bin: 53, # BIN(opt_str_freeze)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_opt_str_freeze(str, BOP_FREEZE, idFreeze);
if (val == Qundef) {
PUSH(rb_str_resurrect(str));
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) str, val"],
preamble: [],
opes: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
54 => Instruction.new(
name: :opt_nil_p,
bin: 54, # BIN(opt_nil_p)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_nil_p(GET_ISEQ(), cd, recv);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
55 => Instruction.new(
name: :opt_str_uminus,
bin: 55, # BIN(opt_str_uminus)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_opt_str_freeze(str, BOP_UMINUS, idUMinus);
if (val == Qundef) {
PUSH(rb_str_resurrect(str));
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) str, val"],
preamble: [],
opes: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
56 => Instruction.new(
name: :opt_newarray_max,
bin: 56, # BIN(opt_newarray_max)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_newarray_max(ec, num, STACK_ADDR_FROM_TOP(num));
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
57 => Instruction.new(
name: :opt_newarray_min,
bin: 57, # BIN(opt_newarray_min)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_newarray_min(ec, num, STACK_ADDR_FROM_TOP(num));
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) num"],
preamble: [],
opes: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
58 => Instruction.new(
name: :invokesuper,
bin: 58, # BIN(invokesuper)
len: 3, # insn_len
expr: <<-EXPR,
{
VALUE bh = vm_caller_setup_arg_block(ec, GET_CFP(), cd->ci, blockiseq, true);
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_super);
if (val == Qundef) {
RESTORE_REGS();
NEXT_INSN();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(ISEQ) blockiseq", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}, {:decl=>"ISEQ blockiseq", :type=>"ISEQ", :name=>"blockiseq"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
59 => Instruction.new(
name: :invokeblock,
bin: 59, # BIN(invokeblock)
len: 2, # insn_len
expr: <<-EXPR,
{
VALUE bh = VM_BLOCK_HANDLER_NONE;
val = vm_sendish(ec, GET_CFP(), cd, bh, mexp_search_invokeblock);
if (val == Qundef) {
RESTORE_REGS();
NEXT_INSN();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
60 => Instruction.new(
name: :leave,
bin: 60, # BIN(leave)
len: 1, # insn_len
expr: <<-EXPR,
{
if (OPT_CHECKED_RUN) {
const VALUE *const bp = vm_base_ptr(GET_CFP());
if (GET_SP() != bp) {
vm_stack_consistency_error(ec, GET_CFP(), bp);
}
}
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
#if OPT_CALL_THREADED_CODE
rb_ec_thread_ptr(ec)->retval = val;
return 0;
#else
return val;
#endif
}
else {
RESTORE_REGS();
}
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
61 => Instruction.new(
name: :throw,
bin: 61, # BIN(throw)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_throw(ec, GET_CFP(), throw_state, throwobj);
THROW_EXCEPTION(val);
/* unreachable */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) throwobj, val", "MAYBE_UNUSED(rb_num_t) throw_state"],
preamble: [],
opes: [{:decl=>"rb_num_t throw_state", :type=>"rb_num_t", :name=>"throw_state"}],
pops: [{:decl=>"VALUE throwobj", :type=>"VALUE", :name=>"throwobj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
62 => Instruction.new(
name: :jump,
bin: 62, # BIN(jump)
len: 2, # insn_len
expr: <<-EXPR,
{
RUBY_VM_CHECK_INTS(ec);
JUMP(dst);
}
EXPR
declarations: ["MAYBE_UNUSED(OFFSET) dst"],
preamble: [],
opes: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
pops: [],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: true,
handles_sp?: false,
),
63 => Instruction.new(
name: :branchif,
bin: 63, # BIN(branchif)
len: 2, # insn_len
expr: <<-EXPR,
{
if (RTEST(val)) {
RUBY_VM_CHECK_INTS(ec);
JUMP(dst);
}
}
EXPR
declarations: ["MAYBE_UNUSED(OFFSET) dst", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: true,
handles_sp?: false,
),
64 => Instruction.new(
name: :branchunless,
bin: 64, # BIN(branchunless)
len: 2, # insn_len
expr: <<-EXPR,
{
if (!RTEST(val)) {
RUBY_VM_CHECK_INTS(ec);
JUMP(dst);
}
}
EXPR
declarations: ["MAYBE_UNUSED(OFFSET) dst", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: true,
handles_sp?: false,
),
65 => Instruction.new(
name: :branchnil,
bin: 65, # BIN(branchnil)
len: 2, # insn_len
expr: <<-EXPR,
{
if (NIL_P(val)) {
RUBY_VM_CHECK_INTS(ec);
JUMP(dst);
}
}
EXPR
declarations: ["MAYBE_UNUSED(OFFSET) dst", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: false,
leaf_without_check_ints?: true,
handles_sp?: false,
),
66 => Instruction.new(
name: :once,
bin: 66, # BIN(once)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_once_dispatch(ec, iseq, ise);
}
EXPR
declarations: ["MAYBE_UNUSED(ISE) ise", "MAYBE_UNUSED(ISEQ) iseq", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}, {:decl=>"ISE ise", :type=>"ISE", :name=>"ise"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: true,
),
67 => Instruction.new(
name: :opt_case_dispatch,
bin: 67, # BIN(opt_case_dispatch)
len: 3, # insn_len
expr: <<-EXPR,
{
OFFSET dst = vm_case_dispatch(hash, else_offset, key);
if (dst) {
JUMP(dst);
}
}
EXPR
declarations: ["MAYBE_UNUSED(CDHASH) hash", "MAYBE_UNUSED(OFFSET) else_offset", "MAYBE_UNUSED(VALUE) key"],
preamble: [],
opes: [{:decl=>"CDHASH hash", :type=>"CDHASH", :name=>"hash"}, {:decl=>"OFFSET else_offset", :type=>"OFFSET", :name=>"else_offset"}],
pops: [{:decl=>"VALUE key", :type=>"VALUE", :name=>"key"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
68 => Instruction.new(
name: :opt_plus,
bin: 68, # BIN(opt_plus)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_plus(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
69 => Instruction.new(
name: :opt_minus,
bin: 69, # BIN(opt_minus)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_minus(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
70 => Instruction.new(
name: :opt_mult,
bin: 70, # BIN(opt_mult)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_mult(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
71 => Instruction.new(
name: :opt_div,
bin: 71, # BIN(opt_div)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_div(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
72 => Instruction.new(
name: :opt_mod,
bin: 72, # BIN(opt_mod)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_mod(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
73 => Instruction.new(
name: :opt_eq,
bin: 73, # BIN(opt_eq)
len: 2, # insn_len
expr: <<-EXPR,
{
val = opt_equality(GET_ISEQ(), recv, obj, cd);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
74 => Instruction.new(
name: :opt_neq,
bin: 74, # BIN(opt_neq)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_opt_neq(GET_ISEQ(), cd, cd_eq, recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd, cd_eq", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd_eq", :type=>"CALL_DATA", :name=>"cd_eq"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
75 => Instruction.new(
name: :opt_lt,
bin: 75, # BIN(opt_lt)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_lt(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
76 => Instruction.new(
name: :opt_le,
bin: 76, # BIN(opt_le)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_le(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
77 => Instruction.new(
name: :opt_gt,
bin: 77, # BIN(opt_gt)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_gt(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
78 => Instruction.new(
name: :opt_ge,
bin: 78, # BIN(opt_ge)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_ge(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
79 => Instruction.new(
name: :opt_ltlt,
bin: 79, # BIN(opt_ltlt)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_ltlt(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
80 => Instruction.new(
name: :opt_and,
bin: 80, # BIN(opt_and)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_and(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
81 => Instruction.new(
name: :opt_or,
bin: 81, # BIN(opt_or)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_or(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
82 => Instruction.new(
name: :opt_aref,
bin: 82, # BIN(opt_aref)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_aref(recv, obj);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
83 => Instruction.new(
name: :opt_aset,
bin: 83, # BIN(opt_aset)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_aset(recv, obj, set);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj, recv, set, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE obj", :type=>"VALUE", :name=>"obj"}, {:decl=>"VALUE set", :type=>"VALUE", :name=>"set"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
84 => Instruction.new(
name: :opt_aset_with,
bin: 84, # BIN(opt_aset_with)
len: 3, # insn_len
expr: <<-EXPR,
{
VALUE tmp = vm_opt_aset_with(recv, key, val);
if (tmp != Qundef) {
val = tmp;
}
else {
#ifndef MJIT_HEADER
TOPN(0) = rb_str_resurrect(key);
PUSH(val);
#endif
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) key, recv, val"],
preamble: [],
opes: [{:decl=>"VALUE key", :type=>"VALUE", :name=>"key"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}, {:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
85 => Instruction.new(
name: :opt_aref_with,
bin: 85, # BIN(opt_aref_with)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_opt_aref_with(recv, key);
if (val == Qundef) {
#ifndef MJIT_HEADER
PUSH(rb_str_resurrect(key));
#endif
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) key, recv, val"],
preamble: [],
opes: [{:decl=>"VALUE key", :type=>"VALUE", :name=>"key"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
86 => Instruction.new(
name: :opt_length,
bin: 86, # BIN(opt_length)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_length(recv, BOP_LENGTH);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
87 => Instruction.new(
name: :opt_size,
bin: 87, # BIN(opt_size)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_length(recv, BOP_SIZE);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
88 => Instruction.new(
name: :opt_empty_p,
bin: 88, # BIN(opt_empty_p)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_empty_p(recv);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
89 => Instruction.new(
name: :opt_succ,
bin: 89, # BIN(opt_succ)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_succ(recv);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
90 => Instruction.new(
name: :opt_not,
bin: 90, # BIN(opt_not)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_not(GET_ISEQ(), cd, recv);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) recv, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE recv", :type=>"VALUE", :name=>"recv"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
91 => Instruction.new(
name: :opt_regexpmatch2,
bin: 91, # BIN(opt_regexpmatch2)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_opt_regexpmatch2(obj2, obj1);
if (val == Qundef) {
CALL_SIMPLE_METHOD();
}
}
EXPR
declarations: ["MAYBE_UNUSED(CALL_DATA) cd", "MAYBE_UNUSED(VALUE) obj1, obj2, val"],
preamble: [],
opes: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
pops: [{:decl=>"VALUE obj2", :type=>"VALUE", :name=>"obj2"}, {:decl=>"VALUE obj1", :type=>"VALUE", :name=>"obj1"}],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
92 => Instruction.new(
name: :invokebuiltin,
bin: 92, # BIN(invokebuiltin)
len: 2, # insn_len
expr: <<-EXPR,
{
val = vm_invoke_builtin(ec, reg_cfp, bf, STACK_ADDR_FROM_TOP(bf->argc));
}
EXPR
declarations: ["MAYBE_UNUSED(RB_BUILTIN) bf", "MAYBE_UNUSED(VALUE) val"],
preamble: [],
opes: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
93 => Instruction.new(
name: :opt_invokebuiltin_delegate,
bin: 93, # BIN(opt_invokebuiltin_delegate)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
}
EXPR
declarations: ["MAYBE_UNUSED(RB_BUILTIN) bf", "MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) index"],
preamble: [],
opes: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}, {:decl=>"rb_num_t index", :type=>"rb_num_t", :name=>"index"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
94 => Instruction.new(
name: :opt_invokebuiltin_delegate_leave,
bin: 94, # BIN(opt_invokebuiltin_delegate_leave)
len: 3, # insn_len
expr: <<-EXPR,
{
val = vm_invoke_builtin_delegate(ec, reg_cfp, bf, (unsigned int)index);
/* leave fastpath */
/* TracePoint/return fallbacks this insn to opt_invokebuiltin_delegate */
if (vm_pop_frame(ec, GET_CFP(), GET_EP())) {
#if OPT_CALL_THREADED_CODE
rb_ec_thread_ptr(ec)->retval = val;
return 0;
#else
return val;
#endif
}
else {
RESTORE_REGS();
}
}
EXPR
declarations: ["MAYBE_UNUSED(RB_BUILTIN) bf", "MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(rb_num_t) index"],
preamble: [],
opes: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}, {:decl=>"rb_num_t index", :type=>"rb_num_t", :name=>"index"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: false,
leaf_without_check_ints?: false,
handles_sp?: false,
),
95 => Instruction.new(
name: :getlocal_WC_0,
bin: 95, # BIN(getlocal_WC_0)
len: 2, # insn_len
expr: <<-EXPR,
{
val = *(vm_get_ep(GET_EP(), level) - idx);
RB_DEBUG_COUNTER_INC(lvar_get);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [" const rb_num_t level = 0;"],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
96 => Instruction.new(
name: :getlocal_WC_1,
bin: 96, # BIN(getlocal_WC_1)
len: 2, # insn_len
expr: <<-EXPR,
{
val = *(vm_get_ep(GET_EP(), level) - idx);
RB_DEBUG_COUNTER_INC(lvar_get);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_get_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [" const rb_num_t level = 1;"],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
97 => Instruction.new(
name: :setlocal_WC_0,
bin: 97, # BIN(setlocal_WC_0)
len: 2, # insn_len
expr: <<-EXPR,
{
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
RB_DEBUG_COUNTER_INC(lvar_set);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [" const rb_num_t level = 0;"],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
98 => Instruction.new(
name: :setlocal_WC_1,
bin: 98, # BIN(setlocal_WC_1)
len: 2, # insn_len
expr: <<-EXPR,
{
vm_env_write(vm_get_ep(GET_EP(), level), -(int)idx, val);
RB_DEBUG_COUNTER_INC(lvar_set);
(void)RB_DEBUG_COUNTER_INC_IF(lvar_set_dynamic, level > 0);
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val", "MAYBE_UNUSED(lindex_t) idx", "MAYBE_UNUSED(rb_num_t) level"],
preamble: [" const rb_num_t level = 1;"],
opes: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
pops: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
rets: [],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
99 => Instruction.new(
name: :putobject_INT2FIX_0_,
bin: 99, # BIN(putobject_INT2FIX_0_)
len: 1, # insn_len
expr: <<-EXPR,
{
/* */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [" const VALUE val = INT2FIX(0);"],
opes: [],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
100 => Instruction.new(
name: :putobject_INT2FIX_1_,
bin: 100, # BIN(putobject_INT2FIX_1_)
len: 1, # insn_len
expr: <<-EXPR,
{
/* */
}
EXPR
declarations: ["MAYBE_UNUSED(VALUE) val"],
preamble: [" const VALUE val = INT2FIX(1);"],
opes: [],
pops: [],
rets: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
always_leaf?: true,
leaf_without_check_ints?: false,
handles_sp?: false,
),
}
private_constant(*constants)
end
share/ruby/pathname.rb 0000644 00000041522 15173547340 0010766 0 ustar 00 # frozen_string_literal: true
#
# = pathname.rb
#
# Object-Oriented Pathname Class
#
# Author:: Tanaka Akira <akr@m17n.org>
# Documentation:: Author and Gavin Sinclair
#
# For documentation, see class Pathname.
#
require 'pathname.so'
class Pathname
# :stopdoc:
# to_path is implemented so Pathname objects are usable with File.open, etc.
TO_PATH = :to_path
SAME_PATHS = if File::FNM_SYSCASE.nonzero?
# Avoid #zero? here because #casecmp can return nil.
proc {|a, b| a.casecmp(b) == 0}
else
proc {|a, b| a == b}
end
if File::ALT_SEPARATOR
SEPARATOR_LIST = "#{Regexp.quote File::ALT_SEPARATOR}#{Regexp.quote File::SEPARATOR}"
SEPARATOR_PAT = /[#{SEPARATOR_LIST}]/
else
SEPARATOR_LIST = "#{Regexp.quote File::SEPARATOR}"
SEPARATOR_PAT = /#{Regexp.quote File::SEPARATOR}/
end
if File.dirname('A:') == 'A:.' # DOSish drive letter
ABSOLUTE_PATH = /\A(?:[A-Za-z]:|#{SEPARATOR_PAT})/o
else
ABSOLUTE_PATH = /\A#{SEPARATOR_PAT}/o
end
private_constant :ABSOLUTE_PATH
# :startdoc:
# chop_basename(path) -> [pre-basename, basename] or nil
def chop_basename(path) # :nodoc:
base = File.basename(path)
if /\A#{SEPARATOR_PAT}?\z/o.match?(base)
return nil
else
return path[0, path.rindex(base)], base
end
end
private :chop_basename
# split_names(path) -> prefix, [name, ...]
def split_names(path) # :nodoc:
names = []
while r = chop_basename(path)
path, basename = r
names.unshift basename
end
return path, names
end
private :split_names
def prepend_prefix(prefix, relpath) # :nodoc:
if relpath.empty?
File.dirname(prefix)
elsif /#{SEPARATOR_PAT}/o.match?(prefix)
prefix = File.dirname(prefix)
prefix = File.join(prefix, "") if File.basename(prefix + 'a') != 'a'
prefix + relpath
else
prefix + relpath
end
end
private :prepend_prefix
# Returns clean pathname of +self+ with consecutive slashes and useless dots
# removed. The filesystem is not accessed.
#
# If +consider_symlink+ is +true+, then a more conservative algorithm is used
# to avoid breaking symbolic linkages. This may retain more +..+
# entries than absolutely necessary, but without accessing the filesystem,
# this can't be avoided.
#
# See Pathname#realpath.
#
def cleanpath(consider_symlink=false)
if consider_symlink
cleanpath_conservative
else
cleanpath_aggressive
end
end
#
# Clean the path simply by resolving and removing excess +.+ and +..+ entries.
# Nothing more, nothing less.
#
def cleanpath_aggressive # :nodoc:
path = @path
names = []
pre = path
while r = chop_basename(pre)
pre, base = r
case base
when '.'
when '..'
names.unshift base
else
if names[0] == '..'
names.shift
else
names.unshift base
end
end
end
pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
if /#{SEPARATOR_PAT}/o.match?(File.basename(pre))
names.shift while names[0] == '..'
end
self.class.new(prepend_prefix(pre, File.join(*names)))
end
private :cleanpath_aggressive
# has_trailing_separator?(path) -> bool
def has_trailing_separator?(path) # :nodoc:
if r = chop_basename(path)
pre, basename = r
pre.length + basename.length < path.length
else
false
end
end
private :has_trailing_separator?
# add_trailing_separator(path) -> path
def add_trailing_separator(path) # :nodoc:
if File.basename(path + 'a') == 'a'
path
else
File.join(path, "") # xxx: Is File.join is appropriate to add separator?
end
end
private :add_trailing_separator
def del_trailing_separator(path) # :nodoc:
if r = chop_basename(path)
pre, basename = r
pre + basename
elsif /#{SEPARATOR_PAT}+\z/o =~ path
$` + File.dirname(path)[/#{SEPARATOR_PAT}*\z/o]
else
path
end
end
private :del_trailing_separator
def cleanpath_conservative # :nodoc:
path = @path
names = []
pre = path
while r = chop_basename(pre)
pre, base = r
names.unshift base if base != '.'
end
pre.tr!(File::ALT_SEPARATOR, File::SEPARATOR) if File::ALT_SEPARATOR
if /#{SEPARATOR_PAT}/o.match?(File.basename(pre))
names.shift while names[0] == '..'
end
if names.empty?
self.class.new(File.dirname(pre))
else
if names.last != '..' && File.basename(path) == '.'
names << '.'
end
result = prepend_prefix(pre, File.join(*names))
if /\A(?:\.|\.\.)\z/ !~ names.last && has_trailing_separator?(path)
self.class.new(add_trailing_separator(result))
else
self.class.new(result)
end
end
end
private :cleanpath_conservative
# Returns the parent directory.
#
# This is same as <code>self + '..'</code>.
def parent
self + '..'
end
# Returns +true+ if +self+ points to a mountpoint.
def mountpoint?
begin
stat1 = self.lstat
stat2 = self.parent.lstat
stat1.dev != stat2.dev || stat1.ino == stat2.ino
rescue Errno::ENOENT
false
end
end
#
# Predicate method for root directories. Returns +true+ if the
# pathname consists of consecutive slashes.
#
# It doesn't access the filesystem. So it may return +false+ for some
# pathnames which points to roots such as <tt>/usr/..</tt>.
#
def root?
chop_basename(@path) == nil && /#{SEPARATOR_PAT}/o.match?(@path)
end
# Predicate method for testing whether a path is absolute.
#
# It returns +true+ if the pathname begins with a slash.
#
# p = Pathname.new('/im/sure')
# p.absolute?
# #=> true
#
# p = Pathname.new('not/so/sure')
# p.absolute?
# #=> false
def absolute?
ABSOLUTE_PATH.match? @path
end
# The opposite of Pathname#absolute?
#
# It returns +false+ if the pathname begins with a slash.
#
# p = Pathname.new('/im/sure')
# p.relative?
# #=> false
#
# p = Pathname.new('not/so/sure')
# p.relative?
# #=> true
def relative?
!absolute?
end
#
# Iterates over each component of the path.
#
# Pathname.new("/usr/bin/ruby").each_filename {|filename| ... }
# # yields "usr", "bin", and "ruby".
#
# Returns an Enumerator if no block was given.
#
# enum = Pathname.new("/usr/bin/ruby").each_filename
# # ... do stuff ...
# enum.each { |e| ... }
# # yields "usr", "bin", and "ruby".
#
def each_filename # :yield: filename
return to_enum(__method__) unless block_given?
_, names = split_names(@path)
names.each {|filename| yield filename }
nil
end
# Iterates over and yields a new Pathname object
# for each element in the given path in descending order.
#
# Pathname.new('/path/to/some/file.rb').descend {|v| p v}
# #<Pathname:/>
# #<Pathname:/path>
# #<Pathname:/path/to>
# #<Pathname:/path/to/some>
# #<Pathname:/path/to/some/file.rb>
#
# Pathname.new('path/to/some/file.rb').descend {|v| p v}
# #<Pathname:path>
# #<Pathname:path/to>
# #<Pathname:path/to/some>
# #<Pathname:path/to/some/file.rb>
#
# Returns an Enumerator if no block was given.
#
# enum = Pathname.new("/usr/bin/ruby").descend
# # ... do stuff ...
# enum.each { |e| ... }
# # yields Pathnames /, /usr, /usr/bin, and /usr/bin/ruby.
#
# It doesn't access the filesystem.
#
def descend
return to_enum(__method__) unless block_given?
vs = []
ascend {|v| vs << v }
vs.reverse_each {|v| yield v }
nil
end
# Iterates over and yields a new Pathname object
# for each element in the given path in ascending order.
#
# Pathname.new('/path/to/some/file.rb').ascend {|v| p v}
# #<Pathname:/path/to/some/file.rb>
# #<Pathname:/path/to/some>
# #<Pathname:/path/to>
# #<Pathname:/path>
# #<Pathname:/>
#
# Pathname.new('path/to/some/file.rb').ascend {|v| p v}
# #<Pathname:path/to/some/file.rb>
# #<Pathname:path/to/some>
# #<Pathname:path/to>
# #<Pathname:path>
#
# Returns an Enumerator if no block was given.
#
# enum = Pathname.new("/usr/bin/ruby").ascend
# # ... do stuff ...
# enum.each { |e| ... }
# # yields Pathnames /usr/bin/ruby, /usr/bin, /usr, and /.
#
# It doesn't access the filesystem.
#
def ascend
return to_enum(__method__) unless block_given?
path = @path
yield self
while r = chop_basename(path)
path, = r
break if path.empty?
yield self.class.new(del_trailing_separator(path))
end
end
#
# Appends a pathname fragment to +self+ to produce a new Pathname object.
# Since +other+ is considered as a path relative to +self+, if +other+ is
# an absolute path, the new Pathname object is created from just +other+.
#
# p1 = Pathname.new("/usr") # Pathname:/usr
# p2 = p1 + "bin/ruby" # Pathname:/usr/bin/ruby
# p3 = p1 + "/etc/passwd" # Pathname:/etc/passwd
#
# # / is aliased to +.
# p4 = p1 / "bin/ruby" # Pathname:/usr/bin/ruby
# p5 = p1 / "/etc/passwd" # Pathname:/etc/passwd
#
# This method doesn't access the file system; it is pure string manipulation.
#
def +(other)
other = Pathname.new(other) unless Pathname === other
Pathname.new(plus(@path, other.to_s))
end
alias / +
def plus(path1, path2) # -> path # :nodoc:
prefix2 = path2
index_list2 = []
basename_list2 = []
while r2 = chop_basename(prefix2)
prefix2, basename2 = r2
index_list2.unshift prefix2.length
basename_list2.unshift basename2
end
return path2 if prefix2 != ''
prefix1 = path1
while true
while !basename_list2.empty? && basename_list2.first == '.'
index_list2.shift
basename_list2.shift
end
break unless r1 = chop_basename(prefix1)
prefix1, basename1 = r1
next if basename1 == '.'
if basename1 == '..' || basename_list2.empty? || basename_list2.first != '..'
prefix1 = prefix1 + basename1
break
end
index_list2.shift
basename_list2.shift
end
r1 = chop_basename(prefix1)
if !r1 && (r1 = /#{SEPARATOR_PAT}/o.match?(File.basename(prefix1)))
while !basename_list2.empty? && basename_list2.first == '..'
index_list2.shift
basename_list2.shift
end
end
if !basename_list2.empty?
suffix2 = path2[index_list2.first..-1]
r1 ? File.join(prefix1, suffix2) : prefix1 + suffix2
else
r1 ? prefix1 : File.dirname(prefix1)
end
end
private :plus
#
# Joins the given pathnames onto +self+ to create a new Pathname object.
# This is effectively the same as using Pathname#+ to append +self+ and
# all arguments sequentially.
#
# path0 = Pathname.new("/usr") # Pathname:/usr
# path0 = path0.join("bin/ruby") # Pathname:/usr/bin/ruby
# # is the same as
# path1 = Pathname.new("/usr") + "bin/ruby" # Pathname:/usr/bin/ruby
# path0 == path1
# #=> true
#
def join(*args)
return self if args.empty?
result = args.pop
result = Pathname.new(result) unless Pathname === result
return result if result.absolute?
args.reverse_each {|arg|
arg = Pathname.new(arg) unless Pathname === arg
result = arg + result
return result if result.absolute?
}
self + result
end
#
# Returns the children of the directory (files and subdirectories, not
# recursive) as an array of Pathname objects.
#
# By default, the returned pathnames will have enough information to access
# the files. If you set +with_directory+ to +false+, then the returned
# pathnames will contain the filename only.
#
# For example:
# pn = Pathname("/usr/lib/ruby/1.8")
# pn.children
# # -> [ Pathname:/usr/lib/ruby/1.8/English.rb,
# Pathname:/usr/lib/ruby/1.8/Env.rb,
# Pathname:/usr/lib/ruby/1.8/abbrev.rb, ... ]
# pn.children(false)
# # -> [ Pathname:English.rb, Pathname:Env.rb, Pathname:abbrev.rb, ... ]
#
# Note that the results never contain the entries +.+ and +..+ in
# the directory because they are not children.
#
def children(with_directory=true)
with_directory = false if @path == '.'
result = []
Dir.foreach(@path) {|e|
next if e == '.' || e == '..'
if with_directory
result << self.class.new(File.join(@path, e))
else
result << self.class.new(e)
end
}
result
end
# Iterates over the children of the directory
# (files and subdirectories, not recursive).
#
# It yields Pathname object for each child.
#
# By default, the yielded pathnames will have enough information to access
# the files.
#
# If you set +with_directory+ to +false+, then the returned pathnames will
# contain the filename only.
#
# Pathname("/usr/local").each_child {|f| p f }
# #=> #<Pathname:/usr/local/share>
# # #<Pathname:/usr/local/bin>
# # #<Pathname:/usr/local/games>
# # #<Pathname:/usr/local/lib>
# # #<Pathname:/usr/local/include>
# # #<Pathname:/usr/local/sbin>
# # #<Pathname:/usr/local/src>
# # #<Pathname:/usr/local/man>
#
# Pathname("/usr/local").each_child(false) {|f| p f }
# #=> #<Pathname:share>
# # #<Pathname:bin>
# # #<Pathname:games>
# # #<Pathname:lib>
# # #<Pathname:include>
# # #<Pathname:sbin>
# # #<Pathname:src>
# # #<Pathname:man>
#
# Note that the results never contain the entries +.+ and +..+ in
# the directory because they are not children.
#
# See Pathname#children
#
def each_child(with_directory=true, &b)
children(with_directory).each(&b)
end
#
# Returns a relative path from the given +base_directory+ to the receiver.
#
# If +self+ is absolute, then +base_directory+ must be absolute too.
#
# If +self+ is relative, then +base_directory+ must be relative too.
#
# This method doesn't access the filesystem. It assumes no symlinks.
#
# ArgumentError is raised when it cannot find a relative path.
#
# Note that this method does not handle situations where the case sensitivity
# of the filesystem in use differs from the operating system default.
#
def relative_path_from(base_directory)
base_directory = Pathname.new(base_directory) unless base_directory.is_a? Pathname
dest_directory = self.cleanpath.to_s
base_directory = base_directory.cleanpath.to_s
dest_prefix = dest_directory
dest_names = []
while r = chop_basename(dest_prefix)
dest_prefix, basename = r
dest_names.unshift basename if basename != '.'
end
base_prefix = base_directory
base_names = []
while r = chop_basename(base_prefix)
base_prefix, basename = r
base_names.unshift basename if basename != '.'
end
unless SAME_PATHS[dest_prefix, base_prefix]
raise ArgumentError, "different prefix: #{dest_prefix.inspect} and #{base_directory.inspect}"
end
while !dest_names.empty? &&
!base_names.empty? &&
SAME_PATHS[dest_names.first, base_names.first]
dest_names.shift
base_names.shift
end
if base_names.include? '..'
raise ArgumentError, "base_directory has ..: #{base_directory.inspect}"
end
base_names.fill('..')
relpath_names = base_names + dest_names
if relpath_names.empty?
Pathname.new('.')
else
Pathname.new(File.join(*relpath_names))
end
end
end
class Pathname # * Find *
#
# Iterates over the directory tree in a depth first manner, yielding a
# Pathname for each file under "this" directory.
#
# Returns an Enumerator if no block is given.
#
# Since it is implemented by the standard library module Find, Find.prune can
# be used to control the traversal.
#
# If +self+ is +.+, yielded pathnames begin with a filename in the
# current directory, not +./+.
#
# See Find.find
#
def find(ignore_error: true) # :yield: pathname
return to_enum(__method__, ignore_error: ignore_error) unless block_given?
require 'find'
if @path == '.'
Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f.sub(%r{\A\./}, '')) }
else
Find.find(@path, ignore_error: ignore_error) {|f| yield self.class.new(f) }
end
end
end
autoload(:FileUtils, 'fileutils')
class Pathname # * FileUtils *
# Creates a full path, including any intermediate directories that don't yet
# exist.
#
# See FileUtils.mkpath and FileUtils.mkdir_p
def mkpath(mode: nil)
FileUtils.mkpath(@path, mode: mode)
nil
end
# Recursively deletes a directory, including all directories beneath it.
#
# See FileUtils.rm_rf
def rmtree(noop: nil, verbose: nil, secure: nil)
# The name "rmtree" is borrowed from File::Path of Perl.
# File::Path provides "mkpath" and "rmtree".
require 'fileutils'
FileUtils.rm_rf(@path, noop: noop, verbose: verbose, secure: secure)
nil
end
end
share/ruby/expect.rb 0000644 00000004304 15173547340 0010456 0 ustar 00 # frozen_string_literal: true
$expect_verbose = false
class IO
# call-seq:
# IO#expect(pattern,timeout=9999999) -> Array
# IO#expect(pattern,timeout=9999999) { |result| ... } -> nil
#
# The +expect+ library adds instance method IO#expect,
# which is similar to the
# {TCL expect extension}[https://www.tcl.tk/man/expect5.31/expect.1.html].
#
# To use this method, you must require +expect+:
#
# require 'expect'
#
# 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
share/ruby/error_highlight/version.rb 0000644 00000000056 15173547340 0014033 0 ustar 00 module ErrorHighlight
VERSION = "0.5.1"
end
share/ruby/error_highlight/core_ext.rb 0000644 00000002610 15173547340 0014154 0 ustar 00 require_relative "formatter"
module ErrorHighlight
module CoreExt
private def generate_snippet
spot = ErrorHighlight.spot(self)
return "" unless spot
return ErrorHighlight.formatter.message_for(spot)
end
if Exception.method_defined?(:detailed_message)
def detailed_message(highlight: false, error_highlight: true, **)
return super unless error_highlight
snippet = generate_snippet
if highlight
snippet = snippet.gsub(/.+/) { "\e[1m" + $& + "\e[m" }
end
super + snippet
end
else
# This is a marker to let `DidYouMean::Correctable#original_message` skip
# the following method definition of `to_s`.
# See https://github.com/ruby/did_you_mean/pull/152
SKIP_TO_S_FOR_SUPER_LOOKUP = true
private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
def to_s
msg = super
snippet = generate_snippet
if snippet != "" && !msg.include?(snippet)
msg + snippet
else
msg
end
end
end
end
NameError.prepend(CoreExt)
if Exception.method_defined?(:detailed_message)
# ErrorHighlight is enabled for TypeError and ArgumentError only when Exception#detailed_message is available.
# This is because changing ArgumentError#message is highly incompatible.
TypeError.prepend(CoreExt)
ArgumentError.prepend(CoreExt)
end
end
share/ruby/error_highlight/formatter.rb 0000644 00000001176 15173547340 0014355 0 ustar 00 module ErrorHighlight
class DefaultFormatter
def self.message_for(spot)
# currently only a one-line code snippet is supported
if spot[:first_lineno] == spot[:last_lineno]
indent = spot[:snippet][0...spot[:first_column]].gsub(/[^\t]/, " ")
marker = indent + "^" * (spot[:last_column] - spot[:first_column])
"\n\n#{ spot[:snippet] }#{ marker }"
else
""
end
end
end
def self.formatter
Ractor.current[:__error_highlight_formatter__] || DefaultFormatter
end
def self.formatter=(formatter)
Ractor.current[:__error_highlight_formatter__] = formatter
end
end
share/ruby/error_highlight/base.rb 0000644 00000033611 15173547340 0013263 0 ustar 00 require_relative "version"
module ErrorHighlight
# Identify the code fragment at that a given exception occurred.
#
# Options:
#
# point_type: :name | :args
# :name (default) points the method/variable name that the exception occurred.
# :args points the arguments of the method call that the exception occurred.
#
# backtrace_location: Thread::Backtrace::Location
# It locates the code fragment of the given backtrace_location.
# By default, it uses the first frame of backtrace_locations of the given exception.
#
# Returns:
# {
# first_lineno: Integer,
# first_column: Integer,
# last_lineno: Integer,
# last_column: Integer,
# snippet: String,
# script_lines: [String],
# } | nil
#
# Limitations:
#
# Currently, ErrorHighlight.spot only supports a single-line code fragment.
# Therefore, if the return value is not nil, first_lineno and last_lineno will have
# the same value. If the relevant code fragment spans multiple lines
# (e.g., Array#[] of +ary[(newline)expr(newline)]+), the method will return nil.
# This restriction may be removed in the future.
def self.spot(obj, **opts)
case obj
when Exception
exc = obj
loc = opts[:backtrace_location]
opts = { point_type: opts.fetch(:point_type, :name) }
unless loc
case exc
when TypeError, ArgumentError
opts[:point_type] = :args
end
locs = exc.backtrace_locations
return nil unless locs
loc = locs.first
return nil unless loc
opts[:name] = exc.name if NameError === obj
end
return nil unless Thread::Backtrace::Location === loc
node = RubyVM::AbstractSyntaxTree.of(loc, keep_script_lines: true)
Spotter.new(node, **opts).spot
when RubyVM::AbstractSyntaxTree::Node
Spotter.new(obj, **opts).spot
else
raise TypeError, "Exception is expected"
end
rescue SyntaxError,
SystemCallError, # file not found or something
ArgumentError # eval'ed code
return nil
end
class Spotter
class NonAscii < Exception; end
private_constant :NonAscii
def initialize(node, point_type: :name, name: nil)
@node = node
@point_type = point_type
@name = name
# Not-implemented-yet options
@arg = nil # Specify the index or keyword at which argument caused the TypeError/ArgumentError
@multiline = false # Allow multiline spot
@fetch = -> (lineno, last_lineno = lineno) do
snippet = @node.script_lines[lineno - 1 .. last_lineno - 1].join("")
snippet += "\n" unless snippet.end_with?("\n")
# It require some work to support Unicode (or multibyte) characters.
# Tentatively, we stop highlighting if the code snippet has non-ascii characters.
# See https://github.com/ruby/error_highlight/issues/4
raise NonAscii unless snippet.ascii_only?
snippet
end
end
def spot
return nil unless @node
case @node.type
when :CALL, :QCALL
case @point_type
when :name
spot_call_for_name
when :args
spot_call_for_args
end
when :ATTRASGN
case @point_type
when :name
spot_attrasgn_for_name
when :args
spot_attrasgn_for_args
end
when :OPCALL
case @point_type
when :name
spot_opcall_for_name
when :args
spot_opcall_for_args
end
when :FCALL
case @point_type
when :name
spot_fcall_for_name
when :args
spot_fcall_for_args
end
when :VCALL
spot_vcall
when :OP_ASGN1
case @point_type
when :name
spot_op_asgn1_for_name
when :args
spot_op_asgn1_for_args
end
when :OP_ASGN2
case @point_type
when :name
spot_op_asgn2_for_name
when :args
spot_op_asgn2_for_args
end
when :CONST
spot_vcall
when :COLON2
spot_colon2
when :COLON3
spot_vcall
when :OP_CDECL
spot_op_cdecl
end
if @snippet && @beg_column && @end_column && @beg_column < @end_column
return {
first_lineno: @beg_lineno,
first_column: @beg_column,
last_lineno: @end_lineno,
last_column: @end_column,
snippet: @snippet,
script_lines: @node.script_lines,
}
else
return nil
end
rescue NonAscii
nil
end
private
# Example:
# x.foo
# ^^^^
# x.foo(42)
# ^^^^
# x&.foo
# ^^^^^
# x[42]
# ^^^^
# x += 1
# ^
def spot_call_for_name
nd_recv, mid, nd_args = @node.children
lineno = nd_recv.last_lineno
lines = @fetch[lineno, @node.last_lineno]
if mid == :[] && lines.match(/\G[\s)]*(\[(?:\s*\])?)/, nd_recv.last_column)
@beg_column = $~.begin(1)
@snippet = lines[/.*\n/]
@beg_lineno = @end_lineno = lineno
if nd_args
if nd_recv.last_lineno == nd_args.last_lineno && @snippet.match(/\s*\]/, nd_args.last_column)
@end_column = $~.end(0)
end
else
if lines.match(/\G[\s)]*?\[\s*\]/, nd_recv.last_column)
@end_column = $~.end(0)
end
end
elsif lines.match(/\G[\s)]*?(\&?\.)(\s*?)(#{ Regexp.quote(mid) }).*\n/, nd_recv.last_column)
lines = $` + $&
@beg_column = $~.begin($2.include?("\n") ? 3 : 1)
@end_column = $~.end(3)
if i = lines[..@beg_column].rindex("\n")
@beg_lineno = @end_lineno = lineno + lines[..@beg_column].count("\n")
@snippet = lines[i + 1..]
@beg_column -= i + 1
@end_column -= i + 1
else
@snippet = lines
@beg_lineno = @end_lineno = lineno
end
elsif mid.to_s =~ /\A\W+\z/ && lines.match(/\G\s*(#{ Regexp.quote(mid) })=.*\n/, nd_recv.last_column)
@snippet = $` + $&
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# x.foo(42)
# ^^
# x[42]
# ^^
# x += 1
# ^
def spot_call_for_args
_nd_recv, _mid, nd_args = @node.children
if nd_args && nd_args.first_lineno == nd_args.last_lineno
fetch_line(nd_args.first_lineno)
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
# TODO: support @arg
end
# Example:
# x.foo = 1
# ^^^^^^
# x[42] = 1
# ^^^^^^
def spot_attrasgn_for_name
nd_recv, mid, nd_args = @node.children
*nd_args, _nd_last_arg, _nil = nd_args.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @snippet.match(/\G[\s)]*(\[)/, nd_recv.last_column)
@beg_column = $~.begin(1)
args_last_column = $~.end(0)
if nd_args.last && nd_recv.last_lineno == nd_args.last.last_lineno
args_last_column = nd_args.last.last_column
end
if @snippet.match(/[\s)]*\]\s*=/, args_last_column)
@end_column = $~.end(0)
end
elsif @snippet.match(/\G[\s)]*(\.\s*#{ Regexp.quote(mid.to_s.sub(/=\z/, "")) }\s*=)/, nd_recv.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# x.foo = 1
# ^
# x[42] = 1
# ^^^^^^^
# x[] = 1
# ^^^^^
def spot_attrasgn_for_args
nd_recv, mid, nd_args = @node.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @snippet.match(/\G[\s)]*\[/, nd_recv.last_column)
@beg_column = $~.end(0)
if nd_recv.last_lineno == nd_args.last_lineno
@end_column = nd_args.last_column
end
elsif nd_args && nd_args.first_lineno == nd_args.last_lineno
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
# TODO: support @arg
end
# Example:
# x + 1
# ^
# +x
# ^
def spot_opcall_for_name
nd_recv, op, nd_arg = @node.children
fetch_line(nd_recv.last_lineno)
if nd_arg
# binary operator
if @snippet.match(/\G[\s)]*(#{ Regexp.quote(op) })/, nd_recv.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
else
# unary operator
if @snippet[...nd_recv.first_column].match(/(#{ Regexp.quote(op.to_s.sub(/@\z/, "")) })\s*\(?\s*\z/)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
end
# Example:
# x + 1
# ^
def spot_opcall_for_args
_nd_recv, _op, nd_arg = @node.children
if nd_arg && nd_arg.first_lineno == nd_arg.last_lineno
# binary operator
fetch_line(nd_arg.first_lineno)
@beg_column = nd_arg.first_column
@end_column = nd_arg.last_column
end
end
# Example:
# foo(42)
# ^^^
# foo 42
# ^^^
def spot_fcall_for_name
mid, _nd_args = @node.children
fetch_line(@node.first_lineno)
if @snippet.match(/(#{ Regexp.quote(mid) })/, @node.first_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
end
# Example:
# foo(42)
# ^^
# foo 42
# ^^
def spot_fcall_for_args
_mid, nd_args = @node.children
if nd_args && nd_args.first_lineno == nd_args.last_lineno
# binary operator
fetch_line(nd_args.first_lineno)
@beg_column = nd_args.first_column
@end_column = nd_args.last_column
end
end
# Example:
# foo
# ^^^
def spot_vcall
if @node.first_lineno == @node.last_lineno
fetch_line(@node.last_lineno)
@beg_column = @node.first_column
@end_column = @node.last_column
end
end
# Example:
# x[1] += 42
# ^^^ (for [])
# x[1] += 42
# ^ (for +)
# x[1] += 42
# ^^^^^^ (for []=)
def spot_op_asgn1_for_name
nd_recv, op, nd_args, _nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if @snippet.match(/\G[\s)]*(\[)/, nd_recv.last_column)
bracket_beg_column = $~.begin(1)
args_last_column = $~.end(0)
if nd_args && nd_recv.last_lineno == nd_args.last_lineno
args_last_column = nd_args.last_column
end
if @snippet.match(/\s*\](\s*)(#{ Regexp.quote(op) })=()/, args_last_column)
case @name
when :[], :[]=
@beg_column = bracket_beg_column
@end_column = $~.begin(@name == :[] ? 1 : 3)
when op
@beg_column = $~.begin(2)
@end_column = $~.end(2)
end
end
end
end
# Example:
# x[1] += 42
# ^^^^^^^^
def spot_op_asgn1_for_args
nd_recv, mid, nd_args, nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if mid == :[]= && @snippet.match(/\G\s*\[/, nd_recv.last_column)
@beg_column = $~.end(0)
if nd_recv.last_lineno == nd_rhs.last_lineno
@end_column = nd_rhs.last_column
end
elsif nd_args && nd_args.first_lineno == nd_rhs.last_lineno
@beg_column = nd_args.first_column
@end_column = nd_rhs.last_column
end
# TODO: support @arg
end
# Example:
# x.foo += 42
# ^^^ (for foo)
# x.foo += 42
# ^ (for +)
# x.foo += 42
# ^^^^^^^ (for foo=)
def spot_op_asgn2_for_name
nd_recv, _qcall, attr, op, _nd_rhs = @node.children
fetch_line(nd_recv.last_lineno)
if @snippet.match(/\G[\s)]*(\.)\s*#{ Regexp.quote(attr) }()\s*(#{ Regexp.quote(op) })(=)/, nd_recv.last_column)
case @name
when attr
@beg_column = $~.begin(1)
@end_column = $~.begin(2)
when op
@beg_column = $~.begin(3)
@end_column = $~.end(3)
when :"#{ attr }="
@beg_column = $~.begin(1)
@end_column = $~.end(4)
end
end
end
# Example:
# x.foo += 42
# ^^
def spot_op_asgn2_for_args
_nd_recv, _qcall, _attr, _op, nd_rhs = @node.children
if nd_rhs.first_lineno == nd_rhs.last_lineno
fetch_line(nd_rhs.first_lineno)
@beg_column = nd_rhs.first_column
@end_column = nd_rhs.last_column
end
end
# Example:
# Foo::Bar
# ^^^^^
def spot_colon2
nd_parent, const = @node.children
if nd_parent.last_lineno == @node.last_lineno
fetch_line(nd_parent.last_lineno)
@beg_column = nd_parent.last_column
@end_column = @node.last_column
else
@snippet = @fetch[@node.last_lineno]
if @snippet[...@node.last_column].match(/#{ Regexp.quote(const) }\z/)
@beg_column = $~.begin(0)
@end_column = $~.end(0)
end
end
end
# Example:
# Foo::Bar += 1
# ^^^^^^^^
def spot_op_cdecl
nd_lhs, op, _nd_rhs = @node.children
*nd_parent_lhs, _const = nd_lhs.children
if @name == op
@snippet = @fetch[nd_lhs.last_lineno]
if @snippet.match(/\G\s*(#{ Regexp.quote(op) })=/, nd_lhs.last_column)
@beg_column = $~.begin(1)
@end_column = $~.end(1)
end
else
# constant access error
@end_column = nd_lhs.last_column
if nd_parent_lhs.empty? # example: ::C += 1
if nd_lhs.first_lineno == nd_lhs.last_lineno
@snippet = @fetch[nd_lhs.last_lineno]
@beg_column = nd_lhs.first_column
end
else # example: Foo::Bar::C += 1
if nd_parent_lhs.last.last_lineno == nd_lhs.last_lineno
@snippet = @fetch[nd_lhs.last_lineno]
@beg_column = nd_parent_lhs.last.last_column
end
end
end
end
def fetch_line(lineno)
@beg_lineno = @end_lineno = lineno
@snippet = @fetch[lineno]
end
end
private_constant :Spotter
end
share/ruby/English.rb 0000644 00000014162 15173547340 0010562 0 ustar 00 # frozen_string_literal: true
# Include the English library file in a Ruby script, and you can
# reference the global variables such as <tt>$_</tt> using less
# cryptic names, listed below.
#
# 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. See Kernel#caller for details. Thread local.
alias $ERROR_POSITION $@
# The default separator pattern used by String#split. May be set from
# the command line using the <tt>-F</tt> flag.
alias $FS $;
# The default separator pattern used by String#split. 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 Kernel#print and Array#join. Defaults to +nil+, which adds no
# text.
alias $OFS $,
# The separator string output between the parameters to methods such
# as Kernel#print and Array#join. 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 Kernel#gets 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 Kernel#gets 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
# Kernel#print and IO#write. The default value is +nil+.
alias $ORS $\
# The string appended to the output of every call to methods such as
# Kernel#print and IO#write. 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 Kernel#gets or
# Kernel#readline. 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 Kernel#print
# and Kernel#printf. 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 $+
share/ruby/objspace/trace.rb 0000644 00000002235 15173547340 0012053 0 ustar 00 # This is a simple tool to enable the object allocation tracer.
# When you have an object of unknown provenance, you can use this
# to investigate where the object in question is created.
#
# = Important notice
#
# This is only for debugging purpose. Do not use this in production.
# Require'ing this file immediately starts tracing the object allocation,
# which brings a large performance overhead.
#
# = Usage
#
# 1. Add `require "objspace/trace"` into your code (or add `-robjspace/trace` into the command line)
# 2. `p obj` will show the allocation site of `obj`
#
# Note: This redefines `Kernel#p` method, but not `Object#inspect`.
#
# = Examples
#
# 1: require "objspace/trace"
# 2:
# 3: obj = "str"
# 4:
# 5: p obj #=> "str" @ test.rb:3
require 'objspace.so'
module Kernel
remove_method :p
define_method(:p) do |*objs|
objs.each do |obj|
file = ObjectSpace.allocation_sourcefile(obj)
line = ObjectSpace.allocation_sourceline(obj)
if file
puts "#{ obj.inspect } @ #{ file }:#{ line }"
else
puts obj.inspect
end
end
end
end
ObjectSpace.trace_object_allocations_start
warn "objspace/trace is enabled"
share/ruby/tmpdir.rb 0000644 00000011314 15173547340 0010464 0 ustar 00 # frozen_string_literal: true
#
# tmpdir - retrieve temporary directory path
#
# $Id$
#
require 'fileutils'
begin
require 'etc.so'
rescue LoadError # rescue LoadError for miniruby
end
class Dir
@@systmpdir ||= defined?(Etc.systmpdir) ? Etc.systmpdir : '/tmp'
##
# Returns the operating system's temporary file path.
def self.tmpdir
['TMPDIR', 'TMP', 'TEMP', ['system temporary path', @@systmpdir], ['/tmp']*2, ['.']*2].find do |name, dir|
unless dir
next if !(dir = ENV[name]) or dir.empty?
end
dir = File.expand_path(dir)
stat = File.stat(dir) rescue next
case
when !stat.directory?
warn "#{name} is not a directory: #{dir}"
when !stat.writable?
warn "#{name} is not writable: #{dir}"
when stat.world_writable? && !stat.sticky?
warn "#{name} is world-writable: #{dir}"
else
break dir
end
end or raise ArgumentError, "could not find a temporary directory"
end
# Dir.mktmpdir creates a temporary directory.
#
# The directory is created with 0700 permission.
# Application should not change the permission to make the temporary directory accessible from other users.
#
# The prefix and suffix of the name of the directory is specified by
# the optional first argument, <i>prefix_suffix</i>.
# - If it is not specified or nil, "d" is used as the prefix and no suffix is used.
# - If it is a string, it is used as the prefix and no suffix is used.
# - If it is an array, first element is used as the prefix and second element is used as a suffix.
#
# Dir.mktmpdir {|dir| dir is ".../d..." }
# Dir.mktmpdir("foo") {|dir| dir is ".../foo..." }
# Dir.mktmpdir(["foo", "bar"]) {|dir| dir is ".../foo...bar" }
#
# The directory is created under Dir.tmpdir or
# the optional second argument <i>tmpdir</i> if non-nil value is given.
#
# Dir.mktmpdir {|dir| dir is "#{Dir.tmpdir}/d..." }
# Dir.mktmpdir(nil, "/var/tmp") {|dir| dir is "/var/tmp/d..." }
#
# If a block is given,
# it is yielded with the path of the directory.
# The directory and its contents are removed
# using FileUtils.remove_entry before Dir.mktmpdir returns.
# The value of the block is returned.
#
# Dir.mktmpdir {|dir|
# # use the directory...
# open("#{dir}/foo", "w") { something using the file }
# }
#
# If a block is not given,
# The path of the directory is returned.
# In this case, Dir.mktmpdir doesn't remove the directory.
#
# dir = Dir.mktmpdir
# begin
# # use the directory...
# open("#{dir}/foo", "w") { something using the file }
# ensure
# # remove the directory.
# FileUtils.remove_entry dir
# end
#
def self.mktmpdir(prefix_suffix=nil, *rest, **options)
base = nil
path = Tmpname.create(prefix_suffix || "d", *rest, **options) {|path, _, _, d|
base = d
mkdir(path, 0700)
}
if block_given?
begin
yield path.dup
ensure
unless base
stat = File.stat(File.dirname(path))
if stat.world_writable? and !stat.sticky?
raise ArgumentError, "parent directory is world writable but not sticky"
end
end
FileUtils.remove_entry path
end
else
path
end
end
# Temporary name generator
module Tmpname # :nodoc:
module_function
def tmpdir
Dir.tmpdir
end
# Unusable characters as path name
UNUSABLE_CHARS = "^,-.0-9A-Z_a-z~"
# Dedicated random number generator
RANDOM = Random.new
class << RANDOM # :nodoc:
# Maximum random number
MAX = 36**6 # < 0x100000000
# Returns new random string upto 6 bytes
def next
rand(MAX).to_s(36)
end
end
private_constant :RANDOM
# Generates and yields random names to create a temporary name
def create(basename, tmpdir=nil, max_try: nil, **opts)
origdir = tmpdir
tmpdir ||= tmpdir()
n = nil
prefix, suffix = basename
prefix = (String.try_convert(prefix) or
raise ArgumentError, "unexpected prefix: #{prefix.inspect}")
prefix = prefix.delete(UNUSABLE_CHARS)
suffix &&= (String.try_convert(suffix) or
raise ArgumentError, "unexpected suffix: #{suffix.inspect}")
suffix &&= suffix.delete(UNUSABLE_CHARS)
begin
t = Time.now.strftime("%Y%m%d")
path = "#{prefix}#{t}-#{$$}-#{RANDOM.next}"\
"#{n ? %[-#{n}] : ''}#{suffix||''}"
path = File.join(tmpdir, path)
yield(path, n, opts, origdir)
rescue Errno::EEXIST
n ||= 0
n += 1
retry if !max_try or n < max_try
raise "cannot generate temporary name using `#{basename}' under `#{tmpdir}'"
end
path
end
end
end
share/ruby/erb/util.rb 0000644 00000002611 15173547340 0010712 0 ustar 00 #--
# ERB::Escape
#
# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope
# Rails will not monkey-patch ERB::Escape#html_escape.
begin
# We don't build the C extension for JRuby, TruffleRuby, and WASM
if $LOAD_PATH.resolve_feature_path('erb/escape')
require 'erb/escape'
end
rescue LoadError # resolve_feature_path raises LoadError on TruffleRuby 22.3.0
end
unless defined?(ERB::Escape)
module ERB::Escape
def html_escape(s)
CGI.escapeHTML(s.to_s)
end
module_function :html_escape
end
end
#--
# ERB::Util
#
# A utility module for conversion routines, often handy in HTML generation.
module ERB::Util
#
# A utility method for escaping HTML tag characters in _s_.
#
# require "erb"
# include ERB::Util
#
# puts html_escape("is a > 0 & a < 10?")
#
# _Generates_
#
# is a > 0 & a < 10?
#
include ERB::Escape # html_escape
module_function :html_escape
alias h html_escape
module_function :h
#
# A utility method for encoding the String _s_ as a URL.
#
# require "erb"
# include ERB::Util
#
# puts url_encode("Programming Ruby: The Pragmatic Programmer's Guide")
#
# _Generates_
#
# Programming%20Ruby%3A%20%20The%20Pragmatic%20Programmer%27s%20Guide
#
def url_encode(s)
CGI.escapeURIComponent(s.to_s)
end
alias u url_encode
module_function :u
module_function :url_encode
end
share/ruby/erb/def_method.rb 0000644 00000001667 15173547340 0012045 0 ustar 00 #--
# ERB::DefMethod
#
# Utility module to define eRuby script as instance method.
#
# === Example
#
# example.rhtml:
# <% for item in @items %>
# <b><%= item %></b>
# <% end %>
#
# example.rb:
# require 'erb'
# class MyClass
# extend ERB::DefMethod
# def_erb_method('render()', 'example.rhtml')
# def initialize(items)
# @items = items
# end
# end
# print MyClass.new([10,20,30]).render()
#
# result:
#
# <b>10</b>
#
# <b>20</b>
#
# <b>30</b>
#
module ERB::DefMethod
# define _methodname_ as instance method of current module, using ERB
# object or eRuby file
def def_erb_method(methodname, erb_or_fname)
if erb_or_fname.kind_of? String
fname = erb_or_fname
erb = ERB.new(File.read(fname))
erb.def_method(self, methodname, fname)
else
erb = erb_or_fname
erb.def_method(self, methodname, erb.filename || '(ERB)')
end
end
module_function :def_erb_method
end
share/ruby/erb/version.rb 0000644 00000000134 15173547340 0011420 0 ustar 00 # frozen_string_literal: true
class ERB
VERSION = '4.0.2'
private_constant :VERSION
end
share/ruby/erb/compiler.rb 0000644 00000026522 15173547340 0011556 0 ustar 00 #--
# ERB::Compiler
#
# Compiles ERB templates into Ruby code; the compiled code produces the
# template result when evaluated. ERB::Compiler provides hooks to define how
# generated output is handled.
#
# Internally ERB does something like this to generate the code returned by
# ERB#src:
#
# compiler = ERB::Compiler.new('<>')
# compiler.pre_cmd = ["_erbout=+''"]
# compiler.put_cmd = "_erbout.<<"
# compiler.insert_cmd = "_erbout.<<"
# compiler.post_cmd = ["_erbout"]
#
# code, enc = compiler.compile("Got <%= obj %>!\n")
# puts code
#
# <i>Generates</i>:
#
# #coding:UTF-8
# _erbout=+''; _erbout.<< "Got ".freeze; _erbout.<<(( obj ).to_s); _erbout.<< "!\n".freeze; _erbout
#
# By default the output is sent to the print method. For example:
#
# compiler = ERB::Compiler.new('<>')
# code, enc = compiler.compile("Got <%= obj %>!\n")
# puts code
#
# <i>Generates</i>:
#
# #coding:UTF-8
# print "Got ".freeze; print(( obj ).to_s); print "!\n".freeze
#
# == Evaluation
#
# The compiled code can be used in any context where the names in the code
# correctly resolve. Using the last example, each of these print 'Got It!'
#
# Evaluate using a variable:
#
# obj = 'It'
# eval code
#
# Evaluate using an input:
#
# mod = Module.new
# mod.module_eval %{
# def get(obj)
# #{code}
# end
# }
# extend mod
# get('It')
#
# Evaluate using an accessor:
#
# klass = Class.new Object
# klass.class_eval %{
# attr_accessor :obj
# def initialize(obj)
# @obj = obj
# end
# def get_it
# #{code}
# end
# }
# klass.new('It').get_it
#
# Good! See also ERB#def_method, ERB#def_module, and ERB#def_class.
class ERB::Compiler # :nodoc:
class PercentLine # :nodoc:
def initialize(str)
@value = str
end
attr_reader :value
alias :to_s :value
end
class Scanner # :nodoc:
@scanner_map = {}
class << self
def register_scanner(klass, trim_mode, percent)
@scanner_map[[trim_mode, percent]] = klass
end
alias :regist_scanner :register_scanner
end
def self.default_scanner=(klass)
@default_scanner = klass
end
def self.make_scanner(src, trim_mode, percent)
klass = @scanner_map.fetch([trim_mode, percent], @default_scanner)
klass.new(src, trim_mode, percent)
end
DEFAULT_STAGS = %w(<%% <%= <%# <%).freeze
DEFAULT_ETAGS = %w(%%> %>).freeze
def initialize(src, trim_mode, percent)
@src = src
@stag = nil
@stags = DEFAULT_STAGS
@etags = DEFAULT_ETAGS
end
attr_accessor :stag
attr_reader :stags, :etags
def scan; end
end
class TrimScanner < Scanner # :nodoc:
def initialize(src, trim_mode, percent)
super
@trim_mode = trim_mode
@percent = percent
if @trim_mode == '>'
@scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m
@scan_line = self.method(:trim_line1)
elsif @trim_mode == '<>'
@scan_reg = /(.*?)(%>\r?\n|#{(stags + etags).join('|')}|\n|\z)/m
@scan_line = self.method(:trim_line2)
elsif @trim_mode == '-'
@scan_reg = /(.*?)(^[ \t]*<%\-|<%\-|-%>\r?\n|-%>|#{(stags + etags).join('|')}|\z)/m
@scan_line = self.method(:explicit_trim_line)
else
@scan_reg = /(.*?)(#{(stags + etags).join('|')}|\n|\z)/m
@scan_line = self.method(:scan_line)
end
end
def scan(&block)
@stag = nil
if @percent
@src.each_line do |line|
percent_line(line, &block)
end
else
@scan_line.call(@src, &block)
end
nil
end
def percent_line(line, &block)
if @stag || line[0] != ?%
return @scan_line.call(line, &block)
end
line[0] = ''
if line[0] == ?%
@scan_line.call(line, &block)
else
yield(PercentLine.new(line.chomp))
end
end
def scan_line(line)
line.scan(@scan_reg) do |tokens|
tokens.each do |token|
next if token.empty?
yield(token)
end
end
end
def trim_line1(line)
line.scan(@scan_reg) do |tokens|
tokens.each do |token|
next if token.empty?
if token == "%>\n" || token == "%>\r\n"
yield('%>')
yield(:cr)
else
yield(token)
end
end
end
end
def trim_line2(line)
head = nil
line.scan(@scan_reg) do |tokens|
tokens.each do |token|
next if token.empty?
head = token unless head
if token == "%>\n" || token == "%>\r\n"
yield('%>')
if is_erb_stag?(head)
yield(:cr)
else
yield("\n")
end
head = nil
else
yield(token)
head = nil if token == "\n"
end
end
end
end
def explicit_trim_line(line)
line.scan(@scan_reg) do |tokens|
tokens.each do |token|
next if token.empty?
if @stag.nil? && /[ \t]*<%-/ =~ token
yield('<%')
elsif @stag && (token == "-%>\n" || token == "-%>\r\n")
yield('%>')
yield(:cr)
elsif @stag && token == '-%>'
yield('%>')
else
yield(token)
end
end
end
end
ERB_STAG = %w(<%= <%# <%)
def is_erb_stag?(s)
ERB_STAG.member?(s)
end
end
Scanner.default_scanner = TrimScanner
begin
require 'strscan'
rescue LoadError
else
class SimpleScanner < Scanner # :nodoc:
def scan
stag_reg = (stags == DEFAULT_STAGS) ? /(.*?)(<%[%=#]?|\z)/m : /(.*?)(#{stags.join('|')}|\z)/m
etag_reg = (etags == DEFAULT_ETAGS) ? /(.*?)(%%?>|\z)/m : /(.*?)(#{etags.join('|')}|\z)/m
scanner = StringScanner.new(@src)
while ! scanner.eos?
scanner.scan(@stag ? etag_reg : stag_reg)
yield(scanner[1])
yield(scanner[2])
end
end
end
Scanner.register_scanner(SimpleScanner, nil, false)
class ExplicitScanner < Scanner # :nodoc:
def scan
stag_reg = /(.*?)(^[ \t]*<%-|<%-|#{stags.join('|')}|\z)/m
etag_reg = /(.*?)(-%>|#{etags.join('|')}|\z)/m
scanner = StringScanner.new(@src)
while ! scanner.eos?
scanner.scan(@stag ? etag_reg : stag_reg)
yield(scanner[1])
elem = scanner[2]
if /[ \t]*<%-/ =~ elem
yield('<%')
elsif elem == '-%>'
yield('%>')
yield(:cr) if scanner.scan(/(\r?\n|\z)/)
else
yield(elem)
end
end
end
end
Scanner.register_scanner(ExplicitScanner, '-', false)
end
class Buffer # :nodoc:
def initialize(compiler, enc=nil, frozen=nil)
@compiler = compiler
@line = []
@script = +''
@script << "#coding:#{enc}\n" if enc
@script << "#frozen-string-literal:#{frozen}\n" unless frozen.nil?
@compiler.pre_cmd.each do |x|
push(x)
end
end
attr_reader :script
def push(cmd)
@line << cmd
end
def cr
@script << (@line.join('; '))
@line = []
@script << "\n"
end
def close
return unless @line
@compiler.post_cmd.each do |x|
push(x)
end
@script << (@line.join('; '))
@line = nil
end
end
def add_put_cmd(out, content)
out.push("#{@put_cmd} #{content.dump}.freeze#{"\n" * content.count("\n")}")
end
def add_insert_cmd(out, content)
out.push("#{@insert_cmd}((#{content}).to_s)")
end
# Compiles an ERB template into Ruby code. Returns an array of the code
# and encoding like ["code", Encoding].
def compile(s)
enc = s.encoding
raise ArgumentError, "#{enc} is not ASCII compatible" if enc.dummy?
s = s.b # see String#b
magic_comment = detect_magic_comment(s, enc)
out = Buffer.new(self, *magic_comment)
self.content = +''
scanner = make_scanner(s)
scanner.scan do |token|
next if token.nil?
next if token == ''
if scanner.stag.nil?
compile_stag(token, out, scanner)
else
compile_etag(token, out, scanner)
end
end
add_put_cmd(out, content) if content.size > 0
out.close
return out.script, *magic_comment
end
def compile_stag(stag, out, scanner)
case stag
when PercentLine
add_put_cmd(out, content) if content.size > 0
self.content = +''
out.push(stag.to_s)
out.cr
when :cr
out.cr
when '<%', '<%=', '<%#'
scanner.stag = stag
add_put_cmd(out, content) if content.size > 0
self.content = +''
when "\n"
content << "\n"
add_put_cmd(out, content)
self.content = +''
when '<%%'
content << '<%'
else
content << stag
end
end
def compile_etag(etag, out, scanner)
case etag
when '%>'
compile_content(scanner.stag, out)
scanner.stag = nil
self.content = +''
when '%%>'
content << '%>'
else
content << etag
end
end
def compile_content(stag, out)
case stag
when '<%'
if content[-1] == ?\n
content.chop!
out.push(content)
out.cr
else
out.push(content)
end
when '<%='
add_insert_cmd(out, content)
when '<%#'
out.push("\n" * content.count("\n")) # only adjust lineno
end
end
def prepare_trim_mode(mode) # :nodoc:
case mode
when 1
return [false, '>']
when 2
return [false, '<>']
when 0, nil
return [false, nil]
when String
unless mode.match?(/\A(%|-|>|<>){1,2}\z/)
warn_invalid_trim_mode(mode, uplevel: 5)
end
perc = mode.include?('%')
if mode.include?('-')
return [perc, '-']
elsif mode.include?('<>')
return [perc, '<>']
elsif mode.include?('>')
return [perc, '>']
else
[perc, nil]
end
else
warn_invalid_trim_mode(mode, uplevel: 5)
return [false, nil]
end
end
def make_scanner(src) # :nodoc:
Scanner.make_scanner(src, @trim_mode, @percent)
end
# Construct a new compiler using the trim_mode. See ERB::new for available
# trim modes.
def initialize(trim_mode)
@percent, @trim_mode = prepare_trim_mode(trim_mode)
@put_cmd = 'print'
@insert_cmd = @put_cmd
@pre_cmd = []
@post_cmd = []
end
attr_reader :percent, :trim_mode
# The command to handle text that ends with a newline
attr_accessor :put_cmd
# The command to handle text that is inserted prior to a newline
attr_accessor :insert_cmd
# An array of commands prepended to compiled code
attr_accessor :pre_cmd
# An array of commands appended to compiled code
attr_accessor :post_cmd
private
# A buffered text in #compile
attr_accessor :content
def detect_magic_comment(s, enc = nil)
re = @percent ? /\G(?:<%#(.*)%>|%#(.*)\n)/ : /\G<%#(.*)%>/
frozen = nil
s.scan(re) do
comment = $+
comment = $1 if comment[/-\*-\s*([^\s].*?)\s*-\*-$/]
case comment
when %r"coding\s*[=:]\s*([[:alnum:]\-_]+)"
enc = Encoding.find($1.sub(/-(?:mac|dos|unix)/i, ''))
when %r"frozen[-_]string[-_]literal\s*:\s*([[:alnum:]]+)"
frozen = $1
end
end
return enc, frozen
end
def warn_invalid_trim_mode(mode, uplevel:)
warn "Invalid ERB trim mode: #{mode.inspect} (trim_mode: nil, 0, 1, 2, or String composed of '%' and/or '-', '>', '<>')", uplevel: uplevel + 1
end
end
share/ruby/delegate.rb 0000644 00000027264 15173547340 0010752 0 ustar 00 # frozen_string_literal: true
# = delegate -- Support for the Delegation Pattern
#
# Documentation by James Edward Gray II and Gavin Sinclair
##
# This library provides three different ways to delegate method calls to an
# object. The easiest to use is SimpleDelegator. Pass an object to the
# constructor and all methods supported by the object will be delegated. This
# object can be changed later.
#
# Going a step further, the top level DelegateClass method allows you to easily
# setup delegation through class inheritance. This is considerably more
# flexible and thus probably the most common use for this library.
#
# Finally, if you need full control over the delegation scheme, you can inherit
# from the abstract class Delegator and customize as needed. (If you find
# yourself needing this control, have a look at Forwardable which is also in
# the standard library. It may suit your needs better.)
#
# SimpleDelegator's implementation serves as a nice example of the use of
# Delegator:
#
# require 'delegate'
#
# class SimpleDelegator < Delegator
# def __getobj__
# @delegate_sd_obj # return object we are delegating to, required
# end
#
# def __setobj__(obj)
# @delegate_sd_obj = obj # change delegation object,
# # a feature we're providing
# end
# end
#
# == Notes
#
# Be advised, RDoc will not detect delegated methods.
#
class Delegator < BasicObject
VERSION = "0.3.0"
kernel = ::Kernel.dup
kernel.class_eval do
alias __raise__ raise
[:to_s, :inspect, :!~, :===, :<=>, :hash].each do |m|
undef_method m
end
private_instance_methods.each do |m|
if /\Ablock_given\?\z|\Aiterator\?\z|\A__.*__\z/ =~ m
next
end
undef_method m
end
end
include kernel
# :stopdoc:
def self.const_missing(n)
::Object.const_get(n)
end
# :startdoc:
##
# :method: raise
# Use #__raise__ if your Delegator does not have a object to delegate the
# #raise method call.
#
#
# Pass in the _obj_ to delegate method calls to. All methods supported by
# _obj_ will be delegated to.
#
def initialize(obj)
__setobj__(obj)
end
#
# Handles the magic of delegation through \_\_getobj\_\_.
#
ruby2_keywords def method_missing(m, *args, &block)
r = true
target = self.__getobj__ {r = false}
if r && target_respond_to?(target, m, false)
target.__send__(m, *args, &block)
elsif ::Kernel.method_defined?(m) || ::Kernel.private_method_defined?(m)
::Kernel.instance_method(m).bind_call(self, *args, &block)
else
super(m, *args, &block)
end
end
#
# Checks for a method provided by this the delegate object by forwarding the
# call through \_\_getobj\_\_.
#
def respond_to_missing?(m, include_private)
r = true
target = self.__getobj__ {r = false}
r &&= target_respond_to?(target, m, include_private)
if r && include_private && !target_respond_to?(target, m, false)
warn "delegator does not forward private method \##{m}", uplevel: 3
return false
end
r
end
KERNEL_RESPOND_TO = ::Kernel.instance_method(:respond_to?)
private_constant :KERNEL_RESPOND_TO
# Handle BasicObject instances
private def target_respond_to?(target, m, include_private)
case target
when Object
target.respond_to?(m, include_private)
else
if KERNEL_RESPOND_TO.bind_call(target, :respond_to?)
target.respond_to?(m, include_private)
else
KERNEL_RESPOND_TO.bind_call(target, m, include_private)
end
end
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ methods.
#
def methods(all=true)
__getobj__.methods(all) | super
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ public methods.
#
def public_methods(all=true)
__getobj__.public_methods(all) | super
end
#
# Returns the methods available to this delegate object as the union
# of this object's and \_\_getobj\_\_ protected methods.
#
def protected_methods(all=true)
__getobj__.protected_methods(all) | super
end
# Note: no need to specialize private_methods, since they are not forwarded
#
# Returns true if two objects are considered of equal value.
#
def ==(obj)
return true if obj.equal?(self)
self.__getobj__ == obj
end
#
# Returns true if two objects are not considered of equal value.
#
def !=(obj)
return false if obj.equal?(self)
__getobj__ != obj
end
#
# Returns true if two objects are considered of equal value.
#
def eql?(obj)
return true if obj.equal?(self)
obj.eql?(__getobj__)
end
#
# Delegates ! to the \_\_getobj\_\_
#
def !
!__getobj__
end
#
# This method must be overridden by subclasses and should return the object
# method calls are being delegated to.
#
def __getobj__
__raise__ ::NotImplementedError, "need to define `__getobj__'"
end
#
# This method must be overridden by subclasses and change the object delegate
# to _obj_.
#
def __setobj__(obj)
__raise__ ::NotImplementedError, "need to define `__setobj__'"
end
#
# Serialization support for the object returned by \_\_getobj\_\_.
#
def marshal_dump
ivars = instance_variables.reject {|var| /\A@delegate_/ =~ var}
[
:__v2__,
ivars, ivars.map {|var| instance_variable_get(var)},
__getobj__
]
end
#
# Reinitializes delegation from a serialized object.
#
def marshal_load(data)
version, vars, values, obj = data
if version == :__v2__
vars.each_with_index {|var, i| instance_variable_set(var, values[i])}
__setobj__(obj)
else
__setobj__(data)
end
end
def initialize_clone(obj, freeze: nil) # :nodoc:
self.__setobj__(obj.__getobj__.clone(freeze: freeze))
end
def initialize_dup(obj) # :nodoc:
self.__setobj__(obj.__getobj__.dup)
end
private :initialize_clone, :initialize_dup
##
# :method: freeze
# Freeze both the object returned by \_\_getobj\_\_ and self.
#
def freeze
__getobj__.freeze
super()
end
@delegator_api = self.public_instance_methods
def self.public_api # :nodoc:
@delegator_api
end
end
##
# A concrete implementation of Delegator, this class provides the means to
# delegate all supported method calls to the object passed into the constructor
# and even to change the object being delegated to at a later time with
# #__setobj__.
#
# class User
# def born_on
# Date.new(1989, 9, 10)
# end
# end
#
# require 'delegate'
#
# class UserDecorator < SimpleDelegator
# def birth_year
# born_on.year
# end
# end
#
# decorated_user = UserDecorator.new(User.new)
# decorated_user.birth_year #=> 1989
# decorated_user.__getobj__ #=> #<User: ...>
#
# A SimpleDelegator instance can take advantage of the fact that SimpleDelegator
# is a subclass of +Delegator+ to call <tt>super</tt> to have methods called on
# the object being delegated to.
#
# class SuperArray < SimpleDelegator
# def [](*args)
# super + 1
# end
# end
#
# SuperArray.new([1])[0] #=> 2
#
# Here's a simple example that takes advantage of the fact that
# SimpleDelegator's delegation object can be changed at any time.
#
# class Stats
# def initialize
# @source = SimpleDelegator.new([])
# end
#
# def stats(records)
# @source.__setobj__(records)
#
# "Elements: #{@source.size}\n" +
# " Non-Nil: #{@source.compact.size}\n" +
# " Unique: #{@source.uniq.size}\n"
# end
# end
#
# s = Stats.new
# puts s.stats(%w{James Edward Gray II})
# puts
# puts s.stats([1, 2, 3, nil, 4, 5, 1, 2])
#
# Prints:
#
# Elements: 4
# Non-Nil: 4
# Unique: 4
#
# Elements: 8
# Non-Nil: 7
# Unique: 6
#
class SimpleDelegator < Delegator
# Returns the current object method calls are being delegated to.
def __getobj__
unless defined?(@delegate_sd_obj)
return yield if block_given?
__raise__ ::ArgumentError, "not delegated"
end
@delegate_sd_obj
end
#
# Changes the delegate object to _obj_.
#
# It's important to note that this does *not* cause SimpleDelegator's methods
# to change. Because of this, you probably only want to change delegation
# to objects of the same type as the original delegate.
#
# Here's an example of changing the delegation object.
#
# names = SimpleDelegator.new(%w{James Edward Gray II})
# puts names[1] # => Edward
# names.__setobj__(%w{Gavin Sinclair})
# puts names[1] # => Sinclair
#
def __setobj__(obj)
__raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_sd_obj = obj
end
end
def Delegator.delegating_block(mid) # :nodoc:
lambda do |*args, &block|
target = self.__getobj__
target.__send__(mid, *args, &block)
end.ruby2_keywords
end
#
# The primary interface to this library. Use to setup delegation when defining
# your class.
#
# class MyClass < DelegateClass(ClassToDelegateTo) # Step 1
# def initialize
# super(obj_of_ClassToDelegateTo) # Step 2
# end
# end
#
# or:
#
# MyClass = DelegateClass(ClassToDelegateTo) do # Step 1
# def initialize
# super(obj_of_ClassToDelegateTo) # Step 2
# end
# end
#
# Here's a sample of use from Tempfile which is really a File object with a
# few special rules about storage location and when the File should be
# deleted. That makes for an almost textbook perfect example of how to use
# delegation.
#
# class Tempfile < DelegateClass(File)
# # constant and class member data initialization...
#
# def initialize(basename, tmpdir=Dir::tmpdir)
# # build up file path/name in var tmpname...
#
# @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
#
# # ...
#
# super(@tmpfile)
#
# # below this point, all methods of File are supported...
# end
#
# # ...
# end
#
def DelegateClass(superclass, &block)
klass = Class.new(Delegator)
ignores = [*::Delegator.public_api, :to_s, :inspect, :=~, :!~, :===]
protected_instance_methods = superclass.protected_instance_methods
protected_instance_methods -= ignores
public_instance_methods = superclass.public_instance_methods
public_instance_methods -= ignores
klass.module_eval do
def __getobj__ # :nodoc:
unless defined?(@delegate_dc_obj)
return yield if block_given?
__raise__ ::ArgumentError, "not delegated"
end
@delegate_dc_obj
end
def __setobj__(obj) # :nodoc:
__raise__ ::ArgumentError, "cannot delegate to self" if self.equal?(obj)
@delegate_dc_obj = obj
end
protected_instance_methods.each do |method|
define_method(method, Delegator.delegating_block(method))
protected method
end
public_instance_methods.each do |method|
define_method(method, Delegator.delegating_block(method))
end
end
klass.define_singleton_method :public_instance_methods do |all=true|
super(all) | superclass.public_instance_methods
end
klass.define_singleton_method :protected_instance_methods do |all=true|
super(all) | superclass.protected_instance_methods
end
klass.define_singleton_method :instance_methods do |all=true|
super(all) | superclass.instance_methods
end
klass.define_singleton_method :public_instance_method do |name|
super(name)
rescue NameError
raise unless self.public_instance_methods.include?(name)
superclass.public_instance_method(name)
end
klass.define_singleton_method :instance_method do |name|
super(name)
rescue NameError
raise unless self.instance_methods.include?(name)
superclass.instance_method(name)
end
klass.module_eval(&block) if block
return klass
end
share/ruby/syntax_suggest/display_invalid_blocks.rb 0000644 00000003403 15173547340 0016764 0 ustar 00 # frozen_string_literal: true
require_relative "capture_code_context"
require_relative "display_code_with_line_numbers"
module SyntaxSuggest
# Used for formatting invalid blocks
class DisplayInvalidBlocks
attr_reader :filename
def initialize(code_lines:, blocks:, io: $stderr, filename: nil, terminal: DEFAULT_VALUE)
@io = io
@blocks = Array(blocks)
@filename = filename
@code_lines = code_lines
@terminal = terminal == DEFAULT_VALUE ? io.isatty : terminal
end
def document_ok?
@blocks.none? { |b| !b.hidden? }
end
def call
if document_ok?
return self
end
if filename
@io.puts("--> #{filename}")
@io.puts
end
@blocks.each do |block|
display_block(block)
end
self
end
private def display_block(block)
# Build explanation
explain = ExplainSyntax.new(
code_lines: block.lines
).call
# Enhance code output
# Also handles several ambiguious cases
lines = CaptureCodeContext.new(
blocks: block,
code_lines: @code_lines
).call
# Build code output
document = DisplayCodeWithLineNumbers.new(
lines: lines,
terminal: @terminal,
highlight_lines: block.lines
).call
# Output syntax error explanation
explain.errors.each do |e|
@io.puts e
end
@io.puts
# Output code
@io.puts(document)
end
private def code_with_context
lines = CaptureCodeContext.new(
blocks: @blocks,
code_lines: @code_lines
).call
DisplayCodeWithLineNumbers.new(
lines: lines,
terminal: @terminal,
highlight_lines: @invalid_lines
).call
end
end
end
share/ruby/syntax_suggest/left_right_lex_count.rb 0000644 00000010074 15173547340 0016465 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Find mis-matched syntax based on lexical count
#
# Used for detecting missing pairs of elements
# each keyword needs an end, each '{' needs a '}'
# etc.
#
# Example:
#
# left_right = LeftRightLexCount.new
# left_right.count_kw
# left_right.missing.first
# # => "end"
#
# left_right = LeftRightLexCount.new
# source = "{ a: b, c: d" # Note missing '}'
# LexAll.new(source: source).each do |lex|
# left_right.count_lex(lex)
# end
# left_right.missing.first
# # => "}"
class LeftRightLexCount
def initialize
@kw_count = 0
@end_count = 0
@count_for_char = {
"{" => 0,
"}" => 0,
"[" => 0,
"]" => 0,
"(" => 0,
")" => 0,
"|" => 0
}
end
def count_kw
@kw_count += 1
end
def count_end
@end_count += 1
end
# Count source code characters
#
# Example:
#
# left_right = LeftRightLexCount.new
# left_right.count_lex(LexValue.new(1, :on_lbrace, "{", Ripper::EXPR_BEG))
# left_right.count_for_char("{")
# # => 1
# left_right.count_for_char("}")
# # => 0
def count_lex(lex)
case lex.type
when :on_tstring_content
# ^^^
# Means it's a string or a symbol `"{"` rather than being
# part of a data structure (like a hash) `{ a: b }`
# ignore it.
when :on_words_beg, :on_symbos_beg, :on_qwords_beg,
:on_qsymbols_beg, :on_regexp_beg, :on_tstring_beg
# ^^^
# Handle shorthand syntaxes like `%Q{ i am a string }`
#
# The start token will be the full thing `%Q{` but we
# need to count it as if it's a `{`. Any token
# can be used
char = lex.token[-1]
@count_for_char[char] += 1 if @count_for_char.key?(char)
when :on_embexpr_beg
# ^^^
# Embedded string expressions like `"#{foo} <-embed"`
# are parsed with chars:
#
# `#{` as :on_embexpr_beg
# `}` as :on_embexpr_end
#
# We cannot ignore both :on_emb_expr_beg and :on_embexpr_end
# because sometimes the lexer thinks something is an embed
# string end, when it is not like `lol = }` (no clue why).
#
# When we see `#{` count it as a `{` or we will
# have a mis-match count.
#
case lex.token
when "\#{"
@count_for_char["{"] += 1
end
else
@end_count += 1 if lex.is_end?
@kw_count += 1 if lex.is_kw?
@count_for_char[lex.token] += 1 if @count_for_char.key?(lex.token)
end
end
def count_for_char(char)
@count_for_char[char]
end
# Returns an array of missing syntax characters
# or `"end"` or `"keyword"`
#
# left_right.missing
# # => ["}"]
def missing
out = missing_pairs
out << missing_pipe
out << missing_keyword_end
out.compact!
out
end
PAIRS = {
"{" => "}",
"[" => "]",
"(" => ")"
}.freeze
# Opening characters like `{` need closing characters # like `}`.
#
# When a mis-match count is detected, suggest the
# missing member.
#
# For example if there are 3 `}` and only two `{`
# return `"{"`
private def missing_pairs
PAIRS.map do |(left, right)|
case @count_for_char[left] <=> @count_for_char[right]
when 1
right
when 0
nil
when -1
left
end
end
end
# Keywords need ends and ends need keywords
#
# If we have more keywords, there's a missing `end`
# if we have more `end`-s, there's a missing keyword
private def missing_keyword_end
case @kw_count <=> @end_count
when 1
"end"
when 0
nil
when -1
"keyword"
end
end
# Pipes come in pairs.
# If there's an odd number of pipes then we
# are missing one
private def missing_pipe
if @count_for_char["|"].odd?
"|"
end
end
end
end
share/ruby/syntax_suggest/around_block_scan.rb 0000644 00000015633 15173547340 0015732 0 ustar 00 # frozen_string_literal: true
require_relative "scan_history"
module SyntaxSuggest
# This class is useful for exploring contents before and after
# a block
#
# It searches above and below the passed in block to match for
# whatever criteria you give it:
#
# Example:
#
# def dog # 1
# puts "bark" # 2
# puts "bark" # 3
# end # 4
#
# scan = AroundBlockScan.new(
# code_lines: code_lines
# block: CodeBlock.new(lines: code_lines[1])
# )
#
# scan.scan_while { true }
#
# puts scan.before_index # => 0
# puts scan.after_index # => 3
#
class AroundBlockScan
def initialize(code_lines:, block:)
@code_lines = code_lines
@orig_indent = block.current_indent
@stop_after_kw = false
@force_add_empty = false
@force_add_hidden = false
@target_indent = nil
@scanner = ScanHistory.new(code_lines: code_lines, block: block)
end
# When using this flag, `scan_while` will
# bypass the block it's given and always add a
# line that responds truthy to `CodeLine#hidden?`
#
# Lines are hidden when they've been evaluated by
# the parser as part of a block and found to contain
# valid code.
def force_add_hidden
@force_add_hidden = true
self
end
# When using this flag, `scan_while` will
# bypass the block it's given and always add a
# line that responds truthy to `CodeLine#empty?`
#
# Empty lines contain no code, only whitespace such
# as leading spaces a newline.
def force_add_empty
@force_add_empty = true
self
end
# Tells `scan_while` to look for mismatched keyword/end-s
#
# When scanning up, if we see more keywords then end-s it will
# stop. This might happen when scanning outside of a method body.
# the first scan line up would be a keyword and this setting would
# trigger a stop.
#
# When scanning down, stop if there are more end-s than keywords.
def stop_after_kw
@stop_after_kw = true
self
end
# Main work method
#
# The scan_while method takes a block that yields lines above and
# below the block. If the yield returns true, the @before_index
# or @after_index are modified to include the matched line.
#
# In addition to yielding individual lines, the internals of this
# object give a mini DSL to handle common situations such as
# stopping if we've found a keyword/end mis-match in one direction
# or the other.
def scan_while
stop_next_up = false
stop_next_down = false
@scanner.scan(
up: ->(line, kw_count, end_count) {
next false if stop_next_up
next true if @force_add_hidden && line.hidden?
next true if @force_add_empty && line.empty?
if @stop_after_kw && kw_count > end_count
stop_next_up = true
end
yield line
},
down: ->(line, kw_count, end_count) {
next false if stop_next_down
next true if @force_add_hidden && line.hidden?
next true if @force_add_empty && line.empty?
if @stop_after_kw && end_count > kw_count
stop_next_down = true
end
yield line
}
)
self
end
# Scanning is intentionally conservative because
# we have no way of rolling back an agressive block (at this time)
#
# If a block was stopped for some trivial reason, (like an empty line)
# but the next line would have caused it to be balanced then we
# can check that condition and grab just one more line either up or
# down.
#
# For example, below if we're scanning up, line 2 might cause
# the scanning to stop. This is because empty lines might
# denote logical breaks where the user intended to chunk code
# which is a good place to stop and check validity. Unfortunately
# it also means we might have a "dangling" keyword or end.
#
# 1 def bark
# 2
# 3 end
#
# If lines 2 and 3 are in the block, then when this method is
# run it would see it is unbalanced, but that acquiring line 1
# would make it balanced, so that's what it does.
def lookahead_balance_one_line
kw_count = 0
end_count = 0
lines.each do |line|
kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
end
return self if kw_count == end_count # nothing to balance
@scanner.commit_if_changed # Rollback point if we don't find anything to optimize
# Try to eat up empty lines
@scanner.scan(
up: ->(line, _, _) { line.hidden? || line.empty? },
down: ->(line, _, _) { line.hidden? || line.empty? }
)
# More ends than keywords, check if we can balance expanding up
next_up = @scanner.next_up
next_down = @scanner.next_down
case end_count - kw_count
when 1
if next_up&.is_kw? && next_up.indent >= @target_indent
@scanner.scan(
up: ->(line, _, _) { line == next_up },
down: ->(line, _, _) { false }
)
@scanner.commit_if_changed
end
when -1
if next_down&.is_end? && next_down.indent >= @target_indent
@scanner.scan(
up: ->(line, _, _) { false },
down: ->(line, _, _) { line == next_down }
)
@scanner.commit_if_changed
end
end
# Rollback any uncommitted changes
@scanner.stash_changes
self
end
# Finds code lines at the same or greater indentation and adds them
# to the block
def scan_neighbors_not_empty
@target_indent = @orig_indent
scan_while { |line| line.not_empty? && line.indent >= @target_indent }
end
# Scan blocks based on indentation of next line above/below block
#
# Determines indentaion of the next line above/below the current block.
#
# Normally this is called when a block has expanded to capture all "neighbors"
# at the same (or greater) indentation and needs to expand out. For example
# the `def/end` lines surrounding a method.
def scan_adjacent_indent
before_after_indent = []
before_after_indent << (@scanner.next_up&.indent || 0)
before_after_indent << (@scanner.next_down&.indent || 0)
@target_indent = before_after_indent.min
scan_while { |line| line.not_empty? && line.indent >= @target_indent }
self
end
# Return the currently matched lines as a `CodeBlock`
#
# When a `CodeBlock` is created it will gather metadata about
# itself, so this is not a free conversion. Avoid allocating
# more CodeBlock's than needed
def code_block
CodeBlock.new(lines: lines)
end
# Returns the lines matched by the current scan as an
# array of CodeLines
def lines
@scanner.lines
end
# Managable rspec errors
def inspect
"#<#{self.class}:0x0000123843lol >"
end
end
end
share/ruby/syntax_suggest/block_expand.rb 0000644 00000011577 15173547340 0014720 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# This class is responsible for taking a code block that exists
# at a far indentaion and then iteratively increasing the block
# so that it captures everything within the same indentation block.
#
# def dog
# puts "bow"
# puts "wow"
# end
#
# block = BlockExpand.new(code_lines: code_lines)
# .call(CodeBlock.new(lines: code_lines[1]))
#
# puts block.to_s
# # => puts "bow"
# puts "wow"
#
#
# Once a code block has captured everything at a given indentation level
# then it will expand to capture surrounding indentation.
#
# block = BlockExpand.new(code_lines: code_lines)
# .call(block)
#
# block.to_s
# # => def dog
# puts "bow"
# puts "wow"
# end
#
class BlockExpand
def initialize(code_lines:)
@code_lines = code_lines
end
# Main interface. Expand current indentation, before
# expanding to a lower indentation
def call(block)
if (next_block = expand_neighbors(block))
next_block
else
expand_indent(block)
end
end
# Expands code to the next lowest indentation
#
# For example:
#
# 1 def dog
# 2 print "dog"
# 3 end
#
# If a block starts on line 2 then it has captured all it's "neighbors" (code at
# the same indentation or higher). To continue expanding, this block must capture
# lines one and three which are at a different indentation level.
#
# This method allows fully expanded blocks to decrease their indentation level (so
# they can expand to capture more code up and down). It does this conservatively
# as there's no undo (currently).
def expand_indent(block)
now = AroundBlockScan.new(code_lines: @code_lines, block: block)
.force_add_hidden
.stop_after_kw
.scan_adjacent_indent
now.lookahead_balance_one_line
now.code_block
end
# A neighbor is code that is at or above the current indent line.
#
# First we build a block with all neighbors. If we can't go further
# then we decrease the indentation threshold and expand via indentation
# i.e. `expand_indent`
#
# Handles two general cases.
#
# ## Case #1: Check code inside of methods/classes/etc.
#
# It's important to note, that not everything in a given indentation level can be parsed
# as valid code even if it's part of valid code. For example:
#
# 1 hash = {
# 2 name: "richard",
# 3 dog: "cinco",
# 4 }
#
# In this case lines 2 and 3 will be neighbors, but they're invalid until `expand_indent`
# is called on them.
#
# When we are adding code within a method or class (at the same indentation level),
# use the empty lines to denote the programmer intended logical chunks.
# Stop and check each one. For example:
#
# 1 def dog
# 2 print "dog"
# 3
# 4 hash = {
# 5 end
#
# If we did not stop parsing at empty newlines then the block might mistakenly grab all
# the contents (lines 2, 3, and 4) and report them as being problems, instead of only
# line 4.
#
# ## Case #2: Expand/grab other logical blocks
#
# Once the search algorithm has converted all lines into blocks at a given indentation
# it will then `expand_indent`. Once the blocks that generates are expanded as neighbors
# we then begin seeing neighbors being other logical blocks i.e. a block's neighbors
# may be another method or class (something with keywords/ends).
#
# For example:
#
# 1 def bark
# 2
# 3 end
# 4
# 5 def sit
# 6 end
#
# In this case if lines 4, 5, and 6 are in a block when it tries to expand neighbors
# it will expand up. If it stops after line 2 or 3 it may cause problems since there's a
# valid kw/end pair, but the block will be checked without it.
#
# We try to resolve this edge case with `lookahead_balance_one_line` below.
def expand_neighbors(block)
now = AroundBlockScan.new(code_lines: @code_lines, block: block)
# Initial scan
now
.force_add_hidden
.stop_after_kw
.scan_neighbors_not_empty
# Slurp up empties
now
.scan_while { |line| line.empty? }
# If next line is kw and it will balance us, take it
expanded_lines = now
.lookahead_balance_one_line
.lines
# Don't allocate a block if it won't be used
#
# If nothing was taken, return nil to indicate that status
# used in `def call` to determine if
# we need to expand up/out (`expand_indent`)
if block.lines == expanded_lines
nil
else
CodeBlock.new(lines: expanded_lines)
end
end
# Managable rspec errors
def inspect
"#<SyntaxSuggest::CodeBlock:0x0000123843lol >"
end
end
end
share/ruby/syntax_suggest/pathname_from_message.rb 0000644 00000002656 15173547340 0016611 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Converts a SyntaxError message to a path
#
# Handles the case where the filename has a colon in it
# such as on a windows file system: https://github.com/ruby/syntax_suggest/issues/111
#
# Example:
#
# message = "/tmp/scratch:2:in `require_relative': /private/tmp/bad.rb:1: syntax error, unexpected `end' (SyntaxError)"
# puts PathnameFromMessage.new(message).call.name
# # => "/tmp/scratch.rb"
#
class PathnameFromMessage
EVAL_RE = /^\(eval\):\d+/
STREAMING_RE = /^-:\d+/
attr_reader :name
def initialize(message, io: $stderr)
@line = message.lines.first
@parts = @line.split(":")
@guess = []
@name = nil
@io = io
end
def call
if skip_missing_file_name?
if ENV["SYNTAX_SUGGEST_DEBUG"]
@io.puts "SyntaxSuggest: Could not find filename from #{@line.inspect}"
end
else
until stop?
@guess << @parts.shift
@name = Pathname(@guess.join(":"))
end
if @parts.empty?
@io.puts "SyntaxSuggest: Could not find filename from #{@line.inspect}"
@name = nil
end
end
self
end
def stop?
return true if @parts.empty?
return false if @guess.empty?
@name&.exist?
end
def skip_missing_file_name?
@line.match?(EVAL_RE) || @line.match?(STREAMING_RE)
end
end
end
share/ruby/syntax_suggest/parse_blocks_from_indent_line.rb 0000644 00000003000 15173547340 0020307 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# This class is responsible for generating initial code blocks
# that will then later be expanded.
#
# The biggest concern when guessing code blocks, is accidentally
# grabbing one that contains only an "end". In this example:
#
# def dog
# begonn # mispelled `begin`
# puts "bark"
# end
# end
#
# The following lines would be matched (from bottom to top):
#
# 1) end
#
# 2) puts "bark"
# end
#
# 3) begonn
# puts "bark"
# end
#
# At this point it has no where else to expand, and it will yield this inner
# code as a block
class ParseBlocksFromIndentLine
attr_reader :code_lines
def initialize(code_lines:)
@code_lines = code_lines
end
# Builds blocks from bottom up
def each_neighbor_block(target_line)
scan = AroundBlockScan.new(code_lines: code_lines, block: CodeBlock.new(lines: target_line))
.force_add_empty
.force_add_hidden
.scan_while { |line| line.indent >= target_line.indent }
neighbors = scan.code_block.lines
block = CodeBlock.new(lines: neighbors)
if neighbors.length <= 2 || block.valid?
yield block
else
until neighbors.empty?
lines = [neighbors.pop]
while (block = CodeBlock.new(lines: lines)) && block.invalid? && neighbors.any?
lines.prepend neighbors.pop
end
yield block if block
end
end
end
end
end
share/ruby/syntax_suggest/api.rb 0000644 00000012241 15173547340 0013025 0 ustar 00 # frozen_string_literal: true
require_relative "version"
require "tmpdir"
require "stringio"
require "pathname"
require "ripper"
require "timeout"
module SyntaxSuggest
# Used to indicate a default value that cannot
# be confused with another input.
DEFAULT_VALUE = Object.new.freeze
class Error < StandardError; end
TIMEOUT_DEFAULT = ENV.fetch("SYNTAX_SUGGEST_TIMEOUT", 1).to_i
# SyntaxSuggest.handle_error [Public]
#
# Takes a `SyntaxError` exception, uses the
# error message to locate the file. Then the file
# will be analyzed to find the location of the syntax
# error and emit that location to stderr.
#
# Example:
#
# begin
# require 'bad_file'
# rescue => e
# SyntaxSuggest.handle_error(e)
# end
#
# By default it will re-raise the exception unless
# `re_raise: false`. The message output location
# can be configured using the `io: $stderr` input.
#
# If a valid filename cannot be determined, the original
# exception will be re-raised (even with
# `re_raise: false`).
def self.handle_error(e, re_raise: true, io: $stderr)
unless e.is_a?(SyntaxError)
io.puts("SyntaxSuggest: Must pass a SyntaxError, got: #{e.class}")
raise e
end
file = PathnameFromMessage.new(e.message, io: io).call.name
raise e unless file
io.sync = true
call(
io: io,
source: file.read,
filename: file
)
raise e if re_raise
end
# SyntaxSuggest.call [Private]
#
# Main private interface
def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: DEFAULT_VALUE, timeout: TIMEOUT_DEFAULT, io: $stderr)
search = nil
filename = nil if filename == DEFAULT_VALUE
Timeout.timeout(timeout) do
record_dir ||= ENV["DEBUG"] ? "tmp" : nil
search = CodeSearch.new(source, record_dir: record_dir).call
end
blocks = search.invalid_blocks
DisplayInvalidBlocks.new(
io: io,
blocks: blocks,
filename: filename,
terminal: terminal,
code_lines: search.code_lines
).call
rescue Timeout::Error => e
io.puts "Search timed out SYNTAX_SUGGEST_TIMEOUT=#{timeout}, run with SYNTAX_SUGGEST_DEBUG=1 for more info"
io.puts e.backtrace.first(3).join($/)
end
# SyntaxSuggest.record_dir [Private]
#
# Used to generate a unique directory to record
# search steps for debugging
def self.record_dir(dir)
time = Time.now.strftime("%Y-%m-%d-%H-%M-%s-%N")
dir = Pathname(dir)
dir.join(time).tap { |path|
path.mkpath
alias_dir = dir.join("last")
FileUtils.rm_rf(alias_dir) if alias_dir.exist?
FileUtils.ln_sf(time, alias_dir)
}
end
# SyntaxSuggest.valid_without? [Private]
#
# This will tell you if the `code_lines` would be valid
# if you removed the `without_lines`. In short it's a
# way to detect if we've found the lines with syntax errors
# in our document yet.
#
# code_lines = [
# CodeLine.new(line: "def foo\n", index: 0)
# CodeLine.new(line: " def bar\n", index: 1)
# CodeLine.new(line: "end\n", index: 2)
# ]
#
# SyntaxSuggest.valid_without?(
# without_lines: code_lines[1],
# code_lines: code_lines
# ) # => true
#
# SyntaxSuggest.valid?(code_lines) # => false
def self.valid_without?(without_lines:, code_lines:)
lines = code_lines - Array(without_lines).flatten
if lines.empty?
true
else
valid?(lines)
end
end
# SyntaxSuggest.invalid? [Private]
#
# Opposite of `SyntaxSuggest.valid?`
def self.invalid?(source)
source = source.join if source.is_a?(Array)
source = source.to_s
Ripper.new(source).tap(&:parse).error?
end
# SyntaxSuggest.valid? [Private]
#
# Returns truthy if a given input source is valid syntax
#
# SyntaxSuggest.valid?(<<~EOM) # => true
# def foo
# end
# EOM
#
# SyntaxSuggest.valid?(<<~EOM) # => false
# def foo
# def bar # Syntax error here
# end
# EOM
#
# You can also pass in an array of lines and they'll be
# joined before evaluating
#
# SyntaxSuggest.valid?(
# [
# "def foo\n",
# "end\n"
# ]
# ) # => true
#
# SyntaxSuggest.valid?(
# [
# "def foo\n",
# " def bar\n", # Syntax error here
# "end\n"
# ]
# ) # => false
#
# As an FYI the CodeLine class instances respond to `to_s`
# so passing a CodeLine in as an object or as an array
# will convert it to it's code representation.
def self.valid?(source)
!invalid?(source)
end
end
# Integration
require_relative "cli"
# Core logic
require_relative "code_search"
require_relative "code_frontier"
require_relative "explain_syntax"
require_relative "clean_document"
# Helpers
require_relative "lex_all"
require_relative "code_line"
require_relative "code_block"
require_relative "block_expand"
require_relative "ripper_errors"
require_relative "priority_queue"
require_relative "unvisited_lines"
require_relative "around_block_scan"
require_relative "priority_engulf_queue"
require_relative "pathname_from_message"
require_relative "display_invalid_blocks"
require_relative "parse_blocks_from_indent_line"
share/ruby/syntax_suggest/explain_syntax.rb 0000644 00000004641 15173547340 0015327 0 ustar 00 # frozen_string_literal: true
require_relative "left_right_lex_count"
module SyntaxSuggest
# Explains syntax errors based on their source
#
# example:
#
# source = "def foo; puts 'lol'" # Note missing end
# explain ExplainSyntax.new(
# code_lines: CodeLine.from_source(source)
# ).call
# explain.errors.first
# # => "Unmatched keyword, missing `end' ?"
#
# When the error cannot be determined by lexical counting
# then ripper is run against the input and the raw ripper
# errors returned.
#
# Example:
#
# source = "1 * " # Note missing a second number
# explain ExplainSyntax.new(
# code_lines: CodeLine.from_source(source)
# ).call
# explain.errors.first
# # => "syntax error, unexpected end-of-input"
class ExplainSyntax
INVERSE = {
"{" => "}",
"}" => "{",
"[" => "]",
"]" => "[",
"(" => ")",
")" => "(",
"|" => "|"
}.freeze
def initialize(code_lines:)
@code_lines = code_lines
@left_right = LeftRightLexCount.new
@missing = nil
end
def call
@code_lines.each do |line|
line.lex.each do |lex|
@left_right.count_lex(lex)
end
end
self
end
# Returns an array of missing elements
#
# For example this:
#
# ExplainSyntax.new(code_lines: lines).missing
# # => ["}"]
#
# Would indicate that the source is missing
# a `}` character in the source code
def missing
@missing ||= @left_right.missing
end
# Converts a missing string to
# an human understandable explanation.
#
# Example:
#
# explain.why("}")
# # => "Unmatched `{', missing `}' ?"
#
def why(miss)
case miss
when "keyword"
"Unmatched `end', missing keyword (`do', `def`, `if`, etc.) ?"
when "end"
"Unmatched keyword, missing `end' ?"
else
inverse = INVERSE.fetch(miss) {
raise "Unknown explain syntax char or key: #{miss.inspect}"
}
"Unmatched `#{inverse}', missing `#{miss}' ?"
end
end
# Returns an array of syntax error messages
#
# If no missing pairs are found it falls back
# on the original ripper error messages
def errors
if missing.empty?
return RipperErrors.new(@code_lines.map(&:original).join).call.errors
end
missing.map { |miss| why(miss) }
end
end
end
share/ruby/syntax_suggest/cli.rb 0000644 00000006166 15173547340 0013034 0 ustar 00 # frozen_string_literal: true
require "pathname"
require "optparse"
module SyntaxSuggest
# All the logic of the exe/syntax_suggest CLI in one handy spot
#
# Cli.new(argv: ["--help"]).call
# Cli.new(argv: ["<path/to/file>.rb"]).call
# Cli.new(argv: ["<path/to/file>.rb", "--record=tmp"]).call
# Cli.new(argv: ["<path/to/file>.rb", "--terminal"]).call
#
class Cli
attr_accessor :options
# ARGV is Everything passed to the executable, does not include executable name
#
# All other intputs are dependency injection for testing
def initialize(argv:, exit_obj: Kernel, io: $stdout, env: ENV)
@options = {}
@parser = nil
options[:record_dir] = env["SYNTAX_SUGGEST_RECORD_DIR"]
options[:record_dir] = "tmp" if env["DEBUG"]
options[:terminal] = SyntaxSuggest::DEFAULT_VALUE
@io = io
@argv = argv
@exit_obj = exit_obj
end
def call
if @argv.empty?
# Display help if raw command
parser.parse! %w[--help]
return
else
# Mutates @argv
parse
return if options[:exit]
end
file_name = @argv.first
if file_name.nil?
@io.puts "No file given"
@exit_obj.exit(1)
return
end
file = Pathname(file_name)
if !file.exist?
@io.puts "file not found: #{file.expand_path} "
@exit_obj.exit(1)
return
end
@io.puts "Record dir: #{options[:record_dir]}" if options[:record_dir]
display = SyntaxSuggest.call(
io: @io,
source: file.read,
filename: file.expand_path,
terminal: options.fetch(:terminal, SyntaxSuggest::DEFAULT_VALUE),
record_dir: options[:record_dir]
)
if display.document_ok?
@io.puts "Syntax OK"
@exit_obj.exit(0)
else
@exit_obj.exit(1)
end
end
def parse
parser.parse!(@argv)
self
end
def parser
@parser ||= OptionParser.new do |opts|
opts.banner = <<~EOM
Usage: syntax_suggest <file> [options]
Parses a ruby source file and searches for syntax error(s) such as
unexpected `end', expecting end-of-input.
Example:
$ syntax_suggest dog.rb
# ...
> 10 defdog
> 15 end
ENV options:
SYNTAX_SUGGEST_RECORD_DIR=<dir>
Records the steps used to search for a syntax error
to the given directory
Options:
EOM
opts.version = SyntaxSuggest::VERSION
opts.on("--help", "Help - displays this message") do |v|
@io.puts opts
options[:exit] = true
@exit_obj.exit
end
opts.on("--record <dir>", "Records the steps used to search for a syntax error to the given directory") do |v|
options[:record_dir] = v
end
opts.on("--terminal", "Enable terminal highlighting") do |v|
options[:terminal] = true
end
opts.on("--no-terminal", "Disable terminal highlighting") do |v|
options[:terminal] = false
end
end
end
end
end
share/ruby/syntax_suggest/version.rb 0000644 00000000114 15173547340 0013735 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
VERSION = "1.1.0"
end
share/ruby/syntax_suggest/core_ext.rb 0000644 00000006004 15173547340 0014064 0 ustar 00 # frozen_string_literal: true
# Ruby 3.2+ has a cleaner way to hook into Ruby that doesn't use `require`
if SyntaxError.method_defined?(:detailed_message)
module SyntaxSuggest
# Mini String IO [Private]
#
# Acts like a StringIO with reduced API, but without having to require that
# class.
class MiniStringIO
def initialize(isatty: $stderr.isatty)
@string = +""
@isatty = isatty
end
attr_reader :isatty
def puts(value = $/, **)
@string << value
end
attr_reader :string
end
# SyntaxSuggest.record_dir [Private]
#
# Used to monkeypatch SyntaxError via Module.prepend
def self.module_for_detailed_message
Module.new {
def detailed_message(highlight: true, syntax_suggest: true, **kwargs)
return super unless syntax_suggest
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
message = super
if path
file = Pathname.new(path)
io = SyntaxSuggest::MiniStringIO.new
SyntaxSuggest.call(
io: io,
source: file.read,
filename: file,
terminal: highlight
)
annotation = io.string
annotation += "\n" unless annotation.end_with?("\n")
annotation + message
else
message
end
rescue => e
if ENV["SYNTAX_SUGGEST_DEBUG"]
$stderr.warn(e.message)
$stderr.warn(e.backtrace)
end
# Ignore internal errors
message
end
}
end
end
SyntaxError.prepend(SyntaxSuggest.module_for_detailed_message)
else
autoload :Pathname, "pathname"
#--
# Monkey patch kernel to ensure that all `require` calls call the same
# method
#++
module Kernel
# :stopdoc:
module_function
alias_method :syntax_suggest_original_require, :require
alias_method :syntax_suggest_original_require_relative, :require_relative
alias_method :syntax_suggest_original_load, :load
def load(file, wrap = false)
syntax_suggest_original_load(file)
rescue SyntaxError => e
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
SyntaxSuggest.handle_error(e)
end
def require(file)
syntax_suggest_original_require(file)
rescue SyntaxError => e
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
SyntaxSuggest.handle_error(e)
end
def require_relative(file)
if Pathname.new(file).absolute?
syntax_suggest_original_require file
else
relative_from = caller_locations(1..1).first
relative_from_path = relative_from.absolute_path || relative_from.path
syntax_suggest_original_require File.expand_path("../#{file}", relative_from_path)
end
rescue SyntaxError => e
require "syntax_suggest/api" unless defined?(SyntaxSuggest::DEFAULT_VALUE)
SyntaxSuggest.handle_error(e)
end
end
end
share/ruby/syntax_suggest/lex_value.rb 0000644 00000003004 15173547340 0014235 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Value object for accessing lex values
#
# This lex:
#
# [1, 0], :on_ident, "describe", CMDARG
#
# Would translate into:
#
# lex.line # => 1
# lex.type # => :on_indent
# lex.token # => "describe"
class LexValue
attr_reader :line, :type, :token, :state
def initialize(line, type, token, state, last_lex = nil)
@line = line
@type = type
@token = token
@state = state
set_kw_end(last_lex)
end
private def set_kw_end(last_lex)
@is_end = false
@is_kw = false
return if type != :on_kw
#
return if last_lex && last_lex.fname? # https://github.com/ruby/ruby/commit/776759e300e4659bb7468e2b97c8c2d4359a2953
case token
when "if", "unless", "while", "until"
# Only count if/unless when it's not a "trailing" if/unless
# https://github.com/ruby/ruby/blob/06b44f819eb7b5ede1ff69cecb25682b56a1d60c/lib/irb/ruby-lex.rb#L374-L375
@is_kw = true unless expr_label?
when "def", "case", "for", "begin", "class", "module", "do"
@is_kw = true
when "end"
@is_end = true
end
end
def fname?
state.allbits?(Ripper::EXPR_FNAME)
end
def ignore_newline?
type == :on_ignored_nl
end
def is_end?
@is_end
end
def is_kw?
@is_kw
end
def expr_beg?
state.anybits?(Ripper::EXPR_BEG)
end
def expr_label?
state.allbits?(Ripper::EXPR_LABEL)
end
end
end
share/ruby/syntax_suggest/lex_all.rb 0000644 00000002171 15173547340 0013675 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Ripper.lex is not guaranteed to lex the entire source document
#
# lex = LexAll.new(source: source)
# lex.each do |value|
# puts value.line
# end
class LexAll
include Enumerable
def initialize(source:, source_lines: nil)
@lex = Ripper::Lexer.new(source, "-", 1).parse.sort_by(&:pos)
lineno = @lex.last.pos.first + 1
source_lines ||= source.lines
last_lineno = source_lines.length
until lineno >= last_lineno
lines = source_lines[lineno..-1]
@lex.concat(
Ripper::Lexer.new(lines.join, "-", lineno + 1).parse.sort_by(&:pos)
)
lineno = @lex.last.pos.first + 1
end
last_lex = nil
@lex.map! { |elem|
last_lex = LexValue.new(elem.pos.first, elem.event, elem.tok, elem.state, last_lex)
}
end
def to_a
@lex
end
def each
return @lex.each unless block_given?
@lex.each do |x|
yield x
end
end
def [](index)
@lex[index]
end
def last
@lex.last
end
end
end
require_relative "lex_value"
share/ruby/syntax_suggest/display_code_with_line_numbers.rb 0000644 00000003424 15173547340 0020513 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Outputs code with highlighted lines
#
# Whatever is passed to this class will be rendered
# even if it is "marked invisible" any filtering of
# output should be done before calling this class.
#
# DisplayCodeWithLineNumbers.new(
# lines: lines,
# highlight_lines: [lines[2], lines[3]]
# ).call
# # =>
# 1
# 2 def cat
# > 3 Dir.chdir
# > 4 end
# 5 end
# 6
class DisplayCodeWithLineNumbers
TERMINAL_HIGHLIGHT = "\e[1;3m" # Bold, italics
TERMINAL_END = "\e[0m"
def initialize(lines:, highlight_lines: [], terminal: false)
@lines = Array(lines).sort
@terminal = terminal
@highlight_line_hash = Array(highlight_lines).each_with_object({}) { |line, h| h[line] = true }
@digit_count = @lines.last&.line_number.to_s.length
end
def call
@lines.map do |line|
format_line(line)
end.join
end
private def format_line(code_line)
# Handle trailing slash lines
code_line.original.lines.map.with_index do |contents, i|
format(
empty: code_line.empty?,
number: (code_line.number + i).to_s,
contents: contents,
highlight: @highlight_line_hash[code_line]
)
end.join
end
private def format(contents:, number:, empty:, highlight: false)
string = +""
string << if highlight
"> "
else
" "
end
string << number.rjust(@digit_count).to_s
if empty
string << contents
else
string << " "
string << TERMINAL_HIGHLIGHT if @terminal && highlight
string << contents
string << TERMINAL_END if @terminal
end
string
end
end
end
share/ruby/syntax_suggest/scan_history.rb 0000644 00000005657 15173547340 0014776 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Scans up/down from the given block
#
# You can try out a change, stash it, or commit it to save for later
#
# Example:
#
# scanner = ScanHistory.new(code_lines: code_lines, block: block)
# scanner.scan(
# up: ->(_, _, _) { true },
# down: ->(_, _, _) { true }
# )
# scanner.changed? # => true
# expect(scanner.lines).to eq(code_lines)
#
# scanner.stash_changes
#
# expect(scanner.lines).to_not eq(code_lines)
class ScanHistory
attr_reader :before_index, :after_index
def initialize(code_lines:, block:)
@code_lines = code_lines
@history = [block]
refresh_index
end
def commit_if_changed
if changed?
@history << CodeBlock.new(lines: @code_lines[before_index..after_index])
end
self
end
# Discards any changes that have not been committed
def stash_changes
refresh_index
self
end
# Discard changes that have not been committed and revert the last commit
#
# Cannot revert the first commit
def revert_last_commit
if @history.length > 1
@history.pop
refresh_index
end
self
end
def changed?
@before_index != current.lines.first.index ||
@after_index != current.lines.last.index
end
# Iterates up and down
#
# Returns line, kw_count, end_count for each iteration
def scan(up:, down:)
kw_count = 0
end_count = 0
up_index = before_lines.reverse_each.take_while do |line|
kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
up.call(line, kw_count, end_count)
end.last&.index
kw_count = 0
end_count = 0
down_index = after_lines.each.take_while do |line|
kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
down.call(line, kw_count, end_count)
end.last&.index
@before_index = if up_index && up_index < @before_index
up_index
else
@before_index
end
@after_index = if down_index && down_index > @after_index
down_index
else
@after_index
end
self
end
def next_up
return nil if @before_index <= 0
@code_lines[@before_index - 1]
end
def next_down
return nil if @after_index >= @code_lines.length
@code_lines[@after_index + 1]
end
def lines
@code_lines[@before_index..@after_index]
end
private def before_lines
@code_lines[0...@before_index] || []
end
# Returns an array of all the CodeLines that exist after
# the currently scanned block
private def after_lines
@code_lines[@after_index.next..-1] || []
end
private def current
@history.last
end
private def refresh_index
@before_index = current.lines.first.index
@after_index = current.lines.last.index
self
end
end
end
share/ruby/syntax_suggest/priority_engulf_queue.rb 0000644 00000002417 15173547340 0016705 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Keeps track of what elements are in the queue in
# priority and also ensures that when one element
# engulfs/covers/eats another that the larger element
# evicts the smaller element
class PriorityEngulfQueue
def initialize
@queue = PriorityQueue.new
end
def to_a
@queue.to_a
end
def empty?
@queue.empty?
end
def length
@queue.length
end
def peek
@queue.peek
end
def pop
@queue.pop
end
def push(block)
prune_engulf(block)
@queue << block
flush_deleted
self
end
private def flush_deleted
while @queue&.peek&.deleted?
@queue.pop
end
end
private def prune_engulf(block)
# If we're about to pop off the same block, we can skip deleting
# things from the frontier this iteration since we'll get it
# on the next iteration
return if @queue.peek && (block <=> @queue.peek) == 1
if block.starts_at != block.ends_at # A block of size 1 cannot engulf another
@queue.to_a.each { |b|
if b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
b.delete
true
end
}
end
end
end
end
share/ruby/syntax_suggest/capture/before_after_keyword_ends.rb 0000644 00000004244 15173547340 0021123 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
module Capture
# Shows surrounding kw/end pairs
#
# The purpose of showing these extra pairs is due to cases
# of ambiguity when only one visible line is matched.
#
# For example:
#
# 1 class Dog
# 2 def bark
# 4 def eat
# 5 end
# 6 end
#
# In this case either line 2 could be missing an `end` or
# line 4 was an extra line added by mistake (it happens).
#
# When we detect the above problem it shows the issue
# as only being on line 2
#
# 2 def bark
#
# Showing "neighbor" keyword pairs gives extra context:
#
# 2 def bark
# 4 def eat
# 5 end
#
#
# Example:
#
# lines = BeforeAfterKeywordEnds.new(
# block: block,
# code_lines: code_lines
# ).call()
#
class BeforeAfterKeywordEnds
def initialize(code_lines:, block:)
@scanner = ScanHistory.new(code_lines: code_lines, block: block)
@original_indent = block.current_indent
end
def call
lines = []
@scanner.scan(
up: ->(line, kw_count, end_count) {
next true if line.empty?
break if line.indent < @original_indent
next true if line.indent != @original_indent
# If we're going up and have one complete kw/end pair, stop
if kw_count != 0 && kw_count == end_count
lines << line
break
end
lines << line if line.is_kw? || line.is_end?
true
},
down: ->(line, kw_count, end_count) {
next true if line.empty?
break if line.indent < @original_indent
next true if line.indent != @original_indent
# if we're going down and have one complete kw/end pair,stop
if kw_count != 0 && kw_count == end_count
lines << line
break
end
lines << line if line.is_kw? || line.is_end?
true
}
)
@scanner.stash_changes
lines
end
end
end
end
share/ruby/syntax_suggest/capture/falling_indent_lines.rb 0000644 00000003127 15173547340 0020071 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
module Capture
# Shows the context around code provided by "falling" indentation
#
# If this is the original code lines:
#
# class OH
# def hello
# it "foo" do
# end
# end
#
# And this is the line that is captured
#
# it "foo" do
#
# It will yield its surrounding context:
#
# class OH
# def hello
# end
# end
#
# Example:
#
# FallingIndentLines.new(
# block: block,
# code_lines: @code_lines
# ).call do |line|
# @lines_to_output << line
# end
#
class FallingIndentLines
def initialize(code_lines:, block:)
@lines = nil
@scanner = ScanHistory.new(code_lines: code_lines, block: block)
@original_indent = block.current_indent
end
def call(&yieldable)
last_indent_up = @original_indent
last_indent_down = @original_indent
@scanner.commit_if_changed
@scanner.scan(
up: ->(line, _, _) {
next true if line.empty?
if line.indent < last_indent_up
yieldable.call(line)
last_indent_up = line.indent
end
true
},
down: ->(line, _, _) {
next true if line.empty?
if line.indent < last_indent_down
yieldable.call(line)
last_indent_down = line.indent
end
true
}
)
@scanner.stash_changes
end
end
end
end
share/ruby/syntax_suggest/code_line.rb 0000644 00000014727 15173547340 0014210 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Represents a single line of code of a given source file
#
# This object contains metadata about the line such as
# amount of indentation, if it is empty or not, and
# lexical data, such as if it has an `end` or a keyword
# in it.
#
# Visibility of lines can be toggled off. Marking a line as invisible
# indicates that it should not be used for syntax checks.
# It's functionally the same as commenting it out.
#
# Example:
#
# line = CodeLine.from_source("def foo\n").first
# line.number => 1
# line.empty? # => false
# line.visible? # => true
# line.mark_invisible
# line.visible? # => false
#
class CodeLine
TRAILING_SLASH = ("\\" + $/).freeze
# Returns an array of CodeLine objects
# from the source string
def self.from_source(source, lines: nil)
lines ||= source.lines
lex_array_for_line = LexAll.new(source: source, source_lines: lines).each_with_object(Hash.new { |h, k| h[k] = [] }) { |lex, hash| hash[lex.line] << lex }
lines.map.with_index do |line, index|
CodeLine.new(
line: line,
index: index,
lex: lex_array_for_line[index + 1]
)
end
end
attr_reader :line, :index, :lex, :line_number, :indent
def initialize(line:, index:, lex:)
@lex = lex
@line = line
@index = index
@original = line
@line_number = @index + 1
strip_line = line.dup
strip_line.lstrip!
@indent = if (@empty = strip_line.empty?)
line.length - 1 # Newline removed from strip_line is not "whitespace"
else
line.length - strip_line.length
end
set_kw_end
end
# Used for stable sort via indentation level
#
# Ruby's sort is not "stable" meaning that when
# multiple elements have the same value, they are
# not guaranteed to return in the same order they
# were put in.
#
# So when multiple code lines have the same indentation
# level, they're sorted by their index value which is unique
# and consistent.
#
# This is mostly needed for consistency of the test suite
def indent_index
@indent_index ||= [indent, index]
end
alias_method :number, :line_number
# Returns true if the code line is determined
# to contain a keyword that matches with an `end`
#
# For example: `def`, `do`, `begin`, `ensure`, etc.
def is_kw?
@is_kw
end
# Returns true if the code line is determined
# to contain an `end` keyword
def is_end?
@is_end
end
# Used to hide lines
#
# The search alorithm will group lines into blocks
# then if those blocks are determined to represent
# valid code they will be hidden
def mark_invisible
@line = ""
end
# Means the line was marked as "invisible"
# Confusingly, "empty" lines are visible...they
# just don't contain any source code other than a newline ("\n").
def visible?
!line.empty?
end
# Opposite or `visible?` (note: different than `empty?`)
def hidden?
!visible?
end
# An `empty?` line is one that was originally left
# empty in the source code, while a "hidden" line
# is one that we've since marked as "invisible"
def empty?
@empty
end
# Opposite of `empty?` (note: different than `visible?`)
def not_empty?
!empty?
end
# Renders the given line
#
# Also allows us to represent source code as
# an array of code lines.
#
# When we have an array of code line elements
# calling `join` on the array will call `to_s`
# on each element, which essentially converts
# it back into it's original source string.
def to_s
line
end
# When the code line is marked invisible
# we retain the original value of it's line
# this is useful for debugging and for
# showing extra context
#
# DisplayCodeWithLineNumbers will render
# all lines given to it, not just visible
# lines, it uses the original method to
# obtain them.
attr_reader :original
# Comparison operator, needed for equality
# and sorting
def <=>(other)
index <=> other.index
end
# [Not stable API]
#
# Lines that have a `on_ignored_nl` type token and NOT
# a `BEG` type seem to be a good proxy for the ability
# to join multiple lines into one.
#
# This predicate method is used to determine when those
# two criteria have been met.
#
# The one known case this doesn't handle is:
#
# Ripper.lex <<~EOM
# a &&
# b ||
# c
# EOM
#
# For some reason this introduces `on_ignore_newline` but with BEG type
def ignore_newline_not_beg?
@ignore_newline_not_beg
end
# Determines if the given line has a trailing slash
#
# lines = CodeLine.from_source(<<~EOM)
# it "foo" \
# EOM
# expect(lines.first.trailing_slash?).to eq(true)
#
def trailing_slash?
last = @lex.last
return false unless last
return false unless last.type == :on_sp
last.token == TRAILING_SLASH
end
# Endless method detection
#
# From https://github.com/ruby/irb/commit/826ae909c9c93a2ddca6f9cfcd9c94dbf53d44ab
# Detecting a "oneliner" seems to need a state machine.
# This can be done by looking mostly at the "state" (last value):
#
# ENDFN -> BEG (token = '=' ) -> END
#
private def set_kw_end
oneliner_count = 0
in_oneliner_def = nil
kw_count = 0
end_count = 0
@ignore_newline_not_beg = false
@lex.each do |lex|
kw_count += 1 if lex.is_kw?
end_count += 1 if lex.is_end?
if lex.type == :on_ignored_nl
@ignore_newline_not_beg = !lex.expr_beg?
end
if in_oneliner_def.nil?
in_oneliner_def = :ENDFN if lex.state.allbits?(Ripper::EXPR_ENDFN)
elsif lex.state.allbits?(Ripper::EXPR_ENDFN)
# Continue
elsif lex.state.allbits?(Ripper::EXPR_BEG)
in_oneliner_def = :BODY if lex.token == "="
elsif lex.state.allbits?(Ripper::EXPR_END)
# We found an endless method, count it
oneliner_count += 1 if in_oneliner_def == :BODY
in_oneliner_def = nil
else
in_oneliner_def = nil
end
end
kw_count -= oneliner_count
@is_kw = (kw_count - end_count) > 0
@is_end = (end_count - kw_count) > 0
end
end
end
share/ruby/syntax_suggest/code_block.rb 0000644 00000004203 15173547340 0014337 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Multiple lines form a singular CodeBlock
#
# Source code is made of multiple CodeBlocks.
#
# Example:
#
# code_block.to_s # =>
# # def foo
# # puts "foo"
# # end
#
# code_block.valid? # => true
# code_block.in_valid? # => false
#
#
class CodeBlock
UNSET = Object.new.freeze
attr_reader :lines, :starts_at, :ends_at
def initialize(lines: [])
@lines = Array(lines)
@valid = UNSET
@deleted = false
@starts_at = @lines.first.number
@ends_at = @lines.last.number
end
def delete
@deleted = true
end
def deleted?
@deleted
end
def visible_lines
@lines.select(&:visible?).select(&:not_empty?)
end
def mark_invisible
@lines.map(&:mark_invisible)
end
def is_end?
to_s.strip == "end"
end
def hidden?
@lines.all?(&:hidden?)
end
# This is used for frontier ordering, we are searching from
# the largest indentation to the smallest. This allows us to
# populate an array with multiple code blocks then call `sort!`
# on it without having to specify the sorting criteria
def <=>(other)
out = current_indent <=> other.current_indent
return out if out != 0
# Stable sort
starts_at <=> other.starts_at
end
def current_indent
@current_indent ||= lines.select(&:not_empty?).map(&:indent).min || 0
end
def invalid?
!valid?
end
def valid?
if @valid == UNSET
# Performance optimization
#
# If all the lines were previously hidden
# and we expand to capture additional empty
# lines then the result cannot be invalid
#
# That means there's no reason to re-check all
# lines with ripper (which is expensive).
# Benchmark in commit message
@valid = if lines.all? { |l| l.hidden? || l.empty? }
true
else
SyntaxSuggest.valid?(lines.map(&:original).join)
end
else
@valid
end
end
def to_s
@lines.join
end
end
end
share/ruby/syntax_suggest/ripper_errors.rb 0000644 00000001505 15173547340 0015152 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Capture parse errors from ripper
#
# Example:
#
# puts RipperErrors.new(" def foo").call.errors
# # => ["syntax error, unexpected end-of-input, expecting ';' or '\\n'"]
class RipperErrors < Ripper
attr_reader :errors
# Comes from ripper, called
# on every parse error, msg
# is a string
def on_parse_error(msg)
@errors ||= []
@errors << msg
end
alias_method :on_alias_error, :on_parse_error
alias_method :on_assign_error, :on_parse_error
alias_method :on_class_name_error, :on_parse_error
alias_method :on_param_error, :on_parse_error
alias_method :compile_error, :on_parse_error
def call
@run_once ||= begin
@errors = []
parse
true
end
self
end
end
end
share/ruby/syntax_suggest/code_search.rb 0000644 00000007504 15173547340 0014521 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Searches code for a syntax error
#
# There are three main phases in the algorithm:
#
# 1. Sanitize/format input source
# 2. Search for invalid blocks
# 3. Format invalid blocks into something meaninful
#
# This class handles the part.
#
# The bulk of the heavy lifting is done in:
#
# - CodeFrontier (Holds information for generating blocks and determining if we can stop searching)
# - ParseBlocksFromLine (Creates blocks into the frontier)
# - BlockExpand (Expands existing blocks to search more code)
#
# ## Syntax error detection
#
# When the frontier holds the syntax error, we can stop searching
#
# search = CodeSearch.new(<<~EOM)
# def dog
# def lol
# end
# EOM
#
# search.call
#
# search.invalid_blocks.map(&:to_s) # =>
# # => ["def lol\n"]
#
class CodeSearch
private
attr_reader :frontier
public
attr_reader :invalid_blocks, :record_dir, :code_lines
def initialize(source, record_dir: DEFAULT_VALUE)
record_dir = if record_dir == DEFAULT_VALUE
ENV["SYNTAX_SUGGEST_RECORD_DIR"] || ENV["SYNTAX_SUGGEST_DEBUG"] ? "tmp" : nil
else
record_dir
end
if record_dir
@record_dir = SyntaxSuggest.record_dir(record_dir)
@write_count = 0
end
@tick = 0
@source = source
@name_tick = Hash.new { |hash, k| hash[k] = 0 }
@invalid_blocks = []
@code_lines = CleanDocument.new(source: source).call.lines
@frontier = CodeFrontier.new(code_lines: @code_lines)
@block_expand = BlockExpand.new(code_lines: @code_lines)
@parse_blocks_from_indent_line = ParseBlocksFromIndentLine.new(code_lines: @code_lines)
end
# Used for debugging
def record(block:, name: "record")
return unless @record_dir
@name_tick[name] += 1
filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}-(#{block.starts_at}__#{block.ends_at}).txt"
if ENV["SYNTAX_SUGGEST_DEBUG"]
puts "\n\n==== #{filename} ===="
puts "\n```#{block.starts_at}..#{block.ends_at}"
puts block.to_s
puts "```"
puts " block indent: #{block.current_indent}"
end
@record_dir.join(filename).open(mode: "a") do |f|
document = DisplayCodeWithLineNumbers.new(
lines: @code_lines.select(&:visible?),
terminal: false,
highlight_lines: block.lines
).call
f.write(" Block lines: #{block.starts_at..block.ends_at} (#{name}) \n\n#{document}")
end
end
def push(block, name:)
record(block: block, name: name)
block.mark_invisible if block.valid?
frontier << block
end
# Parses the most indented lines into blocks that are marked
# and added to the frontier
def create_blocks_from_untracked_lines
max_indent = frontier.next_indent_line&.indent
while (line = frontier.next_indent_line) && (line.indent == max_indent)
@parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block|
push(block, name: "add")
end
end
end
# Given an already existing block in the frontier, expand it to see
# if it contains our invalid syntax
def expand_existing
block = frontier.pop
return unless block
record(block: block, name: "before-expand")
block = @block_expand.call(block)
push(block, name: "expand")
end
# Main search loop
def call
until frontier.holds_all_syntax_errors?
@tick += 1
if frontier.expand?
expand_existing
else
create_blocks_from_untracked_lines
end
end
@invalid_blocks.concat(frontier.detect_invalid_blocks)
@invalid_blocks.sort_by! { |block| block.starts_at }
self
end
end
end
share/ruby/syntax_suggest/code_frontier.rb 0000644 00000013170 15173547340 0015100 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# The main function of the frontier is to hold the edges of our search and to
# evaluate when we can stop searching.
# There are three main phases in the algorithm:
#
# 1. Sanitize/format input source
# 2. Search for invalid blocks
# 3. Format invalid blocks into something meaninful
#
# The Code frontier is a critical part of the second step
#
# ## Knowing where we've been
#
# Once a code block is generated it is added onto the frontier. Then it will be
# sorted by indentation and frontier can be filtered. Large blocks that fully enclose a
# smaller block will cause the smaller block to be evicted.
#
# CodeFrontier#<<(block) # Adds block to frontier
# CodeFrontier#pop # Removes block from frontier
#
# ## Knowing where we can go
#
# Internally the frontier keeps track of "unvisited" lines which are exposed via `next_indent_line`
# when called, this method returns, a line of code with the highest indentation.
#
# The returned line of code can be used to build a CodeBlock and then that code block
# is added back to the frontier. Then, the lines are removed from the
# "unvisited" so we don't double-create the same block.
#
# CodeFrontier#next_indent_line # Shows next line
# CodeFrontier#register_indent_block(block) # Removes lines from unvisited
#
# ## Knowing when to stop
#
# The frontier knows how to check the entire document for a syntax error. When blocks
# are added onto the frontier, they're removed from the document. When all code containing
# syntax errors has been added to the frontier, the document will be parsable without a
# syntax error and the search can stop.
#
# CodeFrontier#holds_all_syntax_errors? # Returns true when frontier holds all syntax errors
#
# ## Filtering false positives
#
# Once the search is completed, the frontier may have multiple blocks that do not contain
# the syntax error. To limit the result to the smallest subset of "invalid blocks" call:
#
# CodeFrontier#detect_invalid_blocks
#
class CodeFrontier
def initialize(code_lines:, unvisited: UnvisitedLines.new(code_lines: code_lines))
@code_lines = code_lines
@unvisited = unvisited
@queue = PriorityEngulfQueue.new
@check_next = true
end
def count
@queue.length
end
# Performance optimization
#
# Parsing with ripper is expensive
# If we know we don't have any blocks with invalid
# syntax, then we know we cannot have found
# the incorrect syntax yet.
#
# When an invalid block is added onto the frontier
# check document state
private def can_skip_check?
check_next = @check_next
@check_next = false
if check_next
false
else
true
end
end
# Returns true if the document is valid with all lines
# removed. By default it checks all blocks in present in
# the frontier array, but can be used for arbitrary arrays
# of codeblocks as well
def holds_all_syntax_errors?(block_array = @queue, can_cache: true)
return false if can_cache && can_skip_check?
without_lines = block_array.to_a.flat_map do |block|
block.lines
end
SyntaxSuggest.valid_without?(
without_lines: without_lines,
code_lines: @code_lines
)
end
# Returns a code block with the largest indentation possible
def pop
@queue.pop
end
def next_indent_line
@unvisited.peek
end
def expand?
return false if @queue.empty?
return true if @unvisited.empty?
frontier_indent = @queue.peek.current_indent
unvisited_indent = next_indent_line.indent
if ENV["SYNTAX_SUGGEST_DEBUG"]
puts "```"
puts @queue.peek.to_s
puts "```"
puts " @frontier indent: #{frontier_indent}"
puts " @unvisited indent: #{unvisited_indent}"
end
# Expand all blocks before moving to unvisited lines
frontier_indent >= unvisited_indent
end
# Keeps track of what lines have been added to blocks and which are not yet
# visited.
def register_indent_block(block)
@unvisited.visit_block(block)
self
end
# When one element fully encapsulates another we remove the smaller
# block from the frontier. This prevents double expansions and all-around
# weird behavior. However this guarantee is quite expensive to maintain
def register_engulf_block(block)
end
# Add a block to the frontier
#
# This method ensures the frontier always remains sorted (in indentation order)
# and that each code block's lines are removed from the indentation hash so we
# don't re-evaluate the same line multiple times.
def <<(block)
@unvisited.visit_block(block)
@queue.push(block)
@check_next = true if block.invalid?
self
end
# Example:
#
# combination([:a, :b, :c, :d])
# # => [[:a], [:b], [:c], [:d], [:a, :b], [:a, :c], [:a, :d], [:b, :c], [:b, :d], [:c, :d], [:a, :b, :c], [:a, :b, :d], [:a, :c, :d], [:b, :c, :d], [:a, :b, :c, :d]]
def self.combination(array)
guesses = []
1.upto(array.length).each do |size|
guesses.concat(array.combination(size).to_a)
end
guesses
end
# Given that we know our syntax error exists somewhere in our frontier, we want to find
# the smallest possible set of blocks that contain all the syntax errors
def detect_invalid_blocks
self.class.combination(@queue.to_a.select(&:invalid?)).detect do |block_array|
holds_all_syntax_errors?(block_array, can_cache: false)
end || []
end
end
end
share/ruby/syntax_suggest/clean_document.rb 0000644 00000021363 15173547340 0015241 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Parses and sanitizes source into a lexically aware document
#
# Internally the document is represented by an array with each
# index containing a CodeLine correlating to a line from the source code.
#
# There are three main phases in the algorithm:
#
# 1. Sanitize/format input source
# 2. Search for invalid blocks
# 3. Format invalid blocks into something meaninful
#
# This class handles the first part.
#
# The reason this class exists is to format input source
# for better/easier/cleaner exploration.
#
# The CodeSearch class operates at the line level so
# we must be careful to not introduce lines that look
# valid by themselves, but when removed will trigger syntax errors
# or strange behavior.
#
# ## Join Trailing slashes
#
# Code with a trailing slash is logically treated as a single line:
#
# 1 it "code can be split" \
# 2 "across multiple lines" do
#
# In this case removing line 2 would add a syntax error. We get around
# this by internally joining the two lines into a single "line" object
#
# ## Logically Consecutive lines
#
# Code that can be broken over multiple
# lines such as method calls are on different lines:
#
# 1 User.
# 2 where(name: "schneems").
# 3 first
#
# Removing line 2 can introduce a syntax error. To fix this, all lines
# are joined into one.
#
# ## Heredocs
#
# A heredoc is an way of defining a multi-line string. They can cause many
# problems. If left as a single line, Ripper would try to parse the contents
# as ruby code rather than as a string. Even without this problem, we still
# hit an issue with indentation
#
# 1 foo = <<~HEREDOC
# 2 "Be yourself; everyone else is already taken.""
# 3 ― Oscar Wilde
# 4 puts "I look like ruby code" # but i'm still a heredoc
# 5 HEREDOC
#
# If we didn't join these lines then our algorithm would think that line 4
# is separate from the rest, has a higher indentation, then look at it first
# and remove it.
#
# If the code evaluates line 5 by itself it will think line 5 is a constant,
# remove it, and introduce a syntax errror.
#
# All of these problems are fixed by joining the whole heredoc into a single
# line.
#
# ## Comments and whitespace
#
# Comments can throw off the way the lexer tells us that the line
# logically belongs with the next line. This is valid ruby but
# results in a different lex output than before:
#
# 1 User.
# 2 where(name: "schneems").
# 3 # Comment here
# 4 first
#
# To handle this we can replace comment lines with empty lines
# and then re-lex the source. This removal and re-lexing preserves
# line index and document size, but generates an easier to work with
# document.
#
class CleanDocument
def initialize(source:)
lines = clean_sweep(source: source)
@document = CodeLine.from_source(lines.join, lines: lines)
end
# Call all of the document "cleaners"
# and return self
def call
join_trailing_slash!
join_consecutive!
join_heredoc!
self
end
# Return an array of CodeLines in the
# document
def lines
@document
end
# Renders the document back to a string
def to_s
@document.join
end
# Remove comments
#
# replace with empty newlines
#
# source = <<~'EOM'
# # Comment 1
# puts "hello"
# # Comment 2
# puts "world"
# EOM
#
# lines = CleanDocument.new(source: source).lines
# expect(lines[0].to_s).to eq("\n")
# expect(lines[1].to_s).to eq("puts "hello")
# expect(lines[2].to_s).to eq("\n")
# expect(lines[3].to_s).to eq("puts "world")
#
# Important: This must be done before lexing.
#
# After this change is made, we lex the document because
# removing comments can change how the doc is parsed.
#
# For example:
#
# values = LexAll.new(source: <<~EOM))
# User.
# # comment
# where(name: 'schneems')
# EOM
# expect(
# values.count {|v| v.type == :on_ignored_nl}
# ).to eq(1)
#
# After the comment is removed:
#
# values = LexAll.new(source: <<~EOM))
# User.
#
# where(name: 'schneems')
# EOM
# expect(
# values.count {|v| v.type == :on_ignored_nl}
# ).to eq(2)
#
def clean_sweep(source:)
# Match comments, but not HEREDOC strings with #{variable} interpolation
# https://rubular.com/r/HPwtW9OYxKUHXQ
source.lines.map do |line|
if line.match?(/^\s*#([^{].*|)$/)
$/
else
line
end
end
end
# Smushes all heredoc lines into one line
#
# source = <<~'EOM'
# foo = <<~HEREDOC
# lol
# hehehe
# HEREDOC
# EOM
#
# lines = CleanDocument.new(source: source).join_heredoc!.lines
# expect(lines[0].to_s).to eq(source)
# expect(lines[1].to_s).to eq("")
def join_heredoc!
start_index_stack = []
heredoc_beg_end_index = []
lines.each do |line|
line.lex.each do |lex_value|
case lex_value.type
when :on_heredoc_beg
start_index_stack << line.index
when :on_heredoc_end
start_index = start_index_stack.pop
end_index = line.index
heredoc_beg_end_index << [start_index, end_index]
end
end
end
heredoc_groups = heredoc_beg_end_index.map { |start_index, end_index| @document[start_index..end_index] }
join_groups(heredoc_groups)
self
end
# Smushes logically "consecutive" lines
#
# source = <<~'EOM'
# User.
# where(name: 'schneems').
# first
# EOM
#
# lines = CleanDocument.new(source: source).join_consecutive!.lines
# expect(lines[0].to_s).to eq(source)
# expect(lines[1].to_s).to eq("")
#
# The one known case this doesn't handle is:
#
# Ripper.lex <<~EOM
# a &&
# b ||
# c
# EOM
#
# For some reason this introduces `on_ignore_newline` but with BEG type
#
def join_consecutive!
consecutive_groups = @document.select(&:ignore_newline_not_beg?).map do |code_line|
take_while_including(code_line.index..-1) do |line|
line.ignore_newline_not_beg?
end
end
join_groups(consecutive_groups)
self
end
# Join lines with a trailing slash
#
# source = <<~'EOM'
# it "code can be split" \
# "across multiple lines" do
# EOM
#
# lines = CleanDocument.new(source: source).join_consecutive!.lines
# expect(lines[0].to_s).to eq(source)
# expect(lines[1].to_s).to eq("")
def join_trailing_slash!
trailing_groups = @document.select(&:trailing_slash?).map do |code_line|
take_while_including(code_line.index..-1) { |x| x.trailing_slash? }
end
join_groups(trailing_groups)
self
end
# Helper method for joining "groups" of lines
#
# Input is expected to be type Array<Array<CodeLine>>
#
# The outer array holds the various "groups" while the
# inner array holds code lines.
#
# All code lines are "joined" into the first line in
# their group.
#
# To preserve document size, empty lines are placed
# in the place of the lines that were "joined"
def join_groups(groups)
groups.each do |lines|
line = lines.first
# Handle the case of multiple groups in a a row
# if one is already replaced, move on
next if @document[line.index].empty?
# Join group into the first line
@document[line.index] = CodeLine.new(
lex: lines.map(&:lex).flatten,
line: lines.join,
index: line.index
)
# Hide the rest of the lines
lines[1..-1].each do |line|
# The above lines already have newlines in them, if add more
# then there will be double newline, use an empty line instead
@document[line.index] = CodeLine.new(line: "", index: line.index, lex: [])
end
end
self
end
# Helper method for grabbing elements from document
#
# Like `take_while` except when it stops
# iterating, it also returns the line
# that caused it to stop
def take_while_including(range = 0..-1)
take_next_and_stop = false
@document[range].take_while do |line|
next if take_next_and_stop
take_next_and_stop = !(yield line)
true
end
end
end
end
share/ruby/syntax_suggest/capture_code_context.rb 0000644 00000015252 15173547340 0016462 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
module Capture
end
end
require_relative "capture/falling_indent_lines"
require_relative "capture/before_after_keyword_ends"
module SyntaxSuggest
# Turns a "invalid block(s)" into useful context
#
# There are three main phases in the algorithm:
#
# 1. Sanitize/format input source
# 2. Search for invalid blocks
# 3. Format invalid blocks into something meaninful
#
# This class handles the third part.
#
# The algorithm is very good at capturing all of a syntax
# error in a single block in number 2, however the results
# can contain ambiguities. Humans are good at pattern matching
# and filtering and can mentally remove extraneous data, but
# they can't add extra data that's not present.
#
# In the case of known ambiguious cases, this class adds context
# back to the ambiguitiy so the programmer has full information.
#
# Beyond handling these ambiguities, it also captures surrounding
# code context information:
#
# puts block.to_s # => "def bark"
#
# context = CaptureCodeContext.new(
# blocks: block,
# code_lines: code_lines
# )
#
# lines = context.call.map(&:original)
# puts lines.join
# # =>
# class Dog
# def bark
# end
#
class CaptureCodeContext
attr_reader :code_lines
def initialize(blocks:, code_lines:)
@blocks = Array(blocks)
@code_lines = code_lines
@visible_lines = @blocks.map(&:visible_lines).flatten
@lines_to_output = @visible_lines.dup
end
def call
@blocks.each do |block|
capture_first_kw_end_same_indent(block)
capture_last_end_same_indent(block)
capture_before_after_kws(block)
capture_falling_indent(block)
end
sorted_lines
end
def sorted_lines
@lines_to_output.select!(&:not_empty?)
@lines_to_output.uniq!
@lines_to_output.sort!
@lines_to_output
end
# Shows the context around code provided by "falling" indentation
#
# Converts:
#
# it "foo" do
#
# into:
#
# class OH
# def hello
# it "foo" do
# end
# end
#
def capture_falling_indent(block)
Capture::FallingIndentLines.new(
block: block,
code_lines: @code_lines
).call do |line|
@lines_to_output << line
end
end
# Shows surrounding kw/end pairs
#
# The purpose of showing these extra pairs is due to cases
# of ambiguity when only one visible line is matched.
#
# For example:
#
# 1 class Dog
# 2 def bark
# 4 def eat
# 5 end
# 6 end
#
# In this case either line 2 could be missing an `end` or
# line 4 was an extra line added by mistake (it happens).
#
# When we detect the above problem it shows the issue
# as only being on line 2
#
# 2 def bark
#
# Showing "neighbor" keyword pairs gives extra context:
#
# 2 def bark
# 4 def eat
# 5 end
#
def capture_before_after_kws(block)
return unless block.visible_lines.count == 1
around_lines = Capture::BeforeAfterKeywordEnds.new(
code_lines: @code_lines,
block: block
).call
around_lines -= block.lines
@lines_to_output.concat(around_lines)
end
# When there is an invalid block with a keyword
# missing an end right before another end,
# it is unclear where which keyword is missing the
# end
#
# Take this example:
#
# class Dog # 1
# def bark # 2
# puts "woof" # 3
# end # 4
#
# However due to https://github.com/ruby/syntax_suggest/issues/32
# the problem line will be identified as:
#
# > class Dog # 1
#
# Because lines 2, 3, and 4 are technically valid code and are expanded
# first, deemed valid, and hidden. We need to un-hide the matching end
# line 4. Also work backwards and if there's a mis-matched keyword, show it
# too
def capture_last_end_same_indent(block)
return if block.visible_lines.length != 1
return unless block.visible_lines.first.is_kw?
visible_line = block.visible_lines.first
lines = @code_lines[visible_line.index..block.lines.last.index]
# Find first end with same indent
# (this would return line 4)
#
# end # 4
matching_end = lines.detect { |line| line.indent == block.current_indent && line.is_end? }
return unless matching_end
@lines_to_output << matching_end
# Work backwards from the end to
# see if there are mis-matched
# keyword/end pairs
#
# Return the first mis-matched keyword
# this would find line 2
#
# def bark # 2
# puts "woof" # 3
# end # 4
end_count = 0
kw_count = 0
kw_line = @code_lines[visible_line.index..matching_end.index].reverse.detect do |line|
end_count += 1 if line.is_end?
kw_count += 1 if line.is_kw?
!kw_count.zero? && kw_count >= end_count
end
return unless kw_line
@lines_to_output << kw_line
end
# The logical inverse of `capture_last_end_same_indent`
#
# When there is an invalid block with an `end`
# missing a keyword right after another `end`,
# it is unclear where which end is missing the
# keyword.
#
# Take this example:
#
# class Dog # 1
# puts "woof" # 2
# end # 3
# end # 4
#
# the problem line will be identified as:
#
# > end # 4
#
# This happens because lines 1, 2, and 3 are technically valid code and are expanded
# first, deemed valid, and hidden. We need to un-hide the matching keyword on
# line 1. Also work backwards and if there's a mis-matched end, show it
# too
def capture_first_kw_end_same_indent(block)
return if block.visible_lines.length != 1
return unless block.visible_lines.first.is_end?
visible_line = block.visible_lines.first
lines = @code_lines[block.lines.first.index..visible_line.index]
matching_kw = lines.reverse.detect { |line| line.indent == block.current_indent && line.is_kw? }
return unless matching_kw
@lines_to_output << matching_kw
kw_count = 0
end_count = 0
orphan_end = @code_lines[matching_kw.index..visible_line.index].detect do |line|
kw_count += 1 if line.is_kw?
end_count += 1 if line.is_end?
end_count >= kw_count
end
return unless orphan_end
@lines_to_output << orphan_end
end
end
end
share/ruby/syntax_suggest/unvisited_lines.rb 0000644 00000001301 15173547340 0015453 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Tracks which lines various code blocks have expanded to
# and which are still unexplored
class UnvisitedLines
def initialize(code_lines:)
@unvisited = code_lines.sort_by(&:indent_index)
@visited_lines = {}
@visited_lines.compare_by_identity
end
def empty?
@unvisited.empty?
end
def peek
@unvisited.last
end
def pop
@unvisited.pop
end
def visit_block(block)
block.lines.each do |line|
next if @visited_lines[line]
@visited_lines[line] = true
end
while @visited_lines[@unvisited.last]
@unvisited.pop
end
end
end
end
share/ruby/syntax_suggest/priority_queue.rb 0000644 00000003776 15173547340 0015356 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Holds elements in a priority heap on insert
#
# Instead of constantly calling `sort!`, put
# the element where it belongs the first time
# around
#
# Example:
#
# queue = PriorityQueue.new
# queue << 33
# queue << 44
# queue << 1
#
# puts queue.peek # => 44
#
class PriorityQueue
attr_reader :elements
def initialize
@elements = []
end
def <<(element)
@elements << element
bubble_up(last_index, element)
end
def pop
exchange(0, last_index)
max = @elements.pop
bubble_down(0)
max
end
def length
@elements.length
end
def empty?
@elements.empty?
end
def peek
@elements.first
end
def to_a
@elements
end
# Used for testing, extremely not performant
def sorted
out = []
elements = @elements.dup
while (element = pop)
out << element
end
@elements = elements
out.reverse
end
private def last_index
@elements.size - 1
end
private def bubble_up(index, element)
return if index <= 0
parent_index = (index - 1) / 2
parent = @elements[parent_index]
return if (parent <=> element) >= 0
exchange(index, parent_index)
bubble_up(parent_index, element)
end
private def bubble_down(index)
child_index = (index * 2) + 1
return if child_index > last_index
not_the_last_element = child_index < last_index
left_element = @elements[child_index]
right_element = @elements[child_index + 1]
child_index += 1 if not_the_last_element && (right_element <=> left_element) == 1
return if (@elements[index] <=> @elements[child_index]) >= 0
exchange(index, child_index)
bubble_down(child_index)
end
def exchange(source, target)
a = @elements[source]
b = @elements[target]
@elements[source] = b
@elements[target] = a
end
end
end
share/ruby/racc/grammar.rb 0000644 00000054266 15173547340 0011540 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
require 'racc/compat'
require 'racc/iset'
require 'racc/sourcetext'
require 'racc/logfilegenerator'
require 'racc/exception'
require 'forwardable'
module Racc
class Grammar
def initialize(debug_flags = DebugFlags.new)
@symboltable = SymbolTable.new
@debug_symbol = debug_flags.token
@rules = [] # :: [Rule]
@start = nil
@n_expected_srconflicts = nil
@prec_table = []
@prec_table_closed = false
@closed = false
@states = nil
end
attr_reader :start
attr_reader :symboltable
attr_accessor :n_expected_srconflicts
def [](x)
@rules[x]
end
def each_rule(&block)
@rules.each(&block)
end
alias each each_rule
def each_index(&block)
@rules.each_index(&block)
end
def each_with_index(&block)
@rules.each_with_index(&block)
end
def size
@rules.size
end
def to_s
"<Racc::Grammar>"
end
extend Forwardable
def_delegator "@symboltable", :each, :each_symbol
def_delegator "@symboltable", :each_terminal
def_delegator "@symboltable", :each_nonterminal
def intern(value, dummy = false)
@symboltable.intern(value, dummy)
end
def symbols
@symboltable.symbols
end
def nonterminal_base
@symboltable.nt_base
end
def useless_nonterminal_exist?
n_useless_nonterminals() != 0
end
def n_useless_nonterminals
@n_useless_nonterminals ||= each_useless_nonterminal.count
end
def each_useless_nonterminal
return to_enum __method__ unless block_given?
@symboltable.each_nonterminal do |sym|
yield sym if sym.useless?
end
end
def useless_rule_exist?
n_useless_rules() != 0
end
def n_useless_rules
@n_useless_rules ||= each_useless_rule.count
end
def each_useless_rule
return to_enum __method__ unless block_given?
each do |r|
yield r if r.useless?
end
end
def nfa
(@states ||= States.new(self)).nfa
end
def dfa
(@states ||= States.new(self)).dfa
end
alias states dfa
def state_transition_table
states().state_transition_table
end
def parser_class
states = states() # cache
if $DEBUG
srcfilename = caller(1).first.slice(/\A(.*?):/, 1)
begin
write_log srcfilename + ".output"
rescue SystemCallError
end
report = lambda {|s| $stderr.puts "racc: #{srcfilename}: #{s}" }
if states.should_report_srconflict?
report["#{states.n_srconflicts} shift/reduce conflicts"]
end
if states.rrconflict_exist?
report["#{states.n_rrconflicts} reduce/reduce conflicts"]
end
g = states.grammar
if g.useless_nonterminal_exist?
report["#{g.n_useless_nonterminals} useless nonterminals"]
end
if g.useless_rule_exist?
report["#{g.n_useless_rules} useless rules"]
end
end
states.state_transition_table.parser_class
end
def write_log(path)
File.open(path, 'w') {|f|
LogFileGenerator.new(states()).output f
}
end
#
# Grammar Definition Interface
#
def add(rule)
raise ArgumentError, "rule added after the Grammar closed" if @closed
@rules.push rule
end
def added?(sym)
@rules.detect {|r| r.target == sym }
end
def start_symbol=(s)
raise CompileError, "start symbol set twice'" if @start
@start = s
end
def declare_precedence(assoc, syms)
raise CompileError, "precedence table defined twice" if @prec_table_closed
@prec_table.push [assoc, syms]
end
def end_precedence_declaration(reverse)
@prec_table_closed = true
return if @prec_table.empty?
table = reverse ? @prec_table.reverse : @prec_table
table.each_with_index do |(assoc, syms), idx|
syms.each do |sym|
sym.assoc = assoc
sym.precedence = idx
end
end
end
#
# Dynamic Generation Interface
#
def Grammar.define(&block)
env = DefinitionEnv.new
env.instance_eval(&block)
env.grammar
end
class DefinitionEnv
def initialize
@grammar = Grammar.new
@seqs = Hash.new(0)
@delayed = []
end
def grammar
flush_delayed
@grammar.each do |rule|
if rule.specified_prec
rule.specified_prec = @grammar.intern(rule.specified_prec)
end
end
@grammar.init
@grammar
end
def precedence_table(&block)
env = PrecedenceDefinitionEnv.new(@grammar)
env.instance_eval(&block)
@grammar.end_precedence_declaration env.reverse
end
def method_missing(mid, *args, &block)
unless mid.to_s[-1,1] == '='
super # raises NoMethodError
end
target = @grammar.intern(mid.to_s.chop.intern)
unless args.size == 1
raise ArgumentError, "too many arguments for #{mid} (#{args.size} for 1)"
end
_add target, args.first
end
def _add(target, x)
case x
when Sym
@delayed.each do |rule|
rule.replace x, target if rule.target == x
end
@grammar.symboltable.delete x
else
x.each_rule do |r|
r.target = target
@grammar.add r
end
end
flush_delayed
end
def _delayed_add(rule)
@delayed.push rule
end
def _added?(sym)
@grammar.added?(sym) or @delayed.detect {|r| r.target == sym }
end
def flush_delayed
return if @delayed.empty?
@delayed.each do |rule|
@grammar.add rule
end
@delayed.clear
end
def seq(*list, &block)
Rule.new(nil, list.map {|x| _intern(x) }, UserAction.proc(block))
end
def null(&block)
seq(&block)
end
def action(&block)
id = "@#{@seqs["action"] += 1}".intern
_delayed_add Rule.new(@grammar.intern(id), [], UserAction.proc(block))
id
end
alias _ action
def option(sym, default = nil, &block)
_defmetasyntax("option", _intern(sym), block) {|target|
seq() { default } | seq(sym)
}
end
def many(sym, &block)
_defmetasyntax("many", _intern(sym), block) {|target|
seq() { [] }\
| seq(target, sym) {|list, x| list.push x; list }
}
end
def many1(sym, &block)
_defmetasyntax("many1", _intern(sym), block) {|target|
seq(sym) {|x| [x] }\
| seq(target, sym) {|list, x| list.push x; list }
}
end
def separated_by(sep, sym, &block)
option(separated_by1(sep, sym), [], &block)
end
def separated_by1(sep, sym, &block)
_defmetasyntax("separated_by1", _intern(sym), block) {|target|
seq(sym) {|x| [x] }\
| seq(target, sep, sym) {|list, _, x| list.push x; list }
}
end
def _intern(x)
case x
when Symbol, String
@grammar.intern(x)
when Racc::Sym
x
else
raise TypeError, "wrong type #{x.class} (expected Symbol/String/Racc::Sym)"
end
end
private
def _defmetasyntax(type, id, action, &block)
if action
idbase = "#{type}@#{id}-#{@seqs[type] += 1}"
target = _wrap(idbase, "#{idbase}-core", action)
_register("#{idbase}-core", &block)
else
target = _register("#{type}@#{id}", &block)
end
@grammar.intern(target)
end
def _register(target_name)
target = target_name.intern
unless _added?(@grammar.intern(target))
yield(target).each_rule do |rule|
rule.target = @grammar.intern(target)
_delayed_add rule
end
end
target
end
def _wrap(target_name, sym, block)
target = target_name.intern
_delayed_add Rule.new(@grammar.intern(target),
[@grammar.intern(sym.intern)],
UserAction.proc(block))
target
end
end
class PrecedenceDefinitionEnv
def initialize(g)
@grammar = g
@prechigh_seen = false
@preclow_seen = false
@reverse = false
end
attr_reader :reverse
def higher
if @prechigh_seen
raise CompileError, "prechigh used twice"
end
@prechigh_seen = true
end
def lower
if @preclow_seen
raise CompileError, "preclow used twice"
end
if @prechigh_seen
@reverse = true
end
@preclow_seen = true
end
def left(*syms)
@grammar.declare_precedence :Left, syms.map {|s| @grammar.intern(s) }
end
def right(*syms)
@grammar.declare_precedence :Right, syms.map {|s| @grammar.intern(s) }
end
def nonassoc(*syms)
@grammar.declare_precedence :Nonassoc, syms.map {|s| @grammar.intern(s)}
end
end
#
# Computation
#
def init
return if @closed
@closed = true
@start ||= @rules.map {|r| r.target }.detect {|sym| not sym.dummy? }
raise CompileError, 'no rule in input' if @rules.empty?
add_start_rule
@rules.freeze
fix_ident
compute_hash
compute_heads
determine_terminals
compute_nullable_0
@symboltable.fix
compute_locate
@symboltable.each_nonterminal {|t| compute_expand t }
compute_nullable
compute_useless
end
private
def add_start_rule
r = Rule.new(@symboltable.dummy,
[@start, @symboltable.anchor, @symboltable.anchor],
UserAction.empty)
r.ident = 0
r.hash = 0
r.precedence = nil
@rules.unshift r
end
# Rule#ident
# LocationPointer#ident
def fix_ident
@rules.each_with_index do |rule, idx|
rule.ident = idx
end
end
# Rule#hash
def compute_hash
hash = 4 # size of dummy rule
@rules.each do |rule|
rule.hash = hash
hash += (rule.size + 1)
end
end
# Sym#heads
def compute_heads
@rules.each do |rule|
rule.target.heads.push rule.ptrs[0]
end
end
# Sym#terminal?
def determine_terminals
@symboltable.each do |s|
s.term = s.heads.empty?
end
end
# Sym#self_null?
def compute_nullable_0
@symboltable.each do |s|
if s.terminal?
s.snull = false
else
s.snull = s.heads.any? {|loc| loc.reduce? }
end
end
end
# Sym#locate
def compute_locate
@rules.each do |rule|
t = nil
rule.ptrs.each do |ptr|
unless ptr.reduce?
tok = ptr.dereference
tok.locate.push ptr
t = tok if tok.terminal?
end
end
rule.precedence = t
end
end
# Sym#expand
def compute_expand(t)
puts "expand> #{t.to_s}" if @debug_symbol
t.expand = _compute_expand(t, ISet.new, [])
puts "expand< #{t.to_s}: #{t.expand.to_s}" if @debug_symbol
end
def _compute_expand(t, set, lock)
if tmp = t.expand
set.update tmp
return set
end
tok = nil
set.update_a t.heads
t.heads.each do |ptr|
tok = ptr.dereference
if tok and tok.nonterminal?
unless lock[tok.ident]
lock[tok.ident] = true
_compute_expand tok, set, lock
end
end
end
set
end
# Sym#nullable?, Rule#nullable?
def compute_nullable
@rules.each {|r| r.null = false }
@symboltable.each {|t| t.null = false }
r = @rules.dup
s = @symboltable.nonterminals
begin
rs = r.size
ss = s.size
check_rules_nullable r
check_symbols_nullable s
end until rs == r.size and ss == s.size
end
def check_rules_nullable(rules)
rules.delete_if do |rule|
rule.null = true
rule.symbols.each do |t|
unless t.nullable?
rule.null = false
break
end
end
rule.nullable?
end
end
def check_symbols_nullable(symbols)
symbols.delete_if do |sym|
sym.heads.each do |ptr|
if ptr.rule.nullable?
sym.null = true
break
end
end
sym.nullable?
end
end
# Sym#useless?, Rule#useless?
# FIXME: what means "useless"?
def compute_useless
@symboltable.each_terminal {|sym| sym.useless = false }
@symboltable.each_nonterminal {|sym| sym.useless = true }
@rules.each {|rule| rule.useless = true }
r = @rules.dup
s = @symboltable.nonterminals
begin
rs = r.size
ss = s.size
check_rules_useless r
check_symbols_useless s
end until r.size == rs and s.size == ss
end
def check_rules_useless(rules)
rules.delete_if do |rule|
rule.useless = false
rule.symbols.each do |sym|
if sym.useless?
rule.useless = true
break
end
end
not rule.useless?
end
end
def check_symbols_useless(s)
s.delete_if do |t|
t.heads.each do |ptr|
unless ptr.rule.useless?
t.useless = false
break
end
end
not t.useless?
end
end
end # class Grammar
class Rule
def initialize(target, syms, act)
@target = target
@symbols = syms
@action = act
@alternatives = []
@ident = nil
@hash = nil
@ptrs = nil
@precedence = nil
@specified_prec = nil
@null = nil
@useless = nil
end
attr_accessor :target
attr_reader :symbols
attr_reader :action
def |(x)
@alternatives.push x.rule
self
end
def rule
self
end
def each_rule(&block)
yield self
@alternatives.each(&block)
end
attr_accessor :ident
attr_reader :hash
attr_reader :ptrs
def hash=(n)
@hash = n
ptrs = []
@symbols.each_with_index do |sym, idx|
ptrs.push LocationPointer.new(self, idx, sym)
end
ptrs.push LocationPointer.new(self, @symbols.size, nil)
@ptrs = ptrs
end
def precedence
@specified_prec || @precedence
end
def precedence=(sym)
@precedence ||= sym
end
def prec(sym, &block)
@specified_prec = sym
if block
unless @action.empty?
raise CompileError, 'both of rule action block and prec block given'
end
@action = UserAction.proc(block)
end
self
end
attr_accessor :specified_prec
def nullable?() @null end
def null=(n) @null = n end
def useless?() @useless end
def useless=(u) @useless = u end
def inspect
"#<Racc::Rule id=#{@ident} (#{@target})>"
end
def ==(other)
other.kind_of?(Rule) and @ident == other.ident
end
def [](idx)
@symbols[idx]
end
def size
@symbols.size
end
def empty?
@symbols.empty?
end
def to_s
"#<rule#{@ident}>"
end
def accept?
if tok = @symbols[-1]
tok.anchor?
else
false
end
end
def each(&block)
@symbols.each(&block)
end
def replace(src, dest)
@target = dest
@symbols = @symbols.map {|s| s == src ? dest : s }
end
end # class Rule
class UserAction
def UserAction.source_text(src)
new(src, nil)
end
def UserAction.proc(pr = nil, &block)
if pr and block
raise ArgumentError, "both of argument and block given"
end
new(nil, pr || block)
end
def UserAction.empty
new(nil, nil)
end
private_class_method :new
def initialize(src, proc)
@source = src
@proc = proc
end
attr_reader :source
attr_reader :proc
def source?
not @proc
end
def proc?
not @source
end
def empty?
not @proc and not @source
end
def name
"{action type=#{@source || @proc || 'nil'}}"
end
alias inspect name
end
class OrMark
def initialize(lineno)
@lineno = lineno
end
def name
'|'
end
alias inspect name
attr_reader :lineno
end
class Prec
def initialize(symbol, lineno)
@symbol = symbol
@lineno = lineno
end
def name
"=#{@symbol}"
end
alias inspect name
attr_reader :symbol
attr_reader :lineno
end
#
# A set of rule and position in it's RHS.
# Note that the number of pointers is more than rule's RHS array,
# because pointer points right edge of the final symbol when reducing.
#
class LocationPointer
def initialize(rule, i, sym)
@rule = rule
@index = i
@symbol = sym
@ident = @rule.hash + i
@reduce = sym.nil?
end
attr_reader :rule
attr_reader :index
attr_reader :symbol
alias dereference symbol
attr_reader :ident
alias hash ident
attr_reader :reduce
alias reduce? reduce
def to_s
sprintf('(%d,%d %s)',
@rule.ident, @index, (reduce?() ? '#' : @symbol.to_s))
end
alias inspect to_s
def eql?(ot)
@hash == ot.hash
end
alias == eql?
def head?
@index == 0
end
def next
@rule.ptrs[@index + 1] or ptr_bug!
end
alias increment next
def before(len)
@rule.ptrs[@index - len] or ptr_bug!
end
private
def ptr_bug!
raise "racc: fatal: pointer not exist: self: #{to_s}"
end
end # class LocationPointer
class SymbolTable
include Enumerable
def initialize
@symbols = [] # :: [Racc::Sym]
@cache = {} # :: {(String|Symbol) => Racc::Sym}
@dummy = intern(:$start, true)
@anchor = intern(false, true) # Symbol ID = 0
@error = intern(:error, false) # Symbol ID = 1
end
attr_reader :dummy
attr_reader :anchor
attr_reader :error
def [](id)
@symbols[id]
end
def intern(val, dummy = false)
@cache[val] ||=
begin
sym = Sym.new(val, dummy)
@symbols.push sym
sym
end
end
attr_reader :symbols
alias to_a symbols
def delete(sym)
@symbols.delete sym
@cache.delete sym.value
end
attr_reader :nt_base
def nt_max
@symbols.size
end
def each(&block)
@symbols.each(&block)
end
def terminals(&block)
@symbols[0, @nt_base]
end
def each_terminal(&block)
@terms.each(&block)
end
def nonterminals
@symbols[@nt_base, @symbols.size - @nt_base]
end
def each_nonterminal(&block)
@nterms.each(&block)
end
def fix
terms, nterms = @symbols.partition {|s| s.terminal? }
@symbols = terms + nterms
@terms = terms
@nterms = nterms
@nt_base = terms.size
fix_ident
check_terminals
end
private
def fix_ident
@symbols.each_with_index do |t, i|
t.ident = i
end
end
def check_terminals
return unless @symbols.any? {|s| s.should_terminal? }
@anchor.should_terminal
@error.should_terminal
each_terminal do |t|
t.should_terminal if t.string_symbol?
end
each do |s|
s.should_terminal if s.assoc
end
terminals().reject {|t| t.should_terminal? }.each do |t|
raise CompileError, "terminal #{t} not declared as terminal"
end
nonterminals().select {|n| n.should_terminal? }.each do |n|
raise CompileError, "symbol #{n} declared as terminal but is not terminal"
end
end
end # class SymbolTable
# Stands terminal and nonterminal symbols.
class Sym
def initialize(value, dummyp)
@ident = nil
@value = value
@dummyp = dummyp
@term = nil
@nterm = nil
@should_terminal = false
@precedence = nil
case value
when Symbol
@to_s = value.to_s
@serialized = value.inspect
@string = false
when String
@to_s = value.inspect
@serialized = value.dump
@string = true
when false
@to_s = '$end'
@serialized = 'false'
@string = false
when ErrorSymbolValue
@to_s = 'error'
@serialized = 'Object.new'
@string = false
else
raise ArgumentError, "unknown symbol value: #{value.class}"
end
@heads = []
@locate = []
@snull = nil
@null = nil
@expand = nil
@useless = nil
end
class << self
def once_writer(nm)
nm = nm.id2name
module_eval(<<-EOS)
def #{nm}=(v)
raise 'racc: fatal: @#{nm} != nil' unless @#{nm}.nil?
@#{nm} = v
end
EOS
end
end
once_writer :ident
attr_reader :ident
alias hash ident
attr_reader :value
def dummy?
@dummyp
end
def terminal?
@term
end
def nonterminal?
@nterm
end
def term=(t)
raise 'racc: fatal: term= called twice' unless @term.nil?
@term = t
@nterm = !t
end
def should_terminal
@should_terminal = true
end
def should_terminal?
@should_terminal
end
def string_symbol?
@string
end
def serialize
@serialized
end
attr_writer :serialized
attr_accessor :precedence
attr_accessor :assoc
def to_s
@to_s.dup
end
alias inspect to_s
def |(x)
rule() | x.rule
end
def rule
Rule.new(nil, [self], UserAction.empty)
end
#
# cache
#
attr_reader :heads
attr_reader :locate
def self_null?
@snull
end
once_writer :snull
def nullable?
@null
end
def null=(n)
@null = n
end
attr_reader :expand
once_writer :expand
def useless?
@useless
end
def useless=(f)
@useless = f
end
end # class Sym
end # module Racc
share/ruby/racc/sourcetext.rb 0000644 00000001033 15173547340 0012277 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
class SourceText
def initialize(text, filename, lineno)
@text = text
@filename = filename
@lineno = lineno
end
attr_reader :text
attr_reader :filename
attr_reader :lineno
def to_s
"#<SourceText #{location()}>"
end
def location
"#{@filename}:#{@lineno}"
end
end
end
share/ruby/racc/state.rb 0000644 00000047471 15173547340 0011232 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
require 'racc/iset'
require 'racc/statetransitiontable'
require 'racc/exception'
require 'forwardable'
module Racc
# A table of LALR states.
class States
include Enumerable
def initialize(grammar, debug_flags = DebugFlags.new)
@grammar = grammar
@symboltable = grammar.symboltable
@d_state = debug_flags.state
@d_la = debug_flags.la
@d_prec = debug_flags.prec
@states = []
@statecache = {}
@actions = ActionTable.new(@grammar, self)
@nfa_computed = false
@dfa_computed = false
end
attr_reader :grammar
attr_reader :actions
def size
@states.size
end
def inspect
'#<state table>'
end
alias to_s inspect
def [](i)
@states[i]
end
def each_state(&block)
@states.each(&block)
end
alias each each_state
def each_index(&block)
@states.each_index(&block)
end
extend Forwardable
def_delegator "@actions", :shift_n
def_delegator "@actions", :reduce_n
def_delegator "@actions", :nt_base
def should_report_srconflict?
srconflict_exist? and
(n_srconflicts() != @grammar.n_expected_srconflicts)
end
def srconflict_exist?
n_srconflicts() != 0
end
def n_srconflicts
@n_srconflicts ||= inject(0) {|sum, st| sum + st.n_srconflicts }
end
def rrconflict_exist?
n_rrconflicts() != 0
end
def n_rrconflicts
@n_rrconflicts ||= inject(0) {|sum, st| sum + st.n_rrconflicts }
end
def state_transition_table
@state_transition_table ||= StateTransitionTable.generate(self.dfa)
end
#
# NFA (Non-deterministic Finite Automaton) Computation
#
public
def nfa
return self if @nfa_computed
compute_nfa
@nfa_computed = true
self
end
private
def compute_nfa
@grammar.init
# add state 0
core_to_state [ @grammar[0].ptrs[0] ]
# generate LALR states
cur = 0
@gotos = []
while cur < @states.size
generate_states @states[cur] # state is added here
cur += 1
end
@actions.init
end
def generate_states(state)
puts "dstate: #{state}" if @d_state
table = {}
state.closure.each do |ptr|
if sym = ptr.dereference
addsym table, sym, ptr.next
end
end
table.each do |sym, core|
puts "dstate: sym=#{sym} ncore=#{core}" if @d_state
dest = core_to_state(core.to_a)
state.goto_table[sym] = dest
id = sym.nonterminal?() ? @gotos.size : nil
g = Goto.new(id, sym, state, dest)
@gotos.push g if sym.nonterminal?
state.gotos[sym] = g
puts "dstate: #{state.ident} --#{sym}--> #{dest.ident}" if @d_state
# check infinite recursion
if state.ident == dest.ident and state.closure.size == 1
raise CompileError,
sprintf("Infinite recursion: state %d, with rule %d",
state.ident, state.ptrs[0].rule.ident)
end
end
end
def addsym(table, sym, ptr)
unless s = table[sym]
table[sym] = s = ISet.new
end
s.add ptr
end
def core_to_state(core)
#
# convert CORE to a State object.
# If matching state does not exist, create it and add to the table.
#
k = fingerprint(core)
unless dest = @statecache[k]
# not registered yet
dest = State.new(@states.size, core)
@states.push dest
@statecache[k] = dest
puts "core_to_state: create state ID #{dest.ident}" if @d_state
else
if @d_state
puts "core_to_state: dest is cached ID #{dest.ident}"
puts "core_to_state: dest core #{dest.core.join(' ')}"
end
end
dest
end
def fingerprint(arr)
arr.map {|i| i.ident }.pack('L*')
end
#
# DFA (Deterministic Finite Automaton) Generation
#
public
def dfa
return self if @dfa_computed
nfa
compute_dfa
@dfa_computed = true
self
end
private
def compute_dfa
la = lookahead()
@states.each do |state|
state.la = la
resolve state
end
set_accept
@states.each do |state|
pack state
end
check_useless
end
def lookahead
#
# lookahead algorithm ver.3 -- from bison 1.26
#
gotos = @gotos
if @d_la
puts "\n--- goto ---"
gotos.each_with_index {|g, i| print i, ' '; p g }
end
### initialize_LA()
### set_goto_map()
la_rules = []
@states.each do |state|
state.check_la la_rules
end
### initialize_F()
f = create_tmap(gotos.size)
reads = []
edge = []
gotos.each do |goto|
goto.to_state.goto_table.each do |t, st|
if t.terminal?
f[goto.ident] |= (1 << t.ident)
elsif t.nullable?
edge.push goto.to_state.gotos[t].ident
end
end
if edge.empty?
reads.push nil
else
reads.push edge
edge = []
end
end
digraph f, reads
if @d_la
puts "\n--- F1 (reads) ---"
print_tab gotos, reads, f
end
### build_relations()
### compute_FOLLOWS
path = nil
edge = []
lookback = Array.new(la_rules.size, nil)
includes = []
gotos.each do |goto|
goto.symbol.heads.each do |ptr|
path = record_path(goto.from_state, ptr.rule)
lastgoto = path.last
st = lastgoto ? lastgoto.to_state : goto.from_state
if st.conflict?
addrel lookback, st.rruleid(ptr.rule), goto
end
path.reverse_each do |g|
break if g.symbol.terminal?
edge.push g.ident
break unless g.symbol.nullable?
end
end
if edge.empty?
includes.push nil
else
includes.push edge
edge = []
end
end
includes = transpose(includes)
digraph f, includes
if @d_la
puts "\n--- F2 (includes) ---"
print_tab gotos, includes, f
end
### compute_lookaheads
la = create_tmap(la_rules.size)
lookback.each_with_index do |arr, i|
if arr
arr.each do |g|
la[i] |= f[g.ident]
end
end
end
if @d_la
puts "\n--- LA (lookback) ---"
print_tab la_rules, lookback, la
end
la
end
def create_tmap(size)
Array.new(size, 0) # use Integer as bitmap
end
def addrel(tbl, i, item)
if a = tbl[i]
a.push item
else
tbl[i] = [item]
end
end
def record_path(begst, rule)
st = begst
path = []
rule.symbols.each do |t|
goto = st.gotos[t]
path.push goto
st = goto.to_state
end
path
end
def transpose(rel)
new = Array.new(rel.size, nil)
rel.each_with_index do |arr, idx|
if arr
arr.each do |i|
addrel new, i, idx
end
end
end
new
end
def digraph(map, relation)
n = relation.size
index = Array.new(n, nil)
vertices = []
@infinity = n + 2
index.each_index do |i|
if not index[i] and relation[i]
traverse i, index, vertices, map, relation
end
end
end
def traverse(i, index, vertices, map, relation)
vertices.push i
index[i] = height = vertices.size
if rp = relation[i]
rp.each do |proci|
unless index[proci]
traverse proci, index, vertices, map, relation
end
if index[i] > index[proci]
# circulative recursion !!!
index[i] = index[proci]
end
map[i] |= map[proci]
end
end
if index[i] == height
while true
proci = vertices.pop
index[proci] = @infinity
break if i == proci
map[proci] |= map[i]
end
end
end
# for debug
def print_atab(idx, tab)
tab.each_with_index do |i,ii|
printf '%-20s', idx[ii].inspect
p i
end
end
def print_tab(idx, rel, tab)
tab.each_with_index do |bin,i|
print i, ' ', idx[i].inspect, ' << '; p rel[i]
print ' '
each_t(@symboltable, bin) {|t| print ' ', t }
puts
end
end
# for debug
def print_tab_i(idx, rel, tab, i)
bin = tab[i]
print i, ' ', idx[i].inspect, ' << '; p rel[i]
print ' '
each_t(@symboltable, bin) {|t| print ' ', t }
end
# for debug
def printb(i)
each_t(@symboltable, i) do |t|
print t, ' '
end
puts
end
def each_t(tbl, set)
0.upto( set.size ) do |i|
(0..7).each do |ii|
if set[idx = i * 8 + ii] == 1
yield tbl[idx]
end
end
end
end
#
# resolve
#
def resolve(state)
if state.conflict?
resolve_rr state, state.ritems
resolve_sr state, state.stokens
else
if state.rrules.empty?
# shift
state.stokens.each do |t|
state.action[t] = @actions.shift(state.goto_table[t])
end
else
# reduce
state.defact = @actions.reduce(state.rrules[0])
end
end
end
def resolve_rr(state, r)
r.each do |item|
item.each_la(@symboltable) do |t|
act = state.action[t]
if act
unless act.kind_of?(Reduce)
raise "racc: fatal: #{act.class} in action table"
end
# Cannot resolve R/R conflict (on t).
# Reduce with upper rule as default.
state.rr_conflict act.rule, item.rule, t
else
# No conflict.
state.action[t] = @actions.reduce(item.rule)
end
end
end
end
def resolve_sr(state, s)
s.each do |stok|
goto = state.goto_table[stok]
act = state.action[stok]
unless act
# no conflict
state.action[stok] = @actions.shift(goto)
else
unless act.kind_of?(Reduce)
puts 'DEBUG -------------------------------'
p stok
p act
state.action.each do |k,v|
print k.inspect, ' ', v.inspect, "\n"
end
raise "racc: fatal: #{act.class} in action table"
end
# conflict on stok
rtok = act.rule.precedence
case do_resolve_sr(stok, rtok)
when :Reduce
# action is already set
when :Shift
# overwrite
act.decref
state.action[stok] = @actions.shift(goto)
when :Error
act.decref
state.action[stok] = @actions.error
when :CantResolve
# shift as default
act.decref
state.action[stok] = @actions.shift(goto)
state.sr_conflict stok, act.rule
end
end
end
end
ASSOC = {
:Left => :Reduce,
:Right => :Shift,
:Nonassoc => :Error
}
def do_resolve_sr(stok, rtok)
puts "resolve_sr: s/r conflict: rtok=#{rtok}, stok=#{stok}" if @d_prec
unless rtok and rtok.precedence
puts "resolve_sr: no prec for #{rtok}(R)" if @d_prec
return :CantResolve
end
rprec = rtok.precedence
unless stok and stok.precedence
puts "resolve_sr: no prec for #{stok}(S)" if @d_prec
return :CantResolve
end
sprec = stok.precedence
ret = if rprec == sprec
ASSOC[rtok.assoc] or
raise "racc: fatal: #{rtok}.assoc is not Left/Right/Nonassoc"
else
(rprec > sprec) ? (:Reduce) : (:Shift)
end
puts "resolve_sr: resolved as #{ret.id2name}" if @d_prec
ret
end
#
# complete
#
def set_accept
anch = @symboltable.anchor
init_state = @states[0].goto_table[@grammar.start]
targ_state = init_state.action[anch].goto_state
acc_state = targ_state.action[anch].goto_state
acc_state.action.clear
acc_state.goto_table.clear
acc_state.defact = @actions.accept
end
def pack(state)
### find most frequently used reduce rule
act = state.action
arr = Array.new(@grammar.size, 0)
act.each do |t, a|
arr[a.ruleid] += 1 if a.kind_of?(Reduce)
end
i = arr.max
s = (i > 0) ? arr.index(i) : nil
### set & delete default action
if s
r = @actions.reduce(s)
if not state.defact or state.defact == r
act.delete_if {|t, a| a == r }
state.defact = r
end
else
state.defact ||= @actions.error
end
end
def check_useless
used = []
@actions.each_reduce do |act|
if not act or act.refn == 0
act.rule.useless = true
else
t = act.rule.target
used[t.ident] = t
end
end
@symboltable.nt_base.upto(@symboltable.nt_max - 1) do |n|
unless used[n]
@symboltable[n].useless = true
end
end
end
end # class StateTable
# A LALR state.
class State
def initialize(ident, core)
@ident = ident
@core = core
@goto_table = {}
@gotos = {}
@stokens = nil
@ritems = nil
@action = {}
@defact = nil
@rrconf = nil
@srconf = nil
@closure = make_closure(@core)
end
attr_reader :ident
alias stateid ident
alias hash ident
attr_reader :core
attr_reader :closure
attr_reader :goto_table
attr_reader :gotos
attr_reader :stokens
attr_reader :ritems
attr_reader :rrules
attr_reader :action
attr_accessor :defact # default action
attr_reader :rrconf
attr_reader :srconf
def inspect
"<state #{@ident}>"
end
alias to_s inspect
def ==(oth)
@ident == oth.ident
end
alias eql? ==
def make_closure(core)
set = ISet.new
core.each do |ptr|
set.add ptr
if t = ptr.dereference and t.nonterminal?
set.update_a t.expand
end
end
set.to_a
end
def check_la(la_rules)
@conflict = false
s = []
r = []
@closure.each do |ptr|
if t = ptr.dereference
if t.terminal?
s[t.ident] = t
if t.ident == 1 # $error
@conflict = true
end
end
else
r.push ptr.rule
end
end
unless r.empty?
if not s.empty? or r.size > 1
@conflict = true
end
end
s.compact!
@stokens = s
@rrules = r
if @conflict
@la_rules_i = la_rules.size
@la_rules = r.map {|i| i.ident }
la_rules.concat r
else
@la_rules_i = @la_rules = nil
end
end
def conflict?
@conflict
end
def rruleid(rule)
if i = @la_rules.index(rule.ident)
@la_rules_i + i
else
puts '/// rruleid'
p self
p rule
p @rrules
p @la_rules_i
raise 'racc: fatal: cannot get reduce rule id'
end
end
def la=(la)
return unless @conflict
i = @la_rules_i
@ritems = r = []
@rrules.each do |rule|
r.push Item.new(rule, la[i])
i += 1
end
end
def rr_conflict(high, low, ctok)
c = RRconflict.new(@ident, high, low, ctok)
@rrconf ||= {}
if a = @rrconf[ctok]
a.push c
else
@rrconf[ctok] = [c]
end
end
def sr_conflict(shift, reduce)
c = SRconflict.new(@ident, shift, reduce)
@srconf ||= {}
if a = @srconf[shift]
a.push c
else
@srconf[shift] = [c]
end
end
def n_srconflicts
@srconf ? @srconf.size : 0
end
def n_rrconflicts
@rrconf ? @rrconf.size : 0
end
end # class State
#
# Represents a transition on the grammar.
# "Real goto" means a transition by nonterminal,
# but this class treats also terminal's.
# If one is a terminal transition, .ident returns nil.
#
class Goto
def initialize(ident, sym, from, to)
@ident = ident
@symbol = sym
@from_state = from
@to_state = to
end
attr_reader :ident
attr_reader :symbol
attr_reader :from_state
attr_reader :to_state
def inspect
"(#{@from_state.ident}-#{@symbol}->#{@to_state.ident})"
end
end
# LALR item. A set of rule and its lookahead tokens.
class Item
def initialize(rule, la)
@rule = rule
@la = la
end
attr_reader :rule
attr_reader :la
def each_la(tbl)
la = @la
0.upto(la.size - 1) do |i|
(0..7).each do |ii|
if la[idx = i * 8 + ii] == 1
yield tbl[idx]
end
end
end
end
end
# The table of LALR actions. Actions are either of
# Shift, Reduce, Accept and Error.
class ActionTable
def initialize(rt, st)
@grammar = rt
@statetable = st
@reduce = []
@shift = []
@accept = nil
@error = nil
end
def init
@grammar.each do |rule|
@reduce.push Reduce.new(rule)
end
@statetable.each do |state|
@shift.push Shift.new(state)
end
@accept = Accept.new
@error = Error.new
end
def reduce_n
@reduce.size
end
def reduce(i)
case i
when Rule then i = i.ident
when Integer then ;
else
raise "racc: fatal: wrong class #{i.class} for reduce"
end
r = @reduce[i] or raise "racc: fatal: reduce action #{i.inspect} not exist"
r.incref
r
end
def each_reduce(&block)
@reduce.each(&block)
end
def shift_n
@shift.size
end
def shift(i)
case i
when State then i = i.ident
when Integer then ;
else
raise "racc: fatal: wrong class #{i.class} for shift"
end
@shift[i] or raise "racc: fatal: shift action #{i} does not exist"
end
def each_shift(&block)
@shift.each(&block)
end
attr_reader :accept
attr_reader :error
end
class Shift
def initialize(goto)
@goto_state = goto
end
attr_reader :goto_state
def goto_id
@goto_state.ident
end
def inspect
"<shift #{@goto_state.ident}>"
end
end
class Reduce
def initialize(rule)
@rule = rule
@refn = 0
end
attr_reader :rule
attr_reader :refn
def ruleid
@rule.ident
end
def inspect
"<reduce #{@rule.ident}>"
end
def incref
@refn += 1
end
def decref
@refn -= 1
raise 'racc: fatal: act.refn < 0' if @refn < 0
end
end
class Accept
def inspect
"<accept>"
end
end
class Error
def inspect
"<error>"
end
end
class SRconflict
def initialize(sid, shift, reduce)
@stateid = sid
@shift = shift
@reduce = reduce
end
attr_reader :stateid
attr_reader :shift
attr_reader :reduce
def to_s
sprintf('state %d: S/R conflict rule %d reduce and shift %s',
@stateid, @reduce.ruleid, @shift.to_s)
end
end
class RRconflict
def initialize(sid, high, low, tok)
@stateid = sid
@high_prec = high
@low_prec = low
@token = tok
end
attr_reader :stateid
attr_reader :high_prec
attr_reader :low_prec
attr_reader :token
def to_s
sprintf('state %d: R/R conflict with rule %d and %d on %s',
@stateid, @high_prec.ident, @low_prec.ident, @token.to_s)
end
end
end
share/ruby/racc/exception.rb 0000644 00000000420 15173547340 0012067 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
class Error < StandardError; end
class CompileError < Error; end
end
share/ruby/racc/parser-text.rb 0000644 00000043773 15173547340 0012371 0 ustar 00 module Racc
PARSER_TEXT = <<'__end_of_file__'
# frozen_string_literal: false
#--
# 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.
#++
require 'racc/info'
unless defined?(NotImplementedError)
NotImplementedError = NotImplementError # :nodoc:
end
module Racc
class ParseError < StandardError; end
end
unless defined?(::ParseError)
ParseError = Racc::ParseError # :nodoc:
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>] [--executable=<var>rubypath</var>]
# [-v] [--verbose]
# [-O<var>filename</var>] [--log-file=<var>filename</var>]
# [-g] [--debug]
# [-E] [--embedded]
# [-l] [--no-line-convert]
# [-c] [--line-convert-all]
# [-a] [--no-omit-actions]
# [-C] [--check-only]
# [-S] [--output-status]
# [--version] [--copyright] [--help] <var>grammarfile</var>
#
# [+grammarfile+]
# Racc grammar file. Any extension is permitted.
# [-o+outfile+, --output-file=+outfile+]
# A filename for output. default is <+filename+>.tab.rb
# [-O+filename+, --log-file=+filename+]
# Place logging output in file +filename+.
# Default log file name is <+filename+>.output.
# [-e+rubypath+, --executable=+rubypath+]
# output executable file(mode 755). where +path+ is the Ruby interpreter.
# [-v, --verbose]
# verbose mode. create +filename+.output file, like yacc's y.output file.
# [-g, --debug]
# add debug code to parser class. To display debuggin 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 ruby license, but your parser is not.
# Your own parser is completely yours.
module Racc
unless defined?(Racc_No_Extensions)
Racc_No_Extensions = false # :nodoc:
end
class Parser
Racc_Runtime_Version = ::Racc::VERSION
Racc_Runtime_Core_Version_R = ::Racc::VERSION
begin
if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
require 'jruby'
require 'racc/cparse-jruby.jar'
com.headius.racc.Cparse.new.load(JRuby.runtime, false)
else
require 'racc/cparse'
end
unless new.respond_to?(:_racc_do_parse_c, true)
raise LoadError, 'old cparse.so'
end
if Racc_No_Extensions
raise LoadError, 'selecting ruby version of racc runtime core'
end
Racc_Main_Parsing_Routine = :_racc_do_parse_c # :nodoc:
Racc_YY_Parse_Method = :_racc_yyparse_c # :nodoc:
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C # :nodoc:
Racc_Runtime_Type = 'c' # :nodoc:
rescue LoadError
Racc_Main_Parsing_Routine = :_racc_do_parse_rb
Racc_YY_Parse_Method = :_racc_yyparse_rb
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
Racc_Runtime_Type = 'ruby'
end
def Parser.racc_runtime_type # :nodoc:
Racc_Runtime_Type
end
def _racc_setup
@yydebug = false unless self.class::Racc_debug_parser
@yydebug = false unless defined?(@yydebug)
if @yydebug
@racc_debug_out = $stderr unless defined?(@racc_debug_out)
@racc_debug_out ||= $stderr
end
arg = self.class::Racc_arg
arg[13] = true if arg.size < 14
arg
end
def _racc_init_sysvars
@racc_state = [0]
@racc_tstack = []
@racc_vstack = []
@racc_t = nil
@racc_val = nil
@racc_read_next = true
@racc_user_yyerror = false
@racc_error_status = 0
end
# The entry point of the parser. This method is used with #next_token.
# If Racc wants to get token (and its value), calls next_token.
#
# Example:
# def parse
# @q = [[1,1],
# [2,2],
# [3,3],
# [false, '$']]
# do_parse
# end
#
# def next_token
# @q.shift
# end
class_eval %{
def do_parse
#{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].
class_eval %{
def yyparse(recv, mid)
#{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false)
end
}
def _racc_yyparse_rb(recv, mid, arg, c_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
catch(:racc_end_parse) {
until i = action_pointer[@racc_state[-1]]
while act = _racc_evalact(action_default[@racc_state[-1]], arg)
;
end
end
recv.__send__(mid) do |tok, val|
unless tok
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
@racc_val = val
@racc_read_next = false
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
while !(i = action_pointer[@racc_state[-1]]) ||
! @racc_read_next ||
@racc_t == 0 # $
unless i and i += @racc_t and
i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
end
}
end
###
### common
###
def _racc_evalact(act, arg)
action_table, action_check, _, action_pointer,
_, _, _, _,
_, _, _, shift_n,
reduce_n, * = arg
nerr = 0 # tmp
if act > 0 and act < shift_n
#
# shift
#
if @racc_error_status > 0
@racc_error_status -= 1 unless @racc_t <= 1 # error token or EOF
end
@racc_vstack.push @racc_val
@racc_state.push act
@racc_read_next = true
if @yydebug
@racc_tstack.push @racc_t
racc_shift @racc_t, @racc_tstack, @racc_vstack
end
elsif act < 0 and act > -reduce_n
#
# reduce
#
code = catch(:racc_jump) {
@racc_state.push _racc_do_reduce(arg, act)
false
}
if code
case code
when 1 # yyerror
@racc_user_yyerror = true # user_yyerror
return -reduce_n
when 2 # yyaccept
return shift_n
else
raise '[Racc Bug] unknown jump code'
end
end
elsif act == shift_n
#
# accept
#
racc_accept if @yydebug
throw :racc_end_parse, @racc_vstack[0]
elsif act == -reduce_n
#
# error
#
case @racc_error_status
when 0
unless arg[21] # user_yyerror
nerr += 1
on_error @racc_t, @racc_val, @racc_vstack
end
when 3
if @racc_t == 0 # is $
# We're at EOF, and another error occurred immediately after
# attempting auto-recovery
throw :racc_end_parse, nil
end
@racc_read_next = true
end
@racc_user_yyerror = false
@racc_error_status = 3
while true
if i = action_pointer[@racc_state[-1]]
i += 1 # error token
if i >= 0 and
(act = action_table[i]) and
action_check[i] == @racc_state[-1]
break
end
end
throw :racc_end_parse, nil if @racc_state.size <= 1
@racc_state.pop
@racc_vstack.pop
if @yydebug
@racc_tstack.pop
racc_e_pop @racc_state, @racc_tstack, @racc_vstack
end
end
return act
else
raise "[Racc Bug] unknown action #{act.inspect}"
end
racc_next_state(@racc_state[-1], @racc_state) if @yydebug
nil
end
def _racc_do_reduce(arg, act)
_, _, _, _,
goto_table, goto_check, goto_default, goto_pointer,
nt_base, reduce_table, _, _,
_, use_result, * = arg
state = @racc_state
vstack = @racc_vstack
tstack = @racc_tstack
i = act * -3
len = reduce_table[i]
reduce_to = reduce_table[i+1]
method_id = reduce_table[i+2]
void_array = []
tmp_t = tstack[-len, len] if @yydebug
tmp_v = vstack[-len, len]
tstack[-len, len] = void_array if @yydebug
vstack[-len, len] = void_array
state[-len, len] = void_array
# tstack must be updated AFTER method call
if use_result
vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
else
vstack.push __send__(method_id, tmp_v, vstack)
end
tstack.push reduce_to
racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
k1 = reduce_to - nt_base
if i = goto_pointer[k1]
i += state[-1]
if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
return curstate
end
end
goto_default[k1]
end
# This method is called when a parse error is found.
#
# ERROR_TOKEN_ID is an internal ID of token which caused error.
# You can get string representation of this ID by calling
# #token_to_str.
#
# ERROR_VALUE is a value of error token.
#
# value_stack is a stack of symbol values.
# DO NOT MODIFY this object.
#
# This method raises ParseError by default.
#
# If this method returns, parsers enter "error recovering mode".
def on_error(t, val, vstack)
raise ParseError, sprintf("\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
__end_of_file__
end
share/ruby/racc/compat.rb 0000644 00000001032 15173547340 0011354 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
unless Object.method_defined?(:__send)
class Object
alias __send __send__
end
end
unless Object.method_defined?(:__send!)
class Object
alias __send! __send__
end
end
unless Array.method_defined?(:map!)
class Array
if Array.method_defined?(:collect!)
alias map! collect!
else
alias map! filter
end
end
end
share/ruby/racc/debugflags.rb 0000644 00000002455 15173547340 0012206 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
class DebugFlags
def DebugFlags.parse_option_string(s)
parse = rule = token = state = la = prec = conf = false
s.split(//).each do |ch|
case ch
when 'p' then parse = true
when 'r' then rule = true
when 't' then token = true
when 's' then state = true
when 'l' then la = true
when 'c' then prec = true
when 'o' then conf = true
else
raise "unknown debug flag char: #{ch.inspect}"
end
end
new(parse, rule, token, state, la, prec, conf)
end
def initialize(parse = false, rule = false, token = false, state = false,
la = false, prec = false, conf = false)
@parse = parse
@rule = rule
@token = token
@state = state
@la = la
@prec = prec
@any = (parse || rule || token || state || la || prec)
@status_logging = conf
end
attr_reader :parse
attr_reader :rule
attr_reader :token
attr_reader :state
attr_reader :la
attr_reader :prec
def any?
@any
end
attr_reader :status_logging
end
end
share/ruby/racc/parserfilegenerator.rb 0000644 00000025124 15173547340 0014144 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
require 'racc/compat'
require 'racc/sourcetext'
require 'racc/parser-text'
require 'rbconfig'
module Racc
class ParserFileGenerator
class Params
def self.bool_attr(name)
module_eval(<<-End)
def #{name}?
@#{name}
end
def #{name}=(b)
@#{name} = b
end
End
end
attr_accessor :filename
attr_accessor :classname
attr_accessor :superclass
bool_attr :omit_action_call
bool_attr :result_var
attr_accessor :header
attr_accessor :inner
attr_accessor :footer
bool_attr :debug_parser
bool_attr :convert_line
bool_attr :convert_line_all
bool_attr :embed_runtime
bool_attr :make_executable
attr_accessor :interpreter
def initialize
# Parameters derived from parser
self.filename = nil
self.classname = nil
self.superclass = 'Racc::Parser'
self.omit_action_call = true
self.result_var = true
self.header = []
self.inner = []
self.footer = []
# Parameters derived from command line options
self.debug_parser = false
self.convert_line = true
self.convert_line_all = false
self.embed_runtime = false
self.make_executable = false
self.interpreter = nil
end
end
def initialize(states, params)
@states = states
@grammar = states.grammar
@params = params
end
def generate_parser
string_io = StringIO.new
init_line_conversion_system
@f = string_io
parser_file
string_io.rewind
string_io.read
end
def generate_parser_file(destpath)
init_line_conversion_system
File.open(destpath, 'w') {|f|
@f = f
parser_file
}
File.chmod 0755, destpath if @params.make_executable?
end
private
def parser_file
shebang @params.interpreter if @params.make_executable?
notice
line
if @params.embed_runtime?
embed_library runtime_source()
else
require 'racc/parser.rb'
end
header
parser_class(@params.classname, @params.superclass) {
inner
state_transition_table
}
footer
end
c = ::RbConfig::CONFIG
RUBY_PATH = "#{c['bindir']}/#{c['ruby_install_name']}#{c['EXEEXT']}"
def shebang(path)
line '#!' + (path == 'ruby' ? RUBY_PATH : path)
end
def notice
line %q[#]
line %q[# DO NOT MODIFY!!!!]
line %Q[# This file is automatically generated by Racc #{Racc::Version}]
line %Q[# from Racc grammar file "#{@params.filename}".]
line %q[#]
end
def runtime_source
SourceText.new(::Racc::PARSER_TEXT, 'racc/parser.rb', 1)
end
def embed_library(src)
line %[###### #{src.filename} begin]
line %[unless $".index '#{src.filename}']
line %[$".push '#{src.filename}']
put src, @params.convert_line?
line %[end]
line %[###### #{src.filename} end]
end
def require(feature)
line "require '#{feature}'"
end
def parser_class(classname, superclass)
mods = classname.split('::')
classid = mods.pop
mods.each do |mod|
indent; line "module #{mod}"
cref_push mod
end
indent; line "class #{classid} < #{superclass}"
cref_push classid
yield
cref_pop
indent; line "end \# class #{classid}"
mods.reverse_each do |mod|
cref_pop
indent; line "end \# module #{mod}"
end
end
def header
@params.header.each do |src|
line
put src, @params.convert_line_all?
end
end
def inner
@params.inner.each do |src|
line
put src, @params.convert_line?
end
end
def footer
@params.footer.each do |src|
line
put src, @params.convert_line_all?
end
end
# Low Level Routines
def put(src, convert_line = false)
if convert_line
replace_location(src) {
@f.puts src.text
}
else
@f.puts src.text
end
end
def line(str = '')
@f.puts str
end
def init_line_conversion_system
@cref = []
@used_separator = {}
end
def cref_push(name)
@cref.push name
end
def cref_pop
@cref.pop
end
def indent
@f.print ' ' * @cref.size
end
def toplevel?
@cref.empty?
end
def replace_location(src)
sep = make_separator(src)
@f.print 'self.class.' if toplevel?
@f.puts "module_eval(<<'#{sep}', '#{src.filename}', #{src.lineno})"
yield
@f.puts sep
end
def make_separator(src)
sep = unique_separator(src.filename)
sep *= 2 while src.text.index(sep)
sep
end
def unique_separator(id)
sep = String.new "...end #{id}/module_eval..."
while @used_separator.key?(sep)
sep.concat sprintf('%02x', rand(255))
end
@used_separator[sep] = true
sep
end
#
# State Transition Table Serialization
#
public
def put_state_transition_table(f)
@f = f
state_transition_table
end
private
def state_transition_table
table = @states.state_transition_table
table.use_result_var = @params.result_var?
table.debug_parser = @params.debug_parser?
line "##### State transition tables begin ###"
line
integer_list 'racc_action_table', table.action_table
line
integer_list 'racc_action_check', table.action_check
line
integer_list 'racc_action_pointer', table.action_pointer
line
integer_list 'racc_action_default', table.action_default
line
integer_list 'racc_goto_table', table.goto_table
line
integer_list 'racc_goto_check', table.goto_check
line
integer_list 'racc_goto_pointer', table.goto_pointer
line
integer_list 'racc_goto_default', table.goto_default
line
i_i_sym_list 'racc_reduce_table', table.reduce_table
line
line "racc_reduce_n = #{table.reduce_n}"
line
line "racc_shift_n = #{table.shift_n}"
line
sym_int_hash 'racc_token_table', table.token_table
line
line "racc_nt_base = #{table.nt_base}"
line
line "racc_use_result_var = #{table.use_result_var}"
line
@f.print(unindent_auto(<<-End))
Racc_arg = [
racc_action_table,
racc_action_check,
racc_action_default,
racc_action_pointer,
racc_goto_table,
racc_goto_check,
racc_goto_default,
racc_goto_pointer,
racc_nt_base,
racc_reduce_table,
racc_token_table,
racc_shift_n,
racc_reduce_n,
racc_use_result_var ]
End
line
string_list 'Racc_token_to_s_table', table.token_to_s_table
line
line "Racc_debug_parser = #{table.debug_parser}"
line
line '##### State transition tables end #####'
actions
end
def integer_list(name, table)
sep = ''
line "#{name} = ["
table.each_slice(10) do |ns|
@f.print sep; sep = ",\n"
@f.print ns.map {|n| sprintf('%6s', n ? n.to_s : 'nil') }.join(',')
end
line ' ]'
end
def i_i_sym_list(name, table)
sep = ''
line "#{name} = ["
table.each_slice(3) do |len, target, mid|
@f.print sep; sep = ",\n"
@f.printf ' %d, %d, %s', len, target, mid.inspect
end
line " ]"
end
def sym_int_hash(name, h)
sep = "\n"
@f.print "#{name} = {"
h.to_a.sort_by {|sym, i| i }.each do |sym, i|
@f.print sep; sep = ",\n"
@f.printf " %s => %d", sym.serialize, i
end
line " }"
end
def string_list(name, list)
sep = " "
line "#{name} = ["
list.each do |s|
@f.print sep; sep = ",\n "
@f.print s.dump
end
line ' ]'
end
def actions
@grammar.each do |rule|
unless rule.action.source?
raise "racc: fatal: cannot generate parser file when any action is a Proc"
end
end
if @params.result_var?
decl = ', result'
retval = "\n result"
default_body = ''
else
decl = ''
retval = ''
default_body = 'val[0]'
end
@grammar.each do |rule|
line
if rule.action.empty? and @params.omit_action_call?
line "# reduce #{rule.ident} omitted"
else
src0 = rule.action.source || SourceText.new(default_body, __FILE__, 0)
if @params.convert_line?
src = remove_blank_lines(src0)
delim = make_delimiter(src.text)
@f.printf unindent_auto(<<-End),
module_eval(<<'%s', '%s', %d)
def _reduce_%d(val, _values%s)
%s%s
end
%s
End
delim, src.filename, src.lineno - 1,
rule.ident, decl,
src.text, retval,
delim
else
src = remove_blank_lines(src0)
@f.printf unindent_auto(<<-End),
def _reduce_%d(val, _values%s)
%s%s
end
End
rule.ident, decl,
src.text, retval
end
end
end
line
@f.printf unindent_auto(<<-'End'), decl
def _reduce_none(val, _values%s)
val[0]
end
End
line
end
def remove_blank_lines(src)
body = src.text.dup
line = src.lineno
while body.slice!(/\A[ \t\f]*(?:\n|\r\n|\r)/)
line += 1
end
SourceText.new(body, src.filename, line)
end
def make_delimiter(body)
delim = '.,.,'
while body.index(delim)
delim *= 2
end
delim
end
def unindent_auto(str)
lines = str.lines.to_a
n = minimum_indent(lines)
lines.map {|line| detab(line).sub(indent_re(n), '').rstrip + "\n" }.join('')
end
def minimum_indent(lines)
lines.map {|line| n_indent(line) }.min
end
def n_indent(line)
line.slice(/\A\s+/).size
end
RE_CACHE = {}
def indent_re(n)
RE_CACHE[n] ||= /\A {#{n}}/
end
def detab(str, ts = 8)
add = 0
len = nil
str.gsub(/\t/) {
len = ts - ($`.size + add) % ts
add += len - 1
' ' * len
}
end
end
end
share/ruby/racc/static.rb 0000644 00000000211 15173547340 0011356 0 ustar 00 require 'racc'
require 'racc/parser'
require 'racc/grammarfileparser'
require 'racc/parserfilegenerator'
require 'racc/logfilegenerator'
share/ruby/racc/iset.rb 0000644 00000002364 15173547340 0011046 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
# An "indexed" set. All items must respond to :ident.
class ISet
def initialize(a = [])
@set = a
end
attr_reader :set
def add(i)
@set[i.ident] = i
end
def [](key)
@set[key.ident]
end
def []=(key, val)
@set[key.ident] = val
end
alias include? []
alias key? []
def update(other)
s = @set
o = other.set
o.each_index do |idx|
if t = o[idx]
s[idx] = t
end
end
end
def update_a(a)
s = @set
a.each {|i| s[i.ident] = i }
end
def delete(key)
i = @set[key.ident]
@set[key.ident] = nil
i
end
def each(&block)
@set.compact.each(&block)
end
def to_a
@set.compact
end
def to_s
"[#{@set.compact.join(' ')}]"
end
alias inspect to_s
def size
@set.nitems
end
def empty?
@set.nitems == 0
end
def clear
@set.clear
end
def dup
ISet.new(@set.dup)
end
end # class ISet
end # module Racc
share/ruby/racc/parser.rb 0000644 00000043666 15173547340 0011410 0 ustar 00 # frozen_string_literal: false
#--
# 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.
#++
require 'racc/info'
unless defined?(NotImplementedError)
NotImplementedError = NotImplementError # :nodoc:
end
module Racc
class ParseError < StandardError; end
end
unless defined?(::ParseError)
ParseError = Racc::ParseError # :nodoc:
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>] [--executable=<var>rubypath</var>]
# [-v] [--verbose]
# [-O<var>filename</var>] [--log-file=<var>filename</var>]
# [-g] [--debug]
# [-E] [--embedded]
# [-l] [--no-line-convert]
# [-c] [--line-convert-all]
# [-a] [--no-omit-actions]
# [-C] [--check-only]
# [-S] [--output-status]
# [--version] [--copyright] [--help] <var>grammarfile</var>
#
# [+grammarfile+]
# Racc grammar file. Any extension is permitted.
# [-o+outfile+, --output-file=+outfile+]
# A filename for output. default is <+filename+>.tab.rb
# [-O+filename+, --log-file=+filename+]
# Place logging output in file +filename+.
# Default log file name is <+filename+>.output.
# [-e+rubypath+, --executable=+rubypath+]
# output executable file(mode 755). where +path+ is the Ruby interpreter.
# [-v, --verbose]
# verbose mode. create +filename+.output file, like yacc's y.output file.
# [-g, --debug]
# add debug code to parser class. To display debuggin 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 ruby license, but your parser is not.
# Your own parser is completely yours.
module Racc
unless defined?(Racc_No_Extensions)
Racc_No_Extensions = false # :nodoc:
end
class Parser
Racc_Runtime_Version = ::Racc::VERSION
Racc_Runtime_Core_Version_R = ::Racc::VERSION
begin
if Object.const_defined?(:RUBY_ENGINE) and RUBY_ENGINE == 'jruby'
require 'jruby'
require 'racc/cparse-jruby.jar'
com.headius.racc.Cparse.new.load(JRuby.runtime, false)
else
require 'racc/cparse'
end
unless new.respond_to?(:_racc_do_parse_c, true)
raise LoadError, 'old cparse.so'
end
if Racc_No_Extensions
raise LoadError, 'selecting ruby version of racc runtime core'
end
Racc_Main_Parsing_Routine = :_racc_do_parse_c # :nodoc:
Racc_YY_Parse_Method = :_racc_yyparse_c # :nodoc:
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_C # :nodoc:
Racc_Runtime_Type = 'c' # :nodoc:
rescue LoadError
Racc_Main_Parsing_Routine = :_racc_do_parse_rb
Racc_YY_Parse_Method = :_racc_yyparse_rb
Racc_Runtime_Core_Version = Racc_Runtime_Core_Version_R
Racc_Runtime_Type = 'ruby'
end
def Parser.racc_runtime_type # :nodoc:
Racc_Runtime_Type
end
def _racc_setup
@yydebug = false unless self.class::Racc_debug_parser
@yydebug = false unless defined?(@yydebug)
if @yydebug
@racc_debug_out = $stderr unless defined?(@racc_debug_out)
@racc_debug_out ||= $stderr
end
arg = self.class::Racc_arg
arg[13] = true if arg.size < 14
arg
end
def _racc_init_sysvars
@racc_state = [0]
@racc_tstack = []
@racc_vstack = []
@racc_t = nil
@racc_val = nil
@racc_read_next = true
@racc_user_yyerror = false
@racc_error_status = 0
end
# The entry point of the parser. This method is used with #next_token.
# If Racc wants to get token (and its value), calls next_token.
#
# Example:
# def parse
# @q = [[1,1],
# [2,2],
# [3,3],
# [false, '$']]
# do_parse
# end
#
# def next_token
# @q.shift
# end
class_eval %{
def do_parse
#{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].
class_eval %{
def yyparse(recv, mid)
#{Racc_YY_Parse_Method}(recv, mid, _racc_setup(), false)
end
}
def _racc_yyparse_rb(recv, mid, arg, c_debug)
action_table, action_check, action_default, action_pointer,
_, _, _, _,
_, _, token_table, * = arg
_racc_init_sysvars
catch(:racc_end_parse) {
until i = action_pointer[@racc_state[-1]]
while act = _racc_evalact(action_default[@racc_state[-1]], arg)
;
end
end
recv.__send__(mid) do |tok, val|
unless tok
@racc_t = 0
else
@racc_t = (token_table[tok] or 1) # error token
end
@racc_val = val
@racc_read_next = false
i += @racc_t
unless i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
while !(i = action_pointer[@racc_state[-1]]) ||
! @racc_read_next ||
@racc_t == 0 # $
unless i and i += @racc_t and
i >= 0 and
act = action_table[i] and
action_check[i] == @racc_state[-1]
act = action_default[@racc_state[-1]]
end
while act = _racc_evalact(act, arg)
;
end
end
end
}
end
###
### common
###
def _racc_evalact(act, arg)
action_table, action_check, _, action_pointer,
_, _, _, _,
_, _, _, shift_n,
reduce_n, * = arg
nerr = 0 # tmp
if act > 0 and act < shift_n
#
# shift
#
if @racc_error_status > 0
@racc_error_status -= 1 unless @racc_t <= 1 # error token or EOF
end
@racc_vstack.push @racc_val
@racc_state.push act
@racc_read_next = true
if @yydebug
@racc_tstack.push @racc_t
racc_shift @racc_t, @racc_tstack, @racc_vstack
end
elsif act < 0 and act > -reduce_n
#
# reduce
#
code = catch(:racc_jump) {
@racc_state.push _racc_do_reduce(arg, act)
false
}
if code
case code
when 1 # yyerror
@racc_user_yyerror = true # user_yyerror
return -reduce_n
when 2 # yyaccept
return shift_n
else
raise '[Racc Bug] unknown jump code'
end
end
elsif act == shift_n
#
# accept
#
racc_accept if @yydebug
throw :racc_end_parse, @racc_vstack[0]
elsif act == -reduce_n
#
# error
#
case @racc_error_status
when 0
unless arg[21] # user_yyerror
nerr += 1
on_error @racc_t, @racc_val, @racc_vstack
end
when 3
if @racc_t == 0 # is $
# We're at EOF, and another error occurred immediately after
# attempting auto-recovery
throw :racc_end_parse, nil
end
@racc_read_next = true
end
@racc_user_yyerror = false
@racc_error_status = 3
while true
if i = action_pointer[@racc_state[-1]]
i += 1 # error token
if i >= 0 and
(act = action_table[i]) and
action_check[i] == @racc_state[-1]
break
end
end
throw :racc_end_parse, nil if @racc_state.size <= 1
@racc_state.pop
@racc_vstack.pop
if @yydebug
@racc_tstack.pop
racc_e_pop @racc_state, @racc_tstack, @racc_vstack
end
end
return act
else
raise "[Racc Bug] unknown action #{act.inspect}"
end
racc_next_state(@racc_state[-1], @racc_state) if @yydebug
nil
end
def _racc_do_reduce(arg, act)
_, _, _, _,
goto_table, goto_check, goto_default, goto_pointer,
nt_base, reduce_table, _, _,
_, use_result, * = arg
state = @racc_state
vstack = @racc_vstack
tstack = @racc_tstack
i = act * -3
len = reduce_table[i]
reduce_to = reduce_table[i+1]
method_id = reduce_table[i+2]
void_array = []
tmp_t = tstack[-len, len] if @yydebug
tmp_v = vstack[-len, len]
tstack[-len, len] = void_array if @yydebug
vstack[-len, len] = void_array
state[-len, len] = void_array
# tstack must be updated AFTER method call
if use_result
vstack.push __send__(method_id, tmp_v, vstack, tmp_v[0])
else
vstack.push __send__(method_id, tmp_v, vstack)
end
tstack.push reduce_to
racc_reduce(tmp_t, reduce_to, tstack, vstack) if @yydebug
k1 = reduce_to - nt_base
if i = goto_pointer[k1]
i += state[-1]
if i >= 0 and (curstate = goto_table[i]) and goto_check[i] == k1
return curstate
end
end
goto_default[k1]
end
# This method is called when a parse error is found.
#
# ERROR_TOKEN_ID is an internal ID of token which caused error.
# You can get string representation of this ID by calling
# #token_to_str.
#
# ERROR_VALUE is a value of error token.
#
# value_stack is a stack of symbol values.
# DO NOT MODIFY this object.
#
# This method raises ParseError by default.
#
# If this method returns, parsers enter "error recovering mode".
def on_error(t, val, vstack)
raise ParseError, sprintf("\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
share/ruby/racc/grammarfileparser.rb 0000644 00000035477 15173547340 0013620 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
require 'racc'
require 'racc/compat'
require 'racc/grammar'
require 'racc/parserfilegenerator'
require 'racc/sourcetext'
require 'stringio'
module Racc
grammar = Grammar.define {
g = self
g.class = seq(:CLASS, :cname, many(:param), :RULE, :rules, option(:END))
g.cname = seq(:rubyconst) {|name|
@result.params.classname = name
}\
| seq(:rubyconst, "<", :rubyconst) {|c, _, s|
@result.params.classname = c
@result.params.superclass = s
}
g.rubyconst = separated_by1(:colon2, :SYMBOL) {|syms|
syms.map {|s| s.to_s }.join('::')
}
g.colon2 = seq(':', ':')
g.param = seq(:CONV, many1(:convdef), :END) {|*|
#@grammar.end_convert_block # FIXME
}\
| seq(:PRECHIGH, many1(:precdef), :PRECLOW) {|*|
@grammar.end_precedence_declaration true
}\
| seq(:PRECLOW, many1(:precdef), :PRECHIGH) {|*|
@grammar.end_precedence_declaration false
}\
| seq(:START, :symbol) {|_, sym|
@grammar.start_symbol = sym
}\
| seq(:TOKEN, :symbols) {|_, syms|
syms.each do |s|
s.should_terminal
end
}\
| seq(:OPTION, :options) {|_, syms|
syms.each do |opt|
case opt
when 'result_var'
@result.params.result_var = true
when 'no_result_var'
@result.params.result_var = false
when 'omit_action_call'
@result.params.omit_action_call = true
when 'no_omit_action_call'
@result.params.omit_action_call = false
else
raise CompileError, "unknown option: #{opt}"
end
end
}\
| seq(:EXPECT, :DIGIT) {|_, num|
if @grammar.n_expected_srconflicts
raise CompileError, "`expect' seen twice"
end
@grammar.n_expected_srconflicts = num
}
g.convdef = seq(:symbol, :STRING) {|sym, code|
sym.serialized = code
}
g.precdef = seq(:LEFT, :symbols) {|_, syms|
@grammar.declare_precedence :Left, syms
}\
| seq(:RIGHT, :symbols) {|_, syms|
@grammar.declare_precedence :Right, syms
}\
| seq(:NONASSOC, :symbols) {|_, syms|
@grammar.declare_precedence :Nonassoc, syms
}
g.symbols = seq(:symbol) {|sym|
[sym]
}\
| seq(:symbols, :symbol) {|list, sym|
list.push sym
list
}\
| seq(:symbols, "|")
g.symbol = seq(:SYMBOL) {|sym| @grammar.intern(sym) }\
| seq(:STRING) {|str| @grammar.intern(str) }
g.options = many(:SYMBOL) {|syms| syms.map {|s| s.to_s } }
g.rules = option(:rules_core) {|list|
add_rule_block list unless list.empty?
nil
}
g.rules_core = seq(:symbol) {|sym|
[sym]
}\
| seq(:rules_core, :rule_item) {|list, i|
list.push i
list
}\
| seq(:rules_core, ';') {|list, *|
add_rule_block list unless list.empty?
list.clear
list
}\
| seq(:rules_core, ':') {|list, *|
next_target = list.pop
add_rule_block list unless list.empty?
[next_target]
}
g.rule_item = seq(:symbol)\
| seq("|") {|*|
OrMark.new(@scanner.lineno)
}\
| seq("=", :symbol) {|_, sym|
Prec.new(sym, @scanner.lineno)
}\
| seq(:ACTION) {|src|
UserAction.source_text(src)
}
}
GrammarFileParser = grammar.parser_class
if grammar.states.srconflict_exist?
raise 'Racc boot script fatal: S/R conflict in build'
end
if grammar.states.rrconflict_exist?
raise 'Racc boot script fatal: R/R conflict in build'
end
class GrammarFileParser # reopen
class Result
def initialize(grammar)
@grammar = grammar
@params = ParserFileGenerator::Params.new
end
attr_reader :grammar
attr_reader :params
end
def GrammarFileParser.parse_file(filename)
parse(File.read(filename), filename, 1)
end
def GrammarFileParser.parse(src, filename = '-', lineno = 1)
new().parse(src, filename, lineno)
end
def initialize(debug_flags = DebugFlags.new)
@yydebug = debug_flags.parse
end
def parse(src, filename = '-', lineno = 1)
@filename = filename
@lineno = lineno
@scanner = GrammarFileScanner.new(src, @filename)
@scanner.debug = @yydebug
@grammar = Grammar.new
@result = Result.new(@grammar)
@embedded_action_seq = 0
yyparse @scanner, :yylex
parse_user_code
@result.grammar.init
@result
end
private
def next_token
@scanner.scan
end
def on_error(tok, val, _values)
if val.respond_to?(:id2name)
v = val.id2name
elsif val.kind_of?(String)
v = val
else
v = val.inspect
end
raise CompileError, "#{location()}: unexpected token '#{v}'"
end
def location
"#{@filename}:#{@lineno - 1 + @scanner.lineno}"
end
def add_rule_block(list)
sprec = nil
target = list.shift
case target
when OrMark, UserAction, Prec
raise CompileError, "#{target.lineno}: unexpected symbol #{target.name}"
end
curr = []
list.each do |i|
case i
when OrMark
add_rule target, curr, sprec
curr = []
sprec = nil
when Prec
raise CompileError, "'=<prec>' used twice in one rule" if sprec
sprec = i.symbol
else
curr.push i
end
end
add_rule target, curr, sprec
end
def add_rule(target, list, sprec)
if list.last.kind_of?(UserAction)
act = list.pop
else
act = UserAction.empty
end
list.map! {|s| s.kind_of?(UserAction) ? embedded_action(s) : s }
rule = Rule.new(target, list, act)
rule.specified_prec = sprec
@grammar.add rule
end
def embedded_action(act)
sym = @grammar.intern("@#{@embedded_action_seq += 1}".intern, true)
@grammar.add Rule.new(sym, [], act)
sym
end
#
# User Code Block
#
def parse_user_code
line = @scanner.lineno
_, *blocks = *@scanner.epilogue.split(/^----/)
blocks.each do |block|
header, *body = block.lines.to_a
label0, paths = *header.sub(/\A-+/, '').split('=', 2)
label = canonical_label(label0)
(paths ? paths.strip.split(' ') : []).each do |path|
add_user_code label, SourceText.new(File.read(path), path, 1)
end
add_user_code label, SourceText.new(body.join(''), @filename, line + 1)
line += (1 + body.size)
end
end
USER_CODE_LABELS = {
'header' => :header,
'prepare' => :header, # obsolete
'inner' => :inner,
'footer' => :footer,
'driver' => :footer # obsolete
}
def canonical_label(src)
label = src.to_s.strip.downcase.slice(/\w+/)
unless USER_CODE_LABELS.key?(label)
raise CompileError, "unknown user code type: #{label.inspect}"
end
label
end
def add_user_code(label, src)
@result.params.public_send(USER_CODE_LABELS[label]).push src
end
end
class GrammarFileScanner
def initialize(str, filename = '-')
@lines = str.b.split(/\n|\r\n|\r/)
@filename = filename
@lineno = -1
@line_head = true
@in_rule_blk = false
@in_conv_blk = false
@in_block = nil
@epilogue = ''
@debug = false
next_line
end
attr_reader :epilogue
def lineno
@lineno + 1
end
attr_accessor :debug
def yylex(&block)
unless @debug
yylex0(&block)
else
yylex0 do |sym, tok|
$stderr.printf "%7d %-10s %s\n", lineno(), sym.inspect, tok.inspect
yield [sym, tok]
end
end
end
private
def yylex0
begin
until @line.empty?
@line.sub!(/\A\s+/, '')
if /\A\#/ =~ @line
break
elsif /\A\/\*/ =~ @line
skip_comment
elsif s = reads(/\A[a-zA-Z_]\w*/)
yield [atom_symbol(s), s.intern]
elsif s = reads(/\A\d+/)
yield [:DIGIT, s.to_i]
elsif ch = reads(/\A./)
case ch
when '"', "'"
yield [:STRING, eval(scan_quoted(ch))]
when '{'
lineno = lineno()
yield [:ACTION, SourceText.new(scan_action(), @filename, lineno)]
else
if ch == '|'
@line_head = false
end
yield [ch, ch]
end
else
end
end
end while next_line()
yield nil
end
def next_line
@lineno += 1
@line = @lines[@lineno]
if not @line or /\A----/ =~ @line
@epilogue = @lines.join("\n")
@lines.clear
@line = nil
if @in_block
@lineno -= 1
scan_error! sprintf('unterminated %s', @in_block)
end
false
else
@line.sub!(/(?:\n|\r\n|\r)\z/, '')
@line_head = true
true
end
end
ReservedWord = {
'right' => :RIGHT,
'left' => :LEFT,
'nonassoc' => :NONASSOC,
'preclow' => :PRECLOW,
'prechigh' => :PRECHIGH,
'token' => :TOKEN,
'convert' => :CONV,
'options' => :OPTION,
'start' => :START,
'expect' => :EXPECT,
'class' => :CLASS,
'rule' => :RULE,
'end' => :END
}
def atom_symbol(token)
if token == 'end'
symbol = :END
@in_conv_blk = false
@in_rule_blk = false
else
if @line_head and not @in_conv_blk and not @in_rule_blk
symbol = ReservedWord[token] || :SYMBOL
else
symbol = :SYMBOL
end
case symbol
when :RULE then @in_rule_blk = true
when :CONV then @in_conv_blk = true
end
end
@line_head = false
symbol
end
def skip_comment
@in_block = 'comment'
until m = /\*\//.match(@line)
next_line
end
@line = m.post_match
@in_block = nil
end
$raccs_print_type = false
def scan_action
buf = String.new
nest = 1
pre = nil
@in_block = 'action'
begin
pre = nil
if s = reads(/\A\s+/)
# does not set 'pre'
buf << s
end
until @line.empty?
if s = reads(/\A[^'"`{}%#\/\$]+/)
buf << (pre = s)
next
end
case ch = read(1)
when '{'
nest += 1
buf << (pre = ch)
when '}'
nest -= 1
if nest == 0
@in_block = nil
buf.sub!(/[ \t\f]+\z/, '')
return buf
end
buf << (pre = ch)
when '#' # comment
buf << ch << @line
break
when "'", '"', '`'
buf << (pre = scan_quoted(ch))
when '%'
if literal_head? pre, @line
# % string, regexp, array
buf << ch
case ch = read(1)
when /[qQx]/n
buf << ch << (pre = scan_quoted(read(1), '%string'))
when /wW/n
buf << ch << (pre = scan_quoted(read(1), '%array'))
when /s/n
buf << ch << (pre = scan_quoted(read(1), '%symbol'))
when /r/n
buf << ch << (pre = scan_quoted(read(1), '%regexp'))
when /[a-zA-Z0-9= ]/n # does not include "_"
scan_error! "unknown type of % literal '%#{ch}'"
else
buf << (pre = scan_quoted(ch, '%string'))
end
else
# operator
buf << '||op->' if $raccs_print_type
buf << (pre = ch)
end
when '/'
if literal_head? pre, @line
# regexp
buf << (pre = scan_quoted(ch, 'regexp'))
else
# operator
buf << '||op->' if $raccs_print_type
buf << (pre = ch)
end
when '$' # gvar
buf << ch << (pre = read(1))
else
raise 'racc: fatal: must not happen'
end
end
buf << "\n"
end while next_line()
raise 'racc: fatal: scan finished before parser finished'
end
def literal_head?(pre, post)
(!pre || /[a-zA-Z_0-9]/n !~ pre[-1,1]) &&
!post.empty? && /\A[\s\=]/n !~ post
end
def read(len)
s = @line[0, len]
@line = @line[len .. -1]
s
end
def reads(re)
m = re.match(@line) or return nil
@line = m.post_match
m[0]
end
def scan_quoted(left, tag = 'string')
buf = left.dup
buf = "||#{tag}->" + buf if $raccs_print_type
re = get_quoted_re(left)
sv, @in_block = @in_block, tag
begin
if s = reads(re)
buf << s
break
else
buf << @line
end
end while next_line()
@in_block = sv
buf << "<-#{tag}||" if $raccs_print_type
buf
end
LEFT_TO_RIGHT = {
'(' => ')',
'{' => '}',
'[' => ']',
'<' => '>'
}
CACHE = {}
def get_quoted_re(left)
term = Regexp.quote(LEFT_TO_RIGHT[left] || left)
CACHE[left] ||= /\A[^#{term}\\]*(?:\\.[^\\#{term}]*)*#{term}/
end
def scan_error!(msg)
raise CompileError, "#{lineno()}: #{msg}"
end
end
end # module Racc
share/ruby/racc/logfilegenerator.rb 0000644 00000012055 15173547340 0013430 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
class LogFileGenerator
def initialize(states, debug_flags = DebugFlags.new)
@states = states
@grammar = states.grammar
@debug_flags = debug_flags
end
def output(out)
output_conflict out; out.puts
output_useless out; out.puts
output_rule out; out.puts
output_token out; out.puts
output_state out
end
#
# Warnings
#
def output_conflict(out)
@states.each do |state|
if state.srconf
out.printf "state %d contains %d shift/reduce conflicts\n",
state.stateid, state.srconf.size
end
if state.rrconf
out.printf "state %d contains %d reduce/reduce conflicts\n",
state.stateid, state.rrconf.size
end
end
end
def output_useless(out)
@grammar.each do |rl|
if rl.useless?
out.printf "rule %d (%s) never reduced\n",
rl.ident, rl.target.to_s
end
end
@grammar.each_nonterminal do |t|
if t.useless?
out.printf "useless nonterminal %s\n", t.to_s
end
end
end
#
# States
#
def output_state(out)
out << "--------- State ---------\n"
showall = @debug_flags.la || @debug_flags.state
@states.each do |state|
out << "\nstate #{state.ident}\n\n"
(showall ? state.closure : state.core).each do |ptr|
pointer_out(out, ptr) if ptr.rule.ident != 0 or showall
end
out << "\n"
action_out out, state
end
end
def pointer_out(out, ptr)
buf = sprintf("%4d) %s :", ptr.rule.ident, ptr.rule.target.to_s)
ptr.rule.symbols.each_with_index do |tok, idx|
buf << ' _' if idx == ptr.index
buf << ' ' << tok.to_s
end
buf << ' _' if ptr.reduce?
out.puts buf
end
def action_out(f, state)
sr = state.srconf && state.srconf.dup
rr = state.rrconf && state.rrconf.dup
acts = state.action
keys = acts.keys
keys.sort! {|a,b| a.ident <=> b.ident }
[ Shift, Reduce, Error, Accept ].each do |klass|
keys.delete_if do |tok|
act = acts[tok]
if act.kind_of?(klass)
outact f, tok, act
if sr and c = sr.delete(tok)
outsrconf f, c
end
if rr and c = rr.delete(tok)
outrrconf f, c
end
true
else
false
end
end
end
sr.each {|tok, c| outsrconf f, c } if sr
rr.each {|tok, c| outrrconf f, c } if rr
act = state.defact
if not act.kind_of?(Error) or @debug_flags.any?
outact f, '$default', act
end
f.puts
state.goto_table.each do |t, st|
if t.nonterminal?
f.printf " %-12s go to state %d\n", t.to_s, st.ident
end
end
end
def outact(f, t, act)
case act
when Shift
f.printf " %-12s shift, and go to state %d\n",
t.to_s, act.goto_id
when Reduce
f.printf " %-12s reduce using rule %d (%s)\n",
t.to_s, act.ruleid, act.rule.target.to_s
when Accept
f.printf " %-12s accept\n", t.to_s
when Error
f.printf " %-12s error\n", t.to_s
else
raise "racc: fatal: wrong act for outact: act=#{act}(#{act.class})"
end
end
def outsrconf(f, confs)
confs.each do |c|
r = c.reduce
f.printf " %-12s [reduce using rule %d (%s)]\n",
c.shift.to_s, r.ident, r.target.to_s
end
end
def outrrconf(f, confs)
confs.each do |c|
r = c.low_prec
f.printf " %-12s [reduce using rule %d (%s)]\n",
c.token.to_s, r.ident, r.target.to_s
end
end
#
# Rules
#
def output_rule(out)
out.print "-------- Grammar --------\n\n"
@grammar.each do |rl|
if @debug_flags.any? or rl.ident != 0
out.printf "rule %d %s: %s\n",
rl.ident, rl.target.to_s, rl.symbols.join(' ')
end
end
end
#
# Tokens
#
def output_token(out)
out.print "------- Symbols -------\n\n"
out.print "**Nonterminals, with rules where they appear\n\n"
@grammar.each_nonterminal do |t|
tmp = <<SRC
%s (%d)
on right: %s
on left : %s
SRC
out.printf tmp, t.to_s, t.ident,
symbol_locations(t.locate).join(' '),
symbol_locations(t.heads).join(' ')
end
out.print "\n**Terminals, with rules where they appear\n\n"
@grammar.each_terminal do |t|
out.printf " %s (%d) %s\n",
t.to_s, t.ident, symbol_locations(t.locate).join(' ')
end
end
def symbol_locations(locs)
locs.map {|loc| loc.rule.ident }.reject {|n| n == 0 }.uniq
end
end
end # module Racc
share/ruby/racc/info.rb 0000644 00000000451 15173547340 0011030 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
module Racc
VERSION = '1.6.2'
Version = VERSION
Copyright = 'Copyright (c) 1999-2006 Minero Aoki'
end
share/ruby/racc/statetransitiontable.rb 0000644 00000017521 15173547340 0014346 0 ustar 00 #--
#
#
#
# Copyright (c) 1999-2006 Minero Aoki
#
# This program is free software.
# You can distribute/modify this program under the same terms of ruby.
# see the file "COPYING".
#
#++
require 'racc/parser'
module Racc
StateTransitionTable = Struct.new(:action_table,
:action_check,
:action_default,
:action_pointer,
:goto_table,
:goto_check,
:goto_default,
:goto_pointer,
:token_table,
:reduce_table,
:reduce_n,
:shift_n,
:nt_base,
:token_to_s_table,
:use_result_var,
:debug_parser)
class StateTransitionTable # reopen
def StateTransitionTable.generate(states)
StateTransitionTableGenerator.new(states).generate
end
def initialize(states)
super()
@states = states
@grammar = states.grammar
self.use_result_var = true
self.debug_parser = true
end
attr_reader :states
attr_reader :grammar
def parser_class
ParserClassGenerator.new(@states).generate
end
def token_value_table
h = {}
token_table().each do |sym, i|
h[sym.value] = i
end
h
end
end
class StateTransitionTableGenerator
def initialize(states)
@states = states
@grammar = states.grammar
end
def generate
t = StateTransitionTable.new(@states)
gen_action_tables t, @states
gen_goto_tables t, @grammar
t.token_table = token_table(@grammar)
t.reduce_table = reduce_table(@grammar)
t.reduce_n = @states.reduce_n
t.shift_n = @states.shift_n
t.nt_base = @grammar.nonterminal_base
t.token_to_s_table = @grammar.symbols.map {|sym| sym.to_s }
t
end
def reduce_table(grammar)
t = [0, 0, :racc_error]
grammar.each_with_index do |rule, idx|
next if idx == 0
t.push rule.size
t.push rule.target.ident
t.push(if rule.action.empty? # and @params.omit_action_call?
then :_reduce_none
else "_reduce_#{idx}".intern
end)
end
t
end
def token_table(grammar)
h = {}
grammar.symboltable.terminals.each do |t|
h[t] = t.ident
end
h
end
def gen_action_tables(t, states)
t.action_table = yytable = []
t.action_check = yycheck = []
t.action_default = yydefact = []
t.action_pointer = yypact = []
e1 = []
e2 = []
states.each do |state|
yydefact.push act2actid(state.defact)
if state.action.empty?
yypact.push nil
next
end
vector = []
state.action.each do |tok, act|
vector[tok.ident] = act2actid(act)
end
addent e1, vector, state.ident, yypact
end
set_table e1, e2, yytable, yycheck, yypact
end
def gen_goto_tables(t, grammar)
t.goto_table = yytable2 = []
t.goto_check = yycheck2 = []
t.goto_pointer = yypgoto = []
t.goto_default = yydefgoto = []
e1 = []
e2 = []
grammar.each_nonterminal do |tok|
tmp = []
# decide default
freq = Array.new(@states.size, 0)
@states.each do |state|
st = state.goto_table[tok]
if st
st = st.ident
freq[st] += 1
end
tmp[state.ident] = st
end
max = freq.max
if max > 1
default = freq.index(max)
tmp.map! {|i| default == i ? nil : i }
else
default = nil
end
yydefgoto.push default
# delete default value
tmp.pop until tmp.last or tmp.empty?
if tmp.compact.empty?
# only default
yypgoto.push nil
next
end
addent e1, tmp, (tok.ident - grammar.nonterminal_base), yypgoto
end
set_table e1, e2, yytable2, yycheck2, yypgoto
end
def addent(all, arr, chkval, ptr)
max = arr.size
min = nil
arr.each_with_index do |item, idx|
if item
min ||= idx
end
end
ptr.push(-7777) # mark
arr = arr[min...max]
all.push [arr, chkval, mkmapexp(arr), min, ptr.size - 1]
end
n = 2 ** 16
begin
Regexp.compile("a{#{n}}")
RE_DUP_MAX = n
rescue RegexpError
n /= 2
retry
end
def mkmapexp(arr)
i = ii = 0
as = arr.size
map = String.new
maxdup = RE_DUP_MAX
curr = nil
while i < as
ii = i + 1
if arr[i]
ii += 1 while ii < as and arr[ii]
curr = '-'
else
ii += 1 while ii < as and not arr[ii]
curr = '.'
end
offset = ii - i
if offset == 1
map << curr
else
while offset > maxdup
map << "#{curr}{#{maxdup}}"
offset -= maxdup
end
map << "#{curr}{#{offset}}" if offset > 1
end
i = ii
end
Regexp.compile(map, Regexp::NOENCODING)
end
def set_table(entries, dummy, tbl, chk, ptr)
upper = 0
map = '-' * 10240
# sort long to short
entries.sort_by!.with_index {|a,i| [-a[0].size, i] }
entries.each do |arr, chkval, expr, min, ptri|
if upper + arr.size > map.size
map << '-' * (arr.size + 1024)
end
idx = map.index(expr)
ptr[ptri] = idx - min
arr.each_with_index do |item, i|
if item
i += idx
tbl[i] = item
chk[i] = chkval
map[i] = ?o
end
end
upper = idx + arr.size
end
end
def act2actid(act)
case act
when Shift then act.goto_id
when Reduce then -act.ruleid
when Accept then @states.shift_n
when Error then @states.reduce_n * -1
else
raise "racc: fatal: wrong act type #{act.class} in action table"
end
end
end
class ParserClassGenerator
def initialize(states)
@states = states
@grammar = states.grammar
end
def generate
table = @states.state_transition_table
c = Class.new(::Racc::Parser)
c.const_set :Racc_arg, [table.action_table,
table.action_check,
table.action_default,
table.action_pointer,
table.goto_table,
table.goto_check,
table.goto_default,
table.goto_pointer,
table.nt_base,
table.reduce_table,
table.token_value_table,
table.shift_n,
table.reduce_n,
false]
c.const_set :Racc_token_to_s_table, table.token_to_s_table
c.const_set :Racc_debug_parser, true
define_actions c
c
end
private
def define_actions(c)
c.module_eval "def _reduce_none(vals, vstack) vals[0] end"
@grammar.each do |rule|
if rule.action.empty?
c.alias_method("_reduce_#{rule.ident}", :_reduce_none)
else
c.define_method("_racc_action_#{rule.ident}", &rule.action.proc)
c.module_eval(<<-End, __FILE__, __LINE__ + 1)
def _reduce_#{rule.ident}(vals, vstack)
_racc_action_#{rule.ident}(*vals)
end
End
end
end
end
end
end # module Racc
share/ruby/tsort.rb 0000644 00000034417 15173547340 0010351 0 ustar 00 # 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 self.tsort(each_node, each_child)
tsort_each(each_node, each_child).to_a
end
# The iterator version of the #tsort method.
# <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
# modification of _obj_ during the iteration may lead to unexpected results.
#
# #tsort_each returns +nil+.
# If there is a cycle, TSort::Cyclic is raised.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.tsort_each {|n| p n }
# #=> 4
# # 2
# # 3
# # 1
#
def tsort_each(&block) # :yields: node
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.tsort_each(each_node, each_child, &block)
end
# The iterator version of the TSort.tsort method.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.tsort_each(each_node, each_child) {|n| p n }
# #=> 4
# # 2
# # 3
# # 1
#
def self.tsort_each(each_node, each_child) # :yields: node
return to_enum(__method__, each_node, each_child) unless block_given?
each_strongly_connected_component(each_node, each_child) {|component|
if component.size == 1
yield component.first
else
raise Cyclic.new("topological sort failed: #{component.inspect}")
end
}
end
# Returns strongly connected components as an array of arrays of nodes.
# The array is sorted from children to parents.
# Each elements of the array represents a strongly connected component.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
#
def strongly_connected_components
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.strongly_connected_components(each_node, each_child)
end
# Returns strongly connected components as an array of arrays of nodes.
# The array is sorted from children to parents.
# Each elements of the array represents a strongly connected component.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.strongly_connected_components(each_node, each_child)
# #=> [[4], [2], [3], [1]]
#
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.strongly_connected_components(each_node, each_child)
# #=> [[4], [2, 3], [1]]
#
def self.strongly_connected_components(each_node, each_child)
each_strongly_connected_component(each_node, each_child).to_a
end
# The iterator version of the #strongly_connected_components method.
# <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
# <tt><em>obj</em>.strongly_connected_components.each</tt>, but
# modification of _obj_ during the iteration may lead to unexpected results.
#
# #each_strongly_connected_component returns +nil+.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.each_strongly_connected_component {|scc| p scc }
# #=> [4]
# # [2]
# # [3]
# # [1]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# graph.each_strongly_connected_component {|scc| p scc }
# #=> [4]
# # [2, 3]
# # [1]
#
def each_strongly_connected_component(&block) # :yields: nodes
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.each_strongly_connected_component(each_node, each_child, &block)
end
# The iterator version of the TSort.strongly_connected_components method.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
# #=> [4]
# # [2]
# # [3]
# # [1]
#
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
# #=> [4]
# # [2, 3]
# # [1]
#
def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
return to_enum(__method__, each_node, each_child) unless block_given?
id_map = {}
stack = []
each_node.call {|node|
unless id_map.include? node
each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
yield c
}
end
}
nil
end
# Iterates over strongly connected component in the subgraph reachable from
# _node_.
#
# Return value is unspecified.
#
# #each_strongly_connected_component_from doesn't call #tsort_each_node.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
# #=> [4]
# # [2]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
# #=> [4]
# # [2, 3]
#
def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
end
# Iterates over strongly connected components in a graph.
# The graph is represented by _node_ and _each_child_.
#
# _node_ is the first node.
# _each_child_ should have +call+ method which takes a node argument
# and yields for each child node.
#
# Return value is unspecified.
#
# #TSort.each_strongly_connected_component_from is a class method and
# it doesn't need a class to represent a graph which includes TSort.
#
# graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_child = lambda {|n, &b| graph[n].each(&b) }
# TSort.each_strongly_connected_component_from(1, each_child) {|scc|
# p scc
# }
# #=> [4]
# # [2, 3]
# # [1]
#
def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
minimum_id = node_id = id_map[node] = id_map.size
stack_length = stack.length
stack << node
each_child.call(node) {|child|
if id_map.include? child
child_id = id_map[child]
minimum_id = child_id if child_id && child_id < minimum_id
else
sub_minimum_id =
each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
yield c
}
minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
end
}
if node_id == minimum_id
component = stack.slice!(stack_length .. -1)
component.each {|n| id_map[n] = nil}
yield component
end
minimum_id
end
# Should be implemented by a extended class.
#
# #tsort_each_node is used to iterate for all nodes over a graph.
#
def tsort_each_node # :yields: node
raise NotImplementedError.new
end
# Should be implemented by a extended class.
#
# #tsort_each_child is used to iterate for child nodes of _node_.
#
def tsort_each_child(node) # :yields: child
raise NotImplementedError.new
end
end
share/ruby/resolv.rb 0000644 00000224551 15173547340 0010510 0 ustar 00 # frozen_string_literal: true
require 'socket'
require 'timeout'
require 'io/wait'
begin
require 'securerandom'
rescue LoadError
end
# Resolv is a thread-aware DNS resolver library written in Ruby. Resolv can
# handle multiple DNS requests concurrently without blocking the entire Ruby
# interpreter.
#
# See also resolv-replace.rb to replace the libc resolver with Resolv.
#
# Resolv can look up various DNS resources using the DNS module directly.
#
# Examples:
#
# p Resolv.getaddress "www.ruby-lang.org"
# p Resolv.getname "210.251.121.214"
#
# Resolv::DNS.open do |dns|
# ress = dns.getresources "www.ruby-lang.org", Resolv::DNS::Resource::IN::A
# p ress.map(&:address)
# ress = dns.getresources "ruby-lang.org", Resolv::DNS::Resource::IN::MX
# p ress.map { |r| [r.exchange.to_s, r.preference] }
# end
#
#
# == Bugs
#
# * NIS is not supported.
# * /etc/nsswitch.conf is not supported.
class Resolv
##
# Looks up the first IP address for +name+.
def self.getaddress(name)
DefaultResolver.getaddress(name)
end
##
# Looks up all IP address for +name+.
def self.getaddresses(name)
DefaultResolver.getaddresses(name)
end
##
# Iterates over all IP addresses for +name+.
def self.each_address(name, &block)
DefaultResolver.each_address(name, &block)
end
##
# Looks up the hostname of +address+.
def self.getname(address)
DefaultResolver.getname(address)
end
##
# Looks up all hostnames for +address+.
def self.getnames(address)
DefaultResolver.getnames(address)
end
##
# Iterates over all hostnames for +address+.
def self.each_name(address, &proc)
DefaultResolver.each_name(address, &proc)
end
##
# Creates a new Resolv using +resolvers+.
def initialize(resolvers=[Hosts.new, DNS.new])
@resolvers = resolvers
end
##
# Looks up the first IP address for +name+.
def getaddress(name)
each_address(name) {|address| return address}
raise ResolvError.new("no address for #{name}")
end
##
# Looks up all IP address for +name+.
def getaddresses(name)
ret = []
each_address(name) {|address| ret << address}
return ret
end
##
# Iterates over all IP addresses for +name+.
def each_address(name)
if AddressRegex =~ name
yield name
return
end
yielded = false
@resolvers.each {|r|
r.each_address(name) {|address|
yield address.to_s
yielded = true
}
return if yielded
}
end
##
# Looks up the hostname of +address+.
def getname(address)
each_name(address) {|name| return name}
raise ResolvError.new("no name for #{address}")
end
##
# Looks up all hostnames for +address+.
def getnames(address)
ret = []
each_name(address) {|name| ret << name}
return ret
end
##
# Iterates over all hostnames for +address+.
def each_name(address)
yielded = false
@resolvers.each {|r|
r.each_name(address) {|name|
yield name.to_s
yielded = true
}
return if yielded
}
end
##
# Indicates a failure to resolve a name or address.
class ResolvError < StandardError; end
##
# Indicates a timeout resolving a name or address.
class ResolvTimeout < Timeout::Error; end
##
# Resolv::Hosts is a hostname resolver that uses the system hosts file.
class Hosts
if /mswin|mingw|cygwin/ =~ RUBY_PLATFORM and
begin
require 'win32/resolv'
DefaultFileName = Win32::Resolv.get_hosts_path || IO::NULL
rescue LoadError
end
end
DefaultFileName ||= '/etc/hosts'
##
# Creates a new Resolv::Hosts, using +filename+ for its data source.
def initialize(filename = DefaultFileName)
@filename = filename
@mutex = Thread::Mutex.new
@initialized = nil
end
def lazy_initialize # :nodoc:
@mutex.synchronize {
unless @initialized
@name2addr = {}
@addr2name = {}
File.open(@filename, 'rb') {|f|
f.each {|line|
line.sub!(/#.*/, '')
addr, hostname, *aliases = line.split(/\s+/)
next unless addr
@addr2name[addr] = [] unless @addr2name.include? addr
@addr2name[addr] << hostname
@addr2name[addr] += aliases
@name2addr[hostname] = [] unless @name2addr.include? hostname
@name2addr[hostname] << addr
aliases.each {|n|
@name2addr[n] = [] unless @name2addr.include? n
@name2addr[n] << addr
}
}
}
@name2addr.each {|name, arr| arr.reverse!}
@initialized = true
end
}
self
end
##
# Gets the IP address of +name+ from the hosts file.
def getaddress(name)
each_address(name) {|address| return address}
raise ResolvError.new("#{@filename} has no name: #{name}")
end
##
# Gets all IP addresses for +name+ from the hosts file.
def getaddresses(name)
ret = []
each_address(name) {|address| ret << address}
return ret
end
##
# Iterates over all IP addresses for +name+ retrieved from the hosts file.
def each_address(name, &proc)
lazy_initialize
@name2addr[name]&.each(&proc)
end
##
# Gets the hostname of +address+ from the hosts file.
def getname(address)
each_name(address) {|name| return name}
raise ResolvError.new("#{@filename} has no address: #{address}")
end
##
# Gets all hostnames for +address+ from the hosts file.
def getnames(address)
ret = []
each_name(address) {|name| ret << name}
return ret
end
##
# Iterates over all hostnames for +address+ retrieved from the hosts file.
def each_name(address, &proc)
lazy_initialize
@addr2name[address]&.each(&proc)
end
end
##
# Resolv::DNS is a DNS stub resolver.
#
# Information taken from the following places:
#
# * STD0013
# * RFC 1035
# * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters
# * etc.
class DNS
##
# Default DNS Port
Port = 53
##
# Default DNS UDP packet size
UDPSize = 512
##
# Creates a new DNS resolver. See Resolv::DNS.new for argument details.
#
# Yields the created DNS resolver to the block, if given, otherwise
# returns it.
def self.open(*args)
dns = new(*args)
return dns unless block_given?
begin
yield dns
ensure
dns.close
end
end
##
# Creates a new DNS resolver.
#
# +config_info+ can be:
#
# nil:: Uses /etc/resolv.conf.
# String:: Path to a file using /etc/resolv.conf's format.
# Hash:: Must contain :nameserver, :search and :ndots keys.
# :nameserver_port can be used to specify port number of nameserver address.
#
# The value of :nameserver should be an address string or
# an array of address strings.
# - :nameserver => '8.8.8.8'
# - :nameserver => ['8.8.8.8', '8.8.4.4']
#
# The value of :nameserver_port should be an array of
# pair of nameserver address and port number.
# - :nameserver_port => [['8.8.8.8', 53], ['8.8.4.4', 53]]
#
# Example:
#
# Resolv::DNS.new(:nameserver => ['210.251.121.21'],
# :search => ['ruby-lang.org'],
# :ndots => 1)
def initialize(config_info=nil)
@mutex = Thread::Mutex.new
@config = Config.new(config_info)
@initialized = nil
end
# Sets the resolver timeouts. This may be a single positive number
# or an array of positive numbers representing timeouts in seconds.
# If an array is specified, a DNS request will retry and wait for
# each successive interval in the array until a successful response
# is received. Specifying +nil+ reverts to the default timeouts:
# [ 5, second = 5 * 2 / nameserver_count, 2 * second, 4 * second ]
#
# Example:
#
# dns.timeouts = 3
#
def timeouts=(values)
@config.timeouts = values
end
def lazy_initialize # :nodoc:
@mutex.synchronize {
unless @initialized
@config.lazy_initialize
@initialized = true
end
}
self
end
##
# Closes the DNS resolver.
def close
@mutex.synchronize {
if @initialized
@initialized = false
end
}
end
##
# Gets the IP address of +name+ from the DNS resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved address will
# be a Resolv::IPv4 or Resolv::IPv6
def getaddress(name)
each_address(name) {|address| return address}
raise ResolvError.new("DNS result has no information for #{name}")
end
##
# Gets all IP addresses for +name+ from the DNS resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
def getaddresses(name)
ret = []
each_address(name) {|address| ret << address}
return ret
end
##
# Iterates over all IP addresses for +name+ retrieved from the DNS
# resolver.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
def each_address(name)
each_resource(name, Resource::IN::A) {|resource| yield resource.address}
if use_ipv6?
each_resource(name, Resource::IN::AAAA) {|resource| yield resource.address}
end
end
def use_ipv6? # :nodoc:
begin
list = Socket.ip_address_list
rescue NotImplementedError
return true
end
list.any? {|a| a.ipv6? && !a.ipv6_loopback? && !a.ipv6_linklocal? }
end
private :use_ipv6?
##
# Gets the hostname for +address+ from the DNS resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# name will be a Resolv::DNS::Name.
def getname(address)
each_name(address) {|name| return name}
raise ResolvError.new("DNS result has no information for #{address}")
end
##
# Gets all hostnames for +address+ from the DNS resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# names will be Resolv::DNS::Name instances.
def getnames(address)
ret = []
each_name(address) {|name| ret << name}
return ret
end
##
# Iterates over all hostnames for +address+ retrieved from the DNS
# resolver.
#
# +address+ must be a Resolv::IPv4, Resolv::IPv6 or a String. Retrieved
# names will be Resolv::DNS::Name instances.
def each_name(address)
case address
when Name
ptr = address
when IPv4, IPv6
ptr = address.to_name
when IPv4::Regex
ptr = IPv4.create(address).to_name
when IPv6::Regex
ptr = IPv6.create(address).to_name
else
raise ResolvError.new("cannot interpret as address: #{address}")
end
each_resource(ptr, Resource::IN::PTR) {|resource| yield resource.name}
end
##
# Look up the +typeclass+ DNS resource of +name+.
#
# +name+ must be a Resolv::DNS::Name or a String.
#
# +typeclass+ should be one of the following:
#
# * Resolv::DNS::Resource::IN::A
# * Resolv::DNS::Resource::IN::AAAA
# * Resolv::DNS::Resource::IN::ANY
# * Resolv::DNS::Resource::IN::CNAME
# * Resolv::DNS::Resource::IN::HINFO
# * Resolv::DNS::Resource::IN::MINFO
# * Resolv::DNS::Resource::IN::MX
# * Resolv::DNS::Resource::IN::NS
# * Resolv::DNS::Resource::IN::PTR
# * Resolv::DNS::Resource::IN::SOA
# * Resolv::DNS::Resource::IN::TXT
# * Resolv::DNS::Resource::IN::WKS
#
# Returned resource is represented as a Resolv::DNS::Resource instance,
# i.e. Resolv::DNS::Resource::IN::A.
def getresource(name, typeclass)
each_resource(name, typeclass) {|resource| return resource}
raise ResolvError.new("DNS result has no information for #{name}")
end
##
# Looks up all +typeclass+ DNS resources for +name+. See #getresource for
# argument details.
def getresources(name, typeclass)
ret = []
each_resource(name, typeclass) {|resource| ret << resource}
return ret
end
##
# Iterates over all +typeclass+ DNS resources for +name+. See
# #getresource for argument details.
def each_resource(name, typeclass, &proc)
fetch_resource(name, typeclass) {|reply, reply_name|
extract_resources(reply, reply_name, typeclass, &proc)
}
end
def fetch_resource(name, typeclass)
lazy_initialize
begin
requester = make_udp_requester
rescue Errno::EACCES
# fall back to TCP
end
senders = {}
begin
@config.resolv(name) {|candidate, tout, nameserver, port|
requester ||= make_tcp_requester(nameserver, port)
msg = Message.new
msg.rd = 1
msg.add_question(candidate, typeclass)
unless sender = senders[[candidate, nameserver, port]]
sender = requester.sender(msg, candidate, nameserver, port)
next if !sender
senders[[candidate, nameserver, port]] = sender
end
reply, reply_name = requester.request(sender, tout)
case reply.rcode
when RCode::NoError
if reply.tc == 1 and not Requester::TCP === requester
requester.close
# Retry via TCP:
requester = make_tcp_requester(nameserver, port)
senders = {}
# This will use TCP for all remaining candidates (assuming the
# current candidate does not already respond successfully via
# TCP). This makes sense because we already know the full
# response will not fit in an untruncated UDP packet.
redo
else
yield(reply, reply_name)
end
return
when RCode::NXDomain
raise Config::NXDomain.new(reply_name.to_s)
else
raise Config::OtherResolvError.new(reply_name.to_s)
end
}
ensure
requester&.close
end
end
def make_udp_requester # :nodoc:
nameserver_port = @config.nameserver_port
if nameserver_port.length == 1
Requester::ConnectedUDP.new(*nameserver_port[0])
else
Requester::UnconnectedUDP.new(*nameserver_port)
end
end
def make_tcp_requester(host, port) # :nodoc:
return Requester::TCP.new(host, port)
end
def extract_resources(msg, name, typeclass) # :nodoc:
if typeclass < Resource::ANY
n0 = Name.create(name)
msg.each_resource {|n, ttl, data|
yield data if n0 == n
}
end
yielded = false
n0 = Name.create(name)
msg.each_resource {|n, ttl, data|
if n0 == n
case data
when typeclass
yield data
yielded = true
when Resource::CNAME
n0 = data.name
end
end
}
return if yielded
msg.each_resource {|n, ttl, data|
if n0 == n
case data
when typeclass
yield data
end
end
}
end
if defined? SecureRandom
def self.random(arg) # :nodoc:
begin
SecureRandom.random_number(arg)
rescue NotImplementedError
rand(arg)
end
end
else
def self.random(arg) # :nodoc:
rand(arg)
end
end
RequestID = {} # :nodoc:
RequestIDMutex = Thread::Mutex.new # :nodoc:
def self.allocate_request_id(host, port) # :nodoc:
id = nil
RequestIDMutex.synchronize {
h = (RequestID[[host, port]] ||= {})
begin
id = random(0x0000..0xffff)
end while h[id]
h[id] = true
}
id
end
def self.free_request_id(host, port, id) # :nodoc:
RequestIDMutex.synchronize {
key = [host, port]
if h = RequestID[key]
h.delete id
if h.empty?
RequestID.delete key
end
end
}
end
def self.bind_random_port(udpsock, bind_host="0.0.0.0") # :nodoc:
begin
port = random(1024..65535)
udpsock.bind(bind_host, port)
rescue Errno::EADDRINUSE, # POSIX
Errno::EACCES, # SunOS: See PRIV_SYS_NFS in privileges(5)
Errno::EPERM # FreeBSD: security.mac.portacl.port_high is configurable. See mac_portacl(4).
retry
end
end
class Requester # :nodoc:
def initialize
@senders = {}
@socks = nil
end
def request(sender, tout)
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
timelimit = start + tout
begin
sender.send
rescue Errno::EHOSTUNREACH, # multi-homed IPv6 may generate this
Errno::ENETUNREACH
raise ResolvTimeout
end
while true
before_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
timeout = timelimit - before_select
if timeout <= 0
raise ResolvTimeout
end
if @socks.size == 1
select_result = @socks[0].wait_readable(timeout) ? [ @socks ] : nil
else
select_result = IO.select(@socks, nil, nil, timeout)
end
if !select_result
after_select = Process.clock_gettime(Process::CLOCK_MONOTONIC)
next if after_select < timelimit
raise ResolvTimeout
end
begin
reply, from = recv_reply(select_result[0])
rescue Errno::ECONNREFUSED, # GNU/Linux, FreeBSD
Errno::ECONNRESET # Windows
# No name server running on the server?
# Don't wait anymore.
raise ResolvTimeout
end
begin
msg = Message.decode(reply)
rescue DecodeError
next # broken DNS message ignored
end
if sender == sender_for(from, msg)
break
else
# unexpected DNS message ignored
end
end
return msg, sender.data
end
def sender_for(addr, msg)
@senders[[addr,msg.id]]
end
def close
socks = @socks
@socks = nil
socks&.each(&:close)
end
class Sender # :nodoc:
def initialize(msg, data, sock)
@msg = msg
@data = data
@sock = sock
end
end
class UnconnectedUDP < Requester # :nodoc:
def initialize(*nameserver_port)
super()
@nameserver_port = nameserver_port
@initialized = false
@mutex = Thread::Mutex.new
end
def lazy_initialize
@mutex.synchronize {
next if @initialized
@initialized = true
@socks_hash = {}
@socks = []
@nameserver_port.each {|host, port|
if host.index(':')
bind_host = "::"
af = Socket::AF_INET6
else
bind_host = "0.0.0.0"
af = Socket::AF_INET
end
next if @socks_hash[bind_host]
begin
sock = UDPSocket.new(af)
rescue Errno::EAFNOSUPPORT
next # The kernel doesn't support the address family.
end
@socks << sock
@socks_hash[bind_host] = sock
sock.do_not_reverse_lookup = true
DNS.bind_random_port(sock, bind_host)
}
}
self
end
def recv_reply(readable_socks)
lazy_initialize
reply, from = readable_socks[0].recvfrom(UDPSize)
return reply, [from[3],from[1]]
end
def sender(msg, data, host, port=Port)
host = Addrinfo.ip(host).ip_address
lazy_initialize
sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
return nil if !sock
service = [host, port]
id = DNS.allocate_request_id(host, port)
request = msg.encode
request[0,2] = [id].pack('n')
return @senders[[service, id]] =
Sender.new(request, data, sock, host, port)
end
def close
@mutex.synchronize {
if @initialized
super
@senders.each_key {|service, id|
DNS.free_request_id(service[0], service[1], id)
}
@initialized = false
end
}
end
class Sender < Requester::Sender # :nodoc:
def initialize(msg, data, sock, host, port)
super(msg, data, sock)
@host = host
@port = port
end
attr_reader :data
def send
raise "@sock is nil." if @sock.nil?
@sock.send(@msg, 0, @host, @port)
end
end
end
class ConnectedUDP < Requester # :nodoc:
def initialize(host, port=Port)
super()
@host = host
@port = port
@mutex = Thread::Mutex.new
@initialized = false
end
def lazy_initialize
@mutex.synchronize {
next if @initialized
@initialized = true
is_ipv6 = @host.index(':')
sock = UDPSocket.new(is_ipv6 ? Socket::AF_INET6 : Socket::AF_INET)
@socks = [sock]
sock.do_not_reverse_lookup = true
DNS.bind_random_port(sock, is_ipv6 ? "::" : "0.0.0.0")
sock.connect(@host, @port)
}
self
end
def recv_reply(readable_socks)
lazy_initialize
reply = readable_socks[0].recv(UDPSize)
return reply, nil
end
def sender(msg, data, host=@host, port=@port)
lazy_initialize
unless host == @host && port == @port
raise RequestError.new("host/port don't match: #{host}:#{port}")
end
id = DNS.allocate_request_id(@host, @port)
request = msg.encode
request[0,2] = [id].pack('n')
return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
end
def close
@mutex.synchronize do
if @initialized
super
@senders.each_key {|from, id|
DNS.free_request_id(@host, @port, id)
}
@initialized = false
end
end
end
class Sender < Requester::Sender # :nodoc:
def send
raise "@sock is nil." if @sock.nil?
@sock.send(@msg, 0)
end
attr_reader :data
end
end
class MDNSOneShot < UnconnectedUDP # :nodoc:
def sender(msg, data, host, port=Port)
lazy_initialize
id = DNS.allocate_request_id(host, port)
request = msg.encode
request[0,2] = [id].pack('n')
sock = @socks_hash[host.index(':') ? "::" : "0.0.0.0"]
return @senders[id] =
UnconnectedUDP::Sender.new(request, data, sock, host, port)
end
def sender_for(addr, msg)
lazy_initialize
@senders[msg.id]
end
end
class TCP < Requester # :nodoc:
def initialize(host, port=Port)
super()
@host = host
@port = port
sock = TCPSocket.new(@host, @port)
@socks = [sock]
@senders = {}
end
def recv_reply(readable_socks)
len = readable_socks[0].read(2).unpack('n')[0]
reply = @socks[0].read(len)
return reply, nil
end
def sender(msg, data, host=@host, port=@port)
unless host == @host && port == @port
raise RequestError.new("host/port don't match: #{host}:#{port}")
end
id = DNS.allocate_request_id(@host, @port)
request = msg.encode
request[0,2] = [request.length, id].pack('nn')
return @senders[[nil,id]] = Sender.new(request, data, @socks[0])
end
class Sender < Requester::Sender # :nodoc:
def send
@sock.print(@msg)
@sock.flush
end
attr_reader :data
end
def close
super
@senders.each_key {|from,id|
DNS.free_request_id(@host, @port, id)
}
end
end
##
# Indicates a problem with the DNS request.
class RequestError < StandardError
end
end
class Config # :nodoc:
def initialize(config_info=nil)
@mutex = Thread::Mutex.new
@config_info = config_info
@initialized = nil
@timeouts = nil
end
def timeouts=(values)
if values
values = Array(values)
values.each do |t|
Numeric === t or raise ArgumentError, "#{t.inspect} is not numeric"
t > 0.0 or raise ArgumentError, "timeout=#{t} must be positive"
end
@timeouts = values
else
@timeouts = nil
end
end
def Config.parse_resolv_conf(filename)
nameserver = []
search = nil
ndots = 1
File.open(filename, 'rb') {|f|
f.each {|line|
line.sub!(/[#;].*/, '')
keyword, *args = line.split(/\s+/)
next unless keyword
case keyword
when 'nameserver'
nameserver += args
when 'domain'
next if args.empty?
search = [args[0]]
when 'search'
next if args.empty?
search = args
when 'options'
args.each {|arg|
case arg
when /\Andots:(\d+)\z/
ndots = $1.to_i
end
}
end
}
}
return { :nameserver => nameserver, :search => search, :ndots => ndots }
end
def Config.default_config_hash(filename="/etc/resolv.conf")
if File.exist? filename
config_hash = Config.parse_resolv_conf(filename)
else
if /mswin|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM
require 'win32/resolv'
search, nameserver = Win32::Resolv.get_resolv_info
config_hash = {}
config_hash[:nameserver] = nameserver if nameserver
config_hash[:search] = [search].flatten if search
end
end
config_hash || {}
end
def lazy_initialize
@mutex.synchronize {
unless @initialized
@nameserver_port = []
@search = nil
@ndots = 1
case @config_info
when nil
config_hash = Config.default_config_hash
when String
config_hash = Config.parse_resolv_conf(@config_info)
when Hash
config_hash = @config_info.dup
if String === config_hash[:nameserver]
config_hash[:nameserver] = [config_hash[:nameserver]]
end
if String === config_hash[:search]
config_hash[:search] = [config_hash[:search]]
end
else
raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}")
end
if config_hash.include? :nameserver
@nameserver_port = config_hash[:nameserver].map {|ns| [ns, Port] }
end
if config_hash.include? :nameserver_port
@nameserver_port = config_hash[:nameserver_port].map {|ns, port| [ns, (port || Port)] }
end
@search = config_hash[:search] if config_hash.include? :search
@ndots = config_hash[:ndots] if config_hash.include? :ndots
if @nameserver_port.empty?
@nameserver_port << ['0.0.0.0', Port]
end
if @search
@search = @search.map {|arg| Label.split(arg) }
else
hostname = Socket.gethostname
if /\./ =~ hostname
@search = [Label.split($')]
else
@search = [[]]
end
end
if !@nameserver_port.kind_of?(Array) ||
@nameserver_port.any? {|ns_port|
!(Array === ns_port) ||
ns_port.length != 2
!(String === ns_port[0]) ||
!(Integer === ns_port[1])
}
raise ArgumentError.new("invalid nameserver config: #{@nameserver_port.inspect}")
end
if !@search.kind_of?(Array) ||
!@search.all? {|ls| ls.all? {|l| Label::Str === l } }
raise ArgumentError.new("invalid search config: #{@search.inspect}")
end
if !@ndots.kind_of?(Integer)
raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}")
end
@initialized = true
end
}
self
end
def single?
lazy_initialize
if @nameserver_port.length == 1
return @nameserver_port[0]
else
return nil
end
end
def nameserver_port
@nameserver_port
end
def generate_candidates(name)
candidates = nil
name = Name.create(name)
if name.absolute?
candidates = [name]
else
if @ndots <= name.length - 1
candidates = [Name.new(name.to_a)]
else
candidates = []
end
candidates.concat(@search.map {|domain| Name.new(name.to_a + domain)})
fname = Name.create("#{name}.")
if !candidates.include?(fname)
candidates << fname
end
end
return candidates
end
InitialTimeout = 5
def generate_timeouts
ts = [InitialTimeout]
ts << ts[-1] * 2 / @nameserver_port.length
ts << ts[-1] * 2
ts << ts[-1] * 2
return ts
end
def resolv(name)
candidates = generate_candidates(name)
timeouts = @timeouts || generate_timeouts
begin
candidates.each {|candidate|
begin
timeouts.each {|tout|
@nameserver_port.each {|nameserver, port|
begin
yield candidate, tout, nameserver, port
rescue ResolvTimeout
end
}
}
raise ResolvError.new("DNS resolv timeout: #{name}")
rescue NXDomain
end
}
rescue ResolvError
end
end
##
# Indicates no such domain was found.
class NXDomain < ResolvError
end
##
# Indicates some other unhandled resolver error was encountered.
class OtherResolvError < ResolvError
end
end
module OpCode # :nodoc:
Query = 0
IQuery = 1
Status = 2
Notify = 4
Update = 5
end
module RCode # :nodoc:
NoError = 0
FormErr = 1
ServFail = 2
NXDomain = 3
NotImp = 4
Refused = 5
YXDomain = 6
YXRRSet = 7
NXRRSet = 8
NotAuth = 9
NotZone = 10
BADVERS = 16
BADSIG = 16
BADKEY = 17
BADTIME = 18
BADMODE = 19
BADNAME = 20
BADALG = 21
end
##
# Indicates that the DNS response was unable to be decoded.
class DecodeError < StandardError
end
##
# Indicates that the DNS request was unable to be encoded.
class EncodeError < StandardError
end
module Label # :nodoc:
def self.split(arg)
labels = []
arg.scan(/[^\.]+/) {labels << Str.new($&)}
return labels
end
class Str # :nodoc:
def initialize(string)
@string = string
# case insensivity of DNS labels doesn't apply non-ASCII characters. [RFC 4343]
# This assumes @string is given in ASCII compatible encoding.
@downcase = string.b.downcase
end
attr_reader :string, :downcase
def to_s
return @string
end
def inspect
return "#<#{self.class} #{self}>"
end
def ==(other)
return self.class == other.class && @downcase == other.downcase
end
def eql?(other)
return self == other
end
def hash
return @downcase.hash
end
end
end
##
# A representation of a DNS name.
class Name
##
# Creates a new DNS name from +arg+. +arg+ can be:
#
# Name:: returns +arg+.
# String:: Creates a new Name.
def self.create(arg)
case arg
when Name
return arg
when String
return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false)
else
raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}")
end
end
def initialize(labels, absolute=true) # :nodoc:
labels = labels.map {|label|
case label
when String then Label::Str.new(label)
when Label::Str then label
else
raise ArgumentError, "unexpected label: #{label.inspect}"
end
}
@labels = labels
@absolute = absolute
end
def inspect # :nodoc:
"#<#{self.class}: #{self}#{@absolute ? '.' : ''}>"
end
##
# True if this name is absolute.
def absolute?
return @absolute
end
def ==(other) # :nodoc:
return false unless Name === other
return false unless @absolute == other.absolute?
return @labels == other.to_a
end
alias eql? == # :nodoc:
##
# Returns true if +other+ is a subdomain.
#
# Example:
#
# domain = Resolv::DNS::Name.create("y.z")
# p Resolv::DNS::Name.create("w.x.y.z").subdomain_of?(domain) #=> true
# p Resolv::DNS::Name.create("x.y.z").subdomain_of?(domain) #=> true
# p Resolv::DNS::Name.create("y.z").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("z").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("x.y.z.").subdomain_of?(domain) #=> false
# p Resolv::DNS::Name.create("w.z").subdomain_of?(domain) #=> false
#
def subdomain_of?(other)
raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other
return false if @absolute != other.absolute?
other_len = other.length
return false if @labels.length <= other_len
return @labels[-other_len, other_len] == other.to_a
end
def hash # :nodoc:
return @labels.hash ^ @absolute.hash
end
def to_a # :nodoc:
return @labels
end
def length # :nodoc:
return @labels.length
end
def [](i) # :nodoc:
return @labels[i]
end
##
# returns the domain name as a string.
#
# The domain name doesn't have a trailing dot even if the name object is
# absolute.
#
# Example:
#
# p Resolv::DNS::Name.create("x.y.z.").to_s #=> "x.y.z"
# p Resolv::DNS::Name.create("x.y.z").to_s #=> "x.y.z"
def to_s
return @labels.join('.')
end
end
class Message # :nodoc:
@@identifier = -1
def initialize(id = (@@identifier += 1) & 0xffff)
@id = id
@qr = 0
@opcode = 0
@aa = 0
@tc = 0
@rd = 0 # recursion desired
@ra = 0 # recursion available
@rcode = 0
@question = []
@answer = []
@authority = []
@additional = []
end
attr_accessor :id, :qr, :opcode, :aa, :tc, :rd, :ra, :rcode
attr_reader :question, :answer, :authority, :additional
def ==(other)
return @id == other.id &&
@qr == other.qr &&
@opcode == other.opcode &&
@aa == other.aa &&
@tc == other.tc &&
@rd == other.rd &&
@ra == other.ra &&
@rcode == other.rcode &&
@question == other.question &&
@answer == other.answer &&
@authority == other.authority &&
@additional == other.additional
end
def add_question(name, typeclass)
@question << [Name.create(name), typeclass]
end
def each_question
@question.each {|name, typeclass|
yield name, typeclass
}
end
def add_answer(name, ttl, data)
@answer << [Name.create(name), ttl, data]
end
def each_answer
@answer.each {|name, ttl, data|
yield name, ttl, data
}
end
def add_authority(name, ttl, data)
@authority << [Name.create(name), ttl, data]
end
def each_authority
@authority.each {|name, ttl, data|
yield name, ttl, data
}
end
def add_additional(name, ttl, data)
@additional << [Name.create(name), ttl, data]
end
def each_additional
@additional.each {|name, ttl, data|
yield name, ttl, data
}
end
def each_resource
each_answer {|name, ttl, data| yield name, ttl, data}
each_authority {|name, ttl, data| yield name, ttl, data}
each_additional {|name, ttl, data| yield name, ttl, data}
end
def encode
return MessageEncoder.new {|msg|
msg.put_pack('nnnnnn',
@id,
(@qr & 1) << 15 |
(@opcode & 15) << 11 |
(@aa & 1) << 10 |
(@tc & 1) << 9 |
(@rd & 1) << 8 |
(@ra & 1) << 7 |
(@rcode & 15),
@question.length,
@answer.length,
@authority.length,
@additional.length)
@question.each {|q|
name, typeclass = q
msg.put_name(name)
msg.put_pack('nn', typeclass::TypeValue, typeclass::ClassValue)
}
[@answer, @authority, @additional].each {|rr|
rr.each {|r|
name, ttl, data = r
msg.put_name(name)
msg.put_pack('nnN', data.class::TypeValue, data.class::ClassValue, ttl)
msg.put_length16 {data.encode_rdata(msg)}
}
}
}.to_s
end
class MessageEncoder # :nodoc:
def initialize
@data = ''.dup
@names = {}
yield self
end
def to_s
return @data
end
def put_bytes(d)
@data << d
end
def put_pack(template, *d)
@data << d.pack(template)
end
def put_length16
length_index = @data.length
@data << "\0\0"
data_start = @data.length
yield
data_end = @data.length
@data[length_index, 2] = [data_end - data_start].pack("n")
end
def put_string(d)
self.put_pack("C", d.length)
@data << d
end
def put_string_list(ds)
ds.each {|d|
self.put_string(d)
}
end
def put_name(d)
put_labels(d.to_a)
end
def put_labels(d)
d.each_index {|i|
domain = d[i..-1]
if idx = @names[domain]
self.put_pack("n", 0xc000 | idx)
return
else
if @data.length < 0x4000
@names[domain] = @data.length
end
self.put_label(d[i])
end
}
@data << "\0"
end
def put_label(d)
self.put_string(d.to_s)
end
end
def Message.decode(m)
o = Message.new(0)
MessageDecoder.new(m) {|msg|
id, flag, qdcount, ancount, nscount, arcount =
msg.get_unpack('nnnnnn')
o.id = id
o.qr = (flag >> 15) & 1
o.opcode = (flag >> 11) & 15
o.aa = (flag >> 10) & 1
o.tc = (flag >> 9) & 1
o.rd = (flag >> 8) & 1
o.ra = (flag >> 7) & 1
o.rcode = flag & 15
(1..qdcount).each {
name, typeclass = msg.get_question
o.add_question(name, typeclass)
}
(1..ancount).each {
name, ttl, data = msg.get_rr
o.add_answer(name, ttl, data)
}
(1..nscount).each {
name, ttl, data = msg.get_rr
o.add_authority(name, ttl, data)
}
(1..arcount).each {
name, ttl, data = msg.get_rr
o.add_additional(name, ttl, data)
}
}
return o
end
class MessageDecoder # :nodoc:
def initialize(data)
@data = data
@index = 0
@limit = data.bytesize
yield self
end
def inspect
"\#<#{self.class}: #{@data.byteslice(0, @index).inspect} #{@data.byteslice(@index..-1).inspect}>"
end
def get_length16
len, = self.get_unpack('n')
save_limit = @limit
@limit = @index + len
d = yield(len)
if @index < @limit
raise DecodeError.new("junk exists")
elsif @limit < @index
raise DecodeError.new("limit exceeded")
end
@limit = save_limit
return d
end
def get_bytes(len = @limit - @index)
raise DecodeError.new("limit exceeded") if @limit < @index + len
d = @data.byteslice(@index, len)
@index += len
return d
end
def get_unpack(template)
len = 0
template.each_byte {|byte|
byte = "%c" % byte
case byte
when ?c, ?C
len += 1
when ?n
len += 2
when ?N
len += 4
else
raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'")
end
}
raise DecodeError.new("limit exceeded") if @limit < @index + len
arr = @data.unpack("@#{@index}#{template}")
@index += len
return arr
end
def get_string
raise DecodeError.new("limit exceeded") if @limit <= @index
len = @data.getbyte(@index)
raise DecodeError.new("limit exceeded") if @limit < @index + 1 + len
d = @data.byteslice(@index + 1, len)
@index += 1 + len
return d
end
def get_string_list
strings = []
while @index < @limit
strings << self.get_string
end
strings
end
def get_name
return Name.new(self.get_labels)
end
def get_labels
prev_index = @index
save_index = nil
d = []
size = -1
while true
raise DecodeError.new("limit exceeded") if @limit <= @index
case @data.getbyte(@index)
when 0
@index += 1
if save_index
@index = save_index
end
return d
when 192..255
idx = self.get_unpack('n')[0] & 0x3fff
if prev_index <= idx
raise DecodeError.new("non-backward name pointer")
end
prev_index = idx
if !save_index
save_index = @index
end
@index = idx
else
l = self.get_label
d << l
size += 1 + l.string.bytesize
raise DecodeError.new("name label data exceed 255 octets") if size > 255
end
end
end
def get_label
return Label::Str.new(self.get_string)
end
def get_question
name = self.get_name
type, klass = self.get_unpack("nn")
return name, Resource.get_class(type, klass)
end
def get_rr
name = self.get_name
type, klass, ttl = self.get_unpack('nnN')
typeclass = Resource.get_class(type, klass)
res = self.get_length16 do
begin
typeclass.decode_rdata self
rescue => e
raise DecodeError, e.message, e.backtrace
end
end
res.instance_variable_set :@ttl, ttl
return name, ttl, res
end
end
end
##
# A DNS query abstract class.
class Query
def encode_rdata(msg) # :nodoc:
raise EncodeError.new("#{self.class} is query.")
end
def self.decode_rdata(msg) # :nodoc:
raise DecodeError.new("#{self.class} is query.")
end
end
##
# A DNS resource abstract class.
class Resource < Query
##
# Remaining Time To Live for this Resource.
attr_reader :ttl
ClassHash = {} # :nodoc:
def encode_rdata(msg) # :nodoc:
raise NotImplementedError.new
end
def self.decode_rdata(msg) # :nodoc:
raise NotImplementedError.new
end
def ==(other) # :nodoc:
return false unless self.class == other.class
s_ivars = self.instance_variables
s_ivars.sort!
s_ivars.delete :@ttl
o_ivars = other.instance_variables
o_ivars.sort!
o_ivars.delete :@ttl
return s_ivars == o_ivars &&
s_ivars.collect {|name| self.instance_variable_get name} ==
o_ivars.collect {|name| other.instance_variable_get name}
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
h = 0
vars = self.instance_variables
vars.delete :@ttl
vars.each {|name|
h ^= self.instance_variable_get(name).hash
}
return h
end
def self.get_class(type_value, class_value) # :nodoc:
return ClassHash[[type_value, class_value]] ||
Generic.create(type_value, class_value)
end
##
# A generic resource abstract class.
class Generic < Resource
##
# Creates a new generic resource.
def initialize(data)
@data = data
end
##
# Data for this generic resource.
attr_reader :data
def encode_rdata(msg) # :nodoc:
msg.put_bytes(data)
end
def self.decode_rdata(msg) # :nodoc:
return self.new(msg.get_bytes)
end
def self.create(type_value, class_value) # :nodoc:
c = Class.new(Generic)
c.const_set(:TypeValue, type_value)
c.const_set(:ClassValue, class_value)
Generic.const_set("Type#{type_value}_Class#{class_value}", c)
ClassHash[[type_value, class_value]] = c
return c
end
end
##
# Domain Name resource abstract class.
class DomainName < Resource
##
# Creates a new DomainName from +name+.
def initialize(name)
@name = name
end
##
# The name of this DomainName.
attr_reader :name
def encode_rdata(msg) # :nodoc:
msg.put_name(@name)
end
def self.decode_rdata(msg) # :nodoc:
return self.new(msg.get_name)
end
end
# Standard (class generic) RRs
ClassValue = nil # :nodoc:
##
# An authoritative name server.
class NS < DomainName
TypeValue = 2 # :nodoc:
end
##
# The canonical name for an alias.
class CNAME < DomainName
TypeValue = 5 # :nodoc:
end
##
# Start Of Authority resource.
class SOA < Resource
TypeValue = 6 # :nodoc:
##
# Creates a new SOA record. See the attr documentation for the
# details of each argument.
def initialize(mname, rname, serial, refresh, retry_, expire, minimum)
@mname = mname
@rname = rname
@serial = serial
@refresh = refresh
@retry = retry_
@expire = expire
@minimum = minimum
end
##
# Name of the host where the master zone file for this zone resides.
attr_reader :mname
##
# The person responsible for this domain name.
attr_reader :rname
##
# The version number of the zone file.
attr_reader :serial
##
# How often, in seconds, a secondary name server is to check for
# updates from the primary name server.
attr_reader :refresh
##
# How often, in seconds, a secondary name server is to retry after a
# failure to check for a refresh.
attr_reader :retry
##
# Time in seconds that a secondary name server is to use the data
# before refreshing from the primary name server.
attr_reader :expire
##
# The minimum number of seconds to be used for TTL values in RRs.
attr_reader :minimum
def encode_rdata(msg) # :nodoc:
msg.put_name(@mname)
msg.put_name(@rname)
msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum)
end
def self.decode_rdata(msg) # :nodoc:
mname = msg.get_name
rname = msg.get_name
serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN')
return self.new(
mname, rname, serial, refresh, retry_, expire, minimum)
end
end
##
# A Pointer to another DNS name.
class PTR < DomainName
TypeValue = 12 # :nodoc:
end
##
# Host Information resource.
class HINFO < Resource
TypeValue = 13 # :nodoc:
##
# Creates a new HINFO running +os+ on +cpu+.
def initialize(cpu, os)
@cpu = cpu
@os = os
end
##
# CPU architecture for this resource.
attr_reader :cpu
##
# Operating system for this resource.
attr_reader :os
def encode_rdata(msg) # :nodoc:
msg.put_string(@cpu)
msg.put_string(@os)
end
def self.decode_rdata(msg) # :nodoc:
cpu = msg.get_string
os = msg.get_string
return self.new(cpu, os)
end
end
##
# Mailing list or mailbox information.
class MINFO < Resource
TypeValue = 14 # :nodoc:
def initialize(rmailbx, emailbx)
@rmailbx = rmailbx
@emailbx = emailbx
end
##
# Domain name responsible for this mail list or mailbox.
attr_reader :rmailbx
##
# Mailbox to use for error messages related to the mail list or mailbox.
attr_reader :emailbx
def encode_rdata(msg) # :nodoc:
msg.put_name(@rmailbx)
msg.put_name(@emailbx)
end
def self.decode_rdata(msg) # :nodoc:
rmailbx = msg.get_string
emailbx = msg.get_string
return self.new(rmailbx, emailbx)
end
end
##
# Mail Exchanger resource.
class MX < Resource
TypeValue= 15 # :nodoc:
##
# Creates a new MX record with +preference+, accepting mail at
# +exchange+.
def initialize(preference, exchange)
@preference = preference
@exchange = exchange
end
##
# The preference for this MX.
attr_reader :preference
##
# The host of this MX.
attr_reader :exchange
def encode_rdata(msg) # :nodoc:
msg.put_pack('n', @preference)
msg.put_name(@exchange)
end
def self.decode_rdata(msg) # :nodoc:
preference, = msg.get_unpack('n')
exchange = msg.get_name
return self.new(preference, exchange)
end
end
##
# Unstructured text resource.
class TXT < Resource
TypeValue = 16 # :nodoc:
def initialize(first_string, *rest_strings)
@strings = [first_string, *rest_strings]
end
##
# Returns an Array of Strings for this TXT record.
attr_reader :strings
##
# Returns the concatenated string from +strings+.
def data
@strings.join("")
end
def encode_rdata(msg) # :nodoc:
msg.put_string_list(@strings)
end
def self.decode_rdata(msg) # :nodoc:
strings = msg.get_string_list
return self.new(*strings)
end
end
##
# Location resource
class LOC < Resource
TypeValue = 29 # :nodoc:
def initialize(version, ssize, hprecision, vprecision, latitude, longitude, altitude)
@version = version
@ssize = Resolv::LOC::Size.create(ssize)
@hprecision = Resolv::LOC::Size.create(hprecision)
@vprecision = Resolv::LOC::Size.create(vprecision)
@latitude = Resolv::LOC::Coord.create(latitude)
@longitude = Resolv::LOC::Coord.create(longitude)
@altitude = Resolv::LOC::Alt.create(altitude)
end
##
# Returns the version value for this LOC record which should always be 00
attr_reader :version
##
# The spherical size of this LOC
# in meters using scientific notation as 2 integers of XeY
attr_reader :ssize
##
# The horizontal precision using ssize type values
# in meters using scientific notation as 2 integers of XeY
# for precision use value/2 e.g. 2m = +/-1m
attr_reader :hprecision
##
# The vertical precision using ssize type values
# in meters using scientific notation as 2 integers of XeY
# for precision use value/2 e.g. 2m = +/-1m
attr_reader :vprecision
##
# The latitude for this LOC where 2**31 is the equator
# in thousandths of an arc second as an unsigned 32bit integer
attr_reader :latitude
##
# The longitude for this LOC where 2**31 is the prime meridian
# in thousandths of an arc second as an unsigned 32bit integer
attr_reader :longitude
##
# The altitude of the LOC above a reference sphere whose surface sits 100km below the WGS84 spheroid
# in centimeters as an unsigned 32bit integer
attr_reader :altitude
def encode_rdata(msg) # :nodoc:
msg.put_bytes(@version)
msg.put_bytes(@ssize.scalar)
msg.put_bytes(@hprecision.scalar)
msg.put_bytes(@vprecision.scalar)
msg.put_bytes(@latitude.coordinates)
msg.put_bytes(@longitude.coordinates)
msg.put_bytes(@altitude.altitude)
end
def self.decode_rdata(msg) # :nodoc:
version = msg.get_bytes(1)
ssize = msg.get_bytes(1)
hprecision = msg.get_bytes(1)
vprecision = msg.get_bytes(1)
latitude = msg.get_bytes(4)
longitude = msg.get_bytes(4)
altitude = msg.get_bytes(4)
return self.new(
version,
Resolv::LOC::Size.new(ssize),
Resolv::LOC::Size.new(hprecision),
Resolv::LOC::Size.new(vprecision),
Resolv::LOC::Coord.new(latitude,"lat"),
Resolv::LOC::Coord.new(longitude,"lon"),
Resolv::LOC::Alt.new(altitude)
)
end
end
##
# A Query type requesting any RR.
class ANY < Query
TypeValue = 255 # :nodoc:
end
ClassInsensitiveTypes = [ # :nodoc:
NS, CNAME, SOA, PTR, HINFO, MINFO, MX, TXT, LOC, ANY
]
##
# module IN contains ARPA Internet specific RRs.
module IN
ClassValue = 1 # :nodoc:
ClassInsensitiveTypes.each {|s|
c = Class.new(s)
c.const_set(:TypeValue, s::TypeValue)
c.const_set(:ClassValue, ClassValue)
ClassHash[[s::TypeValue, ClassValue]] = c
self.const_set(s.name.sub(/.*::/, ''), c)
}
##
# IPv4 Address resource
class A < Resource
TypeValue = 1
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
##
# Creates a new A for +address+.
def initialize(address)
@address = IPv4.create(address)
end
##
# The Resolv::IPv4 address for this A.
attr_reader :address
def encode_rdata(msg) # :nodoc:
msg.put_bytes(@address.address)
end
def self.decode_rdata(msg) # :nodoc:
return self.new(IPv4.new(msg.get_bytes(4)))
end
end
##
# Well Known Service resource.
class WKS < Resource
TypeValue = 11
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
def initialize(address, protocol, bitmap)
@address = IPv4.create(address)
@protocol = protocol
@bitmap = bitmap
end
##
# The host these services run on.
attr_reader :address
##
# IP protocol number for these services.
attr_reader :protocol
##
# A bit map of enabled services on this host.
#
# If protocol is 6 (TCP) then the 26th bit corresponds to the SMTP
# service (port 25). If this bit is set, then an SMTP server should
# be listening on TCP port 25; if zero, SMTP service is not
# supported.
attr_reader :bitmap
def encode_rdata(msg) # :nodoc:
msg.put_bytes(@address.address)
msg.put_pack("n", @protocol)
msg.put_bytes(@bitmap)
end
def self.decode_rdata(msg) # :nodoc:
address = IPv4.new(msg.get_bytes(4))
protocol, = msg.get_unpack("n")
bitmap = msg.get_bytes
return self.new(address, protocol, bitmap)
end
end
##
# An IPv6 address record.
class AAAA < Resource
TypeValue = 28
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
##
# Creates a new AAAA for +address+.
def initialize(address)
@address = IPv6.create(address)
end
##
# The Resolv::IPv6 address for this AAAA.
attr_reader :address
def encode_rdata(msg) # :nodoc:
msg.put_bytes(@address.address)
end
def self.decode_rdata(msg) # :nodoc:
return self.new(IPv6.new(msg.get_bytes(16)))
end
end
##
# SRV resource record defined in RFC 2782
#
# These records identify the hostname and port that a service is
# available at.
class SRV < Resource
TypeValue = 33
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
# Create a SRV resource record.
#
# See the documentation for #priority, #weight, #port and #target
# for +priority+, +weight+, +port and +target+ respectively.
def initialize(priority, weight, port, target)
@priority = priority.to_int
@weight = weight.to_int
@port = port.to_int
@target = Name.create(target)
end
# The priority of this target host.
#
# A client MUST attempt to contact the target host with the
# lowest-numbered priority it can reach; target hosts with the same
# priority SHOULD be tried in an order defined by the weight field.
# The range is 0-65535. Note that it is not widely implemented and
# should be set to zero.
attr_reader :priority
# A server selection mechanism.
#
# The weight field specifies a relative weight for entries with the
# same priority. Larger weights SHOULD be given a proportionately
# higher probability of being selected. The range of this number is
# 0-65535. Domain administrators SHOULD use Weight 0 when there
# isn't any server selection to do, to make the RR easier to read
# for humans (less noisy). Note that it is not widely implemented
# and should be set to zero.
attr_reader :weight
# The port on this target host of this service.
#
# The range is 0-65535.
attr_reader :port
# The domain name of the target host.
#
# A target of "." means that the service is decidedly not available
# at this domain.
attr_reader :target
def encode_rdata(msg) # :nodoc:
msg.put_pack("n", @priority)
msg.put_pack("n", @weight)
msg.put_pack("n", @port)
msg.put_name(@target)
end
def self.decode_rdata(msg) # :nodoc:
priority, = msg.get_unpack("n")
weight, = msg.get_unpack("n")
port, = msg.get_unpack("n")
target = msg.get_name
return self.new(priority, weight, port, target)
end
end
end
end
end
##
# A Resolv::DNS IPv4 address.
class IPv4
##
# Regular expression IPv4 addresses must match.
Regex256 = /0
|1(?:[0-9][0-9]?)?
|2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
|[3-9][0-9]?/x
Regex = /\A(#{Regex256})\.(#{Regex256})\.(#{Regex256})\.(#{Regex256})\z/
def self.create(arg)
case arg
when IPv4
return arg
when Regex
if (0..255) === (a = $1.to_i) &&
(0..255) === (b = $2.to_i) &&
(0..255) === (c = $3.to_i) &&
(0..255) === (d = $4.to_i)
return self.new([a, b, c, d].pack("CCCC"))
else
raise ArgumentError.new("IPv4 address with invalid value: " + arg)
end
else
raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}")
end
end
def initialize(address) # :nodoc:
unless address.kind_of?(String)
raise ArgumentError, 'IPv4 address must be a string'
end
unless address.length == 4
raise ArgumentError, "IPv4 address expects 4 bytes but #{address.length} bytes"
end
@address = address
end
##
# A String representation of this IPv4 address.
##
# The raw IPv4 address as a String.
attr_reader :address
def to_s # :nodoc:
return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC"))
end
def inspect # :nodoc:
return "#<#{self.class} #{self}>"
end
##
# Turns this IPv4 address into a Resolv::DNS::Name.
def to_name
return DNS::Name.create(
'%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse)
end
def ==(other) # :nodoc:
return @address == other.address
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
return @address.hash
end
end
##
# A Resolv::DNS IPv6 address.
class IPv6
##
# IPv6 address format a:b:c:d:e:f:g:h
Regex_8Hex = /\A
(?:[0-9A-Fa-f]{1,4}:){7}
[0-9A-Fa-f]{1,4}
\z/x
##
# Compressed IPv6 address format a::b
Regex_CompressedHex = /\A
((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
\z/x
##
# IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z
Regex_6Hex4Dec = /\A
((?:[0-9A-Fa-f]{1,4}:){6,6})
(\d+)\.(\d+)\.(\d+)\.(\d+)
\z/x
##
# Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z
Regex_CompressedHex4Dec = /\A
((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
((?:[0-9A-Fa-f]{1,4}:)*)
(\d+)\.(\d+)\.(\d+)\.(\d+)
\z/x
##
# IPv6 link local address format fe80:b:c:d:e:f:g:h%em1
Regex_8HexLinkLocal = /\A
[Ff][Ee]80
(?::[0-9A-Fa-f]{1,4}){7}
%[-0-9A-Za-z._~]+
\z/x
##
# Compressed IPv6 link local address format fe80::b%em1
Regex_CompressedHexLinkLocal = /\A
[Ff][Ee]80:
(?:
((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
|
:((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
)?
:[0-9A-Fa-f]{1,4}%[-0-9A-Za-z._~]+
\z/x
##
# A composite IPv6 address Regexp.
Regex = /
(?:#{Regex_8Hex}) |
(?:#{Regex_CompressedHex}) |
(?:#{Regex_6Hex4Dec}) |
(?:#{Regex_CompressedHex4Dec}) |
(?:#{Regex_8HexLinkLocal}) |
(?:#{Regex_CompressedHexLinkLocal})
/x
##
# Creates a new IPv6 address from +arg+ which may be:
#
# IPv6:: returns +arg+.
# String:: +arg+ must match one of the IPv6::Regex* constants
def self.create(arg)
case arg
when IPv6
return arg
when String
address = ''.b
if Regex_8Hex =~ arg
arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
elsif Regex_CompressedHex =~ arg
prefix = $1
suffix = $2
a1 = ''.b
a2 = ''.b
prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
omitlen = 16 - a1.length - a2.length
address << a1 << "\0" * omitlen << a2
elsif Regex_6Hex4Dec =~ arg
prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i
if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')}
address << [a, b, c, d].pack('CCCC')
else
raise ArgumentError.new("not numeric IPv6 address: " + arg)
end
elsif Regex_CompressedHex4Dec =~ arg
prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i
if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d
a1 = ''.b
a2 = ''.b
prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')}
suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')}
omitlen = 12 - a1.length - a2.length
address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC')
else
raise ArgumentError.new("not numeric IPv6 address: " + arg)
end
else
raise ArgumentError.new("not numeric IPv6 address: " + arg)
end
return IPv6.new(address)
else
raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}")
end
end
def initialize(address) # :nodoc:
unless address.kind_of?(String) && address.length == 16
raise ArgumentError.new('IPv6 address must be 16 bytes')
end
@address = address
end
##
# The raw IPv6 address as a String.
attr_reader :address
def to_s # :nodoc:
address = sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn"))
unless address.sub!(/(^|:)0(:0)+(:|$)/, '::')
address.sub!(/(^|:)0(:|$)/, '::')
end
return address
end
def inspect # :nodoc:
return "#<#{self.class} #{self}>"
end
##
# Turns this IPv6 address into a Resolv::DNS::Name.
#--
# ip6.arpa should be searched too. [RFC3152]
def to_name
return DNS::Name.new(
@address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa'])
end
def ==(other) # :nodoc:
return @address == other.address
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
return @address.hash
end
end
##
# Resolv::MDNS is a one-shot Multicast DNS (mDNS) resolver. It blindly
# makes queries to the mDNS addresses without understanding anything about
# multicast ports.
#
# Information taken form the following places:
#
# * RFC 6762
class MDNS < DNS
##
# Default mDNS Port
Port = 5353
##
# Default IPv4 mDNS address
AddressV4 = '224.0.0.251'
##
# Default IPv6 mDNS address
AddressV6 = 'ff02::fb'
##
# Default mDNS addresses
Addresses = [
[AddressV4, Port],
[AddressV6, Port],
]
##
# Creates a new one-shot Multicast DNS (mDNS) resolver.
#
# +config_info+ can be:
#
# nil::
# Uses the default mDNS addresses
#
# Hash::
# Must contain :nameserver or :nameserver_port like
# Resolv::DNS#initialize.
def initialize(config_info=nil)
if config_info then
super({ nameserver_port: Addresses }.merge(config_info))
else
super(nameserver_port: Addresses)
end
end
##
# Iterates over all IP addresses for +name+ retrieved from the mDNS
# resolver, provided name ends with "local". If the name does not end in
# "local" no records will be returned.
#
# +name+ can be a Resolv::DNS::Name or a String. Retrieved addresses will
# be a Resolv::IPv4 or Resolv::IPv6
def each_address(name)
name = Resolv::DNS::Name.create(name)
return unless name[-1].to_s == 'local'
super(name)
end
def make_udp_requester # :nodoc:
nameserver_port = @config.nameserver_port
Requester::MDNSOneShot.new(*nameserver_port)
end
end
module LOC
##
# A Resolv::LOC::Size
class Size
Regex = /^(\d+\.*\d*)[m]$/
##
# Creates a new LOC::Size from +arg+ which may be:
#
# LOC::Size:: returns +arg+.
# String:: +arg+ must match the LOC::Size::Regex constant
def self.create(arg)
case arg
when Size
return arg
when String
scalar = ''
if Regex =~ arg
scalar = [(($1.to_f*(1e2)).to_i.to_s[0].to_i*(2**4)+(($1.to_f*(1e2)).to_i.to_s.length-1))].pack("C")
else
raise ArgumentError.new("not a properly formed Size string: " + arg)
end
return Size.new(scalar)
else
raise ArgumentError.new("cannot interpret as Size: #{arg.inspect}")
end
end
def initialize(scalar)
@scalar = scalar
end
##
# The raw size
attr_reader :scalar
def to_s # :nodoc:
s = @scalar.unpack("H2").join.to_s
return ((s[0].to_i)*(10**(s[1].to_i-2))).to_s << "m"
end
def inspect # :nodoc:
return "#<#{self.class} #{self}>"
end
def ==(other) # :nodoc:
return @scalar == other.scalar
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
return @scalar.hash
end
end
##
# A Resolv::LOC::Coord
class Coord
Regex = /^(\d+)\s(\d+)\s(\d+\.\d+)\s([NESW])$/
##
# Creates a new LOC::Coord from +arg+ which may be:
#
# LOC::Coord:: returns +arg+.
# String:: +arg+ must match the LOC::Coord::Regex constant
def self.create(arg)
case arg
when Coord
return arg
when String
coordinates = ''
if Regex =~ arg && $1.to_f < 180
m = $~
hemi = (m[4][/[NE]/]) || (m[4][/[SW]/]) ? 1 : -1
coordinates = [ ((m[1].to_i*(36e5)) + (m[2].to_i*(6e4)) +
(m[3].to_f*(1e3))) * hemi+(2**31) ].pack("N")
orientation = m[4][/[NS]/] ? 'lat' : 'lon'
else
raise ArgumentError.new("not a properly formed Coord string: " + arg)
end
return Coord.new(coordinates,orientation)
else
raise ArgumentError.new("cannot interpret as Coord: #{arg.inspect}")
end
end
def initialize(coordinates,orientation)
unless coordinates.kind_of?(String)
raise ArgumentError.new("Coord must be a 32bit unsigned integer in hex format: #{coordinates.inspect}")
end
unless orientation.kind_of?(String) && orientation[/^lon$|^lat$/]
raise ArgumentError.new('Coord expects orientation to be a String argument of "lat" or "lon"')
end
@coordinates = coordinates
@orientation = orientation
end
##
# The raw coordinates
attr_reader :coordinates
## The orientation of the hemisphere as 'lat' or 'lon'
attr_reader :orientation
def to_s # :nodoc:
c = @coordinates.unpack("N").join.to_i
val = (c - (2**31)).abs
fracsecs = (val % 1e3).to_i.to_s
val = val / 1e3
secs = (val % 60).to_i.to_s
val = val / 60
mins = (val % 60).to_i.to_s
degs = (val / 60).to_i.to_s
posi = (c >= 2**31)
case posi
when true
hemi = @orientation[/^lat$/] ? "N" : "E"
else
hemi = @orientation[/^lon$/] ? "W" : "S"
end
return degs << " " << mins << " " << secs << "." << fracsecs << " " << hemi
end
def inspect # :nodoc:
return "#<#{self.class} #{self}>"
end
def ==(other) # :nodoc:
return @coordinates == other.coordinates
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
return @coordinates.hash
end
end
##
# A Resolv::LOC::Alt
class Alt
Regex = /^([+-]*\d+\.*\d*)[m]$/
##
# Creates a new LOC::Alt from +arg+ which may be:
#
# LOC::Alt:: returns +arg+.
# String:: +arg+ must match the LOC::Alt::Regex constant
def self.create(arg)
case arg
when Alt
return arg
when String
altitude = ''
if Regex =~ arg
altitude = [($1.to_f*(1e2))+(1e7)].pack("N")
else
raise ArgumentError.new("not a properly formed Alt string: " + arg)
end
return Alt.new(altitude)
else
raise ArgumentError.new("cannot interpret as Alt: #{arg.inspect}")
end
end
def initialize(altitude)
@altitude = altitude
end
##
# The raw altitude
attr_reader :altitude
def to_s # :nodoc:
a = @altitude.unpack("N").join.to_i
return ((a.to_f/1e2)-1e5).to_s + "m"
end
def inspect # :nodoc:
return "#<#{self.class} #{self}>"
end
def ==(other) # :nodoc:
return @altitude == other.altitude
end
def eql?(other) # :nodoc:
return self == other
end
def hash # :nodoc:
return @altitude.hash
end
end
end
##
# Default resolver to use for Resolv class methods.
DefaultResolver = self.new
##
# Replaces the resolvers in the default resolver with +new_resolvers+. This
# allows resolvers to be changed for resolv-replace.
def DefaultResolver.replace_resolvers new_resolvers
@resolvers = new_resolvers
end
##
# Address Regexp to use for matching IP addresses.
AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/
end
share/ruby/date.rb 0000644 00000002251 15173547340 0010102 0 ustar 00 # frozen_string_literal: true
# date.rb: Written by Tadayoshi Funaba 1998-2011
require 'date_core'
class Date
VERSION = "3.3.3" # :nodoc:
# call-seq:
# infinite? -> false
#
# Returns +false+
def infinite?
false
end
class Infinity < Numeric # :nodoc:
def initialize(d=1) @d = d <=> 0 end
def d() @d end
protected :d
def zero?() false end
def finite?() false end
def infinite?() d.nonzero? end
def nan?() d.zero? end
def abs() self.class.new end
def -@() self.class.new(-d) end
def +@() self.class.new(+d) end
def <=>(other)
case other
when Infinity; return d <=> other.d
when Float::INFINITY; return d <=> 1
when -Float::INFINITY; return d <=> -1
when Numeric; return d
else
begin
l, r = other.coerce(self)
return l <=> r
rescue NoMethodError
end
end
nil
end
def coerce(other)
case other
when Numeric; return -d, d
else
super
end
end
def to_f
return 0 if @d == 0
if @d > 0
Float::INFINITY
else
-Float::INFINITY
end
end
end
end
share/ruby/logger/severity.rb 0000644 00000000712 15173547340 0012316 0 ustar 00 # frozen_string_literal: true
class Logger
# Logging severity.
module Severity
# Low-level information, mostly for developers.
DEBUG = 0
# Generic (useful) information about system operation.
INFO = 1
# A warning.
WARN = 2
# A handleable error condition.
ERROR = 3
# An unhandleable error that results in a program crash.
FATAL = 4
# An unknown message that should always be logged.
UNKNOWN = 5
end
end
share/ruby/logger/log_device.rb 0000644 00000013216 15173547340 0012547 0 ustar 00 # frozen_string_literal: true
require_relative 'period'
class Logger
# Device used for logging messages.
class LogDevice
include Period
attr_reader :dev
attr_reader :filename
include MonitorMixin
def initialize(log = nil, shift_age: nil, shift_size: nil, shift_period_suffix: nil, binmode: false)
@dev = @filename = @shift_age = @shift_size = @shift_period_suffix = nil
@binmode = binmode
mon_initialize
set_dev(log)
if @filename
@shift_age = shift_age || 7
@shift_size = shift_size || 1048576
@shift_period_suffix = shift_period_suffix || '%Y%m%d'
unless @shift_age.is_a?(Integer)
base_time = @dev.respond_to?(:stat) ? @dev.stat.mtime : Time.now
@next_rotate_time = next_rotate_time(base_time, @shift_age)
end
end
end
def write(message)
begin
synchronize do
if @shift_age and @dev.respond_to?(:stat)
begin
check_shift_log
rescue
warn("log shifting failed. #{$!}")
end
end
begin
@dev.write(message)
rescue
warn("log writing failed. #{$!}")
end
end
rescue Exception => ignored
warn("log writing failed. #{ignored}")
end
end
def close
begin
synchronize do
@dev.close rescue nil
end
rescue Exception
@dev.close rescue nil
end
end
def reopen(log = nil)
# reopen the same filename if no argument, do nothing for IO
log ||= @filename if @filename
if log
synchronize do
if @filename and @dev
@dev.close rescue nil # close only file opened by Logger
@filename = nil
end
set_dev(log)
end
end
self
end
private
def set_dev(log)
if log.respond_to?(:write) and log.respond_to?(:close)
@dev = log
if log.respond_to?(:path) and path = log.path
if File.exist?(path)
@filename = path
end
end
else
@dev = open_logfile(log)
@dev.sync = true
@dev.binmode if @binmode
@filename = log
end
end
def open_logfile(filename)
begin
File.open(filename, (File::WRONLY | File::APPEND))
rescue Errno::ENOENT
create_logfile(filename)
end
end
def create_logfile(filename)
begin
logdev = File.open(filename, (File::WRONLY | File::APPEND | File::CREAT | File::EXCL))
logdev.flock(File::LOCK_EX)
logdev.sync = true
logdev.binmode if @binmode
add_log_header(logdev)
logdev.flock(File::LOCK_UN)
rescue Errno::EEXIST
# file is created by another process
logdev = open_logfile(filename)
logdev.sync = true
end
logdev
end
def add_log_header(file)
file.write(
"# Logfile created on %s by %s\n" % [Time.now.to_s, Logger::ProgName]
) if file.size == 0
end
def check_shift_log
if @shift_age.is_a?(Integer)
# Note: always returns false if '0'.
if @filename && (@shift_age > 0) && (@dev.stat.size > @shift_size)
lock_shift_log { shift_log_age }
end
else
now = Time.now
if now >= @next_rotate_time
@next_rotate_time = next_rotate_time(now, @shift_age)
lock_shift_log { shift_log_period(previous_period_end(now, @shift_age)) }
end
end
end
if /mswin|mingw|cygwin/ =~ RbConfig::CONFIG['host_os']
def lock_shift_log
yield
end
else
def lock_shift_log
retry_limit = 8
retry_sleep = 0.1
begin
File.open(@filename, File::WRONLY | File::APPEND) do |lock|
lock.flock(File::LOCK_EX) # inter-process locking. will be unlocked at closing file
if File.identical?(@filename, lock) and File.identical?(lock, @dev)
yield # log shifting
else
# log shifted by another process (i-node before locking and i-node after locking are different)
@dev.close rescue nil
@dev = open_logfile(@filename)
@dev.sync = true
end
end
rescue Errno::ENOENT
# @filename file would not exist right after #rename and before #create_logfile
if retry_limit <= 0
warn("log rotation inter-process lock failed. #{$!}")
else
sleep retry_sleep
retry_limit -= 1
retry_sleep *= 2
retry
end
end
rescue
warn("log rotation inter-process lock failed. #{$!}")
end
end
def shift_log_age
(@shift_age-3).downto(0) do |i|
if FileTest.exist?("#{@filename}.#{i}")
File.rename("#{@filename}.#{i}", "#{@filename}.#{i+1}")
end
end
@dev.close rescue nil
File.rename("#{@filename}", "#{@filename}.0")
@dev = create_logfile(@filename)
return true
end
def shift_log_period(period_end)
suffix = period_end.strftime(@shift_period_suffix)
age_file = "#{@filename}.#{suffix}"
if FileTest.exist?(age_file)
# try to avoid filename crash caused by Timestamp change.
idx = 0
# .99 can be overridden; avoid too much file search with 'loop do'
while idx < 100
idx += 1
age_file = "#{@filename}.#{suffix}.#{idx}"
break unless FileTest.exist?(age_file)
end
end
@dev.close rescue nil
File.rename("#{@filename}", age_file)
@dev = create_logfile(@filename)
return true
end
end
end
share/ruby/logger/version.rb 0000644 00000000104 15173547340 0012124 0 ustar 00 # frozen_string_literal: true
class Logger
VERSION = "1.5.3"
end
share/ruby/logger/formatter.rb 0000644 00000001426 15173547340 0012452 0 ustar 00 # frozen_string_literal: true
class Logger
# Default formatter for log messages.
class Formatter
Format = "%.1s, [%s #%d] %5s -- %s: %s\n"
DatetimeFormat = "%Y-%m-%dT%H:%M:%S.%6N"
attr_accessor :datetime_format
def initialize
@datetime_format = nil
end
def call(severity, time, progname, msg)
sprintf(Format, severity, format_datetime(time), Process.pid, severity, progname, msg2str(msg))
end
private
def format_datetime(time)
time.strftime(@datetime_format || DatetimeFormat)
end
def msg2str(msg)
case msg
when ::String
msg
when ::Exception
"#{ msg.message } (#{ msg.class })\n#{ msg.backtrace.join("\n") if msg.backtrace }"
else
msg.inspect
end
end
end
end
share/ruby/logger/period.rb 0000644 00000002644 15173547340 0011734 0 ustar 00 # frozen_string_literal: true
class Logger
module Period
module_function
SiD = 24 * 60 * 60
def next_rotate_time(now, shift_age)
case shift_age
when 'daily'
t = Time.mktime(now.year, now.month, now.mday) + SiD
when 'weekly'
t = Time.mktime(now.year, now.month, now.mday) + SiD * (7 - now.wday)
when 'monthly'
t = Time.mktime(now.year, now.month, 1) + SiD * 32
return Time.mktime(t.year, t.month, 1)
when 'now', 'everytime'
return now
else
raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime"
end
if t.hour.nonzero? or t.min.nonzero? or t.sec.nonzero?
hour = t.hour
t = Time.mktime(t.year, t.month, t.mday)
t += SiD if hour > 12
end
t
end
def previous_period_end(now, shift_age)
case shift_age
when 'daily'
t = Time.mktime(now.year, now.month, now.mday) - SiD / 2
when 'weekly'
t = Time.mktime(now.year, now.month, now.mday) - (SiD * now.wday + SiD / 2)
when 'monthly'
t = Time.mktime(now.year, now.month, 1) - SiD / 2
when 'now', 'everytime'
return now
else
raise ArgumentError, "invalid :shift_age #{shift_age.inspect}, should be daily, weekly, monthly, or everytime"
end
Time.mktime(t.year, t.month, t.mday, 23, 59, 59)
end
end
end
share/ruby/logger/errors.rb 0000644 00000000266 15173547340 0011764 0 ustar 00 # frozen_string_literal: true
class Logger
# not used after 1.2.7. just for compat.
class Error < RuntimeError # :nodoc:
end
class ShiftingError < Error # :nodoc:
end
end
share/ruby/socket.rb 0000644 00000127346 15173547341 0010473 0 ustar 00 # frozen_string_literal: true
require 'socket.so'
unless IO.method_defined?(:wait_writable, false)
# It's only required on older Rubies < v3.2:
require 'io/wait'
end
class Addrinfo
# creates an Addrinfo object from the arguments.
#
# The arguments are interpreted as similar to self.
#
# Addrinfo.tcp("0.0.0.0", 4649).family_addrinfo("www.ruby-lang.org", 80)
# #=> #<Addrinfo: 221.186.184.68:80 TCP (www.ruby-lang.org:80)>
#
# Addrinfo.unix("/tmp/sock").family_addrinfo("/tmp/sock2")
# #=> #<Addrinfo: /tmp/sock2 SOCK_STREAM>
#
def family_addrinfo(*args)
if args.empty?
raise ArgumentError, "no address specified"
elsif Addrinfo === args.first
raise ArgumentError, "too many arguments" if args.length != 1
addrinfo = args.first
if (self.pfamily != addrinfo.pfamily) ||
(self.socktype != addrinfo.socktype)
raise ArgumentError, "Addrinfo type mismatch"
end
addrinfo
elsif self.ip?
raise ArgumentError, "IP address needs host and port but #{args.length} arguments given" if args.length != 2
host, port = args
Addrinfo.getaddrinfo(host, port, self.pfamily, self.socktype, self.protocol)[0]
elsif self.unix?
raise ArgumentError, "UNIX socket needs single path argument but #{args.length} arguments given" if args.length != 1
path, = args
Addrinfo.unix(path)
else
raise ArgumentError, "unexpected family"
end
end
# creates a new Socket connected to the address of +local_addrinfo+.
#
# If _local_addrinfo_ is nil, the address of the socket is not bound.
#
# The _timeout_ specify the seconds for timeout.
# Errno::ETIMEDOUT is raised when timeout occur.
#
# If a block is given the created socket is yielded for each address.
#
def connect_internal(local_addrinfo, timeout=nil) # :yields: socket
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
begin
sock.ipv6only! if self.ipv6?
sock.bind local_addrinfo if local_addrinfo
if timeout
case sock.connect_nonblock(self, exception: false)
when 0 # success or EISCONN, other errors raise
break
when :wait_writable
sock.wait_writable(timeout) or
raise Errno::ETIMEDOUT, 'user specified timeout'
end while true
else
sock.connect(self)
end
rescue Exception
sock.close
raise
end
if block_given?
begin
yield sock
ensure
sock.close
end
else
sock
end
end
protected :connect_internal
# :call-seq:
# addrinfo.connect_from([local_addr_args], [opts]) {|socket| ... }
# addrinfo.connect_from([local_addr_args], [opts])
#
# creates a socket connected to the address of self.
#
# If one or more arguments given as _local_addr_args_,
# it is used as the local address of the socket.
# _local_addr_args_ is given for family_addrinfo to obtain actual address.
#
# If _local_addr_args_ is not given, the local address of the socket is not bound.
#
# The optional last argument _opts_ is options represented by a hash.
# _opts_ may have following options:
#
# [:timeout] specify the timeout in seconds.
#
# If a block is given, it is called with the socket and the value of the block is returned.
# The socket is returned otherwise.
#
# Addrinfo.tcp("www.ruby-lang.org", 80).connect_from("0.0.0.0", 4649) {|s|
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
# puts s.read
# }
#
# # Addrinfo object can be taken for the argument.
# Addrinfo.tcp("www.ruby-lang.org", 80).connect_from(Addrinfo.tcp("0.0.0.0", 4649)) {|s|
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
# puts s.read
# }
#
def connect_from(*args, timeout: nil, &block)
connect_internal(family_addrinfo(*args), timeout, &block)
end
# :call-seq:
# addrinfo.connect([opts]) {|socket| ... }
# addrinfo.connect([opts])
#
# creates a socket connected to the address of self.
#
# The optional argument _opts_ is options represented by a hash.
# _opts_ may have following options:
#
# [:timeout] specify the timeout in seconds.
#
# If a block is given, it is called with the socket and the value of the block is returned.
# The socket is returned otherwise.
#
# Addrinfo.tcp("www.ruby-lang.org", 80).connect {|s|
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
# puts s.read
# }
#
def connect(timeout: nil, &block)
connect_internal(nil, timeout, &block)
end
# :call-seq:
# addrinfo.connect_to([remote_addr_args], [opts]) {|socket| ... }
# addrinfo.connect_to([remote_addr_args], [opts])
#
# creates a socket connected to _remote_addr_args_ and bound to self.
#
# The optional last argument _opts_ is options represented by a hash.
# _opts_ may have following options:
#
# [:timeout] specify the timeout in seconds.
#
# If a block is given, it is called with the socket and the value of the block is returned.
# The socket is returned otherwise.
#
# Addrinfo.tcp("0.0.0.0", 4649).connect_to("www.ruby-lang.org", 80) {|s|
# s.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
# puts s.read
# }
#
def connect_to(*args, timeout: nil, &block)
remote_addrinfo = family_addrinfo(*args)
remote_addrinfo.connect_internal(self, timeout, &block)
end
# creates a socket bound to self.
#
# If a block is given, it is called with the socket and the value of the block is returned.
# The socket is returned otherwise.
#
# Addrinfo.udp("0.0.0.0", 9981).bind {|s|
# s.local_address.connect {|s| s.send "hello", 0 }
# p s.recv(10) #=> "hello"
# }
#
def bind
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
begin
sock.ipv6only! if self.ipv6?
sock.setsockopt(:SOCKET, :REUSEADDR, 1)
sock.bind(self)
rescue Exception
sock.close
raise
end
if block_given?
begin
yield sock
ensure
sock.close
end
else
sock
end
end
# creates a listening socket bound to self.
def listen(backlog=Socket::SOMAXCONN)
sock = Socket.new(self.pfamily, self.socktype, self.protocol)
begin
sock.ipv6only! if self.ipv6?
sock.setsockopt(:SOCKET, :REUSEADDR, 1) unless self.pfamily == Socket::PF_UNIX
sock.bind(self)
sock.listen(backlog)
rescue Exception
sock.close
raise
end
if block_given?
begin
yield sock
ensure
sock.close
end
else
sock
end
end
# iterates over the list of Addrinfo objects obtained by Addrinfo.getaddrinfo.
#
# Addrinfo.foreach(nil, 80) {|x| p x }
# #=> #<Addrinfo: 127.0.0.1:80 TCP (:80)>
# # #<Addrinfo: 127.0.0.1:80 UDP (:80)>
# # #<Addrinfo: [::1]:80 TCP (:80)>
# # #<Addrinfo: [::1]:80 UDP (:80)>
#
def self.foreach(nodename, service, family=nil, socktype=nil, protocol=nil, flags=nil, timeout: nil, &block)
Addrinfo.getaddrinfo(nodename, service, family, socktype, protocol, flags, timeout: timeout).each(&block)
end
end
class BasicSocket < IO
# Returns an address of the socket suitable for connect in the local machine.
#
# This method returns _self_.local_address, except following condition.
#
# - IPv4 unspecified address (0.0.0.0) is replaced by IPv4 loopback address (127.0.0.1).
# - IPv6 unspecified address (::) is replaced by IPv6 loopback address (::1).
#
# If the local address is not suitable for connect, SocketError is raised.
# IPv4 and IPv6 address which port is 0 is not suitable for connect.
# Unix domain socket which has no path is not suitable for connect.
#
# Addrinfo.tcp("0.0.0.0", 0).listen {|serv|
# p serv.connect_address #=> #<Addrinfo: 127.0.0.1:53660 TCP>
# serv.connect_address.connect {|c|
# s, _ = serv.accept
# p [c, s] #=> [#<Socket:fd 4>, #<Socket:fd 6>]
# }
# }
#
def connect_address
addr = local_address
afamily = addr.afamily
if afamily == Socket::AF_INET
raise SocketError, "unbound IPv4 socket" if addr.ip_port == 0
if addr.ip_address == "0.0.0.0"
addr = Addrinfo.new(["AF_INET", addr.ip_port, nil, "127.0.0.1"], addr.pfamily, addr.socktype, addr.protocol)
end
elsif defined?(Socket::AF_INET6) && afamily == Socket::AF_INET6
raise SocketError, "unbound IPv6 socket" if addr.ip_port == 0
if addr.ip_address == "::"
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
elsif addr.ip_address == "0.0.0.0" # MacOS X 10.4 returns "a.b.c.d" for IPv4-mapped IPv6 address.
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
elsif addr.ip_address == "::ffff:0.0.0.0" # MacOS X 10.6 returns "::ffff:a.b.c.d" for IPv4-mapped IPv6 address.
addr = Addrinfo.new(["AF_INET6", addr.ip_port, nil, "::1"], addr.pfamily, addr.socktype, addr.protocol)
end
elsif defined?(Socket::AF_UNIX) && afamily == Socket::AF_UNIX
raise SocketError, "unbound Unix socket" if addr.unix_path == ""
end
addr
end
# call-seq:
# basicsocket.sendmsg(mesg, flags=0, dest_sockaddr=nil, *controls) => numbytes_sent
#
# sendmsg sends a message using sendmsg(2) system call in blocking manner.
#
# _mesg_ is a string to send.
#
# _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_OOB.
#
# _dest_sockaddr_ is a destination socket address for connection-less socket.
# It should be a sockaddr such as a result of Socket.sockaddr_in.
# An Addrinfo object can be used too.
#
# _controls_ is a list of ancillary data.
# The element of _controls_ should be Socket::AncillaryData or
# 3-elements array.
# The 3-element array should contains cmsg_level, cmsg_type and data.
#
# The return value, _numbytes_sent_ is an integer which is the number of bytes sent.
#
# sendmsg can be used to implement send_io as follows:
#
# # use Socket::AncillaryData.
# ancdata = Socket::AncillaryData.int(:UNIX, :SOCKET, :RIGHTS, io.fileno)
# sock.sendmsg("a", 0, nil, ancdata)
#
# # use 3-element array.
# ancdata = [:SOCKET, :RIGHTS, [io.fileno].pack("i!")]
# sock.sendmsg("\0", 0, nil, ancdata)
def sendmsg(mesg, flags = 0, dest_sockaddr = nil, *controls)
__sendmsg(mesg, flags, dest_sockaddr, controls)
end
# call-seq:
# basicsocket.sendmsg_nonblock(mesg, flags=0, dest_sockaddr=nil, *controls, opts={}) => numbytes_sent
#
# sendmsg_nonblock sends a message using sendmsg(2) system call in non-blocking manner.
#
# It is similar to BasicSocket#sendmsg
# but the non-blocking flag is set before the system call
# and it doesn't retry the system call.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that sendmsg_nonblock should not raise an IO::WaitWritable exception, but
# return the symbol +:wait_writable+ instead.
def sendmsg_nonblock(mesg, flags = 0, dest_sockaddr = nil, *controls,
exception: true)
__sendmsg_nonblock(mesg, flags, dest_sockaddr, controls, exception)
end
# call-seq:
# basicsocket.recv_nonblock(maxlen [, flags [, buf [, options ]]]) => mesg
#
# Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# _flags_ is zero or more of the +MSG_+ options.
# The result, _mesg_, is the data received.
#
# When recvfrom(2) returns 0, Socket#recv_nonblock returns
# an empty string as data.
# The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
#
# === Parameters
# * +maxlen+ - the number of bytes to receive from the socket
# * +flags+ - zero or more of the +MSG_+ options
# * +buf+ - destination String buffer
# * +options+ - keyword hash, supporting `exception: false`
#
# === Example
# serv = TCPServer.new("127.0.0.1", 0)
# af, port, host, addr = serv.addr
# c = TCPSocket.new(addr, port)
# s = serv.accept
# c.send "aaa", 0
# begin # emulate blocking recv.
# p s.recv_nonblock(10) #=> "aaa"
# rescue IO::WaitReadable
# IO.select([s])
# retry
# end
#
# Refer to Socket#recvfrom for the exceptions that may be thrown if the call
# to _recv_nonblock_ fails.
#
# BasicSocket#recv_nonblock may raise any error corresponding to recvfrom(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying recv_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that recv_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * Socket#recvfrom
def recv_nonblock(len, flag = 0, str = nil, exception: true)
__recv_nonblock(len, flag, str, exception)
end
# call-seq:
# basicsocket.recvmsg(maxmesglen=nil, flags=0, maxcontrollen=nil, opts={}) => [mesg, sender_addrinfo, rflags, *controls]
#
# recvmsg receives a message using recvmsg(2) system call in blocking manner.
#
# _maxmesglen_ is the maximum length of mesg to receive.
#
# _flags_ is bitwise OR of MSG_* constants such as Socket::MSG_PEEK.
#
# _maxcontrollen_ is the maximum length of controls (ancillary data) to receive.
#
# _opts_ is option hash.
# Currently :scm_rights=>bool is the only option.
#
# :scm_rights option specifies that application expects SCM_RIGHTS control message.
# If the value is nil or false, application don't expects SCM_RIGHTS control message.
# In this case, recvmsg closes the passed file descriptors immediately.
# This is the default behavior.
#
# If :scm_rights value is neither nil nor false, application expects SCM_RIGHTS control message.
# In this case, recvmsg creates IO objects for each file descriptors for
# Socket::AncillaryData#unix_rights method.
#
# The return value is 4-elements array.
#
# _mesg_ is a string of the received message.
#
# _sender_addrinfo_ is a sender socket address for connection-less socket.
# It is an Addrinfo object.
# For connection-oriented socket such as TCP, sender_addrinfo is platform dependent.
#
# _rflags_ is a flags on the received message which is bitwise OR of MSG_* constants such as Socket::MSG_TRUNC.
# It will be nil if the system uses 4.3BSD style old recvmsg system call.
#
# _controls_ is ancillary data which is an array of Socket::AncillaryData objects such as:
#
# #<Socket::AncillaryData: AF_UNIX SOCKET RIGHTS 7>
#
# _maxmesglen_ and _maxcontrollen_ can be nil.
# In that case, the buffer will be grown until the message is not truncated.
# Internally, MSG_PEEK is used.
# Buffer full and MSG_CTRUNC are checked for truncation.
#
# recvmsg can be used to implement recv_io as follows:
#
# mesg, sender_sockaddr, rflags, *controls = sock.recvmsg(:scm_rights=>true)
# controls.each {|ancdata|
# if ancdata.cmsg_is?(:SOCKET, :RIGHTS)
# return ancdata.unix_rights[0]
# end
# }
def recvmsg(dlen = nil, flags = 0, clen = nil, scm_rights: false)
__recvmsg(dlen, flags, clen, scm_rights)
end
# call-seq:
# basicsocket.recvmsg_nonblock(maxdatalen=nil, flags=0, maxcontrollen=nil, opts={}) => [data, sender_addrinfo, rflags, *controls]
#
# recvmsg receives a message using recvmsg(2) system call in non-blocking manner.
#
# It is similar to BasicSocket#recvmsg
# but non-blocking flag is set before the system call
# and it doesn't retry the system call.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that recvmsg_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
def recvmsg_nonblock(dlen = nil, flags = 0, clen = nil,
scm_rights: false, exception: true)
__recvmsg_nonblock(dlen, flags, clen, scm_rights, exception)
end
# Linux-specific optimizations to avoid fcntl for IO#read_nonblock
# and IO#write_nonblock using MSG_DONTWAIT
# Do other platforms support MSG_DONTWAIT reliably?
if RUBY_PLATFORM =~ /linux/ && Socket.const_defined?(:MSG_DONTWAIT)
def read_nonblock(len, str = nil, exception: true) # :nodoc:
__read_nonblock(len, str, exception)
end
def write_nonblock(buf, exception: true) # :nodoc:
__write_nonblock(buf, exception)
end
end
end
class Socket < BasicSocket
# enable the socket option IPV6_V6ONLY if IPV6_V6ONLY is available.
def ipv6only!
if defined? Socket::IPV6_V6ONLY
self.setsockopt(:IPV6, :V6ONLY, 1)
end
end
# call-seq:
# socket.recvfrom_nonblock(maxlen[, flags[, outbuf[, opts]]]) => [mesg, sender_addrinfo]
#
# Receives up to _maxlen_ bytes from +socket+ using recvfrom(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# _flags_ is zero or more of the +MSG_+ options.
# The first element of the results, _mesg_, is the data received.
# The second element, _sender_addrinfo_, contains protocol-specific address
# information of the sender.
#
# When recvfrom(2) returns 0, Socket#recvfrom_nonblock returns
# an empty string as data.
# The meaning depends on the socket: EOF on TCP, empty packet on UDP, etc.
#
# === Parameters
# * +maxlen+ - the maximum number of bytes to receive from the socket
# * +flags+ - zero or more of the +MSG_+ options
# * +outbuf+ - destination String buffer
# * +opts+ - keyword hash, supporting `exception: false`
#
# === Example
# # In one file, start this first
# require 'socket'
# include Socket::Constants
# socket = Socket.new(AF_INET, SOCK_STREAM, 0)
# sockaddr = Socket.sockaddr_in(2200, 'localhost')
# socket.bind(sockaddr)
# socket.listen(5)
# client, client_addrinfo = socket.accept
# begin # emulate blocking recvfrom
# pair = client.recvfrom_nonblock(20)
# rescue IO::WaitReadable
# IO.select([client])
# retry
# end
# data = pair[0].chomp
# puts "I only received 20 bytes '#{data}'"
# sleep 1
# socket.close
#
# # In another file, start this second
# require 'socket'
# include Socket::Constants
# socket = Socket.new(AF_INET, SOCK_STREAM, 0)
# sockaddr = Socket.sockaddr_in(2200, 'localhost')
# socket.connect(sockaddr)
# socket.puts "Watch this get cut short!"
# socket.close
#
# Refer to Socket#recvfrom for the exceptions that may be thrown if the call
# to _recvfrom_nonblock_ fails.
#
# Socket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying
# recvfrom_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that recvfrom_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * Socket#recvfrom
def recvfrom_nonblock(len, flag = 0, str = nil, exception: true)
__recvfrom_nonblock(len, flag, str, exception)
end
# call-seq:
# socket.accept_nonblock([options]) => [client_socket, client_addrinfo]
#
# Accepts an incoming connection using accept(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# It returns an array containing the accepted socket
# for the incoming connection, _client_socket_,
# and an Addrinfo, _client_addrinfo_.
#
# === Example
# # In one script, start this first
# require 'socket'
# include Socket::Constants
# socket = Socket.new(AF_INET, SOCK_STREAM, 0)
# sockaddr = Socket.sockaddr_in(2200, 'localhost')
# socket.bind(sockaddr)
# socket.listen(5)
# begin # emulate blocking accept
# client_socket, client_addrinfo = socket.accept_nonblock
# rescue IO::WaitReadable, Errno::EINTR
# IO.select([socket])
# retry
# end
# puts "The client said, '#{client_socket.readline.chomp}'"
# client_socket.puts "Hello from script one!"
# socket.close
#
# # In another script, start this second
# require 'socket'
# include Socket::Constants
# socket = Socket.new(AF_INET, SOCK_STREAM, 0)
# sockaddr = Socket.sockaddr_in(2200, 'localhost')
# socket.connect(sockaddr)
# socket.puts "Hello from script 2."
# puts "The server said, '#{socket.readline.chomp}'"
# socket.close
#
# Refer to Socket#accept for the exceptions that may be thrown if the call
# to _accept_nonblock_ fails.
#
# Socket#accept_nonblock may raise any error corresponding to accept(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that accept_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * Socket#accept
def accept_nonblock(exception: true)
__accept_nonblock(exception)
end
# :call-seq:
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts]) {|socket| ... }
# Socket.tcp(host, port, local_host=nil, local_port=nil, [opts])
#
# creates a new socket object connected to host:port using TCP/IP.
#
# If local_host:local_port is given,
# the socket is bound to it.
#
# The optional last argument _opts_ is options represented by a hash.
# _opts_ may have following options:
#
# [:connect_timeout] specify the timeout in seconds.
#
# If a block is given, the block is called with the socket.
# The value of the block is returned.
# The socket is closed when this method returns.
#
# If no block is given, the socket is returned.
#
# Socket.tcp("www.ruby-lang.org", 80) {|sock|
# sock.print "GET / HTTP/1.0\r\nHost: www.ruby-lang.org\r\n\r\n"
# sock.close_write
# puts sock.read
# }
#
def self.tcp(host, port, local_host = nil, local_port = nil, connect_timeout: nil, resolv_timeout: nil) # :yield: socket
last_error = nil
ret = nil
local_addr_list = nil
if local_host != nil || local_port != nil
local_addr_list = Addrinfo.getaddrinfo(local_host, local_port, nil, :STREAM, nil)
end
Addrinfo.foreach(host, port, nil, :STREAM, timeout: resolv_timeout) {|ai|
if local_addr_list
local_addr = local_addr_list.find {|local_ai| local_ai.afamily == ai.afamily }
next unless local_addr
else
local_addr = nil
end
begin
sock = local_addr ?
ai.connect_from(local_addr, timeout: connect_timeout) :
ai.connect(timeout: connect_timeout)
rescue SystemCallError
last_error = $!
next
end
ret = sock
break
}
unless ret
if last_error
raise last_error
else
raise SocketError, "no appropriate local address"
end
end
if block_given?
begin
yield ret
ensure
ret.close
end
else
ret
end
end
# :stopdoc:
def self.ip_sockets_port0(ai_list, reuseaddr)
sockets = []
begin
sockets.clear
port = nil
ai_list.each {|ai|
begin
s = Socket.new(ai.pfamily, ai.socktype, ai.protocol)
rescue SystemCallError
next
end
sockets << s
s.ipv6only! if ai.ipv6?
if reuseaddr
s.setsockopt(:SOCKET, :REUSEADDR, 1)
end
unless port
s.bind(ai)
port = s.local_address.ip_port
else
s.bind(ai.family_addrinfo(ai.ip_address, port))
end
}
rescue Errno::EADDRINUSE
sockets.each(&:close)
retry
rescue Exception
sockets.each(&:close)
raise
end
sockets
end
class << self
private :ip_sockets_port0
end
def self.tcp_server_sockets_port0(host)
ai_list = Addrinfo.getaddrinfo(host, 0, nil, :STREAM, nil, Socket::AI_PASSIVE)
sockets = ip_sockets_port0(ai_list, true)
begin
sockets.each {|s|
s.listen(Socket::SOMAXCONN)
}
rescue Exception
sockets.each(&:close)
raise
end
sockets
end
class << self
private :tcp_server_sockets_port0
end
# :startdoc:
# creates TCP/IP server sockets for _host_ and _port_.
# _host_ is optional.
#
# If no block given,
# it returns an array of listening sockets.
#
# If a block is given, the block is called with the sockets.
# The value of the block is returned.
# The socket is closed when this method returns.
#
# If _port_ is 0, actual port number is chosen dynamically.
# However all sockets in the result has same port number.
#
# # tcp_server_sockets returns two sockets.
# sockets = Socket.tcp_server_sockets(1296)
# p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
#
# # The sockets contains IPv6 and IPv4 sockets.
# sockets.each {|s| p s.local_address }
# #=> #<Addrinfo: [::]:1296 TCP>
# # #<Addrinfo: 0.0.0.0:1296 TCP>
#
# # IPv6 and IPv4 socket has same port number, 53114, even if it is chosen dynamically.
# sockets = Socket.tcp_server_sockets(0)
# sockets.each {|s| p s.local_address }
# #=> #<Addrinfo: [::]:53114 TCP>
# # #<Addrinfo: 0.0.0.0:53114 TCP>
#
# # The block is called with the sockets.
# Socket.tcp_server_sockets(0) {|sockets|
# p sockets #=> [#<Socket:fd 3>, #<Socket:fd 4>]
# }
#
def self.tcp_server_sockets(host=nil, port)
if port == 0
sockets = tcp_server_sockets_port0(host)
else
last_error = nil
sockets = []
begin
Addrinfo.foreach(host, port, nil, :STREAM, nil, Socket::AI_PASSIVE) {|ai|
begin
s = ai.listen
rescue SystemCallError
last_error = $!
next
end
sockets << s
}
if sockets.empty?
raise last_error
end
rescue Exception
sockets.each(&:close)
raise
end
end
if block_given?
begin
yield sockets
ensure
sockets.each(&:close)
end
else
sockets
end
end
# yield socket and client address for each a connection accepted via given sockets.
#
# The arguments are a list of sockets.
# The individual argument should be a socket or an array of sockets.
#
# This method yields the block sequentially.
# It means that the next connection is not accepted until the block returns.
# So concurrent mechanism, thread for example, should be used to service multiple clients at a time.
#
def self.accept_loop(*sockets) # :yield: socket, client_addrinfo
sockets.flatten!(1)
if sockets.empty?
raise ArgumentError, "no sockets"
end
loop {
readable, _, _ = IO.select(sockets)
readable.each {|r|
sock, addr = r.accept_nonblock(exception: false)
next if sock == :wait_readable
yield sock, addr
}
}
end
# creates a TCP/IP server on _port_ and calls the block for each connection accepted.
# The block is called with a socket and a client_address as an Addrinfo object.
#
# If _host_ is specified, it is used with _port_ to determine the server addresses.
#
# The socket is *not* closed when the block returns.
# So application should close it explicitly.
#
# This method calls the block sequentially.
# It means that the next connection is not accepted until the block returns.
# So concurrent mechanism, thread for example, should be used to service multiple clients at a time.
#
# Note that Addrinfo.getaddrinfo is used to determine the server socket addresses.
# When Addrinfo.getaddrinfo returns two or more addresses,
# IPv4 and IPv6 address for example,
# all of them are used.
# Socket.tcp_server_loop succeeds if one socket can be used at least.
#
# # Sequential echo server.
# # It services only one client at a time.
# Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
# begin
# IO.copy_stream(sock, sock)
# ensure
# sock.close
# end
# }
#
# # Threaded echo server
# # It services multiple clients at a time.
# # Note that it may accept connections too much.
# Socket.tcp_server_loop(16807) {|sock, client_addrinfo|
# Thread.new {
# begin
# IO.copy_stream(sock, sock)
# ensure
# sock.close
# end
# }
# }
#
def self.tcp_server_loop(host=nil, port, &b) # :yield: socket, client_addrinfo
tcp_server_sockets(host, port) {|sockets|
accept_loop(sockets, &b)
}
end
# :call-seq:
# Socket.udp_server_sockets([host, ] port)
#
# Creates UDP/IP sockets for a UDP server.
#
# If no block given, it returns an array of sockets.
#
# If a block is given, the block is called with the sockets.
# The value of the block is returned.
# The sockets are closed when this method returns.
#
# If _port_ is zero, some port is chosen.
# But the chosen port is used for the all sockets.
#
# # UDP/IP echo server
# Socket.udp_server_sockets(0) {|sockets|
# p sockets.first.local_address.ip_port #=> 32963
# Socket.udp_server_loop_on(sockets) {|msg, msg_src|
# msg_src.reply msg
# }
# }
#
def self.udp_server_sockets(host=nil, port)
last_error = nil
sockets = []
ipv6_recvpktinfo = nil
if defined? Socket::AncillaryData
if defined? Socket::IPV6_RECVPKTINFO # RFC 3542
ipv6_recvpktinfo = Socket::IPV6_RECVPKTINFO
elsif defined? Socket::IPV6_PKTINFO # RFC 2292
ipv6_recvpktinfo = Socket::IPV6_PKTINFO
end
end
local_addrs = Socket.ip_address_list
ip_list = []
Addrinfo.foreach(host, port, nil, :DGRAM, nil, Socket::AI_PASSIVE) {|ai|
if ai.ipv4? && ai.ip_address == "0.0.0.0"
local_addrs.each {|a|
next unless a.ipv4?
ip_list << Addrinfo.new(a.to_sockaddr, :INET, :DGRAM, 0);
}
elsif ai.ipv6? && ai.ip_address == "::" && !ipv6_recvpktinfo
local_addrs.each {|a|
next unless a.ipv6?
ip_list << Addrinfo.new(a.to_sockaddr, :INET6, :DGRAM, 0);
}
else
ip_list << ai
end
}
ip_list.uniq!(&:to_sockaddr)
if port == 0
sockets = ip_sockets_port0(ip_list, false)
else
ip_list.each {|ip|
ai = Addrinfo.udp(ip.ip_address, port)
begin
s = ai.bind
rescue SystemCallError
last_error = $!
next
end
sockets << s
}
if sockets.empty?
raise last_error
end
end
sockets.each {|s|
ai = s.local_address
if ipv6_recvpktinfo && ai.ipv6? && ai.ip_address == "::"
s.setsockopt(:IPV6, ipv6_recvpktinfo, 1)
end
}
if block_given?
begin
yield sockets
ensure
sockets.each(&:close) if sockets
end
else
sockets
end
end
# :call-seq:
# Socket.udp_server_recv(sockets) {|msg, msg_src| ... }
#
# Receive UDP/IP packets from the given _sockets_.
# For each packet received, the block is called.
#
# The block receives _msg_ and _msg_src_.
# _msg_ is a string which is the payload of the received packet.
# _msg_src_ is a Socket::UDPSource object which is used for reply.
#
# Socket.udp_server_loop can be implemented using this method as follows.
#
# udp_server_sockets(host, port) {|sockets|
# loop {
# readable, _, _ = IO.select(sockets)
# udp_server_recv(readable) {|msg, msg_src| ... }
# }
# }
#
def self.udp_server_recv(sockets)
sockets.each {|r|
msg, sender_addrinfo, _, *controls = r.recvmsg_nonblock(exception: false)
next if msg == :wait_readable
ai = r.local_address
if ai.ipv6? and pktinfo = controls.find {|c| c.cmsg_is?(:IPV6, :PKTINFO) }
ai = Addrinfo.udp(pktinfo.ipv6_pktinfo_addr.ip_address, ai.ip_port)
yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
r.sendmsg reply_msg, 0, sender_addrinfo, pktinfo
}
else
yield msg, UDPSource.new(sender_addrinfo, ai) {|reply_msg|
r.send reply_msg, 0, sender_addrinfo
}
end
}
end
# :call-seq:
# Socket.udp_server_loop_on(sockets) {|msg, msg_src| ... }
#
# Run UDP/IP server loop on the given sockets.
#
# The return value of Socket.udp_server_sockets is appropriate for the argument.
#
# It calls the block for each message received.
#
def self.udp_server_loop_on(sockets, &b) # :yield: msg, msg_src
loop {
readable, _, _ = IO.select(sockets)
udp_server_recv(readable, &b)
}
end
# :call-seq:
# Socket.udp_server_loop(port) {|msg, msg_src| ... }
# Socket.udp_server_loop(host, port) {|msg, msg_src| ... }
#
# creates a UDP/IP server on _port_ and calls the block for each message arrived.
# The block is called with the message and its source information.
#
# This method allocates sockets internally using _port_.
# If _host_ is specified, it is used conjunction with _port_ to determine the server addresses.
#
# The _msg_ is a string.
#
# The _msg_src_ is a Socket::UDPSource object.
# It is used for reply.
#
# # UDP/IP echo server.
# Socket.udp_server_loop(9261) {|msg, msg_src|
# msg_src.reply msg
# }
#
def self.udp_server_loop(host=nil, port, &b) # :yield: message, message_source
udp_server_sockets(host, port) {|sockets|
udp_server_loop_on(sockets, &b)
}
end
# UDP/IP address information used by Socket.udp_server_loop.
class UDPSource
# +remote_address+ is an Addrinfo object.
#
# +local_address+ is an Addrinfo object.
#
# +reply_proc+ is a Proc used to send reply back to the source.
def initialize(remote_address, local_address, &reply_proc)
@remote_address = remote_address
@local_address = local_address
@reply_proc = reply_proc
end
# Address of the source
attr_reader :remote_address
# Local address
attr_reader :local_address
def inspect # :nodoc:
"\#<#{self.class}: #{@remote_address.inspect_sockaddr} to #{@local_address.inspect_sockaddr}>".dup
end
# Sends the String +msg+ to the source
def reply(msg)
@reply_proc.call msg
end
end
# creates a new socket connected to path using UNIX socket socket.
#
# If a block is given, the block is called with the socket.
# The value of the block is returned.
# The socket is closed when this method returns.
#
# If no block is given, the socket is returned.
#
# # talk to /tmp/sock socket.
# Socket.unix("/tmp/sock") {|sock|
# t = Thread.new { IO.copy_stream(sock, STDOUT) }
# IO.copy_stream(STDIN, sock)
# t.join
# }
#
def self.unix(path) # :yield: socket
addr = Addrinfo.unix(path)
sock = addr.connect
if block_given?
begin
yield sock
ensure
sock.close
end
else
sock
end
end
# creates a UNIX server socket on _path_
#
# If no block given, it returns a listening socket.
#
# If a block is given, it is called with the socket and the block value is returned.
# When the block exits, the socket is closed and the socket file is removed.
#
# socket = Socket.unix_server_socket("/tmp/s")
# p socket #=> #<Socket:fd 3>
# p socket.local_address #=> #<Addrinfo: /tmp/s SOCK_STREAM>
#
# Socket.unix_server_socket("/tmp/sock") {|s|
# p s #=> #<Socket:fd 3>
# p s.local_address #=> # #<Addrinfo: /tmp/sock SOCK_STREAM>
# }
#
def self.unix_server_socket(path)
unless unix_socket_abstract_name?(path)
begin
st = File.lstat(path)
rescue Errno::ENOENT
end
if st&.socket? && st.owned?
File.unlink path
end
end
s = Addrinfo.unix(path).listen
if block_given?
begin
yield s
ensure
s.close
unless unix_socket_abstract_name?(path)
File.unlink path
end
end
else
s
end
end
class << self
private
def unix_socket_abstract_name?(path)
/linux/ =~ RUBY_PLATFORM && /\A(\0|\z)/ =~ path
end
end
# creates a UNIX socket server on _path_.
# It calls the block for each socket accepted.
#
# If _host_ is specified, it is used with _port_ to determine the server ports.
#
# The socket is *not* closed when the block returns.
# So application should close it.
#
# This method deletes the socket file pointed by _path_ at first if
# the file is a socket file and it is owned by the user of the application.
# This is safe only if the directory of _path_ is not changed by a malicious user.
# So don't use /tmp/malicious-users-directory/socket.
# Note that /tmp/socket and /tmp/your-private-directory/socket is safe assuming that /tmp has sticky bit.
#
# # Sequential echo server.
# # It services only one client at a time.
# Socket.unix_server_loop("/tmp/sock") {|sock, client_addrinfo|
# begin
# IO.copy_stream(sock, sock)
# ensure
# sock.close
# end
# }
#
def self.unix_server_loop(path, &b) # :yield: socket, client_addrinfo
unix_server_socket(path) {|serv|
accept_loop(serv, &b)
}
end
# call-seq:
# socket.connect_nonblock(remote_sockaddr, [options]) => 0
#
# Requests a connection to be made on the given +remote_sockaddr+ after
# O_NONBLOCK is set for the underlying file descriptor.
# Returns 0 if successful, otherwise an exception is raised.
#
# === Parameter
# # +remote_sockaddr+ - the +struct+ sockaddr contained in a string or Addrinfo object
#
# === Example:
# # Pull down Google's web page
# require 'socket'
# include Socket::Constants
# socket = Socket.new(AF_INET, SOCK_STREAM, 0)
# sockaddr = Socket.sockaddr_in(80, 'www.google.com')
# begin # emulate blocking connect
# socket.connect_nonblock(sockaddr)
# rescue IO::WaitWritable
# IO.select(nil, [socket]) # wait 3-way handshake completion
# begin
# socket.connect_nonblock(sockaddr) # check connection failure
# rescue Errno::EISCONN
# end
# end
# socket.write("GET / HTTP/1.0\r\n\r\n")
# results = socket.read
#
# Refer to Socket#connect for the exceptions that may be thrown if the call
# to _connect_nonblock_ fails.
#
# Socket#connect_nonblock may raise any error corresponding to connect(2) failure,
# including Errno::EINPROGRESS.
#
# If the exception is Errno::EINPROGRESS,
# it is extended by IO::WaitWritable.
# So IO::WaitWritable can be used to rescue the exceptions for retrying connect_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that connect_nonblock should not raise an IO::WaitWritable exception, but
# return the symbol +:wait_writable+ instead.
#
# === See
# # Socket#connect
def connect_nonblock(addr, exception: true)
__connect_nonblock(addr, exception)
end
end
class UDPSocket < IPSocket
# call-seq:
# udpsocket.recvfrom_nonblock(maxlen [, flags[, outbuf [, options]]]) => [mesg, sender_inet_addr]
#
# Receives up to _maxlen_ bytes from +udpsocket+ using recvfrom(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# _flags_ is zero or more of the +MSG_+ options.
# The first element of the results, _mesg_, is the data received.
# The second element, _sender_inet_addr_, is an array to represent the sender address.
#
# When recvfrom(2) returns 0,
# Socket#recvfrom_nonblock returns an empty string as data.
# It means an empty packet.
#
# === Parameters
# * +maxlen+ - the number of bytes to receive from the socket
# * +flags+ - zero or more of the +MSG_+ options
# * +outbuf+ - destination String buffer
# * +options+ - keyword hash, supporting `exception: false`
#
# === Example
# require 'socket'
# s1 = UDPSocket.new
# s1.bind("127.0.0.1", 0)
# s2 = UDPSocket.new
# s2.bind("127.0.0.1", 0)
# s2.connect(*s1.addr.values_at(3,1))
# s1.connect(*s2.addr.values_at(3,1))
# s1.send "aaa", 0
# begin # emulate blocking recvfrom
# p s2.recvfrom_nonblock(10) #=> ["aaa", ["AF_INET", 33302, "localhost.localdomain", "127.0.0.1"]]
# rescue IO::WaitReadable
# IO.select([s2])
# retry
# end
#
# Refer to Socket#recvfrom for the exceptions that may be thrown if the call
# to _recvfrom_nonblock_ fails.
#
# UDPSocket#recvfrom_nonblock may raise any error corresponding to recvfrom(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK or Errno::EAGAIN,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying recvfrom_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that recvfrom_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * Socket#recvfrom
def recvfrom_nonblock(len, flag = 0, outbuf = nil, exception: true)
__recvfrom_nonblock(len, flag, outbuf, exception)
end
end
class TCPServer < TCPSocket
# call-seq:
# tcpserver.accept_nonblock([options]) => tcpsocket
#
# Accepts an incoming connection using accept(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# It returns an accepted TCPSocket for the incoming connection.
#
# === Example
# require 'socket'
# serv = TCPServer.new(2202)
# begin # emulate blocking accept
# sock = serv.accept_nonblock
# rescue IO::WaitReadable, Errno::EINTR
# IO.select([serv])
# retry
# end
# # sock is an accepted socket.
#
# Refer to Socket#accept for the exceptions that may be thrown if the call
# to TCPServer#accept_nonblock fails.
#
# TCPServer#accept_nonblock may raise any error corresponding to accept(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED, Errno::EPROTO,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that accept_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * TCPServer#accept
# * Socket#accept
def accept_nonblock(exception: true)
__accept_nonblock(exception)
end
end
class UNIXServer < UNIXSocket
# call-seq:
# unixserver.accept_nonblock([options]) => unixsocket
#
# Accepts an incoming connection using accept(2) after
# O_NONBLOCK is set for the underlying file descriptor.
# It returns an accepted UNIXSocket for the incoming connection.
#
# === Example
# require 'socket'
# serv = UNIXServer.new("/tmp/sock")
# begin # emulate blocking accept
# sock = serv.accept_nonblock
# rescue IO::WaitReadable, Errno::EINTR
# IO.select([serv])
# retry
# end
# # sock is an accepted socket.
#
# Refer to Socket#accept for the exceptions that may be thrown if the call
# to UNIXServer#accept_nonblock fails.
#
# UNIXServer#accept_nonblock may raise any error corresponding to accept(2) failure,
# including Errno::EWOULDBLOCK.
#
# If the exception is Errno::EWOULDBLOCK, Errno::EAGAIN, Errno::ECONNABORTED or Errno::EPROTO,
# it is extended by IO::WaitReadable.
# So IO::WaitReadable can be used to rescue the exceptions for retrying accept_nonblock.
#
# By specifying a keyword argument _exception_ to +false+, you can indicate
# that accept_nonblock should not raise an IO::WaitReadable exception, but
# return the symbol +:wait_readable+ instead.
#
# === See
# * UNIXServer#accept
# * Socket#accept
def accept_nonblock(exception: true)
__accept_nonblock(exception)
end
end if defined?(UNIXSocket)
share/ruby/did_you_mean/experimental.rb 0000644 00000000213 15173547341 0014313 0 ustar 00 warn "Experimental features in the did_you_mean gem has been removed " \
"and `require \"did_you_mean/experimental\"' has no effect."
share/ruby/did_you_mean/core_ext/name_error.rb 0000644 00000002420 15173547341 0015561 0 ustar 00 module DidYouMean
module Correctable
if Exception.method_defined?(:detailed_message)
# just for compatibility
def original_message
# we cannot use alias here because
to_s
end
def detailed_message(highlight: true, did_you_mean: true, **)
msg = super.dup
return msg unless did_you_mean
suggestion = DidYouMean.formatter.message_for(corrections)
if highlight
suggestion = suggestion.gsub(/.+/) { "\e[1m" + $& + "\e[m" }
end
msg << suggestion
msg
rescue
super
end
else
SKIP_TO_S_FOR_SUPER_LOOKUP = true
private_constant :SKIP_TO_S_FOR_SUPER_LOOKUP
def original_message
meth = method(:to_s)
while meth.owner.const_defined?(:SKIP_TO_S_FOR_SUPER_LOOKUP)
meth = meth.super_method
end
meth.call
end
def to_s
msg = super.dup
suggestion = DidYouMean.formatter.message_for(corrections)
msg << suggestion if !msg.include?(suggestion)
msg
rescue
super
end
end
def corrections
@corrections ||= spell_checker.corrections
end
def spell_checker
DidYouMean.spell_checkers[self.class.to_s].new(self)
end
end
end
share/ruby/did_you_mean/tree_spell_checker.rb 0000644 00000005471 15173547341 0015453 0 ustar 00 # frozen_string_literal: true
module DidYouMean
# spell checker for a dictionary that has a tree
# structure, see doc/tree_spell_checker_api.md
class TreeSpellChecker
attr_reader :dictionary, :separator, :augment
def initialize(dictionary:, separator: '/', augment: nil)
@dictionary = dictionary
@separator = separator
@augment = augment
end
def correct(input)
plausibles = plausible_dimensions(input)
return fall_back_to_normal_spell_check(input) if plausibles.empty?
suggestions = find_suggestions(input, plausibles)
return fall_back_to_normal_spell_check(input) if suggestions.empty?
suggestions
end
def dictionary_without_leaves
@dictionary_without_leaves ||= dictionary.map { |word| word.split(separator)[0..-2] }.uniq
end
def tree_depth
@tree_depth ||= dictionary_without_leaves.max { |a, b| a.size <=> b.size }.size
end
def dimensions
@dimensions ||= tree_depth.times.map do |index|
dictionary_without_leaves.map { |element| element[index] }.compact.uniq
end
end
def find_leaves(path)
path_with_separator = "#{path}#{separator}"
dictionary
.select {|str| str.include?(path_with_separator) }
.map {|str| str.gsub(path_with_separator, '') }
end
def plausible_dimensions(input)
input.split(separator)[0..-2]
.map
.with_index { |element, index| correct_element(dimensions[index], element) if dimensions[index] }
.compact
end
def possible_paths(states)
states.map { |state| state.join(separator) }
end
private
def find_suggestions(input, plausibles)
states = plausibles[0].product(*plausibles[1..-1])
paths = possible_paths(states)
leaf = input.split(separator).last
find_ideas(paths, leaf)
end
def fall_back_to_normal_spell_check(input)
return [] unless augment
::DidYouMean::SpellChecker.new(dictionary: dictionary).correct(input)
end
def find_ideas(paths, leaf)
paths.flat_map do |path|
names = find_leaves(path)
ideas = correct_element(names, leaf)
ideas_to_paths(ideas, leaf, names, path)
end.compact
end
def ideas_to_paths(ideas, leaf, names, path)
if ideas.empty?
nil
elsif names.include?(leaf)
["#{path}#{separator}#{leaf}"]
else
ideas.map {|str| "#{path}#{separator}#{str}" }
end
end
def correct_element(names, element)
return names if names.size == 1
str = normalize(element)
return [str] if names.include?(str)
::DidYouMean::SpellChecker.new(dictionary: names).correct(str)
end
def normalize(str)
str.downcase!
str.tr!('@', ' ') if str.include?('@')
str
end
end
end
share/ruby/did_you_mean/levenshtein.rb 0000644 00000002537 15173547341 0014155 0 ustar 00 module DidYouMean
module Levenshtein # :nodoc:
# This code is based directly on the Text gem implementation
# Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher.
#
# Returns a value representing the "cost" of transforming str1 into str2
def distance(str1, str2)
n = str1.length
m = str2.length
return m if n.zero?
return n if m.zero?
d = (0..m).to_a
x = nil
# to avoid duplicating an enumerable object, create it outside of the loop
str2_codepoints = str2.codepoints
str1.each_codepoint.with_index(1) do |char1, i|
j = 0
while j < m
cost = (char1 == str2_codepoints[j]) ? 0 : 1
x = min3(
d[j+1] + 1, # insertion
i + 1, # deletion
d[j] + cost # substitution
)
d[j] = i
i = x
j += 1
end
d[m] = x
end
x
end
module_function :distance
private
# detects the minimum value out of three arguments. This method is
# faster than `[a, b, c].min` and puts less GC pressure.
# See https://github.com/ruby/did_you_mean/pull/1 for a performance
# benchmark.
def min3(a, b, c)
if a < b && a < c
a
elsif b < c
b
else
c
end
end
module_function :min3
end
end
share/ruby/did_you_mean/version.rb 0000644 00000000061 15173547341 0013304 0 ustar 00 module DidYouMean
VERSION = "1.6.3".freeze
end
share/ruby/did_you_mean/jaro_winkler.rb 0000644 00000003451 15173547341 0014313 0 ustar 00 module DidYouMean
module Jaro
module_function
def distance(str1, str2)
str1, str2 = str2, str1 if str1.length > str2.length
length1, length2 = str1.length, str2.length
m = 0.0
t = 0.0
range = (length2 / 2).floor - 1
range = 0 if range < 0
flags1 = 0
flags2 = 0
# Avoid duplicating enumerable objects
str1_codepoints = str1.codepoints
str2_codepoints = str2.codepoints
i = 0
while i < length1
last = i + range
j = (i >= range) ? i - range : 0
while j <= last
if flags2[j] == 0 && str1_codepoints[i] == str2_codepoints[j]
flags2 |= (1 << j)
flags1 |= (1 << i)
m += 1
break
end
j += 1
end
i += 1
end
k = i = 0
while i < length1
if flags1[i] != 0
j = index = k
k = while j < length2
index = j
break(j + 1) if flags2[j] != 0
j += 1
end
t += 1 if str1_codepoints[i] != str2_codepoints[index]
end
i += 1
end
t = (t / 2).floor
m == 0 ? 0 : (m / length1 + m / length2 + (m - t) / m) / 3
end
end
module JaroWinkler
WEIGHT = 0.1
THRESHOLD = 0.7
module_function
def distance(str1, str2)
jaro_distance = Jaro.distance(str1, str2)
if jaro_distance > THRESHOLD
codepoints2 = str2.codepoints
prefix_bonus = 0
i = 0
str1.each_codepoint do |char1|
char1 == codepoints2[i] && i < 4 ? prefix_bonus += 1 : break
i += 1
end
jaro_distance + (prefix_bonus * WEIGHT * (1 - jaro_distance))
else
jaro_distance
end
end
end
end
share/ruby/did_you_mean/formatter.rb 0000644 00000002430 15173547341 0013624 0 ustar 00 # frozen-string-literal: true
module DidYouMean
# The +DidYouMean::Formatter+ is the basic, default formatter for the
# gem. The formatter responds to the +message_for+ method and it returns a
# human readable string.
class Formatter
# Returns a human readable string that contains +corrections+. This
# formatter is designed to be less verbose to not take too much screen
# space while being helpful enough to the user.
#
# @example
#
# formatter = DidYouMean::Formatter.new
#
# # displays suggestions in two lines with the leading empty line
# puts formatter.message_for(["methods", "method"])
#
# Did you mean? methods
# method
# # => nil
#
# # displays an empty line
# puts formatter.message_for([])
#
# # => nil
#
def self.message_for(corrections)
corrections.empty? ? "" : "\nDid you mean? #{corrections.join("\n ")}"
end
def message_for(corrections)
warn "The instance method #message_for has been deprecated. Please use the class method " \
"DidYouMean::Formatter.message_for(...) instead."
self.class.message_for(corrections)
end
end
PlainFormatter = Formatter
deprecate_constant :PlainFormatter
end
share/ruby/did_you_mean/spell_checker.rb 0000644 00000002421 15173547341 0014424 0 ustar 00 # frozen-string-literal: true
require_relative "levenshtein"
require_relative "jaro_winkler"
module DidYouMean
class SpellChecker
def initialize(dictionary:)
@dictionary = dictionary
end
def correct(input)
normalized_input = normalize(input)
threshold = normalized_input.length > 3 ? 0.834 : 0.77
words = @dictionary.select { |word| JaroWinkler.distance(normalize(word), normalized_input) >= threshold }
words.reject! { |word| input.to_s == word.to_s }
words.sort_by! { |word| JaroWinkler.distance(word.to_s, normalized_input) }
words.reverse!
# Correct mistypes
threshold = (normalized_input.length * 0.25).ceil
corrections = words.select { |c| Levenshtein.distance(normalize(c), normalized_input) <= threshold }
# Correct misspells
if corrections.empty?
corrections = words.select do |word|
word = normalize(word)
length = normalized_input.length < word.length ? normalized_input.length : word.length
Levenshtein.distance(word, normalized_input) < length
end.first(1)
end
corrections
end
private
def normalize(str_or_symbol) #:nodoc:
str = str_or_symbol.to_s.downcase
str.tr!("@", "")
str
end
end
end
share/ruby/did_you_mean/spell_checkers/pattern_key_name_checker.rb 0000644 00000001033 15173547341 0021616 0 ustar 00 require_relative "../spell_checker"
module DidYouMean
class PatternKeyNameChecker
def initialize(no_matching_pattern_key_error)
@key = no_matching_pattern_key_error.key
@keys = no_matching_pattern_key_error.matchee.keys
end
def corrections
@corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches
end
private
def exact_matches
@exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect)
end
end
end
share/ruby/did_you_mean/spell_checkers/name_error_checkers.rb 0000644 00000001067 15173547341 0020614 0 ustar 00 require_relative 'name_error_checkers/class_name_checker'
require_relative 'name_error_checkers/variable_name_checker'
module DidYouMean
class << (NameErrorCheckers = Object.new)
def new(exception)
case exception.original_message
when /uninitialized constant/
ClassNameChecker
when /undefined local variable or method/,
/undefined method/,
/uninitialized class variable/,
/no member '.*' in struct/
VariableNameChecker
else
NullChecker
end.new(exception)
end
end
end
share/ruby/did_you_mean/spell_checkers/null_checker.rb 0000644 00000000150 15173547341 0017242 0 ustar 00 module DidYouMean
class NullChecker
def initialize(*); end
def corrections; [] end
end
end
share/ruby/did_you_mean/spell_checkers/key_error_checker.rb 0000644 00000000732 15173547341 0020277 0 ustar 00 require_relative "../spell_checker"
module DidYouMean
class KeyErrorChecker
def initialize(key_error)
@key = key_error.key
@keys = key_error.receiver.keys
end
def corrections
@corrections ||= exact_matches.empty? ? SpellChecker.new(dictionary: @keys).correct(@key).map(&:inspect) : exact_matches
end
private
def exact_matches
@exact_matches ||= @keys.select { |word| @key == word.to_s }.map(&:inspect)
end
end
end
share/ruby/did_you_mean/spell_checkers/require_path_checker.rb 0000644 00000002356 15173547341 0020772 0 ustar 00 # frozen-string-literal: true
require_relative "../spell_checker"
require_relative "../tree_spell_checker"
require "rbconfig"
module DidYouMean
class RequirePathChecker
attr_reader :path
INITIAL_LOAD_PATH = $LOAD_PATH.dup.freeze
Ractor.make_shareable(INITIAL_LOAD_PATH) if defined?(Ractor)
ENV_SPECIFIC_EXT = ".#{RbConfig::CONFIG["DLEXT"]}"
Ractor.make_shareable(ENV_SPECIFIC_EXT) if defined?(Ractor)
private_constant :INITIAL_LOAD_PATH, :ENV_SPECIFIC_EXT
def self.requireables
@requireables ||= INITIAL_LOAD_PATH
.flat_map {|path| Dir.glob("**/???*{.rb,#{ENV_SPECIFIC_EXT}}", base: path) }
.map {|path| path.chomp!(".rb") || path.chomp!(ENV_SPECIFIC_EXT) }
end
def initialize(exception)
@path = exception.path
end
def corrections
@corrections ||= begin
threshold = path.size * 2
dictionary = self.class.requireables.reject {|str| str.size >= threshold }
spell_checker = path.include?("/") ? TreeSpellChecker : SpellChecker
spell_checker.new(dictionary: dictionary).correct(path).uniq
end
end
end
end
share/ruby/did_you_mean/spell_checkers/name_error_checkers/variable_name_checker.rb 0000644 00000004115 15173547341 0025062 0 ustar 00 # frozen-string-literal: true
require_relative "../../spell_checker"
module DidYouMean
class VariableNameChecker
attr_reader :name, :method_names, :lvar_names, :ivar_names, :cvar_names
NAMES_TO_EXCLUDE = { 'foo' => [:fork, :for] }
NAMES_TO_EXCLUDE.default = []
Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor)
# +VariableNameChecker::RB_RESERVED_WORDS+ is the list of all reserved
# words in Ruby. They could be declared like methods are, and a typo would
# cause Ruby to raise a +NameError+ because of the way they are declared.
#
# The +:VariableNameChecker+ will use this list to suggest a reversed word
# if a +NameError+ is raised and found closest matches, excluding:
#
# * +do+
# * +if+
# * +in+
# * +or+
#
# Also see +MethodNameChecker::RB_RESERVED_WORDS+.
RB_RESERVED_WORDS = %i(
BEGIN
END
alias
and
begin
break
case
class
def
defined?
else
elsif
end
ensure
false
for
module
next
nil
not
redo
rescue
retry
return
self
super
then
true
undef
unless
until
when
while
yield
__LINE__
__FILE__
__ENCODING__
)
Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor)
def initialize(exception)
@name = exception.name.to_s.tr("@", "")
@lvar_names = exception.respond_to?(:local_variables) ? exception.local_variables : []
receiver = exception.receiver
@method_names = receiver.methods + receiver.private_methods
@ivar_names = receiver.instance_variables
@cvar_names = receiver.class.class_variables
@cvar_names += receiver.class_variables if receiver.kind_of?(Module)
end
def corrections
@corrections ||= SpellChecker
.new(dictionary: (RB_RESERVED_WORDS + lvar_names + method_names + ivar_names + cvar_names))
.correct(name).uniq - NAMES_TO_EXCLUDE[@name]
end
end
end
share/ruby/did_you_mean/spell_checkers/name_error_checkers/class_name_checker.rb 0000644 00000002300 15173547341 0024374 0 ustar 00 # frozen-string-literal: true
require_relative "../../spell_checker"
module DidYouMean
class ClassNameChecker
attr_reader :class_name
def initialize(exception)
@class_name, @receiver, @original_message = exception.name, exception.receiver, exception.original_message
end
def corrections
@corrections ||= SpellChecker.new(dictionary: class_names)
.correct(class_name)
.map(&:full_name)
.reject {|qualified_name| @original_message.include?(qualified_name) }
end
def class_names
scopes.flat_map do |scope|
scope.constants.map do |c|
ClassName.new(c, scope == Object ? "" : "#{scope}::")
end
end
end
def scopes
@scopes ||= @receiver.to_s.split("::").inject([Object]) do |_scopes, scope|
_scopes << _scopes.last.const_get(scope)
end.uniq
end
class ClassName < String
attr :namespace
def initialize(name, namespace = '')
super(name.to_s)
@namespace = namespace
end
def full_name
self.class.new("#{namespace}#{self}")
end
end
private_constant :ClassName
end
end
share/ruby/did_you_mean/spell_checkers/method_name_checker.rb 0000644 00000004345 15173547341 0020562 0 ustar 00 require_relative "../spell_checker"
module DidYouMean
class MethodNameChecker
attr_reader :method_name, :receiver
NAMES_TO_EXCLUDE = { NilClass => nil.methods }
NAMES_TO_EXCLUDE.default = []
Ractor.make_shareable(NAMES_TO_EXCLUDE) if defined?(Ractor)
# +MethodNameChecker::RB_RESERVED_WORDS+ is the list of reserved words in
# Ruby that take an argument. Unlike
# +VariableNameChecker::RB_RESERVED_WORDS+, these reserved words require
# an argument, and a +NoMethodError+ is raised due to the presence of the
# argument.
#
# The +MethodNameChecker+ will use this list to suggest a reversed word if
# a +NoMethodError+ is raised and found closest matches.
#
# Also see +VariableNameChecker::RB_RESERVED_WORDS+.
RB_RESERVED_WORDS = %i(
alias
case
def
defined?
elsif
end
ensure
for
rescue
super
undef
unless
until
when
while
yield
)
Ractor.make_shareable(RB_RESERVED_WORDS) if defined?(Ractor)
def initialize(exception)
@method_name = exception.name
@receiver = exception.receiver
@private_call = exception.respond_to?(:private_call?) ? exception.private_call? : false
end
def corrections
@corrections ||= begin
dictionary = method_names
dictionary = RB_RESERVED_WORDS + dictionary if @private_call
SpellChecker.new(dictionary: dictionary).correct(method_name) - names_to_exclude
end
end
def method_names
if Object === receiver
method_names = receiver.methods + receiver.singleton_methods
method_names += receiver.private_methods if @private_call
method_names.uniq!
# Assume that people trying to use a writer are not interested in a reader
# and vice versa
if method_name.match?(/=\Z/)
method_names.select! { |name| name.match?(/=\Z/) }
else
method_names.reject! { |name| name.match?(/=\Z/) }
end
method_names
else
[]
end
end
def names_to_exclude
Object === receiver ? NAMES_TO_EXCLUDE[receiver.class] : []
end
end
end
share/ruby/did_you_mean/formatters/verbose_formatter.rb 0000644 00000000402 15173547341 0017534 0 ustar 00 # frozen-string-literal: true
warn "`require 'did_you_mean/formatters/verbose_formatter'` is deprecated and falls back to the default formatter. "
require_relative '../formatter'
module DidYouMean
# For compatibility:
VerboseFormatter = Formatter
end
share/ruby/did_you_mean/formatters/plain_formatter.rb 0000644 00000000250 15173547341 0017173 0 ustar 00 require_relative '../formatter'
warn "`require 'did_you_mean/formatters/plain_formatter'` is deprecated. Please `require 'did_you_mean/formatter'` " \
"instead."
share/ruby/did_you_mean/verbose.rb 0000644 00000000211 15173547341 0013261 0 ustar 00 warn "The verbose formatter has been removed and now `require 'did_you_mean/verbose'` has no effect. Please " \
"remove this call."
share/gems/gems/io-console-0.6.0/lib/io 0000755 00000000000 15173547341 0013345 0 ustar 00 share/ruby/tempfile.rb 0000644 00000033532 15173547341 0011001 0 ustar 00 # frozen_string_literal: true
#
# tempfile - manipulates temporary files
#
# $Id$
#
require 'delegate'
require 'tmpdir'
# A utility class for managing temporary files. When you create a Tempfile
# object, it will create a temporary file with a unique filename. A Tempfile
# objects behaves just like a File object, and you can perform all the usual
# file operations on it: reading data, writing data, changing its permissions,
# etc. So although this class does not explicitly document all instance methods
# supported by File, you can in fact call any File instance method on a
# Tempfile object.
#
# == Synopsis
#
# require 'tempfile'
#
# file = Tempfile.new('foo')
# file.path # => A unique filename in the OS's temp directory,
# # e.g.: "/tmp/foo.24722.0"
# # This filename contains 'foo' in its basename.
# file.write("hello world")
# file.rewind
# file.read # => "hello world"
# file.close
# file.unlink # deletes the temp file
#
# == Good practices
#
# === Explicit close
#
# When a Tempfile object is garbage collected, or when the Ruby interpreter
# exits, its associated temporary file is automatically deleted. This means
# that it's unnecessary to explicitly delete a Tempfile after use, though
# it's a good practice to do so: not explicitly deleting unused Tempfiles can
# potentially leave behind a large number of temp files on the filesystem
# until they're garbage collected. The existence of these temp files can make
# it harder to determine a new Tempfile filename.
#
# Therefore, one should always call #unlink or close in an ensure block, like
# this:
#
# file = Tempfile.new('foo')
# begin
# # ...do something with file...
# ensure
# file.close
# file.unlink # deletes the temp file
# end
#
# Tempfile.create { ... } exists for this purpose and is more convenient to use.
# Note that Tempfile.create returns a File instance instead of a Tempfile, which
# also avoids the overhead and complications of delegation.
#
# Tempfile.open('foo') do |file|
# # ...do something with file...
# end
#
# === Unlink after creation
#
# On POSIX systems, it's possible to unlink a file right after creating it,
# and before closing it. This removes the filesystem entry without closing
# the file handle, so it ensures that only the processes that already had
# the file handle open can access the file's contents. It's strongly
# recommended that you do this if you do not want any other processes to
# be able to read from or write to the Tempfile, and you do not need to
# know the Tempfile's filename either.
#
# For example, a practical use case for unlink-after-creation would be this:
# you need a large byte buffer that's too large to comfortably fit in RAM,
# e.g. when you're writing a web server and you want to buffer the client's
# file upload data.
#
# Please refer to #unlink for more information and a code example.
#
# == Minor notes
#
# Tempfile's filename picking method is both thread-safe and inter-process-safe:
# it guarantees that no other threads or processes will pick the same filename.
#
# Tempfile itself however may not be entirely thread-safe. If you access the
# same Tempfile object from multiple threads then you should protect it with a
# mutex.
class Tempfile < DelegateClass(File)
# Creates a file in the underlying file system;
# returns a new \Tempfile object based on that file.
#
# If possible, consider instead using Tempfile.create, which:
#
# - Avoids the performance cost of delegation,
# incurred when Tempfile.new calls its superclass <tt>DelegateClass(File)</tt>.
# - Does not rely on a finalizer to close and unlink the file,
# which can be unreliable.
#
# Creates and returns file whose:
#
# - Class is \Tempfile (not \File, as in Tempfile.create).
# - Directory is the system temporary directory (system-dependent).
# - Generated filename is unique in that directory.
# - Permissions are <tt>0600</tt>;
# see {File Permissions}[rdoc-ref:File@File+Permissions].
# - Mode is <tt>'w+'</tt> (read/write mode, positioned at the end).
#
# The underlying file is removed when the \Tempfile object dies
# and is reclaimed by the garbage collector.
#
# Example:
#
# f = Tempfile.new # => #<Tempfile:/tmp/20220505-17839-1s0kt30>
# f.class # => Tempfile
# f.path # => "/tmp/20220505-17839-1s0kt30"
# f.stat.mode.to_s(8) # => "100600"
# File.exist?(f.path) # => true
# File.unlink(f.path) #
# File.exist?(f.path) # => false
#
# Argument +basename+, if given, may be one of:
#
# - A string: the generated filename begins with +basename+:
#
# Tempfile.new('foo') # => #<Tempfile:/tmp/foo20220505-17839-1whk2f>
#
# - An array of two strings <tt>[prefix, suffix]</tt>:
# the generated filename begins with +prefix+ and ends with +suffix+:
#
# Tempfile.new(%w/foo .jpg/) # => #<Tempfile:/tmp/foo20220505-17839-58xtfi.jpg>
#
# With arguments +basename+ and +tmpdir+, the file is created in directory +tmpdir+:
#
# Tempfile.new('foo', '.') # => #<Tempfile:./foo20220505-17839-xfstr8>
#
# Keyword arguments +mode+ and +options+ are passed directly to method
# {File.open}[rdoc-ref:File.open]:
#
# - The value given with +mode+ must be an integer,
# and may be expressed as the logical OR of constants defined in
# {File::Constants}[rdoc-ref:File::Constants].
# - For +options+, see {Open Options}[rdoc-ref:IO@Open+Options].
#
# Related: Tempfile.create.
#
def initialize(basename="", tmpdir=nil, mode: 0, **options)
warn "Tempfile.new doesn't call the given block.", uplevel: 1 if block_given?
@unlinked = false
@mode = mode|File::RDWR|File::CREAT|File::EXCL
::Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
opts[:perm] = 0600
@tmpfile = File.open(tmpname, @mode, **opts)
@opts = opts.freeze
end
ObjectSpace.define_finalizer(self, Remover.new(@tmpfile))
super(@tmpfile)
end
# Opens or reopens the file with mode "r+".
def open
_close
mode = @mode & ~(File::CREAT|File::EXCL)
@tmpfile = File.open(@tmpfile.path, mode, **@opts)
__setobj__(@tmpfile)
end
def _close # :nodoc:
@tmpfile.close
end
protected :_close
# Closes the file. If +unlink_now+ is true, then the file will be unlinked
# (deleted) after closing. Of course, you can choose to later call #unlink
# if you do not unlink it now.
#
# If you don't explicitly unlink the temporary file, the removal
# will be delayed until the object is finalized.
def close(unlink_now=false)
_close
unlink if unlink_now
end
# Closes and unlinks (deletes) the file. Has the same effect as called
# <tt>close(true)</tt>.
def close!
close(true)
end
# Unlinks (deletes) the file from the filesystem. One should always unlink
# the file after using it, as is explained in the "Explicit close" good
# practice section in the Tempfile overview:
#
# file = Tempfile.new('foo')
# begin
# # ...do something with file...
# ensure
# file.close
# file.unlink # deletes the temp file
# end
#
# === Unlink-before-close
#
# On POSIX systems it's possible to unlink a file before closing it. This
# practice is explained in detail in the Tempfile overview (section
# "Unlink after creation"); please refer there for more information.
#
# However, unlink-before-close may not be supported on non-POSIX operating
# systems. Microsoft Windows is the most notable case: unlinking a non-closed
# file will result in an error, which this method will silently ignore. If
# you want to practice unlink-before-close whenever possible, then you should
# write code like this:
#
# file = Tempfile.new('foo')
# file.unlink # On Windows this silently fails.
# begin
# # ... do something with file ...
# ensure
# file.close! # Closes the file handle. If the file wasn't unlinked
# # because #unlink failed, then this method will attempt
# # to do so again.
# end
def unlink
return if @unlinked
begin
File.unlink(@tmpfile.path)
rescue Errno::ENOENT
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
return
end
ObjectSpace.undefine_finalizer(self)
@unlinked = true
end
alias delete unlink
# Returns the full path name of the temporary file.
# This will be nil if #unlink has been called.
def path
@unlinked ? nil : @tmpfile.path
end
# Returns the size of the temporary file. As a side effect, the IO
# buffer is flushed before determining the size.
def size
if !@tmpfile.closed?
@tmpfile.size # File#size calls rb_io_flush_raw()
else
File.size(@tmpfile.path)
end
end
alias length size
# :stopdoc:
def inspect
if @tmpfile.closed?
"#<#{self.class}:#{path} (closed)>"
else
"#<#{self.class}:#{path}>"
end
end
class Remover # :nodoc:
def initialize(tmpfile)
@pid = Process.pid
@tmpfile = tmpfile
end
def call(*args)
return if @pid != Process.pid
$stderr.puts "removing #{@tmpfile.path}..." if $DEBUG
@tmpfile.close
begin
File.unlink(@tmpfile.path)
rescue Errno::ENOENT
end
$stderr.puts "done" if $DEBUG
end
end
class << self
# :startdoc:
# Creates a new Tempfile.
#
# This method is not recommended and exists mostly for backward compatibility.
# Please use Tempfile.create instead, which avoids the cost of delegation,
# does not rely on a finalizer, and also unlinks the file when given a block.
#
# Tempfile.open is still appropriate if you need the Tempfile to be unlinked
# by a finalizer and you cannot explicitly know where in the program the
# Tempfile can be unlinked safely.
#
# If no block is given, this is a synonym for Tempfile.new.
#
# If a block is given, then a Tempfile object will be constructed,
# and the block is run with the Tempfile object as argument. The Tempfile
# object will be automatically closed after the block terminates.
# However, the file will *not* be unlinked and needs to be manually unlinked
# with Tempfile#close! or Tempfile#unlink. The finalizer will try to unlink
# but should not be relied upon as it can keep the file on the disk much
# longer than intended. For instance, on CRuby, finalizers can be delayed
# due to conservative stack scanning and references left in unused memory.
#
# The call returns the value of the block.
#
# In any case, all arguments (<code>*args</code>) will be passed to Tempfile.new.
#
# Tempfile.open('foo', '/home/temp') do |f|
# # ... do something with f ...
# end
#
# # Equivalent:
# f = Tempfile.open('foo', '/home/temp')
# begin
# # ... do something with f ...
# ensure
# f.close
# end
def open(*args, **kw)
tempfile = new(*args, **kw)
if block_given?
begin
yield(tempfile)
ensure
tempfile.close
end
else
tempfile
end
end
end
end
# Creates a file in the underlying file system;
# returns a new \File object based on that file.
#
# With no block given and no arguments, creates and returns file whose:
#
# - Class is {File}[rdoc-ref:File] (not \Tempfile).
# - Directory is the system temporary directory (system-dependent).
# - Generated filename is unique in that directory.
# - Permissions are <tt>0600</tt>;
# see {File Permissions}[rdoc-ref:File@File+Permissions].
# - Mode is <tt>'w+'</tt> (read/write mode, positioned at the end).
#
# With no block, the file is not removed automatically,
# and so should be explicitly removed.
#
# Example:
#
# f = Tempfile.create # => #<File:/tmp/20220505-9795-17ky6f6>
# f.class # => File
# f.path # => "/tmp/20220505-9795-17ky6f6"
# f.stat.mode.to_s(8) # => "100600"
# File.exist?(f.path) # => true
# File.unlink(f.path)
# File.exist?(f.path) # => false
#
# Argument +basename+, if given, may be one of:
#
# - A string: the generated filename begins with +basename+:
#
# Tempfile.create('foo') # => #<File:/tmp/foo20220505-9795-1gok8l9>
#
# - An array of two strings <tt>[prefix, suffix]</tt>:
# the generated filename begins with +prefix+ and ends with +suffix+:
#
# Tempfile.create(%w/foo .jpg/) # => #<File:/tmp/foo20220505-17839-tnjchh.jpg>
#
# With arguments +basename+ and +tmpdir+, the file is created in directory +tmpdir+:
#
# Tempfile.create('foo', '.') # => #<File:./foo20220505-9795-1emu6g8>
#
# Keyword arguments +mode+ and +options+ are passed directly to method
# {File.open}[rdoc-ref:File.open]:
#
# - The value given with +mode+ must be an integer,
# and may be expressed as the logical OR of constants defined in
# {File::Constants}[rdoc-ref:File::Constants].
# - For +options+, see {Open Options}[rdoc-ref:IO@Open+Options].
#
# With a block given, creates the file as above, passes it to the block,
# and returns the block's value;
# before the return, the file object is closed and the underlying file is removed:
#
# Tempfile.create {|file| file.path } # => "/tmp/20220505-9795-rkists"
#
# Related: Tempfile.new.
#
def Tempfile.create(basename="", tmpdir=nil, mode: 0, **options)
tmpfile = nil
Dir::Tmpname.create(basename, tmpdir, **options) do |tmpname, n, opts|
mode |= File::RDWR|File::CREAT|File::EXCL
opts[:perm] = 0600
tmpfile = File.open(tmpname, mode, **opts)
end
if block_given?
begin
yield tmpfile
ensure
unless tmpfile.closed?
if File.identical?(tmpfile, tmpfile.path)
unlinked = File.unlink tmpfile.path rescue nil
end
tmpfile.close
end
unless unlinked
begin
File.unlink tmpfile.path
rescue Errno::ENOENT
end
end
end
else
tmpfile
end
end
share/ruby/ostruct.rb 0000644 00000033611 15173547341 0010675 0 ustar 00 # frozen_string_literal: true
#
# = ostruct.rb: OpenStruct implementation
#
# Author:: Yukihiro Matsumoto
# Documentation:: Gavin Sinclair
#
# OpenStruct allows the creation of data objects with arbitrary attributes.
# See OpenStruct for an example.
#
#
# An OpenStruct is a data structure, similar to a Hash, that allows the
# definition of arbitrary attributes with their accompanying values. This is
# accomplished by using Ruby's metaprogramming to define methods on the class
# itself.
#
# == Examples
#
# require "ostruct"
#
# person = OpenStruct.new
# person.name = "John Smith"
# person.age = 70
#
# person.name # => "John Smith"
# person.age # => 70
# person.address # => nil
#
# An OpenStruct employs a Hash internally to store the attributes and values
# and can even be initialized with one:
#
# australia = OpenStruct.new(:country => "Australia", :capital => "Canberra")
# # => #<OpenStruct country="Australia", capital="Canberra">
#
# Hash keys with spaces or characters that could normally not be used for
# method calls (e.g. <code>()[]*</code>) will not be immediately available
# on the OpenStruct object as a method for retrieval or assignment, but can
# still be reached through the Object#send method or using [].
#
# measurements = OpenStruct.new("length (in inches)" => 24)
# measurements[:"length (in inches)"] # => 24
# measurements.send("length (in inches)") # => 24
#
# message = OpenStruct.new(:queued? => true)
# message.queued? # => true
# message.send("queued?=", false)
# message.queued? # => false
#
# Removing the presence of an attribute requires the execution of the
# delete_field method as setting the property value to +nil+ will not
# remove the attribute.
#
# first_pet = OpenStruct.new(:name => "Rowdy", :owner => "John Smith")
# second_pet = OpenStruct.new(:name => "Rowdy")
#
# first_pet.owner = nil
# first_pet # => #<OpenStruct name="Rowdy", owner=nil>
# first_pet == second_pet # => false
#
# first_pet.delete_field(:owner)
# first_pet # => #<OpenStruct name="Rowdy">
# first_pet == second_pet # => true
#
# Ractor compatibility: A frozen OpenStruct with shareable values is itself shareable.
#
# == Caveats
#
# An OpenStruct utilizes Ruby's method lookup structure to find and define the
# necessary methods for properties. This is accomplished through the methods
# method_missing and define_singleton_method.
#
# This should be a consideration if there is a concern about the performance of
# the objects that are created, as there is much more overhead in the setting
# of these properties compared to using a Hash or a Struct.
# Creating an open struct from a small Hash and accessing a few of the
# entries can be 200 times slower than accessing the hash directly.
#
# This is a potential security issue; building OpenStruct from untrusted user data
# (e.g. JSON web request) may be susceptible to a "symbol denial of service" attack
# since the keys create methods and names of methods are never garbage collected.
#
# This may also be the source of incompatibilities between Ruby versions:
#
# o = OpenStruct.new
# o.then # => nil in Ruby < 2.6, enumerator for Ruby >= 2.6
#
# Builtin methods may be overwritten this way, which may be a source of bugs
# or security issues:
#
# o = OpenStruct.new
# o.methods # => [:to_h, :marshal_load, :marshal_dump, :each_pair, ...
# o.methods = [:foo, :bar]
# o.methods # => [:foo, :bar]
#
# To help remedy clashes, OpenStruct uses only protected/private methods ending with <code>!</code>
# and defines aliases for builtin public methods by adding a <code>!</code>:
#
# o = OpenStruct.new(make: 'Bentley', class: :luxury)
# o.class # => :luxury
# o.class! # => OpenStruct
#
# It is recommended (but not enforced) to not use fields ending in <code>!</code>;
# Note that a subclass' methods may not be overwritten, nor can OpenStruct's own methods
# ending with <code>!</code>.
#
# For all these reasons, consider not using OpenStruct at all.
#
class OpenStruct
VERSION = "0.5.5"
#
# Creates a new OpenStruct object. By default, the resulting OpenStruct
# object will have no attributes.
#
# The optional +hash+, if given, will generate attributes and values
# (can be a Hash, an OpenStruct or a Struct).
# For example:
#
# require "ostruct"
# hash = { "country" => "Australia", :capital => "Canberra" }
# data = OpenStruct.new(hash)
#
# data # => #<OpenStruct country="Australia", capital="Canberra">
#
def initialize(hash=nil)
if hash
update_to_values!(hash)
else
@table = {}
end
end
# Duplicates an OpenStruct object's Hash table.
private def initialize_clone(orig) # :nodoc:
super # clones the singleton class for us
@table = @table.dup unless @table.frozen?
end
private def initialize_dup(orig) # :nodoc:
super
update_to_values!(@table)
end
private def update_to_values!(hash) # :nodoc:
@table = {}
hash.each_pair do |k, v|
set_ostruct_member_value!(k, v)
end
end
#
# call-seq:
# ostruct.to_h -> hash
# ostruct.to_h {|name, value| block } -> hash
#
# Converts the OpenStruct to a hash with keys representing
# each attribute (as symbols) and their corresponding values.
#
# If a block is given, the results of the block on each pair of
# the receiver will be used as pairs.
#
# require "ostruct"
# data = OpenStruct.new("country" => "Australia", :capital => "Canberra")
# data.to_h # => {:country => "Australia", :capital => "Canberra" }
# data.to_h {|name, value| [name.to_s, value.upcase] }
# # => {"country" => "AUSTRALIA", "capital" => "CANBERRA" }
#
if {test: :to_h}.to_h{ [:works, true] }[:works] # RUBY_VERSION < 2.6 compatibility
def to_h(&block)
if block
@table.to_h(&block)
else
@table.dup
end
end
else
def to_h(&block)
if block
@table.map(&block).to_h
else
@table.dup
end
end
end
#
# :call-seq:
# ostruct.each_pair {|name, value| block } -> ostruct
# ostruct.each_pair -> Enumerator
#
# Yields all attributes (as symbols) along with the corresponding values
# or returns an enumerator if no block is given.
#
# require "ostruct"
# data = OpenStruct.new("country" => "Australia", :capital => "Canberra")
# data.each_pair.to_a # => [[:country, "Australia"], [:capital, "Canberra"]]
#
def each_pair
return to_enum(__method__) { @table.size } unless defined?(yield)
@table.each_pair{|p| yield p}
self
end
#
# Provides marshalling support for use by the Marshal library.
#
def marshal_dump # :nodoc:
@table
end
#
# Provides marshalling support for use by the Marshal library.
#
alias_method :marshal_load, :update_to_values! # :nodoc:
#
# Used internally to defined properties on the
# OpenStruct. It does this by using the metaprogramming function
# define_singleton_method for both the getter method and the setter method.
#
def new_ostruct_member!(name) # :nodoc:
unless @table.key?(name) || is_method_protected!(name)
if defined?(::Ractor)
getter_proc = nil.instance_eval{ Proc.new { @table[name] } }
setter_proc = nil.instance_eval{ Proc.new {|x| @table[name] = x} }
::Ractor.make_shareable(getter_proc)
::Ractor.make_shareable(setter_proc)
else
getter_proc = Proc.new { @table[name] }
setter_proc = Proc.new {|x| @table[name] = x}
end
define_singleton_method!(name, &getter_proc)
define_singleton_method!("#{name}=", &setter_proc)
end
end
private :new_ostruct_member!
private def is_method_protected!(name) # :nodoc:
if !respond_to?(name, true)
false
elsif name.match?(/!$/)
true
else
owner = method!(name).owner
if owner.class == ::Class
owner < ::OpenStruct
else
self.class!.ancestors.any? do |mod|
return false if mod == ::OpenStruct
mod == owner
end
end
end
end
def freeze
@table.freeze
super
end
private def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise! ArgumentError, "wrong number of arguments (given #{len}, expected 1)", caller(1)
end
set_ostruct_member_value!(mname, args[0])
elsif len == 0
@table[mid]
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise!
end
end
end
#
# :call-seq:
# ostruct[name] -> object
#
# Returns the value of an attribute, or +nil+ if there is no such attribute.
#
# require "ostruct"
# person = OpenStruct.new("name" => "John Smith", "age" => 70)
# person[:age] # => 70, same as person.age
#
def [](name)
@table[name.to_sym]
end
#
# :call-seq:
# ostruct[name] = obj -> obj
#
# Sets the value of an attribute.
#
# require "ostruct"
# person = OpenStruct.new("name" => "John Smith", "age" => 70)
# person[:age] = 42 # equivalent to person.age = 42
# person.age # => 42
#
def []=(name, value)
name = name.to_sym
new_ostruct_member!(name)
@table[name] = value
end
alias_method :set_ostruct_member_value!, :[]=
private :set_ostruct_member_value!
# :call-seq:
# ostruct.dig(name, *identifiers) -> object
#
# Finds and returns the object in nested objects
# that is specified by +name+ and +identifiers+.
# The nested objects may be instances of various classes.
# See {Dig Methods}[rdoc-ref:dig_methods.rdoc].
#
# Examples:
# require "ostruct"
# address = OpenStruct.new("city" => "Anytown NC", "zip" => 12345)
# person = OpenStruct.new("name" => "John Smith", "address" => address)
# person.dig(:address, "zip") # => 12345
# person.dig(:business_address, "zip") # => nil
def dig(name, *names)
begin
name = name.to_sym
rescue NoMethodError
raise! TypeError, "#{name} is not a symbol nor a string"
end
@table.dig(name, *names)
end
#
# Removes the named field from the object and returns the value the field
# contained if it was defined. You may optionally provide a block.
# If the field is not defined, the result of the block is returned,
# or a NameError is raised if no block was given.
#
# require "ostruct"
#
# person = OpenStruct.new(name: "John", age: 70, pension: 300)
#
# person.delete_field!("age") # => 70
# person # => #<OpenStruct name="John", pension=300>
#
# Setting the value to +nil+ will not remove the attribute:
#
# person.pension = nil
# person # => #<OpenStruct name="John", pension=nil>
#
# person.delete_field('number') # => NameError
#
# person.delete_field('number') { 8675_309 } # => 8675309
#
def delete_field(name, &block)
sym = name.to_sym
begin
singleton_class.remove_method(sym, "#{sym}=")
rescue NameError
end
@table.delete(sym) do
return yield if block
raise! NameError.new("no field `#{sym}' in #{self}", sym)
end
end
InspectKey = :__inspect_key__ # :nodoc:
#
# Returns a string containing a detailed summary of the keys and values.
#
def inspect
ids = (Thread.current[InspectKey] ||= [])
if ids.include?(object_id)
detail = ' ...'
else
ids << object_id
begin
detail = @table.map do |key, value|
" #{key}=#{value.inspect}"
end.join(',')
ensure
ids.pop
end
end
['#<', self.class!, detail, '>'].join
end
alias :to_s :inspect
attr_reader :table # :nodoc:
alias table! table
protected :table!
#
# Compares this object and +other+ for equality. An OpenStruct is equal to
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
# equal.
#
# require "ostruct"
# first_pet = OpenStruct.new("name" => "Rowdy")
# second_pet = OpenStruct.new(:name => "Rowdy")
# third_pet = OpenStruct.new("name" => "Rowdy", :age => nil)
#
# first_pet == second_pet # => true
# first_pet == third_pet # => false
#
def ==(other)
return false unless other.kind_of?(OpenStruct)
@table == other.table!
end
#
# Compares this object and +other+ for equality. An OpenStruct is eql? to
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
# eql?.
#
def eql?(other)
return false unless other.kind_of?(OpenStruct)
@table.eql?(other.table!)
end
# Computes a hash code for this OpenStruct.
def hash # :nodoc:
@table.hash
end
#
# Provides marshalling support for use by the YAML library.
#
def encode_with(coder) # :nodoc:
@table.each_pair do |key, value|
coder[key.to_s] = value
end
if @table.size == 1 && @table.key?(:table) # support for legacy format
# in the very unlikely case of a single entry called 'table'
coder['legacy_support!'] = true # add a bogus second entry
end
end
#
# Provides marshalling support for use by the YAML library.
#
def init_with(coder) # :nodoc:
h = coder.map
if h.size == 1 # support for legacy format
key, val = h.first
if key == 'table'
h = val
end
end
update_to_values!(h)
end
# Make all public methods (builtin or our own) accessible with <code>!</code>:
give_access = instance_methods
# See https://github.com/ruby/ostruct/issues/30
give_access -= %i[instance_exec instance_eval eval] if RUBY_ENGINE == 'jruby'
give_access.each do |method|
next if method.match(/\W$/)
new_name = "#{method}!"
alias_method new_name, method
end
# Other builtin private methods we use:
alias_method :raise!, :raise
private :raise!
# See https://github.com/ruby/ostruct/issues/40
if RUBY_ENGINE != 'jruby'
alias_method :block_given!, :block_given?
private :block_given!
end
end
share/doc/alt-ruby32-libs/NEWS.md 0000644 00000073023 15173547341 0012363 0 ustar 00 # NEWS for Ruby 3.2.0
This document is a list of user-visible feature changes
since the **3.1.0** release, except for bug fixes.
Note that each entry is kept to a minimum, see links for details.
## Language changes
* Anonymous rest and keyword rest arguments can now be passed as
arguments, instead of just used in method parameters.
[[Feature #18351]]
```ruby
def foo(*)
bar(*)
end
def baz(**)
quux(**)
end
```
* A proc that accepts a single positional argument and keywords will
no longer autosplat. [[Bug #18633]]
```ruby
proc{|a, **k| a}.call([1, 2])
# Ruby 3.1 and before
# => 1
# Ruby 3.2 and after
# => [1, 2]
```
* Constant assignment evaluation order for constants set on explicit
objects has been made consistent with single attribute assignment
evaluation order. With this code:
```ruby
foo::BAR = baz
```
`foo` is now called before `baz`. Similarly, for multiple assignments
to constants, left-to-right evaluation order is used. With this
code:
```ruby
foo1::BAR1, foo2::BAR2 = baz1, baz2
```
The following evaluation order is now used:
1. `foo1`
2. `foo2`
3. `baz1`
4. `baz2`
[[Bug #15928]]
* "Find pattern" is no longer experimental.
[[Feature #18585]]
* Methods taking a rest parameter (like `*args`) and wishing to delegate keyword
arguments through `foo(*args)` must now be marked with `ruby2_keywords`
(if not already the case). In other words, all methods wishing to delegate
keyword arguments through `*args` must now be marked with `ruby2_keywords`,
with no exception. This will make it easier to transition to other ways of
delegation once a library can require Ruby 3+. Previously, the `ruby2_keywords`
flag was kept if the receiving method took `*args`, but this was a bug and an
inconsistency. A good technique to find the potentially-missing `ruby2_keywords`
is to run the test suite, for where it fails find the last method which must
receive keyword arguments, use `puts nil, caller, nil` there, and check each
method/block on the call chain which must delegate keywords is correctly marked
as `ruby2_keywords`. [[Bug #18625]] [[Bug #16466]]
```ruby
def target(**kw)
end
# Accidentally worked without ruby2_keywords in Ruby 2.7-3.1, ruby2_keywords
# needed in 3.2+. Just like (*args, **kwargs) or (...) would be needed on
# both #foo and #bar when migrating away from ruby2_keywords.
ruby2_keywords def bar(*args)
target(*args)
end
ruby2_keywords def foo(*args)
bar(*args)
end
foo(k: 1)
```
## Core classes updates
Note: We're only listing outstanding class updates.
* Fiber
* Introduce Fiber.[] and Fiber.[]= for inheritable fiber storage.
Introduce Fiber#storage and Fiber#storage= (experimental) for
getting and resetting the current storage. Introduce
`Fiber.new(storage:)` for setting the storage when creating a
fiber. [[Feature #19078]]
Existing Thread and Fiber local variables can be tricky to use.
Thread-local variables are shared between all fibers, making it
hard to isolate, while Fiber-local variables can be hard to
share. It is often desirable to define unit of execution
("execution context") such that some state is shared between all
fibers and threads created in that context. This is what Fiber
storage provides.
```ruby
def log(message)
puts "#{Fiber[:request_id]}: #{message}"
end
def handle_requests
while request = read_request
Fiber.schedule do
Fiber[:request_id] = SecureRandom.uuid
request.messages.each do |message|
Fiber.schedule do
log("Handling #{message}") # Log includes inherited request_id.
end
end
end
end
end
```
You should generally consider Fiber storage for any state which
you want to be shared implicitly between all fibers and threads
created in a given context, e.g. a connection pool, a request
id, a logger level, environment variables, configuration, etc.
* Fiber::Scheduler
* Introduce `Fiber::Scheduler#io_select` for non-blocking IO.select.
[[Feature #19060]]
* IO
* Introduce IO#timeout= and IO#timeout which can cause
IO::TimeoutError to be raised if a blocking operation exceeds the
specified timeout. [[Feature #18630]]
```ruby
STDIN.timeout = 1
STDIN.read # => Blocking operation timed out! (IO::TimeoutError)
```
* Introduce `IO.new(..., path:)` and promote `File#path` to `IO#path`.
[[Feature #19036]]
* Class
* Class#attached_object, which returns the object for which
the receiver is the singleton class. Raises TypeError if the
receiver is not a singleton class.
[[Feature #12084]]
```ruby
class Foo; end
Foo.singleton_class.attached_object #=> Foo
Foo.new.singleton_class.attached_object #=> #<Foo:0x000000010491a370>
Foo.attached_object #=> TypeError: `Foo' is not a singleton class
nil.singleton_class.attached_object #=> TypeError: `NilClass' is not a singleton class
```
* Data
* New core class to represent simple immutable value object. The class is
similar to Struct and partially shares an implementation, but has more
lean and strict API. [[Feature #16122]]
```ruby
Measure = Data.define(:amount, :unit)
distance = Measure.new(100, 'km') #=> #<data Measure amount=100, unit="km">
weight = Measure.new(amount: 50, unit: 'kg') #=> #<data Measure amount=50, unit="kg">
weight.with(amount: 40) #=> #<data Measure amount=40, unit="kg">
weight.amount #=> 50
weight.amount = 40 #=> NoMethodError: undefined method `amount='
```
* Encoding
* Encoding#replicate has been deprecated and will be removed in 3.3. [[Feature #18949]]
* The dummy `Encoding::UTF_16` and `Encoding::UTF_32` encodings no longer
try to dynamically guess the endian based on a byte order mark.
Use `Encoding::UTF_16BE`/`UTF_16LE` and `Encoding::UTF_32BE`/`UTF_32LE` instead.
This change speeds up getting the encoding of a String. [[Feature #18949]]
* Limit maximum encoding set size by 256.
If exceeding maximum size, `EncodingError` will be raised. [[Feature #18949]]
* Enumerator
* Enumerator.product has been added. Enumerator::Product is the implementation. [[Feature #18685]]
* Exception
* Exception#detailed_message has been added.
The default error printer calls this method on the Exception object
instead of #message. [[Feature #18564]]
* Hash
* Hash#shift now always returns nil if the hash is
empty, instead of returning the default value or
calling the default proc. [[Bug #16908]]
* Integer
* Integer#ceildiv has been added. [[Feature #18809]]
* Kernel
* Kernel#binding raises RuntimeError if called from a non-Ruby frame
(such as a method defined in C). [[Bug #18487]]
* MatchData
* MatchData#byteoffset has been added. [[Feature #13110]]
* MatchData#deconstruct has been added. [[Feature #18821]]
* MatchData#deconstruct_keys has been added. [[Feature #18821]]
* Module
* Module.used_refinements has been added. [[Feature #14332]]
* Module#refinements has been added. [[Feature #12737]]
* Module#const_added has been added. [[Feature #17881]]
* Module#undefined_instance_methods has been added. [[Feature #12655]]
* Proc
* Proc#dup returns an instance of subclass. [[Bug #17545]]
* Proc#parameters now accepts lambda keyword. [[Feature #15357]]
* Process
* Added `RLIMIT_NPTS` constant to FreeBSD platform
* Regexp
* The cache-based optimization is introduced.
Many (but not all) Regexp matching is now in linear time, which
will prevent regular expression denial of service (ReDoS)
vulnerability. [[Feature #19104]]
* Regexp.linear_time? is introduced. [[Feature #19194]]
* Regexp.new now supports passing the regexp flags not only as an Integer,
but also as a String. Unknown flags raise ArgumentError.
Otherwise, anything other than `true`, `false`, `nil` or Integer will be warned.
[[Feature #18788]]
* Regexp.timeout= has been added. Also, Regexp.new new supports timeout keyword.
See [[Feature #17837]]
* Refinement
* Refinement#refined_class has been added. [[Feature #12737]]
* RubyVM::AbstractSyntaxTree
* Add `error_tolerant` option for `parse`, `parse_file` and `of`. [[Feature #19013]]
With this option
1. SyntaxError is suppressed
2. AST is returned for invalid input
3. `end` is complemented when a parser reaches to the end of input but `end` is insufficient
4. `end` is treated as keyword based on indent
```ruby
# Without error_tolerant option
root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY)
def m
a = 10
if
end
RUBY
# => <internal:ast>:33:in `parse': syntax error, unexpected `end' (SyntaxError)
# With error_tolerant option
root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
def m
a = 10
if
end
RUBY
p root # => #<RubyVM::AbstractSyntaxTree::Node:SCOPE@1:0-4:3>
# `end` is treated as keyword based on indent
root = RubyVM::AbstractSyntaxTree.parse(<<~RUBY, error_tolerant: true)
module Z
class Foo
foo.
end
def bar
end
end
RUBY
p root.children[-1].children[-1].children[-1].children[-2..-1]
# => [#<RubyVM::AbstractSyntaxTree::Node:CLASS@2:2-4:5>, #<RubyVM::AbstractSyntaxTree::Node:DEFN@6:2-7:5>]
```
* Add `keep_tokens` option for `parse`, `parse_file` and `of`. Add `#tokens` and `#all_tokens`
for RubyVM::AbstractSyntaxTree::Node [[Feature #19070]]
```ruby
root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true)
root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...]
root.tokens.map{_1[2]}.join # => "x = 1 + 2"
```
* Set
* Set is now available as a built-in class without the need for `require "set"`. [[Feature #16989]]
It is currently autoloaded via the Set constant or a call to Enumerable#to_set.
* String
* String#byteindex and String#byterindex have been added. [[Feature #13110]]
* Update Unicode to Version 15.0.0 and Emoji Version 15.0. [[Feature #18639]]
(also applies to Regexp)
* String#bytesplice has been added. [[Feature #18598]]
* String#dedup has been added as an alias to String#-@. [[Feature #18595]]
* Struct
* A Struct class can also be initialized with keyword arguments
without `keyword_init: true` on Struct.new [[Feature #16806]]
```ruby
Post = Struct.new(:id, :name)
Post.new(1, "hello") #=> #<struct Post id=1, name="hello">
# From Ruby 3.2, the following code also works without keyword_init: true.
Post.new(id: 1, name: "hello") #=> #<struct Post id=1, name="hello">
```
* Thread
* Thread.each_caller_location is added. [[Feature #16663]]
* Thread::Queue
* Thread::Queue#pop(timeout: sec) is added. [[Feature #18774]]
* Thread::SizedQueue
* Thread::SizedQueue#pop(timeout: sec) is added. [[Feature #18774]]
* Thread::SizedQueue#push(timeout: sec) is added. [[Feature #18944]]
* Time
* Time#deconstruct_keys is added, allowing to use Time instances
in pattern-matching expressions [[Feature #19071]]
* Time.new now can parse a string like generated by Time#inspect
and return a Time instance based on the given argument.
[[Feature #18033]]
* SyntaxError
* SyntaxError#path has been added. [[Feature #19138]]
* TracePoint
* TracePoint#binding now returns `nil` for `c_call`/`c_return` TracePoints.
[[Bug #18487]]
* TracePoint#enable `target_thread` keyword argument now defaults to the
current thread if a block is given and `target` and `target_line` keyword
arguments are not passed. [[Bug #16889]]
* UnboundMethod
* `UnboundMethod#==` returns `true` if the actual method is same. For example,
`String.instance_method(:object_id) == Array.instance_method(:object_id)`
returns `true`. [[Feature #18798]]
* `UnboundMethod#inspect` does not show the receiver of `instance_method`.
For example `String.instance_method(:object_id).inspect` returns
`"#<UnboundMethod: Kernel#object_id()>"`
(was `"#<UnboundMethod: String(Kernel)#object_id()>"`).
* GC
* Expose `need_major_gc` via `GC.latest_gc_info`. [GH-6791]
* ObjectSpace
* `ObjectSpace.dump_all` dump shapes as well. [GH-6868]
## Stdlib updates
* Bundler
* Bundler now uses [PubGrub] resolver instead of [Molinillo] for performance improvement.
* Add --ext=rust support to bundle gem for creating simple gems with Rust extensions.
[[GH-rubygems-6149]]
* Make cloning git repos faster [[GH-rubygems-4475]]
* RubyGems
* Add mswin support for cargo builder. [[GH-rubygems-6167]]
* CGI
* `CGI.escapeURIComponent` and `CGI.unescapeURIComponent` are added.
[[Feature #18822]]
* Coverage
* `Coverage.setup` now accepts `eval: true`. By this, `eval` and related methods are
able to generate code coverage. [[Feature #19008]]
* `Coverage.supported?(mode)` enables detection of what coverage modes are
supported. [[Feature #19026]]
* Date
* Added `Date#deconstruct_keys` and `DateTime#deconstruct_keys` same as [[Feature #19071]]
* ERB
* `ERB::Util.html_escape` is made faster than `CGI.escapeHTML`.
* It no longer allocates a String object when no character needs to be escaped.
* It skips calling `#to_s` method when an argument is already a String.
* `ERB::Escape.html_escape` is added as an alias to `ERB::Util.html_escape`,
which has not been monkey-patched by Rails.
* `ERB::Util.url_encode` is made faster using `CGI.escapeURIComponent`.
* `-S` option is removed from `erb` command.
* FileUtils
* Add FileUtils.ln_sr method and `relative:` option to FileUtils.ln_s.
[[Feature #18925]]
* IRB
* debug.gem integration commands have been added: `debug`, `break`, `catch`,
`next`, `delete`, `step`, `continue`, `finish`, `backtrace`, `info`
* They work even if you don't have `gem "debug"` in your Gemfile.
* See also: [What's new in Ruby 3.2's IRB?](https://st0012.dev/whats-new-in-ruby-3-2-irb)
* More Pry-like commands and features have been added.
* `edit` and `show_cmds` (like Pry's `help`) are added.
* `ls` takes `-g` or `-G` option to filter out outputs.
* `show_source` is aliased from `$` and accepts unquoted inputs.
* `whereami` is aliased from `@`.
* Net::Protocol
* Improve `Net::BufferedIO` performance. [[GH-net-protocol-14]]
* Pathname
* Added `Pathname#lutime`. [[GH-pathname-20]]
* Socket
* Added the following constants for supported platforms.
* `SO_INCOMING_CPU`
* `SO_INCOMING_NAPI_ID`
* `SO_RTABLE`
* `SO_SETFIB`
* `SO_USER_COOKIE`
* `TCP_KEEPALIVE`
* `TCP_CONNECTION_INFO`
* SyntaxSuggest
* The feature of `syntax_suggest` formerly `dead_end` is integrated in Ruby.
[[Feature #18159]]
* UNIXSocket
* Add support for UNIXSocket on Windows. Emulate anonymous sockets. Add
support for File.socket? and File::Stat#socket? where possible.
[[Feature #19135]]
* The following default gems are updated.
* RubyGems 3.4.1
* abbrev 0.1.1
* benchmark 0.2.1
* bigdecimal 3.1.3
* bundler 2.4.1
* cgi 0.3.6
* csv 3.2.6
* date 3.3.3
* delegate 0.3.0
* did_you_mean 1.6.3
* digest 3.1.1
* drb 2.1.1
* english 0.7.2
* erb 4.0.2
* error_highlight 0.5.1
* etc 1.4.2
* fcntl 1.0.2
* fiddle 1.1.1
* fileutils 1.7.0
* forwardable 1.3.3
* getoptlong 0.2.0
* io-console 0.6.0
* io-nonblock 0.2.0
* io-wait 0.3.0
* ipaddr 1.2.5
* irb 1.6.2
* json 2.6.3
* logger 1.5.3
* mutex_m 0.1.2
* net-http 0.4.0
* net-protocol 0.2.1
* nkf 0.1.2
* open-uri 0.3.0
* open3 0.1.2
* openssl 3.1.0
* optparse 0.3.1
* ostruct 0.5.5
* pathname 0.2.1
* pp 0.4.0
* pstore 0.1.2
* psych 5.0.1
* racc 1.6.2
* rdoc 6.5.0
* readline-ext 0.1.5
* reline 0.3.2
* resolv 0.2.2
* resolv-replace 0.1.1
* securerandom 0.2.2
* set 1.0.3
* stringio 3.0.4
* strscan 3.0.5
* syntax_suggest 1.0.2
* syslog 0.1.1
* tempfile 0.1.3
* time 0.2.1
* timeout 0.3.1
* tmpdir 0.1.3
* tsort 0.1.1
* un 0.2.1
* uri 0.12.0
* weakref 0.1.2
* win32ole 1.8.9
* yaml 0.2.1
* zlib 3.0.0
* The following bundled gems are updated.
* minitest 5.16.3
* power_assert 2.0.3
* test-unit 3.5.7
* net-ftp 0.2.0
* net-imap 0.3.4
* net-pop 0.1.2
* net-smtp 0.3.3
* rbs 2.8.2
* typeprof 0.21.3
* debug 1.7.1
See GitHub releases like [GitHub Releases of Logger](https://github.com/ruby/logger/releases) or changelog for details of the default gems or bundled gems.
## Supported platforms
* WebAssembly/WASI is added. See [wasm/README.md] and [ruby.wasm] for more details. [[Feature #18462]]
## Compatibility issues
* `String#to_c` currently treat a sequence of underscores as an end of Complex
string. [[Bug #19087]]
* Now `ENV.clone` raises `TypeError` as well as `ENV.dup` [[Bug #17767]]
### Removed constants
The following deprecated constants are removed.
* `Fixnum` and `Bignum` [[Feature #12005]]
* `Random::DEFAULT` [[Feature #17351]]
* `Struct::Group`
* `Struct::Passwd`
### Removed methods
The following deprecated methods are removed.
* `Dir.exists?` [[Feature #17391]]
* `File.exists?` [[Feature #17391]]
* `Kernel#=~` [[Feature #15231]]
* `Kernel#taint`, `Kernel#untaint`, `Kernel#tainted?`
[[Feature #16131]]
* `Kernel#trust`, `Kernel#untrust`, `Kernel#untrusted?`
[[Feature #16131]]
* `Method#public?`, `Method#private?`, `Method#protected?`,
`UnboundMethod#public?`, `UnboundMethod#private?`, `UnboundMethod#protected?`
[[Bug #18729]] [[Bug #18751]] [[Bug #18435]]
### Source code incompatibility of extension libraries
* Extension libraries provide PRNG, subclasses of Random, need updates.
See [PRNG update] below for more information. [[Bug #19100]]
### Error printer
* Ruby no longer escapes control characters and backslashes in an
error message. [[Feature #18367]]
### Constant lookup when defining a class/module
* When defining a class/module directly under the Object class by class/module
statement, if there is already a class/module defined by `Module#include`
with the same name, the statement was handled as "open class" in Ruby 3.1 or before.
Since Ruby 3.2, a new class is defined instead. [[Feature #18832]]
## Stdlib compatibility issues
* Psych no longer bundles libyaml sources.
And also Fiddle no longer bundles libffi sources.
Users need to install the libyaml/libffi library themselves via the package
manager like apt, yum, brew, etc.
Psych and fiddle supported the static build with specific version of libyaml
and libffi sources. You can build psych with libyaml-0.2.5 like this.
```bash
$ ./configure --with-libyaml-source-dir=/path/to/libyaml-0.2.5
```
And you can build fiddle with libffi-3.4.4 like this.
```bash
$ ./configure --with-libffi-source-dir=/path/to/libffi-3.4.4
```
[[Feature #18571]]
* Check cookie name/path/domain characters in `CGI::Cookie`. [[CVE-2021-33621]]
* `URI.parse` return empty string in host instead of nil. [[sec-156615]]
## C API updates
### Updated C APIs
The following APIs are updated.
* PRNG update
`rb_random_interface_t` in ruby/random.h updated and versioned.
Extension libraries which use this interface and built for older
versions need to rebuild with adding `init_int32` function.
### Added C APIs
* `VALUE rb_hash_new_capa(long capa)` was added to created hashes with the desired capacity.
* `rb_internal_thread_add_event_hook` and `rb_internal_thread_add_event_hook` were added to instrument threads scheduling.
The following events are available:
* `RUBY_INTERNAL_THREAD_EVENT_STARTED`
* `RUBY_INTERNAL_THREAD_EVENT_READY`
* `RUBY_INTERNAL_THREAD_EVENT_RESUMED`
* `RUBY_INTERNAL_THREAD_EVENT_SUSPENDED`
* `RUBY_INTERNAL_THREAD_EVENT_EXITED`
* `rb_debug_inspector_current_depth` and `rb_debug_inspector_frame_depth` are added for debuggers.
### Removed C APIs
The following deprecated APIs are removed.
* `rb_cData` variable.
* "taintedness" and "trustedness" functions. [[Feature #16131]]
## Implementation improvements
* Fixed several race conditions in Kernel#autoload. [[Bug #18782]]
* Cache invalidation for expressions referencing constants is now
more fine-grained. `RubyVM.stat(:global_constant_state)` was
removed because it was closely tied to the previous caching scheme
where setting any constant invalidates all caches in the system.
New keys, `:constant_cache_invalidations` and `:constant_cache_misses`,
were introduced to help with use cases for `:global_constant_state`.
[[Feature #18589]]
* The cache-based optimization for Regexp matching is introduced.
[[Feature #19104]]
* [Variable Width Allocation](https://shopify.engineering/ruby-variable-width-allocation)
is now enabled by default. [[Feature #18239]]
* Added a new instance variable caching mechanism, called object shapes, which
improves inline cache hits for most objects and allows us to generate very
efficient JIT code. Objects whose instance variables are defined in a
consistent order will see the most performance benefits.
[[Feature #18776]]
* Speed up marking instruction sequences by using a bitmap to find "markable"
objects. This change results in faster major collections.
[[Feature #18875]]
## JIT
### YJIT
* YJIT is no longer experimental
* Has been tested on production workloads for over a year and proven to be quite stable.
* YJIT now supports both x86-64 and arm64/aarch64 CPUs on Linux, MacOS, BSD and other UNIX platforms.
* This release brings support for Mac M1/M2, AWS Graviton and Raspberry Pi 4.
* Building YJIT now requires Rust 1.58.0+. [[Feature #18481]]
* In order to ensure that CRuby is built with YJIT, please install `rustc` >= 1.58.0
before running `./configure`
* Please reach out to the YJIT team should you run into any issues.
* Physical memory for JIT code is lazily allocated. Unlike Ruby 3.1,
the RSS of a Ruby process is minimized because virtual memory pages
allocated by `--yjit-exec-mem-size` will not be mapped to physical
memory pages until actually utilized by JIT code.
* Introduce Code GC that frees all code pages when the memory consumption
by JIT code reaches `--yjit-exec-mem-size`.
* `RubyVM::YJIT.runtime_stats` returns Code GC metrics in addition to
existing `inline_code_size` and `outlined_code_size` keys:
`code_gc_count`, `live_page_count`, `freed_page_count`, and `freed_code_size`.
* Most of the statistics produced by `RubyVM::YJIT.runtime_stats` are now available in release builds.
* Simply run ruby with `--yjit-stats` to compute and dump stats (incurs some run-time overhead).
* YJIT is now optimized to take advantage of object shapes. [[Feature #18776]]
* Take advantage of finer-grained constant invalidation to invalidate less code when defining new constants. [[Feature #18589]]
* The default `--yjit-exec-mem-size` is changed to 64 (MiB).
* The default `--yjit-call-threshold` is changed to 30.
### MJIT
* The MJIT compiler is re-implemented in Ruby as `ruby_vm/mjit/compiler`.
* MJIT compiler is executed under a forked Ruby process instead of
doing it in a native thread called MJIT worker. [[Feature #18968]]
* As a result, Microsoft Visual Studio (MSWIN) is no longer supported.
* MinGW is no longer supported. [[Feature #18824]]
* Rename `--mjit-min-calls` to `--mjit-call-threshold`.
* Change default `--mjit-max-cache` back from 10000 to 100.
[Feature #12005]: https://bugs.ruby-lang.org/issues/12005
[Feature #12084]: https://bugs.ruby-lang.org/issues/12084
[Feature #12655]: https://bugs.ruby-lang.org/issues/12655
[Feature #12737]: https://bugs.ruby-lang.org/issues/12737
[Feature #13110]: https://bugs.ruby-lang.org/issues/13110
[Feature #14332]: https://bugs.ruby-lang.org/issues/14332
[Feature #15231]: https://bugs.ruby-lang.org/issues/15231
[Feature #15357]: https://bugs.ruby-lang.org/issues/15357
[Bug #15928]: https://bugs.ruby-lang.org/issues/15928
[Feature #16122]: https://bugs.ruby-lang.org/issues/16122
[Feature #16131]: https://bugs.ruby-lang.org/issues/16131
[Bug #16466]: https://bugs.ruby-lang.org/issues/16466
[Feature #16663]: https://bugs.ruby-lang.org/issues/16663
[Feature #16806]: https://bugs.ruby-lang.org/issues/16806
[Bug #16889]: https://bugs.ruby-lang.org/issues/16889
[Bug #16908]: https://bugs.ruby-lang.org/issues/16908
[Feature #16989]: https://bugs.ruby-lang.org/issues/16989
[Feature #17351]: https://bugs.ruby-lang.org/issues/17351
[Feature #17391]: https://bugs.ruby-lang.org/issues/17391
[Bug #17545]: https://bugs.ruby-lang.org/issues/17545
[Bug #17767]: https://bugs.ruby-lang.org/issues/17767
[Feature #17837]: https://bugs.ruby-lang.org/issues/17837
[Feature #17881]: https://bugs.ruby-lang.org/issues/17881
[Feature #18033]: https://bugs.ruby-lang.org/issues/18033
[Feature #18159]: https://bugs.ruby-lang.org/issues/18159
[Feature #18239]: https://bugs.ruby-lang.org/issues/18239#note-17
[Feature #18351]: https://bugs.ruby-lang.org/issues/18351
[Feature #18367]: https://bugs.ruby-lang.org/issues/18367
[Bug #18435]: https://bugs.ruby-lang.org/issues/18435
[Feature #18462]: https://bugs.ruby-lang.org/issues/18462
[Feature #18481]: https://bugs.ruby-lang.org/issues/18481
[Bug #18487]: https://bugs.ruby-lang.org/issues/18487
[Feature #18564]: https://bugs.ruby-lang.org/issues/18564
[Feature #18571]: https://bugs.ruby-lang.org/issues/18571
[Feature #18585]: https://bugs.ruby-lang.org/issues/18585
[Feature #18589]: https://bugs.ruby-lang.org/issues/18589
[Feature #18595]: https://bugs.ruby-lang.org/issues/18595
[Feature #18598]: https://bugs.ruby-lang.org/issues/18598
[Bug #18625]: https://bugs.ruby-lang.org/issues/18625
[Feature #18630]: https://bugs.ruby-lang.org/issues/18630
[Bug #18633]: https://bugs.ruby-lang.org/issues/18633
[Feature #18639]: https://bugs.ruby-lang.org/issues/18639
[Feature #18685]: https://bugs.ruby-lang.org/issues/18685
[Bug #18729]: https://bugs.ruby-lang.org/issues/18729
[Bug #18751]: https://bugs.ruby-lang.org/issues/18751
[Feature #18774]: https://bugs.ruby-lang.org/issues/18774
[Feature #18776]: https://bugs.ruby-lang.org/issues/18776
[Bug #18782]: https://bugs.ruby-lang.org/issues/18782
[Feature #18788]: https://bugs.ruby-lang.org/issues/18788
[Feature #18798]: https://bugs.ruby-lang.org/issues/18798
[Feature #18809]: https://bugs.ruby-lang.org/issues/18809
[Feature #18821]: https://bugs.ruby-lang.org/issues/18821
[Feature #18822]: https://bugs.ruby-lang.org/issues/18822
[Feature #18824]: https://bugs.ruby-lang.org/issues/18824
[Feature #18832]: https://bugs.ruby-lang.org/issues/18832
[Feature #18875]: https://bugs.ruby-lang.org/issues/18875
[Feature #18925]: https://bugs.ruby-lang.org/issues/18925
[Feature #18944]: https://bugs.ruby-lang.org/issues/18944
[Feature #18949]: https://bugs.ruby-lang.org/issues/18949
[Feature #18968]: https://bugs.ruby-lang.org/issues/18968
[Feature #19008]: https://bugs.ruby-lang.org/issues/19008
[Feature #19013]: https://bugs.ruby-lang.org/issues/19013
[Feature #19026]: https://bugs.ruby-lang.org/issues/19026
[Feature #19036]: https://bugs.ruby-lang.org/issues/19036
[Feature #19060]: https://bugs.ruby-lang.org/issues/19060
[Feature #19070]: https://bugs.ruby-lang.org/issues/19070
[Feature #19071]: https://bugs.ruby-lang.org/issues/19071
[Feature #19078]: https://bugs.ruby-lang.org/issues/19078
[Bug #19087]: https://bugs.ruby-lang.org/issues/19087
[Bug #19100]: https://bugs.ruby-lang.org/issues/19100
[Feature #19104]: https://bugs.ruby-lang.org/issues/19104
[Feature #19135]: https://bugs.ruby-lang.org/issues/19135
[Feature #19138]: https://bugs.ruby-lang.org/issues/19138
[Feature #19194]: https://bugs.ruby-lang.org/issues/19194
[Molinillo]: https://github.com/CocoaPods/Molinillo
[PubGrub]: https://github.com/jhawthorn/pub_grub
[GH-net-protocol-14]: https://github.com/ruby/net-protocol/pull/14
[GH-pathname-20]: https://github.com/ruby/pathname/pull/20
[GH-6791]: https://github.com/ruby/ruby/pull/6791
[GH-6868]: https://github.com/ruby/ruby/pull/6868
[GH-rubygems-4475]: https://github.com/rubygems/rubygems/pull/4475
[GH-rubygems-6149]: https://github.com/rubygems/rubygems/pull/6149
[GH-rubygems-6167]: https://github.com/rubygems/rubygems/pull/6167
[sec-156615]: https://hackerone.com/reports/156615
[CVE-2021-33621]: https://www.ruby-lang.org/en/news/2022/11/22/http-response-splitting-in-cgi-cve-2021-33621/
[wasm/README.md]: https://github.com/ruby/ruby/blob/master/wasm/README.md
[ruby.wasm]: https://github.com/ruby/ruby.wasm
share/doc/alt-ruby32-libs/README.md 0000644 00000006712 15173547341 0012545 0 ustar 00 [](https://github.com/ruby/ruby/actions?query=workflow%3A"MinGW")
[](https://github.com/ruby/ruby/actions?query=workflow%3A"MJIT")
[](https://github.com/ruby/ruby/actions?query=workflow%3A"Ubuntu")
[](https://github.com/ruby/ruby/actions?query=workflow%3A"Windows")
[](https://ci.appveyor.com/project/ruby/ruby/branch/master)
[](https://app.travis-ci.com/ruby/ruby)
# What is Ruby?
Ruby is an interpreted object-oriented programming language often
used for web development. It also offers many scripting features
to process plain text and serialized files, or manage system tasks.
It is simple, straightforward, and extensible.
## Features of Ruby
* Simple Syntax
* **Normal** Object-oriented Features (e.g. class, method calls)
* **Advanced** Object-oriented Features (e.g. mix-in, singleton-method)
* Operator Overloading
* Exception Handling
* Iterators and Closures
* Garbage Collection
* Dynamic Loading of Object Files (on some architectures)
* Highly Portable (works on many Unix-like/POSIX compatible platforms as
well as Windows, macOS, etc.) cf.
https://github.com/ruby/ruby/blob/master/doc/maintainers.rdoc#label-Platform+Maintainers
## How to get Ruby with Git
For a complete list of ways to install Ruby, including using third-party tools
like rvm, see:
https://www.ruby-lang.org/en/downloads/
The mirror of the Ruby source tree can be checked out with the following command:
$ git clone https://github.com/ruby/ruby.git
There are some other branches under development. Try the following command
to see the list of branches:
$ git ls-remote https://github.com/ruby/ruby.git
You may also want to use https://git.ruby-lang.org/ruby.git (actual master of Ruby source)
if you are a committer.
## How to build
see [Building Ruby](doc/contributing/building_ruby.md)
## Ruby home page
https://www.ruby-lang.org/
## Documentation
- [English](https://docs.ruby-lang.org/en/master/index.html)
- [Japanese](https://docs.ruby-lang.org/ja/master/index.html)
## Mailing list
There is a mailing list to discuss Ruby. To subscribe to this list, please
send the following phrase:
subscribe
in the mail body (not subject) to the address [ruby-talk-request@ruby-lang.org].
[ruby-talk-request@ruby-lang.org]: mailto:ruby-talk-request@ruby-lang.org?subject=Join%20Ruby%20Mailing%20List&body=subscribe
## Copying
See the file [COPYING](rdoc-ref:COPYING).
## Feedback
Questions about the Ruby language can be asked on the [Ruby-Talk](https://www.ruby-lang.org/en/community/mailing-lists) mailing list
or on websites like https://stackoverflow.com.
Bugs should be reported at https://bugs.ruby-lang.org. Read ["Reporting Issues"](https://docs.ruby-lang.org/en/master/contributing/reporting_issues_md.html) for more information.
## Contributing
See ["Contributing to Ruby"](https://docs.ruby-lang.org/en/master/contributing_md.html), which includes setup and build instructions.
## The Author
Ruby was originally designed and developed by Yukihiro Matsumoto (Matz) in 1995.
<matz@ruby-lang.org>
share/licenses/alt-ruby32/COPYING 0000644 00000004573 15173547341 0012435 0 ustar 00 Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
You can redistribute it and/or modify it under either the terms of the
2-clause BSDL (see the file BSDL), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or binary form,
provided that you do at least ONE of the following:
a. distribute the binaries and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under these terms.
For the list of those files and their copying conditions, see the
file LEGAL.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
share/licenses/alt-ruby32/COPYING.ja 0000644 00000004776 15173547341 0013033 0 ustar 00 本プログラムはフリーソフトウェアです.2-clause BSDL
または以下に示す条件で本プログラムを再配布できます
2-clause BSDLについてはBSDLファイルを参照して下さい.
1. 複製は制限なく自由です.
2. 以下の条件のいずれかを満たす時に本プログラムのソースを
自由に変更できます.
a. ネットニューズにポストしたり,作者に変更を送付する
などの方法で,変更を公開する.
b. 変更した本プログラムを自分の所属する組織内部だけで
使う.
c. 変更点を明示したうえ,ソフトウェアの名前を変更する.
そのソフトウェアを配布する時には変更前の本プログラ
ムも同時に配布する.または変更前の本プログラムのソー
スの入手法を明示する.
d. その他の変更条件を作者と合意する.
3. 以下の条件のいずれかを満たす時に本プログラムをコンパイ
ルしたオブジェクトコードや実行形式でも配布できます.
a. バイナリを受け取った人がソースを入手できるように,
ソースの入手法を明示する.
b. 機械可読なソースコードを添付する.
c. 変更を行ったバイナリは名前を変更したうえ,オリジナ
ルのソースコードの入手法を明示する.
d. その他の配布条件を作者と合意する.
4. 他のプログラムへの引用はいかなる目的であれ自由です.た
だし,本プログラムに含まれる他の作者によるコードは,そ
れぞれの作者の意向による制限が加えられる場合があります.
それらファイルの一覧とそれぞれの配布条件などに付いては
LEGALファイルを参照してください.
5. 本プログラムへの入力となるスクリプトおよび,本プログラ
ムからの出力の権利は本プログラムの作者ではなく,それぞ
れの入出力を生成した人に属します.また,本プログラムに
組み込まれるための拡張ライブラリについても同様です.
6. 本プログラムは無保証です.作者は本プログラムをサポート
する意志はありますが,プログラム自身のバグあるいは本プ
ログラムの実行などから発生するいかなる損害に対しても責
任を持ちません.
share/licenses/alt-ruby32/BSDL 0000644 00000002413 15173547341 0012040 0 ustar 00 Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
share/licenses/alt-ruby32/LEGAL 0000644 00000127612 15173547341 0012151 0 ustar 00 # -*- rdoc -*-
= LEGAL NOTICE INFORMATION
--------------------------
All the files in this distribution are covered under either the Ruby's
license (see the file COPYING) or public-domain except some files
mentioned below.
[addr2line.c]
A part of this file is from FreeBSD.
>>>
Copyright (c) 1986, 1988, 1991, 1993::
The Regents of the University of California. All rights reserved.
(c) UNIX System Laboratories, Inc.
All or some portions of this file are derived from material licensed
to the University of California by American Telephone and Telegraph
Co. or Unix System Laboratories, Inc. and are reproduced herein with
the permission of UNIX System Laboratories, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
4. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
@(#)subr_prf.c 8.3 (Berkeley) 1/21/94
[ccan/build_assert/build_assert.h]
[ccan/check_type/check_type.h]
[ccan/container_of/container_of.h]
[ccan/str/str.h]
These files are licensed under the {CC0}[https://creativecommons.org/choose/zero/].
[ccan/list/list.h]
This file is licensed under the {MIT License}[rdoc-label:label-MIT+License].
[coroutine]
Unless otherwise specified, these files are licensed under the
{MIT License}[rdoc-label:label-MIT+License].
[include/ruby/onigmo.h]
[include/ruby/oniguruma.h]
[regcomp.c]
[regenc.c]
[regenc.h]
[regerror.c]
[regexec.c]
[regint.h]
[regparse.c]
[regparse.h]
[enc/ascii.c]
[enc/big5.c]
[enc/cp949.c]
[enc/emacs_mule.c]
[enc/encdb.c]
[enc/euc_jp.c]
[enc/euc_kr.c]
[enc/euc_tw.c]
[enc/gb18030.c]
[enc/gb2312.c]
[enc/gbk.c]
[enc/iso_8859_1.c]
[enc/iso_8859_10.c]
[enc/iso_8859_11.c]
[enc/iso_8859_13.c]
[enc/iso_8859_14.c]
[enc/iso_8859_15.c]
[enc/iso_8859_16.c]
[enc/iso_8859_2.c]
[enc/iso_8859_3.c]
[enc/iso_8859_4.c]
[enc/iso_8859_5.c]
[enc/iso_8859_6.c]
[enc/iso_8859_7.c]
[enc/iso_8859_8.c]
[enc/iso_8859_9.c]
[enc/koi8_r.c]
[enc/koi8_u.c]
[enc/shift_jis.c]
[enc/unicode.c]
[enc/us_ascii.c]
[enc/utf_16be.c]
[enc/utf_16le.c]
[enc/utf_32be.c]
[enc/utf_32le.c]
[enc/utf_8.c]
[enc/windows_1251.c]
[enc/windows_31j.c]
Onigmo (Oniguruma-mod) LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
Copyright (c) 2011-2014:: K.Takata <kentkt AT csc DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Oniguruma LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
* https://github.com/k-takata/Onigmo/
* https://github.com/kkos/oniguruma
* https://svnweb.freebsd.org/ports/head/devel/oniguruma/
When this software is partly used or it is distributed with Ruby,
this of Ruby follows the license of Ruby.
[enc/windows_1250.c]
[enc/windows_1252.c]
>>>
Copyright (c) 2006-2007:: Byte <byte AT mail DOT kna DOT ru>
K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/cesu_8.c]
[enc/windows_1253.c]
[enc/windows_1254.c]
[enc/windows_1257.c]
>>>
Copyright (c) 2002-2007:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/trans/GB/GB12345%UCS.src]
[enc/trans/GB/UCS%GB12345.src]
[enc/trans/GB/GB2312%UCS.src]
[enc/trans/GB/UCS%GB2312.src]
These files have this explanatory texts.
>>>
This mapping data was created from files provided by Unicode, Inc.
(The Unicode Consortium). The files were used to create a product supporting
Unicode, as explicitly permitted in the files' copyright notices.
Please note that Unicode, Inc. never made any claims as to fitness of these
files for any particular purpose, and has ceased to publish the files many
years ago.
[enc/trans/JIS/JISX0201-KANA%UCS.src]
[enc/trans/JIS/JISX0208\@1990%UCS.src]
[enc/trans/JIS/JISX0212%UCS.src]
[enc/trans/JIS/UCS%JISX0201-KANA.src]
[enc/trans/JIS/UCS%JISX0208@1990.src]
[enc/trans/JIS/UCS%JISX0212.src]
These files are copyrighted as the following.
>>>
© 2015 Unicode®, Inc.
For terms of use, see http://www.unicode.org/terms_of_use.html
[enc/trans/JIS/JISX0213-1%UCS@BMP.src]
[enc/trans/JIS/JISX0213-1%UCS@SIP.src]
[enc/trans/JIS/JISX0213-2%UCS@BMP.src]
[enc/trans/JIS/JISX0213-2%UCS@SIP.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
Copyright (C) 2006:: Project X0213, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/JIS/UCS@BMP%JISX0213-1.src]
[enc/trans/JIS/UCS@BMP%JISX0213-2.src]
[enc/trans/JIS/UCS@SIP%JISX0213-1.src]
[enc/trans/JIS/UCS@SIP%JISX0213-2.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/ucm/glibc-BIG5-2.3.3.ucm]
[enc/trans/ucm/glibc-BIG5HKSCS-2.3.3.ucm]
>>>
Copyright (C) 2001-2005:: International Business Machines
Corporation and others. All Rights Reserved.
[enc/trans/ucm/windows-950-2000.ucm]
[enc/trans/ucm/windows-950_hkscs-2001.ucm]
>>>
Copyright (C) 2001-2002:: International Business Machines
Corporation and others. All Rights Reserved.
[configure]
This file is free software.
>>>
Copyright (C) 1992-1996, 1998-2012:: Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
[tool/config.guess]
[tool/config.sub]
As long as you distribute these files with the file configure, they
are covered under the Ruby's license.
>>>
Copyright 1992-2018:: Free Software Foundation, Inc.
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
As a special exception to the GNU General Public License, if you
distribute this file as part of a program that contains a
configuration script generated by Autoconf, you may include it under
the same distribution terms that you use for the rest of that
program. This Exception is an additional permission under section 7
of the GNU General Public License, version 3 ("GPLv3").
[tool/lib/test/*]
[tool/lib/core_assertions.rb]
Some of methods on these files are based on MiniTest 4. MiniTest 4 is
distributed under the MIT License.
>>>
Copyright (c) Ryan Davis, seattle.rb
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[parse.c]
[parse.h]
These files are licensed under the GPL, but are incorporated into Ruby and
redistributed under the terms of the Ruby license, as permitted by the
exception to the GPL below.
>>>
Copyright (C) 1984, 1989-1990, 2000-2015, 2018:: Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison.
[missing/dtoa.c]
This file is under these licenses.
>>>
Copyright (c) 1991, 2000, 2001:: by Lucent Technologies.
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
>>>
Copyright (c) 2004-2008:: David Schultz <das@FreeBSD.ORG>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[win32/win32.c]
[include/ruby/win32.h]
You can apply the Artistic License to these files. (or GPL,
alternatively)
>>>
Copyright (c) 1993:: Intergraph Corporation
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the perl README file.
[missing/mt19937.c]
This file is under the new-style BSD license.
>>>
A C-program for MT19937, with initialization improved 2002/2/10.::
Coded by Takuji Nishimura and Makoto Matsumoto.
This is a faster version by taking Shawn Cokus's optimization,
Matthe Bellew's simplification, Isaku Wada's real version.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002:: Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.keio.ac.jp/matumoto/emt.html
email: matumoto@math.keio.ac.jp
The Wayback Machine url: http://web.archive.org/web/19990429082237/http://www.math.keio.ac.jp/matumoto/emt.html
[missing/procstat_vm.c]
This file is under the new-style BSD license.
>>>
Copyright (c) 2007:: Robert N. M. Watson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
$FreeBSD: head/usr.bin/procstat/procstat_vm.c 261780 2014-02-11 21:57:37Z jhb $
[vsnprintf.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1990, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Chris Torek.
[st.c]
[strftime.c]
[include/ruby/st.h]
[missing/acosh.c]
[missing/alloca.c]
[missing/erf.c]
[missing/hypot.c]
[missing/lgamma_r.c]
[missing/memcmp.c]
[missing/memmove.c]
[missing/strchr.c]
[missing/strerror.c]
[missing/strstr.c]
[missing/tgamma.c]
[ext/date/date_strftime.c]
[ext/digest/sha1/sha1.c]
[ext/digest/sha1/sha1.h]
These files are all under public domain.
[missing/crypt.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1989, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Tom Truscott.
[missing/setproctitle.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright 2003:: Damien Miller
Copyright (c) 1983, 1995-1997:: Eric P. Allman
Copyright (c) 1988, 1993::
The Regents of the University of California. All rights reserved.
[missing/strlcat.c]
[missing/strlcpy.c]
These files are under an ISC-style license.
>>>
Copyright (c) 1998, 2015:: Todd C. Miller <Todd.Miller@courtesan.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
[missing/langinfo.c]
This file is from http://www.cl.cam.ac.uk/~mgk25/ucs/langinfo.c.
Ruby uses a modified version. The file contains the following
author/copyright notice:
>>>
Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11::
Permission to use, copy, modify, and distribute this software
for any purpose and without fee is hereby granted. The author
disclaims all warranties with regard to this software.
[ext/digest/md5/md5.c]
[ext/digest/md5/md5.h]
These files are under the following license. Ruby uses modified
versions of them.
>>>
Copyright (C) 1999, 2000:: Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
[ext/digest/rmd160/rmd160.c]
[ext/digest/rmd160/rmd160.h]
These files have the following copyright information, and by the
author we are allowed to use it under the new-style BSD license.
>>>
AUTHOR:: Antoon Bosselaers, ESAT-COSIC
(Arranged for libc by Todd C. Miller)
DATE:: 1 March 1996
Copyright (c):: Katholieke Universiteit Leuven
1996, All Rights Reserved
[ext/digest/sha2/sha2.c]
[ext/digest/sha2/sha2.h]
These files are under the new-style BSD license.
>>>
Copyright 2000:: Aaron D. Gifford. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTOR(S) ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTOR(S) BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/json/generator/generator.c]
The file contains the following copyright notice.
>>>
Copyright 2001-2004:: Unicode, Inc.
Disclaimer::
This source code is provided as is by Unicode, Inc. No claims are
made as to fitness for any particular purpose. No warranties of any
kind are expressed or implied. The recipient agrees to determine
applicability of information provided. If this file has been
purchased on magnetic or optical media from Unicode, Inc., the
sole remedy for any claim will be exchange of defective media
within 90 days of receipt.
Limitations on Rights to Redistribute This Code::
Unicode, Inc. hereby grants the right to freely use the information
supplied in this file in the creation of products supporting the
Unicode Standard, and to make copies of this file in any form
for internal or external distribution as long as this notice
remains attached.
[ext/nkf/nkf-utf8/config.h]
[ext/nkf/nkf-utf8/nkf.c]
[ext/nkf/nkf-utf8/utf8tbl.c]
These files are under the following license. So to speak, it is
copyrighted semi-public-domain software.
>>>
Copyright (C) 1987:: Fujitsu LTD. (Itaru ICHIKAWA)
Everyone is permitted to do anything on this program
including copying, modifying, improving,
as long as you don't try to pretend that you wrote it.
i.e., the above copyright notice has to appear in all copies.
Binary distribution requires original version messages.
You don't have to ask before copying, redistribution or publishing.
THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE.
[ext/psych]
[test/psych]
The files under these directories are under the following license, except for
ext/psych/yaml.
>>>
Copyright 2009:: Aaron Patterson, et al.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/psych/yaml]
The files under this directory are under the following license.
>>>
Copyright (c) 2006:: Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/pty/pty.c]
>>>
C) Copyright 1998:: by Akinori Ito.
This software may be redistributed freely for this purpose, in full
or in part, provided that this entire copyright notice is included
on any copies of this software and applications and derivations thereof.
This software is provided on an "as is" basis, without warranty of any
kind, either expressed or implied, as to any matter including, but not
limited to warranty of fitness of purpose, or merchantability, or
results obtained from use of this software.
[ext/socket/addrinfo.h]
[ext/socket/getaddrinfo.c]
[ext/socket/getnameinfo.c]
These files are under the new-style BSD license.
>>>
Copyright (C) 1995, 1996, 1997, 1998, and 1999:: WIDE Project.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/win32ole/win32ole.c]
You can apply the Artistic License to this file. (or GPL,
alternatively)
>>>
(c) 1995:: Microsoft Corporation. All rights reserved.
Developed by ActiveWare Internet Corp., http://www.ActiveWare.com
Other modifications Copyright (c) 1997, 1998:: by Gurusamy Sarathy
<gsar@umich.edu> and Jan Dubois <jan.dubois@ibm.net>
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the README file
of the Perl distribution.
The Wayback Machine url: http://web.archive.org/web/19970607104352/http://www.activeware.com:80/
[lib/rdoc/generator/template/darkfish/css/fonts.css]
This file is licensed under the {SIL Open Font License}[http://scripts.sil.org/OFL].
[spec/mspec]
[spec/ruby]
The files under these directories are under the following license.
>>>
Copyright (c) 2008:: Engine Yard, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[lib/rubygems.rb]
[lib/rubygems]
[test/rubygems]
RubyGems is under the following license.
>>>
RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim
Weirich and others. You can redistribute it and/or modify it under
either the terms of the {MIT license}[rdoc-label:label-MIT+License], or the conditions
below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or executable
form, provided that you do at least ONE of the following:
a. distribute the executables and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial).
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
[lib/bundler]
[lib/bundler.rb]
[spec/bundler]
Bundler is under the following license.
>>>
Portions copyright (c) 2010:: Andre Arko
Portions copyright (c) 2009:: Engine Yard
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/thor]
Thor is under the following license.
>>>
Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
{MIT License}[rdoc-label:label-MIT+License]
[lib/rubygems/resolver/molinillo]
molinillo is under the following license.
>>>
Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/pub_grub]
pub_grub is under the following license.
>>>
Copyright (c) 2018 John Hawthorn
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/connection_pool]
connection_pool is under the following license.
>>>
Copyright (c) 2011 Mike Perham
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/net-http-persistent]
net-http-persistent is under the following license.
>>>
Copyright (c) Eric Hodel, Aaron Patterson
{MIT License}[rdoc-label:label-MIT+License]
[lib/did_you_mean]
[lib/did_you_mean.rb]
[test/did_you_mean]
did_you_mean is under the following license.
>>>
Copyright (c) 2014-2016 Yuki Nishijima
{MIT License}[rdoc-label:label-MIT+License]
[lib/error_highlight]
[lib/error_highlight.rb]
[test/error_highlight]
error_highlight is under the following license.
>>>
Copyright (c) 2021 Yusuke Endoh
{MIT License}[rdoc-label:label-MIT+License]
[benchmark/so_ackermann.rb]
[benchmark/so_array.rb]
[benchmark/so_binary_trees.rb]
[benchmark/so_concatenate.rb]
[benchmark/so_count_words.yml]
[benchmark/so_exception.rb]
[benchmark/so_fannkuch.rb]
[benchmark/so_fasta.rb]
[benchmark/so_k_nucleotide.yml]
[benchmark/so_lists.rb]
[benchmark/so_mandelbrot.rb]
[benchmark/so_matrix.rb]
[benchmark/so_meteor_contest.rb]
[benchmark/so_nbody.rb]
[benchmark/so_nested_loop.rb]
[benchmark/so_nsieve.rb]
[benchmark/so_nsieve_bits.rb]
[benchmark/so_object.rb]
[benchmark/so_partial_sums.rb]
[benchmark/so_pidigits.rb]
[benchmark/so_random.rb]
[benchmark/so_reverse_complement.yml]
[benchmark/so_sieve.rb]
[benchmark/so_spectralnorm.rb]
These files are very old copy of then-called "The Great Computer Language
Shootout". LEGAL SITUATION OF THESE FILES ARE UNCLEAR because the original
site has been lost. Upstream diverged to delete several benchmarks listed
above.
== MIT License
>>>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== Old-style BSD license
>>>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
IMPORTANT NOTE::
From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change
paragraph 3 above is now null and void.
share/licenses/alt-ruby32/GPL 0000644 00000043254 15173547341 0011746 0 ustar 00 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
share/licenses/alt-ruby32-devel/COPYING 0000644 00000004573 15173547341 0013532 0 ustar 00 Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
You can redistribute it and/or modify it under either the terms of the
2-clause BSDL (see the file BSDL), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or binary form,
provided that you do at least ONE of the following:
a. distribute the binaries and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under these terms.
For the list of those files and their copying conditions, see the
file LEGAL.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
share/licenses/alt-ruby32-devel/COPYING.ja 0000644 00000004776 15173547341 0014130 0 ustar 00 本プログラムはフリーソフトウェアです.2-clause BSDL
または以下に示す条件で本プログラムを再配布できます
2-clause BSDLについてはBSDLファイルを参照して下さい.
1. 複製は制限なく自由です.
2. 以下の条件のいずれかを満たす時に本プログラムのソースを
自由に変更できます.
a. ネットニューズにポストしたり,作者に変更を送付する
などの方法で,変更を公開する.
b. 変更した本プログラムを自分の所属する組織内部だけで
使う.
c. 変更点を明示したうえ,ソフトウェアの名前を変更する.
そのソフトウェアを配布する時には変更前の本プログラ
ムも同時に配布する.または変更前の本プログラムのソー
スの入手法を明示する.
d. その他の変更条件を作者と合意する.
3. 以下の条件のいずれかを満たす時に本プログラムをコンパイ
ルしたオブジェクトコードや実行形式でも配布できます.
a. バイナリを受け取った人がソースを入手できるように,
ソースの入手法を明示する.
b. 機械可読なソースコードを添付する.
c. 変更を行ったバイナリは名前を変更したうえ,オリジナ
ルのソースコードの入手法を明示する.
d. その他の配布条件を作者と合意する.
4. 他のプログラムへの引用はいかなる目的であれ自由です.た
だし,本プログラムに含まれる他の作者によるコードは,そ
れぞれの作者の意向による制限が加えられる場合があります.
それらファイルの一覧とそれぞれの配布条件などに付いては
LEGALファイルを参照してください.
5. 本プログラムへの入力となるスクリプトおよび,本プログラ
ムからの出力の権利は本プログラムの作者ではなく,それぞ
れの入出力を生成した人に属します.また,本プログラムに
組み込まれるための拡張ライブラリについても同様です.
6. 本プログラムは無保証です.作者は本プログラムをサポート
する意志はありますが,プログラム自身のバグあるいは本プ
ログラムの実行などから発生するいかなる損害に対しても責
任を持ちません.
share/licenses/alt-ruby32-devel/BSDL 0000644 00000002413 15173547341 0013135 0 ustar 00 Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
share/licenses/alt-ruby32-devel/LEGAL 0000644 00000127612 15173547341 0013246 0 ustar 00 # -*- rdoc -*-
= LEGAL NOTICE INFORMATION
--------------------------
All the files in this distribution are covered under either the Ruby's
license (see the file COPYING) or public-domain except some files
mentioned below.
[addr2line.c]
A part of this file is from FreeBSD.
>>>
Copyright (c) 1986, 1988, 1991, 1993::
The Regents of the University of California. All rights reserved.
(c) UNIX System Laboratories, Inc.
All or some portions of this file are derived from material licensed
to the University of California by American Telephone and Telegraph
Co. or Unix System Laboratories, Inc. and are reproduced herein with
the permission of UNIX System Laboratories, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
4. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
@(#)subr_prf.c 8.3 (Berkeley) 1/21/94
[ccan/build_assert/build_assert.h]
[ccan/check_type/check_type.h]
[ccan/container_of/container_of.h]
[ccan/str/str.h]
These files are licensed under the {CC0}[https://creativecommons.org/choose/zero/].
[ccan/list/list.h]
This file is licensed under the {MIT License}[rdoc-label:label-MIT+License].
[coroutine]
Unless otherwise specified, these files are licensed under the
{MIT License}[rdoc-label:label-MIT+License].
[include/ruby/onigmo.h]
[include/ruby/oniguruma.h]
[regcomp.c]
[regenc.c]
[regenc.h]
[regerror.c]
[regexec.c]
[regint.h]
[regparse.c]
[regparse.h]
[enc/ascii.c]
[enc/big5.c]
[enc/cp949.c]
[enc/emacs_mule.c]
[enc/encdb.c]
[enc/euc_jp.c]
[enc/euc_kr.c]
[enc/euc_tw.c]
[enc/gb18030.c]
[enc/gb2312.c]
[enc/gbk.c]
[enc/iso_8859_1.c]
[enc/iso_8859_10.c]
[enc/iso_8859_11.c]
[enc/iso_8859_13.c]
[enc/iso_8859_14.c]
[enc/iso_8859_15.c]
[enc/iso_8859_16.c]
[enc/iso_8859_2.c]
[enc/iso_8859_3.c]
[enc/iso_8859_4.c]
[enc/iso_8859_5.c]
[enc/iso_8859_6.c]
[enc/iso_8859_7.c]
[enc/iso_8859_8.c]
[enc/iso_8859_9.c]
[enc/koi8_r.c]
[enc/koi8_u.c]
[enc/shift_jis.c]
[enc/unicode.c]
[enc/us_ascii.c]
[enc/utf_16be.c]
[enc/utf_16le.c]
[enc/utf_32be.c]
[enc/utf_32le.c]
[enc/utf_8.c]
[enc/windows_1251.c]
[enc/windows_31j.c]
Onigmo (Oniguruma-mod) LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
Copyright (c) 2011-2014:: K.Takata <kentkt AT csc DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Oniguruma LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
* https://github.com/k-takata/Onigmo/
* https://github.com/kkos/oniguruma
* https://svnweb.freebsd.org/ports/head/devel/oniguruma/
When this software is partly used or it is distributed with Ruby,
this of Ruby follows the license of Ruby.
[enc/windows_1250.c]
[enc/windows_1252.c]
>>>
Copyright (c) 2006-2007:: Byte <byte AT mail DOT kna DOT ru>
K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/cesu_8.c]
[enc/windows_1253.c]
[enc/windows_1254.c]
[enc/windows_1257.c]
>>>
Copyright (c) 2002-2007:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/trans/GB/GB12345%UCS.src]
[enc/trans/GB/UCS%GB12345.src]
[enc/trans/GB/GB2312%UCS.src]
[enc/trans/GB/UCS%GB2312.src]
These files have this explanatory texts.
>>>
This mapping data was created from files provided by Unicode, Inc.
(The Unicode Consortium). The files were used to create a product supporting
Unicode, as explicitly permitted in the files' copyright notices.
Please note that Unicode, Inc. never made any claims as to fitness of these
files for any particular purpose, and has ceased to publish the files many
years ago.
[enc/trans/JIS/JISX0201-KANA%UCS.src]
[enc/trans/JIS/JISX0208\@1990%UCS.src]
[enc/trans/JIS/JISX0212%UCS.src]
[enc/trans/JIS/UCS%JISX0201-KANA.src]
[enc/trans/JIS/UCS%JISX0208@1990.src]
[enc/trans/JIS/UCS%JISX0212.src]
These files are copyrighted as the following.
>>>
© 2015 Unicode®, Inc.
For terms of use, see http://www.unicode.org/terms_of_use.html
[enc/trans/JIS/JISX0213-1%UCS@BMP.src]
[enc/trans/JIS/JISX0213-1%UCS@SIP.src]
[enc/trans/JIS/JISX0213-2%UCS@BMP.src]
[enc/trans/JIS/JISX0213-2%UCS@SIP.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
Copyright (C) 2006:: Project X0213, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/JIS/UCS@BMP%JISX0213-1.src]
[enc/trans/JIS/UCS@BMP%JISX0213-2.src]
[enc/trans/JIS/UCS@SIP%JISX0213-1.src]
[enc/trans/JIS/UCS@SIP%JISX0213-2.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/ucm/glibc-BIG5-2.3.3.ucm]
[enc/trans/ucm/glibc-BIG5HKSCS-2.3.3.ucm]
>>>
Copyright (C) 2001-2005:: International Business Machines
Corporation and others. All Rights Reserved.
[enc/trans/ucm/windows-950-2000.ucm]
[enc/trans/ucm/windows-950_hkscs-2001.ucm]
>>>
Copyright (C) 2001-2002:: International Business Machines
Corporation and others. All Rights Reserved.
[configure]
This file is free software.
>>>
Copyright (C) 1992-1996, 1998-2012:: Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
[tool/config.guess]
[tool/config.sub]
As long as you distribute these files with the file configure, they
are covered under the Ruby's license.
>>>
Copyright 1992-2018:: Free Software Foundation, Inc.
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
As a special exception to the GNU General Public License, if you
distribute this file as part of a program that contains a
configuration script generated by Autoconf, you may include it under
the same distribution terms that you use for the rest of that
program. This Exception is an additional permission under section 7
of the GNU General Public License, version 3 ("GPLv3").
[tool/lib/test/*]
[tool/lib/core_assertions.rb]
Some of methods on these files are based on MiniTest 4. MiniTest 4 is
distributed under the MIT License.
>>>
Copyright (c) Ryan Davis, seattle.rb
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[parse.c]
[parse.h]
These files are licensed under the GPL, but are incorporated into Ruby and
redistributed under the terms of the Ruby license, as permitted by the
exception to the GPL below.
>>>
Copyright (C) 1984, 1989-1990, 2000-2015, 2018:: Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison.
[missing/dtoa.c]
This file is under these licenses.
>>>
Copyright (c) 1991, 2000, 2001:: by Lucent Technologies.
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
>>>
Copyright (c) 2004-2008:: David Schultz <das@FreeBSD.ORG>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[win32/win32.c]
[include/ruby/win32.h]
You can apply the Artistic License to these files. (or GPL,
alternatively)
>>>
Copyright (c) 1993:: Intergraph Corporation
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the perl README file.
[missing/mt19937.c]
This file is under the new-style BSD license.
>>>
A C-program for MT19937, with initialization improved 2002/2/10.::
Coded by Takuji Nishimura and Makoto Matsumoto.
This is a faster version by taking Shawn Cokus's optimization,
Matthe Bellew's simplification, Isaku Wada's real version.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002:: Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.keio.ac.jp/matumoto/emt.html
email: matumoto@math.keio.ac.jp
The Wayback Machine url: http://web.archive.org/web/19990429082237/http://www.math.keio.ac.jp/matumoto/emt.html
[missing/procstat_vm.c]
This file is under the new-style BSD license.
>>>
Copyright (c) 2007:: Robert N. M. Watson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
$FreeBSD: head/usr.bin/procstat/procstat_vm.c 261780 2014-02-11 21:57:37Z jhb $
[vsnprintf.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1990, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Chris Torek.
[st.c]
[strftime.c]
[include/ruby/st.h]
[missing/acosh.c]
[missing/alloca.c]
[missing/erf.c]
[missing/hypot.c]
[missing/lgamma_r.c]
[missing/memcmp.c]
[missing/memmove.c]
[missing/strchr.c]
[missing/strerror.c]
[missing/strstr.c]
[missing/tgamma.c]
[ext/date/date_strftime.c]
[ext/digest/sha1/sha1.c]
[ext/digest/sha1/sha1.h]
These files are all under public domain.
[missing/crypt.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1989, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Tom Truscott.
[missing/setproctitle.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright 2003:: Damien Miller
Copyright (c) 1983, 1995-1997:: Eric P. Allman
Copyright (c) 1988, 1993::
The Regents of the University of California. All rights reserved.
[missing/strlcat.c]
[missing/strlcpy.c]
These files are under an ISC-style license.
>>>
Copyright (c) 1998, 2015:: Todd C. Miller <Todd.Miller@courtesan.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
[missing/langinfo.c]
This file is from http://www.cl.cam.ac.uk/~mgk25/ucs/langinfo.c.
Ruby uses a modified version. The file contains the following
author/copyright notice:
>>>
Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11::
Permission to use, copy, modify, and distribute this software
for any purpose and without fee is hereby granted. The author
disclaims all warranties with regard to this software.
[ext/digest/md5/md5.c]
[ext/digest/md5/md5.h]
These files are under the following license. Ruby uses modified
versions of them.
>>>
Copyright (C) 1999, 2000:: Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
[ext/digest/rmd160/rmd160.c]
[ext/digest/rmd160/rmd160.h]
These files have the following copyright information, and by the
author we are allowed to use it under the new-style BSD license.
>>>
AUTHOR:: Antoon Bosselaers, ESAT-COSIC
(Arranged for libc by Todd C. Miller)
DATE:: 1 March 1996
Copyright (c):: Katholieke Universiteit Leuven
1996, All Rights Reserved
[ext/digest/sha2/sha2.c]
[ext/digest/sha2/sha2.h]
These files are under the new-style BSD license.
>>>
Copyright 2000:: Aaron D. Gifford. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTOR(S) ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTOR(S) BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/json/generator/generator.c]
The file contains the following copyright notice.
>>>
Copyright 2001-2004:: Unicode, Inc.
Disclaimer::
This source code is provided as is by Unicode, Inc. No claims are
made as to fitness for any particular purpose. No warranties of any
kind are expressed or implied. The recipient agrees to determine
applicability of information provided. If this file has been
purchased on magnetic or optical media from Unicode, Inc., the
sole remedy for any claim will be exchange of defective media
within 90 days of receipt.
Limitations on Rights to Redistribute This Code::
Unicode, Inc. hereby grants the right to freely use the information
supplied in this file in the creation of products supporting the
Unicode Standard, and to make copies of this file in any form
for internal or external distribution as long as this notice
remains attached.
[ext/nkf/nkf-utf8/config.h]
[ext/nkf/nkf-utf8/nkf.c]
[ext/nkf/nkf-utf8/utf8tbl.c]
These files are under the following license. So to speak, it is
copyrighted semi-public-domain software.
>>>
Copyright (C) 1987:: Fujitsu LTD. (Itaru ICHIKAWA)
Everyone is permitted to do anything on this program
including copying, modifying, improving,
as long as you don't try to pretend that you wrote it.
i.e., the above copyright notice has to appear in all copies.
Binary distribution requires original version messages.
You don't have to ask before copying, redistribution or publishing.
THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE.
[ext/psych]
[test/psych]
The files under these directories are under the following license, except for
ext/psych/yaml.
>>>
Copyright 2009:: Aaron Patterson, et al.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/psych/yaml]
The files under this directory are under the following license.
>>>
Copyright (c) 2006:: Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/pty/pty.c]
>>>
C) Copyright 1998:: by Akinori Ito.
This software may be redistributed freely for this purpose, in full
or in part, provided that this entire copyright notice is included
on any copies of this software and applications and derivations thereof.
This software is provided on an "as is" basis, without warranty of any
kind, either expressed or implied, as to any matter including, but not
limited to warranty of fitness of purpose, or merchantability, or
results obtained from use of this software.
[ext/socket/addrinfo.h]
[ext/socket/getaddrinfo.c]
[ext/socket/getnameinfo.c]
These files are under the new-style BSD license.
>>>
Copyright (C) 1995, 1996, 1997, 1998, and 1999:: WIDE Project.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/win32ole/win32ole.c]
You can apply the Artistic License to this file. (or GPL,
alternatively)
>>>
(c) 1995:: Microsoft Corporation. All rights reserved.
Developed by ActiveWare Internet Corp., http://www.ActiveWare.com
Other modifications Copyright (c) 1997, 1998:: by Gurusamy Sarathy
<gsar@umich.edu> and Jan Dubois <jan.dubois@ibm.net>
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the README file
of the Perl distribution.
The Wayback Machine url: http://web.archive.org/web/19970607104352/http://www.activeware.com:80/
[lib/rdoc/generator/template/darkfish/css/fonts.css]
This file is licensed under the {SIL Open Font License}[http://scripts.sil.org/OFL].
[spec/mspec]
[spec/ruby]
The files under these directories are under the following license.
>>>
Copyright (c) 2008:: Engine Yard, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[lib/rubygems.rb]
[lib/rubygems]
[test/rubygems]
RubyGems is under the following license.
>>>
RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim
Weirich and others. You can redistribute it and/or modify it under
either the terms of the {MIT license}[rdoc-label:label-MIT+License], or the conditions
below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or executable
form, provided that you do at least ONE of the following:
a. distribute the executables and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial).
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
[lib/bundler]
[lib/bundler.rb]
[spec/bundler]
Bundler is under the following license.
>>>
Portions copyright (c) 2010:: Andre Arko
Portions copyright (c) 2009:: Engine Yard
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/thor]
Thor is under the following license.
>>>
Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
{MIT License}[rdoc-label:label-MIT+License]
[lib/rubygems/resolver/molinillo]
molinillo is under the following license.
>>>
Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/pub_grub]
pub_grub is under the following license.
>>>
Copyright (c) 2018 John Hawthorn
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/connection_pool]
connection_pool is under the following license.
>>>
Copyright (c) 2011 Mike Perham
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/net-http-persistent]
net-http-persistent is under the following license.
>>>
Copyright (c) Eric Hodel, Aaron Patterson
{MIT License}[rdoc-label:label-MIT+License]
[lib/did_you_mean]
[lib/did_you_mean.rb]
[test/did_you_mean]
did_you_mean is under the following license.
>>>
Copyright (c) 2014-2016 Yuki Nishijima
{MIT License}[rdoc-label:label-MIT+License]
[lib/error_highlight]
[lib/error_highlight.rb]
[test/error_highlight]
error_highlight is under the following license.
>>>
Copyright (c) 2021 Yusuke Endoh
{MIT License}[rdoc-label:label-MIT+License]
[benchmark/so_ackermann.rb]
[benchmark/so_array.rb]
[benchmark/so_binary_trees.rb]
[benchmark/so_concatenate.rb]
[benchmark/so_count_words.yml]
[benchmark/so_exception.rb]
[benchmark/so_fannkuch.rb]
[benchmark/so_fasta.rb]
[benchmark/so_k_nucleotide.yml]
[benchmark/so_lists.rb]
[benchmark/so_mandelbrot.rb]
[benchmark/so_matrix.rb]
[benchmark/so_meteor_contest.rb]
[benchmark/so_nbody.rb]
[benchmark/so_nested_loop.rb]
[benchmark/so_nsieve.rb]
[benchmark/so_nsieve_bits.rb]
[benchmark/so_object.rb]
[benchmark/so_partial_sums.rb]
[benchmark/so_pidigits.rb]
[benchmark/so_random.rb]
[benchmark/so_reverse_complement.yml]
[benchmark/so_sieve.rb]
[benchmark/so_spectralnorm.rb]
These files are very old copy of then-called "The Great Computer Language
Shootout". LEGAL SITUATION OF THESE FILES ARE UNCLEAR because the original
site has been lost. Upstream diverged to delete several benchmarks listed
above.
== MIT License
>>>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== Old-style BSD license
>>>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
IMPORTANT NOTE::
From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change
paragraph 3 above is now null and void.
share/licenses/alt-ruby32-devel/GPL 0000644 00000043254 15173547341 0013043 0 ustar 00 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
share/licenses/alt-ruby32-libs/COPYING 0000644 00000004573 15173547341 0013364 0 ustar 00 Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.jp>.
You can redistribute it and/or modify it under either the terms of the
2-clause BSDL (see the file BSDL), or the conditions below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or binary form,
provided that you do at least ONE of the following:
a. distribute the binaries and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard binaries non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under these terms.
For the list of those files and their copying conditions, see the
file LEGAL.
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
share/licenses/alt-ruby32-libs/COPYING.ja 0000644 00000004776 15173547341 0013762 0 ustar 00 本プログラムはフリーソフトウェアです.2-clause BSDL
または以下に示す条件で本プログラムを再配布できます
2-clause BSDLについてはBSDLファイルを参照して下さい.
1. 複製は制限なく自由です.
2. 以下の条件のいずれかを満たす時に本プログラムのソースを
自由に変更できます.
a. ネットニューズにポストしたり,作者に変更を送付する
などの方法で,変更を公開する.
b. 変更した本プログラムを自分の所属する組織内部だけで
使う.
c. 変更点を明示したうえ,ソフトウェアの名前を変更する.
そのソフトウェアを配布する時には変更前の本プログラ
ムも同時に配布する.または変更前の本プログラムのソー
スの入手法を明示する.
d. その他の変更条件を作者と合意する.
3. 以下の条件のいずれかを満たす時に本プログラムをコンパイ
ルしたオブジェクトコードや実行形式でも配布できます.
a. バイナリを受け取った人がソースを入手できるように,
ソースの入手法を明示する.
b. 機械可読なソースコードを添付する.
c. 変更を行ったバイナリは名前を変更したうえ,オリジナ
ルのソースコードの入手法を明示する.
d. その他の配布条件を作者と合意する.
4. 他のプログラムへの引用はいかなる目的であれ自由です.た
だし,本プログラムに含まれる他の作者によるコードは,そ
れぞれの作者の意向による制限が加えられる場合があります.
それらファイルの一覧とそれぞれの配布条件などに付いては
LEGALファイルを参照してください.
5. 本プログラムへの入力となるスクリプトおよび,本プログラ
ムからの出力の権利は本プログラムの作者ではなく,それぞ
れの入出力を生成した人に属します.また,本プログラムに
組み込まれるための拡張ライブラリについても同様です.
6. 本プログラムは無保証です.作者は本プログラムをサポート
する意志はありますが,プログラム自身のバグあるいは本プ
ログラムの実行などから発生するいかなる損害に対しても責
任を持ちません.
share/licenses/alt-ruby32-libs/LEGAL 0000644 00000127612 15173547341 0013100 0 ustar 00 # -*- rdoc -*-
= LEGAL NOTICE INFORMATION
--------------------------
All the files in this distribution are covered under either the Ruby's
license (see the file COPYING) or public-domain except some files
mentioned below.
[addr2line.c]
A part of this file is from FreeBSD.
>>>
Copyright (c) 1986, 1988, 1991, 1993::
The Regents of the University of California. All rights reserved.
(c) UNIX System Laboratories, Inc.
All or some portions of this file are derived from material licensed
to the University of California by American Telephone and Telegraph
Co. or Unix System Laboratories, Inc. and are reproduced herein with
the permission of UNIX System Laboratories, Inc.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
4. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
@(#)subr_prf.c 8.3 (Berkeley) 1/21/94
[ccan/build_assert/build_assert.h]
[ccan/check_type/check_type.h]
[ccan/container_of/container_of.h]
[ccan/str/str.h]
These files are licensed under the {CC0}[https://creativecommons.org/choose/zero/].
[ccan/list/list.h]
This file is licensed under the {MIT License}[rdoc-label:label-MIT+License].
[coroutine]
Unless otherwise specified, these files are licensed under the
{MIT License}[rdoc-label:label-MIT+License].
[include/ruby/onigmo.h]
[include/ruby/oniguruma.h]
[regcomp.c]
[regenc.c]
[regenc.h]
[regerror.c]
[regexec.c]
[regint.h]
[regparse.c]
[regparse.h]
[enc/ascii.c]
[enc/big5.c]
[enc/cp949.c]
[enc/emacs_mule.c]
[enc/encdb.c]
[enc/euc_jp.c]
[enc/euc_kr.c]
[enc/euc_tw.c]
[enc/gb18030.c]
[enc/gb2312.c]
[enc/gbk.c]
[enc/iso_8859_1.c]
[enc/iso_8859_10.c]
[enc/iso_8859_11.c]
[enc/iso_8859_13.c]
[enc/iso_8859_14.c]
[enc/iso_8859_15.c]
[enc/iso_8859_16.c]
[enc/iso_8859_2.c]
[enc/iso_8859_3.c]
[enc/iso_8859_4.c]
[enc/iso_8859_5.c]
[enc/iso_8859_6.c]
[enc/iso_8859_7.c]
[enc/iso_8859_8.c]
[enc/iso_8859_9.c]
[enc/koi8_r.c]
[enc/koi8_u.c]
[enc/shift_jis.c]
[enc/unicode.c]
[enc/us_ascii.c]
[enc/utf_16be.c]
[enc/utf_16le.c]
[enc/utf_32be.c]
[enc/utf_32le.c]
[enc/utf_8.c]
[enc/windows_1251.c]
[enc/windows_31j.c]
Onigmo (Oniguruma-mod) LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
Copyright (c) 2011-2014:: K.Takata <kentkt AT csc DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
Oniguruma LICENSE
>>>
Copyright (c) 2002-2009:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
* https://github.com/k-takata/Onigmo/
* https://github.com/kkos/oniguruma
* https://svnweb.freebsd.org/ports/head/devel/oniguruma/
When this software is partly used or it is distributed with Ruby,
this of Ruby follows the license of Ruby.
[enc/windows_1250.c]
[enc/windows_1252.c]
>>>
Copyright (c) 2006-2007:: Byte <byte AT mail DOT kna DOT ru>
K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/cesu_8.c]
[enc/windows_1253.c]
[enc/windows_1254.c]
[enc/windows_1257.c]
>>>
Copyright (c) 2002-2007:: K.Kosako <sndgk393 AT ybb DOT ne DOT jp>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[enc/trans/GB/GB12345%UCS.src]
[enc/trans/GB/UCS%GB12345.src]
[enc/trans/GB/GB2312%UCS.src]
[enc/trans/GB/UCS%GB2312.src]
These files have this explanatory texts.
>>>
This mapping data was created from files provided by Unicode, Inc.
(The Unicode Consortium). The files were used to create a product supporting
Unicode, as explicitly permitted in the files' copyright notices.
Please note that Unicode, Inc. never made any claims as to fitness of these
files for any particular purpose, and has ceased to publish the files many
years ago.
[enc/trans/JIS/JISX0201-KANA%UCS.src]
[enc/trans/JIS/JISX0208\@1990%UCS.src]
[enc/trans/JIS/JISX0212%UCS.src]
[enc/trans/JIS/UCS%JISX0201-KANA.src]
[enc/trans/JIS/UCS%JISX0208@1990.src]
[enc/trans/JIS/UCS%JISX0212.src]
These files are copyrighted as the following.
>>>
© 2015 Unicode®, Inc.
For terms of use, see http://www.unicode.org/terms_of_use.html
[enc/trans/JIS/JISX0213-1%UCS@BMP.src]
[enc/trans/JIS/JISX0213-1%UCS@SIP.src]
[enc/trans/JIS/JISX0213-2%UCS@BMP.src]
[enc/trans/JIS/JISX0213-2%UCS@SIP.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
Copyright (C) 2006:: Project X0213, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/JIS/UCS@BMP%JISX0213-1.src]
[enc/trans/JIS/UCS@BMP%JISX0213-2.src]
[enc/trans/JIS/UCS@SIP%JISX0213-1.src]
[enc/trans/JIS/UCS@SIP%JISX0213-2.src]
These files are copyrighted as the following.
>>>
Copyright (C) 2001:: earthian@tama.or.jp, All Rights Reserved.
Copyright (C) 2001:: I'O, All Rights Reserved.
You can use, modify, distribute this table freely.
[enc/trans/ucm/glibc-BIG5-2.3.3.ucm]
[enc/trans/ucm/glibc-BIG5HKSCS-2.3.3.ucm]
>>>
Copyright (C) 2001-2005:: International Business Machines
Corporation and others. All Rights Reserved.
[enc/trans/ucm/windows-950-2000.ucm]
[enc/trans/ucm/windows-950_hkscs-2001.ucm]
>>>
Copyright (C) 2001-2002:: International Business Machines
Corporation and others. All Rights Reserved.
[configure]
This file is free software.
>>>
Copyright (C) 1992-1996, 1998-2012:: Free Software Foundation, Inc.
This configure script is free software; the Free Software Foundation
gives unlimited permission to copy, distribute and modify it.
[tool/config.guess]
[tool/config.sub]
As long as you distribute these files with the file configure, they
are covered under the Ruby's license.
>>>
Copyright 1992-2018:: Free Software Foundation, Inc.
This file is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <https://www.gnu.org/licenses/>.
As a special exception to the GNU General Public License, if you
distribute this file as part of a program that contains a
configuration script generated by Autoconf, you may include it under
the same distribution terms that you use for the rest of that
program. This Exception is an additional permission under section 7
of the GNU General Public License, version 3 ("GPLv3").
[tool/lib/test/*]
[tool/lib/core_assertions.rb]
Some of methods on these files are based on MiniTest 4. MiniTest 4 is
distributed under the MIT License.
>>>
Copyright (c) Ryan Davis, seattle.rb
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
[parse.c]
[parse.h]
These files are licensed under the GPL, but are incorporated into Ruby and
redistributed under the terms of the Ruby license, as permitted by the
exception to the GPL below.
>>>
Copyright (C) 1984, 1989-1990, 2000-2015, 2018:: Free Software Foundation, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
As a special exception, you may create a larger work that contains
part or all of the Bison parser skeleton and distribute that work
under terms of your choice, so long as that work isn't itself a
parser generator using the skeleton or a modified version thereof
as a parser skeleton. Alternatively, if you modify or redistribute
the parser skeleton itself, you may (at your option) remove this
special exception, which will cause the skeleton and the resulting
Bison output files to be licensed under the GNU General Public
License without this special exception.
This special exception was added by the Free Software Foundation in
version 2.2 of Bison.
[missing/dtoa.c]
This file is under these licenses.
>>>
Copyright (c) 1991, 2000, 2001:: by Lucent Technologies.
Permission to use, copy, modify, and distribute this software for any
purpose without fee is hereby granted, provided that this entire notice
is included in all copies of any software which is or includes a copy
or modification of this software and in all copies of the supporting
documentation for such software.
THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY
REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
>>>
Copyright (c) 2004-2008:: David Schultz <das@FreeBSD.ORG>
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[win32/win32.c]
[include/ruby/win32.h]
You can apply the Artistic License to these files. (or GPL,
alternatively)
>>>
Copyright (c) 1993:: Intergraph Corporation
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the perl README file.
[missing/mt19937.c]
This file is under the new-style BSD license.
>>>
A C-program for MT19937, with initialization improved 2002/2/10.::
Coded by Takuji Nishimura and Makoto Matsumoto.
This is a faster version by taking Shawn Cokus's optimization,
Matthe Bellew's simplification, Isaku Wada's real version.
Before using, initialize the state by using init_genrand(seed)
or init_by_array(init_key, key_length).
Copyright (C) 1997 - 2002:: Makoto Matsumoto and Takuji Nishimura,
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. The names of its contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Any feedback is very welcome.
http://www.math.keio.ac.jp/matumoto/emt.html
email: matumoto@math.keio.ac.jp
The Wayback Machine url: http://web.archive.org/web/19990429082237/http://www.math.keio.ac.jp/matumoto/emt.html
[missing/procstat_vm.c]
This file is under the new-style BSD license.
>>>
Copyright (c) 2007:: Robert N. M. Watson
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
$FreeBSD: head/usr.bin/procstat/procstat_vm.c 261780 2014-02-11 21:57:37Z jhb $
[vsnprintf.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1990, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Chris Torek.
[st.c]
[strftime.c]
[include/ruby/st.h]
[missing/acosh.c]
[missing/alloca.c]
[missing/erf.c]
[missing/hypot.c]
[missing/lgamma_r.c]
[missing/memcmp.c]
[missing/memmove.c]
[missing/strchr.c]
[missing/strerror.c]
[missing/strstr.c]
[missing/tgamma.c]
[ext/date/date_strftime.c]
[ext/digest/sha1/sha1.c]
[ext/digest/sha1/sha1.h]
These files are all under public domain.
[missing/crypt.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright (c) 1989, 1993::
The Regents of the University of California. All rights reserved.
This code is derived from software contributed to Berkeley by
Tom Truscott.
[missing/setproctitle.c]
This file is under the {old-style BSD license}[rdoc-label:label-Old-style+BSD+license].
>>>
Copyright 2003:: Damien Miller
Copyright (c) 1983, 1995-1997:: Eric P. Allman
Copyright (c) 1988, 1993::
The Regents of the University of California. All rights reserved.
[missing/strlcat.c]
[missing/strlcpy.c]
These files are under an ISC-style license.
>>>
Copyright (c) 1998, 2015:: Todd C. Miller <Todd.Miller@courtesan.com>
Permission to use, copy, modify, and distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
[missing/langinfo.c]
This file is from http://www.cl.cam.ac.uk/~mgk25/ucs/langinfo.c.
Ruby uses a modified version. The file contains the following
author/copyright notice:
>>>
Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11::
Permission to use, copy, modify, and distribute this software
for any purpose and without fee is hereby granted. The author
disclaims all warranties with regard to this software.
[ext/digest/md5/md5.c]
[ext/digest/md5/md5.h]
These files are under the following license. Ruby uses modified
versions of them.
>>>
Copyright (C) 1999, 2000:: Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
[ext/digest/rmd160/rmd160.c]
[ext/digest/rmd160/rmd160.h]
These files have the following copyright information, and by the
author we are allowed to use it under the new-style BSD license.
>>>
AUTHOR:: Antoon Bosselaers, ESAT-COSIC
(Arranged for libc by Todd C. Miller)
DATE:: 1 March 1996
Copyright (c):: Katholieke Universiteit Leuven
1996, All Rights Reserved
[ext/digest/sha2/sha2.c]
[ext/digest/sha2/sha2.h]
These files are under the new-style BSD license.
>>>
Copyright 2000:: Aaron D. Gifford. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) AND CONTRIBUTOR(S) ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) OR CONTRIBUTOR(S) BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/json/generator/generator.c]
The file contains the following copyright notice.
>>>
Copyright 2001-2004:: Unicode, Inc.
Disclaimer::
This source code is provided as is by Unicode, Inc. No claims are
made as to fitness for any particular purpose. No warranties of any
kind are expressed or implied. The recipient agrees to determine
applicability of information provided. If this file has been
purchased on magnetic or optical media from Unicode, Inc., the
sole remedy for any claim will be exchange of defective media
within 90 days of receipt.
Limitations on Rights to Redistribute This Code::
Unicode, Inc. hereby grants the right to freely use the information
supplied in this file in the creation of products supporting the
Unicode Standard, and to make copies of this file in any form
for internal or external distribution as long as this notice
remains attached.
[ext/nkf/nkf-utf8/config.h]
[ext/nkf/nkf-utf8/nkf.c]
[ext/nkf/nkf-utf8/utf8tbl.c]
These files are under the following license. So to speak, it is
copyrighted semi-public-domain software.
>>>
Copyright (C) 1987:: Fujitsu LTD. (Itaru ICHIKAWA)
Everyone is permitted to do anything on this program
including copying, modifying, improving,
as long as you don't try to pretend that you wrote it.
i.e., the above copyright notice has to appear in all copies.
Binary distribution requires original version messages.
You don't have to ask before copying, redistribution or publishing.
THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE.
[ext/psych]
[test/psych]
The files under these directories are under the following license, except for
ext/psych/yaml.
>>>
Copyright 2009:: Aaron Patterson, et al.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/psych/yaml]
The files under this directory are under the following license.
>>>
Copyright (c) 2006:: Kirill Simonov
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
[ext/pty/pty.c]
>>>
C) Copyright 1998:: by Akinori Ito.
This software may be redistributed freely for this purpose, in full
or in part, provided that this entire copyright notice is included
on any copies of this software and applications and derivations thereof.
This software is provided on an "as is" basis, without warranty of any
kind, either expressed or implied, as to any matter including, but not
limited to warranty of fitness of purpose, or merchantability, or
results obtained from use of this software.
[ext/socket/addrinfo.h]
[ext/socket/getaddrinfo.c]
[ext/socket/getnameinfo.c]
These files are under the new-style BSD license.
>>>
Copyright (C) 1995, 1996, 1997, 1998, and 1999:: WIDE Project.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the project nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
[ext/win32ole/win32ole.c]
You can apply the Artistic License to this file. (or GPL,
alternatively)
>>>
(c) 1995:: Microsoft Corporation. All rights reserved.
Developed by ActiveWare Internet Corp., http://www.ActiveWare.com
Other modifications Copyright (c) 1997, 1998:: by Gurusamy Sarathy
<gsar@umich.edu> and Jan Dubois <jan.dubois@ibm.net>
You may distribute under the terms of either the GNU General Public
License or the Artistic License, as specified in the README file
of the Perl distribution.
The Wayback Machine url: http://web.archive.org/web/19970607104352/http://www.activeware.com:80/
[lib/rdoc/generator/template/darkfish/css/fonts.css]
This file is licensed under the {SIL Open Font License}[http://scripts.sil.org/OFL].
[spec/mspec]
[spec/ruby]
The files under these directories are under the following license.
>>>
Copyright (c) 2008:: Engine Yard, Inc. All rights reserved.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
[lib/rubygems.rb]
[lib/rubygems]
[test/rubygems]
RubyGems is under the following license.
>>>
RubyGems is copyrighted free software by Chad Fowler, Rich Kilmer, Jim
Weirich and others. You can redistribute it and/or modify it under
either the terms of the {MIT license}[rdoc-label:label-MIT+License], or the conditions
below:
1. You may make and give away verbatim copies of the source form of the
software without restriction, provided that you duplicate all of the
original copyright notices and associated disclaimers.
2. You may modify your copy of the software in any way, provided that
you do at least ONE of the following:
a. place your modifications in the Public Domain or otherwise
make them Freely Available, such as by posting said
modifications to Usenet or an equivalent medium, or by allowing
the author to include your modifications in the software.
b. use the modified software only within your corporation or
organization.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
3. You may distribute the software in object code or executable
form, provided that you do at least ONE of the following:
a. distribute the executables and library files of the software,
together with instructions (in the manual page or equivalent)
on where to get the original distribution.
b. accompany the distribution with the machine-readable source of
the software.
c. give non-standard executables non-standard names, with
instructions on where to get the original software distribution.
d. make other distribution arrangements with the author.
4. You may modify and include the part of the software into any other
software (possibly commercial).
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
[lib/bundler]
[lib/bundler.rb]
[spec/bundler]
Bundler is under the following license.
>>>
Portions copyright (c) 2010:: Andre Arko
Portions copyright (c) 2009:: Engine Yard
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/thor]
Thor is under the following license.
>>>
Copyright (c) 2008 Yehuda Katz, Eric Hodel, et al.
{MIT License}[rdoc-label:label-MIT+License]
[lib/rubygems/resolver/molinillo]
molinillo is under the following license.
>>>
Copyright (c) 2014 Samuel E. Giddins segiddins@segiddins.me
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/pub_grub]
pub_grub is under the following license.
>>>
Copyright (c) 2018 John Hawthorn
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/connection_pool]
connection_pool is under the following license.
>>>
Copyright (c) 2011 Mike Perham
{MIT License}[rdoc-label:label-MIT+License]
[lib/bundler/vendor/net-http-persistent]
net-http-persistent is under the following license.
>>>
Copyright (c) Eric Hodel, Aaron Patterson
{MIT License}[rdoc-label:label-MIT+License]
[lib/did_you_mean]
[lib/did_you_mean.rb]
[test/did_you_mean]
did_you_mean is under the following license.
>>>
Copyright (c) 2014-2016 Yuki Nishijima
{MIT License}[rdoc-label:label-MIT+License]
[lib/error_highlight]
[lib/error_highlight.rb]
[test/error_highlight]
error_highlight is under the following license.
>>>
Copyright (c) 2021 Yusuke Endoh
{MIT License}[rdoc-label:label-MIT+License]
[benchmark/so_ackermann.rb]
[benchmark/so_array.rb]
[benchmark/so_binary_trees.rb]
[benchmark/so_concatenate.rb]
[benchmark/so_count_words.yml]
[benchmark/so_exception.rb]
[benchmark/so_fannkuch.rb]
[benchmark/so_fasta.rb]
[benchmark/so_k_nucleotide.yml]
[benchmark/so_lists.rb]
[benchmark/so_mandelbrot.rb]
[benchmark/so_matrix.rb]
[benchmark/so_meteor_contest.rb]
[benchmark/so_nbody.rb]
[benchmark/so_nested_loop.rb]
[benchmark/so_nsieve.rb]
[benchmark/so_nsieve_bits.rb]
[benchmark/so_object.rb]
[benchmark/so_partial_sums.rb]
[benchmark/so_pidigits.rb]
[benchmark/so_random.rb]
[benchmark/so_reverse_complement.yml]
[benchmark/so_sieve.rb]
[benchmark/so_spectralnorm.rb]
These files are very old copy of then-called "The Great Computer Language
Shootout". LEGAL SITUATION OF THESE FILES ARE UNCLEAR because the original
site has been lost. Upstream diverged to delete several benchmarks listed
above.
== MIT License
>>>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
== Old-style BSD license
>>>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
IMPORTANT NOTE::
From ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change
paragraph 3 above is now null and void.
share/licenses/alt-ruby32-libs/GPL 0000644 00000043254 15173547341 0012675 0 ustar 00 GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
share/systemtap/tapset/libruby.so.3.2.stp 0000644 00000020726 15173547341 0014341 0 ustar 00 /* SystemTap tapset to make it easier to trace Ruby 2.0
*
* All probes provided by Ruby can be listed using following command
* (the path to the library must be adjuste appropriately):
*
* stap -L 'process("/opt/alt/ruby32/lib*\/libruby.so.3.2").mark("*")'
*/
/**
* probe ruby.array.create - Allocation of new array.
*
* @size: Number of elements (an int)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.array.create =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("array__create")
{
size = $arg1
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.cmethod.entry - Fired just before a method implemented in C is entered.
*
* @classname: Name of the class (string)
* @methodname: The method about bo be executed (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.cmethod.entry =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("cmethod__entry")
{
classname = user_string($arg1)
methodname = user_string($arg2)
file = user_string($arg3)
line = $arg4
}
/**
* probe ruby.cmethod.return - Fired just after a method implemented in C has returned.
*
* @classname: Name of the class (string)
* @methodname: The executed method (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.cmethod.return =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("cmethod__return")
{
classname = user_string($arg1)
methodname = user_string($arg2)
file = user_string($arg3)
line = $arg4
}
/**
* probe ruby.find.require.entry - Fired when require starts to search load
* path for suitable file to require.
*
* @requiredfile: The name of the file to be required (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.find.require.entry =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("find__require__entry")
{
requiredfile = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.find.require.return - Fired just after require has finished
* search of load path for suitable file to require.
*
* @requiredfile: The name of the file to be required (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.find.require.return =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("find__require__return")
{
requiredfile = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.gc.mark.begin - Fired when a GC mark phase is about to start.
*
* It takes no arguments.
*/
probe ruby.gc.mark.begin =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("gc__mark__begin")
{
}
/**
* probe ruby.gc.mark.end - Fired when a GC mark phase has ended.
*
* It takes no arguments.
*/
probe ruby.gc.mark.end =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("gc__mark__end")
{
}
/**
* probe ruby.gc.sweep.begin - Fired when a GC sweep phase is about to start.
*
* It takes no arguments.
*/
probe ruby.gc.sweep.begin =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("gc__sweep__begin")
{
}
/**
* probe ruby.gc.sweep.end - Fired when a GC sweep phase has ended.
*
* It takes no arguments.
*/
probe ruby.gc.sweep.end =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("gc__sweep__end")
{
}
/**
* probe ruby.hash.create - Allocation of new hash.
*
* @size: Number of elements (int)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.hash.create =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("hash__create")
{
size = $arg1
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.load.entry - Fired when calls to "load" are made.
*
* @loadedfile: The name of the file to be loaded (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.load.entry =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("load__entry")
{
loadedfile = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.load.return - Fired just after require has finished
* search of load path for suitable file to require.
*
* @loadedfile: The name of the file that was loaded (string)
*/
probe ruby.load.return =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("load__return")
{
loadedfile = user_string($arg1)
}
/**
* probe ruby.method.entry - Fired just before a method implemented in Ruby is entered.
*
* @classname: Name of the class (string)
* @methodname: The method about bo be executed (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.method.entry =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("method__entry")
{
classname = user_string($arg1)
methodname = user_string($arg2)
file = user_string($arg3)
line = $arg4
}
/**
* probe ruby.method.return - Fired just after a method implemented in Ruby has returned.
*
* @classname: Name of the class (string)
* @methodname: The executed method (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.method.return =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("method__return")
{
classname = user_string($arg1)
methodname = user_string($arg2)
file = user_string($arg3)
line = $arg4
}
/**
* probe ruby.object.create - Allocation of new object.
*
* @classname: Name of the class (string)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.object.create =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("object__create")
{
classname = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.parse.begin - Fired just before a Ruby source file is parsed.
*
* @parsedfile: The name of the file to be parsed (string)
* @parsedline: The line number of beginning of parsing (int)
*/
probe ruby.parse.begin =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("parse__begin")
{
parsedfile = user_string($arg1)
parsedline = $arg2
}
/**
* probe ruby.parse.end - Fired just after a Ruby source file was parsed.
*
* @parsedfile: The name of parsed the file (string)
* @parsedline: The line number of beginning of parsing (int)
*/
probe ruby.parse.end =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("parse__end")
{
parsedfile = user_string($arg1)
parsedline = $arg2
}
/**
* probe ruby.raise - Fired when an exception is raised.
*
* @classname: The class name of the raised exception (string)
* @file: The name of the file where the exception was raised (string)
* @line: The line number in the file where the exception was raised (int)
*/
probe ruby.raise =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("raise")
{
classname = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.require.entry - Fired on calls to rb_require_safe (when a file
* is required).
*
* @requiredfile: The name of the file to be required (string)
* @file: The file that called "require" (string)
* @line: The line number where the call to require was made(int)
*/
probe ruby.require.entry =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("require__entry")
{
requiredfile = user_string($arg1)
file = user_string($arg2)
line = $arg3
}
/**
* probe ruby.require.return - Fired just after require has finished
* search of load path for suitable file to require.
*
* @requiredfile: The file that was required (string)
*/
probe ruby.require.return =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("require__return")
{
requiredfile = user_string($arg1)
}
/**
* probe ruby.string.create - Allocation of new string.
*
* @size: Number of elements (an int)
* @file: The file name where the method is being called (string)
* @line: The line number where the method is being called (int)
*/
probe ruby.string.create =
process("/opt/alt/ruby32/lib*/libruby.so.3.2").mark("string__create")
{
size = $arg1
file = user_string($arg2)
line = $arg3
}
share/gems/cache/rackup-2.1.0.gem 0000644 00000037000 15173547341 0012271 0 ustar 00 metadata.gz 0000444 0000000 0000000 00000001271 14364637605 013455 0 ustar 00wheel wheel 0000000 0000000 � �?�c�W]o�0}��0{�S>�
�,1 �@�eC �Pt��&f�cl��B�۹��ڮe�+K+%���>>>��(���拤�?a��B�Y,�Ldc���;(�[���<�bG��C�2iO�Ya�8M�Pm�����'���=!�С^��s0��l~n�C�riJ��;+Ѕ��AR>�1��ӣ(�D�G<ME�����߳-�M!��|{ϖ�j��,4��k觫bJ�H��:$��ɓ�>�����K� K|�kM� ���R��(�+�
{!|C}�`��_�����O����(�P�5�B�w'�k"�8G�ر�����e��Ȁ>����*�G�S�RM*�Z�B�d�{�ߍ�#o�4�^�+��s����K�.����m�\
;�qo�MW��J
d�l�l&����mC��^ĕ� ��_gc����*�ګ���+<)*�Ӳq�UN�Fv���R�C�[�(�/H&�u�I�P��ٓ�N�EՍF9��z�$�u��E�QF���zM_�|�4��<�����4>�R�F�Y?%M?�ä���,��o��/�2��]snٴ�Un����[;���g�D���=���|;Ŷ�� �2�T�9.���U�1����?�t���'N��ɑ��{��&���jl�Ǭ[3�^��t��~ data.tar.gz 0000444 0000000 0000000 00000026114 14364637605 013376 0 ustar 00wheel wheel 0000000 0000000 � �?�c�}�v7�`�.��2!�Pԇ�/�6%�mIԐ�G��M;jv���9�߾��9�o�>��}��$[U ������q�L�4K�( ��P�*T�lwͷ���⛯�������o��O���ƃ�o6��?�zx���u�۸�7l��?�'
B�T.g�;��T��?��ʽ�(��F��{��h�,����}��0}�=:v�}�i�Џx����l�}�X�}�Y��Xs�\G�R�~7�}�_p��K���#��y[�Y�ā��G_��nm����o=z�y��_��+��n|�"w�}�8;�=�n� ��-��}6�ls}ss��_g�%�[�;��8�5�ҥ廬����������V��K��c���P�DB��ҧ�GH��+��7�N�K�Mu�͚�Ts%�NJw���a��F��O�Ç[w�����ܒ���/Vp��e/:�Ww�o���*����R����Gw����,��w�B�݅ P؞[����K>���y�f��])���g��������������9Z�[a�KM<���=���w2������7�j���v�N4�S
�����l�#v_u�Z1TD��,��sf���I� l��(������b�ȭΗ�bQ{�� k~��y�D�尙̠��8[X�+��r4�����=�6���>�|[�f%m�e����La��>_���ș�,1���z�Pl�@�
���)��L��"h��#H�@���aC�y�O+@2&��]I���7,��bb���\��i�jkr�vH���x���m�wK(�� ���3V�u�M-'�
GT��8<����7�sU�O��*
��:9%J�nlv�wc�ӝ|���_��@��8��Jھ�qk+�s�z>oh��GW��4F0k�� �:��7ɞ�E* /�#$� Y⛴P��$I�l�/0�&�D�Ϛ�D�m��
�Uc1@���f��:�;h�`���C�=N�$o*��~������} ��]]Ds'�I
�֘���TcᓰҚ�ܐ5�6w�Aނ��~2�²k$Ef��g��� ��ǖ�[ˏ̋��,e �J; �1&�G�h��Pl�d�5d=ƞ��ѓ
�Rg�2�� ���2�ٔ��\��RU�7$�Ԭ�4�`d�[��x��K�l̵��eM��UK����~Жc�:Ͳk°����H��l�"g�Vp��D��>�d+��1���n�v�(��Ê�0�hd�>@��ѻs)'G�s���mK�:���
&&rP�A�ǐ�D
*�+l�[n ��j3&Y�X1���)��M�smPp��v8�B]6�.�G6�jRN��9E!~��3m�煂�n��;'�\��@3Ō/=~�U��W�_�X~R2Y��� �^�)�vGz9���'v�O&�O1�����V���.��`F�7,������Y��k;i��t��Z�}f_��/ Vyc�]�c�R�U^���
����R��M���6�/�s�m
AD�V+%���e�?�y_��o��z�{�����_��˱y�R@�M�c҆��\�.*`��mVg�/L� �ѿ
�6F���0��ݠ�f �i;����P���������`p<��
�G��A���`�\�!O�����+4E�eک�%P�fVmD� l�UM�=�st�v�ހ㮧�ڽ^��7���g������a[����<