/home/lnzliplg/public_html/ruby33.tar
share/man/man5/gemfile.5 0000644 00000056000 15173517734 0010775 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "GEMFILE" "5" "September 2024" ""
.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
.P
The version file should conform to any of the following formats:
.IP "\(bu" 4
\fB3\.1\.2\fR (\.ruby\-version)
.IP "\(bu" 4
\fBruby 3\.1\.2\fR (\.tool\-versions, read: https://asdf\-vm\.com/manage/configuration\.html#tool\-versions)
.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?
.IP "\(bu" 4
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 \fIhttps://www\.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\.
.P
The following platform values are deprecated and should be replaced with \fBwindows\fR:
.IP "\(bu" 4
\fBmswin\fR, \fBmswin64\fR, \fBmingw32\fR, \fBx64_mingw\fR
.IP "" 0
.P
Note that, while unfortunately using the same terminology, the values of this option are different from the values that \fBbundle lock \-\-add\-platform\fR can take\. The values of this option are more closer to "Ruby Implementation" while the values that \fBbundle lock \-\-add\-platform\fR understands are more related to OS and architecture of the different systems where your lockfile will be used\.
.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 \fIhttps://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 00000001664 15173517734 0011605 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-LIST" "1" "September 2024" ""
.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 00000006106 15173517734 0011556 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-LOCK" "1" "September 2024" ""
.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 00000032551 15173517734 0012113 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-UPDATE" "1" "September 2024" ""
.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 00000001326 15173517734 0011707 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-CLEAN" "1" "September 2024" ""
.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 00000004162 15173517734 0012124 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-PLUGIN" "1" "September 2024" ""
.SH "NAME"
\fBbundle\-plugin\fR \- Manage Bundler plugins
.SH "SYNOPSIS"
\fBbundle plugin\fR install PLUGINS [\-\-source=\fISOURCE\fR] [\-\-version=\fIversion\fR] [\-\-git=\fIgit\-url\fR] [\-\-branch=\fIbranch\fR|\-\-ref=\fIrev\fR] [\-\-path=\fIpath\fR]
.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)\.
.TP
\fBbundle plugin install bundler\-graph\fR
Install bundler\-graph gem from globally configured sources (defaults to RubyGems\.org)\. The global source, specified in source in Gemfile is ignored\.
.TP
\fBbundle plugin install bundler\-graph \-\-source https://example\.com\fR
Install bundler\-graph gem from example\.com\. The global source, specified in source in Gemfile is not considered\.
.TP
\fBbundle plugin install bundler\-graph \-\-version 0\.2\.1\fR
You can specify the version of the gem via \fB\-\-version\fR\.
.TP
\fBbundle plugin install bundler\-graph \-\-git https://github\.com/rubygems/bundler\-graph\fR
Install bundler\-graph gem from Git repository\. You can use standard Git URLs like:
.IP
\fBssh://[user@]host\.xz[:port]/path/to/repo\.git\fR
.br
\fBhttp[s]://host\.xz[:port]/path/to/repo\.git\fR
.br
\fB/path/to/repo\fR
.br
\fBfile:///path/to/repo\fR
.IP
When you specify \fB\-\-git\fR, you can use \fB\-\-branch\fR or \fB\-\-ref\fR to specify any branch, tag, or commit hash (revision) to use\.
.TP
\fBbundle plugin install bundler\-graph \-\-path \.\./bundler\-graph\fR
Install bundler\-graph gem from a local path\.
.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 15173517734 0010140 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 00000000726 15173517734 0011563 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-INFO" "1" "September 2024" ""
.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 00000001426 15173517734 0011567 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-OPEN" "1" "September 2024" ""
.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 00000001457 15173517734 0012106 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-INJECT" "1" "September 2024" ""
.SH "NAME"
\fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
.SH "SYNOPSIS"
\fBbundle inject\fR [GEM] [VERSION]
.SH "DESCRIPTION"
Adds the named gem(s) with their version requirements to the resolved [\fBGemfile(5)\fR][Gemfile(5)]\.
.P
This command will add the gem to both your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock if it isn't listed yet\.
.P
Example:
.IP "" 4
.nf
bundle install
bundle inject 'rack' '> 0'
.fi
.IP "" 0
.P
This will inject the 'rack' gem with a version greater than 0 in your [\fBGemfile(5)\fR][Gemfile(5)] and Gemfile\.lock\.
.P
The \fBbundle inject\fR command was deprecated in Bundler 2\.1 and will be removed in Bundler 3\.0\.
share/man/man1/ruby.1 0000644 00000052046 15173517734 0010344 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 -crash-report Ns = Ns Ar template
.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 -backtrace-limit Ns = Ns Ar num
Limits the maximum length of backtraces to
.Ar num
lines (default -1, meaning no limit).
.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
This option is not guaranteed to be compatible.
.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.
.Pp
.It Fl -crash-report Ns = Ns Ar template
Sets the template of path name to save crash report.
See
.Ev RUBY_CRASH_REPORT
environment variable for details.
.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. Applies to all slot sizes. Introduced in Ruby 2.1, default: 10000.
.Pp
.It Ev RUBY_GC_HEAP_%d_INIT_SLOTS
Initial allocation of slots in a specific heap.
The available heaps can be found in the keys of `GC.stat_heap`.
Introduced in Ruby 3.3.
.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 CRASH REPORT ENVIRONMENT
.Pp
.Bl -tag -compact -width "RUBY_CRASH_REPORT"
.It Ev RUBY_CRASH_REPORT
The template of path name to save crash report.
default: none
.El
.Ss Naming crash report files
The template can contain
.Li \fB%\fP
specifiers which are substituted by the following values when a crash
report file is created:
.Pp
.Bl -hang -compact -width "%NNN"
.It Li \fB%%\fP
A single
.Li \fB%\fP
character.
.It Li \fB%e\fP
Basename of executable.
.It Li \fB%E\fP
Pathname of executable,
with slashes (\fB/\fP) replaced by exclamation marks (\fB!\fP).
.It Li \fB%f\fP
Basename of the program name,
.Li "$0" .
.It Li \fB%F\fP
Pathname of the program name,
.Li "$0",
with slashes (\fB/\fP) replaced by exclamation marks (\fB!\fP).
.It Li \fB%p\fP
PID of dumped process.
.It Li \fB%t\fP
Time of dump, expressed as seconds since the
Epoch, 1970-01-01 00:00:00 +0000 (UTC).
.It Li \fB%NNN\fP
A character code in octal.
.El
.Pp
A single
.Li \fB%\fP
at the end of the template is dropped from the core filename, as is
the combination of a
.Li \fB%\fP
followed by any character other than those listed above. All other
characters in the template become a literal part of the core filename.
The template may include \(aq/\(aq characters, which are interpreted
as delimiters for directory names.
.Ss Piping crash reports to a program
If the first character of this file is a pipe symbol (\fB|\fP),
then the remainder of the line is interpreted as the command-line for
a program (or script) that is to be executed.
.Pp
The pipe template is split on spaces into an argument list before the
template parameters are expanded.
.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://github.com/ruby/ruby/graphs/contributors
for contributors to Ruby.
share/man/man1/bundle-viz.1 0000644 00000002316 15173517734 0011435 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-VIZ" "1" "September 2024" ""
.SH "NAME"
\fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
.SH "SYNOPSIS"
\fBbundle viz\fR [\-\-file=FILE] [\-\-format=FORMAT] [\-\-requirements] [\-\-version] [\-\-without=GROUP GROUP]
.SH "DESCRIPTION"
\fBviz\fR generates a PNG file of the current \fBGemfile(5)\fR as a dependency graph\. \fBviz\fR requires the ruby\-graphviz gem (and its dependencies)\.
.P
The associated gems must also be installed via \fBbundle install(1)\fR \fIbundle\-install\.1\.html\fR\.
.P
\fBviz\fR command was deprecated in Bundler 2\.2\. Use bundler\-graph plugin \fIhttps://github\.com/rubygems/bundler\-graph\fR instead\.
.SH "OPTIONS"
.TP
\fB\-\-file\fR, \fB\-f\fR
The name to use for the generated file\. See \fB\-\-format\fR option
.TP
\fB\-\-format\fR, \fB\-F\fR
This is output format option\. Supported format is png, jpg, svg, dot \|\.\|\.\|\.
.TP
\fB\-\-requirements\fR, \fB\-R\fR
Set to show the version of each required dependency\.
.TP
\fB\-\-version\fR, \fB\-v\fR
Set to show each gem version\.
.TP
\fB\-\-without\fR, \fB\-W\fR
Exclude gems that are part of the specified named group\.
share/man/man1/bundle-console.1 0000644 00000003167 15173517734 0012274 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-CONSOLE" "1" "September 2024" ""
.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 15173517734 0007771 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 00000001152 15173517734 0012307 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-VERSION" "1" "September 2024" ""
.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 00000002107 15173517734 0011700 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-CHECK" "1" "September 2024" ""
.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\.
.P
If the lockfile needs to be updated then it will be resolved using the gems installed on the local machine, if they satisfy the requirements\.
.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 00000002161 15173517734 0012115 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-DOCTOR" "1" "September 2024" ""
.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 15173517734 0010303 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 00000002613 15173517734 0012451 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-PLATFORM" "1" "September 2024" ""
.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 00000052017 15173517734 0012075 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-CONFIG" "1" "September 2024" ""
.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_offline_install\fR (\fBBUNDLE_ALLOW_OFFLINE_INSTALL\fR): Allow Bundler to use cached data when installing without network access\.
.IP "\(bu" 4
\fBauto_clean_without_path\fR (\fBBUNDLE_AUTO_CLEAN_WITHOUT_PATH\fR): Automatically run \fBbundle clean\fR after installing when an explicit \fBpath\fR has not been set and Bundler is not installing into the system gems\.
.IP "\(bu" 4
\fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR): Automatically run \fBbundle install\fR when gems are missing\.
.IP "\(bu" 4
\fBbin\fR (\fBBUNDLE_BIN\fR): Install executables from gems in the bundle to the specified directory\. Defaults to \fBfalse\fR\.
.IP "\(bu" 4
\fBcache_all\fR (\fBBUNDLE_CACHE_ALL\fR): Cache all gems, including path and git gems\. This needs to be explicitly configured on bundler 1 and bundler 2, but will be the default on bundler 3\.
.IP "\(bu" 4
\fBcache_all_platforms\fR (\fBBUNDLE_CACHE_ALL_PLATFORMS\fR): Cache gems for all platforms\.
.IP "\(bu" 4
\fBcache_path\fR (\fBBUNDLE_CACHE_PATH\fR): The directory that bundler will place cached gems in when running \fBbundle package\fR, and that bundler will look in when installing gems\. Defaults to \fBvendor/cache\fR\.
.IP "\(bu" 4
\fBclean\fR (\fBBUNDLE_CLEAN\fR): Whether Bundler should run \fBbundle clean\fR automatically after \fBbundle install\fR\.
.IP "\(bu" 4
\fBconsole\fR (\fBBUNDLE_CONSOLE\fR): The console that \fBbundle console\fR starts\. Defaults to \fBirb\fR\.
.IP "\(bu" 4
\fBdefault_install_uses_path\fR (\fBBUNDLE_DEFAULT_INSTALL_USES_PATH\fR): Whether a \fBbundle install\fR without an explicit \fB\-\-path\fR argument defaults to installing gems in \fB\.bundle\fR\.
.IP "\(bu" 4
\fBdeployment\fR (\fBBUNDLE_DEPLOYMENT\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\.
.IP "\(bu" 4
\fBdisable_checksum_validation\fR (\fBBUNDLE_DISABLE_CHECKSUM_VALIDATION\fR): Allow installing gems even if they do not match the checksum provided by RubyGems\.
.IP "\(bu" 4
\fBdisable_exec_load\fR (\fBBUNDLE_DISABLE_EXEC_LOAD\fR): Stop Bundler from using \fBload\fR to launch an executable in\-process in \fBbundle exec\fR\.
.IP "\(bu" 4
\fBdisable_local_branch_check\fR (\fBBUNDLE_DISABLE_LOCAL_BRANCH_CHECK\fR): Allow Bundler to use a local git override without a branch specified in the Gemfile\.
.IP "\(bu" 4
\fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
.IP "\(bu" 4
\fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems' normal location\.
.IP "\(bu" 4
\fBdisable_version_check\fR (\fBBUNDLE_DISABLE_VERSION_CHECK\fR): Stop Bundler from checking if a newer Bundler version is available on rubygems\.org\.
.IP "\(bu" 4
\fBforce_ruby_platform\fR (\fBBUNDLE_FORCE_RUBY_PLATFORM\fR): Ignore the current machine's platform and install only \fBruby\fR platform gems\. As a result, gems with native extensions will be compiled from source\.
.IP "\(bu" 4
\fBfrozen\fR (\fBBUNDLE_FROZEN\fR): Disallow changes to the \fBGemfile\fR\. When the \fBGemfile\fR is changed and the lockfile has not been updated, running Bundler commands will be blocked\. Defaults to \fBtrue\fR when \fB\-\-deployment\fR is used\.
.IP "\(bu" 4
\fBgem\.github_username\fR (\fBBUNDLE_GEM__GITHUB_USERNAME\fR): Sets a GitHub username or organization to be used in \fBREADME\fR file when you create a new gem via \fBbundle gem\fR command\. It can be overridden by passing an explicit \fB\-\-github\-username\fR flag to \fBbundle gem\fR\.
.IP "\(bu" 4
\fBgem\.push_key\fR (\fBBUNDLE_GEM__PUSH_KEY\fR): Sets the \fB\-\-key\fR parameter for \fBgem push\fR when using the \fBrake release\fR command with a private gemstash server\.
.IP "\(bu" 4
\fBgemfile\fR (\fBBUNDLE_GEMFILE\fR): The name of the file that bundler should use as the \fBGemfile\fR\. This location of this file also sets the root of the project, which is used to resolve relative paths in the \fBGemfile\fR, among other things\. By default, bundler will search up from the current working directory until it finds a \fBGemfile\fR\.
.IP "\(bu" 4
\fBglobal_gem_cache\fR (\fBBUNDLE_GLOBAL_GEM_CACHE\fR): Whether Bundler should cache all gems globally, rather than locally to the installing Ruby installation\.
.IP "\(bu" 4
\fBignore_funding_requests\fR (\fBBUNDLE_IGNORE_FUNDING_REQUESTS\fR): When set, no funding requests will be printed\.
.IP "\(bu" 4
\fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
.IP "\(bu" 4
\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
.IP "\(bu" 4
\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to the number of available processors\.
.IP "\(bu" 4
\fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
.IP "\(bu" 4
\fBno_prune\fR (\fBBUNDLE_NO_PRUNE\fR): Whether Bundler should leave outdated gems unpruned when caching\.
.IP "\(bu" 4
\fBonly\fR (\fBBUNDLE_ONLY\fR): A space\-separated list of groups to install only gems of the specified groups\.
.IP "\(bu" 4
\fBpath\fR (\fBBUNDLE_PATH\fR): The location on disk where all gems in your bundle will be located regardless of \fB$GEM_HOME\fR or \fB$GEM_PATH\fR values\. Bundle gems not found in this location will be installed by \fBbundle install\fR\. Defaults to \fBGem\.dir\fR\. When \-\-deployment is used, defaults to vendor/bundle\.
.IP "\(bu" 4
\fBpath\.system\fR (\fBBUNDLE_PATH__SYSTEM\fR): Whether Bundler will install gems into the default system path (\fBGem\.dir\fR)\.
.IP "\(bu" 4
\fBpath_relative_to_cwd\fR (\fBBUNDLE_PATH_RELATIVE_TO_CWD\fR) Makes \fB\-\-path\fR relative to the CWD instead of the \fBGemfile\fR\.
.IP "\(bu" 4
\fBplugins\fR (\fBBUNDLE_PLUGINS\fR): Enable Bundler's experimental plugin system\.
.IP "\(bu" 4
\fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
.IP "\(bu" 4
\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.
.IP "\(bu" 4
\fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
.IP "\(bu" 4
\fBretry\fR (\fBBUNDLE_RETRY\fR): The number of times to retry failed network requests\. Defaults to \fB3\fR\.
.IP "\(bu" 4
\fBsetup_makes_kernel_gem_public\fR (\fBBUNDLE_SETUP_MAKES_KERNEL_GEM_PUBLIC\fR): Have \fBBundler\.setup\fR make the \fBKernel#gem\fR method public, even though RubyGems declares it as private\.
.IP "\(bu" 4
\fBshebang\fR (\fBBUNDLE_SHEBANG\fR): The program name that should be invoked for generated binstubs\. Defaults to the ruby install name used to generate the binstub\.
.IP "\(bu" 4
\fBsilence_deprecations\fR (\fBBUNDLE_SILENCE_DEPRECATIONS\fR): Whether Bundler should silence deprecation warnings for behavior that will be changed in the next major version\.
.IP "\(bu" 4
\fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR): Silence the warning Bundler prints when installing gems as root\.
.IP "\(bu" 4
\fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\.
.IP "\(bu" 4
\fBssl_client_cert\fR (\fBBUNDLE_SSL_CLIENT_CERT\fR): Path to a designated file containing a X\.509 client certificate and key in PEM format\.
.IP "\(bu" 4
\fBssl_verify_mode\fR (\fBBUNDLE_SSL_VERIFY_MODE\fR): The SSL verification mode Bundler uses when making HTTPS requests\. Defaults to verify peer\.
.IP "\(bu" 4
\fBsystem_bindir\fR (\fBBUNDLE_SYSTEM_BINDIR\fR): The location where RubyGems installs binstubs\. Defaults to \fBGem\.bindir\fR\.
.IP "\(bu" 4
\fBtimeout\fR (\fBBUNDLE_TIMEOUT\fR): The seconds allowed before timing out for network requests\. Defaults to \fB10\fR\.
.IP "\(bu" 4
\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
.IP "\(bu" 4
\fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
.IP "\(bu" 4
\fBversion\fR (\fBBUNDLE_VERSION\fR): The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\.
.IP "\(bu" 4
\fBwith\fR (\fBBUNDLE_WITH\fR): A \fB:\fR\-separated list of groups whose gems bundler should install\.
.IP "\(bu" 4
\fBwithout\fR (\fBBUNDLE_WITHOUT\fR): A \fB:\fR\-separated list of groups whose gems bundler should not install\.
.IP "" 0
.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 underscore (\fB___\fR) in the corresponding environment variable\.
.IP "\(bu" 4
Any \fB\.\fR characters in a host name are mapped to a double underscore (\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, cache and plugin directories and config file can 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 00000006265 15173517734 0012445 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-OUTDATED" "1" "September 2024" ""
.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 00000003166 15173517734 0012466 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-PRISTINE" "1" "September 2024" ""
.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 00000001245 15173517734 0011605 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-SHOW" "1" "September 2024" ""
.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 00000040672 15173517734 0012302 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-INSTALL" "1" "September 2024" ""
.SH "NAME"
\fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
.SH "SYNOPSIS"
\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=POLICY] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]]
.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 \fIhttps://guides\.rubygems\.org/rubygems\-basics/#installing\-gems\fR
.IP "\(bu" 4
Rubygems signing docs \fIhttps://guides\.rubygems\.org/security/\fR
.IP "" 0
share/man/man1/bundle-cache.1 0000644 00000006564 15173517734 0011701 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-CACHE" "1" "September 2024" ""
.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 00000004773 15173517734 0011366 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-ADD" "1" "September 2024" ""
.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 [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
.SH "OPTIONS"
.TP
\fB\-\-version\fR, \fB\-v\fR
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\.
.SH "EXAMPLES"
.IP "1." 4
You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.
.IP
\fBbundle add rails\fR
.IP "2." 4
You can add the \fBrails\fR gem with version greater than 1\.1 (not including 1\.1) and less than 3\.0\.
.IP
\fBbundle add rails \-\-version "> 1\.1, < 3\.0"\fR
.IP "3." 4
You can use the \fBhttps://gems\.example\.com\fR custom source and assign the gem to a group\.
.IP
\fBbundle add rails \-\-version "~> 5\.0\.0" \-\-source "https://gems\.example\.com" \-\-group "development"\fR
.IP "4." 4
The following adds the \fBgem\fR entry to the Gemfile without installing the gem\. You can install gems later via \fBbundle install\fR\.
.IP
\fBbundle add rails \-\-skip\-install\fR
.IP "5." 4
You can assign the gem to more than one group\.
.IP
\fBbundle add rails \-\-group "development, test"\fR
.IP "" 0
.SH "SEE ALSO"
Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR
share/man/man1/bundle-remove.1 0000644 00000001504 15173517734 0012120 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-REMOVE" "1" "September 2024" ""
.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 00000000700 15173517734 0011550 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-HELP" "1" "September 2024" ""
.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 00000003067 15173517734 0012462 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-BINSTUBS" "1" "September 2024" ""
.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 00000014702 15173517734 0011553 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-EXEC" "1" "September 2024" ""
.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
Passes all file descriptors to the new processes\. Default is true from bundler version 2\.2\.26\. Setting it to false is now deprecated\.
.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 00000012750 15173517734 0011400 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-GEM" "1" "September 2024" ""
.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\-\-no\-test\fR: Do not use a test framework (overrides \fB\-\-test\fR specified in the global config)\.
.IP "\(bu" 4
\fB\-\-ci\fR, \fB\-\-ci=github\fR, \fB\-\-ci=gitlab\fR, \fB\-\-ci=circle\fR: Specify the continuous integration service that Bundler should use when generating the project\. Acceptable values are \fBgithub\fR, \fBgitlab\fR and \fBcircle\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to generate CI files, this defaults to Bundler's global config setting \fBgem\.ci\fR\.
.IP
When Bundler is configured to not generate CI files, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
.IP "\(bu" 4
\fB\-\-no\-ci\fR: Do not use a continuous integration service (overrides \fB\-\-ci\fR specified in the global config)\.
.IP "\(bu" 4
\fB\-\-linter\fR, \fB\-\-linter=rubocop\fR, \fB\-\-linter=standard\fR: Specify the linter and code formatter that Bundler should add to the project's development dependencies\. Acceptable values are \fBrubocop\fR and \fBstandard\fR\. A configuration file will be generated in the project directory\. Given no option is specified:
.IP
When Bundler is configured to add a linter, this defaults to Bundler's global config setting \fBgem\.linter\fR\.
.IP
When Bundler is configured not to add a linter, an interactive prompt will be displayed and the answer will be used for the current rubygem project\.
.IP
When Bundler is unconfigured, an interactive prompt will be displayed and the answer will be saved in Bundler's global config for future \fBbundle gem\fR use\.
.IP "\(bu" 4
\fB\-\-no\-linter\fR: Do not add a linter (overrides \fB\-\-linter\fR specified in the global config)\.
.IP "\(bu" 4
\fB\-e\fR, \fB\-\-edit[=EDITOR]\fR: Open the resulting GEM_NAME\.gemspec in EDITOR, or the default editor if not specified\. The default is \fB$BUNDLER_EDITOR\fR, \fB$VISUAL\fR, or \fB$EDITOR\fR\.
.IP "" 0
.SH "SEE ALSO"
.IP "\(bu" 4
bundle config(1) \fIbundle\-config\.1\.html\fR
.IP "" 0
share/man/man1/bundle.1 0000644 00000007260 15173517734 0010632 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE" "1" "September 2024" ""
.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 00000002176 15173517734 0011574 0 ustar 00 .\" generated with nRonn/v0.11.1
.\" https://github.com/n-ronn/nronn/tree/0.11.1
.TH "BUNDLE\-INIT" "1" "September 2024" ""
.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 00000235277 15173517734 0011211 0 ustar 00 # frozen_string_literal: true
begin
require 'rbconfig'
rescue LoadError
# for make rjit-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.2"
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"
#
# 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
#
# 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+.
#
# 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
#
# 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.
#
# 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
#
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
#
# 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.
#
# 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.
#
# 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
if d.end_with?('/')
mkdir_p d
copy_file s, d + File.basename(s)
else
mkdir_p File.expand_path('..', d)
copy_file s, d
end
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 00000044657 15173517734 0011143 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
VERSION = "0.3.0"
BENCHMARK_VERSION = "2002-04-25" # :nodoc:
# Invokes the block with a Benchmark::Report object, which
# may be used to collect and report on the results of individual
# benchmark tests. Reserves +label_width+ leading spaces for
# labels on each line. Prints +caption+ at the top of the
# report, and uses +format+ to format each line.
# (Note: +caption+ must contain a terminating newline character,
# see the default Benchmark::Tms::CAPTION for an example.)
#
# Returns an array of Benchmark::Tms objects.
#
# If the block returns an array of
# Benchmark::Tms objects, these will be used to format
# additional lines of output. If +labels+ parameter are
# given, these are used to label these extra lines.
#
# _Note_: Other methods provide a simpler interface to this one, and are
# suitable for nearly all benchmarking requirements. See the examples in
# Benchmark, and the #bm and #bmbm methods.
#
# Example:
#
# require 'benchmark'
# include Benchmark # we need the CAPTION and FORMAT constants
#
# n = 5000000
# Benchmark.benchmark(CAPTION, 7, FORMAT, ">total:", ">avg:") do |x|
# tf = x.report("for:") { for i in 1..n; a = "1"; end }
# tt = x.report("times:") { n.times do ; a = "1"; end }
# tu = x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# [tf+tt+tu, (tf+tt+tu)/3]
# end
#
# Generates:
#
# user system total real
# for: 0.970000 0.000000 0.970000 ( 0.970493)
# times: 0.990000 0.000000 0.990000 ( 0.989542)
# upto: 0.970000 0.000000 0.970000 ( 0.972854)
# >total: 2.930000 0.000000 2.930000 ( 2.932889)
# >avg: 0.976667 0.000000 0.976667 ( 0.977630)
#
def benchmark(caption = "", label_width = nil, format = nil, *labels) # :yield: report
sync = $stdout.sync
$stdout.sync = true
label_width ||= 0
label_width += 1
format ||= FORMAT
print ' '*label_width + caption unless caption.empty?
report = Report.new(label_width, format)
results = yield(report)
Array === results and results.grep(Tms).each {|t|
print((labels.shift || t.label || "").ljust(label_width), t.format(format))
}
report.list
ensure
$stdout.sync = sync unless sync.nil?
end
# A simple interface to the #benchmark method, #bm generates sequential
# reports with labels. +label_width+ and +labels+ parameters have the same
# meaning as for #benchmark.
#
# require 'benchmark'
#
# n = 5000000
# Benchmark.bm(7) do |x|
# x.report("for:") { for i in 1..n; a = "1"; end }
# x.report("times:") { n.times do ; a = "1"; end }
# x.report("upto:") { 1.upto(n) do ; a = "1"; end }
# end
#
# Generates:
#
# user system total real
# for: 0.960000 0.000000 0.960000 ( 0.957966)
# times: 0.960000 0.000000 0.960000 ( 0.960423)
# upto: 0.950000 0.000000 0.950000 ( 0.954864)
#
def bm(label_width = 0, *labels, &blk) # :yield: report
benchmark(CAPTION, label_width, FORMAT, *labels, &blk)
end
# Sometimes benchmark results are skewed because code executed
# earlier encounters different garbage collection overheads than
# that run later. #bmbm attempts to minimize this effect by running
# the tests twice, the first time as a rehearsal in order to get the
# runtime environment stable, the second time for
# real. GC.start is executed before the start of each of
# the real timings; the cost of this is not included in the
# timings. In reality, though, there's only so much that #bmbm can
# do, and the results are not guaranteed to be isolated from garbage
# collection and other effects.
#
# Because #bmbm takes two passes through the tests, it can
# calculate the required label width.
#
# require 'benchmark'
#
# array = (1..1000000).map { rand }
#
# Benchmark.bmbm do |x|
# x.report("sort!") { array.dup.sort! }
# x.report("sort") { array.dup.sort }
# end
#
# Generates:
#
# Rehearsal -----------------------------------------
# sort! 1.440000 0.010000 1.450000 ( 1.446833)
# sort 1.440000 0.000000 1.440000 ( 1.448257)
# -------------------------------- total: 2.890000sec
#
# user system total real
# sort! 1.460000 0.000000 1.460000 ( 1.458065)
# sort 1.450000 0.000000 1.450000 ( 1.455963)
#
# #bmbm yields a Benchmark::Job object and returns an array of
# Benchmark::Tms objects.
#
def bmbm(width = 0) # :yield: job
job = Job.new(width)
yield(job)
width = job.width + 1
sync = $stdout.sync
$stdout.sync = true
# rehearsal
puts 'Rehearsal '.ljust(width+CAPTION.length,'-')
ets = job.list.inject(Tms.new) { |sum,(label,item)|
print label.ljust(width)
res = Benchmark.measure(&item)
print res.format
sum + res
}.format("total: %tsec")
print " #{ets}\n\n".rjust(width+CAPTION.length+2,'-')
# take
print ' '*width + CAPTION
job.list.map { |label,item|
GC.start
print label.ljust(width)
Benchmark.measure(label, &item).tap { |res| print res }
}
ensure
$stdout.sync = sync unless sync.nil?
end
#
# Returns the time used to execute the given block as a
# Benchmark::Tms object. Takes +label+ option.
#
# require 'benchmark'
#
# n = 1000000
#
# time = Benchmark.measure do
# n.times { a = "1" }
# end
# puts time
#
# Generates:
#
# 0.220000 0.000000 0.220000 ( 0.227313)
#
def measure(label = "") # :yield:
t0, r0 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
t1, r1 = Process.times, Process.clock_gettime(Process::CLOCK_MONOTONIC)
Benchmark::Tms.new(t1.utime - t0.utime,
t1.stime - t0.stime,
t1.cutime - t0.cutime,
t1.cstime - t0.cstime,
r1 - r0,
label)
end
#
# Returns the elapsed real time used to execute the given block.
#
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 00000006735 15173517734 0010445 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
VERSION = "0.1.2"
# 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 15173517734 0012427 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 00000057365 15173517734 0010147 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
VERSION = "0.3.0"
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 00000054036 15173517734 0010460 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 'fiber'
require 'monitor'
require 'rbconfig'
require_relative 'logger/version'
require_relative 'logger/formatter'
require_relative 'logger/log_device'
require_relative 'logger/severity'
require_relative 'logger/errors'
# \Class \Logger provides a simple but sophisticated logging utility that
# you can use to create one or more
# {event logs}[https://en.wikipedia.org/wiki/Logging_(software)#Event_logs]
# for your program.
# Each such log contains a chronological sequence of entries
# that provides a record of the program's activities.
#
# == About the Examples
#
# All examples on this page assume that \Logger has been required:
#
# require 'logger'
#
# == Synopsis
#
# Create a log with Logger.new:
#
# # Single log file.
# logger = Logger.new('t.log')
# # Size-based rotated logging: 3 10-megabyte files.
# logger = Logger.new('t.log', 3, 10485760)
# # Period-based rotated logging: daily (also allowed: 'weekly', 'monthly').
# logger = Logger.new('t.log', 'daily')
# # Log to an IO stream.
# logger = Logger.new($stdout)
#
# Add entries (level, message) with Logger#add:
#
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# logger.add(Logger::INFO, 'Non-error information')
# logger.add(Logger::WARN, 'Non-error warning')
# logger.add(Logger::ERROR, 'Non-fatal error')
# logger.add(Logger::FATAL, 'Fatal error')
# logger.add(Logger::UNKNOWN, 'Most severe')
#
# Close the log with Logger#close:
#
# logger.close
#
# == Entries
#
# You can add entries with method Logger#add:
#
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# logger.add(Logger::INFO, 'Non-error information')
# logger.add(Logger::WARN, 'Non-error warning')
# logger.add(Logger::ERROR, 'Non-fatal error')
# logger.add(Logger::FATAL, 'Fatal error')
# logger.add(Logger::UNKNOWN, 'Most severe')
#
# These shorthand methods also add entries:
#
# logger.debug('Maximal debugging info')
# logger.info('Non-error information')
# logger.warn('Non-error warning')
# logger.error('Non-fatal error')
# logger.fatal('Fatal error')
# logger.unknown('Most severe')
#
# When you call any of these methods,
# the entry may or may not be written to the log,
# depending on the entry's severity and on the log level;
# see {Log Level}[rdoc-ref:Logger@Log+Level]
#
# An entry always has:
#
# - A severity (the required argument to #add).
# - An automatically created timestamp.
#
# And may also have:
#
# - A message.
# - A program name.
#
# Example:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message.', 'mung')
# # => I, [2022-05-07T17:21:46.536234 #20536] INFO -- mung: My message.
#
# The default format for an entry is:
#
# "%s, [%s #%d] %5s -- %s: %s\n"
#
# where the values to be formatted are:
#
# - \Severity (one letter).
# - Timestamp.
# - Process id.
# - \Severity (word).
# - Program name.
# - Message.
#
# You can use a different entry format by:
#
# - Setting a custom format proc (affects following entries);
# see {formatter=}[Logger.html#attribute-i-formatter].
# - Calling any of the methods above with a block
# (affects only the one entry).
# Doing so can have two benefits:
#
# - Context: the block can evaluate the entire program context
# and create a context-dependent message.
# - Performance: the block is not evaluated unless the log level
# permits the entry actually to be written:
#
# logger.error { my_slow_message_generator }
#
# Contrast this with the string form, where the string is
# always evaluated, regardless of the log level:
#
# logger.error("#{my_slow_message_generator}")
#
# === \Severity
#
# The severity of a log entry has two effects:
#
# - Determines whether the entry is selected for inclusion in the log;
# see {Log Level}[rdoc-ref:Logger@Log+Level].
# - Indicates to any log reader (whether a person or a program)
# the relative importance of the entry.
#
# === Timestamp
#
# The timestamp for a log entry is generated automatically
# when the entry is created.
#
# The logged timestamp is formatted by method
# {Time#strftime}[rdoc-ref:Time#strftime]
# using this format string:
#
# '%Y-%m-%dT%H:%M:%S.%6N'
#
# Example:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO)
# # => I, [2022-05-07T17:04:32.318331 #20536] INFO -- : nil
#
# You can set a different format using method #datetime_format=.
#
# === Message
#
# The message is an optional argument to an entry method:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message')
# # => I, [2022-05-07T18:15:37.647581 #20536] INFO -- : My message
#
# For the default entry formatter, <tt>Logger::Formatter</tt>,
# the message object may be:
#
# - A string: used as-is.
# - An Exception: <tt>message.message</tt> is used.
# - Anything else: <tt>message.inspect</tt> is used.
#
# *Note*: Logger::Formatter does not escape or sanitize
# the message passed to it.
# Developers should be aware that malicious data (user input)
# may be in the message, and should explicitly escape untrusted data.
#
# You can use a custom formatter to escape message data;
# see the example at {formatter=}[Logger.html#attribute-i-formatter].
#
# === Program Name
#
# The program name is an optional argument to an entry method:
#
# logger = Logger.new($stdout)
# logger.add(Logger::INFO, 'My message', 'mung')
# # => I, [2022-05-07T18:17:38.084716 #20536] INFO -- mung: My message
#
# The default program name for a new logger may be set in the call to
# Logger.new via optional keyword argument +progname+:
#
# logger = Logger.new('t.log', progname: 'mung')
#
# The default program name for an existing logger may be set
# by a call to method #progname=:
#
# logger.progname = 'mung'
#
# The current program name may be retrieved with method
# {progname}[Logger.html#attribute-i-progname]:
#
# logger.progname # => "mung"
#
# == Log Level
#
# The log level setting determines whether an entry is actually
# written to the log, based on the entry's severity.
#
# These are the defined severities (least severe to most severe):
#
# logger = Logger.new($stdout)
# logger.add(Logger::DEBUG, 'Maximal debugging info')
# # => D, [2022-05-07T17:57:41.776220 #20536] DEBUG -- : Maximal debugging info
# logger.add(Logger::INFO, 'Non-error information')
# # => I, [2022-05-07T17:59:14.349167 #20536] INFO -- : Non-error information
# logger.add(Logger::WARN, 'Non-error warning')
# # => W, [2022-05-07T18:00:45.337538 #20536] WARN -- : Non-error warning
# logger.add(Logger::ERROR, 'Non-fatal error')
# # => E, [2022-05-07T18:02:41.592912 #20536] ERROR -- : Non-fatal error
# logger.add(Logger::FATAL, 'Fatal error')
# # => F, [2022-05-07T18:05:24.703931 #20536] FATAL -- : Fatal error
# logger.add(Logger::UNKNOWN, 'Most severe')
# # => A, [2022-05-07T18:07:54.657491 #20536] ANY -- : Most severe
#
# The default initial level setting is Logger::DEBUG, the lowest level,
# which means that all entries are to be written, regardless of severity:
#
# logger = Logger.new($stdout)
# logger.level # => 0
# logger.add(0, "My message")
# # => D, [2022-05-11T15:10:59.773668 #20536] DEBUG -- : My message
#
# You can specify a different setting in a new logger
# using keyword argument +level+ with an appropriate value:
#
# logger = Logger.new($stdout, level: Logger::ERROR)
# logger = Logger.new($stdout, level: 'error')
# logger = Logger.new($stdout, level: :error)
# logger.level # => 3
#
# With this level, entries with severity Logger::ERROR and higher
# are written, while those with lower severities are not written:
#
# logger = Logger.new($stdout, level: Logger::ERROR)
# logger.add(3)
# # => E, [2022-05-11T15:17:20.933362 #20536] ERROR -- : nil
# logger.add(2) # Silent.
#
# You can set the log level for an existing logger
# with method #level=:
#
# logger.level = Logger::ERROR
#
# These shorthand methods also set the level:
#
# logger.debug! # => 0
# logger.info! # => 1
# logger.warn! # => 2
# logger.error! # => 3
# logger.fatal! # => 4
#
# You can retrieve the log level with method #level.
#
# logger.level = Logger::ERROR
# logger.level # => 3
#
# These methods return whether a given
# level is to be written:
#
# logger.level = Logger::ERROR
# logger.debug? # => false
# logger.info? # => false
# logger.warn? # => false
# logger.error? # => true
# logger.fatal? # => true
#
# == Log File Rotation
#
# By default, a log file is a single file that grows indefinitely
# (until explicitly closed); there is no file rotation.
#
# To keep log files to a manageable size,
# you can use _log_ _file_ _rotation_, which uses multiple log files:
#
# - Each log file has entries for a non-overlapping
# time interval.
# - Only the most recent log file is open and active;
# the others are closed and inactive.
#
# === Size-Based Rotation
#
# For size-based log file rotation, call Logger.new with:
#
# - Argument +logdev+ as a file path.
# - Argument +shift_age+ with a positive integer:
# the number of log files to be in the rotation.
# - Argument +shift_size+ as a positive integer:
# the maximum size (in bytes) of each log file;
# defaults to 1048576 (1 megabyte).
#
# Examples:
#
# logger = Logger.new('t.log', 3) # Three 1-megabyte files.
# logger = Logger.new('t.log', 5, 10485760) # Five 10-megabyte files.
#
# For these examples, suppose:
#
# logger = Logger.new('t.log', 3)
#
# Logging begins in the new log file, +t.log+;
# the log file is "full" and ready for rotation
# when a new entry would cause its size to exceed +shift_size+.
#
# The first time +t.log+ is full:
#
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# The second time +t.log+ is full:
#
# - +t.log.0 is renamed as +t.log.1+.
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# Each subsequent time that +t.log+ is full,
# the log files are rotated:
#
# - +t.log.1+ is removed.
# - +t.log.0 is renamed as +t.log.1+.
# - +t.log+ is closed and renamed to +t.log.0+.
# - A new file +t.log+ is opened.
#
# === Periodic Rotation
#
# For periodic rotation, call Logger.new with:
#
# - Argument +logdev+ as a file path.
# - Argument +shift_age+ as a string period indicator.
#
# Examples:
#
# logger = Logger.new('t.log', 'daily') # Rotate log files daily.
# logger = Logger.new('t.log', 'weekly') # Rotate log files weekly.
# logger = Logger.new('t.log', 'monthly') # Rotate log files monthly.
#
# Example:
#
# logger = Logger.new('t.log', 'daily')
#
# When the given period expires:
#
# - The base log file, +t.log+ is closed and renamed
# with a date-based suffix such as +t.log.20220509+.
# - A new log file +t.log+ is opened.
# - Nothing is removed.
#
# The default format for the suffix is <tt>'%Y%m%d'</tt>,
# which produces a suffix similar to the one above.
# You can set a different format using create-time option
# +shift_period_suffix+;
# see details and suggestions at
# {Time#strftime}[rdoc-ref:Time#strftime].
#
class Logger
_, name, rev = %w$Id$
if name
name = name.chomp(",v")
else
name = File.basename(__FILE__)
end
rev ||= "v#{VERSION}"
ProgName = "#{name}/#{rev}"
include Severity
# Logging severity threshold (e.g. <tt>Logger::INFO</tt>).
def level
@level_override[Fiber.current] || @level
end
# Sets the log level; returns +severity+.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
# Argument +severity+ may be an integer, a string, or a symbol:
#
# logger.level = Logger::ERROR # => 3
# logger.level = 3 # => 3
# logger.level = 'error' # => "error"
# logger.level = :error # => :error
#
# Logger#sev_threshold= is an alias for Logger#level=.
#
def level=(severity)
@level = Severity.coerce(severity)
end
# Adjust the log level during the block execution for the current Fiber only
#
# logger.with_level(:debug) do
# logger.debug { "Hello" }
# end
def with_level(severity)
prev, @level_override[Fiber.current] = level, Severity.coerce(severity)
begin
yield
ensure
if prev
@level_override[Fiber.current] = prev
else
@level_override.delete(Fiber.current)
end
end
end
# Program name to include in log messages.
attr_accessor :progname
# Sets the date-time format.
#
# Argument +datetime_format+ should be either of these:
#
# - A string suitable for use as a format for method
# {Time#strftime}[rdoc-ref:Time#strftime].
# - +nil+: the logger uses <tt>'%Y-%m-%dT%H:%M:%S.%6N'</tt>.
#
def datetime_format=(datetime_format)
@default_formatter.datetime_format = datetime_format
end
# Returns the date-time format; see #datetime_format=.
#
def datetime_format
@default_formatter.datetime_format
end
# Sets or retrieves the logger entry formatter proc.
#
# When +formatter+ is +nil+, the logger uses Logger::Formatter.
#
# When +formatter+ is a proc, a new entry is formatted by the proc,
# which is called with four arguments:
#
# - +severity+: The severity of the entry.
# - +time+: A Time object representing the entry's timestamp.
# - +progname+: The program name for the entry.
# - +msg+: The message for the entry (string or string-convertible object).
#
# The proc should return a string containing the formatted entry.
#
# This custom formatter uses
# {String#dump}[rdoc-ref:String#dump]
# to escape the message string:
#
# logger = Logger.new($stdout, progname: 'mung')
# original_formatter = logger.formatter || Logger::Formatter.new
# logger.formatter = proc { |severity, time, progname, msg|
# original_formatter.call(severity, time, progname, msg.dump)
# }
# logger.add(Logger::INFO, "hello \n ''")
# logger.add(Logger::INFO, "\f\x00\xff\\\"")
#
# Output:
#
# I, [2022-05-13T13:16:29.637488 #8492] INFO -- mung: "hello \n ''"
# I, [2022-05-13T13:16:29.637610 #8492] INFO -- mung: "\f\x00\xFF\\\""
#
attr_accessor :formatter
alias sev_threshold level
alias sev_threshold= level=
# Returns +true+ if the log level allows entries with severity
# Logger::DEBUG to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def debug?; level <= DEBUG; end
# Sets the log level to Logger::DEBUG.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def debug!; self.level = DEBUG; end
# Returns +true+ if the log level allows entries with severity
# Logger::INFO to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def info?; level <= INFO; end
# Sets the log level to Logger::INFO.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def info!; self.level = INFO; end
# Returns +true+ if the log level allows entries with severity
# Logger::WARN to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def warn?; level <= WARN; end
# Sets the log level to Logger::WARN.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def warn!; self.level = WARN; end
# Returns +true+ if the log level allows entries with severity
# Logger::ERROR to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def error?; level <= ERROR; end
# Sets the log level to Logger::ERROR.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def error!; self.level = ERROR; end
# Returns +true+ if the log level allows entries with severity
# Logger::FATAL to be written, +false+ otherwise.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def fatal?; level <= FATAL; end
# Sets the log level to Logger::FATAL.
# See {Log Level}[rdoc-ref:Logger@Log+Level].
#
def fatal!; self.level = FATAL; end
# :call-seq:
# Logger.new(logdev, shift_age = 0, shift_size = 1048576, **options)
#
# With the single argument +logdev+,
# returns a new logger with all default options:
#
# Logger.new('t.log') # => #<Logger:0x000001e685dc6ac8>
#
# Argument +logdev+ must be one of:
#
# - A string filepath: entries are to be written
# to the file at that path; if the file at that path exists,
# new entries are appended.
# - An IO stream (typically +$stdout+, +$stderr+. or an open file):
# entries are to be written to the given stream.
# - +nil+ or +File::NULL+: no entries are to be written.
#
# Examples:
#
# Logger.new('t.log')
# Logger.new($stdout)
#
# The keyword options are:
#
# - +level+: sets the log level; default value is Logger::DEBUG.
# See {Log Level}[rdoc-ref:Logger@Log+Level]:
#
# Logger.new('t.log', level: Logger::ERROR)
#
# - +progname+: sets the default program name; default is +nil+.
# See {Program Name}[rdoc-ref:Logger@Program+Name]:
#
# Logger.new('t.log', progname: 'mung')
#
# - +formatter+: sets the entry formatter; default is +nil+.
# See {formatter=}[Logger.html#attribute-i-formatter].
# - +datetime_format+: sets the format for entry timestamp;
# default is +nil+.
# See #datetime_format=.
# - +binmode+: sets whether the logger writes in binary mode;
# default is +false+.
# - +shift_period_suffix+: sets the format for the filename suffix
# for periodic log file rotation; default is <tt>'%Y%m%d'</tt>.
# See {Periodic Rotation}[rdoc-ref:Logger@Periodic+Rotation].
#
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
@level_override = {}
if logdev && logdev != File::NULL
@logdev = LogDevice.new(logdev, shift_age: shift_age,
shift_size: shift_size,
shift_period_suffix: shift_period_suffix,
binmode: binmode)
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 15173517734 0012346 0 ustar 00 require_relative "error_highlight/base"
require_relative "error_highlight/core_ext"
share/ruby/observer.rb 0000644 00000014604 15173517734 0011025 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.2"
#
# 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 15173517734 0011772 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 00000020105 15173517734 0010706 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.escapeURIComponent("'Stop!' said Fred")
# # => "%27Stop%21%27%20said%20Fred"
def escapeURIComponent(string)
encoding = string.encoding
buffer = string.b
buffer.gsub!(/([^a-zA-Z0-9_.\-~]+)/) do |m|
'%' + m.unpack('H2' * m.bytesize).join('%').upcase
end
buffer.force_encoding(encoding)
end
alias escape_uri_component escapeURIComponent
# URL-decode a string following RFC 3986 with encoding(optional).
# string = CGI.unescapeURIComponent("%27Stop%21%27+said%20Fred")
# # => "'Stop!'+said Fred"
def unescapeURIComponent(string, encoding = @@accept_charset)
str = string.b
str.gsub!(/((?:%[0-9a-fA-F]{2})+)/) do |m|
[m.delete('%')].pack('H*')
end
str.force_encoding(encoding)
str.valid_encoding? ? str : str.force_encoding(string.encoding)
end
alias unescape_uri_component unescapeURIComponent
# The set of special characters and their escaped values
TABLE_FOR_ESCAPE_HTML__ = {
"'" => ''',
'&' => '&',
'"' => '"',
'<' => '<',
'>' => '>',
}
# Escape special characters in HTML, namely '&\"<>
# CGI.escapeHTML('Usage: foo "bar" <baz>')
# # => "Usage: foo "bar" <baz>"
def escapeHTML(string)
enc = string.encoding
unless enc.ascii_compatible?
if enc.dummy?
origenc = enc
enc = Encoding::Converter.asciicompat_encoding(enc)
string = enc ? string.encode(enc) : string.b
end
table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}]
string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table)
string.encode!(origenc) if origenc
string
else
string = string.b
string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__)
string.force_encoding(enc)
end
end
# TruffleRuby runs the pure-Ruby variant faster, do not use the C extension there
unless RUBY_ENGINE == 'truffleruby'
begin
require 'cgi/escape'
rescue LoadError
end
end
# Unescape a string that has been HTML-escaped
# CGI.unescapeHTML("Usage: foo "bar" <baz>")
# # => "Usage: foo \"bar\" <baz>"
def unescapeHTML(string)
enc = string.encoding
unless enc.ascii_compatible?
if enc.dummy?
origenc = enc
enc = Encoding::Converter.asciicompat_encoding(enc)
string = enc ? string.encode(enc) : string.b
end
string = string.gsub(Regexp.new('&(apos|amp|quot|gt|lt|#[0-9]+|#x[0-9A-Fa-f]+);'.encode(enc))) do
case $1.encode(Encoding::US_ASCII)
when 'apos' then "'".encode(enc)
when 'amp' then '&'.encode(enc)
when 'quot' then '"'.encode(enc)
when 'gt' then '>'.encode(enc)
when 'lt' then '<'.encode(enc)
when /\A#0*(\d+)\z/ then $1.to_i.chr(enc)
when /\A#x([0-9a-f]+)\z/i then $1.hex.chr(enc)
end
end
string.encode!(origenc) if origenc
return string
end
return string unless string.include? '&'
charlimit = case enc
when Encoding::UTF_8; 0x10ffff
when Encoding::ISO_8859_1; 256
else 128
end
string = string.b
string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do
match = $1.dup
case match
when 'apos' then "'"
when 'amp' then '&'
when 'quot' then '"'
when 'gt' then '>'
when 'lt' then '<'
when /\A#0*(\d+)\z/
n = $1.to_i
if n < charlimit
n.chr(enc)
else
"&##{$1};"
end
when /\A#x([0-9a-f]+)\z/i
n = $1.hex
if n < charlimit
n.chr(enc)
else
"&#x#{$1};"
end
else
"&#{match};"
end
end
string.force_encoding enc
end
# Synonym for CGI.escapeHTML(str)
alias escape_html escapeHTML
# Synonym for CGI.unescapeHTML(str)
alias unescape_html unescapeHTML
# Escape only the tags of certain HTML elements in +string+.
#
# Takes an element or elements or array of elements. Each element
# is specified by the name of the element, without angle brackets.
# This matches both the start and the end tag of that element.
# The attribute list of the open tag will also be escaped (for
# instance, the double-quotes surrounding attribute values).
#
# print CGI.escapeElement('<BR><A HREF="url"></A>', "A", "IMG")
# # "<BR><A HREF="url"></A>"
#
# print CGI.escapeElement('<BR><A HREF="url"></A>', ["A", "IMG"])
# # "<BR><A HREF="url"></A>"
def escapeElement(string, *elements)
elements = elements[0] if elements[0].kind_of?(Array)
unless elements.empty?
string.gsub(/<\/?(?:#{elements.join("|")})\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 15173517734 0011210 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 15173517734 0010704 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 00000005204 15173517734 0012733 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'
begin
require 'pstore'
rescue LoadError
end
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 if defined?(::PStore)
end
end
# :enddoc:
share/ruby/cgi/session.rb 0000644 00000046263 15173517735 0011432 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 https://blade.ruby-lang.org/ruby-list/37805)
#
# cgi = CGI.new("html4")
# class << cgi
# undef_method :fieldset
# end
#
def initialize(request, option={})
@new_session = false
session_key = option['session_key'] || '_session_id'
session_id = option['session_id']
unless session_id
if option['new_session']
session_id = create_new_id
@new_session = true
end
end
unless session_id
if request.key?(session_key)
session_id = request[session_key]
session_id = session_id.read if session_id.respond_to?(:read)
end
unless session_id
session_id, = request.cookies[session_key]
end
unless session_id
unless option.fetch('new_session', true)
raise ArgumentError, "session_key `%s' should be supplied"%session_key
end
session_id = create_new_id
@new_session = true
end
end
@session_id = session_id
dbman = option['database_manager'] || FileStore
begin
@dbman = dbman::new(self, option)
rescue NoSession
unless option.fetch('new_session', true)
raise ArgumentError, "invalid session_id `%s'"%session_id
end
session_id = @session_id = create_new_id unless session_id
@new_session=true
retry
end
request.instance_eval do
@output_hidden = {session_key => session_id} unless option['no_hidden']
@output_cookies = [
Cookie::new("name" => session_key,
"value" => session_id,
"expires" => option['session_expires'],
"domain" => option['session_domain'],
"secure" => option['session_secure'],
"path" =>
if option['session_path']
option['session_path']
elsif ENV["SCRIPT_NAME"]
File::dirname(ENV["SCRIPT_NAME"])
else
""
end)
] unless option['no_cookies']
end
@dbprot = [@dbman]
ObjectSpace::define_finalizer(self, Session::callback(@dbprot))
end
# Retrieve the session data for key +key+.
def [](key)
@data ||= @dbman.restore
@data[key]
end
# Set the session data for key +key+.
def []=(key, val)
@write_lock ||= true
@data ||= @dbman.restore
@data[key] = val
end
# Store session data on the server. For some session storage types,
# this is a no-op.
def update
@dbman.update
end
# Store session data on the server and close the session storage.
# For some session storage types, this is a no-op.
def close
@dbman.close
@dbprot.clear
end
# Delete the session from storage. Also closes the storage.
#
# Note that the session's data is _not_ automatically deleted
# upon the session expiring.
def delete
@dbman.delete
@dbprot.clear
end
# File-based session storage class.
#
# Implements session storage as a flat file of 'key=value' values.
# This storage type only works directly with String values; the
# user is responsible for converting other types to Strings when
# storing and from Strings when retrieving.
class FileStore
# Create a new FileStore instance.
#
# This constructor is used internally by CGI::Session. The
# user does not generally need to call it directly.
#
# +session+ is the session for which this instance is being
# created. The session id must only contain alphanumeric
# characters; automatically generated session ids observe
# this requirement.
#
# +option+ is a hash of options for the initializer. The
# following options are recognised:
#
# tmpdir:: the directory to use for storing the FileStore
# file. Defaults to Dir::tmpdir (generally "/tmp"
# on Unix systems).
# prefix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to "cgi_sid_".
# suffix:: the prefix to add to the session id when generating
# the filename for this session's FileStore file.
# Defaults to the empty string.
#
# This session's FileStore file will be created if it does
# not exist, or opened if it does.
def initialize(session, option={})
option = {'prefix' => 'cgi_sid_'}.update(option)
@path, @hash = session.new_store_file(option)
end
# Restore session state from the session's FileStore file.
#
# Returns the session state as a hash.
def restore
unless @hash
@hash = {}
begin
lockf = File.open(@path+".lock", "r")
lockf.flock File::LOCK_SH
f = File.open(@path, 'r')
for line in f
line.chomp!
k, v = line.split('=',2)
@hash[CGI.unescape(k)] = Marshal.restore(CGI.unescape(v))
end
ensure
f&.close
lockf&.close
end
end
@hash
end
# Save session state to the session's FileStore file.
def update
return unless @hash
begin
lockf = File.open(@path+".lock", File::CREAT|File::RDWR, 0600)
lockf.flock File::LOCK_EX
f = File.open(@path+".new", File::CREAT|File::TRUNC|File::WRONLY, 0600)
for k,v in @hash
f.printf "%s=%s\n", CGI.escape(k), CGI.escape(String(Marshal.dump(v)))
end
f.close
File.rename @path+".new", @path
ensure
f&.close
lockf&.close
end
end
# Update and close the session's FileStore file.
def close
update
end
# Close and delete the session's FileStore file.
def delete
File::unlink @path+".lock" rescue nil
File::unlink @path+".new" rescue nil
File::unlink @path rescue nil
end
end
# In-memory session storage class.
#
# Implements session storage as a global in-memory hash. Session
# data will only persist for as long as the Ruby interpreter
# instance does.
class MemoryStore
GLOBAL_HASH_TABLE = {} #:nodoc:
# Create a new MemoryStore instance.
#
# +session+ is the session this instance is associated with.
# +option+ is a list of initialisation options. None are
# currently recognized.
def initialize(session, option=nil)
@session_id = session.session_id
unless GLOBAL_HASH_TABLE.key?(@session_id)
unless session.new_session
raise CGI::Session::NoSession, "uninitialized session"
end
GLOBAL_HASH_TABLE[@session_id] = {}
end
end
# Restore session state.
#
# Returns session data as a hash.
def restore
GLOBAL_HASH_TABLE[@session_id]
end
# Update session state.
#
# A no-op.
def update
# don't need to update; hash is shared
end
# Close session storage.
#
# A no-op.
def close
# don't need to close
end
# Delete the session state.
def delete
GLOBAL_HASH_TABLE.delete(@session_id)
end
end
# Dummy session storage class.
#
# Implements session storage place holder. No actual storage
# will be done.
class NullStore
# Create a new NullStore instance.
#
# +session+ is the session this instance is associated with.
# +option+ is a list of initialisation options. None are
# currently recognised.
def initialize(session, option=nil)
end
# Restore (empty) session state.
def restore
{}
end
# Update session state.
#
# A no-op.
def update
end
# Close session storage.
#
# A no-op.
def close
end
# Delete the session state.
#
# A no-op.
def delete
end
end
end
end
share/ruby/cgi/core.rb 0000644 00000072445 15173517735 0010700 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/bundled_gems.rb 0000644 00000015555 15173517735 0011635 0 ustar 00 # -*- frozen-string-literal: true -*-
# :stopdoc:
module Gem::BUNDLED_GEMS
SINCE = {
"rexml" => "3.0.0",
"rss" => "3.0.0",
"webrick" => "3.0.0",
"matrix" => "3.1.0",
"net-ftp" => "3.1.0",
"net-imap" => "3.1.0",
"net-pop" => "3.1.0",
"net-smtp" => "3.1.0",
"prime" => "3.1.0",
"racc" => "3.3.0",
"abbrev" => "3.4.0",
"base64" => "3.4.0",
"bigdecimal" => "3.4.0",
"csv" => "3.4.0",
"drb" => "3.4.0",
"getoptlong" => "3.4.0",
"mutex_m" => "3.4.0",
"nkf" => "3.4.0",
"observer" => "3.4.0",
"resolv-replace" => "3.4.0",
"rinda" => "3.4.0",
"syslog" => "3.4.0",
}.freeze
SINCE_FAST_PATH = SINCE.transform_keys { |g| g.sub(/\A.*\-/, "") }.freeze
EXACT = {
"kconv" => "nkf",
}.freeze
PREFIXED = {
"bigdecimal" => true,
"csv" => true,
"drb" => true,
"rinda" => true,
"syslog" => true,
}.freeze
WARNED = {} # unfrozen
conf = ::RbConfig::CONFIG
LIBDIR = (conf["rubylibdir"] + "/").freeze
ARCHDIR = (conf["rubyarchdir"] + "/").freeze
dlext = [conf["DLEXT"], "so"].uniq
DLEXT = /\.#{Regexp.union(dlext)}\z/
LIBEXT = /\.#{Regexp.union("rb", *dlext)}\z/
def self.replace_require(specs)
return if [::Kernel.singleton_class, ::Kernel].any? {|klass| klass.respond_to?(:no_warning_require) }
spec_names = specs.to_a.each_with_object({}) {|spec, h| h[spec.name] = true }
[::Kernel.singleton_class, ::Kernel].each do |kernel_class|
kernel_class.send(:alias_method, :no_warning_require, :require)
kernel_class.send(:define_method, :require) do |name|
if message = ::Gem::BUNDLED_GEMS.warning?(name, specs: spec_names)
if ::Gem::BUNDLED_GEMS.uplevel > 0
Kernel.warn message, uplevel: ::Gem::BUNDLED_GEMS.uplevel
else
Kernel.warn message
end
end
kernel_class.send(:no_warning_require, name)
end
if kernel_class == ::Kernel
kernel_class.send(:private, :require)
else
kernel_class.send(:public, :require)
end
end
end
def self.uplevel
frame_count = 0
frames_to_skip = 3
uplevel = 0
require_found = false
Thread.each_caller_location do |cl|
frame_count += 1
if frames_to_skip >= 1
frames_to_skip -= 1
next
end
uplevel += 1
if require_found
if cl.base_label != "require"
return uplevel
end
else
if cl.base_label == "require"
require_found = true
end
end
# Don't show script name when bundle exec and call ruby script directly.
if cl.path.end_with?("bundle")
frame_count = 0
break
end
end
require_found ? 1 : frame_count - 1
end
def self.find_gem(path)
if !path
return
elsif path.start_with?(ARCHDIR)
n = path.delete_prefix(ARCHDIR).sub(DLEXT, "")
elsif path.start_with?(LIBDIR)
n = path.delete_prefix(LIBDIR).chomp(".rb")
else
return
end
(EXACT[n] || !!SINCE[n]) or PREFIXED[n = n[%r[\A[^/]+(?=/)]]] && n
end
def self.warning?(name, specs: nil)
# name can be a feature name or a file path with String or Pathname
feature = File.path(name)
# The actual checks needed to properly identify the gem being required
# are costly (see [Bug #20641]), so we first do a much cheaper check
# to exclude the vast majority of candidates.
if feature.include?("/")
# If requiring $LIBDIR/mutex_m.rb, we check SINCE_FAST_PATH["mutex_m"]
# We'll fail to warn requires for files that are not the entry point
# of the gem, e.g. require "logger/formatter.rb" won't warn.
# But that's acceptable because this warning is best effort,
# and in the overwhelming majority of cases logger.rb will end
# up required.
return unless SINCE_FAST_PATH[File.basename(feature, ".*")]
else
return unless SINCE_FAST_PATH[feature]
end
# bootsnap expands `require "csv"` to `require "#{LIBDIR}/csv.rb"`,
# and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`.
name = feature.delete_prefix(ARCHDIR)
name.delete_prefix!(LIBDIR)
name.tr!("/", "-")
name.sub!(LIBEXT, "")
return if specs.include?(name)
_t, path = $:.resolve_feature_path(feature)
if gem = find_gem(path)
return if specs.include?(gem)
caller = caller_locations(3, 3)&.find {|c| c&.absolute_path}
return if find_gem(caller&.absolute_path)
elsif SINCE[name] && !path
gem = true
else
return
end
return if WARNED[name]
WARNED[name] = true
if gem == true
gem = name
"#{feature} was loaded from the standard library, but"
elsif gem
return if WARNED[gem]
WARNED[gem] = true
"#{feature} is found in #{gem}, which"
else
return
end + build_message(gem)
end
def self.build_message(gem)
msg = " #{RUBY_VERSION < SINCE[gem] ? "will no longer be" : "is not"} part of the default gems starting from Ruby #{SINCE[gem]}."
if defined?(Bundler)
msg += "\nYou can add #{gem} to your Gemfile or gemspec to silence this warning."
# We detect the gem name from caller_locations. First we walk until we find `require`
# then take the first frame that's not from `require`.
#
# Additionally, we need to skip Bootsnap and Zeitwerk if present, these
# gems decorate Kernel#require, so they are not really the ones issuing
# the require call users should be warned about. Those are upwards.
frames_to_skip = 3
location = nil
require_found = false
Thread.each_caller_location do |cl|
if frames_to_skip >= 1
frames_to_skip -= 1
next
end
if require_found
if cl.base_label != "require"
location = cl.path
break
end
else
if cl.base_label == "require"
require_found = true
end
end
end
if location && File.file?(location) && !location.start_with?(Gem::BUNDLED_GEMS::LIBDIR)
caller_gem = nil
Gem.path.each do |path|
if location =~ %r{#{path}/gems/([\w\-\.]+)}
caller_gem = $1
break
end
end
if caller_gem
msg += "\nAlso please contact the author of #{caller_gem} to request adding #{gem} into its gemspec."
end
end
else
msg += " Install #{gem} from RubyGems."
end
msg
end
freeze
end
# for RubyGems without Bundler environment.
# If loading library is not part of the default gems and the bundled gems, warn it.
class LoadError
def message
return super unless path
name = path.tr("/", "-")
if !defined?(Bundler) && Gem::BUNDLED_GEMS::SINCE[name] && !Gem::BUNDLED_GEMS::WARNED[name]
warn name + Gem::BUNDLED_GEMS.build_message(name), uplevel: Gem::BUNDLED_GEMS.uplevel
end
super
end
end
# :startdoc:
share/ruby/coverage.rb 0000644 00000000560 15173517735 0010766 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/prism.rb 0000644 00000006257 15173517735 0010336 0 ustar 00 # frozen_string_literal: true
# The Prism Ruby parser.
#
# "Parsing Ruby is suddenly manageable!"
# - You, hopefully
#
module Prism
# There are many files in prism that are templated to handle every node type,
# which means the files can end up being quite large. We autoload them to make
# our require speed faster since consuming libraries are unlikely to use all
# of these features.
autoload :BasicVisitor, "prism/visitor"
autoload :Compiler, "prism/compiler"
autoload :Debug, "prism/debug"
autoload :DesugarCompiler, "prism/desugar_compiler"
autoload :Dispatcher, "prism/dispatcher"
autoload :DotVisitor, "prism/dot_visitor"
autoload :DSL, "prism/dsl"
autoload :LexCompat, "prism/lex_compat"
autoload :LexRipper, "prism/lex_compat"
autoload :MutationCompiler, "prism/mutation_compiler"
autoload :NodeInspector, "prism/node_inspector"
autoload :RipperCompat, "prism/ripper_compat"
autoload :Pack, "prism/pack"
autoload :Pattern, "prism/pattern"
autoload :Serialize, "prism/serialize"
autoload :Visitor, "prism/visitor"
# Some of these constants are not meant to be exposed, so marking them as
# private here.
private_constant :Debug
private_constant :LexCompat
private_constant :LexRipper
# :call-seq:
# Prism::lex_compat(source, **options) -> ParseResult
#
# Returns a parse result whose value is an array of tokens that closely
# resembles the return value of Ripper::lex. The main difference is that the
# `:on_sp` token is not emitted.
#
# For supported options, see Prism::parse.
def self.lex_compat(source, **options)
LexCompat.new(source, **options).result
end
# :call-seq:
# Prism::lex_ripper(source) -> Array
#
# This lexes with the Ripper lex. It drops any space events but otherwise
# returns the same tokens. Raises SyntaxError if the syntax in source is
# invalid.
def self.lex_ripper(source)
LexRipper.new(source).result
end
# :call-seq:
# Prism::load(source, serialized) -> ParseResult
#
# Load the serialized AST using the source as a reference into a tree.
def self.load(source, serialized)
Serialize.load(source, serialized)
end
# :call-seq:
# Prism::parse_failure?(source, **options) -> bool
#
# Returns true if the source parses with errors.
def self.parse_failure?(source, **options)
!parse_success?(source, **options)
end
# :call-seq:
# Prism::parse_file_failure?(filepath, **options) -> bool
#
# Returns true if the file at filepath parses with errors.
def self.parse_file_failure?(filepath, **options)
!parse_file_success?(filepath, **options)
end
end
require_relative "prism/node"
require_relative "prism/node_ext"
require_relative "prism/parse_result"
require_relative "prism/parse_result/comments"
require_relative "prism/parse_result/newlines"
# This is a Ruby implementation of the prism parser. If we're running on CRuby
# and we haven't explicitly set the PRISM_FFI_BACKEND environment variable, then
# it's going to require the built library. Otherwise, it's going to require a
# module that uses FFI to call into the library.
if RUBY_ENGINE == "ruby" and !ENV["PRISM_FFI_BACKEND"]
require "prism/prism"
else
require_relative "prism/ffi"
end
share/ruby/weakref.rb 0000644 00000002554 15173517735 0010624 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.3"
##
# 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 00000030541 15173517735 0012460 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
alias uuid_v4 uuid
# Generate a random v7 UUID (Universally Unique IDentifier).
#
# require 'random/formatter'
#
# Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e"
# Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5"
# Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23"
# Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31"
# # |<--sorted-->| |<----- random ---->|
#
# # or
# prng = Random.new
# prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98"
#
# The version 7 UUID starts with the least significant 48 bits of a 64 bit
# Unix timestamp (milliseconds since the epoch) and fills the remaining bits
# with random data, excluding the version and variant bits.
#
# This allows version 7 UUIDs to be sorted by creation time. Time ordered
# UUIDs can be used for better database index locality of newly inserted
# records, which may have a significant performance benefit compared to random
# data inserts.
#
# The result contains 74 random bits (9.25 random bytes).
#
# Note that this method cannot be made reproducable because its output
# includes not only random bits but also timestamp.
#
# See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/]
# for details of UUIDv7.
#
# ==== Monotonicity
#
# UUIDv7 has millisecond precision by default, so multiple UUIDs created
# within the same millisecond are not issued in monotonically increasing
# order. To create UUIDs that are time-ordered with sub-millisecond
# precision, up to 12 bits of additional timestamp may added with
# +extra_timestamp_bits+. The extra timestamp precision comes at the expense
# of random bits. Setting <tt>extra_timestamp_bits: 12</tt> provides ~244ns
# of precision, but only 62 random bits (7.75 random bytes).
#
# prng = Random.new
# Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) }
# # =>
# ["0188d4c7-13da-74f9-8b53-22a786ffdd5a",
# "0188d4c7-13da-753b-83a5-7fb9b2afaeea",
# "0188d4c7-13da-754a-88ea-ac0baeedd8db",
# "0188d4c7-13da-7557-83e1-7cad9cda0d8d"]
# # |<--- sorted --->| |<-- random --->|
#
# Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) }
# # =>
# ["0188d4c7-3333-7a95-850a-de6edb858f7e",
# "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order
# "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order
# "0188d4c7-3333-7af9-87c3-8f612edac82e"]
# # |<--- sorted -->||<---- random --->|
#
# Any rollbacks of the system clock will break monotonicity. UUIDv7 is based
# on UTC, which excludes leap seconds and can rollback the clock. To avoid
# this, the system clock can synchronize with an NTP server configured to use
# a "leap smear" approach. NTP or PTP will also be needed to synchronize
# across distributed nodes.
#
# Counters and other mechanisms for stronger guarantees of monotonicity are
# not implemented. Applications with stricter requirements should follow
# {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters]
# of the specification.
#
def uuid_v7(extra_timestamp_bits: 0)
case (extra_timestamp_bits = Integer(extra_timestamp_bits))
when 0 # min timestamp precision
ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
rand = random_bytes(10)
rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version
rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant
"%08x-%04x-%s" % [
(ms & 0x0000_ffff_ffff_0000) >> 16,
(ms & 0x0000_0000_0000_ffff),
rand.unpack("H4H4H12").join("-")
]
when 12 # max timestamp precision
ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
.divmod(1_000_000)
extra_bits = ns * 4096 / 1_000_000
rand = random_bytes(8)
rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant
"%08x-%04x-7%03x-%s" % [
(ms & 0x0000_ffff_ffff_0000) >> 16,
(ms & 0x0000_0000_0000_ffff),
extra_bits,
rand.unpack("H4H12").join("-")
]
when (0..12) # the generic version is slower than the special cases above
rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN")
rand_mask_bits = 12 - extra_timestamp_bits
ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
.divmod(1_000_000)
"%08x-%04x-%04x-%04x-%04x%08x" % [
(ms & 0x0000_ffff_ffff_0000) >> 16,
(ms & 0x0000_0000_0000_ffff),
0x7000 |
((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) |
rand_a & ((1 << rand_mask_bits) - 1),
0x8000 | (rand_b1 & 0x3fff),
rand_b2,
rand_b3
]
else
raise ArgumentError, "extra_timestamp_bits must be in 0..12"
end
end
# Internal interface to Random; Generate random data _n_ bytes.
private def gen_random(n)
self.bytes(n)
end
# Generate a string that randomly draws from a
# source array of characters.
#
# The argument _source_ specifies the array of characters from which
# to generate the string.
# The argument _n_ specifies the length, in characters, of the string to be
# generated.
#
# The result may contain whatever characters are in the source array.
#
# require 'random/formatter'
#
# prng.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron"
# prng.choose([*'0'..'9'], 5) #=> "27309"
private def choose(source, n)
size = source.size
m = 1
limit = size
while limit * size <= 0x100000000
limit *= size
m += 1
end
result = ''.dup
while m <= n
rs = random_number(limit)
is = rs.digits(size)
(m-is.length).times { is << 0 }
result << source.values_at(*is).join('')
n -= m
end
if 0 < n
rs = random_number(limit)
is = rs.digits(size)
if is.length < n
(n-is.length).times { is << 0 }
else
is.pop while n < is.length
end
result.concat source.values_at(*is).join('')
end
result
end
# The default character list for #alphanumeric.
ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
# Generate a random alphanumeric string.
#
# The argument _n_ specifies the length, in characters, of the alphanumeric
# string to be generated.
# The argument _chars_ specifies the character list which the result is
# consist of.
#
# If _n_ is not specified or is nil, 16 is assumed.
# It may be larger in the future.
#
# The result may contain A-Z, a-z and 0-9, unless _chars_ is specified.
#
# require 'random/formatter'
#
# Random.alphanumeric #=> "2BuBuLf3WfSKyQbR"
# # or
# prng = Random.new
# prng.alphanumeric(10) #=> "i6K93NdqiH"
#
# Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952"
# # or
# prng = Random.new
# prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''."
def alphanumeric(n = nil, chars: ALPHANUMERIC)
n = 16 if n.nil?
choose(chars, n)
end
end
share/gems/gems/json-2.7.2/lib/json.rb 0000644 00000047171 15173517735 0013245 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)
#
# ====== Escaping Options
#
# Options +script_safe+ (boolean) specifies wether <tt>'\u2028'</tt>, <tt>'\u2029'</tt>
# and <tt>'/'</tt> should be escaped as to make the JSON object safe to interpolate in script
# tags.
#
# Options +ascii_only+ (boolean) specifies wether all characters outside the ASCII range
# should be escaped.
#
# ====== 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 00000037666 15173517735 0011620 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,
# https://lindig.github.io/papers/strictly-pretty-2000.pdf
#
# Philip Wadler, A prettier printer, March 1998,
# https://homepages.inf.ed.ac.uk/wadler/topics/language-design.html#prettier
#
# == Author
# Tanaka Akira <akr@fsij.org>
#
class PrettyPrint
VERSION = "0.2.0"
# 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/prism/node_ext.rb 0000644 00000012015 15173517735 0012130 0 ustar 00 # frozen_string_literal: true
# Here we are reopening the prism module to provide methods on nodes that aren't
# templated and are meant as convenience methods.
module Prism
module RegularExpressionOptions # :nodoc:
# Returns a numeric value that represents the flags that were used to create
# the regular expression.
def options
o = flags & (RegularExpressionFlags::IGNORE_CASE | RegularExpressionFlags::EXTENDED | RegularExpressionFlags::MULTI_LINE)
o |= Regexp::FIXEDENCODING if flags.anybits?(RegularExpressionFlags::EUC_JP | RegularExpressionFlags::WINDOWS_31J | RegularExpressionFlags::UTF_8)
o |= Regexp::NOENCODING if flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
o
end
end
class InterpolatedMatchLastLineNode < Node
include RegularExpressionOptions
end
class InterpolatedRegularExpressionNode < Node
include RegularExpressionOptions
end
class MatchLastLineNode < Node
include RegularExpressionOptions
end
class RegularExpressionNode < Node
include RegularExpressionOptions
end
private_constant :RegularExpressionOptions
module HeredocQuery # :nodoc:
# Returns true if this node was represented as a heredoc in the source code.
def heredoc?
opening&.start_with?("<<")
end
end
class InterpolatedStringNode < Node
include HeredocQuery
end
class InterpolatedXStringNode < Node
include HeredocQuery
end
class StringNode < Node
include HeredocQuery
end
class XStringNode < Node
include HeredocQuery
end
private_constant :HeredocQuery
class FloatNode < Node
# Returns the value of the node as a Ruby Float.
def value
Float(slice)
end
end
class ImaginaryNode < Node
# Returns the value of the node as a Ruby Complex.
def value
Complex(0, numeric.value)
end
end
class IntegerNode < Node
# Returns the value of the node as a Ruby Integer.
def value
Integer(slice)
end
end
class RationalNode < Node
# Returns the value of the node as a Ruby Rational.
def value
Rational(slice.chomp("r"))
end
end
class ConstantReadNode < Node
# Returns the list of parts for the full name of this constant.
# For example: [:Foo]
def full_name_parts
[name]
end
# Returns the full name of this constant. For example: "Foo"
def full_name
name.name
end
end
class ConstantPathNode < Node
# An error class raised when dynamic parts are found while computing a
# constant path's full name. For example:
# Foo::Bar::Baz -> does not raise because all parts of the constant path are
# simple constants
# var::Bar::Baz -> raises because the first part of the constant path is a
# local variable
class DynamicPartsInConstantPathError < StandardError; end
# Returns the list of parts for the full name of this constant path.
# For example: [:Foo, :Bar]
def full_name_parts
parts = [child.name]
current = parent
while current.is_a?(ConstantPathNode)
parts.unshift(current.child.name)
current = current.parent
end
unless current.is_a?(ConstantReadNode)
raise DynamicPartsInConstantPathError, "Constant path contains dynamic parts. Cannot compute full name"
end
parts.unshift(current&.name || :"")
end
# Returns the full name of this constant path. For example: "Foo::Bar"
def full_name
full_name_parts.join("::")
end
end
class ConstantPathTargetNode < Node
# Returns the list of parts for the full name of this constant path.
# For example: [:Foo, :Bar]
def full_name_parts
(parent&.full_name_parts || [:""]).push(child.name)
end
# Returns the full name of this constant path. For example: "Foo::Bar"
def full_name
full_name_parts.join("::")
end
end
class ParametersNode < Node
# Mirrors the Method#parameters method.
def signature
names = []
requireds.each do |param|
names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name])
end
optionals.each { |param| names << [:opt, param.name] }
names << [:rest, rest.name || :*] if rest
posts.each do |param|
names << (param.is_a?(MultiTargetNode) ? [:req] : [:req, param.name])
end
# Regardless of the order in which the keywords were defined, the required
# keywords always come first followed by the optional keywords.
keyopt = []
keywords.each do |param|
if param.is_a?(OptionalKeywordParameterNode)
keyopt << param
else
names << [:keyreq, param.name]
end
end
keyopt.each { |param| names << [:key, param.name] }
case keyword_rest
when ForwardingParameterNode
names.concat([[:rest, :*], [:keyrest, :**], [:block, :&]])
when KeywordRestParameterNode
names << [:keyrest, keyword_rest.name || :**]
when NoKeywordsParameterNode
names << [:nokey]
end
names << [:block, block.name || :&] if block
names
end
end
end
share/ruby/prism/debug.rb 0000644 00000014170 15173517735 0011415 0 ustar 00 # frozen_string_literal: true
module Prism
# This module is used for testing and debugging and is not meant to be used by
# consumers of this library.
module Debug
# A wrapper around a RubyVM::InstructionSequence that provides a more
# convenient interface for accessing parts of the iseq.
class ISeq # :nodoc:
attr_reader :parts
def initialize(parts)
@parts = parts
end
def type
parts[0]
end
def local_table
parts[10]
end
def instructions
parts[13]
end
def each_child
instructions.each do |instruction|
# Only look at arrays. Other instructions are line numbers or
# tracepoint events.
next unless instruction.is_a?(Array)
instruction.each do |opnd|
# Only look at arrays. Other operands are literals.
next unless opnd.is_a?(Array)
# Only look at instruction sequences. Other operands are literals.
next unless opnd[0] == "YARVInstructionSequence/SimpleDataFormat"
yield ISeq.new(opnd)
end
end
end
end
private_constant :ISeq
# :call-seq:
# Debug::cruby_locals(source) -> Array
#
# For the given source, compiles with CRuby and returns a list of all of the
# sets of local variables that were encountered.
def self.cruby_locals(source)
verbose, $VERBOSE = $VERBOSE, nil
begin
locals = []
stack = [ISeq.new(RubyVM::InstructionSequence.compile(source).to_a)]
while (iseq = stack.pop)
names = [*iseq.local_table]
names.map!.with_index do |name, index|
# When an anonymous local variable is present in the iseq's local
# table, it is represented as the stack offset from the top.
# However, when these are dumped to binary and read back in, they
# are replaced with the symbol :#arg_rest. To consistently handle
# this, we replace them here with their index.
if name == :"#arg_rest"
names.length - index + 1
else
name
end
end
locals << names
iseq.each_child { |child| stack << child }
end
locals
ensure
$VERBOSE = verbose
end
end
# Used to hold the place of a local that will be in the local table but
# cannot be accessed directly from the source code. For example, the
# iteration variable in a for loop or the positional parameter on a method
# definition that is destructured.
AnonymousLocal = Object.new
private_constant :AnonymousLocal
# :call-seq:
# Debug::prism_locals(source) -> Array
#
# For the given source, parses with prism and returns a list of all of the
# sets of local variables that were encountered.
def self.prism_locals(source)
locals = []
stack = [Prism.parse(source).value]
while (node = stack.pop)
case node
when BlockNode, DefNode, LambdaNode
names = node.locals
params =
if node.is_a?(DefNode)
node.parameters
elsif node.parameters.is_a?(NumberedParametersNode)
nil
else
node.parameters&.parameters
end
# prism places parameters in the same order that they appear in the
# source. CRuby places them in the order that they need to appear
# according to their own internal calling convention. We mimic that
# order here so that we can compare properly.
if params
sorted = [
*params.requireds.map do |required|
if required.is_a?(RequiredParameterNode)
required.name
else
AnonymousLocal
end
end,
*params.optionals.map(&:name),
*((params.rest.name || :*) if params.rest && !params.rest.is_a?(ImplicitRestNode)),
*params.posts.map do |post|
if post.is_a?(RequiredParameterNode)
post.name
else
AnonymousLocal
end
end,
*params.keywords.grep(RequiredKeywordParameterNode).map(&:name),
*params.keywords.grep(OptionalKeywordParameterNode).map(&:name),
]
if params.keyword_rest.is_a?(ForwardingParameterNode)
sorted.push(:*, :&, :"...")
end
sorted << AnonymousLocal if params.keywords.any?
# Recurse down the parameter tree to find any destructured
# parameters and add them after the other parameters.
param_stack = params.requireds.concat(params.posts).grep(MultiTargetNode).reverse
while (param = param_stack.pop)
case param
when MultiTargetNode
param_stack.concat(param.rights.reverse)
param_stack << param.rest
param_stack.concat(param.lefts.reverse)
when RequiredParameterNode
sorted << param.name
when SplatNode
sorted << param.expression.name if param.expression
end
end
names = sorted.concat(names - sorted)
end
names.map!.with_index do |name, index|
if name == AnonymousLocal
names.length - index + 1
else
name
end
end
locals << names
when ClassNode, ModuleNode, ProgramNode, SingletonClassNode
locals << node.locals
when ForNode
locals << [2]
when PostExecutionNode
locals.push([], [])
when InterpolatedRegularExpressionNode
locals << [] if node.once?
end
stack.concat(node.compact_child_nodes)
end
locals
end
# :call-seq:
# Debug::newlines(source) -> Array
#
# For the given source string, return the byte offsets of every newline in
# the source.
def self.newlines(source)
Prism.parse(source).source.offsets
end
end
end
share/ruby/prism/parse_result/comments.rb 0000644 00000013100 15173517735 0014654 0 ustar 00 # frozen_string_literal: true
module Prism
class ParseResult
# When we've parsed the source, we have both the syntax tree and the list of
# comments that we found in the source. This class is responsible for
# walking the tree and finding the nearest location to attach each comment.
#
# It does this by first finding the nearest locations to each comment.
# Locations can either come from nodes directly or from location fields on
# nodes. For example, a `ClassNode` has an overall location encompassing the
# entire class, but it also has a location for the `class` keyword.
#
# Once the nearest locations are found, it determines which one to attach
# to. If it's a trailing comment (a comment on the same line as other source
# code), it will favor attaching to the nearest location that occurs before
# the comment. Otherwise it will favor attaching to the nearest location
# that is after the comment.
class Comments
# A target for attaching comments that is based on a specific node's
# location.
class NodeTarget # :nodoc:
attr_reader :node
def initialize(node)
@node = node
end
def start_offset
node.location.start_offset
end
def end_offset
node.location.end_offset
end
def encloses?(comment)
start_offset <= comment.location.start_offset &&
comment.location.end_offset <= end_offset
end
def <<(comment)
node.location.comments << comment
end
end
# A target for attaching comments that is based on a location field on a
# node. For example, the `end` token of a ClassNode.
class LocationTarget # :nodoc:
attr_reader :location
def initialize(location)
@location = location
end
def start_offset
location.start_offset
end
def end_offset
location.end_offset
end
def encloses?(comment)
false
end
def <<(comment)
location.comments << comment
end
end
# The parse result that we are attaching comments to.
attr_reader :parse_result
# Create a new Comments object that will attach comments to the given
# parse result.
def initialize(parse_result)
@parse_result = parse_result
end
# Attach the comments to their respective locations in the tree by
# mutating the parse result.
def attach!
parse_result.comments.each do |comment|
preceding, enclosing, following = nearest_targets(parse_result.value, comment)
target =
if comment.trailing?
preceding || following || enclosing || NodeTarget.new(parse_result.value)
else
# If a comment exists on its own line, prefer a leading comment.
following || preceding || enclosing || NodeTarget.new(parse_result.value)
end
target << comment
end
end
private
# Responsible for finding the nearest targets to the given comment within
# the context of the given encapsulating node.
def nearest_targets(node, comment)
comment_start = comment.location.start_offset
comment_end = comment.location.end_offset
targets = []
node.comment_targets.map do |value|
case value
when StatementsNode
targets.concat(value.body.map { |node| NodeTarget.new(node) })
when Node
targets << NodeTarget.new(value)
when Location
targets << LocationTarget.new(value)
end
end
targets.sort_by!(&:start_offset)
preceding = nil
following = nil
left = 0
right = targets.length
# This is a custom binary search that finds the nearest nodes to the
# given comment. When it finds a node that completely encapsulates the
# comment, it recurses downward into the tree.
while left < right
middle = (left + right) / 2
target = targets[middle]
target_start = target.start_offset
target_end = target.end_offset
if target.encloses?(comment)
# The comment is completely contained by this target. Abandon the
# binary search at this level.
return nearest_targets(target.node, comment)
end
if target_end <= comment_start
# This target falls completely before the comment. Because we will
# never consider this target or any targets before it again, this
# target must be the closest preceding target we have encountered so
# far.
preceding = target
left = middle + 1
next
end
if comment_end <= target_start
# This target falls completely after the comment. Because we will
# never consider this target or any targets after it again, this
# target must be the closest following target we have encountered so
# far.
following = target
right = middle
next
end
# This should only happen if there is a bug in this parser.
raise "Comment location overlaps with a target location"
end
[preceding, NodeTarget.new(node), following]
end
end
private_constant :Comments
# Attach the list of comments to their respective locations in the tree.
def attach_comments!
Comments.new(self).attach!
end
end
end
share/ruby/prism/parse_result/newlines.rb 0000644 00000003713 15173517735 0014664 0 ustar 00 # frozen_string_literal: true
module Prism
class ParseResult
# The :line tracepoint event gets fired whenever the Ruby VM encounters an
# expression on a new line. The types of expressions that can trigger this
# event are:
#
# * if statements
# * unless statements
# * nodes that are children of statements lists
#
# In order to keep track of the newlines, we have a list of offsets that
# come back from the parser. We assign these offsets to the first nodes that
# we find in the tree that are on those lines.
#
# Note that the logic in this file should be kept in sync with the Java
# MarkNewlinesVisitor, since that visitor is responsible for marking the
# newlines for JRuby/TruffleRuby.
class Newlines < Visitor
# Create a new Newlines visitor with the given newline offsets.
def initialize(newline_marked)
@newline_marked = newline_marked
end
# Permit block/lambda nodes to mark newlines within themselves.
def visit_block_node(node)
old_newline_marked = @newline_marked
@newline_marked = Array.new(old_newline_marked.size, false)
begin
super(node)
ensure
@newline_marked = old_newline_marked
end
end
alias_method :visit_lambda_node, :visit_block_node
# Mark if/unless nodes as newlines.
def visit_if_node(node)
node.set_newline_flag(@newline_marked)
super(node)
end
alias_method :visit_unless_node, :visit_if_node
# Permit statements lists to mark newlines within themselves.
def visit_statements_node(node)
node.body.each do |child|
child.set_newline_flag(@newline_marked)
end
super(node)
end
end
private_constant :Newlines
# Walk the tree and mark nodes that are on a new line.
def mark_newlines!
value.accept(Newlines.new(Array.new(1 + source.offsets.size, false)))
end
end
end
share/ruby/prism/serialize.rb 0000644 00000161325 15173517735 0012323 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/serialize.rb.erb
if you are looking to modify the template
=end
require "stringio"
# Polyfill for String#unpack1 with the offset parameter.
if String.instance_method(:unpack1).parameters.none? { |_, name| name == :offset }
String.prepend(
Module.new {
def unpack1(format, offset: 0) # :nodoc:
offset == 0 ? super(format) : self[offset..].unpack1(format)
end
}
)
end
module Prism
# A module responsible for deserializing parse results.
module Serialize
# The major version of prism that we are expecting to find in the serialized
# strings.
MAJOR_VERSION = 0
# The minor version of prism that we are expecting to find in the serialized
# strings.
MINOR_VERSION = 19
# The patch version of prism that we are expecting to find in the serialized
# strings.
PATCH_VERSION = 0
# Deserialize the AST represented by the given string into a parse result.
def self.load(input, serialized)
input = input.dup
source = Source.new(input)
loader = Loader.new(source, serialized)
result = loader.load_result
input.force_encoding(loader.encoding)
result
end
# Deserialize the tokens represented by the given string into a parse
# result.
def self.load_tokens(source, serialized)
Loader.new(source, serialized).load_tokens_result
end
class Loader # :nodoc:
attr_reader :encoding, :input, :serialized, :io
attr_reader :constant_pool_offset, :constant_pool, :source
attr_reader :start_line
def initialize(source, serialized)
@encoding = Encoding::UTF_8
@input = source.source.dup
@serialized = serialized
@io = StringIO.new(serialized)
@io.set_encoding(Encoding::BINARY)
@constant_pool_offset = nil
@constant_pool = nil
@source = source
define_load_node_lambdas unless RUBY_ENGINE == "ruby"
end
def load_header
raise "Invalid serialization" if io.read(5) != "PRISM"
raise "Invalid serialization" if io.read(3).unpack("C3") != [MAJOR_VERSION, MINOR_VERSION, PATCH_VERSION]
only_semantic_fields = io.read(1).unpack1("C")
unless only_semantic_fields == 0
raise "Invalid serialization (location fields must be included but are not)"
end
end
def load_encoding
@encoding = Encoding.find(io.read(load_varuint))
@input = input.force_encoding(@encoding).freeze
@encoding
end
def load_start_line
source.start_line = load_varsint
end
def load_comments
load_varuint.times.map do
case load_varuint
when 0 then InlineComment.new(load_location)
when 1 then EmbDocComment.new(load_location)
when 2 then DATAComment.new(load_location)
end
end
end
def load_metadata
comments = load_comments
magic_comments = load_varuint.times.map { MagicComment.new(load_location, load_location) }
data_loc = load_optional_location
errors = load_varuint.times.map { ParseError.new(load_embedded_string, load_location) }
warnings = load_varuint.times.map { ParseWarning.new(load_embedded_string, load_location) }
[comments, magic_comments, data_loc, errors, warnings]
end
def load_tokens
tokens = []
while type = TOKEN_TYPES.fetch(load_varuint)
start = load_varuint
length = load_varuint
lex_state = load_varuint
location = Location.new(@source, start, length)
tokens << [Prism::Token.new(type, location.slice, location), lex_state]
end
tokens
end
def load_tokens_result
tokens = load_tokens
encoding = load_encoding
load_start_line
comments, magic_comments, data_loc, errors, warnings = load_metadata
tokens.each { |token,| token.value.force_encoding(encoding) }
raise "Expected to consume all bytes while deserializing" unless @io.eof?
Prism::ParseResult.new(tokens, comments, magic_comments, data_loc, errors, warnings, @source)
end
def load_nodes
load_header
load_encoding
load_start_line
comments, magic_comments, data_loc, errors, warnings = load_metadata
@constant_pool_offset = io.read(4).unpack1("L")
@constant_pool = Array.new(load_varuint, nil)
[load_node, comments, magic_comments, data_loc, errors, warnings]
end
def load_result
node, comments, magic_comments, data_loc, errors, warnings = load_nodes
Prism::ParseResult.new(node, comments, magic_comments, data_loc, errors, warnings, @source)
end
private
# variable-length integer using https://en.wikipedia.org/wiki/LEB128
# This is also what protobuf uses: https://protobuf.dev/programming-guides/encoding/#varints
def load_varuint
n = io.getbyte
if n < 128
n
else
n -= 128
shift = 0
while (b = io.getbyte) >= 128
n += (b - 128) << (shift += 7)
end
n + (b << (shift + 7))
end
end
def load_varsint
n = load_varuint
(n >> 1) ^ (-(n & 1))
end
def load_serialized_length
io.read(4).unpack1("L")
end
def load_optional_node
if io.getbyte != 0
io.pos -= 1
load_node
end
end
def load_embedded_string
io.read(load_varuint).force_encoding(encoding)
end
def load_string
type = io.getbyte
case type
when 1
input.byteslice(load_varuint, load_varuint).force_encoding(encoding)
when 2
load_embedded_string
else
raise "Unknown serialized string type: #{type}"
end
end
def load_location
Location.new(source, load_varuint, load_varuint)
end
def load_optional_location
load_location if io.getbyte != 0
end
def load_constant(index)
constant = constant_pool[index]
unless constant
offset = constant_pool_offset + index * 8
start = serialized.unpack1("L", offset: offset)
length = serialized.unpack1("L", offset: offset + 4)
constant =
if start.nobits?(1 << 31)
input.byteslice(start, length).to_sym
else
serialized.byteslice(start & ((1 << 31) - 1), length).to_sym
end
constant_pool[index] = constant
end
constant
end
def load_required_constant
load_constant(load_varuint - 1)
end
def load_optional_constant
index = load_varuint
load_constant(index - 1) if index != 0
end
if RUBY_ENGINE == 'ruby'
def load_node
type = io.getbyte
location = load_location
case type
when 1 then
AliasGlobalVariableNode.new(load_node, load_node, load_location, location)
when 2 then
AliasMethodNode.new(load_node, load_node, load_location, location)
when 3 then
AlternationPatternNode.new(load_node, load_node, load_location, location)
when 4 then
AndNode.new(load_node, load_node, load_location, location)
when 5 then
ArgumentsNode.new(load_varuint, Array.new(load_varuint) { load_node }, location)
when 6 then
ArrayNode.new(load_varuint, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
when 7 then
ArrayPatternNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
when 8 then
AssocNode.new(load_node, load_optional_node, load_optional_location, location)
when 9 then
AssocSplatNode.new(load_optional_node, load_location, location)
when 10 then
BackReferenceReadNode.new(load_required_constant, location)
when 11 then
BeginNode.new(load_optional_location, load_optional_node, load_optional_node, load_optional_node, load_optional_node, load_optional_location, location)
when 12 then
BlockArgumentNode.new(load_optional_node, load_location, location)
when 13 then
BlockLocalVariableNode.new(load_required_constant, location)
when 14 then
BlockNode.new(Array.new(load_varuint) { load_required_constant }, load_varuint, load_optional_node, load_optional_node, load_location, load_location, location)
when 15 then
BlockParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
when 16 then
BlockParametersNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
when 17 then
BreakNode.new(load_optional_node, load_location, location)
when 18 then
CallAndWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_location, load_node, location)
when 19 then
CallNode.new(load_varuint, load_optional_node, load_optional_location, load_required_constant, load_optional_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, location)
when 20 then
CallOperatorWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_required_constant, load_location, load_node, location)
when 21 then
CallOrWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_location, load_node, location)
when 22 then
CallTargetNode.new(load_varuint, load_node, load_location, load_required_constant, load_location, location)
when 23 then
CapturePatternNode.new(load_node, load_node, load_location, location)
when 24 then
CaseMatchNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_location, load_location, location)
when 25 then
CaseNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_location, load_location, location)
when 26 then
ClassNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_location, load_required_constant, location)
when 27 then
ClassVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 28 then
ClassVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
when 29 then
ClassVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 30 then
ClassVariableReadNode.new(load_required_constant, location)
when 31 then
ClassVariableTargetNode.new(load_required_constant, location)
when 32 then
ClassVariableWriteNode.new(load_required_constant, load_location, load_node, load_optional_location, location)
when 33 then
ConstantAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 34 then
ConstantOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
when 35 then
ConstantOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 36 then
ConstantPathAndWriteNode.new(load_node, load_location, load_node, location)
when 37 then
ConstantPathNode.new(load_optional_node, load_node, load_location, location)
when 38 then
ConstantPathOperatorWriteNode.new(load_node, load_location, load_node, load_required_constant, location)
when 39 then
ConstantPathOrWriteNode.new(load_node, load_location, load_node, location)
when 40 then
ConstantPathTargetNode.new(load_optional_node, load_node, load_location, location)
when 41 then
ConstantPathWriteNode.new(load_node, load_location, load_node, location)
when 42 then
ConstantReadNode.new(load_required_constant, location)
when 43 then
ConstantTargetNode.new(load_required_constant, location)
when 44 then
ConstantWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
when 45 then
load_serialized_length
DefNode.new(load_required_constant, load_location, load_optional_node, load_optional_node, load_optional_node, Array.new(load_varuint) { load_required_constant }, load_varuint, load_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, location)
when 46 then
DefinedNode.new(load_optional_location, load_node, load_optional_location, load_location, location)
when 47 then
ElseNode.new(load_location, load_optional_node, load_optional_location, location)
when 48 then
EmbeddedStatementsNode.new(load_location, load_optional_node, load_location, location)
when 49 then
EmbeddedVariableNode.new(load_location, load_node, location)
when 50 then
EnsureNode.new(load_location, load_optional_node, load_location, location)
when 51 then
FalseNode.new(location)
when 52 then
FindPatternNode.new(load_optional_node, load_node, Array.new(load_varuint) { load_node }, load_node, load_optional_location, load_optional_location, location)
when 53 then
FlipFlopNode.new(load_varuint, load_optional_node, load_optional_node, load_location, location)
when 54 then
FloatNode.new(location)
when 55 then
ForNode.new(load_node, load_node, load_optional_node, load_location, load_location, load_optional_location, load_location, location)
when 56 then
ForwardingArgumentsNode.new(location)
when 57 then
ForwardingParameterNode.new(location)
when 58 then
ForwardingSuperNode.new(load_optional_node, location)
when 59 then
GlobalVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 60 then
GlobalVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
when 61 then
GlobalVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 62 then
GlobalVariableReadNode.new(load_required_constant, location)
when 63 then
GlobalVariableTargetNode.new(load_required_constant, location)
when 64 then
GlobalVariableWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
when 65 then
HashNode.new(load_location, Array.new(load_varuint) { load_node }, load_location, location)
when 66 then
HashPatternNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_optional_location, load_optional_location, location)
when 67 then
IfNode.new(load_optional_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_optional_location, location)
when 68 then
ImaginaryNode.new(load_node, location)
when 69 then
ImplicitNode.new(load_node, location)
when 70 then
ImplicitRestNode.new(location)
when 71 then
InNode.new(load_node, load_optional_node, load_location, load_optional_location, location)
when 72 then
IndexAndWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_location, load_node, location)
when 73 then
IndexOperatorWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_required_constant, load_location, load_node, location)
when 74 then
IndexOrWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_location, load_node, location)
when 75 then
IndexTargetNode.new(load_varuint, load_node, load_location, load_optional_node, load_location, load_optional_node, location)
when 76 then
InstanceVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 77 then
InstanceVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
when 78 then
InstanceVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
when 79 then
InstanceVariableReadNode.new(load_required_constant, location)
when 80 then
InstanceVariableTargetNode.new(load_required_constant, location)
when 81 then
InstanceVariableWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
when 82 then
IntegerNode.new(load_varuint, location)
when 83 then
InterpolatedMatchLastLineNode.new(load_varuint, load_location, Array.new(load_varuint) { load_node }, load_location, location)
when 84 then
InterpolatedRegularExpressionNode.new(load_varuint, load_location, Array.new(load_varuint) { load_node }, load_location, location)
when 85 then
InterpolatedStringNode.new(load_optional_location, Array.new(load_varuint) { load_node }, load_optional_location, location)
when 86 then
InterpolatedSymbolNode.new(load_optional_location, Array.new(load_varuint) { load_node }, load_optional_location, location)
when 87 then
InterpolatedXStringNode.new(load_location, Array.new(load_varuint) { load_node }, load_location, location)
when 88 then
KeywordHashNode.new(load_varuint, Array.new(load_varuint) { load_node }, location)
when 89 then
KeywordRestParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
when 90 then
LambdaNode.new(Array.new(load_varuint) { load_required_constant }, load_varuint, load_location, load_location, load_location, load_optional_node, load_optional_node, location)
when 91 then
LocalVariableAndWriteNode.new(load_location, load_location, load_node, load_required_constant, load_varuint, location)
when 92 then
LocalVariableOperatorWriteNode.new(load_location, load_location, load_node, load_required_constant, load_required_constant, load_varuint, location)
when 93 then
LocalVariableOrWriteNode.new(load_location, load_location, load_node, load_required_constant, load_varuint, location)
when 94 then
LocalVariableReadNode.new(load_required_constant, load_varuint, location)
when 95 then
LocalVariableTargetNode.new(load_required_constant, load_varuint, location)
when 96 then
LocalVariableWriteNode.new(load_required_constant, load_varuint, load_location, load_node, load_location, location)
when 97 then
MatchLastLineNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
when 98 then
MatchPredicateNode.new(load_node, load_node, load_location, location)
when 99 then
MatchRequiredNode.new(load_node, load_node, load_location, location)
when 100 then
MatchWriteNode.new(load_node, Array.new(load_varuint) { load_node }, location)
when 101 then
MissingNode.new(location)
when 102 then
ModuleNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_node, load_optional_node, load_location, load_required_constant, location)
when 103 then
MultiTargetNode.new(Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
when 104 then
MultiWriteNode.new(Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, load_location, load_node, location)
when 105 then
NextNode.new(load_optional_node, load_location, location)
when 106 then
NilNode.new(location)
when 107 then
NoKeywordsParameterNode.new(load_location, load_location, location)
when 108 then
NumberedParametersNode.new(io.getbyte, location)
when 109 then
NumberedReferenceReadNode.new(load_varuint, location)
when 110 then
OptionalKeywordParameterNode.new(load_required_constant, load_location, load_node, location)
when 111 then
OptionalParameterNode.new(load_required_constant, load_location, load_location, load_node, location)
when 112 then
OrNode.new(load_node, load_node, load_location, location)
when 113 then
ParametersNode.new(Array.new(load_varuint) { load_node }, Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, Array.new(load_varuint) { load_node }, load_optional_node, load_optional_node, location)
when 114 then
ParenthesesNode.new(load_optional_node, load_location, load_location, location)
when 115 then
PinnedExpressionNode.new(load_node, load_location, load_location, load_location, location)
when 116 then
PinnedVariableNode.new(load_node, load_location, location)
when 117 then
PostExecutionNode.new(load_optional_node, load_location, load_location, load_location, location)
when 118 then
PreExecutionNode.new(load_optional_node, load_location, load_location, load_location, location)
when 119 then
ProgramNode.new(Array.new(load_varuint) { load_required_constant }, load_node, location)
when 120 then
RangeNode.new(load_varuint, load_optional_node, load_optional_node, load_location, location)
when 121 then
RationalNode.new(load_node, location)
when 122 then
RedoNode.new(location)
when 123 then
RegularExpressionNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
when 124 then
RequiredKeywordParameterNode.new(load_required_constant, load_location, location)
when 125 then
RequiredParameterNode.new(load_required_constant, location)
when 126 then
RescueModifierNode.new(load_node, load_location, load_node, location)
when 127 then
RescueNode.new(load_location, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_node, load_optional_node, load_optional_node, location)
when 128 then
RestParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
when 129 then
RetryNode.new(location)
when 130 then
ReturnNode.new(load_location, load_optional_node, location)
when 131 then
SelfNode.new(location)
when 132 then
SingletonClassNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_location, load_node, load_optional_node, load_location, location)
when 133 then
SourceEncodingNode.new(location)
when 134 then
SourceFileNode.new(load_string, location)
when 135 then
SourceLineNode.new(location)
when 136 then
SplatNode.new(load_location, load_optional_node, location)
when 137 then
StatementsNode.new(Array.new(load_varuint) { load_node }, location)
when 138 then
StringNode.new(load_varuint, load_optional_location, load_location, load_optional_location, load_string, location)
when 139 then
SuperNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, location)
when 140 then
SymbolNode.new(load_varuint, load_optional_location, load_optional_location, load_optional_location, load_string, location)
when 141 then
TrueNode.new(location)
when 142 then
UndefNode.new(Array.new(load_varuint) { load_node }, load_location, location)
when 143 then
UnlessNode.new(load_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_optional_location, location)
when 144 then
UntilNode.new(load_varuint, load_location, load_optional_location, load_node, load_optional_node, location)
when 145 then
WhenNode.new(load_location, Array.new(load_varuint) { load_node }, load_optional_node, location)
when 146 then
WhileNode.new(load_varuint, load_location, load_optional_location, load_node, load_optional_node, location)
when 147 then
XStringNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
when 148 then
YieldNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, location)
end
end
else
def load_node
type = io.getbyte
@load_node_lambdas[type].call
end
def define_load_node_lambdas
@load_node_lambdas = [
nil,
-> {
location = load_location
AliasGlobalVariableNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
AliasMethodNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
AlternationPatternNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
AndNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
ArgumentsNode.new(load_varuint, Array.new(load_varuint) { load_node }, location)
},
-> {
location = load_location
ArrayNode.new(load_varuint, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
ArrayPatternNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
AssocNode.new(load_node, load_optional_node, load_optional_location, location)
},
-> {
location = load_location
AssocSplatNode.new(load_optional_node, load_location, location)
},
-> {
location = load_location
BackReferenceReadNode.new(load_required_constant, location)
},
-> {
location = load_location
BeginNode.new(load_optional_location, load_optional_node, load_optional_node, load_optional_node, load_optional_node, load_optional_location, location)
},
-> {
location = load_location
BlockArgumentNode.new(load_optional_node, load_location, location)
},
-> {
location = load_location
BlockLocalVariableNode.new(load_required_constant, location)
},
-> {
location = load_location
BlockNode.new(Array.new(load_varuint) { load_required_constant }, load_varuint, load_optional_node, load_optional_node, load_location, load_location, location)
},
-> {
location = load_location
BlockParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
},
-> {
location = load_location
BlockParametersNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
BreakNode.new(load_optional_node, load_location, location)
},
-> {
location = load_location
CallAndWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_location, load_node, location)
},
-> {
location = load_location
CallNode.new(load_varuint, load_optional_node, load_optional_location, load_required_constant, load_optional_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, location)
},
-> {
location = load_location
CallOperatorWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_required_constant, load_location, load_node, location)
},
-> {
location = load_location
CallOrWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_optional_location, load_required_constant, load_required_constant, load_location, load_node, location)
},
-> {
location = load_location
CallTargetNode.new(load_varuint, load_node, load_location, load_required_constant, load_location, location)
},
-> {
location = load_location
CapturePatternNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
CaseMatchNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_location, load_location, location)
},
-> {
location = load_location
CaseNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_location, load_location, location)
},
-> {
location = load_location
ClassNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_location, load_required_constant, location)
},
-> {
location = load_location
ClassVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
ClassVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
},
-> {
location = load_location
ClassVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
ClassVariableReadNode.new(load_required_constant, location)
},
-> {
location = load_location
ClassVariableTargetNode.new(load_required_constant, location)
},
-> {
location = load_location
ClassVariableWriteNode.new(load_required_constant, load_location, load_node, load_optional_location, location)
},
-> {
location = load_location
ConstantAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
ConstantOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
},
-> {
location = load_location
ConstantOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
ConstantPathAndWriteNode.new(load_node, load_location, load_node, location)
},
-> {
location = load_location
ConstantPathNode.new(load_optional_node, load_node, load_location, location)
},
-> {
location = load_location
ConstantPathOperatorWriteNode.new(load_node, load_location, load_node, load_required_constant, location)
},
-> {
location = load_location
ConstantPathOrWriteNode.new(load_node, load_location, load_node, location)
},
-> {
location = load_location
ConstantPathTargetNode.new(load_optional_node, load_node, load_location, location)
},
-> {
location = load_location
ConstantPathWriteNode.new(load_node, load_location, load_node, location)
},
-> {
location = load_location
ConstantReadNode.new(load_required_constant, location)
},
-> {
location = load_location
ConstantTargetNode.new(load_required_constant, location)
},
-> {
location = load_location
ConstantWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
},
-> {
location = load_location
load_serialized_length
DefNode.new(load_required_constant, load_location, load_optional_node, load_optional_node, load_optional_node, Array.new(load_varuint) { load_required_constant }, load_varuint, load_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
DefinedNode.new(load_optional_location, load_node, load_optional_location, load_location, location)
},
-> {
location = load_location
ElseNode.new(load_location, load_optional_node, load_optional_location, location)
},
-> {
location = load_location
EmbeddedStatementsNode.new(load_location, load_optional_node, load_location, location)
},
-> {
location = load_location
EmbeddedVariableNode.new(load_location, load_node, location)
},
-> {
location = load_location
EnsureNode.new(load_location, load_optional_node, load_location, location)
},
-> {
location = load_location
FalseNode.new(location)
},
-> {
location = load_location
FindPatternNode.new(load_optional_node, load_node, Array.new(load_varuint) { load_node }, load_node, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
FlipFlopNode.new(load_varuint, load_optional_node, load_optional_node, load_location, location)
},
-> {
location = load_location
FloatNode.new(location)
},
-> {
location = load_location
ForNode.new(load_node, load_node, load_optional_node, load_location, load_location, load_optional_location, load_location, location)
},
-> {
location = load_location
ForwardingArgumentsNode.new(location)
},
-> {
location = load_location
ForwardingParameterNode.new(location)
},
-> {
location = load_location
ForwardingSuperNode.new(load_optional_node, location)
},
-> {
location = load_location
GlobalVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
GlobalVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
},
-> {
location = load_location
GlobalVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
GlobalVariableReadNode.new(load_required_constant, location)
},
-> {
location = load_location
GlobalVariableTargetNode.new(load_required_constant, location)
},
-> {
location = load_location
GlobalVariableWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
},
-> {
location = load_location
HashNode.new(load_location, Array.new(load_varuint) { load_node }, load_location, location)
},
-> {
location = load_location
HashPatternNode.new(load_optional_node, Array.new(load_varuint) { load_node }, load_optional_node, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
IfNode.new(load_optional_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_optional_location, location)
},
-> {
location = load_location
ImaginaryNode.new(load_node, location)
},
-> {
location = load_location
ImplicitNode.new(load_node, location)
},
-> {
location = load_location
ImplicitRestNode.new(location)
},
-> {
location = load_location
InNode.new(load_node, load_optional_node, load_location, load_optional_location, location)
},
-> {
location = load_location
IndexAndWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_location, load_node, location)
},
-> {
location = load_location
IndexOperatorWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_required_constant, load_location, load_node, location)
},
-> {
location = load_location
IndexOrWriteNode.new(load_varuint, load_optional_node, load_optional_location, load_location, load_optional_node, load_location, load_optional_node, load_location, load_node, location)
},
-> {
location = load_location
IndexTargetNode.new(load_varuint, load_node, load_location, load_optional_node, load_location, load_optional_node, location)
},
-> {
location = load_location
InstanceVariableAndWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
InstanceVariableOperatorWriteNode.new(load_required_constant, load_location, load_location, load_node, load_required_constant, location)
},
-> {
location = load_location
InstanceVariableOrWriteNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
InstanceVariableReadNode.new(load_required_constant, location)
},
-> {
location = load_location
InstanceVariableTargetNode.new(load_required_constant, location)
},
-> {
location = load_location
InstanceVariableWriteNode.new(load_required_constant, load_location, load_node, load_location, location)
},
-> {
location = load_location
IntegerNode.new(load_varuint, location)
},
-> {
location = load_location
InterpolatedMatchLastLineNode.new(load_varuint, load_location, Array.new(load_varuint) { load_node }, load_location, location)
},
-> {
location = load_location
InterpolatedRegularExpressionNode.new(load_varuint, load_location, Array.new(load_varuint) { load_node }, load_location, location)
},
-> {
location = load_location
InterpolatedStringNode.new(load_optional_location, Array.new(load_varuint) { load_node }, load_optional_location, location)
},
-> {
location = load_location
InterpolatedSymbolNode.new(load_optional_location, Array.new(load_varuint) { load_node }, load_optional_location, location)
},
-> {
location = load_location
InterpolatedXStringNode.new(load_location, Array.new(load_varuint) { load_node }, load_location, location)
},
-> {
location = load_location
KeywordHashNode.new(load_varuint, Array.new(load_varuint) { load_node }, location)
},
-> {
location = load_location
KeywordRestParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
},
-> {
location = load_location
LambdaNode.new(Array.new(load_varuint) { load_required_constant }, load_varuint, load_location, load_location, load_location, load_optional_node, load_optional_node, location)
},
-> {
location = load_location
LocalVariableAndWriteNode.new(load_location, load_location, load_node, load_required_constant, load_varuint, location)
},
-> {
location = load_location
LocalVariableOperatorWriteNode.new(load_location, load_location, load_node, load_required_constant, load_required_constant, load_varuint, location)
},
-> {
location = load_location
LocalVariableOrWriteNode.new(load_location, load_location, load_node, load_required_constant, load_varuint, location)
},
-> {
location = load_location
LocalVariableReadNode.new(load_required_constant, load_varuint, location)
},
-> {
location = load_location
LocalVariableTargetNode.new(load_required_constant, load_varuint, location)
},
-> {
location = load_location
LocalVariableWriteNode.new(load_required_constant, load_varuint, load_location, load_node, load_location, location)
},
-> {
location = load_location
MatchLastLineNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
},
-> {
location = load_location
MatchPredicateNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
MatchRequiredNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
MatchWriteNode.new(load_node, Array.new(load_varuint) { load_node }, location)
},
-> {
location = load_location
MissingNode.new(location)
},
-> {
location = load_location
ModuleNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_node, load_optional_node, load_location, load_required_constant, location)
},
-> {
location = load_location
MultiTargetNode.new(Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, location)
},
-> {
location = load_location
MultiWriteNode.new(Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_location, load_location, load_node, location)
},
-> {
location = load_location
NextNode.new(load_optional_node, load_location, location)
},
-> {
location = load_location
NilNode.new(location)
},
-> {
location = load_location
NoKeywordsParameterNode.new(load_location, load_location, location)
},
-> {
location = load_location
NumberedParametersNode.new(io.getbyte, location)
},
-> {
location = load_location
NumberedReferenceReadNode.new(load_varuint, location)
},
-> {
location = load_location
OptionalKeywordParameterNode.new(load_required_constant, load_location, load_node, location)
},
-> {
location = load_location
OptionalParameterNode.new(load_required_constant, load_location, load_location, load_node, location)
},
-> {
location = load_location
OrNode.new(load_node, load_node, load_location, location)
},
-> {
location = load_location
ParametersNode.new(Array.new(load_varuint) { load_node }, Array.new(load_varuint) { load_node }, load_optional_node, Array.new(load_varuint) { load_node }, Array.new(load_varuint) { load_node }, load_optional_node, load_optional_node, location)
},
-> {
location = load_location
ParenthesesNode.new(load_optional_node, load_location, load_location, location)
},
-> {
location = load_location
PinnedExpressionNode.new(load_node, load_location, load_location, load_location, location)
},
-> {
location = load_location
PinnedVariableNode.new(load_node, load_location, location)
},
-> {
location = load_location
PostExecutionNode.new(load_optional_node, load_location, load_location, load_location, location)
},
-> {
location = load_location
PreExecutionNode.new(load_optional_node, load_location, load_location, load_location, location)
},
-> {
location = load_location
ProgramNode.new(Array.new(load_varuint) { load_required_constant }, load_node, location)
},
-> {
location = load_location
RangeNode.new(load_varuint, load_optional_node, load_optional_node, load_location, location)
},
-> {
location = load_location
RationalNode.new(load_node, location)
},
-> {
location = load_location
RedoNode.new(location)
},
-> {
location = load_location
RegularExpressionNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
},
-> {
location = load_location
RequiredKeywordParameterNode.new(load_required_constant, load_location, location)
},
-> {
location = load_location
RequiredParameterNode.new(load_required_constant, location)
},
-> {
location = load_location
RescueModifierNode.new(load_node, load_location, load_node, location)
},
-> {
location = load_location
RescueNode.new(load_location, Array.new(load_varuint) { load_node }, load_optional_location, load_optional_node, load_optional_node, load_optional_node, location)
},
-> {
location = load_location
RestParameterNode.new(load_optional_constant, load_optional_location, load_location, location)
},
-> {
location = load_location
RetryNode.new(location)
},
-> {
location = load_location
ReturnNode.new(load_location, load_optional_node, location)
},
-> {
location = load_location
SelfNode.new(location)
},
-> {
location = load_location
SingletonClassNode.new(Array.new(load_varuint) { load_required_constant }, load_location, load_location, load_node, load_optional_node, load_location, location)
},
-> {
location = load_location
SourceEncodingNode.new(location)
},
-> {
location = load_location
SourceFileNode.new(load_string, location)
},
-> {
location = load_location
SourceLineNode.new(location)
},
-> {
location = load_location
SplatNode.new(load_location, load_optional_node, location)
},
-> {
location = load_location
StatementsNode.new(Array.new(load_varuint) { load_node }, location)
},
-> {
location = load_location
StringNode.new(load_varuint, load_optional_location, load_location, load_optional_location, load_string, location)
},
-> {
location = load_location
SuperNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, load_optional_node, location)
},
-> {
location = load_location
SymbolNode.new(load_varuint, load_optional_location, load_optional_location, load_optional_location, load_string, location)
},
-> {
location = load_location
TrueNode.new(location)
},
-> {
location = load_location
UndefNode.new(Array.new(load_varuint) { load_node }, load_location, location)
},
-> {
location = load_location
UnlessNode.new(load_location, load_node, load_optional_location, load_optional_node, load_optional_node, load_optional_location, location)
},
-> {
location = load_location
UntilNode.new(load_varuint, load_location, load_optional_location, load_node, load_optional_node, location)
},
-> {
location = load_location
WhenNode.new(load_location, Array.new(load_varuint) { load_node }, load_optional_node, location)
},
-> {
location = load_location
WhileNode.new(load_varuint, load_location, load_optional_location, load_node, load_optional_node, location)
},
-> {
location = load_location
XStringNode.new(load_varuint, load_location, load_location, load_location, load_string, location)
},
-> {
location = load_location
YieldNode.new(load_location, load_optional_location, load_optional_node, load_optional_location, location)
},
]
end
end
end
# The token types that can be indexed by their enum values.
TOKEN_TYPES = [
nil,
:EOF,
:MISSING,
:NOT_PROVIDED,
:AMPERSAND,
:AMPERSAND_AMPERSAND,
:AMPERSAND_AMPERSAND_EQUAL,
:AMPERSAND_DOT,
:AMPERSAND_EQUAL,
:BACKTICK,
:BACK_REFERENCE,
:BANG,
:BANG_EQUAL,
:BANG_TILDE,
:BRACE_LEFT,
:BRACE_RIGHT,
:BRACKET_LEFT,
:BRACKET_LEFT_ARRAY,
:BRACKET_LEFT_RIGHT,
:BRACKET_LEFT_RIGHT_EQUAL,
:BRACKET_RIGHT,
:CARET,
:CARET_EQUAL,
:CHARACTER_LITERAL,
:CLASS_VARIABLE,
:COLON,
:COLON_COLON,
:COMMA,
:COMMENT,
:CONSTANT,
:DOT,
:DOT_DOT,
:DOT_DOT_DOT,
:EMBDOC_BEGIN,
:EMBDOC_END,
:EMBDOC_LINE,
:EMBEXPR_BEGIN,
:EMBEXPR_END,
:EMBVAR,
:EQUAL,
:EQUAL_EQUAL,
:EQUAL_EQUAL_EQUAL,
:EQUAL_GREATER,
:EQUAL_TILDE,
:FLOAT,
:FLOAT_IMAGINARY,
:FLOAT_RATIONAL,
:FLOAT_RATIONAL_IMAGINARY,
:GLOBAL_VARIABLE,
:GREATER,
:GREATER_EQUAL,
:GREATER_GREATER,
:GREATER_GREATER_EQUAL,
:HEREDOC_END,
:HEREDOC_START,
:IDENTIFIER,
:IGNORED_NEWLINE,
:INSTANCE_VARIABLE,
:INTEGER,
:INTEGER_IMAGINARY,
:INTEGER_RATIONAL,
:INTEGER_RATIONAL_IMAGINARY,
:KEYWORD_ALIAS,
:KEYWORD_AND,
:KEYWORD_BEGIN,
:KEYWORD_BEGIN_UPCASE,
:KEYWORD_BREAK,
:KEYWORD_CASE,
:KEYWORD_CLASS,
:KEYWORD_DEF,
:KEYWORD_DEFINED,
:KEYWORD_DO,
:KEYWORD_DO_LOOP,
:KEYWORD_ELSE,
:KEYWORD_ELSIF,
:KEYWORD_END,
:KEYWORD_END_UPCASE,
:KEYWORD_ENSURE,
:KEYWORD_FALSE,
:KEYWORD_FOR,
:KEYWORD_IF,
:KEYWORD_IF_MODIFIER,
:KEYWORD_IN,
:KEYWORD_MODULE,
:KEYWORD_NEXT,
:KEYWORD_NIL,
:KEYWORD_NOT,
:KEYWORD_OR,
:KEYWORD_REDO,
:KEYWORD_RESCUE,
:KEYWORD_RESCUE_MODIFIER,
:KEYWORD_RETRY,
:KEYWORD_RETURN,
:KEYWORD_SELF,
:KEYWORD_SUPER,
:KEYWORD_THEN,
:KEYWORD_TRUE,
:KEYWORD_UNDEF,
:KEYWORD_UNLESS,
:KEYWORD_UNLESS_MODIFIER,
:KEYWORD_UNTIL,
:KEYWORD_UNTIL_MODIFIER,
:KEYWORD_WHEN,
:KEYWORD_WHILE,
:KEYWORD_WHILE_MODIFIER,
:KEYWORD_YIELD,
:KEYWORD___ENCODING__,
:KEYWORD___FILE__,
:KEYWORD___LINE__,
:LABEL,
:LABEL_END,
:LAMBDA_BEGIN,
:LESS,
:LESS_EQUAL,
:LESS_EQUAL_GREATER,
:LESS_LESS,
:LESS_LESS_EQUAL,
:METHOD_NAME,
:MINUS,
:MINUS_EQUAL,
:MINUS_GREATER,
:NEWLINE,
:NUMBERED_REFERENCE,
:PARENTHESIS_LEFT,
:PARENTHESIS_LEFT_PARENTHESES,
:PARENTHESIS_RIGHT,
:PERCENT,
:PERCENT_EQUAL,
:PERCENT_LOWER_I,
:PERCENT_LOWER_W,
:PERCENT_LOWER_X,
:PERCENT_UPPER_I,
:PERCENT_UPPER_W,
:PIPE,
:PIPE_EQUAL,
:PIPE_PIPE,
:PIPE_PIPE_EQUAL,
:PLUS,
:PLUS_EQUAL,
:QUESTION_MARK,
:REGEXP_BEGIN,
:REGEXP_END,
:SEMICOLON,
:SLASH,
:SLASH_EQUAL,
:STAR,
:STAR_EQUAL,
:STAR_STAR,
:STAR_STAR_EQUAL,
:STRING_BEGIN,
:STRING_CONTENT,
:STRING_END,
:SYMBOL_BEGIN,
:TILDE,
:UAMPERSAND,
:UCOLON_COLON,
:UDOT_DOT,
:UDOT_DOT_DOT,
:UMINUS,
:UMINUS_NUM,
:UPLUS,
:USTAR,
:USTAR_STAR,
:WORDS_SEP,
:__END__,
]
end
end
share/ruby/prism/parse_result.rb 0000644 00000032670 15173517735 0013044 0 ustar 00 # frozen_string_literal: true
module Prism
# This represents a source of Ruby code that has been parsed. It is used in
# conjunction with locations to allow them to resolve line numbers and source
# ranges.
class Source
# The source code that this source object represents.
attr_reader :source
# The line number where this source starts.
attr_accessor :start_line
# The list of newline byte offsets in the source code.
attr_reader :offsets
# Create a new source object with the given source code and newline byte
# offsets. If no newline byte offsets are given, they will be computed from
# the source code.
def initialize(source, start_line = 1, offsets = compute_offsets(source))
@source = source
@start_line = start_line
@offsets = offsets
end
# Perform a byteslice on the source code using the given byte offset and
# byte length.
def slice(byte_offset, length)
source.byteslice(byte_offset, length)
end
# Binary search through the offsets to find the line number for the given
# byte offset.
def line(byte_offset)
start_line + find_line(byte_offset)
end
# Return the byte offset of the start of the line corresponding to the given
# byte offset.
def line_start(byte_offset)
offsets[find_line(byte_offset)]
end
# Return the column number for the given byte offset.
def column(byte_offset)
byte_offset - line_start(byte_offset)
end
# Return the character offset for the given byte offset.
def character_offset(byte_offset)
source.byteslice(0, byte_offset).length
end
# Return the column number in characters for the given byte offset.
def character_column(byte_offset)
character_offset(byte_offset) - character_offset(line_start(byte_offset))
end
private
# Binary search through the offsets to find the line number for the given
# byte offset.
def find_line(byte_offset)
left = 0
right = offsets.length - 1
while left <= right
mid = left + (right - left) / 2
return mid if offsets[mid] == byte_offset
if offsets[mid] < byte_offset
left = mid + 1
else
right = mid - 1
end
end
left - 1
end
# Find all of the newlines in the source code and return their byte offsets
# from the start of the string an array.
def compute_offsets(code)
offsets = [0]
code.b.scan("\n") { offsets << $~.end(0) }
offsets
end
end
# This represents a location in the source.
class Location
# A Source object that is used to determine more information from the given
# offset and length.
protected attr_reader :source
# The byte offset from the beginning of the source where this location
# starts.
attr_reader :start_offset
# The length of this location in bytes.
attr_reader :length
# The list of comments attached to this location
attr_reader :comments
# Create a new location object with the given source, start byte offset, and
# byte length.
def initialize(source, start_offset, length)
@source = source
@start_offset = start_offset
@length = length
@comments = []
end
# Create a new location object with the given options.
def copy(**options)
Location.new(
options.fetch(:source) { source },
options.fetch(:start_offset) { start_offset },
options.fetch(:length) { length }
)
end
# Returns a string representation of this location.
def inspect
"#<Prism::Location @start_offset=#{@start_offset} @length=#{@length} start_line=#{start_line}>"
end
# The source code that this location represents.
def slice
source.slice(start_offset, length)
end
# The character offset from the beginning of the source where this location
# starts.
def start_character_offset
source.character_offset(start_offset)
end
# The byte offset from the beginning of the source where this location ends.
def end_offset
start_offset + length
end
# The character offset from the beginning of the source where this location
# ends.
def end_character_offset
source.character_offset(end_offset)
end
# The line number where this location starts.
def start_line
source.line(start_offset)
end
# The content of the line where this location starts before this location.
def start_line_slice
offset = source.line_start(start_offset)
source.slice(offset, start_offset - offset)
end
# The line number where this location ends.
def end_line
source.line(end_offset)
end
# The column number in bytes where this location starts from the start of
# the line.
def start_column
source.column(start_offset)
end
# The column number in characters where this location ends from the start of
# the line.
def start_character_column
source.character_column(start_offset)
end
# The column number in bytes where this location ends from the start of the
# line.
def end_column
source.column(end_offset)
end
# The column number in characters where this location ends from the start of
# the line.
def end_character_column
source.character_column(end_offset)
end
# Implement the hash pattern matching interface for Location.
def deconstruct_keys(keys)
{ start_offset: start_offset, end_offset: end_offset }
end
# Implement the pretty print interface for Location.
def pretty_print(q)
q.text("(#{start_line},#{start_column})-(#{end_line},#{end_column})")
end
# Returns true if the given other location is equal to this location.
def ==(other)
other.is_a?(Location) &&
other.start_offset == start_offset &&
other.end_offset == end_offset
end
# Returns a new location that stretches from this location to the given
# other location. Raises an error if this location is not before the other
# location or if they don't share the same source.
def join(other)
raise "Incompatible sources" if source != other.source
raise "Incompatible locations" if start_offset > other.start_offset
Location.new(source, start_offset, other.end_offset - start_offset)
end
# Returns a null location that does not correspond to a source and points to
# the beginning of the file. Useful for when you want a location object but
# do not care where it points.
def self.null
new(nil, 0, 0)
end
end
# This represents a comment that was encountered during parsing. It is the
# base class for all comment types.
class Comment
# The location of this comment in the source.
attr_reader :location
# Create a new comment object with the given location.
def initialize(location)
@location = location
end
# Implement the hash pattern matching interface for Comment.
def deconstruct_keys(keys)
{ location: location }
end
end
# InlineComment objects are the most common. They correspond to comments in
# the source file like this one that start with #.
class InlineComment < Comment
# Returns true if this comment happens on the same line as other code and
# false if the comment is by itself.
def trailing?
!location.start_line_slice.strip.empty?
end
# Returns a string representation of this comment.
def inspect
"#<Prism::InlineComment @location=#{location.inspect}>"
end
end
# EmbDocComment objects correspond to comments that are surrounded by =begin
# and =end.
class EmbDocComment < Comment
# This can only be true for inline comments.
def trailing?
false
end
# Returns a string representation of this comment.
def inspect
"#<Prism::EmbDocComment @location=#{location.inspect}>"
end
end
# This represents a magic comment that was encountered during parsing.
class MagicComment
# A Location object representing the location of the key in the source.
attr_reader :key_loc
# A Location object representing the location of the value in the source.
attr_reader :value_loc
# Create a new magic comment object with the given key and value locations.
def initialize(key_loc, value_loc)
@key_loc = key_loc
@value_loc = value_loc
end
# Returns the key of the magic comment by slicing it from the source code.
def key
key_loc.slice
end
# Returns the value of the magic comment by slicing it from the source code.
def value
value_loc.slice
end
# Implement the hash pattern matching interface for MagicComment.
def deconstruct_keys(keys)
{ key_loc: key_loc, value_loc: value_loc }
end
# Returns a string representation of this magic comment.
def inspect
"#<Prism::MagicComment @key=#{key.inspect} @value=#{value.inspect}>"
end
end
# This represents an error that was encountered during parsing.
class ParseError
# The message associated with this error.
attr_reader :message
# A Location object representing the location of this error in the source.
attr_reader :location
# Create a new error object with the given message and location.
def initialize(message, location)
@message = message
@location = location
end
# Implement the hash pattern matching interface for ParseError.
def deconstruct_keys(keys)
{ message: message, location: location }
end
# Returns a string representation of this error.
def inspect
"#<Prism::ParseError @message=#{@message.inspect} @location=#{@location.inspect}>"
end
end
# This represents a warning that was encountered during parsing.
class ParseWarning
# The message associated with this warning.
attr_reader :message
# A Location object representing the location of this warning in the source.
attr_reader :location
# Create a new warning object with the given message and location.
def initialize(message, location)
@message = message
@location = location
end
# Implement the hash pattern matching interface for ParseWarning.
def deconstruct_keys(keys)
{ message: message, location: location }
end
# Returns a string representation of this warning.
def inspect
"#<Prism::ParseWarning @message=#{@message.inspect} @location=#{@location.inspect}>"
end
end
# This represents the result of a call to ::parse or ::parse_file. It contains
# the AST, any comments that were encounters, and any errors that were
# encountered.
class ParseResult
# The value that was generated by parsing. Normally this holds the AST, but
# it can sometimes how a list of tokens or other results passed back from
# the parser.
attr_reader :value
# The list of comments that were encountered during parsing.
attr_reader :comments
# The list of magic comments that were encountered during parsing.
attr_reader :magic_comments
# An optional location that represents the location of the content after the
# __END__ marker. This content is loaded into the DATA constant when the
# file being parsed is the main file being executed.
attr_reader :data_loc
# The list of errors that were generated during parsing.
attr_reader :errors
# The list of warnings that were generated during parsing.
attr_reader :warnings
# A Source instance that represents the source code that was parsed.
attr_reader :source
# Create a new parse result object with the given values.
def initialize(value, comments, magic_comments, data_loc, errors, warnings, source)
@value = value
@comments = comments
@magic_comments = magic_comments
@data_loc = data_loc
@errors = errors
@warnings = warnings
@source = source
end
# Implement the hash pattern matching interface for ParseResult.
def deconstruct_keys(keys)
{ value: value, comments: comments, magic_comments: magic_comments, data_loc: data_loc, errors: errors, warnings: warnings }
end
# Returns true if there were no errors during parsing and false if there
# were.
def success?
errors.empty?
end
# Returns true if there were errors during parsing and false if there were
# not.
def failure?
!success?
end
end
# This represents a token from the Ruby source.
class Token
# The type of token that this token is.
attr_reader :type
# A byteslice of the source that this token represents.
attr_reader :value
# A Location object representing the location of this token in the source.
attr_reader :location
# Create a new token object with the given type, value, and location.
def initialize(type, value, location)
@type = type
@value = value
@location = location
end
# Implement the hash pattern matching interface for Token.
def deconstruct_keys(keys)
{ type: type, value: value, location: location }
end
# Implement the pretty print interface for Token.
def pretty_print(q)
q.group do
q.text(type.to_s)
self.location.pretty_print(q)
q.text("(")
q.nest(2) do
q.breakable("")
q.pp(value)
end
q.breakable("")
q.text(")")
end
end
# Returns true if the given other token is equal to this token.
def ==(other)
other.is_a?(Token) &&
other.type == type &&
other.value == value
end
end
end
share/ruby/prism/pattern.rb 0000644 00000017073 15173517735 0012011 0 ustar 00 # frozen_string_literal: true
module Prism
# A pattern is an object that wraps a Ruby pattern matching expression. The
# expression would normally be passed to an `in` clause within a `case`
# expression or a rightward assignment expression. For example, in the
# following snippet:
#
# case node
# in ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]
# end
#
# the pattern is the <tt>ConstantPathNode[...]</tt> expression.
#
# The pattern gets compiled into an object that responds to #call by running
# the #compile method. This method itself will run back through Prism to
# parse the expression into a tree, then walk the tree to generate the
# necessary callable objects. For example, if you wanted to compile the
# expression above into a callable, you would:
#
# callable = Prism::Pattern.new("ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]").compile
# callable.call(node)
#
# The callable object returned by #compile is guaranteed to respond to #call
# with a single argument, which is the node to match against. It also is
# guaranteed to respond to #===, which means it itself can be used in a `case`
# expression, as in:
#
# case node
# when callable
# end
#
# If the query given to the initializer cannot be compiled into a valid
# matcher (either because of a syntax error or because it is using syntax we
# do not yet support) then a Prism::Pattern::CompilationError will be
# raised.
class Pattern
# Raised when the query given to a pattern is either invalid Ruby syntax or
# is using syntax that we don't yet support.
class CompilationError < StandardError
# Create a new CompilationError with the given representation of the node
# that caused the error.
def initialize(repr)
super(<<~ERROR)
prism was unable to compile the pattern you provided into a usable
expression. It failed on to understand the node represented by:
#{repr}
Note that not all syntax supported by Ruby's pattern matching syntax
is also supported by prism's patterns. If you're using some syntax
that you believe should be supported, please open an issue on
GitHub at https://github.com/ruby/prism/issues/new.
ERROR
end
end
# The query that this pattern was initialized with.
attr_reader :query
# Create a new pattern with the given query. The query should be a string
# containing a Ruby pattern matching expression.
def initialize(query)
@query = query
@compiled = nil
end
# Compile the query into a callable object that can be used to match against
# nodes.
def compile
result = Prism.parse("case nil\nin #{query}\nend")
compile_node(result.value.statements.body.last.conditions.last.pattern)
end
# Scan the given node and all of its children for nodes that match the
# pattern. If a block is given, it will be called with each node that
# matches the pattern. If no block is given, an enumerator will be returned
# that will yield each node that matches the pattern.
def scan(root)
return to_enum(__method__, root) unless block_given?
@compiled ||= compile
queue = [root]
while (node = queue.shift)
yield node if @compiled.call(node)
queue.concat(node.compact_child_nodes)
end
end
private
# Shortcut for combining two procs into one that returns true if both return
# true.
def combine_and(left, right)
->(other) { left.call(other) && right.call(other) }
end
# Shortcut for combining two procs into one that returns true if either
# returns true.
def combine_or(left, right)
->(other) { left.call(other) || right.call(other) }
end
# Raise an error because the given node is not supported.
def compile_error(node)
raise CompilationError, node.inspect
end
# in [foo, bar, baz]
def compile_array_pattern_node(node)
compile_error(node) if !node.rest.nil? || node.posts.any?
constant = node.constant
compiled_constant = compile_node(constant) if constant
preprocessed = node.requireds.map { |required| compile_node(required) }
compiled_requireds = ->(other) do
deconstructed = other.deconstruct
deconstructed.length == preprocessed.length &&
preprocessed
.zip(deconstructed)
.all? { |(matcher, value)| matcher.call(value) }
end
if compiled_constant
combine_and(compiled_constant, compiled_requireds)
else
compiled_requireds
end
end
# in foo | bar
def compile_alternation_pattern_node(node)
combine_or(compile_node(node.left), compile_node(node.right))
end
# in Prism::ConstantReadNode
def compile_constant_path_node(node)
parent = node.parent
if parent.is_a?(ConstantReadNode) && parent.slice == "Prism"
compile_node(node.child)
else
compile_error(node)
end
end
# in ConstantReadNode
# in String
def compile_constant_read_node(node)
value = node.slice
if Prism.const_defined?(value, false)
clazz = Prism.const_get(value)
->(other) { clazz === other }
elsif Object.const_defined?(value, false)
clazz = Object.const_get(value)
->(other) { clazz === other }
else
compile_error(node)
end
end
# in InstanceVariableReadNode[name: Symbol]
# in { name: Symbol }
def compile_hash_pattern_node(node)
compile_error(node) if node.rest
compiled_constant = compile_node(node.constant) if node.constant
preprocessed =
node.elements.to_h do |element|
[element.key.unescaped.to_sym, compile_node(element.value)]
end
compiled_keywords = ->(other) do
deconstructed = other.deconstruct_keys(preprocessed.keys)
preprocessed.all? do |keyword, matcher|
deconstructed.key?(keyword) && matcher.call(deconstructed[keyword])
end
end
if compiled_constant
combine_and(compiled_constant, compiled_keywords)
else
compiled_keywords
end
end
# in nil
def compile_nil_node(node)
->(attribute) { attribute.nil? }
end
# in /foo/
def compile_regular_expression_node(node)
regexp = Regexp.new(node.unescaped, node.closing[1..])
->(attribute) { regexp === attribute }
end
# in ""
# in "foo"
def compile_string_node(node)
string = node.unescaped
->(attribute) { string === attribute }
end
# in :+
# in :foo
def compile_symbol_node(node)
symbol = node.unescaped.to_sym
->(attribute) { symbol === attribute }
end
# Compile any kind of node. Dispatch out to the individual compilation
# methods based on the type of node.
def compile_node(node)
case node
when AlternationPatternNode
compile_alternation_pattern_node(node)
when ArrayPatternNode
compile_array_pattern_node(node)
when ConstantPathNode
compile_constant_path_node(node)
when ConstantReadNode
compile_constant_read_node(node)
when HashPatternNode
compile_hash_pattern_node(node)
when NilNode
compile_nil_node(node)
when RegularExpressionNode
compile_regular_expression_node(node)
when StringNode
compile_string_node(node)
when SymbolNode
compile_symbol_node(node)
else
compile_error(node)
end
end
end
end
share/ruby/prism/visitor.rb 0000644 00000036027 15173517735 0012033 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/visitor.rb.erb
if you are looking to modify the template
=end
module Prism
# A class that knows how to walk down the tree. None of the individual visit
# methods are implemented on this visitor, so it forces the consumer to
# implement each one that they need. For a default implementation that
# continues walking the tree, see the Visitor class.
class BasicVisitor
# Calls `accept` on the given node if it is not `nil`, which in turn should
# call back into this visitor by calling the appropriate `visit_*` method.
def visit(node)
node&.accept(self)
end
# Visits each node in `nodes` by calling `accept` on each one.
def visit_all(nodes)
nodes.each { |node| node&.accept(self) }
end
# Visits the child nodes of `node` by calling `accept` on each one.
def visit_child_nodes(node)
node.compact_child_nodes.each { |node| node.accept(self) }
end
end
# A visitor is a class that provides a default implementation for every accept
# method defined on the nodes. This means it can walk a tree without the
# caller needing to define any special handling. This allows you to handle a
# subset of the tree, while still walking the whole tree.
#
# For example, to find all of the method calls that call the `foo` method, you
# could write:
#
# class FooCalls < Prism::Visitor
# def visit_call_node(node)
# if node.name == "foo"
# # Do something with the node
# end
#
# # Call super so that the visitor continues walking the tree
# super
# end
# end
#
class Visitor < BasicVisitor
# Visit a AliasGlobalVariableNode node
alias visit_alias_global_variable_node visit_child_nodes
# Visit a AliasMethodNode node
alias visit_alias_method_node visit_child_nodes
# Visit a AlternationPatternNode node
alias visit_alternation_pattern_node visit_child_nodes
# Visit a AndNode node
alias visit_and_node visit_child_nodes
# Visit a ArgumentsNode node
alias visit_arguments_node visit_child_nodes
# Visit a ArrayNode node
alias visit_array_node visit_child_nodes
# Visit a ArrayPatternNode node
alias visit_array_pattern_node visit_child_nodes
# Visit a AssocNode node
alias visit_assoc_node visit_child_nodes
# Visit a AssocSplatNode node
alias visit_assoc_splat_node visit_child_nodes
# Visit a BackReferenceReadNode node
alias visit_back_reference_read_node visit_child_nodes
# Visit a BeginNode node
alias visit_begin_node visit_child_nodes
# Visit a BlockArgumentNode node
alias visit_block_argument_node visit_child_nodes
# Visit a BlockLocalVariableNode node
alias visit_block_local_variable_node visit_child_nodes
# Visit a BlockNode node
alias visit_block_node visit_child_nodes
# Visit a BlockParameterNode node
alias visit_block_parameter_node visit_child_nodes
# Visit a BlockParametersNode node
alias visit_block_parameters_node visit_child_nodes
# Visit a BreakNode node
alias visit_break_node visit_child_nodes
# Visit a CallAndWriteNode node
alias visit_call_and_write_node visit_child_nodes
# Visit a CallNode node
alias visit_call_node visit_child_nodes
# Visit a CallOperatorWriteNode node
alias visit_call_operator_write_node visit_child_nodes
# Visit a CallOrWriteNode node
alias visit_call_or_write_node visit_child_nodes
# Visit a CallTargetNode node
alias visit_call_target_node visit_child_nodes
# Visit a CapturePatternNode node
alias visit_capture_pattern_node visit_child_nodes
# Visit a CaseMatchNode node
alias visit_case_match_node visit_child_nodes
# Visit a CaseNode node
alias visit_case_node visit_child_nodes
# Visit a ClassNode node
alias visit_class_node visit_child_nodes
# Visit a ClassVariableAndWriteNode node
alias visit_class_variable_and_write_node visit_child_nodes
# Visit a ClassVariableOperatorWriteNode node
alias visit_class_variable_operator_write_node visit_child_nodes
# Visit a ClassVariableOrWriteNode node
alias visit_class_variable_or_write_node visit_child_nodes
# Visit a ClassVariableReadNode node
alias visit_class_variable_read_node visit_child_nodes
# Visit a ClassVariableTargetNode node
alias visit_class_variable_target_node visit_child_nodes
# Visit a ClassVariableWriteNode node
alias visit_class_variable_write_node visit_child_nodes
# Visit a ConstantAndWriteNode node
alias visit_constant_and_write_node visit_child_nodes
# Visit a ConstantOperatorWriteNode node
alias visit_constant_operator_write_node visit_child_nodes
# Visit a ConstantOrWriteNode node
alias visit_constant_or_write_node visit_child_nodes
# Visit a ConstantPathAndWriteNode node
alias visit_constant_path_and_write_node visit_child_nodes
# Visit a ConstantPathNode node
alias visit_constant_path_node visit_child_nodes
# Visit a ConstantPathOperatorWriteNode node
alias visit_constant_path_operator_write_node visit_child_nodes
# Visit a ConstantPathOrWriteNode node
alias visit_constant_path_or_write_node visit_child_nodes
# Visit a ConstantPathTargetNode node
alias visit_constant_path_target_node visit_child_nodes
# Visit a ConstantPathWriteNode node
alias visit_constant_path_write_node visit_child_nodes
# Visit a ConstantReadNode node
alias visit_constant_read_node visit_child_nodes
# Visit a ConstantTargetNode node
alias visit_constant_target_node visit_child_nodes
# Visit a ConstantWriteNode node
alias visit_constant_write_node visit_child_nodes
# Visit a DefNode node
alias visit_def_node visit_child_nodes
# Visit a DefinedNode node
alias visit_defined_node visit_child_nodes
# Visit a ElseNode node
alias visit_else_node visit_child_nodes
# Visit a EmbeddedStatementsNode node
alias visit_embedded_statements_node visit_child_nodes
# Visit a EmbeddedVariableNode node
alias visit_embedded_variable_node visit_child_nodes
# Visit a EnsureNode node
alias visit_ensure_node visit_child_nodes
# Visit a FalseNode node
alias visit_false_node visit_child_nodes
# Visit a FindPatternNode node
alias visit_find_pattern_node visit_child_nodes
# Visit a FlipFlopNode node
alias visit_flip_flop_node visit_child_nodes
# Visit a FloatNode node
alias visit_float_node visit_child_nodes
# Visit a ForNode node
alias visit_for_node visit_child_nodes
# Visit a ForwardingArgumentsNode node
alias visit_forwarding_arguments_node visit_child_nodes
# Visit a ForwardingParameterNode node
alias visit_forwarding_parameter_node visit_child_nodes
# Visit a ForwardingSuperNode node
alias visit_forwarding_super_node visit_child_nodes
# Visit a GlobalVariableAndWriteNode node
alias visit_global_variable_and_write_node visit_child_nodes
# Visit a GlobalVariableOperatorWriteNode node
alias visit_global_variable_operator_write_node visit_child_nodes
# Visit a GlobalVariableOrWriteNode node
alias visit_global_variable_or_write_node visit_child_nodes
# Visit a GlobalVariableReadNode node
alias visit_global_variable_read_node visit_child_nodes
# Visit a GlobalVariableTargetNode node
alias visit_global_variable_target_node visit_child_nodes
# Visit a GlobalVariableWriteNode node
alias visit_global_variable_write_node visit_child_nodes
# Visit a HashNode node
alias visit_hash_node visit_child_nodes
# Visit a HashPatternNode node
alias visit_hash_pattern_node visit_child_nodes
# Visit a IfNode node
alias visit_if_node visit_child_nodes
# Visit a ImaginaryNode node
alias visit_imaginary_node visit_child_nodes
# Visit a ImplicitNode node
alias visit_implicit_node visit_child_nodes
# Visit a ImplicitRestNode node
alias visit_implicit_rest_node visit_child_nodes
# Visit a InNode node
alias visit_in_node visit_child_nodes
# Visit a IndexAndWriteNode node
alias visit_index_and_write_node visit_child_nodes
# Visit a IndexOperatorWriteNode node
alias visit_index_operator_write_node visit_child_nodes
# Visit a IndexOrWriteNode node
alias visit_index_or_write_node visit_child_nodes
# Visit a IndexTargetNode node
alias visit_index_target_node visit_child_nodes
# Visit a InstanceVariableAndWriteNode node
alias visit_instance_variable_and_write_node visit_child_nodes
# Visit a InstanceVariableOperatorWriteNode node
alias visit_instance_variable_operator_write_node visit_child_nodes
# Visit a InstanceVariableOrWriteNode node
alias visit_instance_variable_or_write_node visit_child_nodes
# Visit a InstanceVariableReadNode node
alias visit_instance_variable_read_node visit_child_nodes
# Visit a InstanceVariableTargetNode node
alias visit_instance_variable_target_node visit_child_nodes
# Visit a InstanceVariableWriteNode node
alias visit_instance_variable_write_node visit_child_nodes
# Visit a IntegerNode node
alias visit_integer_node visit_child_nodes
# Visit a InterpolatedMatchLastLineNode node
alias visit_interpolated_match_last_line_node visit_child_nodes
# Visit a InterpolatedRegularExpressionNode node
alias visit_interpolated_regular_expression_node visit_child_nodes
# Visit a InterpolatedStringNode node
alias visit_interpolated_string_node visit_child_nodes
# Visit a InterpolatedSymbolNode node
alias visit_interpolated_symbol_node visit_child_nodes
# Visit a InterpolatedXStringNode node
alias visit_interpolated_x_string_node visit_child_nodes
# Visit a KeywordHashNode node
alias visit_keyword_hash_node visit_child_nodes
# Visit a KeywordRestParameterNode node
alias visit_keyword_rest_parameter_node visit_child_nodes
# Visit a LambdaNode node
alias visit_lambda_node visit_child_nodes
# Visit a LocalVariableAndWriteNode node
alias visit_local_variable_and_write_node visit_child_nodes
# Visit a LocalVariableOperatorWriteNode node
alias visit_local_variable_operator_write_node visit_child_nodes
# Visit a LocalVariableOrWriteNode node
alias visit_local_variable_or_write_node visit_child_nodes
# Visit a LocalVariableReadNode node
alias visit_local_variable_read_node visit_child_nodes
# Visit a LocalVariableTargetNode node
alias visit_local_variable_target_node visit_child_nodes
# Visit a LocalVariableWriteNode node
alias visit_local_variable_write_node visit_child_nodes
# Visit a MatchLastLineNode node
alias visit_match_last_line_node visit_child_nodes
# Visit a MatchPredicateNode node
alias visit_match_predicate_node visit_child_nodes
# Visit a MatchRequiredNode node
alias visit_match_required_node visit_child_nodes
# Visit a MatchWriteNode node
alias visit_match_write_node visit_child_nodes
# Visit a MissingNode node
alias visit_missing_node visit_child_nodes
# Visit a ModuleNode node
alias visit_module_node visit_child_nodes
# Visit a MultiTargetNode node
alias visit_multi_target_node visit_child_nodes
# Visit a MultiWriteNode node
alias visit_multi_write_node visit_child_nodes
# Visit a NextNode node
alias visit_next_node visit_child_nodes
# Visit a NilNode node
alias visit_nil_node visit_child_nodes
# Visit a NoKeywordsParameterNode node
alias visit_no_keywords_parameter_node visit_child_nodes
# Visit a NumberedParametersNode node
alias visit_numbered_parameters_node visit_child_nodes
# Visit a NumberedReferenceReadNode node
alias visit_numbered_reference_read_node visit_child_nodes
# Visit a OptionalKeywordParameterNode node
alias visit_optional_keyword_parameter_node visit_child_nodes
# Visit a OptionalParameterNode node
alias visit_optional_parameter_node visit_child_nodes
# Visit a OrNode node
alias visit_or_node visit_child_nodes
# Visit a ParametersNode node
alias visit_parameters_node visit_child_nodes
# Visit a ParenthesesNode node
alias visit_parentheses_node visit_child_nodes
# Visit a PinnedExpressionNode node
alias visit_pinned_expression_node visit_child_nodes
# Visit a PinnedVariableNode node
alias visit_pinned_variable_node visit_child_nodes
# Visit a PostExecutionNode node
alias visit_post_execution_node visit_child_nodes
# Visit a PreExecutionNode node
alias visit_pre_execution_node visit_child_nodes
# Visit a ProgramNode node
alias visit_program_node visit_child_nodes
# Visit a RangeNode node
alias visit_range_node visit_child_nodes
# Visit a RationalNode node
alias visit_rational_node visit_child_nodes
# Visit a RedoNode node
alias visit_redo_node visit_child_nodes
# Visit a RegularExpressionNode node
alias visit_regular_expression_node visit_child_nodes
# Visit a RequiredKeywordParameterNode node
alias visit_required_keyword_parameter_node visit_child_nodes
# Visit a RequiredParameterNode node
alias visit_required_parameter_node visit_child_nodes
# Visit a RescueModifierNode node
alias visit_rescue_modifier_node visit_child_nodes
# Visit a RescueNode node
alias visit_rescue_node visit_child_nodes
# Visit a RestParameterNode node
alias visit_rest_parameter_node visit_child_nodes
# Visit a RetryNode node
alias visit_retry_node visit_child_nodes
# Visit a ReturnNode node
alias visit_return_node visit_child_nodes
# Visit a SelfNode node
alias visit_self_node visit_child_nodes
# Visit a SingletonClassNode node
alias visit_singleton_class_node visit_child_nodes
# Visit a SourceEncodingNode node
alias visit_source_encoding_node visit_child_nodes
# Visit a SourceFileNode node
alias visit_source_file_node visit_child_nodes
# Visit a SourceLineNode node
alias visit_source_line_node visit_child_nodes
# Visit a SplatNode node
alias visit_splat_node visit_child_nodes
# Visit a StatementsNode node
alias visit_statements_node visit_child_nodes
# Visit a StringNode node
alias visit_string_node visit_child_nodes
# Visit a SuperNode node
alias visit_super_node visit_child_nodes
# Visit a SymbolNode node
alias visit_symbol_node visit_child_nodes
# Visit a TrueNode node
alias visit_true_node visit_child_nodes
# Visit a UndefNode node
alias visit_undef_node visit_child_nodes
# Visit a UnlessNode node
alias visit_unless_node visit_child_nodes
# Visit a UntilNode node
alias visit_until_node visit_child_nodes
# Visit a WhenNode node
alias visit_when_node visit_child_nodes
# Visit a WhileNode node
alias visit_while_node visit_child_nodes
# Visit a XStringNode node
alias visit_x_string_node visit_child_nodes
# Visit a YieldNode node
alias visit_yield_node visit_child_nodes
end
end
share/ruby/prism/dsl.rb 0000644 00000077765 15173517735 0011134 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/dsl.rb.erb
if you are looking to modify the template
=end
module Prism
# The DSL module provides a set of methods that can be used to create prism
# nodes in a more concise manner. For example, instead of writing:
#
# source = Prism::Source.new("[1]")
#
# Prism::ArrayNode.new(
# [
# Prism::IntegerNode.new(
# Prism::IntegerBaseFlags::DECIMAL,
# Prism::Location.new(source, 1, 1),
# )
# ],
# Prism::Location.new(source, 0, 1),
# Prism::Location.new(source, 2, 1)
# )
#
# you could instead write:
#
# source = Prism::Source.new("[1]")
#
# ArrayNode(
# IntegerNode(Prism::IntegerBaseFlags::DECIMAL, Location(source, 1, 1))),
# Location(source, 0, 1),
# Location(source, 2, 1)
# )
#
# This is mostly helpful in the context of writing tests, but can also be used
# to generate trees programmatically.
module DSL
private
# Create a new Location object
def Location(source = nil, start_offset = 0, length = 0)
Location.new(source, start_offset, length)
end
# Create a new AliasGlobalVariableNode node
def AliasGlobalVariableNode(new_name, old_name, keyword_loc, location = Location())
AliasGlobalVariableNode.new(new_name, old_name, keyword_loc, location)
end
# Create a new AliasMethodNode node
def AliasMethodNode(new_name, old_name, keyword_loc, location = Location())
AliasMethodNode.new(new_name, old_name, keyword_loc, location)
end
# Create a new AlternationPatternNode node
def AlternationPatternNode(left, right, operator_loc, location = Location())
AlternationPatternNode.new(left, right, operator_loc, location)
end
# Create a new AndNode node
def AndNode(left, right, operator_loc, location = Location())
AndNode.new(left, right, operator_loc, location)
end
# Create a new ArgumentsNode node
def ArgumentsNode(flags, arguments, location = Location())
ArgumentsNode.new(flags, arguments, location)
end
# Create a new ArrayNode node
def ArrayNode(flags, elements, opening_loc, closing_loc, location = Location())
ArrayNode.new(flags, elements, opening_loc, closing_loc, location)
end
# Create a new ArrayPatternNode node
def ArrayPatternNode(constant, requireds, rest, posts, opening_loc, closing_loc, location = Location())
ArrayPatternNode.new(constant, requireds, rest, posts, opening_loc, closing_loc, location)
end
# Create a new AssocNode node
def AssocNode(key, value, operator_loc, location = Location())
AssocNode.new(key, value, operator_loc, location)
end
# Create a new AssocSplatNode node
def AssocSplatNode(value, operator_loc, location = Location())
AssocSplatNode.new(value, operator_loc, location)
end
# Create a new BackReferenceReadNode node
def BackReferenceReadNode(name, location = Location())
BackReferenceReadNode.new(name, location)
end
# Create a new BeginNode node
def BeginNode(begin_keyword_loc, statements, rescue_clause, else_clause, ensure_clause, end_keyword_loc, location = Location())
BeginNode.new(begin_keyword_loc, statements, rescue_clause, else_clause, ensure_clause, end_keyword_loc, location)
end
# Create a new BlockArgumentNode node
def BlockArgumentNode(expression, operator_loc, location = Location())
BlockArgumentNode.new(expression, operator_loc, location)
end
# Create a new BlockLocalVariableNode node
def BlockLocalVariableNode(name, location = Location())
BlockLocalVariableNode.new(name, location)
end
# Create a new BlockNode node
def BlockNode(locals, locals_body_index, parameters, body, opening_loc, closing_loc, location = Location())
BlockNode.new(locals, locals_body_index, parameters, body, opening_loc, closing_loc, location)
end
# Create a new BlockParameterNode node
def BlockParameterNode(name, name_loc, operator_loc, location = Location())
BlockParameterNode.new(name, name_loc, operator_loc, location)
end
# Create a new BlockParametersNode node
def BlockParametersNode(parameters, locals, opening_loc, closing_loc, location = Location())
BlockParametersNode.new(parameters, locals, opening_loc, closing_loc, location)
end
# Create a new BreakNode node
def BreakNode(arguments, keyword_loc, location = Location())
BreakNode.new(arguments, keyword_loc, location)
end
# Create a new CallAndWriteNode node
def CallAndWriteNode(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location = Location())
CallAndWriteNode.new(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location)
end
# Create a new CallNode node
def CallNode(flags, receiver, call_operator_loc, name, message_loc, opening_loc, arguments, closing_loc, block, location = Location())
CallNode.new(flags, receiver, call_operator_loc, name, message_loc, opening_loc, arguments, closing_loc, block, location)
end
# Create a new CallOperatorWriteNode node
def CallOperatorWriteNode(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator, operator_loc, value, location = Location())
CallOperatorWriteNode.new(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator, operator_loc, value, location)
end
# Create a new CallOrWriteNode node
def CallOrWriteNode(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location = Location())
CallOrWriteNode.new(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location)
end
# Create a new CallTargetNode node
def CallTargetNode(flags, receiver, call_operator_loc, name, message_loc, location = Location())
CallTargetNode.new(flags, receiver, call_operator_loc, name, message_loc, location)
end
# Create a new CapturePatternNode node
def CapturePatternNode(value, target, operator_loc, location = Location())
CapturePatternNode.new(value, target, operator_loc, location)
end
# Create a new CaseMatchNode node
def CaseMatchNode(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location = Location())
CaseMatchNode.new(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location)
end
# Create a new CaseNode node
def CaseNode(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location = Location())
CaseNode.new(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location)
end
# Create a new ClassNode node
def ClassNode(locals, class_keyword_loc, constant_path, inheritance_operator_loc, superclass, body, end_keyword_loc, name, location = Location())
ClassNode.new(locals, class_keyword_loc, constant_path, inheritance_operator_loc, superclass, body, end_keyword_loc, name, location)
end
# Create a new ClassVariableAndWriteNode node
def ClassVariableAndWriteNode(name, name_loc, operator_loc, value, location = Location())
ClassVariableAndWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new ClassVariableOperatorWriteNode node
def ClassVariableOperatorWriteNode(name, name_loc, operator_loc, value, operator, location = Location())
ClassVariableOperatorWriteNode.new(name, name_loc, operator_loc, value, operator, location)
end
# Create a new ClassVariableOrWriteNode node
def ClassVariableOrWriteNode(name, name_loc, operator_loc, value, location = Location())
ClassVariableOrWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new ClassVariableReadNode node
def ClassVariableReadNode(name, location = Location())
ClassVariableReadNode.new(name, location)
end
# Create a new ClassVariableTargetNode node
def ClassVariableTargetNode(name, location = Location())
ClassVariableTargetNode.new(name, location)
end
# Create a new ClassVariableWriteNode node
def ClassVariableWriteNode(name, name_loc, value, operator_loc, location = Location())
ClassVariableWriteNode.new(name, name_loc, value, operator_loc, location)
end
# Create a new ConstantAndWriteNode node
def ConstantAndWriteNode(name, name_loc, operator_loc, value, location = Location())
ConstantAndWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new ConstantOperatorWriteNode node
def ConstantOperatorWriteNode(name, name_loc, operator_loc, value, operator, location = Location())
ConstantOperatorWriteNode.new(name, name_loc, operator_loc, value, operator, location)
end
# Create a new ConstantOrWriteNode node
def ConstantOrWriteNode(name, name_loc, operator_loc, value, location = Location())
ConstantOrWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new ConstantPathAndWriteNode node
def ConstantPathAndWriteNode(target, operator_loc, value, location = Location())
ConstantPathAndWriteNode.new(target, operator_loc, value, location)
end
# Create a new ConstantPathNode node
def ConstantPathNode(parent, child, delimiter_loc, location = Location())
ConstantPathNode.new(parent, child, delimiter_loc, location)
end
# Create a new ConstantPathOperatorWriteNode node
def ConstantPathOperatorWriteNode(target, operator_loc, value, operator, location = Location())
ConstantPathOperatorWriteNode.new(target, operator_loc, value, operator, location)
end
# Create a new ConstantPathOrWriteNode node
def ConstantPathOrWriteNode(target, operator_loc, value, location = Location())
ConstantPathOrWriteNode.new(target, operator_loc, value, location)
end
# Create a new ConstantPathTargetNode node
def ConstantPathTargetNode(parent, child, delimiter_loc, location = Location())
ConstantPathTargetNode.new(parent, child, delimiter_loc, location)
end
# Create a new ConstantPathWriteNode node
def ConstantPathWriteNode(target, operator_loc, value, location = Location())
ConstantPathWriteNode.new(target, operator_loc, value, location)
end
# Create a new ConstantReadNode node
def ConstantReadNode(name, location = Location())
ConstantReadNode.new(name, location)
end
# Create a new ConstantTargetNode node
def ConstantTargetNode(name, location = Location())
ConstantTargetNode.new(name, location)
end
# Create a new ConstantWriteNode node
def ConstantWriteNode(name, name_loc, value, operator_loc, location = Location())
ConstantWriteNode.new(name, name_loc, value, operator_loc, location)
end
# Create a new DefNode node
def DefNode(name, name_loc, receiver, parameters, body, locals, locals_body_index, def_keyword_loc, operator_loc, lparen_loc, rparen_loc, equal_loc, end_keyword_loc, location = Location())
DefNode.new(name, name_loc, receiver, parameters, body, locals, locals_body_index, def_keyword_loc, operator_loc, lparen_loc, rparen_loc, equal_loc, end_keyword_loc, location)
end
# Create a new DefinedNode node
def DefinedNode(lparen_loc, value, rparen_loc, keyword_loc, location = Location())
DefinedNode.new(lparen_loc, value, rparen_loc, keyword_loc, location)
end
# Create a new ElseNode node
def ElseNode(else_keyword_loc, statements, end_keyword_loc, location = Location())
ElseNode.new(else_keyword_loc, statements, end_keyword_loc, location)
end
# Create a new EmbeddedStatementsNode node
def EmbeddedStatementsNode(opening_loc, statements, closing_loc, location = Location())
EmbeddedStatementsNode.new(opening_loc, statements, closing_loc, location)
end
# Create a new EmbeddedVariableNode node
def EmbeddedVariableNode(operator_loc, variable, location = Location())
EmbeddedVariableNode.new(operator_loc, variable, location)
end
# Create a new EnsureNode node
def EnsureNode(ensure_keyword_loc, statements, end_keyword_loc, location = Location())
EnsureNode.new(ensure_keyword_loc, statements, end_keyword_loc, location)
end
# Create a new FalseNode node
def FalseNode(location = Location())
FalseNode.new(location)
end
# Create a new FindPatternNode node
def FindPatternNode(constant, left, requireds, right, opening_loc, closing_loc, location = Location())
FindPatternNode.new(constant, left, requireds, right, opening_loc, closing_loc, location)
end
# Create a new FlipFlopNode node
def FlipFlopNode(flags, left, right, operator_loc, location = Location())
FlipFlopNode.new(flags, left, right, operator_loc, location)
end
# Create a new FloatNode node
def FloatNode(location = Location())
FloatNode.new(location)
end
# Create a new ForNode node
def ForNode(index, collection, statements, for_keyword_loc, in_keyword_loc, do_keyword_loc, end_keyword_loc, location = Location())
ForNode.new(index, collection, statements, for_keyword_loc, in_keyword_loc, do_keyword_loc, end_keyword_loc, location)
end
# Create a new ForwardingArgumentsNode node
def ForwardingArgumentsNode(location = Location())
ForwardingArgumentsNode.new(location)
end
# Create a new ForwardingParameterNode node
def ForwardingParameterNode(location = Location())
ForwardingParameterNode.new(location)
end
# Create a new ForwardingSuperNode node
def ForwardingSuperNode(block, location = Location())
ForwardingSuperNode.new(block, location)
end
# Create a new GlobalVariableAndWriteNode node
def GlobalVariableAndWriteNode(name, name_loc, operator_loc, value, location = Location())
GlobalVariableAndWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new GlobalVariableOperatorWriteNode node
def GlobalVariableOperatorWriteNode(name, name_loc, operator_loc, value, operator, location = Location())
GlobalVariableOperatorWriteNode.new(name, name_loc, operator_loc, value, operator, location)
end
# Create a new GlobalVariableOrWriteNode node
def GlobalVariableOrWriteNode(name, name_loc, operator_loc, value, location = Location())
GlobalVariableOrWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new GlobalVariableReadNode node
def GlobalVariableReadNode(name, location = Location())
GlobalVariableReadNode.new(name, location)
end
# Create a new GlobalVariableTargetNode node
def GlobalVariableTargetNode(name, location = Location())
GlobalVariableTargetNode.new(name, location)
end
# Create a new GlobalVariableWriteNode node
def GlobalVariableWriteNode(name, name_loc, value, operator_loc, location = Location())
GlobalVariableWriteNode.new(name, name_loc, value, operator_loc, location)
end
# Create a new HashNode node
def HashNode(opening_loc, elements, closing_loc, location = Location())
HashNode.new(opening_loc, elements, closing_loc, location)
end
# Create a new HashPatternNode node
def HashPatternNode(constant, elements, rest, opening_loc, closing_loc, location = Location())
HashPatternNode.new(constant, elements, rest, opening_loc, closing_loc, location)
end
# Create a new IfNode node
def IfNode(if_keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location = Location())
IfNode.new(if_keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location)
end
# Create a new ImaginaryNode node
def ImaginaryNode(numeric, location = Location())
ImaginaryNode.new(numeric, location)
end
# Create a new ImplicitNode node
def ImplicitNode(value, location = Location())
ImplicitNode.new(value, location)
end
# Create a new ImplicitRestNode node
def ImplicitRestNode(location = Location())
ImplicitRestNode.new(location)
end
# Create a new InNode node
def InNode(pattern, statements, in_loc, then_loc, location = Location())
InNode.new(pattern, statements, in_loc, then_loc, location)
end
# Create a new IndexAndWriteNode node
def IndexAndWriteNode(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location = Location())
IndexAndWriteNode.new(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location)
end
# Create a new IndexOperatorWriteNode node
def IndexOperatorWriteNode(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator, operator_loc, value, location = Location())
IndexOperatorWriteNode.new(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator, operator_loc, value, location)
end
# Create a new IndexOrWriteNode node
def IndexOrWriteNode(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location = Location())
IndexOrWriteNode.new(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location)
end
# Create a new IndexTargetNode node
def IndexTargetNode(flags, receiver, opening_loc, arguments, closing_loc, block, location = Location())
IndexTargetNode.new(flags, receiver, opening_loc, arguments, closing_loc, block, location)
end
# Create a new InstanceVariableAndWriteNode node
def InstanceVariableAndWriteNode(name, name_loc, operator_loc, value, location = Location())
InstanceVariableAndWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new InstanceVariableOperatorWriteNode node
def InstanceVariableOperatorWriteNode(name, name_loc, operator_loc, value, operator, location = Location())
InstanceVariableOperatorWriteNode.new(name, name_loc, operator_loc, value, operator, location)
end
# Create a new InstanceVariableOrWriteNode node
def InstanceVariableOrWriteNode(name, name_loc, operator_loc, value, location = Location())
InstanceVariableOrWriteNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new InstanceVariableReadNode node
def InstanceVariableReadNode(name, location = Location())
InstanceVariableReadNode.new(name, location)
end
# Create a new InstanceVariableTargetNode node
def InstanceVariableTargetNode(name, location = Location())
InstanceVariableTargetNode.new(name, location)
end
# Create a new InstanceVariableWriteNode node
def InstanceVariableWriteNode(name, name_loc, value, operator_loc, location = Location())
InstanceVariableWriteNode.new(name, name_loc, value, operator_loc, location)
end
# Create a new IntegerNode node
def IntegerNode(flags, location = Location())
IntegerNode.new(flags, location)
end
# Create a new InterpolatedMatchLastLineNode node
def InterpolatedMatchLastLineNode(flags, opening_loc, parts, closing_loc, location = Location())
InterpolatedMatchLastLineNode.new(flags, opening_loc, parts, closing_loc, location)
end
# Create a new InterpolatedRegularExpressionNode node
def InterpolatedRegularExpressionNode(flags, opening_loc, parts, closing_loc, location = Location())
InterpolatedRegularExpressionNode.new(flags, opening_loc, parts, closing_loc, location)
end
# Create a new InterpolatedStringNode node
def InterpolatedStringNode(opening_loc, parts, closing_loc, location = Location())
InterpolatedStringNode.new(opening_loc, parts, closing_loc, location)
end
# Create a new InterpolatedSymbolNode node
def InterpolatedSymbolNode(opening_loc, parts, closing_loc, location = Location())
InterpolatedSymbolNode.new(opening_loc, parts, closing_loc, location)
end
# Create a new InterpolatedXStringNode node
def InterpolatedXStringNode(opening_loc, parts, closing_loc, location = Location())
InterpolatedXStringNode.new(opening_loc, parts, closing_loc, location)
end
# Create a new KeywordHashNode node
def KeywordHashNode(flags, elements, location = Location())
KeywordHashNode.new(flags, elements, location)
end
# Create a new KeywordRestParameterNode node
def KeywordRestParameterNode(name, name_loc, operator_loc, location = Location())
KeywordRestParameterNode.new(name, name_loc, operator_loc, location)
end
# Create a new LambdaNode node
def LambdaNode(locals, locals_body_index, operator_loc, opening_loc, closing_loc, parameters, body, location = Location())
LambdaNode.new(locals, locals_body_index, operator_loc, opening_loc, closing_loc, parameters, body, location)
end
# Create a new LocalVariableAndWriteNode node
def LocalVariableAndWriteNode(name_loc, operator_loc, value, name, depth, location = Location())
LocalVariableAndWriteNode.new(name_loc, operator_loc, value, name, depth, location)
end
# Create a new LocalVariableOperatorWriteNode node
def LocalVariableOperatorWriteNode(name_loc, operator_loc, value, name, operator, depth, location = Location())
LocalVariableOperatorWriteNode.new(name_loc, operator_loc, value, name, operator, depth, location)
end
# Create a new LocalVariableOrWriteNode node
def LocalVariableOrWriteNode(name_loc, operator_loc, value, name, depth, location = Location())
LocalVariableOrWriteNode.new(name_loc, operator_loc, value, name, depth, location)
end
# Create a new LocalVariableReadNode node
def LocalVariableReadNode(name, depth, location = Location())
LocalVariableReadNode.new(name, depth, location)
end
# Create a new LocalVariableTargetNode node
def LocalVariableTargetNode(name, depth, location = Location())
LocalVariableTargetNode.new(name, depth, location)
end
# Create a new LocalVariableWriteNode node
def LocalVariableWriteNode(name, depth, name_loc, value, operator_loc, location = Location())
LocalVariableWriteNode.new(name, depth, name_loc, value, operator_loc, location)
end
# Create a new MatchLastLineNode node
def MatchLastLineNode(flags, opening_loc, content_loc, closing_loc, unescaped, location = Location())
MatchLastLineNode.new(flags, opening_loc, content_loc, closing_loc, unescaped, location)
end
# Create a new MatchPredicateNode node
def MatchPredicateNode(value, pattern, operator_loc, location = Location())
MatchPredicateNode.new(value, pattern, operator_loc, location)
end
# Create a new MatchRequiredNode node
def MatchRequiredNode(value, pattern, operator_loc, location = Location())
MatchRequiredNode.new(value, pattern, operator_loc, location)
end
# Create a new MatchWriteNode node
def MatchWriteNode(call, targets, location = Location())
MatchWriteNode.new(call, targets, location)
end
# Create a new MissingNode node
def MissingNode(location = Location())
MissingNode.new(location)
end
# Create a new ModuleNode node
def ModuleNode(locals, module_keyword_loc, constant_path, body, end_keyword_loc, name, location = Location())
ModuleNode.new(locals, module_keyword_loc, constant_path, body, end_keyword_loc, name, location)
end
# Create a new MultiTargetNode node
def MultiTargetNode(lefts, rest, rights, lparen_loc, rparen_loc, location = Location())
MultiTargetNode.new(lefts, rest, rights, lparen_loc, rparen_loc, location)
end
# Create a new MultiWriteNode node
def MultiWriteNode(lefts, rest, rights, lparen_loc, rparen_loc, operator_loc, value, location = Location())
MultiWriteNode.new(lefts, rest, rights, lparen_loc, rparen_loc, operator_loc, value, location)
end
# Create a new NextNode node
def NextNode(arguments, keyword_loc, location = Location())
NextNode.new(arguments, keyword_loc, location)
end
# Create a new NilNode node
def NilNode(location = Location())
NilNode.new(location)
end
# Create a new NoKeywordsParameterNode node
def NoKeywordsParameterNode(operator_loc, keyword_loc, location = Location())
NoKeywordsParameterNode.new(operator_loc, keyword_loc, location)
end
# Create a new NumberedParametersNode node
def NumberedParametersNode(maximum, location = Location())
NumberedParametersNode.new(maximum, location)
end
# Create a new NumberedReferenceReadNode node
def NumberedReferenceReadNode(number, location = Location())
NumberedReferenceReadNode.new(number, location)
end
# Create a new OptionalKeywordParameterNode node
def OptionalKeywordParameterNode(name, name_loc, value, location = Location())
OptionalKeywordParameterNode.new(name, name_loc, value, location)
end
# Create a new OptionalParameterNode node
def OptionalParameterNode(name, name_loc, operator_loc, value, location = Location())
OptionalParameterNode.new(name, name_loc, operator_loc, value, location)
end
# Create a new OrNode node
def OrNode(left, right, operator_loc, location = Location())
OrNode.new(left, right, operator_loc, location)
end
# Create a new ParametersNode node
def ParametersNode(requireds, optionals, rest, posts, keywords, keyword_rest, block, location = Location())
ParametersNode.new(requireds, optionals, rest, posts, keywords, keyword_rest, block, location)
end
# Create a new ParenthesesNode node
def ParenthesesNode(body, opening_loc, closing_loc, location = Location())
ParenthesesNode.new(body, opening_loc, closing_loc, location)
end
# Create a new PinnedExpressionNode node
def PinnedExpressionNode(expression, operator_loc, lparen_loc, rparen_loc, location = Location())
PinnedExpressionNode.new(expression, operator_loc, lparen_loc, rparen_loc, location)
end
# Create a new PinnedVariableNode node
def PinnedVariableNode(variable, operator_loc, location = Location())
PinnedVariableNode.new(variable, operator_loc, location)
end
# Create a new PostExecutionNode node
def PostExecutionNode(statements, keyword_loc, opening_loc, closing_loc, location = Location())
PostExecutionNode.new(statements, keyword_loc, opening_loc, closing_loc, location)
end
# Create a new PreExecutionNode node
def PreExecutionNode(statements, keyword_loc, opening_loc, closing_loc, location = Location())
PreExecutionNode.new(statements, keyword_loc, opening_loc, closing_loc, location)
end
# Create a new ProgramNode node
def ProgramNode(locals, statements, location = Location())
ProgramNode.new(locals, statements, location)
end
# Create a new RangeNode node
def RangeNode(flags, left, right, operator_loc, location = Location())
RangeNode.new(flags, left, right, operator_loc, location)
end
# Create a new RationalNode node
def RationalNode(numeric, location = Location())
RationalNode.new(numeric, location)
end
# Create a new RedoNode node
def RedoNode(location = Location())
RedoNode.new(location)
end
# Create a new RegularExpressionNode node
def RegularExpressionNode(flags, opening_loc, content_loc, closing_loc, unescaped, location = Location())
RegularExpressionNode.new(flags, opening_loc, content_loc, closing_loc, unescaped, location)
end
# Create a new RequiredKeywordParameterNode node
def RequiredKeywordParameterNode(name, name_loc, location = Location())
RequiredKeywordParameterNode.new(name, name_loc, location)
end
# Create a new RequiredParameterNode node
def RequiredParameterNode(name, location = Location())
RequiredParameterNode.new(name, location)
end
# Create a new RescueModifierNode node
def RescueModifierNode(expression, keyword_loc, rescue_expression, location = Location())
RescueModifierNode.new(expression, keyword_loc, rescue_expression, location)
end
# Create a new RescueNode node
def RescueNode(keyword_loc, exceptions, operator_loc, reference, statements, consequent, location = Location())
RescueNode.new(keyword_loc, exceptions, operator_loc, reference, statements, consequent, location)
end
# Create a new RestParameterNode node
def RestParameterNode(name, name_loc, operator_loc, location = Location())
RestParameterNode.new(name, name_loc, operator_loc, location)
end
# Create a new RetryNode node
def RetryNode(location = Location())
RetryNode.new(location)
end
# Create a new ReturnNode node
def ReturnNode(keyword_loc, arguments, location = Location())
ReturnNode.new(keyword_loc, arguments, location)
end
# Create a new SelfNode node
def SelfNode(location = Location())
SelfNode.new(location)
end
# Create a new SingletonClassNode node
def SingletonClassNode(locals, class_keyword_loc, operator_loc, expression, body, end_keyword_loc, location = Location())
SingletonClassNode.new(locals, class_keyword_loc, operator_loc, expression, body, end_keyword_loc, location)
end
# Create a new SourceEncodingNode node
def SourceEncodingNode(location = Location())
SourceEncodingNode.new(location)
end
# Create a new SourceFileNode node
def SourceFileNode(filepath, location = Location())
SourceFileNode.new(filepath, location)
end
# Create a new SourceLineNode node
def SourceLineNode(location = Location())
SourceLineNode.new(location)
end
# Create a new SplatNode node
def SplatNode(operator_loc, expression, location = Location())
SplatNode.new(operator_loc, expression, location)
end
# Create a new StatementsNode node
def StatementsNode(body, location = Location())
StatementsNode.new(body, location)
end
# Create a new StringNode node
def StringNode(flags, opening_loc, content_loc, closing_loc, unescaped, location = Location())
StringNode.new(flags, opening_loc, content_loc, closing_loc, unescaped, location)
end
# Create a new SuperNode node
def SuperNode(keyword_loc, lparen_loc, arguments, rparen_loc, block, location = Location())
SuperNode.new(keyword_loc, lparen_loc, arguments, rparen_loc, block, location)
end
# Create a new SymbolNode node
def SymbolNode(flags, opening_loc, value_loc, closing_loc, unescaped, location = Location())
SymbolNode.new(flags, opening_loc, value_loc, closing_loc, unescaped, location)
end
# Create a new TrueNode node
def TrueNode(location = Location())
TrueNode.new(location)
end
# Create a new UndefNode node
def UndefNode(names, keyword_loc, location = Location())
UndefNode.new(names, keyword_loc, location)
end
# Create a new UnlessNode node
def UnlessNode(keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location = Location())
UnlessNode.new(keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location)
end
# Create a new UntilNode node
def UntilNode(flags, keyword_loc, closing_loc, predicate, statements, location = Location())
UntilNode.new(flags, keyword_loc, closing_loc, predicate, statements, location)
end
# Create a new WhenNode node
def WhenNode(keyword_loc, conditions, statements, location = Location())
WhenNode.new(keyword_loc, conditions, statements, location)
end
# Create a new WhileNode node
def WhileNode(flags, keyword_loc, closing_loc, predicate, statements, location = Location())
WhileNode.new(flags, keyword_loc, closing_loc, predicate, statements, location)
end
# Create a new XStringNode node
def XStringNode(flags, opening_loc, content_loc, closing_loc, unescaped, location = Location())
XStringNode.new(flags, opening_loc, content_loc, closing_loc, unescaped, location)
end
# Create a new YieldNode node
def YieldNode(keyword_loc, lparen_loc, arguments, rparen_loc, location = Location())
YieldNode.new(keyword_loc, lparen_loc, arguments, rparen_loc, location)
end
end
end
share/ruby/prism/ripper_compat.rb 0000644 00000013713 15173517735 0013175 0 ustar 00 # frozen_string_literal: true
require "ripper"
module Prism
# Note: This integration is not finished, and therefore still has many
# inconsistencies with Ripper. If you'd like to help out, pull requests would
# be greatly appreciated!
#
# This class is meant to provide a compatibility layer between prism and
# Ripper. It functions by parsing the entire tree first and then walking it
# and executing each of the Ripper callbacks as it goes.
#
# This class is going to necessarily be slower than the native Ripper API. It
# is meant as a stopgap until developers migrate to using prism. It is also
# meant as a test harness for the prism parser.
#
# To use this class, you treat `Prism::RipperCompat` effectively as you would
# treat the `Ripper` class.
class RipperCompat < Visitor
# This class mirrors the ::Ripper::SexpBuilder subclass of ::Ripper that
# returns the arrays of [type, *children].
class SexpBuilder < RipperCompat
private
Ripper::PARSER_EVENTS.each do |event|
define_method(:"on_#{event}") do |*args|
[event, *args]
end
end
Ripper::SCANNER_EVENTS.each do |event|
define_method(:"on_#{event}") do |value|
[:"@#{event}", value, [lineno, column]]
end
end
end
# This class mirrors the ::Ripper::SexpBuilderPP subclass of ::Ripper that
# returns the same values as ::Ripper::SexpBuilder except with a couple of
# niceties that flatten linked lists into arrays.
class SexpBuilderPP < SexpBuilder
private
def _dispatch_event_new # :nodoc:
[]
end
def _dispatch_event_push(list, item) # :nodoc:
list << item
list
end
Ripper::PARSER_EVENT_TABLE.each do |event, arity|
case event
when /_new\z/
alias_method :"on_#{event}", :_dispatch_event_new if arity == 0
when /_add\z/
alias_method :"on_#{event}", :_dispatch_event_push
end
end
end
# The source that is being parsed.
attr_reader :source
# The current line number of the parser.
attr_reader :lineno
# The current column number of the parser.
attr_reader :column
# Create a new RipperCompat object with the given source.
def initialize(source)
@source = source
@result = nil
@lineno = nil
@column = nil
end
############################################################################
# Public interface
############################################################################
# True if the parser encountered an error during parsing.
def error?
result.failure?
end
# Parse the source and return the result.
def parse
result.magic_comments.each do |magic_comment|
on_magic_comment(magic_comment.key, magic_comment.value)
end
if error?
result.errors.each do |error|
on_parse_error(error.message)
end
else
result.value.accept(self)
end
end
############################################################################
# Visitor methods
############################################################################
# Visit a CallNode node.
def visit_call_node(node)
if !node.message.match?(/^[[:alpha:]_]/) && node.opening_loc.nil? && node.arguments&.arguments&.length == 1
left = visit(node.receiver)
right = visit(node.arguments.arguments.first)
bounds(node.location)
on_binary(left, node.name, right)
else
raise NotImplementedError
end
end
# Visit a FloatNode node.
def visit_float_node(node)
bounds(node.location)
on_float(node.slice)
end
# Visit a ImaginaryNode node.
def visit_imaginary_node(node)
bounds(node.location)
on_imaginary(node.slice)
end
# Visit an IntegerNode node.
def visit_integer_node(node)
bounds(node.location)
on_int(node.slice)
end
# Visit a RationalNode node.
def visit_rational_node(node)
bounds(node.location)
on_rational(node.slice)
end
# Visit a StatementsNode node.
def visit_statements_node(node)
bounds(node.location)
node.body.inject(on_stmts_new) do |stmts, stmt|
on_stmts_add(stmts, visit(stmt))
end
end
# Visit a ProgramNode node.
def visit_program_node(node)
statements = visit(node.statements)
bounds(node.location)
on_program(statements)
end
############################################################################
# Entrypoints for subclasses
############################################################################
# This is a convenience method that runs the SexpBuilder subclass parser.
def self.sexp_raw(source)
SexpBuilder.new(source).parse
end
# This is a convenience method that runs the SexpBuilderPP subclass parser.
def self.sexp(source)
SexpBuilderPP.new(source).parse
end
private
# This method is responsible for updating lineno and column information
# to reflect the current node.
#
# This method could be drastically improved with some caching on the start
# of every line, but for now it's good enough.
def bounds(location)
@lineno = location.start_line
@column = location.start_column
end
# Lazily initialize the parse result.
def result
@result ||= Prism.parse(source)
end
def _dispatch0; end # :nodoc:
def _dispatch1(_); end # :nodoc:
def _dispatch2(_, _); end # :nodoc:
def _dispatch3(_, _, _); end # :nodoc:
def _dispatch4(_, _, _, _); end # :nodoc:
def _dispatch5(_, _, _, _, _); end # :nodoc:
def _dispatch7(_, _, _, _, _, _, _); end # :nodoc:
alias_method :on_parse_error, :_dispatch1
alias_method :on_magic_comment, :_dispatch2
(Ripper::SCANNER_EVENT_TABLE.merge(Ripper::PARSER_EVENT_TABLE)).each do |event, arity|
alias_method :"on_#{event}", :"_dispatch#{arity}"
end
end
end
share/ruby/prism/ffi.rb 0000644 00000024420 15173517735 0011072 0 ustar 00 # frozen_string_literal: true
# This file is responsible for mirroring the API provided by the C extension by
# using FFI to call into the shared library.
require "rbconfig"
require "ffi"
module Prism
BACKEND = :FFI
module LibRubyParser # :nodoc:
extend FFI::Library
# Define the library that we will be pulling functions from. Note that this
# must align with the build shared library from make/rake.
ffi_lib File.expand_path("../../build/libprism.#{RbConfig::CONFIG["SOEXT"]}", __dir__)
# Convert a native C type declaration into a symbol that FFI understands.
# For example:
#
# const char * -> :pointer
# bool -> :bool
# size_t -> :size_t
# void -> :void
#
def self.resolve_type(type)
type = type.strip
type.end_with?("*") ? :pointer : type.delete_prefix("const ").to_sym
end
# Read through the given header file and find the declaration of each of the
# given functions. For each one, define a function with the same name and
# signature as the C function.
def self.load_exported_functions_from(header, *functions)
File.foreach(File.expand_path("../../include/#{header}", __dir__)) do |line|
# We only want to attempt to load exported functions.
next unless line.start_with?("PRISM_EXPORTED_FUNCTION ")
# We only want to load the functions that we are interested in.
next unless functions.any? { |function| line.include?(function) }
# Parse the function declaration.
unless /^PRISM_EXPORTED_FUNCTION (?<return_type>.+) (?<name>\w+)\((?<arg_types>.+)\);$/ =~ line
raise "Could not parse #{line}"
end
# Delete the function from the list of functions we are looking for to
# mark it as having been found.
functions.delete(name)
# Split up the argument types into an array, ensure we handle the case
# where there are no arguments (by explicit void).
arg_types = arg_types.split(",").map(&:strip)
arg_types = [] if arg_types == %w[void]
# Resolve the type of the argument by dropping the name of the argument
# first if it is present.
arg_types.map! { |type| resolve_type(type.sub(/\w+$/, "")) }
# Attach the function using the FFI library.
attach_function name, arg_types, resolve_type(return_type)
end
# If we didn't find all of the functions, raise an error.
raise "Could not find functions #{functions.inspect}" unless functions.empty?
end
load_exported_functions_from(
"prism.h",
"pm_version",
"pm_serialize_parse",
"pm_serialize_parse_comments",
"pm_serialize_lex",
"pm_serialize_parse_lex",
"pm_parse_success_p"
)
load_exported_functions_from(
"prism/util/pm_buffer.h",
"pm_buffer_sizeof",
"pm_buffer_init",
"pm_buffer_value",
"pm_buffer_length",
"pm_buffer_free"
)
load_exported_functions_from(
"prism/util/pm_string.h",
"pm_string_mapped_init",
"pm_string_free",
"pm_string_source",
"pm_string_length",
"pm_string_sizeof"
)
# This object represents a pm_buffer_t. We only use it as an opaque pointer,
# so it doesn't need to know the fields of pm_buffer_t.
class PrismBuffer # :nodoc:
SIZEOF = LibRubyParser.pm_buffer_sizeof
attr_reader :pointer
def initialize(pointer)
@pointer = pointer
end
def value
LibRubyParser.pm_buffer_value(pointer)
end
def length
LibRubyParser.pm_buffer_length(pointer)
end
def read
value.read_string(length)
end
# Initialize a new buffer and yield it to the block. The buffer will be
# automatically freed when the block returns.
def self.with(&block)
pointer = FFI::MemoryPointer.new(SIZEOF)
begin
raise unless LibRubyParser.pm_buffer_init(pointer)
yield new(pointer)
ensure
LibRubyParser.pm_buffer_free(pointer)
pointer.free
end
end
end
# This object represents a pm_string_t. We only use it as an opaque pointer,
# so it doesn't have to be an FFI::Struct.
class PrismString # :nodoc:
SIZEOF = LibRubyParser.pm_string_sizeof
attr_reader :pointer
def initialize(pointer)
@pointer = pointer
end
def source
LibRubyParser.pm_string_source(pointer)
end
def length
LibRubyParser.pm_string_length(pointer)
end
def read
source.read_string(length)
end
# Yields a pm_string_t pointer to the given block.
def self.with(filepath, &block)
pointer = FFI::MemoryPointer.new(SIZEOF)
begin
raise unless LibRubyParser.pm_string_mapped_init(pointer, filepath)
yield new(pointer)
ensure
LibRubyParser.pm_string_free(pointer)
pointer.free
end
end
end
end
# Mark the LibRubyParser module as private as it should only be called through
# the prism module.
private_constant :LibRubyParser
# The version constant is set by reading the result of calling pm_version.
VERSION = LibRubyParser.pm_version.read_string
class << self
# Mirror the Prism.dump API by using the serialization API.
def dump(code, **options)
LibRubyParser::PrismBuffer.with do |buffer|
LibRubyParser.pm_serialize_parse(buffer.pointer, code, code.bytesize, dump_options(options))
buffer.read
end
end
# Mirror the Prism.dump_file API by using the serialization API.
def dump_file(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
dump(string.read, **options, filepath: filepath)
end
end
# Mirror the Prism.lex API by using the serialization API.
def lex(code, **options)
LibRubyParser::PrismBuffer.with do |buffer|
LibRubyParser.pm_serialize_lex(buffer.pointer, code, code.bytesize, dump_options(options))
Serialize.load_tokens(Source.new(code), buffer.read)
end
end
# Mirror the Prism.lex_file API by using the serialization API.
def lex_file(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
lex(string.read, **options, filepath: filepath)
end
end
# Mirror the Prism.parse API by using the serialization API.
def parse(code, **options)
Prism.load(code, dump(code, **options))
end
# Mirror the Prism.parse_file API by using the serialization API. This uses
# native strings instead of Ruby strings because it allows us to use mmap when
# it is available.
def parse_file(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
parse(string.read, **options, filepath: filepath)
end
end
# Mirror the Prism.parse_comments API by using the serialization API.
def parse_comments(code, **options)
LibRubyParser::PrismBuffer.with do |buffer|
LibRubyParser.pm_serialize_parse_comments(buffer.pointer, code, code.bytesize, dump_options(options))
source = Source.new(code)
loader = Serialize::Loader.new(source, buffer.read)
loader.load_header
loader.load_encoding
loader.load_start_line
loader.load_comments
end
end
# Mirror the Prism.parse_file_comments API by using the serialization
# API. This uses native strings instead of Ruby strings because it allows us
# to use mmap when it is available.
def parse_file_comments(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
parse_comments(string.read, **options, filepath: filepath)
end
end
# Mirror the Prism.parse_lex API by using the serialization API.
def parse_lex(code, **options)
LibRubyParser::PrismBuffer.with do |buffer|
LibRubyParser.pm_serialize_parse_lex(buffer.pointer, code, code.bytesize, dump_options(options))
source = Source.new(code)
loader = Serialize::Loader.new(source, buffer.read)
tokens = loader.load_tokens
node, comments, magic_comments, data_loc, errors, warnings = loader.load_nodes
tokens.each { |token,| token.value.force_encoding(loader.encoding) }
ParseResult.new([node, tokens], comments, magic_comments, data_loc, errors, warnings, source)
end
end
# Mirror the Prism.parse_lex_file API by using the serialization API.
def parse_lex_file(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
parse_lex(string.read, **options, filepath: filepath)
end
end
# Mirror the Prism.parse_success? API by using the serialization API.
def parse_success?(code, **options)
LibRubyParser.pm_parse_success_p(code, code.bytesize, dump_options(options))
end
# Mirror the Prism.parse_file_success? API by using the serialization API.
def parse_file_success?(filepath, **options)
LibRubyParser::PrismString.with(filepath) do |string|
parse_success?(string.read, **options, filepath: filepath)
end
end
private
# Convert the given options into a serialized options string.
def dump_options(options)
template = +""
values = []
template << "L"
if (filepath = options[:filepath])
values.push(filepath.bytesize, filepath.b)
template << "A*"
else
values << 0
end
template << "L"
values << options.fetch(:line, 1)
template << "L"
if (encoding = options[:encoding])
name = encoding.name
values.push(name.bytesize, name.b)
template << "A*"
else
values << 0
end
template << "C"
values << (options.fetch(:frozen_string_literal, false) ? 1 : 0)
template << "C"
values << (options.fetch(:verbose, true) ? 0 : 1)
template << "L"
if (scopes = options[:scopes])
values << scopes.length
scopes.each do |scope|
template << "L"
values << scope.length
scope.each do |local|
name = local.name
template << "L"
values << name.bytesize
template << "A*"
values << name.b
end
end
else
values << 0
end
values.pack(template)
end
end
end
share/ruby/prism/dispatcher.rb 0000644 00000331631 15173517735 0012461 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/dispatcher.rb.erb
if you are looking to modify the template
=end
module Prism
# The dispatcher class fires events for nodes that are found while walking an
# AST to all registered listeners. It's useful for performing different types
# of analysis on the AST while only having to walk the tree once.
#
# To use the dispatcher, you would first instantiate it and register listeners
# for the events you're interested in:
#
# class OctalListener
# def on_integer_node_enter(node)
# if node.octal? && !node.slice.start_with?("0o")
# warn("Octal integers should be written with the 0o prefix")
# end
# end
# end
#
# dispatcher = Dispatcher.new
# dispatcher.register(listener, :on_integer_node_enter)
#
# Then, you can walk any number of trees and dispatch events to the listeners:
#
# result = Prism.parse("001 + 002 + 003")
# dispatcher.dispatch(result.value)
#
# Optionally, you can also use `#dispatch_once` to dispatch enter and leave
# events for a single node without recursing further down the tree. This can
# be useful in circumstances where you want to reuse the listeners you already
# have registers but want to stop walking the tree at a certain point.
#
# integer = result.value.statements.body.first.receiver.receiver
# dispatcher.dispatch_once(integer)
#
class Dispatcher < Visitor
# attr_reader listeners: Hash[Symbol, Array[Listener]]
attr_reader :listeners
# Initialize a new dispatcher.
def initialize
@listeners = {}
end
# Register a listener for one or more events.
#
# def register: (Listener, *Symbol) -> void
def register(listener, *events)
events.each { |event| (listeners[event] ||= []) << listener }
end
# Walks `root` dispatching events to all registered listeners.
#
# def dispatch: (Node) -> void
alias dispatch visit
# Dispatches a single event for `node` to all registered listeners.
#
# def dispatch_once: (Node) -> void
def dispatch_once(node)
node.accept(DispatchOnce.new(listeners))
end
# Dispatch enter and leave events for AliasGlobalVariableNode nodes and continue
# walking the tree.
def visit_alias_global_variable_node(node)
listeners[:on_alias_global_variable_node_enter]&.each { |listener| listener.on_alias_global_variable_node_enter(node) }
super
listeners[:on_alias_global_variable_node_leave]&.each { |listener| listener.on_alias_global_variable_node_leave(node) }
end
# Dispatch enter and leave events for AliasMethodNode nodes and continue
# walking the tree.
def visit_alias_method_node(node)
listeners[:on_alias_method_node_enter]&.each { |listener| listener.on_alias_method_node_enter(node) }
super
listeners[:on_alias_method_node_leave]&.each { |listener| listener.on_alias_method_node_leave(node) }
end
# Dispatch enter and leave events for AlternationPatternNode nodes and continue
# walking the tree.
def visit_alternation_pattern_node(node)
listeners[:on_alternation_pattern_node_enter]&.each { |listener| listener.on_alternation_pattern_node_enter(node) }
super
listeners[:on_alternation_pattern_node_leave]&.each { |listener| listener.on_alternation_pattern_node_leave(node) }
end
# Dispatch enter and leave events for AndNode nodes and continue
# walking the tree.
def visit_and_node(node)
listeners[:on_and_node_enter]&.each { |listener| listener.on_and_node_enter(node) }
super
listeners[:on_and_node_leave]&.each { |listener| listener.on_and_node_leave(node) }
end
# Dispatch enter and leave events for ArgumentsNode nodes and continue
# walking the tree.
def visit_arguments_node(node)
listeners[:on_arguments_node_enter]&.each { |listener| listener.on_arguments_node_enter(node) }
super
listeners[:on_arguments_node_leave]&.each { |listener| listener.on_arguments_node_leave(node) }
end
# Dispatch enter and leave events for ArrayNode nodes and continue
# walking the tree.
def visit_array_node(node)
listeners[:on_array_node_enter]&.each { |listener| listener.on_array_node_enter(node) }
super
listeners[:on_array_node_leave]&.each { |listener| listener.on_array_node_leave(node) }
end
# Dispatch enter and leave events for ArrayPatternNode nodes and continue
# walking the tree.
def visit_array_pattern_node(node)
listeners[:on_array_pattern_node_enter]&.each { |listener| listener.on_array_pattern_node_enter(node) }
super
listeners[:on_array_pattern_node_leave]&.each { |listener| listener.on_array_pattern_node_leave(node) }
end
# Dispatch enter and leave events for AssocNode nodes and continue
# walking the tree.
def visit_assoc_node(node)
listeners[:on_assoc_node_enter]&.each { |listener| listener.on_assoc_node_enter(node) }
super
listeners[:on_assoc_node_leave]&.each { |listener| listener.on_assoc_node_leave(node) }
end
# Dispatch enter and leave events for AssocSplatNode nodes and continue
# walking the tree.
def visit_assoc_splat_node(node)
listeners[:on_assoc_splat_node_enter]&.each { |listener| listener.on_assoc_splat_node_enter(node) }
super
listeners[:on_assoc_splat_node_leave]&.each { |listener| listener.on_assoc_splat_node_leave(node) }
end
# Dispatch enter and leave events for BackReferenceReadNode nodes and continue
# walking the tree.
def visit_back_reference_read_node(node)
listeners[:on_back_reference_read_node_enter]&.each { |listener| listener.on_back_reference_read_node_enter(node) }
super
listeners[:on_back_reference_read_node_leave]&.each { |listener| listener.on_back_reference_read_node_leave(node) }
end
# Dispatch enter and leave events for BeginNode nodes and continue
# walking the tree.
def visit_begin_node(node)
listeners[:on_begin_node_enter]&.each { |listener| listener.on_begin_node_enter(node) }
super
listeners[:on_begin_node_leave]&.each { |listener| listener.on_begin_node_leave(node) }
end
# Dispatch enter and leave events for BlockArgumentNode nodes and continue
# walking the tree.
def visit_block_argument_node(node)
listeners[:on_block_argument_node_enter]&.each { |listener| listener.on_block_argument_node_enter(node) }
super
listeners[:on_block_argument_node_leave]&.each { |listener| listener.on_block_argument_node_leave(node) }
end
# Dispatch enter and leave events for BlockLocalVariableNode nodes and continue
# walking the tree.
def visit_block_local_variable_node(node)
listeners[:on_block_local_variable_node_enter]&.each { |listener| listener.on_block_local_variable_node_enter(node) }
super
listeners[:on_block_local_variable_node_leave]&.each { |listener| listener.on_block_local_variable_node_leave(node) }
end
# Dispatch enter and leave events for BlockNode nodes and continue
# walking the tree.
def visit_block_node(node)
listeners[:on_block_node_enter]&.each { |listener| listener.on_block_node_enter(node) }
super
listeners[:on_block_node_leave]&.each { |listener| listener.on_block_node_leave(node) }
end
# Dispatch enter and leave events for BlockParameterNode nodes and continue
# walking the tree.
def visit_block_parameter_node(node)
listeners[:on_block_parameter_node_enter]&.each { |listener| listener.on_block_parameter_node_enter(node) }
super
listeners[:on_block_parameter_node_leave]&.each { |listener| listener.on_block_parameter_node_leave(node) }
end
# Dispatch enter and leave events for BlockParametersNode nodes and continue
# walking the tree.
def visit_block_parameters_node(node)
listeners[:on_block_parameters_node_enter]&.each { |listener| listener.on_block_parameters_node_enter(node) }
super
listeners[:on_block_parameters_node_leave]&.each { |listener| listener.on_block_parameters_node_leave(node) }
end
# Dispatch enter and leave events for BreakNode nodes and continue
# walking the tree.
def visit_break_node(node)
listeners[:on_break_node_enter]&.each { |listener| listener.on_break_node_enter(node) }
super
listeners[:on_break_node_leave]&.each { |listener| listener.on_break_node_leave(node) }
end
# Dispatch enter and leave events for CallAndWriteNode nodes and continue
# walking the tree.
def visit_call_and_write_node(node)
listeners[:on_call_and_write_node_enter]&.each { |listener| listener.on_call_and_write_node_enter(node) }
super
listeners[:on_call_and_write_node_leave]&.each { |listener| listener.on_call_and_write_node_leave(node) }
end
# Dispatch enter and leave events for CallNode nodes and continue
# walking the tree.
def visit_call_node(node)
listeners[:on_call_node_enter]&.each { |listener| listener.on_call_node_enter(node) }
super
listeners[:on_call_node_leave]&.each { |listener| listener.on_call_node_leave(node) }
end
# Dispatch enter and leave events for CallOperatorWriteNode nodes and continue
# walking the tree.
def visit_call_operator_write_node(node)
listeners[:on_call_operator_write_node_enter]&.each { |listener| listener.on_call_operator_write_node_enter(node) }
super
listeners[:on_call_operator_write_node_leave]&.each { |listener| listener.on_call_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for CallOrWriteNode nodes and continue
# walking the tree.
def visit_call_or_write_node(node)
listeners[:on_call_or_write_node_enter]&.each { |listener| listener.on_call_or_write_node_enter(node) }
super
listeners[:on_call_or_write_node_leave]&.each { |listener| listener.on_call_or_write_node_leave(node) }
end
# Dispatch enter and leave events for CallTargetNode nodes and continue
# walking the tree.
def visit_call_target_node(node)
listeners[:on_call_target_node_enter]&.each { |listener| listener.on_call_target_node_enter(node) }
super
listeners[:on_call_target_node_leave]&.each { |listener| listener.on_call_target_node_leave(node) }
end
# Dispatch enter and leave events for CapturePatternNode nodes and continue
# walking the tree.
def visit_capture_pattern_node(node)
listeners[:on_capture_pattern_node_enter]&.each { |listener| listener.on_capture_pattern_node_enter(node) }
super
listeners[:on_capture_pattern_node_leave]&.each { |listener| listener.on_capture_pattern_node_leave(node) }
end
# Dispatch enter and leave events for CaseMatchNode nodes and continue
# walking the tree.
def visit_case_match_node(node)
listeners[:on_case_match_node_enter]&.each { |listener| listener.on_case_match_node_enter(node) }
super
listeners[:on_case_match_node_leave]&.each { |listener| listener.on_case_match_node_leave(node) }
end
# Dispatch enter and leave events for CaseNode nodes and continue
# walking the tree.
def visit_case_node(node)
listeners[:on_case_node_enter]&.each { |listener| listener.on_case_node_enter(node) }
super
listeners[:on_case_node_leave]&.each { |listener| listener.on_case_node_leave(node) }
end
# Dispatch enter and leave events for ClassNode nodes and continue
# walking the tree.
def visit_class_node(node)
listeners[:on_class_node_enter]&.each { |listener| listener.on_class_node_enter(node) }
super
listeners[:on_class_node_leave]&.each { |listener| listener.on_class_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableAndWriteNode nodes and continue
# walking the tree.
def visit_class_variable_and_write_node(node)
listeners[:on_class_variable_and_write_node_enter]&.each { |listener| listener.on_class_variable_and_write_node_enter(node) }
super
listeners[:on_class_variable_and_write_node_leave]&.each { |listener| listener.on_class_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableOperatorWriteNode nodes and continue
# walking the tree.
def visit_class_variable_operator_write_node(node)
listeners[:on_class_variable_operator_write_node_enter]&.each { |listener| listener.on_class_variable_operator_write_node_enter(node) }
super
listeners[:on_class_variable_operator_write_node_leave]&.each { |listener| listener.on_class_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableOrWriteNode nodes and continue
# walking the tree.
def visit_class_variable_or_write_node(node)
listeners[:on_class_variable_or_write_node_enter]&.each { |listener| listener.on_class_variable_or_write_node_enter(node) }
super
listeners[:on_class_variable_or_write_node_leave]&.each { |listener| listener.on_class_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableReadNode nodes and continue
# walking the tree.
def visit_class_variable_read_node(node)
listeners[:on_class_variable_read_node_enter]&.each { |listener| listener.on_class_variable_read_node_enter(node) }
super
listeners[:on_class_variable_read_node_leave]&.each { |listener| listener.on_class_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableTargetNode nodes and continue
# walking the tree.
def visit_class_variable_target_node(node)
listeners[:on_class_variable_target_node_enter]&.each { |listener| listener.on_class_variable_target_node_enter(node) }
super
listeners[:on_class_variable_target_node_leave]&.each { |listener| listener.on_class_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableWriteNode nodes and continue
# walking the tree.
def visit_class_variable_write_node(node)
listeners[:on_class_variable_write_node_enter]&.each { |listener| listener.on_class_variable_write_node_enter(node) }
super
listeners[:on_class_variable_write_node_leave]&.each { |listener| listener.on_class_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantAndWriteNode nodes and continue
# walking the tree.
def visit_constant_and_write_node(node)
listeners[:on_constant_and_write_node_enter]&.each { |listener| listener.on_constant_and_write_node_enter(node) }
super
listeners[:on_constant_and_write_node_leave]&.each { |listener| listener.on_constant_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantOperatorWriteNode nodes and continue
# walking the tree.
def visit_constant_operator_write_node(node)
listeners[:on_constant_operator_write_node_enter]&.each { |listener| listener.on_constant_operator_write_node_enter(node) }
super
listeners[:on_constant_operator_write_node_leave]&.each { |listener| listener.on_constant_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantOrWriteNode nodes and continue
# walking the tree.
def visit_constant_or_write_node(node)
listeners[:on_constant_or_write_node_enter]&.each { |listener| listener.on_constant_or_write_node_enter(node) }
super
listeners[:on_constant_or_write_node_leave]&.each { |listener| listener.on_constant_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathAndWriteNode nodes and continue
# walking the tree.
def visit_constant_path_and_write_node(node)
listeners[:on_constant_path_and_write_node_enter]&.each { |listener| listener.on_constant_path_and_write_node_enter(node) }
super
listeners[:on_constant_path_and_write_node_leave]&.each { |listener| listener.on_constant_path_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathNode nodes and continue
# walking the tree.
def visit_constant_path_node(node)
listeners[:on_constant_path_node_enter]&.each { |listener| listener.on_constant_path_node_enter(node) }
super
listeners[:on_constant_path_node_leave]&.each { |listener| listener.on_constant_path_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathOperatorWriteNode nodes and continue
# walking the tree.
def visit_constant_path_operator_write_node(node)
listeners[:on_constant_path_operator_write_node_enter]&.each { |listener| listener.on_constant_path_operator_write_node_enter(node) }
super
listeners[:on_constant_path_operator_write_node_leave]&.each { |listener| listener.on_constant_path_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathOrWriteNode nodes and continue
# walking the tree.
def visit_constant_path_or_write_node(node)
listeners[:on_constant_path_or_write_node_enter]&.each { |listener| listener.on_constant_path_or_write_node_enter(node) }
super
listeners[:on_constant_path_or_write_node_leave]&.each { |listener| listener.on_constant_path_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathTargetNode nodes and continue
# walking the tree.
def visit_constant_path_target_node(node)
listeners[:on_constant_path_target_node_enter]&.each { |listener| listener.on_constant_path_target_node_enter(node) }
super
listeners[:on_constant_path_target_node_leave]&.each { |listener| listener.on_constant_path_target_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathWriteNode nodes and continue
# walking the tree.
def visit_constant_path_write_node(node)
listeners[:on_constant_path_write_node_enter]&.each { |listener| listener.on_constant_path_write_node_enter(node) }
super
listeners[:on_constant_path_write_node_leave]&.each { |listener| listener.on_constant_path_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantReadNode nodes and continue
# walking the tree.
def visit_constant_read_node(node)
listeners[:on_constant_read_node_enter]&.each { |listener| listener.on_constant_read_node_enter(node) }
super
listeners[:on_constant_read_node_leave]&.each { |listener| listener.on_constant_read_node_leave(node) }
end
# Dispatch enter and leave events for ConstantTargetNode nodes and continue
# walking the tree.
def visit_constant_target_node(node)
listeners[:on_constant_target_node_enter]&.each { |listener| listener.on_constant_target_node_enter(node) }
super
listeners[:on_constant_target_node_leave]&.each { |listener| listener.on_constant_target_node_leave(node) }
end
# Dispatch enter and leave events for ConstantWriteNode nodes and continue
# walking the tree.
def visit_constant_write_node(node)
listeners[:on_constant_write_node_enter]&.each { |listener| listener.on_constant_write_node_enter(node) }
super
listeners[:on_constant_write_node_leave]&.each { |listener| listener.on_constant_write_node_leave(node) }
end
# Dispatch enter and leave events for DefNode nodes and continue
# walking the tree.
def visit_def_node(node)
listeners[:on_def_node_enter]&.each { |listener| listener.on_def_node_enter(node) }
super
listeners[:on_def_node_leave]&.each { |listener| listener.on_def_node_leave(node) }
end
# Dispatch enter and leave events for DefinedNode nodes and continue
# walking the tree.
def visit_defined_node(node)
listeners[:on_defined_node_enter]&.each { |listener| listener.on_defined_node_enter(node) }
super
listeners[:on_defined_node_leave]&.each { |listener| listener.on_defined_node_leave(node) }
end
# Dispatch enter and leave events for ElseNode nodes and continue
# walking the tree.
def visit_else_node(node)
listeners[:on_else_node_enter]&.each { |listener| listener.on_else_node_enter(node) }
super
listeners[:on_else_node_leave]&.each { |listener| listener.on_else_node_leave(node) }
end
# Dispatch enter and leave events for EmbeddedStatementsNode nodes and continue
# walking the tree.
def visit_embedded_statements_node(node)
listeners[:on_embedded_statements_node_enter]&.each { |listener| listener.on_embedded_statements_node_enter(node) }
super
listeners[:on_embedded_statements_node_leave]&.each { |listener| listener.on_embedded_statements_node_leave(node) }
end
# Dispatch enter and leave events for EmbeddedVariableNode nodes and continue
# walking the tree.
def visit_embedded_variable_node(node)
listeners[:on_embedded_variable_node_enter]&.each { |listener| listener.on_embedded_variable_node_enter(node) }
super
listeners[:on_embedded_variable_node_leave]&.each { |listener| listener.on_embedded_variable_node_leave(node) }
end
# Dispatch enter and leave events for EnsureNode nodes and continue
# walking the tree.
def visit_ensure_node(node)
listeners[:on_ensure_node_enter]&.each { |listener| listener.on_ensure_node_enter(node) }
super
listeners[:on_ensure_node_leave]&.each { |listener| listener.on_ensure_node_leave(node) }
end
# Dispatch enter and leave events for FalseNode nodes and continue
# walking the tree.
def visit_false_node(node)
listeners[:on_false_node_enter]&.each { |listener| listener.on_false_node_enter(node) }
super
listeners[:on_false_node_leave]&.each { |listener| listener.on_false_node_leave(node) }
end
# Dispatch enter and leave events for FindPatternNode nodes and continue
# walking the tree.
def visit_find_pattern_node(node)
listeners[:on_find_pattern_node_enter]&.each { |listener| listener.on_find_pattern_node_enter(node) }
super
listeners[:on_find_pattern_node_leave]&.each { |listener| listener.on_find_pattern_node_leave(node) }
end
# Dispatch enter and leave events for FlipFlopNode nodes and continue
# walking the tree.
def visit_flip_flop_node(node)
listeners[:on_flip_flop_node_enter]&.each { |listener| listener.on_flip_flop_node_enter(node) }
super
listeners[:on_flip_flop_node_leave]&.each { |listener| listener.on_flip_flop_node_leave(node) }
end
# Dispatch enter and leave events for FloatNode nodes and continue
# walking the tree.
def visit_float_node(node)
listeners[:on_float_node_enter]&.each { |listener| listener.on_float_node_enter(node) }
super
listeners[:on_float_node_leave]&.each { |listener| listener.on_float_node_leave(node) }
end
# Dispatch enter and leave events for ForNode nodes and continue
# walking the tree.
def visit_for_node(node)
listeners[:on_for_node_enter]&.each { |listener| listener.on_for_node_enter(node) }
super
listeners[:on_for_node_leave]&.each { |listener| listener.on_for_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingArgumentsNode nodes and continue
# walking the tree.
def visit_forwarding_arguments_node(node)
listeners[:on_forwarding_arguments_node_enter]&.each { |listener| listener.on_forwarding_arguments_node_enter(node) }
super
listeners[:on_forwarding_arguments_node_leave]&.each { |listener| listener.on_forwarding_arguments_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingParameterNode nodes and continue
# walking the tree.
def visit_forwarding_parameter_node(node)
listeners[:on_forwarding_parameter_node_enter]&.each { |listener| listener.on_forwarding_parameter_node_enter(node) }
super
listeners[:on_forwarding_parameter_node_leave]&.each { |listener| listener.on_forwarding_parameter_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingSuperNode nodes and continue
# walking the tree.
def visit_forwarding_super_node(node)
listeners[:on_forwarding_super_node_enter]&.each { |listener| listener.on_forwarding_super_node_enter(node) }
super
listeners[:on_forwarding_super_node_leave]&.each { |listener| listener.on_forwarding_super_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableAndWriteNode nodes and continue
# walking the tree.
def visit_global_variable_and_write_node(node)
listeners[:on_global_variable_and_write_node_enter]&.each { |listener| listener.on_global_variable_and_write_node_enter(node) }
super
listeners[:on_global_variable_and_write_node_leave]&.each { |listener| listener.on_global_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableOperatorWriteNode nodes and continue
# walking the tree.
def visit_global_variable_operator_write_node(node)
listeners[:on_global_variable_operator_write_node_enter]&.each { |listener| listener.on_global_variable_operator_write_node_enter(node) }
super
listeners[:on_global_variable_operator_write_node_leave]&.each { |listener| listener.on_global_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableOrWriteNode nodes and continue
# walking the tree.
def visit_global_variable_or_write_node(node)
listeners[:on_global_variable_or_write_node_enter]&.each { |listener| listener.on_global_variable_or_write_node_enter(node) }
super
listeners[:on_global_variable_or_write_node_leave]&.each { |listener| listener.on_global_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableReadNode nodes and continue
# walking the tree.
def visit_global_variable_read_node(node)
listeners[:on_global_variable_read_node_enter]&.each { |listener| listener.on_global_variable_read_node_enter(node) }
super
listeners[:on_global_variable_read_node_leave]&.each { |listener| listener.on_global_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableTargetNode nodes and continue
# walking the tree.
def visit_global_variable_target_node(node)
listeners[:on_global_variable_target_node_enter]&.each { |listener| listener.on_global_variable_target_node_enter(node) }
super
listeners[:on_global_variable_target_node_leave]&.each { |listener| listener.on_global_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableWriteNode nodes and continue
# walking the tree.
def visit_global_variable_write_node(node)
listeners[:on_global_variable_write_node_enter]&.each { |listener| listener.on_global_variable_write_node_enter(node) }
super
listeners[:on_global_variable_write_node_leave]&.each { |listener| listener.on_global_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for HashNode nodes and continue
# walking the tree.
def visit_hash_node(node)
listeners[:on_hash_node_enter]&.each { |listener| listener.on_hash_node_enter(node) }
super
listeners[:on_hash_node_leave]&.each { |listener| listener.on_hash_node_leave(node) }
end
# Dispatch enter and leave events for HashPatternNode nodes and continue
# walking the tree.
def visit_hash_pattern_node(node)
listeners[:on_hash_pattern_node_enter]&.each { |listener| listener.on_hash_pattern_node_enter(node) }
super
listeners[:on_hash_pattern_node_leave]&.each { |listener| listener.on_hash_pattern_node_leave(node) }
end
# Dispatch enter and leave events for IfNode nodes and continue
# walking the tree.
def visit_if_node(node)
listeners[:on_if_node_enter]&.each { |listener| listener.on_if_node_enter(node) }
super
listeners[:on_if_node_leave]&.each { |listener| listener.on_if_node_leave(node) }
end
# Dispatch enter and leave events for ImaginaryNode nodes and continue
# walking the tree.
def visit_imaginary_node(node)
listeners[:on_imaginary_node_enter]&.each { |listener| listener.on_imaginary_node_enter(node) }
super
listeners[:on_imaginary_node_leave]&.each { |listener| listener.on_imaginary_node_leave(node) }
end
# Dispatch enter and leave events for ImplicitNode nodes and continue
# walking the tree.
def visit_implicit_node(node)
listeners[:on_implicit_node_enter]&.each { |listener| listener.on_implicit_node_enter(node) }
super
listeners[:on_implicit_node_leave]&.each { |listener| listener.on_implicit_node_leave(node) }
end
# Dispatch enter and leave events for ImplicitRestNode nodes and continue
# walking the tree.
def visit_implicit_rest_node(node)
listeners[:on_implicit_rest_node_enter]&.each { |listener| listener.on_implicit_rest_node_enter(node) }
super
listeners[:on_implicit_rest_node_leave]&.each { |listener| listener.on_implicit_rest_node_leave(node) }
end
# Dispatch enter and leave events for InNode nodes and continue
# walking the tree.
def visit_in_node(node)
listeners[:on_in_node_enter]&.each { |listener| listener.on_in_node_enter(node) }
super
listeners[:on_in_node_leave]&.each { |listener| listener.on_in_node_leave(node) }
end
# Dispatch enter and leave events for IndexAndWriteNode nodes and continue
# walking the tree.
def visit_index_and_write_node(node)
listeners[:on_index_and_write_node_enter]&.each { |listener| listener.on_index_and_write_node_enter(node) }
super
listeners[:on_index_and_write_node_leave]&.each { |listener| listener.on_index_and_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexOperatorWriteNode nodes and continue
# walking the tree.
def visit_index_operator_write_node(node)
listeners[:on_index_operator_write_node_enter]&.each { |listener| listener.on_index_operator_write_node_enter(node) }
super
listeners[:on_index_operator_write_node_leave]&.each { |listener| listener.on_index_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexOrWriteNode nodes and continue
# walking the tree.
def visit_index_or_write_node(node)
listeners[:on_index_or_write_node_enter]&.each { |listener| listener.on_index_or_write_node_enter(node) }
super
listeners[:on_index_or_write_node_leave]&.each { |listener| listener.on_index_or_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexTargetNode nodes and continue
# walking the tree.
def visit_index_target_node(node)
listeners[:on_index_target_node_enter]&.each { |listener| listener.on_index_target_node_enter(node) }
super
listeners[:on_index_target_node_leave]&.each { |listener| listener.on_index_target_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableAndWriteNode nodes and continue
# walking the tree.
def visit_instance_variable_and_write_node(node)
listeners[:on_instance_variable_and_write_node_enter]&.each { |listener| listener.on_instance_variable_and_write_node_enter(node) }
super
listeners[:on_instance_variable_and_write_node_leave]&.each { |listener| listener.on_instance_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableOperatorWriteNode nodes and continue
# walking the tree.
def visit_instance_variable_operator_write_node(node)
listeners[:on_instance_variable_operator_write_node_enter]&.each { |listener| listener.on_instance_variable_operator_write_node_enter(node) }
super
listeners[:on_instance_variable_operator_write_node_leave]&.each { |listener| listener.on_instance_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableOrWriteNode nodes and continue
# walking the tree.
def visit_instance_variable_or_write_node(node)
listeners[:on_instance_variable_or_write_node_enter]&.each { |listener| listener.on_instance_variable_or_write_node_enter(node) }
super
listeners[:on_instance_variable_or_write_node_leave]&.each { |listener| listener.on_instance_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableReadNode nodes and continue
# walking the tree.
def visit_instance_variable_read_node(node)
listeners[:on_instance_variable_read_node_enter]&.each { |listener| listener.on_instance_variable_read_node_enter(node) }
super
listeners[:on_instance_variable_read_node_leave]&.each { |listener| listener.on_instance_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableTargetNode nodes and continue
# walking the tree.
def visit_instance_variable_target_node(node)
listeners[:on_instance_variable_target_node_enter]&.each { |listener| listener.on_instance_variable_target_node_enter(node) }
super
listeners[:on_instance_variable_target_node_leave]&.each { |listener| listener.on_instance_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableWriteNode nodes and continue
# walking the tree.
def visit_instance_variable_write_node(node)
listeners[:on_instance_variable_write_node_enter]&.each { |listener| listener.on_instance_variable_write_node_enter(node) }
super
listeners[:on_instance_variable_write_node_leave]&.each { |listener| listener.on_instance_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for IntegerNode nodes and continue
# walking the tree.
def visit_integer_node(node)
listeners[:on_integer_node_enter]&.each { |listener| listener.on_integer_node_enter(node) }
super
listeners[:on_integer_node_leave]&.each { |listener| listener.on_integer_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedMatchLastLineNode nodes and continue
# walking the tree.
def visit_interpolated_match_last_line_node(node)
listeners[:on_interpolated_match_last_line_node_enter]&.each { |listener| listener.on_interpolated_match_last_line_node_enter(node) }
super
listeners[:on_interpolated_match_last_line_node_leave]&.each { |listener| listener.on_interpolated_match_last_line_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedRegularExpressionNode nodes and continue
# walking the tree.
def visit_interpolated_regular_expression_node(node)
listeners[:on_interpolated_regular_expression_node_enter]&.each { |listener| listener.on_interpolated_regular_expression_node_enter(node) }
super
listeners[:on_interpolated_regular_expression_node_leave]&.each { |listener| listener.on_interpolated_regular_expression_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedStringNode nodes and continue
# walking the tree.
def visit_interpolated_string_node(node)
listeners[:on_interpolated_string_node_enter]&.each { |listener| listener.on_interpolated_string_node_enter(node) }
super
listeners[:on_interpolated_string_node_leave]&.each { |listener| listener.on_interpolated_string_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedSymbolNode nodes and continue
# walking the tree.
def visit_interpolated_symbol_node(node)
listeners[:on_interpolated_symbol_node_enter]&.each { |listener| listener.on_interpolated_symbol_node_enter(node) }
super
listeners[:on_interpolated_symbol_node_leave]&.each { |listener| listener.on_interpolated_symbol_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedXStringNode nodes and continue
# walking the tree.
def visit_interpolated_x_string_node(node)
listeners[:on_interpolated_x_string_node_enter]&.each { |listener| listener.on_interpolated_x_string_node_enter(node) }
super
listeners[:on_interpolated_x_string_node_leave]&.each { |listener| listener.on_interpolated_x_string_node_leave(node) }
end
# Dispatch enter and leave events for KeywordHashNode nodes and continue
# walking the tree.
def visit_keyword_hash_node(node)
listeners[:on_keyword_hash_node_enter]&.each { |listener| listener.on_keyword_hash_node_enter(node) }
super
listeners[:on_keyword_hash_node_leave]&.each { |listener| listener.on_keyword_hash_node_leave(node) }
end
# Dispatch enter and leave events for KeywordRestParameterNode nodes and continue
# walking the tree.
def visit_keyword_rest_parameter_node(node)
listeners[:on_keyword_rest_parameter_node_enter]&.each { |listener| listener.on_keyword_rest_parameter_node_enter(node) }
super
listeners[:on_keyword_rest_parameter_node_leave]&.each { |listener| listener.on_keyword_rest_parameter_node_leave(node) }
end
# Dispatch enter and leave events for LambdaNode nodes and continue
# walking the tree.
def visit_lambda_node(node)
listeners[:on_lambda_node_enter]&.each { |listener| listener.on_lambda_node_enter(node) }
super
listeners[:on_lambda_node_leave]&.each { |listener| listener.on_lambda_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableAndWriteNode nodes and continue
# walking the tree.
def visit_local_variable_and_write_node(node)
listeners[:on_local_variable_and_write_node_enter]&.each { |listener| listener.on_local_variable_and_write_node_enter(node) }
super
listeners[:on_local_variable_and_write_node_leave]&.each { |listener| listener.on_local_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableOperatorWriteNode nodes and continue
# walking the tree.
def visit_local_variable_operator_write_node(node)
listeners[:on_local_variable_operator_write_node_enter]&.each { |listener| listener.on_local_variable_operator_write_node_enter(node) }
super
listeners[:on_local_variable_operator_write_node_leave]&.each { |listener| listener.on_local_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableOrWriteNode nodes and continue
# walking the tree.
def visit_local_variable_or_write_node(node)
listeners[:on_local_variable_or_write_node_enter]&.each { |listener| listener.on_local_variable_or_write_node_enter(node) }
super
listeners[:on_local_variable_or_write_node_leave]&.each { |listener| listener.on_local_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableReadNode nodes and continue
# walking the tree.
def visit_local_variable_read_node(node)
listeners[:on_local_variable_read_node_enter]&.each { |listener| listener.on_local_variable_read_node_enter(node) }
super
listeners[:on_local_variable_read_node_leave]&.each { |listener| listener.on_local_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableTargetNode nodes and continue
# walking the tree.
def visit_local_variable_target_node(node)
listeners[:on_local_variable_target_node_enter]&.each { |listener| listener.on_local_variable_target_node_enter(node) }
super
listeners[:on_local_variable_target_node_leave]&.each { |listener| listener.on_local_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableWriteNode nodes and continue
# walking the tree.
def visit_local_variable_write_node(node)
listeners[:on_local_variable_write_node_enter]&.each { |listener| listener.on_local_variable_write_node_enter(node) }
super
listeners[:on_local_variable_write_node_leave]&.each { |listener| listener.on_local_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for MatchLastLineNode nodes and continue
# walking the tree.
def visit_match_last_line_node(node)
listeners[:on_match_last_line_node_enter]&.each { |listener| listener.on_match_last_line_node_enter(node) }
super
listeners[:on_match_last_line_node_leave]&.each { |listener| listener.on_match_last_line_node_leave(node) }
end
# Dispatch enter and leave events for MatchPredicateNode nodes and continue
# walking the tree.
def visit_match_predicate_node(node)
listeners[:on_match_predicate_node_enter]&.each { |listener| listener.on_match_predicate_node_enter(node) }
super
listeners[:on_match_predicate_node_leave]&.each { |listener| listener.on_match_predicate_node_leave(node) }
end
# Dispatch enter and leave events for MatchRequiredNode nodes and continue
# walking the tree.
def visit_match_required_node(node)
listeners[:on_match_required_node_enter]&.each { |listener| listener.on_match_required_node_enter(node) }
super
listeners[:on_match_required_node_leave]&.each { |listener| listener.on_match_required_node_leave(node) }
end
# Dispatch enter and leave events for MatchWriteNode nodes and continue
# walking the tree.
def visit_match_write_node(node)
listeners[:on_match_write_node_enter]&.each { |listener| listener.on_match_write_node_enter(node) }
super
listeners[:on_match_write_node_leave]&.each { |listener| listener.on_match_write_node_leave(node) }
end
# Dispatch enter and leave events for MissingNode nodes and continue
# walking the tree.
def visit_missing_node(node)
listeners[:on_missing_node_enter]&.each { |listener| listener.on_missing_node_enter(node) }
super
listeners[:on_missing_node_leave]&.each { |listener| listener.on_missing_node_leave(node) }
end
# Dispatch enter and leave events for ModuleNode nodes and continue
# walking the tree.
def visit_module_node(node)
listeners[:on_module_node_enter]&.each { |listener| listener.on_module_node_enter(node) }
super
listeners[:on_module_node_leave]&.each { |listener| listener.on_module_node_leave(node) }
end
# Dispatch enter and leave events for MultiTargetNode nodes and continue
# walking the tree.
def visit_multi_target_node(node)
listeners[:on_multi_target_node_enter]&.each { |listener| listener.on_multi_target_node_enter(node) }
super
listeners[:on_multi_target_node_leave]&.each { |listener| listener.on_multi_target_node_leave(node) }
end
# Dispatch enter and leave events for MultiWriteNode nodes and continue
# walking the tree.
def visit_multi_write_node(node)
listeners[:on_multi_write_node_enter]&.each { |listener| listener.on_multi_write_node_enter(node) }
super
listeners[:on_multi_write_node_leave]&.each { |listener| listener.on_multi_write_node_leave(node) }
end
# Dispatch enter and leave events for NextNode nodes and continue
# walking the tree.
def visit_next_node(node)
listeners[:on_next_node_enter]&.each { |listener| listener.on_next_node_enter(node) }
super
listeners[:on_next_node_leave]&.each { |listener| listener.on_next_node_leave(node) }
end
# Dispatch enter and leave events for NilNode nodes and continue
# walking the tree.
def visit_nil_node(node)
listeners[:on_nil_node_enter]&.each { |listener| listener.on_nil_node_enter(node) }
super
listeners[:on_nil_node_leave]&.each { |listener| listener.on_nil_node_leave(node) }
end
# Dispatch enter and leave events for NoKeywordsParameterNode nodes and continue
# walking the tree.
def visit_no_keywords_parameter_node(node)
listeners[:on_no_keywords_parameter_node_enter]&.each { |listener| listener.on_no_keywords_parameter_node_enter(node) }
super
listeners[:on_no_keywords_parameter_node_leave]&.each { |listener| listener.on_no_keywords_parameter_node_leave(node) }
end
# Dispatch enter and leave events for NumberedParametersNode nodes and continue
# walking the tree.
def visit_numbered_parameters_node(node)
listeners[:on_numbered_parameters_node_enter]&.each { |listener| listener.on_numbered_parameters_node_enter(node) }
super
listeners[:on_numbered_parameters_node_leave]&.each { |listener| listener.on_numbered_parameters_node_leave(node) }
end
# Dispatch enter and leave events for NumberedReferenceReadNode nodes and continue
# walking the tree.
def visit_numbered_reference_read_node(node)
listeners[:on_numbered_reference_read_node_enter]&.each { |listener| listener.on_numbered_reference_read_node_enter(node) }
super
listeners[:on_numbered_reference_read_node_leave]&.each { |listener| listener.on_numbered_reference_read_node_leave(node) }
end
# Dispatch enter and leave events for OptionalKeywordParameterNode nodes and continue
# walking the tree.
def visit_optional_keyword_parameter_node(node)
listeners[:on_optional_keyword_parameter_node_enter]&.each { |listener| listener.on_optional_keyword_parameter_node_enter(node) }
super
listeners[:on_optional_keyword_parameter_node_leave]&.each { |listener| listener.on_optional_keyword_parameter_node_leave(node) }
end
# Dispatch enter and leave events for OptionalParameterNode nodes and continue
# walking the tree.
def visit_optional_parameter_node(node)
listeners[:on_optional_parameter_node_enter]&.each { |listener| listener.on_optional_parameter_node_enter(node) }
super
listeners[:on_optional_parameter_node_leave]&.each { |listener| listener.on_optional_parameter_node_leave(node) }
end
# Dispatch enter and leave events for OrNode nodes and continue
# walking the tree.
def visit_or_node(node)
listeners[:on_or_node_enter]&.each { |listener| listener.on_or_node_enter(node) }
super
listeners[:on_or_node_leave]&.each { |listener| listener.on_or_node_leave(node) }
end
# Dispatch enter and leave events for ParametersNode nodes and continue
# walking the tree.
def visit_parameters_node(node)
listeners[:on_parameters_node_enter]&.each { |listener| listener.on_parameters_node_enter(node) }
super
listeners[:on_parameters_node_leave]&.each { |listener| listener.on_parameters_node_leave(node) }
end
# Dispatch enter and leave events for ParenthesesNode nodes and continue
# walking the tree.
def visit_parentheses_node(node)
listeners[:on_parentheses_node_enter]&.each { |listener| listener.on_parentheses_node_enter(node) }
super
listeners[:on_parentheses_node_leave]&.each { |listener| listener.on_parentheses_node_leave(node) }
end
# Dispatch enter and leave events for PinnedExpressionNode nodes and continue
# walking the tree.
def visit_pinned_expression_node(node)
listeners[:on_pinned_expression_node_enter]&.each { |listener| listener.on_pinned_expression_node_enter(node) }
super
listeners[:on_pinned_expression_node_leave]&.each { |listener| listener.on_pinned_expression_node_leave(node) }
end
# Dispatch enter and leave events for PinnedVariableNode nodes and continue
# walking the tree.
def visit_pinned_variable_node(node)
listeners[:on_pinned_variable_node_enter]&.each { |listener| listener.on_pinned_variable_node_enter(node) }
super
listeners[:on_pinned_variable_node_leave]&.each { |listener| listener.on_pinned_variable_node_leave(node) }
end
# Dispatch enter and leave events for PostExecutionNode nodes and continue
# walking the tree.
def visit_post_execution_node(node)
listeners[:on_post_execution_node_enter]&.each { |listener| listener.on_post_execution_node_enter(node) }
super
listeners[:on_post_execution_node_leave]&.each { |listener| listener.on_post_execution_node_leave(node) }
end
# Dispatch enter and leave events for PreExecutionNode nodes and continue
# walking the tree.
def visit_pre_execution_node(node)
listeners[:on_pre_execution_node_enter]&.each { |listener| listener.on_pre_execution_node_enter(node) }
super
listeners[:on_pre_execution_node_leave]&.each { |listener| listener.on_pre_execution_node_leave(node) }
end
# Dispatch enter and leave events for ProgramNode nodes and continue
# walking the tree.
def visit_program_node(node)
listeners[:on_program_node_enter]&.each { |listener| listener.on_program_node_enter(node) }
super
listeners[:on_program_node_leave]&.each { |listener| listener.on_program_node_leave(node) }
end
# Dispatch enter and leave events for RangeNode nodes and continue
# walking the tree.
def visit_range_node(node)
listeners[:on_range_node_enter]&.each { |listener| listener.on_range_node_enter(node) }
super
listeners[:on_range_node_leave]&.each { |listener| listener.on_range_node_leave(node) }
end
# Dispatch enter and leave events for RationalNode nodes and continue
# walking the tree.
def visit_rational_node(node)
listeners[:on_rational_node_enter]&.each { |listener| listener.on_rational_node_enter(node) }
super
listeners[:on_rational_node_leave]&.each { |listener| listener.on_rational_node_leave(node) }
end
# Dispatch enter and leave events for RedoNode nodes and continue
# walking the tree.
def visit_redo_node(node)
listeners[:on_redo_node_enter]&.each { |listener| listener.on_redo_node_enter(node) }
super
listeners[:on_redo_node_leave]&.each { |listener| listener.on_redo_node_leave(node) }
end
# Dispatch enter and leave events for RegularExpressionNode nodes and continue
# walking the tree.
def visit_regular_expression_node(node)
listeners[:on_regular_expression_node_enter]&.each { |listener| listener.on_regular_expression_node_enter(node) }
super
listeners[:on_regular_expression_node_leave]&.each { |listener| listener.on_regular_expression_node_leave(node) }
end
# Dispatch enter and leave events for RequiredKeywordParameterNode nodes and continue
# walking the tree.
def visit_required_keyword_parameter_node(node)
listeners[:on_required_keyword_parameter_node_enter]&.each { |listener| listener.on_required_keyword_parameter_node_enter(node) }
super
listeners[:on_required_keyword_parameter_node_leave]&.each { |listener| listener.on_required_keyword_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RequiredParameterNode nodes and continue
# walking the tree.
def visit_required_parameter_node(node)
listeners[:on_required_parameter_node_enter]&.each { |listener| listener.on_required_parameter_node_enter(node) }
super
listeners[:on_required_parameter_node_leave]&.each { |listener| listener.on_required_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RescueModifierNode nodes and continue
# walking the tree.
def visit_rescue_modifier_node(node)
listeners[:on_rescue_modifier_node_enter]&.each { |listener| listener.on_rescue_modifier_node_enter(node) }
super
listeners[:on_rescue_modifier_node_leave]&.each { |listener| listener.on_rescue_modifier_node_leave(node) }
end
# Dispatch enter and leave events for RescueNode nodes and continue
# walking the tree.
def visit_rescue_node(node)
listeners[:on_rescue_node_enter]&.each { |listener| listener.on_rescue_node_enter(node) }
super
listeners[:on_rescue_node_leave]&.each { |listener| listener.on_rescue_node_leave(node) }
end
# Dispatch enter and leave events for RestParameterNode nodes and continue
# walking the tree.
def visit_rest_parameter_node(node)
listeners[:on_rest_parameter_node_enter]&.each { |listener| listener.on_rest_parameter_node_enter(node) }
super
listeners[:on_rest_parameter_node_leave]&.each { |listener| listener.on_rest_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RetryNode nodes and continue
# walking the tree.
def visit_retry_node(node)
listeners[:on_retry_node_enter]&.each { |listener| listener.on_retry_node_enter(node) }
super
listeners[:on_retry_node_leave]&.each { |listener| listener.on_retry_node_leave(node) }
end
# Dispatch enter and leave events for ReturnNode nodes and continue
# walking the tree.
def visit_return_node(node)
listeners[:on_return_node_enter]&.each { |listener| listener.on_return_node_enter(node) }
super
listeners[:on_return_node_leave]&.each { |listener| listener.on_return_node_leave(node) }
end
# Dispatch enter and leave events for SelfNode nodes and continue
# walking the tree.
def visit_self_node(node)
listeners[:on_self_node_enter]&.each { |listener| listener.on_self_node_enter(node) }
super
listeners[:on_self_node_leave]&.each { |listener| listener.on_self_node_leave(node) }
end
# Dispatch enter and leave events for SingletonClassNode nodes and continue
# walking the tree.
def visit_singleton_class_node(node)
listeners[:on_singleton_class_node_enter]&.each { |listener| listener.on_singleton_class_node_enter(node) }
super
listeners[:on_singleton_class_node_leave]&.each { |listener| listener.on_singleton_class_node_leave(node) }
end
# Dispatch enter and leave events for SourceEncodingNode nodes and continue
# walking the tree.
def visit_source_encoding_node(node)
listeners[:on_source_encoding_node_enter]&.each { |listener| listener.on_source_encoding_node_enter(node) }
super
listeners[:on_source_encoding_node_leave]&.each { |listener| listener.on_source_encoding_node_leave(node) }
end
# Dispatch enter and leave events for SourceFileNode nodes and continue
# walking the tree.
def visit_source_file_node(node)
listeners[:on_source_file_node_enter]&.each { |listener| listener.on_source_file_node_enter(node) }
super
listeners[:on_source_file_node_leave]&.each { |listener| listener.on_source_file_node_leave(node) }
end
# Dispatch enter and leave events for SourceLineNode nodes and continue
# walking the tree.
def visit_source_line_node(node)
listeners[:on_source_line_node_enter]&.each { |listener| listener.on_source_line_node_enter(node) }
super
listeners[:on_source_line_node_leave]&.each { |listener| listener.on_source_line_node_leave(node) }
end
# Dispatch enter and leave events for SplatNode nodes and continue
# walking the tree.
def visit_splat_node(node)
listeners[:on_splat_node_enter]&.each { |listener| listener.on_splat_node_enter(node) }
super
listeners[:on_splat_node_leave]&.each { |listener| listener.on_splat_node_leave(node) }
end
# Dispatch enter and leave events for StatementsNode nodes and continue
# walking the tree.
def visit_statements_node(node)
listeners[:on_statements_node_enter]&.each { |listener| listener.on_statements_node_enter(node) }
super
listeners[:on_statements_node_leave]&.each { |listener| listener.on_statements_node_leave(node) }
end
# Dispatch enter and leave events for StringNode nodes and continue
# walking the tree.
def visit_string_node(node)
listeners[:on_string_node_enter]&.each { |listener| listener.on_string_node_enter(node) }
super
listeners[:on_string_node_leave]&.each { |listener| listener.on_string_node_leave(node) }
end
# Dispatch enter and leave events for SuperNode nodes and continue
# walking the tree.
def visit_super_node(node)
listeners[:on_super_node_enter]&.each { |listener| listener.on_super_node_enter(node) }
super
listeners[:on_super_node_leave]&.each { |listener| listener.on_super_node_leave(node) }
end
# Dispatch enter and leave events for SymbolNode nodes and continue
# walking the tree.
def visit_symbol_node(node)
listeners[:on_symbol_node_enter]&.each { |listener| listener.on_symbol_node_enter(node) }
super
listeners[:on_symbol_node_leave]&.each { |listener| listener.on_symbol_node_leave(node) }
end
# Dispatch enter and leave events for TrueNode nodes and continue
# walking the tree.
def visit_true_node(node)
listeners[:on_true_node_enter]&.each { |listener| listener.on_true_node_enter(node) }
super
listeners[:on_true_node_leave]&.each { |listener| listener.on_true_node_leave(node) }
end
# Dispatch enter and leave events for UndefNode nodes and continue
# walking the tree.
def visit_undef_node(node)
listeners[:on_undef_node_enter]&.each { |listener| listener.on_undef_node_enter(node) }
super
listeners[:on_undef_node_leave]&.each { |listener| listener.on_undef_node_leave(node) }
end
# Dispatch enter and leave events for UnlessNode nodes and continue
# walking the tree.
def visit_unless_node(node)
listeners[:on_unless_node_enter]&.each { |listener| listener.on_unless_node_enter(node) }
super
listeners[:on_unless_node_leave]&.each { |listener| listener.on_unless_node_leave(node) }
end
# Dispatch enter and leave events for UntilNode nodes and continue
# walking the tree.
def visit_until_node(node)
listeners[:on_until_node_enter]&.each { |listener| listener.on_until_node_enter(node) }
super
listeners[:on_until_node_leave]&.each { |listener| listener.on_until_node_leave(node) }
end
# Dispatch enter and leave events for WhenNode nodes and continue
# walking the tree.
def visit_when_node(node)
listeners[:on_when_node_enter]&.each { |listener| listener.on_when_node_enter(node) }
super
listeners[:on_when_node_leave]&.each { |listener| listener.on_when_node_leave(node) }
end
# Dispatch enter and leave events for WhileNode nodes and continue
# walking the tree.
def visit_while_node(node)
listeners[:on_while_node_enter]&.each { |listener| listener.on_while_node_enter(node) }
super
listeners[:on_while_node_leave]&.each { |listener| listener.on_while_node_leave(node) }
end
# Dispatch enter and leave events for XStringNode nodes and continue
# walking the tree.
def visit_x_string_node(node)
listeners[:on_x_string_node_enter]&.each { |listener| listener.on_x_string_node_enter(node) }
super
listeners[:on_x_string_node_leave]&.each { |listener| listener.on_x_string_node_leave(node) }
end
# Dispatch enter and leave events for YieldNode nodes and continue
# walking the tree.
def visit_yield_node(node)
listeners[:on_yield_node_enter]&.each { |listener| listener.on_yield_node_enter(node) }
super
listeners[:on_yield_node_leave]&.each { |listener| listener.on_yield_node_leave(node) }
end
class DispatchOnce < Visitor # :nodoc:
attr_reader :listeners
def initialize(listeners)
@listeners = listeners
end
# Dispatch enter and leave events for AliasGlobalVariableNode nodes.
def visit_alias_global_variable_node(node)
listeners[:on_alias_global_variable_node_enter]&.each { |listener| listener.on_alias_global_variable_node_enter(node) }
listeners[:on_alias_global_variable_node_leave]&.each { |listener| listener.on_alias_global_variable_node_leave(node) }
end
# Dispatch enter and leave events for AliasMethodNode nodes.
def visit_alias_method_node(node)
listeners[:on_alias_method_node_enter]&.each { |listener| listener.on_alias_method_node_enter(node) }
listeners[:on_alias_method_node_leave]&.each { |listener| listener.on_alias_method_node_leave(node) }
end
# Dispatch enter and leave events for AlternationPatternNode nodes.
def visit_alternation_pattern_node(node)
listeners[:on_alternation_pattern_node_enter]&.each { |listener| listener.on_alternation_pattern_node_enter(node) }
listeners[:on_alternation_pattern_node_leave]&.each { |listener| listener.on_alternation_pattern_node_leave(node) }
end
# Dispatch enter and leave events for AndNode nodes.
def visit_and_node(node)
listeners[:on_and_node_enter]&.each { |listener| listener.on_and_node_enter(node) }
listeners[:on_and_node_leave]&.each { |listener| listener.on_and_node_leave(node) }
end
# Dispatch enter and leave events for ArgumentsNode nodes.
def visit_arguments_node(node)
listeners[:on_arguments_node_enter]&.each { |listener| listener.on_arguments_node_enter(node) }
listeners[:on_arguments_node_leave]&.each { |listener| listener.on_arguments_node_leave(node) }
end
# Dispatch enter and leave events for ArrayNode nodes.
def visit_array_node(node)
listeners[:on_array_node_enter]&.each { |listener| listener.on_array_node_enter(node) }
listeners[:on_array_node_leave]&.each { |listener| listener.on_array_node_leave(node) }
end
# Dispatch enter and leave events for ArrayPatternNode nodes.
def visit_array_pattern_node(node)
listeners[:on_array_pattern_node_enter]&.each { |listener| listener.on_array_pattern_node_enter(node) }
listeners[:on_array_pattern_node_leave]&.each { |listener| listener.on_array_pattern_node_leave(node) }
end
# Dispatch enter and leave events for AssocNode nodes.
def visit_assoc_node(node)
listeners[:on_assoc_node_enter]&.each { |listener| listener.on_assoc_node_enter(node) }
listeners[:on_assoc_node_leave]&.each { |listener| listener.on_assoc_node_leave(node) }
end
# Dispatch enter and leave events for AssocSplatNode nodes.
def visit_assoc_splat_node(node)
listeners[:on_assoc_splat_node_enter]&.each { |listener| listener.on_assoc_splat_node_enter(node) }
listeners[:on_assoc_splat_node_leave]&.each { |listener| listener.on_assoc_splat_node_leave(node) }
end
# Dispatch enter and leave events for BackReferenceReadNode nodes.
def visit_back_reference_read_node(node)
listeners[:on_back_reference_read_node_enter]&.each { |listener| listener.on_back_reference_read_node_enter(node) }
listeners[:on_back_reference_read_node_leave]&.each { |listener| listener.on_back_reference_read_node_leave(node) }
end
# Dispatch enter and leave events for BeginNode nodes.
def visit_begin_node(node)
listeners[:on_begin_node_enter]&.each { |listener| listener.on_begin_node_enter(node) }
listeners[:on_begin_node_leave]&.each { |listener| listener.on_begin_node_leave(node) }
end
# Dispatch enter and leave events for BlockArgumentNode nodes.
def visit_block_argument_node(node)
listeners[:on_block_argument_node_enter]&.each { |listener| listener.on_block_argument_node_enter(node) }
listeners[:on_block_argument_node_leave]&.each { |listener| listener.on_block_argument_node_leave(node) }
end
# Dispatch enter and leave events for BlockLocalVariableNode nodes.
def visit_block_local_variable_node(node)
listeners[:on_block_local_variable_node_enter]&.each { |listener| listener.on_block_local_variable_node_enter(node) }
listeners[:on_block_local_variable_node_leave]&.each { |listener| listener.on_block_local_variable_node_leave(node) }
end
# Dispatch enter and leave events for BlockNode nodes.
def visit_block_node(node)
listeners[:on_block_node_enter]&.each { |listener| listener.on_block_node_enter(node) }
listeners[:on_block_node_leave]&.each { |listener| listener.on_block_node_leave(node) }
end
# Dispatch enter and leave events for BlockParameterNode nodes.
def visit_block_parameter_node(node)
listeners[:on_block_parameter_node_enter]&.each { |listener| listener.on_block_parameter_node_enter(node) }
listeners[:on_block_parameter_node_leave]&.each { |listener| listener.on_block_parameter_node_leave(node) }
end
# Dispatch enter and leave events for BlockParametersNode nodes.
def visit_block_parameters_node(node)
listeners[:on_block_parameters_node_enter]&.each { |listener| listener.on_block_parameters_node_enter(node) }
listeners[:on_block_parameters_node_leave]&.each { |listener| listener.on_block_parameters_node_leave(node) }
end
# Dispatch enter and leave events for BreakNode nodes.
def visit_break_node(node)
listeners[:on_break_node_enter]&.each { |listener| listener.on_break_node_enter(node) }
listeners[:on_break_node_leave]&.each { |listener| listener.on_break_node_leave(node) }
end
# Dispatch enter and leave events for CallAndWriteNode nodes.
def visit_call_and_write_node(node)
listeners[:on_call_and_write_node_enter]&.each { |listener| listener.on_call_and_write_node_enter(node) }
listeners[:on_call_and_write_node_leave]&.each { |listener| listener.on_call_and_write_node_leave(node) }
end
# Dispatch enter and leave events for CallNode nodes.
def visit_call_node(node)
listeners[:on_call_node_enter]&.each { |listener| listener.on_call_node_enter(node) }
listeners[:on_call_node_leave]&.each { |listener| listener.on_call_node_leave(node) }
end
# Dispatch enter and leave events for CallOperatorWriteNode nodes.
def visit_call_operator_write_node(node)
listeners[:on_call_operator_write_node_enter]&.each { |listener| listener.on_call_operator_write_node_enter(node) }
listeners[:on_call_operator_write_node_leave]&.each { |listener| listener.on_call_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for CallOrWriteNode nodes.
def visit_call_or_write_node(node)
listeners[:on_call_or_write_node_enter]&.each { |listener| listener.on_call_or_write_node_enter(node) }
listeners[:on_call_or_write_node_leave]&.each { |listener| listener.on_call_or_write_node_leave(node) }
end
# Dispatch enter and leave events for CallTargetNode nodes.
def visit_call_target_node(node)
listeners[:on_call_target_node_enter]&.each { |listener| listener.on_call_target_node_enter(node) }
listeners[:on_call_target_node_leave]&.each { |listener| listener.on_call_target_node_leave(node) }
end
# Dispatch enter and leave events for CapturePatternNode nodes.
def visit_capture_pattern_node(node)
listeners[:on_capture_pattern_node_enter]&.each { |listener| listener.on_capture_pattern_node_enter(node) }
listeners[:on_capture_pattern_node_leave]&.each { |listener| listener.on_capture_pattern_node_leave(node) }
end
# Dispatch enter and leave events for CaseMatchNode nodes.
def visit_case_match_node(node)
listeners[:on_case_match_node_enter]&.each { |listener| listener.on_case_match_node_enter(node) }
listeners[:on_case_match_node_leave]&.each { |listener| listener.on_case_match_node_leave(node) }
end
# Dispatch enter and leave events for CaseNode nodes.
def visit_case_node(node)
listeners[:on_case_node_enter]&.each { |listener| listener.on_case_node_enter(node) }
listeners[:on_case_node_leave]&.each { |listener| listener.on_case_node_leave(node) }
end
# Dispatch enter and leave events for ClassNode nodes.
def visit_class_node(node)
listeners[:on_class_node_enter]&.each { |listener| listener.on_class_node_enter(node) }
listeners[:on_class_node_leave]&.each { |listener| listener.on_class_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableAndWriteNode nodes.
def visit_class_variable_and_write_node(node)
listeners[:on_class_variable_and_write_node_enter]&.each { |listener| listener.on_class_variable_and_write_node_enter(node) }
listeners[:on_class_variable_and_write_node_leave]&.each { |listener| listener.on_class_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableOperatorWriteNode nodes.
def visit_class_variable_operator_write_node(node)
listeners[:on_class_variable_operator_write_node_enter]&.each { |listener| listener.on_class_variable_operator_write_node_enter(node) }
listeners[:on_class_variable_operator_write_node_leave]&.each { |listener| listener.on_class_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableOrWriteNode nodes.
def visit_class_variable_or_write_node(node)
listeners[:on_class_variable_or_write_node_enter]&.each { |listener| listener.on_class_variable_or_write_node_enter(node) }
listeners[:on_class_variable_or_write_node_leave]&.each { |listener| listener.on_class_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableReadNode nodes.
def visit_class_variable_read_node(node)
listeners[:on_class_variable_read_node_enter]&.each { |listener| listener.on_class_variable_read_node_enter(node) }
listeners[:on_class_variable_read_node_leave]&.each { |listener| listener.on_class_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableTargetNode nodes.
def visit_class_variable_target_node(node)
listeners[:on_class_variable_target_node_enter]&.each { |listener| listener.on_class_variable_target_node_enter(node) }
listeners[:on_class_variable_target_node_leave]&.each { |listener| listener.on_class_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for ClassVariableWriteNode nodes.
def visit_class_variable_write_node(node)
listeners[:on_class_variable_write_node_enter]&.each { |listener| listener.on_class_variable_write_node_enter(node) }
listeners[:on_class_variable_write_node_leave]&.each { |listener| listener.on_class_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantAndWriteNode nodes.
def visit_constant_and_write_node(node)
listeners[:on_constant_and_write_node_enter]&.each { |listener| listener.on_constant_and_write_node_enter(node) }
listeners[:on_constant_and_write_node_leave]&.each { |listener| listener.on_constant_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantOperatorWriteNode nodes.
def visit_constant_operator_write_node(node)
listeners[:on_constant_operator_write_node_enter]&.each { |listener| listener.on_constant_operator_write_node_enter(node) }
listeners[:on_constant_operator_write_node_leave]&.each { |listener| listener.on_constant_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantOrWriteNode nodes.
def visit_constant_or_write_node(node)
listeners[:on_constant_or_write_node_enter]&.each { |listener| listener.on_constant_or_write_node_enter(node) }
listeners[:on_constant_or_write_node_leave]&.each { |listener| listener.on_constant_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathAndWriteNode nodes.
def visit_constant_path_and_write_node(node)
listeners[:on_constant_path_and_write_node_enter]&.each { |listener| listener.on_constant_path_and_write_node_enter(node) }
listeners[:on_constant_path_and_write_node_leave]&.each { |listener| listener.on_constant_path_and_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathNode nodes.
def visit_constant_path_node(node)
listeners[:on_constant_path_node_enter]&.each { |listener| listener.on_constant_path_node_enter(node) }
listeners[:on_constant_path_node_leave]&.each { |listener| listener.on_constant_path_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathOperatorWriteNode nodes.
def visit_constant_path_operator_write_node(node)
listeners[:on_constant_path_operator_write_node_enter]&.each { |listener| listener.on_constant_path_operator_write_node_enter(node) }
listeners[:on_constant_path_operator_write_node_leave]&.each { |listener| listener.on_constant_path_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathOrWriteNode nodes.
def visit_constant_path_or_write_node(node)
listeners[:on_constant_path_or_write_node_enter]&.each { |listener| listener.on_constant_path_or_write_node_enter(node) }
listeners[:on_constant_path_or_write_node_leave]&.each { |listener| listener.on_constant_path_or_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathTargetNode nodes.
def visit_constant_path_target_node(node)
listeners[:on_constant_path_target_node_enter]&.each { |listener| listener.on_constant_path_target_node_enter(node) }
listeners[:on_constant_path_target_node_leave]&.each { |listener| listener.on_constant_path_target_node_leave(node) }
end
# Dispatch enter and leave events for ConstantPathWriteNode nodes.
def visit_constant_path_write_node(node)
listeners[:on_constant_path_write_node_enter]&.each { |listener| listener.on_constant_path_write_node_enter(node) }
listeners[:on_constant_path_write_node_leave]&.each { |listener| listener.on_constant_path_write_node_leave(node) }
end
# Dispatch enter and leave events for ConstantReadNode nodes.
def visit_constant_read_node(node)
listeners[:on_constant_read_node_enter]&.each { |listener| listener.on_constant_read_node_enter(node) }
listeners[:on_constant_read_node_leave]&.each { |listener| listener.on_constant_read_node_leave(node) }
end
# Dispatch enter and leave events for ConstantTargetNode nodes.
def visit_constant_target_node(node)
listeners[:on_constant_target_node_enter]&.each { |listener| listener.on_constant_target_node_enter(node) }
listeners[:on_constant_target_node_leave]&.each { |listener| listener.on_constant_target_node_leave(node) }
end
# Dispatch enter and leave events for ConstantWriteNode nodes.
def visit_constant_write_node(node)
listeners[:on_constant_write_node_enter]&.each { |listener| listener.on_constant_write_node_enter(node) }
listeners[:on_constant_write_node_leave]&.each { |listener| listener.on_constant_write_node_leave(node) }
end
# Dispatch enter and leave events for DefNode nodes.
def visit_def_node(node)
listeners[:on_def_node_enter]&.each { |listener| listener.on_def_node_enter(node) }
listeners[:on_def_node_leave]&.each { |listener| listener.on_def_node_leave(node) }
end
# Dispatch enter and leave events for DefinedNode nodes.
def visit_defined_node(node)
listeners[:on_defined_node_enter]&.each { |listener| listener.on_defined_node_enter(node) }
listeners[:on_defined_node_leave]&.each { |listener| listener.on_defined_node_leave(node) }
end
# Dispatch enter and leave events for ElseNode nodes.
def visit_else_node(node)
listeners[:on_else_node_enter]&.each { |listener| listener.on_else_node_enter(node) }
listeners[:on_else_node_leave]&.each { |listener| listener.on_else_node_leave(node) }
end
# Dispatch enter and leave events for EmbeddedStatementsNode nodes.
def visit_embedded_statements_node(node)
listeners[:on_embedded_statements_node_enter]&.each { |listener| listener.on_embedded_statements_node_enter(node) }
listeners[:on_embedded_statements_node_leave]&.each { |listener| listener.on_embedded_statements_node_leave(node) }
end
# Dispatch enter and leave events for EmbeddedVariableNode nodes.
def visit_embedded_variable_node(node)
listeners[:on_embedded_variable_node_enter]&.each { |listener| listener.on_embedded_variable_node_enter(node) }
listeners[:on_embedded_variable_node_leave]&.each { |listener| listener.on_embedded_variable_node_leave(node) }
end
# Dispatch enter and leave events for EnsureNode nodes.
def visit_ensure_node(node)
listeners[:on_ensure_node_enter]&.each { |listener| listener.on_ensure_node_enter(node) }
listeners[:on_ensure_node_leave]&.each { |listener| listener.on_ensure_node_leave(node) }
end
# Dispatch enter and leave events for FalseNode nodes.
def visit_false_node(node)
listeners[:on_false_node_enter]&.each { |listener| listener.on_false_node_enter(node) }
listeners[:on_false_node_leave]&.each { |listener| listener.on_false_node_leave(node) }
end
# Dispatch enter and leave events for FindPatternNode nodes.
def visit_find_pattern_node(node)
listeners[:on_find_pattern_node_enter]&.each { |listener| listener.on_find_pattern_node_enter(node) }
listeners[:on_find_pattern_node_leave]&.each { |listener| listener.on_find_pattern_node_leave(node) }
end
# Dispatch enter and leave events for FlipFlopNode nodes.
def visit_flip_flop_node(node)
listeners[:on_flip_flop_node_enter]&.each { |listener| listener.on_flip_flop_node_enter(node) }
listeners[:on_flip_flop_node_leave]&.each { |listener| listener.on_flip_flop_node_leave(node) }
end
# Dispatch enter and leave events for FloatNode nodes.
def visit_float_node(node)
listeners[:on_float_node_enter]&.each { |listener| listener.on_float_node_enter(node) }
listeners[:on_float_node_leave]&.each { |listener| listener.on_float_node_leave(node) }
end
# Dispatch enter and leave events for ForNode nodes.
def visit_for_node(node)
listeners[:on_for_node_enter]&.each { |listener| listener.on_for_node_enter(node) }
listeners[:on_for_node_leave]&.each { |listener| listener.on_for_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingArgumentsNode nodes.
def visit_forwarding_arguments_node(node)
listeners[:on_forwarding_arguments_node_enter]&.each { |listener| listener.on_forwarding_arguments_node_enter(node) }
listeners[:on_forwarding_arguments_node_leave]&.each { |listener| listener.on_forwarding_arguments_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingParameterNode nodes.
def visit_forwarding_parameter_node(node)
listeners[:on_forwarding_parameter_node_enter]&.each { |listener| listener.on_forwarding_parameter_node_enter(node) }
listeners[:on_forwarding_parameter_node_leave]&.each { |listener| listener.on_forwarding_parameter_node_leave(node) }
end
# Dispatch enter and leave events for ForwardingSuperNode nodes.
def visit_forwarding_super_node(node)
listeners[:on_forwarding_super_node_enter]&.each { |listener| listener.on_forwarding_super_node_enter(node) }
listeners[:on_forwarding_super_node_leave]&.each { |listener| listener.on_forwarding_super_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableAndWriteNode nodes.
def visit_global_variable_and_write_node(node)
listeners[:on_global_variable_and_write_node_enter]&.each { |listener| listener.on_global_variable_and_write_node_enter(node) }
listeners[:on_global_variable_and_write_node_leave]&.each { |listener| listener.on_global_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableOperatorWriteNode nodes.
def visit_global_variable_operator_write_node(node)
listeners[:on_global_variable_operator_write_node_enter]&.each { |listener| listener.on_global_variable_operator_write_node_enter(node) }
listeners[:on_global_variable_operator_write_node_leave]&.each { |listener| listener.on_global_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableOrWriteNode nodes.
def visit_global_variable_or_write_node(node)
listeners[:on_global_variable_or_write_node_enter]&.each { |listener| listener.on_global_variable_or_write_node_enter(node) }
listeners[:on_global_variable_or_write_node_leave]&.each { |listener| listener.on_global_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableReadNode nodes.
def visit_global_variable_read_node(node)
listeners[:on_global_variable_read_node_enter]&.each { |listener| listener.on_global_variable_read_node_enter(node) }
listeners[:on_global_variable_read_node_leave]&.each { |listener| listener.on_global_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableTargetNode nodes.
def visit_global_variable_target_node(node)
listeners[:on_global_variable_target_node_enter]&.each { |listener| listener.on_global_variable_target_node_enter(node) }
listeners[:on_global_variable_target_node_leave]&.each { |listener| listener.on_global_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for GlobalVariableWriteNode nodes.
def visit_global_variable_write_node(node)
listeners[:on_global_variable_write_node_enter]&.each { |listener| listener.on_global_variable_write_node_enter(node) }
listeners[:on_global_variable_write_node_leave]&.each { |listener| listener.on_global_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for HashNode nodes.
def visit_hash_node(node)
listeners[:on_hash_node_enter]&.each { |listener| listener.on_hash_node_enter(node) }
listeners[:on_hash_node_leave]&.each { |listener| listener.on_hash_node_leave(node) }
end
# Dispatch enter and leave events for HashPatternNode nodes.
def visit_hash_pattern_node(node)
listeners[:on_hash_pattern_node_enter]&.each { |listener| listener.on_hash_pattern_node_enter(node) }
listeners[:on_hash_pattern_node_leave]&.each { |listener| listener.on_hash_pattern_node_leave(node) }
end
# Dispatch enter and leave events for IfNode nodes.
def visit_if_node(node)
listeners[:on_if_node_enter]&.each { |listener| listener.on_if_node_enter(node) }
listeners[:on_if_node_leave]&.each { |listener| listener.on_if_node_leave(node) }
end
# Dispatch enter and leave events for ImaginaryNode nodes.
def visit_imaginary_node(node)
listeners[:on_imaginary_node_enter]&.each { |listener| listener.on_imaginary_node_enter(node) }
listeners[:on_imaginary_node_leave]&.each { |listener| listener.on_imaginary_node_leave(node) }
end
# Dispatch enter and leave events for ImplicitNode nodes.
def visit_implicit_node(node)
listeners[:on_implicit_node_enter]&.each { |listener| listener.on_implicit_node_enter(node) }
listeners[:on_implicit_node_leave]&.each { |listener| listener.on_implicit_node_leave(node) }
end
# Dispatch enter and leave events for ImplicitRestNode nodes.
def visit_implicit_rest_node(node)
listeners[:on_implicit_rest_node_enter]&.each { |listener| listener.on_implicit_rest_node_enter(node) }
listeners[:on_implicit_rest_node_leave]&.each { |listener| listener.on_implicit_rest_node_leave(node) }
end
# Dispatch enter and leave events for InNode nodes.
def visit_in_node(node)
listeners[:on_in_node_enter]&.each { |listener| listener.on_in_node_enter(node) }
listeners[:on_in_node_leave]&.each { |listener| listener.on_in_node_leave(node) }
end
# Dispatch enter and leave events for IndexAndWriteNode nodes.
def visit_index_and_write_node(node)
listeners[:on_index_and_write_node_enter]&.each { |listener| listener.on_index_and_write_node_enter(node) }
listeners[:on_index_and_write_node_leave]&.each { |listener| listener.on_index_and_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexOperatorWriteNode nodes.
def visit_index_operator_write_node(node)
listeners[:on_index_operator_write_node_enter]&.each { |listener| listener.on_index_operator_write_node_enter(node) }
listeners[:on_index_operator_write_node_leave]&.each { |listener| listener.on_index_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexOrWriteNode nodes.
def visit_index_or_write_node(node)
listeners[:on_index_or_write_node_enter]&.each { |listener| listener.on_index_or_write_node_enter(node) }
listeners[:on_index_or_write_node_leave]&.each { |listener| listener.on_index_or_write_node_leave(node) }
end
# Dispatch enter and leave events for IndexTargetNode nodes.
def visit_index_target_node(node)
listeners[:on_index_target_node_enter]&.each { |listener| listener.on_index_target_node_enter(node) }
listeners[:on_index_target_node_leave]&.each { |listener| listener.on_index_target_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableAndWriteNode nodes.
def visit_instance_variable_and_write_node(node)
listeners[:on_instance_variable_and_write_node_enter]&.each { |listener| listener.on_instance_variable_and_write_node_enter(node) }
listeners[:on_instance_variable_and_write_node_leave]&.each { |listener| listener.on_instance_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableOperatorWriteNode nodes.
def visit_instance_variable_operator_write_node(node)
listeners[:on_instance_variable_operator_write_node_enter]&.each { |listener| listener.on_instance_variable_operator_write_node_enter(node) }
listeners[:on_instance_variable_operator_write_node_leave]&.each { |listener| listener.on_instance_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableOrWriteNode nodes.
def visit_instance_variable_or_write_node(node)
listeners[:on_instance_variable_or_write_node_enter]&.each { |listener| listener.on_instance_variable_or_write_node_enter(node) }
listeners[:on_instance_variable_or_write_node_leave]&.each { |listener| listener.on_instance_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableReadNode nodes.
def visit_instance_variable_read_node(node)
listeners[:on_instance_variable_read_node_enter]&.each { |listener| listener.on_instance_variable_read_node_enter(node) }
listeners[:on_instance_variable_read_node_leave]&.each { |listener| listener.on_instance_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableTargetNode nodes.
def visit_instance_variable_target_node(node)
listeners[:on_instance_variable_target_node_enter]&.each { |listener| listener.on_instance_variable_target_node_enter(node) }
listeners[:on_instance_variable_target_node_leave]&.each { |listener| listener.on_instance_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for InstanceVariableWriteNode nodes.
def visit_instance_variable_write_node(node)
listeners[:on_instance_variable_write_node_enter]&.each { |listener| listener.on_instance_variable_write_node_enter(node) }
listeners[:on_instance_variable_write_node_leave]&.each { |listener| listener.on_instance_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for IntegerNode nodes.
def visit_integer_node(node)
listeners[:on_integer_node_enter]&.each { |listener| listener.on_integer_node_enter(node) }
listeners[:on_integer_node_leave]&.each { |listener| listener.on_integer_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedMatchLastLineNode nodes.
def visit_interpolated_match_last_line_node(node)
listeners[:on_interpolated_match_last_line_node_enter]&.each { |listener| listener.on_interpolated_match_last_line_node_enter(node) }
listeners[:on_interpolated_match_last_line_node_leave]&.each { |listener| listener.on_interpolated_match_last_line_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedRegularExpressionNode nodes.
def visit_interpolated_regular_expression_node(node)
listeners[:on_interpolated_regular_expression_node_enter]&.each { |listener| listener.on_interpolated_regular_expression_node_enter(node) }
listeners[:on_interpolated_regular_expression_node_leave]&.each { |listener| listener.on_interpolated_regular_expression_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedStringNode nodes.
def visit_interpolated_string_node(node)
listeners[:on_interpolated_string_node_enter]&.each { |listener| listener.on_interpolated_string_node_enter(node) }
listeners[:on_interpolated_string_node_leave]&.each { |listener| listener.on_interpolated_string_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedSymbolNode nodes.
def visit_interpolated_symbol_node(node)
listeners[:on_interpolated_symbol_node_enter]&.each { |listener| listener.on_interpolated_symbol_node_enter(node) }
listeners[:on_interpolated_symbol_node_leave]&.each { |listener| listener.on_interpolated_symbol_node_leave(node) }
end
# Dispatch enter and leave events for InterpolatedXStringNode nodes.
def visit_interpolated_x_string_node(node)
listeners[:on_interpolated_x_string_node_enter]&.each { |listener| listener.on_interpolated_x_string_node_enter(node) }
listeners[:on_interpolated_x_string_node_leave]&.each { |listener| listener.on_interpolated_x_string_node_leave(node) }
end
# Dispatch enter and leave events for KeywordHashNode nodes.
def visit_keyword_hash_node(node)
listeners[:on_keyword_hash_node_enter]&.each { |listener| listener.on_keyword_hash_node_enter(node) }
listeners[:on_keyword_hash_node_leave]&.each { |listener| listener.on_keyword_hash_node_leave(node) }
end
# Dispatch enter and leave events for KeywordRestParameterNode nodes.
def visit_keyword_rest_parameter_node(node)
listeners[:on_keyword_rest_parameter_node_enter]&.each { |listener| listener.on_keyword_rest_parameter_node_enter(node) }
listeners[:on_keyword_rest_parameter_node_leave]&.each { |listener| listener.on_keyword_rest_parameter_node_leave(node) }
end
# Dispatch enter and leave events for LambdaNode nodes.
def visit_lambda_node(node)
listeners[:on_lambda_node_enter]&.each { |listener| listener.on_lambda_node_enter(node) }
listeners[:on_lambda_node_leave]&.each { |listener| listener.on_lambda_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableAndWriteNode nodes.
def visit_local_variable_and_write_node(node)
listeners[:on_local_variable_and_write_node_enter]&.each { |listener| listener.on_local_variable_and_write_node_enter(node) }
listeners[:on_local_variable_and_write_node_leave]&.each { |listener| listener.on_local_variable_and_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableOperatorWriteNode nodes.
def visit_local_variable_operator_write_node(node)
listeners[:on_local_variable_operator_write_node_enter]&.each { |listener| listener.on_local_variable_operator_write_node_enter(node) }
listeners[:on_local_variable_operator_write_node_leave]&.each { |listener| listener.on_local_variable_operator_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableOrWriteNode nodes.
def visit_local_variable_or_write_node(node)
listeners[:on_local_variable_or_write_node_enter]&.each { |listener| listener.on_local_variable_or_write_node_enter(node) }
listeners[:on_local_variable_or_write_node_leave]&.each { |listener| listener.on_local_variable_or_write_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableReadNode nodes.
def visit_local_variable_read_node(node)
listeners[:on_local_variable_read_node_enter]&.each { |listener| listener.on_local_variable_read_node_enter(node) }
listeners[:on_local_variable_read_node_leave]&.each { |listener| listener.on_local_variable_read_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableTargetNode nodes.
def visit_local_variable_target_node(node)
listeners[:on_local_variable_target_node_enter]&.each { |listener| listener.on_local_variable_target_node_enter(node) }
listeners[:on_local_variable_target_node_leave]&.each { |listener| listener.on_local_variable_target_node_leave(node) }
end
# Dispatch enter and leave events for LocalVariableWriteNode nodes.
def visit_local_variable_write_node(node)
listeners[:on_local_variable_write_node_enter]&.each { |listener| listener.on_local_variable_write_node_enter(node) }
listeners[:on_local_variable_write_node_leave]&.each { |listener| listener.on_local_variable_write_node_leave(node) }
end
# Dispatch enter and leave events for MatchLastLineNode nodes.
def visit_match_last_line_node(node)
listeners[:on_match_last_line_node_enter]&.each { |listener| listener.on_match_last_line_node_enter(node) }
listeners[:on_match_last_line_node_leave]&.each { |listener| listener.on_match_last_line_node_leave(node) }
end
# Dispatch enter and leave events for MatchPredicateNode nodes.
def visit_match_predicate_node(node)
listeners[:on_match_predicate_node_enter]&.each { |listener| listener.on_match_predicate_node_enter(node) }
listeners[:on_match_predicate_node_leave]&.each { |listener| listener.on_match_predicate_node_leave(node) }
end
# Dispatch enter and leave events for MatchRequiredNode nodes.
def visit_match_required_node(node)
listeners[:on_match_required_node_enter]&.each { |listener| listener.on_match_required_node_enter(node) }
listeners[:on_match_required_node_leave]&.each { |listener| listener.on_match_required_node_leave(node) }
end
# Dispatch enter and leave events for MatchWriteNode nodes.
def visit_match_write_node(node)
listeners[:on_match_write_node_enter]&.each { |listener| listener.on_match_write_node_enter(node) }
listeners[:on_match_write_node_leave]&.each { |listener| listener.on_match_write_node_leave(node) }
end
# Dispatch enter and leave events for MissingNode nodes.
def visit_missing_node(node)
listeners[:on_missing_node_enter]&.each { |listener| listener.on_missing_node_enter(node) }
listeners[:on_missing_node_leave]&.each { |listener| listener.on_missing_node_leave(node) }
end
# Dispatch enter and leave events for ModuleNode nodes.
def visit_module_node(node)
listeners[:on_module_node_enter]&.each { |listener| listener.on_module_node_enter(node) }
listeners[:on_module_node_leave]&.each { |listener| listener.on_module_node_leave(node) }
end
# Dispatch enter and leave events for MultiTargetNode nodes.
def visit_multi_target_node(node)
listeners[:on_multi_target_node_enter]&.each { |listener| listener.on_multi_target_node_enter(node) }
listeners[:on_multi_target_node_leave]&.each { |listener| listener.on_multi_target_node_leave(node) }
end
# Dispatch enter and leave events for MultiWriteNode nodes.
def visit_multi_write_node(node)
listeners[:on_multi_write_node_enter]&.each { |listener| listener.on_multi_write_node_enter(node) }
listeners[:on_multi_write_node_leave]&.each { |listener| listener.on_multi_write_node_leave(node) }
end
# Dispatch enter and leave events for NextNode nodes.
def visit_next_node(node)
listeners[:on_next_node_enter]&.each { |listener| listener.on_next_node_enter(node) }
listeners[:on_next_node_leave]&.each { |listener| listener.on_next_node_leave(node) }
end
# Dispatch enter and leave events for NilNode nodes.
def visit_nil_node(node)
listeners[:on_nil_node_enter]&.each { |listener| listener.on_nil_node_enter(node) }
listeners[:on_nil_node_leave]&.each { |listener| listener.on_nil_node_leave(node) }
end
# Dispatch enter and leave events for NoKeywordsParameterNode nodes.
def visit_no_keywords_parameter_node(node)
listeners[:on_no_keywords_parameter_node_enter]&.each { |listener| listener.on_no_keywords_parameter_node_enter(node) }
listeners[:on_no_keywords_parameter_node_leave]&.each { |listener| listener.on_no_keywords_parameter_node_leave(node) }
end
# Dispatch enter and leave events for NumberedParametersNode nodes.
def visit_numbered_parameters_node(node)
listeners[:on_numbered_parameters_node_enter]&.each { |listener| listener.on_numbered_parameters_node_enter(node) }
listeners[:on_numbered_parameters_node_leave]&.each { |listener| listener.on_numbered_parameters_node_leave(node) }
end
# Dispatch enter and leave events for NumberedReferenceReadNode nodes.
def visit_numbered_reference_read_node(node)
listeners[:on_numbered_reference_read_node_enter]&.each { |listener| listener.on_numbered_reference_read_node_enter(node) }
listeners[:on_numbered_reference_read_node_leave]&.each { |listener| listener.on_numbered_reference_read_node_leave(node) }
end
# Dispatch enter and leave events for OptionalKeywordParameterNode nodes.
def visit_optional_keyword_parameter_node(node)
listeners[:on_optional_keyword_parameter_node_enter]&.each { |listener| listener.on_optional_keyword_parameter_node_enter(node) }
listeners[:on_optional_keyword_parameter_node_leave]&.each { |listener| listener.on_optional_keyword_parameter_node_leave(node) }
end
# Dispatch enter and leave events for OptionalParameterNode nodes.
def visit_optional_parameter_node(node)
listeners[:on_optional_parameter_node_enter]&.each { |listener| listener.on_optional_parameter_node_enter(node) }
listeners[:on_optional_parameter_node_leave]&.each { |listener| listener.on_optional_parameter_node_leave(node) }
end
# Dispatch enter and leave events for OrNode nodes.
def visit_or_node(node)
listeners[:on_or_node_enter]&.each { |listener| listener.on_or_node_enter(node) }
listeners[:on_or_node_leave]&.each { |listener| listener.on_or_node_leave(node) }
end
# Dispatch enter and leave events for ParametersNode nodes.
def visit_parameters_node(node)
listeners[:on_parameters_node_enter]&.each { |listener| listener.on_parameters_node_enter(node) }
listeners[:on_parameters_node_leave]&.each { |listener| listener.on_parameters_node_leave(node) }
end
# Dispatch enter and leave events for ParenthesesNode nodes.
def visit_parentheses_node(node)
listeners[:on_parentheses_node_enter]&.each { |listener| listener.on_parentheses_node_enter(node) }
listeners[:on_parentheses_node_leave]&.each { |listener| listener.on_parentheses_node_leave(node) }
end
# Dispatch enter and leave events for PinnedExpressionNode nodes.
def visit_pinned_expression_node(node)
listeners[:on_pinned_expression_node_enter]&.each { |listener| listener.on_pinned_expression_node_enter(node) }
listeners[:on_pinned_expression_node_leave]&.each { |listener| listener.on_pinned_expression_node_leave(node) }
end
# Dispatch enter and leave events for PinnedVariableNode nodes.
def visit_pinned_variable_node(node)
listeners[:on_pinned_variable_node_enter]&.each { |listener| listener.on_pinned_variable_node_enter(node) }
listeners[:on_pinned_variable_node_leave]&.each { |listener| listener.on_pinned_variable_node_leave(node) }
end
# Dispatch enter and leave events for PostExecutionNode nodes.
def visit_post_execution_node(node)
listeners[:on_post_execution_node_enter]&.each { |listener| listener.on_post_execution_node_enter(node) }
listeners[:on_post_execution_node_leave]&.each { |listener| listener.on_post_execution_node_leave(node) }
end
# Dispatch enter and leave events for PreExecutionNode nodes.
def visit_pre_execution_node(node)
listeners[:on_pre_execution_node_enter]&.each { |listener| listener.on_pre_execution_node_enter(node) }
listeners[:on_pre_execution_node_leave]&.each { |listener| listener.on_pre_execution_node_leave(node) }
end
# Dispatch enter and leave events for ProgramNode nodes.
def visit_program_node(node)
listeners[:on_program_node_enter]&.each { |listener| listener.on_program_node_enter(node) }
listeners[:on_program_node_leave]&.each { |listener| listener.on_program_node_leave(node) }
end
# Dispatch enter and leave events for RangeNode nodes.
def visit_range_node(node)
listeners[:on_range_node_enter]&.each { |listener| listener.on_range_node_enter(node) }
listeners[:on_range_node_leave]&.each { |listener| listener.on_range_node_leave(node) }
end
# Dispatch enter and leave events for RationalNode nodes.
def visit_rational_node(node)
listeners[:on_rational_node_enter]&.each { |listener| listener.on_rational_node_enter(node) }
listeners[:on_rational_node_leave]&.each { |listener| listener.on_rational_node_leave(node) }
end
# Dispatch enter and leave events for RedoNode nodes.
def visit_redo_node(node)
listeners[:on_redo_node_enter]&.each { |listener| listener.on_redo_node_enter(node) }
listeners[:on_redo_node_leave]&.each { |listener| listener.on_redo_node_leave(node) }
end
# Dispatch enter and leave events for RegularExpressionNode nodes.
def visit_regular_expression_node(node)
listeners[:on_regular_expression_node_enter]&.each { |listener| listener.on_regular_expression_node_enter(node) }
listeners[:on_regular_expression_node_leave]&.each { |listener| listener.on_regular_expression_node_leave(node) }
end
# Dispatch enter and leave events for RequiredKeywordParameterNode nodes.
def visit_required_keyword_parameter_node(node)
listeners[:on_required_keyword_parameter_node_enter]&.each { |listener| listener.on_required_keyword_parameter_node_enter(node) }
listeners[:on_required_keyword_parameter_node_leave]&.each { |listener| listener.on_required_keyword_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RequiredParameterNode nodes.
def visit_required_parameter_node(node)
listeners[:on_required_parameter_node_enter]&.each { |listener| listener.on_required_parameter_node_enter(node) }
listeners[:on_required_parameter_node_leave]&.each { |listener| listener.on_required_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RescueModifierNode nodes.
def visit_rescue_modifier_node(node)
listeners[:on_rescue_modifier_node_enter]&.each { |listener| listener.on_rescue_modifier_node_enter(node) }
listeners[:on_rescue_modifier_node_leave]&.each { |listener| listener.on_rescue_modifier_node_leave(node) }
end
# Dispatch enter and leave events for RescueNode nodes.
def visit_rescue_node(node)
listeners[:on_rescue_node_enter]&.each { |listener| listener.on_rescue_node_enter(node) }
listeners[:on_rescue_node_leave]&.each { |listener| listener.on_rescue_node_leave(node) }
end
# Dispatch enter and leave events for RestParameterNode nodes.
def visit_rest_parameter_node(node)
listeners[:on_rest_parameter_node_enter]&.each { |listener| listener.on_rest_parameter_node_enter(node) }
listeners[:on_rest_parameter_node_leave]&.each { |listener| listener.on_rest_parameter_node_leave(node) }
end
# Dispatch enter and leave events for RetryNode nodes.
def visit_retry_node(node)
listeners[:on_retry_node_enter]&.each { |listener| listener.on_retry_node_enter(node) }
listeners[:on_retry_node_leave]&.each { |listener| listener.on_retry_node_leave(node) }
end
# Dispatch enter and leave events for ReturnNode nodes.
def visit_return_node(node)
listeners[:on_return_node_enter]&.each { |listener| listener.on_return_node_enter(node) }
listeners[:on_return_node_leave]&.each { |listener| listener.on_return_node_leave(node) }
end
# Dispatch enter and leave events for SelfNode nodes.
def visit_self_node(node)
listeners[:on_self_node_enter]&.each { |listener| listener.on_self_node_enter(node) }
listeners[:on_self_node_leave]&.each { |listener| listener.on_self_node_leave(node) }
end
# Dispatch enter and leave events for SingletonClassNode nodes.
def visit_singleton_class_node(node)
listeners[:on_singleton_class_node_enter]&.each { |listener| listener.on_singleton_class_node_enter(node) }
listeners[:on_singleton_class_node_leave]&.each { |listener| listener.on_singleton_class_node_leave(node) }
end
# Dispatch enter and leave events for SourceEncodingNode nodes.
def visit_source_encoding_node(node)
listeners[:on_source_encoding_node_enter]&.each { |listener| listener.on_source_encoding_node_enter(node) }
listeners[:on_source_encoding_node_leave]&.each { |listener| listener.on_source_encoding_node_leave(node) }
end
# Dispatch enter and leave events for SourceFileNode nodes.
def visit_source_file_node(node)
listeners[:on_source_file_node_enter]&.each { |listener| listener.on_source_file_node_enter(node) }
listeners[:on_source_file_node_leave]&.each { |listener| listener.on_source_file_node_leave(node) }
end
# Dispatch enter and leave events for SourceLineNode nodes.
def visit_source_line_node(node)
listeners[:on_source_line_node_enter]&.each { |listener| listener.on_source_line_node_enter(node) }
listeners[:on_source_line_node_leave]&.each { |listener| listener.on_source_line_node_leave(node) }
end
# Dispatch enter and leave events for SplatNode nodes.
def visit_splat_node(node)
listeners[:on_splat_node_enter]&.each { |listener| listener.on_splat_node_enter(node) }
listeners[:on_splat_node_leave]&.each { |listener| listener.on_splat_node_leave(node) }
end
# Dispatch enter and leave events for StatementsNode nodes.
def visit_statements_node(node)
listeners[:on_statements_node_enter]&.each { |listener| listener.on_statements_node_enter(node) }
listeners[:on_statements_node_leave]&.each { |listener| listener.on_statements_node_leave(node) }
end
# Dispatch enter and leave events for StringNode nodes.
def visit_string_node(node)
listeners[:on_string_node_enter]&.each { |listener| listener.on_string_node_enter(node) }
listeners[:on_string_node_leave]&.each { |listener| listener.on_string_node_leave(node) }
end
# Dispatch enter and leave events for SuperNode nodes.
def visit_super_node(node)
listeners[:on_super_node_enter]&.each { |listener| listener.on_super_node_enter(node) }
listeners[:on_super_node_leave]&.each { |listener| listener.on_super_node_leave(node) }
end
# Dispatch enter and leave events for SymbolNode nodes.
def visit_symbol_node(node)
listeners[:on_symbol_node_enter]&.each { |listener| listener.on_symbol_node_enter(node) }
listeners[:on_symbol_node_leave]&.each { |listener| listener.on_symbol_node_leave(node) }
end
# Dispatch enter and leave events for TrueNode nodes.
def visit_true_node(node)
listeners[:on_true_node_enter]&.each { |listener| listener.on_true_node_enter(node) }
listeners[:on_true_node_leave]&.each { |listener| listener.on_true_node_leave(node) }
end
# Dispatch enter and leave events for UndefNode nodes.
def visit_undef_node(node)
listeners[:on_undef_node_enter]&.each { |listener| listener.on_undef_node_enter(node) }
listeners[:on_undef_node_leave]&.each { |listener| listener.on_undef_node_leave(node) }
end
# Dispatch enter and leave events for UnlessNode nodes.
def visit_unless_node(node)
listeners[:on_unless_node_enter]&.each { |listener| listener.on_unless_node_enter(node) }
listeners[:on_unless_node_leave]&.each { |listener| listener.on_unless_node_leave(node) }
end
# Dispatch enter and leave events for UntilNode nodes.
def visit_until_node(node)
listeners[:on_until_node_enter]&.each { |listener| listener.on_until_node_enter(node) }
listeners[:on_until_node_leave]&.each { |listener| listener.on_until_node_leave(node) }
end
# Dispatch enter and leave events for WhenNode nodes.
def visit_when_node(node)
listeners[:on_when_node_enter]&.each { |listener| listener.on_when_node_enter(node) }
listeners[:on_when_node_leave]&.each { |listener| listener.on_when_node_leave(node) }
end
# Dispatch enter and leave events for WhileNode nodes.
def visit_while_node(node)
listeners[:on_while_node_enter]&.each { |listener| listener.on_while_node_enter(node) }
listeners[:on_while_node_leave]&.each { |listener| listener.on_while_node_leave(node) }
end
# Dispatch enter and leave events for XStringNode nodes.
def visit_x_string_node(node)
listeners[:on_x_string_node_enter]&.each { |listener| listener.on_x_string_node_enter(node) }
listeners[:on_x_string_node_leave]&.each { |listener| listener.on_x_string_node_leave(node) }
end
# Dispatch enter and leave events for YieldNode nodes.
def visit_yield_node(node)
listeners[:on_yield_node_enter]&.each { |listener| listener.on_yield_node_enter(node) }
listeners[:on_yield_node_leave]&.each { |listener| listener.on_yield_node_leave(node) }
end
end
private_constant :DispatchOnce
end
end
share/ruby/prism/node_inspector.rb 0000644 00000004101 15173517735 0013333 0 ustar 00 # frozen_string_literal: true
module Prism
# This object is responsible for generating the output for the inspect method
# implementations of child nodes.
class NodeInspector # :nodoc:
attr_reader :prefix, :output
def initialize(prefix = "")
@prefix = prefix
@output = +""
end
# Appends a line to the output with the current prefix.
def <<(line)
output << "#{prefix}#{line}"
end
# This generates a string that is used as the header of the inspect output
# for any given node.
def header(node)
output = +"@ #{node.class.name.split("::").last} ("
output << "location: (#{node.location.start_line},#{node.location.start_column})-(#{node.location.end_line},#{node.location.end_column})"
output << ", newline: true" if node.newline?
output << ")\n"
output
end
# Generates a string that represents a list of nodes. It handles properly
# using the box drawing characters to make the output look nice.
def list(prefix, nodes)
output = +"(length: #{nodes.length})\n"
last_index = nodes.length - 1
nodes.each_with_index do |node, index|
pointer, preadd = (index == last_index) ? ["└── ", " "] : ["├── ", "│ "]
node_prefix = "#{prefix}#{preadd}"
output << node.inspect(NodeInspector.new(node_prefix)).sub(node_prefix, "#{prefix}#{pointer}")
end
output
end
# Generates a string that represents a location field on a node.
def location(value)
if value
"(#{value.start_line},#{value.start_column})-(#{value.end_line},#{value.end_column}) = #{value.slice.inspect}"
else
"∅"
end
end
# Generates a string that represents a child node.
def child_node(node, append)
node.inspect(child_inspector(append)).delete_prefix(prefix)
end
# Returns a new inspector that can be used to inspect a child node.
def child_inspector(append)
NodeInspector.new("#{prefix}#{append}")
end
# Returns the output as a string.
def to_str
output
end
end
end
share/ruby/prism/pack.rb 0000644 00000013420 15173517735 0011242 0 ustar 00 # frozen_string_literal: true
module Prism
# A parser for the pack template language.
module Pack
%i[
SPACE
COMMENT
INTEGER
UTF8
BER
FLOAT
STRING_SPACE_PADDED
STRING_NULL_PADDED
STRING_NULL_TERMINATED
STRING_MSB
STRING_LSB
STRING_HEX_HIGH
STRING_HEX_LOW
STRING_UU
STRING_MIME
STRING_BASE64
STRING_FIXED
STRING_POINTER
MOVE
BACK
NULL
UNSIGNED
SIGNED
SIGNED_NA
AGNOSTIC_ENDIAN
LITTLE_ENDIAN
BIG_ENDIAN
NATIVE_ENDIAN
ENDIAN_NA
SIZE_SHORT
SIZE_INT
SIZE_LONG
SIZE_LONG_LONG
SIZE_8
SIZE_16
SIZE_32
SIZE_64
SIZE_P
SIZE_NA
LENGTH_FIXED
LENGTH_MAX
LENGTH_RELATIVE
LENGTH_NA
].each do |const|
const_set(const, const)
end
# A directive in the pack template language.
class Directive
# A symbol representing the version of Ruby.
attr_reader :version
# A symbol representing whether or not we are packing or unpacking.
attr_reader :variant
# A byteslice of the source string that this directive represents.
attr_reader :source
# The type of the directive.
attr_reader :type
# The type of signedness of the directive.
attr_reader :signed
# The type of endianness of the directive.
attr_reader :endian
# The size of the directive.
attr_reader :size
# The length type of this directive (used for integers).
attr_reader :length_type
# The length of this directive (used for integers).
attr_reader :length
# Initialize a new directive with the given values.
def initialize(version, variant, source, type, signed, endian, size, length_type, length)
@version = version
@variant = variant
@source = source
@type = type
@signed = signed
@endian = endian
@size = size
@length_type = length_type
@length = length
end
# The descriptions of the various types of endianness.
ENDIAN_DESCRIPTIONS = {
AGNOSTIC_ENDIAN: "agnostic",
LITTLE_ENDIAN: "little-endian (VAX)",
BIG_ENDIAN: "big-endian (network)",
NATIVE_ENDIAN: "native-endian",
ENDIAN_NA: "n/a"
}
# The descriptions of the various types of signedness.
SIGNED_DESCRIPTIONS = {
UNSIGNED: "unsigned",
SIGNED: "signed",
SIGNED_NA: "n/a"
}
# The descriptions of the various types of sizes.
SIZE_DESCRIPTIONS = {
SIZE_SHORT: "short",
SIZE_INT: "int-width",
SIZE_LONG: "long",
SIZE_LONG_LONG: "long long",
SIZE_8: "8-bit",
SIZE_16: "16-bit",
SIZE_32: "32-bit",
SIZE_64: "64-bit",
SIZE_P: "pointer-width"
}
# Provide a human-readable description of the directive.
def describe
case type
when SPACE
"whitespace"
when COMMENT
"comment"
when INTEGER
if size == SIZE_8
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} integer"
else
base = "#{SIGNED_DESCRIPTIONS[signed]} #{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} integer"
end
case length_type
when LENGTH_FIXED
if length > 1
base + ", x#{length}"
else
base
end
when LENGTH_MAX
base + ", as many as possible"
end
when UTF8
"UTF-8 character"
when BER
"BER-compressed integer"
when FLOAT
"#{SIZE_DESCRIPTIONS[size]} #{ENDIAN_DESCRIPTIONS[endian]} float"
when STRING_SPACE_PADDED
"arbitrary binary string (space padded)"
when STRING_NULL_PADDED
"arbitrary binary string (null padded, count is width)"
when STRING_NULL_TERMINATED
"arbitrary binary string (null padded, count is width), except that null is added with *"
when STRING_MSB
"bit string (MSB first)"
when STRING_LSB
"bit string (LSB first)"
when STRING_HEX_HIGH
"hex string (high nibble first)"
when STRING_HEX_LOW
"hex string (low nibble first)"
when STRING_UU
"UU-encoded string"
when STRING_MIME
"quoted printable, MIME encoding"
when STRING_BASE64
"base64 encoded string"
when STRING_FIXED
"pointer to a structure (fixed-length string)"
when STRING_POINTER
"pointer to a null-terminated string"
when MOVE
"move to absolute position"
when BACK
"back up a byte"
when NULL
"null byte"
else
raise
end
end
end
# The result of parsing a pack template.
class Format
# A list of the directives in the template.
attr_reader :directives
# The encoding of the template.
attr_reader :encoding
# Create a new Format with the given directives and encoding.
def initialize(directives, encoding)
@directives = directives
@encoding = encoding
end
# Provide a human-readable description of the format.
def describe
source_width = directives.map { |d| d.source.inspect.length }.max
directive_lines = directives.map do |directive|
if directive.type == SPACE
source = directive.source.inspect
else
source = directive.source
end
" #{source.ljust(source_width)} #{directive.describe}"
end
(["Directives:"] + directive_lines + ["Encoding:", " #{encoding}"]).join("\n")
end
end
end
end
share/ruby/prism/node.rb 0000644 00002176644 15173517735 0011275 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/node.rb.erb
if you are looking to modify the template
=end
module Prism
# This represents a node in the tree. It is the parent class of all of the
# various node types.
class Node
# A Location instance that represents the location of this node in the
# source.
attr_reader :location
def newline? # :nodoc:
@newline ? true : false
end
def set_newline_flag(newline_marked) # :nodoc:
line = location.start_line
unless newline_marked[line]
newline_marked[line] = true
@newline = true
end
end
# Slice the location of the node from the source.
def slice
location.slice
end
# Similar to inspect, but respects the current level of indentation given by
# the pretty print object.
def pretty_print(q)
q.seplist(inspect.chomp.each_line, -> { q.breakable }) do |line|
q.text(line.chomp)
end
q.current_group.break
end
# Convert this node into a graphviz dot graph string.
def to_dot
DotVisitor.new.tap { |visitor| accept(visitor) }.to_dot
end
end
# Represents the use of the `alias` keyword to alias a global variable.
#
# alias $foo $bar
# ^^^^^^^^^^^^^^^
class AliasGlobalVariableNode < Node
# attr_reader new_name: Node
attr_reader :new_name
# attr_reader old_name: Node
attr_reader :old_name
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (new_name: Node, old_name: Node, keyword_loc: Location, location: Location) -> void
def initialize(new_name, old_name, keyword_loc, location)
@new_name = new_name
@old_name = old_name
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_alias_global_variable_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[new_name, old_name]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[new_name, old_name]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[new_name, old_name, keyword_loc]
end
# def copy: (**params) -> AliasGlobalVariableNode
def copy(**params)
AliasGlobalVariableNode.new(
params.fetch(:new_name) { new_name },
params.fetch(:old_name) { old_name },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ new_name: new_name, old_name: old_name, keyword_loc: keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── new_name:\n"
inspector << inspector.child_node(new_name, "│ ")
inspector << "├── old_name:\n"
inspector << inspector.child_node(old_name, "│ ")
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:alias_global_variable_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:alias_global_variable_node
end
end
# Represents the use of the `alias` keyword to alias a method.
#
# alias foo bar
# ^^^^^^^^^^^^^
class AliasMethodNode < Node
# attr_reader new_name: Node
attr_reader :new_name
# attr_reader old_name: Node
attr_reader :old_name
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (new_name: Node, old_name: Node, keyword_loc: Location, location: Location) -> void
def initialize(new_name, old_name, keyword_loc, location)
@new_name = new_name
@old_name = old_name
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_alias_method_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[new_name, old_name]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[new_name, old_name]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[new_name, old_name, keyword_loc]
end
# def copy: (**params) -> AliasMethodNode
def copy(**params)
AliasMethodNode.new(
params.fetch(:new_name) { new_name },
params.fetch(:old_name) { old_name },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ new_name: new_name, old_name: old_name, keyword_loc: keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── new_name:\n"
inspector << inspector.child_node(new_name, "│ ")
inspector << "├── old_name:\n"
inspector << inspector.child_node(old_name, "│ ")
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:alias_method_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:alias_method_node
end
end
# Represents an alternation pattern in pattern matching.
#
# foo => bar | baz
# ^^^^^^^^^
class AlternationPatternNode < Node
# attr_reader left: Node
attr_reader :left
# attr_reader right: Node
attr_reader :right
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (left: Node, right: Node, operator_loc: Location, location: Location) -> void
def initialize(left, right, operator_loc, location)
@left = left
@right = right
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_alternation_pattern_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[left, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[left, right]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[left, right, operator_loc]
end
# def copy: (**params) -> AlternationPatternNode
def copy(**params)
AlternationPatternNode.new(
params.fetch(:left) { left },
params.fetch(:right) { right },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ left: left, right: right, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── left:\n"
inspector << inspector.child_node(left, "│ ")
inspector << "├── right:\n"
inspector << inspector.child_node(right, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:alternation_pattern_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:alternation_pattern_node
end
end
# Represents the use of the `&&` operator or the `and` keyword.
#
# left and right
# ^^^^^^^^^^^^^^
class AndNode < Node
# attr_reader left: Node
attr_reader :left
# attr_reader right: Node
attr_reader :right
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (left: Node, right: Node, operator_loc: Location, location: Location) -> void
def initialize(left, right, operator_loc, location)
@left = left
@right = right
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_and_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[left, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[left, right]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[left, right, operator_loc]
end
# def copy: (**params) -> AndNode
def copy(**params)
AndNode.new(
params.fetch(:left) { left },
params.fetch(:right) { right },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ left: left, right: right, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── left:\n"
inspector << inspector.child_node(left, "│ ")
inspector << "├── right:\n"
inspector << inspector.child_node(right, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:and_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:and_node
end
end
# Represents a set of arguments to a method or a keyword.
#
# return foo, bar, baz
# ^^^^^^^^^^^^^
class ArgumentsNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader arguments: Array[Node]
attr_reader :arguments
# def initialize: (flags: Integer, arguments: Array[Node], location: Location) -> void
def initialize(flags, arguments, location)
@flags = flags
@arguments = arguments
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_arguments_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*arguments]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*arguments]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*arguments]
end
# def copy: (**params) -> ArgumentsNode
def copy(**params)
ArgumentsNode.new(
params.fetch(:flags) { flags },
params.fetch(:arguments) { arguments },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, arguments: arguments, location: location }
end
# def contains_keyword_splat?: () -> bool
def contains_keyword_splat?
flags.anybits?(ArgumentsNodeFlags::CONTAINS_KEYWORD_SPLAT)
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("contains_keyword_splat" if contains_keyword_splat?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "└── arguments: #{inspector.list("#{inspector.prefix} ", arguments)}"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:arguments_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:arguments_node
end
end
# Represents an array literal. This can be a regular array using brackets or
# a special array using % like %w or %i.
#
# [1, 2, 3]
# ^^^^^^^^^
class ArrayNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader elements: Array[Node]
attr_reader :elements
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (flags: Integer, elements: Array[Node], opening_loc: Location?, closing_loc: Location?, location: Location) -> void
def initialize(flags, elements, opening_loc, closing_loc, location)
@flags = flags
@elements = elements
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_array_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*elements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*elements]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*elements, *opening_loc, *closing_loc]
end
# def copy: (**params) -> ArrayNode
def copy(**params)
ArrayNode.new(
params.fetch(:flags) { flags },
params.fetch(:elements) { elements },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, elements: elements, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def contains_splat?: () -> bool
def contains_splat?
flags.anybits?(ArrayNodeFlags::CONTAINS_SPLAT)
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("contains_splat" if contains_splat?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── elements: #{inspector.list("#{inspector.prefix}│ ", elements)}"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:array_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:array_node
end
end
# Represents an array pattern in pattern matching.
#
# foo in 1, 2
# ^^^^^^^^^^^
#
# foo in [1, 2]
# ^^^^^^^^^^^^^
#
# foo in *1
# ^^^^^^^^^
#
# foo in Bar[]
# ^^^^^^^^^^^^
#
# foo in Bar[1, 2, 3]
# ^^^^^^^^^^^^^^^^^^^
class ArrayPatternNode < Node
# attr_reader constant: Node?
attr_reader :constant
# attr_reader requireds: Array[Node]
attr_reader :requireds
# attr_reader rest: Node?
attr_reader :rest
# attr_reader posts: Array[Node]
attr_reader :posts
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (constant: Node?, requireds: Array[Node], rest: Node?, posts: Array[Node], opening_loc: Location?, closing_loc: Location?, location: Location) -> void
def initialize(constant, requireds, rest, posts, opening_loc, closing_loc, location)
@constant = constant
@requireds = requireds
@rest = rest
@posts = posts
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_array_pattern_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[constant, *requireds, rest, *posts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << constant if constant
compact.concat(requireds)
compact << rest if rest
compact.concat(posts)
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*constant, *requireds, *rest, *posts, *opening_loc, *closing_loc]
end
# def copy: (**params) -> ArrayPatternNode
def copy(**params)
ArrayPatternNode.new(
params.fetch(:constant) { constant },
params.fetch(:requireds) { requireds },
params.fetch(:rest) { rest },
params.fetch(:posts) { posts },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ constant: constant, requireds: requireds, rest: rest, posts: posts, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (constant = self.constant).nil?
inspector << "├── constant: ∅\n"
else
inspector << "├── constant:\n"
inspector << constant.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── requireds: #{inspector.list("#{inspector.prefix}│ ", requireds)}"
if (rest = self.rest).nil?
inspector << "├── rest: ∅\n"
else
inspector << "├── rest:\n"
inspector << rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── posts: #{inspector.list("#{inspector.prefix}│ ", posts)}"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:array_pattern_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:array_pattern_node
end
end
# Represents a hash key/value pair.
#
# { a => b }
# ^^^^^^
class AssocNode < Node
# attr_reader key: Node
attr_reader :key
# attr_reader value: Node?
attr_reader :value
# attr_reader operator_loc: Location?
attr_reader :operator_loc
# def initialize: (key: Node, value: Node?, operator_loc: Location?, location: Location) -> void
def initialize(key, value, operator_loc, location)
@key = key
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_assoc_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[key, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << key
compact << value if value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[key, *value, *operator_loc]
end
# def copy: (**params) -> AssocNode
def copy(**params)
AssocNode.new(
params.fetch(:key) { key },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ key: key, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String?
def operator
operator_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── key:\n"
inspector << inspector.child_node(key, "│ ")
if (value = self.value).nil?
inspector << "├── value: ∅\n"
else
inspector << "├── value:\n"
inspector << value.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:assoc_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:assoc_node
end
end
# Represents a splat in a hash literal.
#
# { **foo }
# ^^^^^
class AssocSplatNode < Node
# attr_reader value: Node?
attr_reader :value
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (value: Node?, operator_loc: Location, location: Location) -> void
def initialize(value, operator_loc, location)
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_assoc_splat_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << value if value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*value, operator_loc]
end
# def copy: (**params) -> AssocSplatNode
def copy(**params)
AssocSplatNode.new(
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (value = self.value).nil?
inspector << "├── value: ∅\n"
else
inspector << "├── value:\n"
inspector << value.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:assoc_splat_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:assoc_splat_node
end
end
# Represents reading a reference to a field in the previous match.
#
# $'
# ^^
class BackReferenceReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_back_reference_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> BackReferenceReadNode
def copy(**params)
BackReferenceReadNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:back_reference_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:back_reference_read_node
end
end
# Represents a begin statement.
#
# begin
# foo
# end
# ^^^^^
class BeginNode < Node
# attr_reader begin_keyword_loc: Location?
attr_reader :begin_keyword_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader rescue_clause: RescueNode?
attr_reader :rescue_clause
# attr_reader else_clause: ElseNode?
attr_reader :else_clause
# attr_reader ensure_clause: EnsureNode?
attr_reader :ensure_clause
# attr_reader end_keyword_loc: Location?
attr_reader :end_keyword_loc
# def initialize: (begin_keyword_loc: Location?, statements: StatementsNode?, rescue_clause: RescueNode?, else_clause: ElseNode?, ensure_clause: EnsureNode?, end_keyword_loc: Location?, location: Location) -> void
def initialize(begin_keyword_loc, statements, rescue_clause, else_clause, ensure_clause, end_keyword_loc, location)
@begin_keyword_loc = begin_keyword_loc
@statements = statements
@rescue_clause = rescue_clause
@else_clause = else_clause
@ensure_clause = ensure_clause
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_begin_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
# Never mark BeginNode with a newline flag, mark children instead
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements, rescue_clause, else_clause, ensure_clause]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact << rescue_clause if rescue_clause
compact << else_clause if else_clause
compact << ensure_clause if ensure_clause
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*begin_keyword_loc, *statements, *rescue_clause, *else_clause, *ensure_clause, *end_keyword_loc]
end
# def copy: (**params) -> BeginNode
def copy(**params)
BeginNode.new(
params.fetch(:begin_keyword_loc) { begin_keyword_loc },
params.fetch(:statements) { statements },
params.fetch(:rescue_clause) { rescue_clause },
params.fetch(:else_clause) { else_clause },
params.fetch(:ensure_clause) { ensure_clause },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ begin_keyword_loc: begin_keyword_loc, statements: statements, rescue_clause: rescue_clause, else_clause: else_clause, ensure_clause: ensure_clause, end_keyword_loc: end_keyword_loc, location: location }
end
# def begin_keyword: () -> String?
def begin_keyword
begin_keyword_loc&.slice
end
# def end_keyword: () -> String?
def end_keyword
end_keyword_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── begin_keyword_loc: #{inspector.location(begin_keyword_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (rescue_clause = self.rescue_clause).nil?
inspector << "├── rescue_clause: ∅\n"
else
inspector << "├── rescue_clause:\n"
inspector << rescue_clause.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (else_clause = self.else_clause).nil?
inspector << "├── else_clause: ∅\n"
else
inspector << "├── else_clause:\n"
inspector << else_clause.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (ensure_clause = self.ensure_clause).nil?
inspector << "├── ensure_clause: ∅\n"
else
inspector << "├── ensure_clause:\n"
inspector << ensure_clause.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:begin_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:begin_node
end
end
# Represents block method arguments.
#
# bar(&args)
# ^^^^^^^^^^
class BlockArgumentNode < Node
# attr_reader expression: Node?
attr_reader :expression
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (expression: Node?, operator_loc: Location, location: Location) -> void
def initialize(expression, operator_loc, location)
@expression = expression
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_block_argument_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[expression]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << expression if expression
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*expression, operator_loc]
end
# def copy: (**params) -> BlockArgumentNode
def copy(**params)
BlockArgumentNode.new(
params.fetch(:expression) { expression },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ expression: expression, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (expression = self.expression).nil?
inspector << "├── expression: ∅\n"
else
inspector << "├── expression:\n"
inspector << expression.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:block_argument_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:block_argument_node
end
end
# Represents a block local variable.
#
# a { |; b| }
# ^
class BlockLocalVariableNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_block_local_variable_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> BlockLocalVariableNode
def copy(**params)
BlockLocalVariableNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:block_local_variable_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:block_local_variable_node
end
end
# Represents a block of ruby code.
#
# [1, 2, 3].each { |i| puts x }
# ^^^^^^^^^^^^^^
class BlockNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader locals_body_index: Integer
attr_reader :locals_body_index
# attr_reader parameters: Node?
attr_reader :parameters
# attr_reader body: Node?
attr_reader :body
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (locals: Array[Symbol], locals_body_index: Integer, parameters: Node?, body: Node?, opening_loc: Location, closing_loc: Location, location: Location) -> void
def initialize(locals, locals_body_index, parameters, body, opening_loc, closing_loc, location)
@locals = locals
@locals_body_index = locals_body_index
@parameters = parameters
@body = body
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_block_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[parameters, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << parameters if parameters
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*parameters, *body, opening_loc, closing_loc]
end
# def copy: (**params) -> BlockNode
def copy(**params)
BlockNode.new(
params.fetch(:locals) { locals },
params.fetch(:locals_body_index) { locals_body_index },
params.fetch(:parameters) { parameters },
params.fetch(:body) { body },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, locals_body_index: locals_body_index, parameters: parameters, body: body, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── locals_body_index: #{locals_body_index.inspect}\n"
if (parameters = self.parameters).nil?
inspector << "├── parameters: ∅\n"
else
inspector << "├── parameters:\n"
inspector << parameters.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:block_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:block_node
end
end
# Represents a block parameter to a method, block, or lambda definition.
#
# def a(&b)
# ^^
# end
class BlockParameterNode < Node
# attr_reader name: Symbol?
attr_reader :name
# attr_reader name_loc: Location?
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol?, name_loc: Location?, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, operator_loc, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_block_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*name_loc, operator_loc]
end
# def copy: (**params) -> BlockParameterNode
def copy(**params)
BlockParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (name = self.name).nil?
inspector << "├── name: ∅\n"
else
inspector << "├── name: #{name.inspect}\n"
end
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:block_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:block_parameter_node
end
end
# Represents a block's parameters declaration.
#
# -> (a, b = 1; local) { }
# ^^^^^^^^^^^^^^^^^
#
# foo do |a, b = 1; local|
# ^^^^^^^^^^^^^^^^^
# end
class BlockParametersNode < Node
# attr_reader parameters: ParametersNode?
attr_reader :parameters
# attr_reader locals: Array[Node]
attr_reader :locals
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (parameters: ParametersNode?, locals: Array[Node], opening_loc: Location?, closing_loc: Location?, location: Location) -> void
def initialize(parameters, locals, opening_loc, closing_loc, location)
@parameters = parameters
@locals = locals
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_block_parameters_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[parameters, *locals]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << parameters if parameters
compact.concat(locals)
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*parameters, *locals, *opening_loc, *closing_loc]
end
# def copy: (**params) -> BlockParametersNode
def copy(**params)
BlockParametersNode.new(
params.fetch(:parameters) { parameters },
params.fetch(:locals) { locals },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ parameters: parameters, locals: locals, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (parameters = self.parameters).nil?
inspector << "├── parameters: ∅\n"
else
inspector << "├── parameters:\n"
inspector << parameters.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── locals: #{inspector.list("#{inspector.prefix}│ ", locals)}"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:block_parameters_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:block_parameters_node
end
end
# Represents the use of the `break` keyword.
#
# break foo
# ^^^^^^^^^
class BreakNode < Node
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (arguments: ArgumentsNode?, keyword_loc: Location, location: Location) -> void
def initialize(arguments, keyword_loc, location)
@arguments = arguments
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_break_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[arguments]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << arguments if arguments
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*arguments, keyword_loc]
end
# def copy: (**params) -> BreakNode
def copy(**params)
BreakNode.new(
params.fetch(:arguments) { arguments },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ arguments: arguments, keyword_loc: keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:break_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:break_node
end
end
# Represents the use of the `&&=` operator on a call.
#
# foo.bar &&= value
# ^^^^^^^^^^^^^^^^^
class CallAndWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader message_loc: Location?
attr_reader :message_loc
# attr_reader read_name: Symbol
attr_reader :read_name
# attr_reader write_name: Symbol
attr_reader :write_name
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, message_loc: Location?, read_name: Symbol, write_name: Symbol, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@message_loc = message_loc
@read_name = read_name
@write_name = write_name
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_call_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, *message_loc, operator_loc, value]
end
# def copy: (**params) -> CallAndWriteNode
def copy(**params)
CallAndWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:message_loc) { message_loc },
params.fetch(:read_name) { read_name },
params.fetch(:write_name) { write_name },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, message_loc: message_loc, read_name: read_name, write_name: write_name, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def message: () -> String?
def message
message_loc&.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── message_loc: #{inspector.location(message_loc)}\n"
inspector << "├── read_name: #{read_name.inspect}\n"
inspector << "├── write_name: #{write_name.inspect}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:call_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:call_and_write_node
end
end
# Represents a method call, in all of the various forms that can take.
#
# foo
# ^^^
#
# foo()
# ^^^^^
#
# +foo
# ^^^^
#
# foo + bar
# ^^^^^^^^^
#
# foo.bar
# ^^^^^^^
#
# foo&.bar
# ^^^^^^^^
class CallNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader name: Symbol
attr_reader :name
# attr_reader message_loc: Location?
attr_reader :message_loc
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# attr_reader block: Node?
attr_reader :block
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, name: Symbol, message_loc: Location?, opening_loc: Location?, arguments: ArgumentsNode?, closing_loc: Location?, block: Node?, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, name, message_loc, opening_loc, arguments, closing_loc, block, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@name = name
@message_loc = message_loc
@opening_loc = opening_loc
@arguments = arguments
@closing_loc = closing_loc
@block = block
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_call_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, arguments, block]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << arguments if arguments
compact << block if block
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, *message_loc, *opening_loc, *arguments, *closing_loc, *block]
end
# def copy: (**params) -> CallNode
def copy(**params)
CallNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:name) { name },
params.fetch(:message_loc) { message_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:arguments) { arguments },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:block) { block },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, name: name, message_loc: message_loc, opening_loc: opening_loc, arguments: arguments, closing_loc: closing_loc, block: block, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def message: () -> String?
def message
message_loc&.slice
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── name: #{name.inspect}\n"
inspector << "├── message_loc: #{inspector.location(message_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (block = self.block).nil?
inspector << "└── block: ∅\n"
else
inspector << "└── block:\n"
inspector << block.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:call_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:call_node
end
end
# Represents the use of an assignment operator on a call.
#
# foo.bar += baz
# ^^^^^^^^^^^^^^
class CallOperatorWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader message_loc: Location?
attr_reader :message_loc
# attr_reader read_name: Symbol
attr_reader :read_name
# attr_reader write_name: Symbol
attr_reader :write_name
# attr_reader operator: Symbol
attr_reader :operator
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, message_loc: Location?, read_name: Symbol, write_name: Symbol, operator: Symbol, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@message_loc = message_loc
@read_name = read_name
@write_name = write_name
@operator = operator
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_call_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, *message_loc, operator_loc, value]
end
# def copy: (**params) -> CallOperatorWriteNode
def copy(**params)
CallOperatorWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:message_loc) { message_loc },
params.fetch(:read_name) { read_name },
params.fetch(:write_name) { write_name },
params.fetch(:operator) { operator },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, message_loc: message_loc, read_name: read_name, write_name: write_name, operator: operator, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def message: () -> String?
def message
message_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── message_loc: #{inspector.location(message_loc)}\n"
inspector << "├── read_name: #{read_name.inspect}\n"
inspector << "├── write_name: #{write_name.inspect}\n"
inspector << "├── operator: #{operator.inspect}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:call_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:call_operator_write_node
end
end
# Represents the use of the `||=` operator on a call.
#
# foo.bar ||= value
# ^^^^^^^^^^^^^^^^^
class CallOrWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader message_loc: Location?
attr_reader :message_loc
# attr_reader read_name: Symbol
attr_reader :read_name
# attr_reader write_name: Symbol
attr_reader :write_name
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, message_loc: Location?, read_name: Symbol, write_name: Symbol, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, message_loc, read_name, write_name, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@message_loc = message_loc
@read_name = read_name
@write_name = write_name
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_call_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, *message_loc, operator_loc, value]
end
# def copy: (**params) -> CallOrWriteNode
def copy(**params)
CallOrWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:message_loc) { message_loc },
params.fetch(:read_name) { read_name },
params.fetch(:write_name) { write_name },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, message_loc: message_loc, read_name: read_name, write_name: write_name, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def message: () -> String?
def message
message_loc&.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── message_loc: #{inspector.location(message_loc)}\n"
inspector << "├── read_name: #{read_name.inspect}\n"
inspector << "├── write_name: #{write_name.inspect}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:call_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:call_or_write_node
end
end
# Represents assigning to a method call.
#
# foo.bar, = 1
# ^^^^^^^
#
# begin
# rescue => foo.bar
# ^^^^^^^
# end
#
# for foo.bar in baz do end
# ^^^^^^^
class CallTargetNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node
attr_reader :receiver
# attr_reader call_operator_loc: Location
attr_reader :call_operator_loc
# attr_reader name: Symbol
attr_reader :name
# attr_reader message_loc: Location
attr_reader :message_loc
# def initialize: (flags: Integer, receiver: Node, call_operator_loc: Location, name: Symbol, message_loc: Location, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, name, message_loc, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@name = name
@message_loc = message_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_call_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[receiver]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[receiver, call_operator_loc, message_loc]
end
# def copy: (**params) -> CallTargetNode
def copy(**params)
CallTargetNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:name) { name },
params.fetch(:message_loc) { message_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, name: name, message_loc: message_loc, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String
def call_operator
call_operator_loc.slice
end
# def message: () -> String
def message
message_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── receiver:\n"
inspector << inspector.child_node(receiver, "│ ")
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── name: #{name.inspect}\n"
inspector << "└── message_loc: #{inspector.location(message_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:call_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:call_target_node
end
end
# Represents assigning to a local variable in pattern matching.
#
# foo => [bar => baz]
# ^^^^^^^^^^^^
class CapturePatternNode < Node
# attr_reader value: Node
attr_reader :value
# attr_reader target: Node
attr_reader :target
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (value: Node, target: Node, operator_loc: Location, location: Location) -> void
def initialize(value, target, operator_loc, location)
@value = value
@target = target
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_capture_pattern_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value, target]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value, target]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[value, target, operator_loc]
end
# def copy: (**params) -> CapturePatternNode
def copy(**params)
CapturePatternNode.new(
params.fetch(:value) { value },
params.fetch(:target) { target },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ value: value, target: target, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── target:\n"
inspector << inspector.child_node(target, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:capture_pattern_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:capture_pattern_node
end
end
# Represents the use of a case statement for pattern matching.
#
# case true
# in false
# end
# ^^^^^^^^^
class CaseMatchNode < Node
# attr_reader predicate: Node?
attr_reader :predicate
# attr_reader conditions: Array[Node]
attr_reader :conditions
# attr_reader consequent: ElseNode?
attr_reader :consequent
# attr_reader case_keyword_loc: Location
attr_reader :case_keyword_loc
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# def initialize: (predicate: Node?, conditions: Array[Node], consequent: ElseNode?, case_keyword_loc: Location, end_keyword_loc: Location, location: Location) -> void
def initialize(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location)
@predicate = predicate
@conditions = conditions
@consequent = consequent
@case_keyword_loc = case_keyword_loc
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_case_match_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, *conditions, consequent]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate if predicate
compact.concat(conditions)
compact << consequent if consequent
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*predicate, *conditions, *consequent, case_keyword_loc, end_keyword_loc]
end
# def copy: (**params) -> CaseMatchNode
def copy(**params)
CaseMatchNode.new(
params.fetch(:predicate) { predicate },
params.fetch(:conditions) { conditions },
params.fetch(:consequent) { consequent },
params.fetch(:case_keyword_loc) { case_keyword_loc },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ predicate: predicate, conditions: conditions, consequent: consequent, case_keyword_loc: case_keyword_loc, end_keyword_loc: end_keyword_loc, location: location }
end
# def case_keyword: () -> String
def case_keyword
case_keyword_loc.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (predicate = self.predicate).nil?
inspector << "├── predicate: ∅\n"
else
inspector << "├── predicate:\n"
inspector << predicate.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── conditions: #{inspector.list("#{inspector.prefix}│ ", conditions)}"
if (consequent = self.consequent).nil?
inspector << "├── consequent: ∅\n"
else
inspector << "├── consequent:\n"
inspector << consequent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── case_keyword_loc: #{inspector.location(case_keyword_loc)}\n"
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:case_match_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:case_match_node
end
end
# Represents the use of a case statement.
#
# case true
# when false
# end
# ^^^^^^^^^^
class CaseNode < Node
# attr_reader predicate: Node?
attr_reader :predicate
# attr_reader conditions: Array[Node]
attr_reader :conditions
# attr_reader consequent: ElseNode?
attr_reader :consequent
# attr_reader case_keyword_loc: Location
attr_reader :case_keyword_loc
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# def initialize: (predicate: Node?, conditions: Array[Node], consequent: ElseNode?, case_keyword_loc: Location, end_keyword_loc: Location, location: Location) -> void
def initialize(predicate, conditions, consequent, case_keyword_loc, end_keyword_loc, location)
@predicate = predicate
@conditions = conditions
@consequent = consequent
@case_keyword_loc = case_keyword_loc
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_case_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, *conditions, consequent]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate if predicate
compact.concat(conditions)
compact << consequent if consequent
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*predicate, *conditions, *consequent, case_keyword_loc, end_keyword_loc]
end
# def copy: (**params) -> CaseNode
def copy(**params)
CaseNode.new(
params.fetch(:predicate) { predicate },
params.fetch(:conditions) { conditions },
params.fetch(:consequent) { consequent },
params.fetch(:case_keyword_loc) { case_keyword_loc },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ predicate: predicate, conditions: conditions, consequent: consequent, case_keyword_loc: case_keyword_loc, end_keyword_loc: end_keyword_loc, location: location }
end
# def case_keyword: () -> String
def case_keyword
case_keyword_loc.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (predicate = self.predicate).nil?
inspector << "├── predicate: ∅\n"
else
inspector << "├── predicate:\n"
inspector << predicate.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── conditions: #{inspector.list("#{inspector.prefix}│ ", conditions)}"
if (consequent = self.consequent).nil?
inspector << "├── consequent: ∅\n"
else
inspector << "├── consequent:\n"
inspector << consequent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── case_keyword_loc: #{inspector.location(case_keyword_loc)}\n"
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:case_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:case_node
end
end
# Represents a class declaration involving the `class` keyword.
#
# class Foo end
# ^^^^^^^^^^^^^
class ClassNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader class_keyword_loc: Location
attr_reader :class_keyword_loc
# attr_reader constant_path: Node
attr_reader :constant_path
# attr_reader inheritance_operator_loc: Location?
attr_reader :inheritance_operator_loc
# attr_reader superclass: Node?
attr_reader :superclass
# attr_reader body: Node?
attr_reader :body
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# attr_reader name: Symbol
attr_reader :name
# def initialize: (locals: Array[Symbol], class_keyword_loc: Location, constant_path: Node, inheritance_operator_loc: Location?, superclass: Node?, body: Node?, end_keyword_loc: Location, name: Symbol, location: Location) -> void
def initialize(locals, class_keyword_loc, constant_path, inheritance_operator_loc, superclass, body, end_keyword_loc, name, location)
@locals = locals
@class_keyword_loc = class_keyword_loc
@constant_path = constant_path
@inheritance_operator_loc = inheritance_operator_loc
@superclass = superclass
@body = body
@end_keyword_loc = end_keyword_loc
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[constant_path, superclass, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << constant_path
compact << superclass if superclass
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[class_keyword_loc, constant_path, *inheritance_operator_loc, *superclass, *body, end_keyword_loc]
end
# def copy: (**params) -> ClassNode
def copy(**params)
ClassNode.new(
params.fetch(:locals) { locals },
params.fetch(:class_keyword_loc) { class_keyword_loc },
params.fetch(:constant_path) { constant_path },
params.fetch(:inheritance_operator_loc) { inheritance_operator_loc },
params.fetch(:superclass) { superclass },
params.fetch(:body) { body },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, class_keyword_loc: class_keyword_loc, constant_path: constant_path, inheritance_operator_loc: inheritance_operator_loc, superclass: superclass, body: body, end_keyword_loc: end_keyword_loc, name: name, location: location }
end
# def class_keyword: () -> String
def class_keyword
class_keyword_loc.slice
end
# def inheritance_operator: () -> String?
def inheritance_operator
inheritance_operator_loc&.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── class_keyword_loc: #{inspector.location(class_keyword_loc)}\n"
inspector << "├── constant_path:\n"
inspector << inspector.child_node(constant_path, "│ ")
inspector << "├── inheritance_operator_loc: #{inspector.location(inheritance_operator_loc)}\n"
if (superclass = self.superclass).nil?
inspector << "├── superclass: ∅\n"
else
inspector << "├── superclass:\n"
inspector << superclass.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_node
end
end
# Represents the use of the `&&=` operator for assignment to a class variable.
#
# @@target &&= value
# ^^^^^^^^^^^^^^^^^^
class ClassVariableAndWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ClassVariableAndWriteNode
def copy(**params)
ClassVariableAndWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_and_write_node
end
end
# Represents assigning to a class variable using an operator that isn't `=`.
#
# @@target += value
# ^^^^^^^^^^^^^^^^^
class ClassVariableOperatorWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator: Symbol
attr_reader :operator
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, operator: Symbol, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, operator, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@operator = operator
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ClassVariableOperatorWriteNode
def copy(**params)
ClassVariableOperatorWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:operator) { operator },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, operator: operator, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator: #{operator.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to a class variable.
#
# @@target ||= value
# ^^^^^^^^^^^^^^^^^^
class ClassVariableOrWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ClassVariableOrWriteNode
def copy(**params)
ClassVariableOrWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_or_write_node
end
end
# Represents referencing a class variable.
#
# @@foo
# ^^^^^
class ClassVariableReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ClassVariableReadNode
def copy(**params)
ClassVariableReadNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_read_node
end
end
# Represents writing to a class variable in a context that doesn't have an explicit value.
#
# @@foo, @@bar = baz
# ^^^^^ ^^^^^
class ClassVariableTargetNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ClassVariableTargetNode
def copy(**params)
ClassVariableTargetNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_target_node
end
end
# Represents writing to a class variable.
#
# @@foo = 1
# ^^^^^^^^^
class ClassVariableWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator_loc: Location?
attr_reader :operator_loc
# def initialize: (name: Symbol, name_loc: Location, value: Node, operator_loc: Location?, location: Location) -> void
def initialize(name, name_loc, value, operator_loc, location)
@name = name
@name_loc = name_loc
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_class_variable_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value, *operator_loc]
end
# def copy: (**params) -> ClassVariableWriteNode
def copy(**params)
ClassVariableWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String?
def operator
operator_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:class_variable_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:class_variable_write_node
end
end
# Represents the use of the `&&=` operator for assignment to a constant.
#
# Target &&= value
# ^^^^^^^^^^^^^^^^
class ConstantAndWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ConstantAndWriteNode
def copy(**params)
ConstantAndWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_and_write_node
end
end
# Represents assigning to a constant using an operator that isn't `=`.
#
# Target += value
# ^^^^^^^^^^^^^^^
class ConstantOperatorWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator: Symbol
attr_reader :operator
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, operator: Symbol, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, operator, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@operator = operator
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ConstantOperatorWriteNode
def copy(**params)
ConstantOperatorWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:operator) { operator },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, operator: operator, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator: #{operator.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to a constant.
#
# Target ||= value
# ^^^^^^^^^^^^^^^^
class ConstantOrWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> ConstantOrWriteNode
def copy(**params)
ConstantOrWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_or_write_node
end
end
# Represents the use of the `&&=` operator for assignment to a constant path.
#
# Parent::Child &&= value
# ^^^^^^^^^^^^^^^^^^^^^^^
class ConstantPathAndWriteNode < Node
# attr_reader target: ConstantPathNode
attr_reader :target
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (target: ConstantPathNode, operator_loc: Location, value: Node, location: Location) -> void
def initialize(target, operator_loc, value, location)
@target = target
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[target, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[target, value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[target, operator_loc, value]
end
# def copy: (**params) -> ConstantPathAndWriteNode
def copy(**params)
ConstantPathAndWriteNode.new(
params.fetch(:target) { target },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ target: target, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── target:\n"
inspector << inspector.child_node(target, "│ ")
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_and_write_node
end
end
# Represents accessing a constant through a path of `::` operators.
#
# Foo::Bar
# ^^^^^^^^
class ConstantPathNode < Node
# attr_reader parent: Node?
attr_reader :parent
# attr_reader child: Node
attr_reader :child
# attr_reader delimiter_loc: Location
attr_reader :delimiter_loc
# def initialize: (parent: Node?, child: Node, delimiter_loc: Location, location: Location) -> void
def initialize(parent, child, delimiter_loc, location)
@parent = parent
@child = child
@delimiter_loc = delimiter_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[parent, child]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << parent if parent
compact << child
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*parent, child, delimiter_loc]
end
# def copy: (**params) -> ConstantPathNode
def copy(**params)
ConstantPathNode.new(
params.fetch(:parent) { parent },
params.fetch(:child) { child },
params.fetch(:delimiter_loc) { delimiter_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ parent: parent, child: child, delimiter_loc: delimiter_loc, location: location }
end
# def delimiter: () -> String
def delimiter
delimiter_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (parent = self.parent).nil?
inspector << "├── parent: ∅\n"
else
inspector << "├── parent:\n"
inspector << parent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── child:\n"
inspector << inspector.child_node(child, "│ ")
inspector << "└── delimiter_loc: #{inspector.location(delimiter_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_node
end
end
# Represents assigning to a constant path using an operator that isn't `=`.
#
# Parent::Child += value
# ^^^^^^^^^^^^^^^^^^^^^^
class ConstantPathOperatorWriteNode < Node
# attr_reader target: ConstantPathNode
attr_reader :target
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator: Symbol
attr_reader :operator
# def initialize: (target: ConstantPathNode, operator_loc: Location, value: Node, operator: Symbol, location: Location) -> void
def initialize(target, operator_loc, value, operator, location)
@target = target
@operator_loc = operator_loc
@value = value
@operator = operator
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[target, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[target, value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[target, operator_loc, value]
end
# def copy: (**params) -> ConstantPathOperatorWriteNode
def copy(**params)
ConstantPathOperatorWriteNode.new(
params.fetch(:target) { target },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:operator) { operator },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ target: target, operator_loc: operator_loc, value: value, operator: operator, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── target:\n"
inspector << inspector.child_node(target, "│ ")
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator: #{operator.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to a constant path.
#
# Parent::Child ||= value
# ^^^^^^^^^^^^^^^^^^^^^^^
class ConstantPathOrWriteNode < Node
# attr_reader target: ConstantPathNode
attr_reader :target
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (target: ConstantPathNode, operator_loc: Location, value: Node, location: Location) -> void
def initialize(target, operator_loc, value, location)
@target = target
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[target, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[target, value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[target, operator_loc, value]
end
# def copy: (**params) -> ConstantPathOrWriteNode
def copy(**params)
ConstantPathOrWriteNode.new(
params.fetch(:target) { target },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ target: target, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── target:\n"
inspector << inspector.child_node(target, "│ ")
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_or_write_node
end
end
# Represents writing to a constant path in a context that doesn't have an explicit value.
#
# Foo::Foo, Bar::Bar = baz
# ^^^^^^^^ ^^^^^^^^
class ConstantPathTargetNode < Node
# attr_reader parent: Node?
attr_reader :parent
# attr_reader child: Node
attr_reader :child
# attr_reader delimiter_loc: Location
attr_reader :delimiter_loc
# def initialize: (parent: Node?, child: Node, delimiter_loc: Location, location: Location) -> void
def initialize(parent, child, delimiter_loc, location)
@parent = parent
@child = child
@delimiter_loc = delimiter_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[parent, child]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << parent if parent
compact << child
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*parent, child, delimiter_loc]
end
# def copy: (**params) -> ConstantPathTargetNode
def copy(**params)
ConstantPathTargetNode.new(
params.fetch(:parent) { parent },
params.fetch(:child) { child },
params.fetch(:delimiter_loc) { delimiter_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ parent: parent, child: child, delimiter_loc: delimiter_loc, location: location }
end
# def delimiter: () -> String
def delimiter
delimiter_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (parent = self.parent).nil?
inspector << "├── parent: ∅\n"
else
inspector << "├── parent:\n"
inspector << parent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── child:\n"
inspector << inspector.child_node(child, "│ ")
inspector << "└── delimiter_loc: #{inspector.location(delimiter_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_target_node
end
end
# Represents writing to a constant path.
#
# ::Foo = 1
# ^^^^^^^^^
#
# Foo::Bar = 1
# ^^^^^^^^^^^^
#
# ::Foo::Bar = 1
# ^^^^^^^^^^^^^^
class ConstantPathWriteNode < Node
# attr_reader target: ConstantPathNode
attr_reader :target
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (target: ConstantPathNode, operator_loc: Location, value: Node, location: Location) -> void
def initialize(target, operator_loc, value, location)
@target = target
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_path_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[target, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[target, value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[target, operator_loc, value]
end
# def copy: (**params) -> ConstantPathWriteNode
def copy(**params)
ConstantPathWriteNode.new(
params.fetch(:target) { target },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ target: target, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── target:\n"
inspector << inspector.child_node(target, "│ ")
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_path_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_path_write_node
end
end
# Represents referencing a constant.
#
# Foo
# ^^^
class ConstantReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ConstantReadNode
def copy(**params)
ConstantReadNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_read_node
end
end
# Represents writing to a constant in a context that doesn't have an explicit value.
#
# Foo, Bar = baz
# ^^^ ^^^
class ConstantTargetNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ConstantTargetNode
def copy(**params)
ConstantTargetNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_target_node
end
end
# Represents writing to a constant.
#
# Foo = 1
# ^^^^^^^
class ConstantWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol, name_loc: Location, value: Node, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, value, operator_loc, location)
@name = name
@name_loc = name_loc
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_constant_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value, operator_loc]
end
# def copy: (**params) -> ConstantWriteNode
def copy(**params)
ConstantWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:constant_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:constant_write_node
end
end
# Represents a method definition.
#
# def method
# end
# ^^^^^^^^^^
class DefNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader parameters: ParametersNode?
attr_reader :parameters
# attr_reader body: Node?
attr_reader :body
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader locals_body_index: Integer
attr_reader :locals_body_index
# attr_reader def_keyword_loc: Location
attr_reader :def_keyword_loc
# attr_reader operator_loc: Location?
attr_reader :operator_loc
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# attr_reader equal_loc: Location?
attr_reader :equal_loc
# attr_reader end_keyword_loc: Location?
attr_reader :end_keyword_loc
# def initialize: (name: Symbol, name_loc: Location, receiver: Node?, parameters: ParametersNode?, body: Node?, locals: Array[Symbol], locals_body_index: Integer, def_keyword_loc: Location, operator_loc: Location?, lparen_loc: Location?, rparen_loc: Location?, equal_loc: Location?, end_keyword_loc: Location?, location: Location) -> void
def initialize(name, name_loc, receiver, parameters, body, locals, locals_body_index, def_keyword_loc, operator_loc, lparen_loc, rparen_loc, equal_loc, end_keyword_loc, location)
@name = name
@name_loc = name_loc
@receiver = receiver
@parameters = parameters
@body = body
@locals = locals
@locals_body_index = locals_body_index
@def_keyword_loc = def_keyword_loc
@operator_loc = operator_loc
@lparen_loc = lparen_loc
@rparen_loc = rparen_loc
@equal_loc = equal_loc
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_def_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, parameters, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << parameters if parameters
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, *receiver, *parameters, *body, def_keyword_loc, *operator_loc, *lparen_loc, *rparen_loc, *equal_loc, *end_keyword_loc]
end
# def copy: (**params) -> DefNode
def copy(**params)
DefNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:receiver) { receiver },
params.fetch(:parameters) { parameters },
params.fetch(:body) { body },
params.fetch(:locals) { locals },
params.fetch(:locals_body_index) { locals_body_index },
params.fetch(:def_keyword_loc) { def_keyword_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:equal_loc) { equal_loc },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, receiver: receiver, parameters: parameters, body: body, locals: locals, locals_body_index: locals_body_index, def_keyword_loc: def_keyword_loc, operator_loc: operator_loc, lparen_loc: lparen_loc, rparen_loc: rparen_loc, equal_loc: equal_loc, end_keyword_loc: end_keyword_loc, location: location }
end
# def def_keyword: () -> String
def def_keyword
def_keyword_loc.slice
end
# def operator: () -> String?
def operator
operator_loc&.slice
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def equal: () -> String?
def equal
equal_loc&.slice
end
# def end_keyword: () -> String?
def end_keyword
end_keyword_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (parameters = self.parameters).nil?
inspector << "├── parameters: ∅\n"
else
inspector << "├── parameters:\n"
inspector << parameters.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── locals_body_index: #{locals_body_index.inspect}\n"
inspector << "├── def_keyword_loc: #{inspector.location(def_keyword_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
inspector << "├── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector << "├── equal_loc: #{inspector.location(equal_loc)}\n"
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:def_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:def_node
end
end
# Represents the use of the `defined?` keyword.
#
# defined?(a)
# ^^^^^^^^^^^
class DefinedNode < Node
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader value: Node
attr_reader :value
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (lparen_loc: Location?, value: Node, rparen_loc: Location?, keyword_loc: Location, location: Location) -> void
def initialize(lparen_loc, value, rparen_loc, keyword_loc, location)
@lparen_loc = lparen_loc
@value = value
@rparen_loc = rparen_loc
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_defined_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*lparen_loc, value, *rparen_loc, keyword_loc]
end
# def copy: (**params) -> DefinedNode
def copy(**params)
DefinedNode.new(
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:value) { value },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ lparen_loc: lparen_loc, value: value, rparen_loc: rparen_loc, keyword_loc: keyword_loc, location: location }
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:defined_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:defined_node
end
end
# Represents an `else` clause in a `case`, `if`, or `unless` statement.
#
# if a then b else c end
# ^^^^^^^^^^
class ElseNode < Node
# attr_reader else_keyword_loc: Location
attr_reader :else_keyword_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader end_keyword_loc: Location?
attr_reader :end_keyword_loc
# def initialize: (else_keyword_loc: Location, statements: StatementsNode?, end_keyword_loc: Location?, location: Location) -> void
def initialize(else_keyword_loc, statements, end_keyword_loc, location)
@else_keyword_loc = else_keyword_loc
@statements = statements
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_else_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[else_keyword_loc, *statements, *end_keyword_loc]
end
# def copy: (**params) -> ElseNode
def copy(**params)
ElseNode.new(
params.fetch(:else_keyword_loc) { else_keyword_loc },
params.fetch(:statements) { statements },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ else_keyword_loc: else_keyword_loc, statements: statements, end_keyword_loc: end_keyword_loc, location: location }
end
# def else_keyword: () -> String
def else_keyword
else_keyword_loc.slice
end
# def end_keyword: () -> String?
def end_keyword
end_keyword_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── else_keyword_loc: #{inspector.location(else_keyword_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:else_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:else_node
end
end
# Represents an interpolated set of statements.
#
# "foo #{bar}"
# ^^^^^^
class EmbeddedStatementsNode < Node
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (opening_loc: Location, statements: StatementsNode?, closing_loc: Location, location: Location) -> void
def initialize(opening_loc, statements, closing_loc, location)
@opening_loc = opening_loc
@statements = statements
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_embedded_statements_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, *statements, closing_loc]
end
# def copy: (**params) -> EmbeddedStatementsNode
def copy(**params)
EmbeddedStatementsNode.new(
params.fetch(:opening_loc) { opening_loc },
params.fetch(:statements) { statements },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ opening_loc: opening_loc, statements: statements, closing_loc: closing_loc, location: location }
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:embedded_statements_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:embedded_statements_node
end
end
# Represents an interpolated variable.
#
# "foo #@bar"
# ^^^^^
class EmbeddedVariableNode < Node
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader variable: Node
attr_reader :variable
# def initialize: (operator_loc: Location, variable: Node, location: Location) -> void
def initialize(operator_loc, variable, location)
@operator_loc = operator_loc
@variable = variable
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_embedded_variable_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[variable]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[variable]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[operator_loc, variable]
end
# def copy: (**params) -> EmbeddedVariableNode
def copy(**params)
EmbeddedVariableNode.new(
params.fetch(:operator_loc) { operator_loc },
params.fetch(:variable) { variable },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ operator_loc: operator_loc, variable: variable, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── variable:\n"
inspector << inspector.child_node(variable, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:embedded_variable_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:embedded_variable_node
end
end
# Represents an `ensure` clause in a `begin` statement.
#
# begin
# foo
# ensure
# ^^^^^^
# bar
# end
class EnsureNode < Node
# attr_reader ensure_keyword_loc: Location
attr_reader :ensure_keyword_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# def initialize: (ensure_keyword_loc: Location, statements: StatementsNode?, end_keyword_loc: Location, location: Location) -> void
def initialize(ensure_keyword_loc, statements, end_keyword_loc, location)
@ensure_keyword_loc = ensure_keyword_loc
@statements = statements
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_ensure_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[ensure_keyword_loc, *statements, end_keyword_loc]
end
# def copy: (**params) -> EnsureNode
def copy(**params)
EnsureNode.new(
params.fetch(:ensure_keyword_loc) { ensure_keyword_loc },
params.fetch(:statements) { statements },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ ensure_keyword_loc: ensure_keyword_loc, statements: statements, end_keyword_loc: end_keyword_loc, location: location }
end
# def ensure_keyword: () -> String
def ensure_keyword
ensure_keyword_loc.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── ensure_keyword_loc: #{inspector.location(ensure_keyword_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:ensure_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:ensure_node
end
end
# Represents the use of the literal `false` keyword.
#
# false
# ^^^^^
class FalseNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_false_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> FalseNode
def copy(**params)
FalseNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:false_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:false_node
end
end
# Represents a find pattern in pattern matching.
#
# foo in *bar, baz, *qux
# ^^^^^^^^^^^^^^^
#
# foo in [*bar, baz, *qux]
# ^^^^^^^^^^^^^^^^^
#
# foo in Foo(*bar, baz, *qux)
# ^^^^^^^^^^^^^^^^^^^^
class FindPatternNode < Node
# attr_reader constant: Node?
attr_reader :constant
# attr_reader left: Node
attr_reader :left
# attr_reader requireds: Array[Node]
attr_reader :requireds
# attr_reader right: Node
attr_reader :right
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (constant: Node?, left: Node, requireds: Array[Node], right: Node, opening_loc: Location?, closing_loc: Location?, location: Location) -> void
def initialize(constant, left, requireds, right, opening_loc, closing_loc, location)
@constant = constant
@left = left
@requireds = requireds
@right = right
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_find_pattern_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[constant, left, *requireds, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << constant if constant
compact << left
compact.concat(requireds)
compact << right
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*constant, left, *requireds, right, *opening_loc, *closing_loc]
end
# def copy: (**params) -> FindPatternNode
def copy(**params)
FindPatternNode.new(
params.fetch(:constant) { constant },
params.fetch(:left) { left },
params.fetch(:requireds) { requireds },
params.fetch(:right) { right },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ constant: constant, left: left, requireds: requireds, right: right, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (constant = self.constant).nil?
inspector << "├── constant: ∅\n"
else
inspector << "├── constant:\n"
inspector << constant.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── left:\n"
inspector << inspector.child_node(left, "│ ")
inspector << "├── requireds: #{inspector.list("#{inspector.prefix}│ ", requireds)}"
inspector << "├── right:\n"
inspector << inspector.child_node(right, "│ ")
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:find_pattern_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:find_pattern_node
end
end
# Represents the use of the `..` or `...` operators to create flip flops.
#
# baz if foo .. bar
# ^^^^^^^^^^
class FlipFlopNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader left: Node?
attr_reader :left
# attr_reader right: Node?
attr_reader :right
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (flags: Integer, left: Node?, right: Node?, operator_loc: Location, location: Location) -> void
def initialize(flags, left, right, operator_loc, location)
@flags = flags
@left = left
@right = right
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_flip_flop_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[left, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << left if left
compact << right if right
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*left, *right, operator_loc]
end
# def copy: (**params) -> FlipFlopNode
def copy(**params)
FlipFlopNode.new(
params.fetch(:flags) { flags },
params.fetch(:left) { left },
params.fetch(:right) { right },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, left: left, right: right, operator_loc: operator_loc, location: location }
end
# def exclude_end?: () -> bool
def exclude_end?
flags.anybits?(RangeFlags::EXCLUDE_END)
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("exclude_end" if exclude_end?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (left = self.left).nil?
inspector << "├── left: ∅\n"
else
inspector << "├── left:\n"
inspector << left.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (right = self.right).nil?
inspector << "├── right: ∅\n"
else
inspector << "├── right:\n"
inspector << right.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:flip_flop_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:flip_flop_node
end
end
# Represents a floating point number literal.
#
# 1.0
# ^^^
class FloatNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_float_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> FloatNode
def copy(**params)
FloatNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:float_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:float_node
end
end
# Represents the use of the `for` keyword.
#
# for i in a end
# ^^^^^^^^^^^^^^
class ForNode < Node
# attr_reader index: Node
attr_reader :index
# attr_reader collection: Node
attr_reader :collection
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader for_keyword_loc: Location
attr_reader :for_keyword_loc
# attr_reader in_keyword_loc: Location
attr_reader :in_keyword_loc
# attr_reader do_keyword_loc: Location?
attr_reader :do_keyword_loc
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# def initialize: (index: Node, collection: Node, statements: StatementsNode?, for_keyword_loc: Location, in_keyword_loc: Location, do_keyword_loc: Location?, end_keyword_loc: Location, location: Location) -> void
def initialize(index, collection, statements, for_keyword_loc, in_keyword_loc, do_keyword_loc, end_keyword_loc, location)
@index = index
@collection = collection
@statements = statements
@for_keyword_loc = for_keyword_loc
@in_keyword_loc = in_keyword_loc
@do_keyword_loc = do_keyword_loc
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_for_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[index, collection, statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << index
compact << collection
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[index, collection, *statements, for_keyword_loc, in_keyword_loc, *do_keyword_loc, end_keyword_loc]
end
# def copy: (**params) -> ForNode
def copy(**params)
ForNode.new(
params.fetch(:index) { index },
params.fetch(:collection) { collection },
params.fetch(:statements) { statements },
params.fetch(:for_keyword_loc) { for_keyword_loc },
params.fetch(:in_keyword_loc) { in_keyword_loc },
params.fetch(:do_keyword_loc) { do_keyword_loc },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ index: index, collection: collection, statements: statements, for_keyword_loc: for_keyword_loc, in_keyword_loc: in_keyword_loc, do_keyword_loc: do_keyword_loc, end_keyword_loc: end_keyword_loc, location: location }
end
# def for_keyword: () -> String
def for_keyword
for_keyword_loc.slice
end
# def in_keyword: () -> String
def in_keyword
in_keyword_loc.slice
end
# def do_keyword: () -> String?
def do_keyword
do_keyword_loc&.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── index:\n"
inspector << inspector.child_node(index, "│ ")
inspector << "├── collection:\n"
inspector << inspector.child_node(collection, "│ ")
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── for_keyword_loc: #{inspector.location(for_keyword_loc)}\n"
inspector << "├── in_keyword_loc: #{inspector.location(in_keyword_loc)}\n"
inspector << "├── do_keyword_loc: #{inspector.location(do_keyword_loc)}\n"
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:for_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:for_node
end
end
# Represents forwarding all arguments to this method to another method.
#
# def foo(...)
# bar(...)
# ^^^
# end
class ForwardingArgumentsNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_forwarding_arguments_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ForwardingArgumentsNode
def copy(**params)
ForwardingArgumentsNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:forwarding_arguments_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:forwarding_arguments_node
end
end
# Represents the use of the forwarding parameter in a method, block, or lambda declaration.
#
# def foo(...)
# ^^^
# end
class ForwardingParameterNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_forwarding_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ForwardingParameterNode
def copy(**params)
ForwardingParameterNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:forwarding_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:forwarding_parameter_node
end
end
# Represents the use of the `super` keyword without parentheses or arguments.
#
# super
# ^^^^^
class ForwardingSuperNode < Node
# attr_reader block: BlockNode?
attr_reader :block
# def initialize: (block: BlockNode?, location: Location) -> void
def initialize(block, location)
@block = block
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_forwarding_super_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[block]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << block if block
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*block]
end
# def copy: (**params) -> ForwardingSuperNode
def copy(**params)
ForwardingSuperNode.new(
params.fetch(:block) { block },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ block: block, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (block = self.block).nil?
inspector << "└── block: ∅\n"
else
inspector << "└── block:\n"
inspector << block.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:forwarding_super_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:forwarding_super_node
end
end
# Represents the use of the `&&=` operator for assignment to a global variable.
#
# $target &&= value
# ^^^^^^^^^^^^^^^^^
class GlobalVariableAndWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> GlobalVariableAndWriteNode
def copy(**params)
GlobalVariableAndWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_and_write_node
end
end
# Represents assigning to a global variable using an operator that isn't `=`.
#
# $target += value
# ^^^^^^^^^^^^^^^^
class GlobalVariableOperatorWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator: Symbol
attr_reader :operator
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, operator: Symbol, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, operator, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@operator = operator
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> GlobalVariableOperatorWriteNode
def copy(**params)
GlobalVariableOperatorWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:operator) { operator },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, operator: operator, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator: #{operator.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to a global variable.
#
# $target ||= value
# ^^^^^^^^^^^^^^^^^
class GlobalVariableOrWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> GlobalVariableOrWriteNode
def copy(**params)
GlobalVariableOrWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_or_write_node
end
end
# Represents referencing a global variable.
#
# $foo
# ^^^^
class GlobalVariableReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> GlobalVariableReadNode
def copy(**params)
GlobalVariableReadNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_read_node
end
end
# Represents writing to a global variable in a context that doesn't have an explicit value.
#
# $foo, $bar = baz
# ^^^^ ^^^^
class GlobalVariableTargetNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> GlobalVariableTargetNode
def copy(**params)
GlobalVariableTargetNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_target_node
end
end
# Represents writing to a global variable.
#
# $foo = 1
# ^^^^^^^^
class GlobalVariableWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol, name_loc: Location, value: Node, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, value, operator_loc, location)
@name = name
@name_loc = name_loc
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_global_variable_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value, operator_loc]
end
# def copy: (**params) -> GlobalVariableWriteNode
def copy(**params)
GlobalVariableWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:global_variable_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:global_variable_write_node
end
end
# Represents a hash literal.
#
# { a => b }
# ^^^^^^^^^^
class HashNode < Node
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader elements: Array[Node]
attr_reader :elements
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (opening_loc: Location, elements: Array[Node], closing_loc: Location, location: Location) -> void
def initialize(opening_loc, elements, closing_loc, location)
@opening_loc = opening_loc
@elements = elements
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_hash_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*elements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*elements]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, *elements, closing_loc]
end
# def copy: (**params) -> HashNode
def copy(**params)
HashNode.new(
params.fetch(:opening_loc) { opening_loc },
params.fetch(:elements) { elements },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ opening_loc: opening_loc, elements: elements, closing_loc: closing_loc, location: location }
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── elements: #{inspector.list("#{inspector.prefix}│ ", elements)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:hash_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:hash_node
end
end
# Represents a hash pattern in pattern matching.
#
# foo => { a: 1, b: 2 }
# ^^^^^^^^^^^^^^
#
# foo => { a: 1, b: 2, **c }
# ^^^^^^^^^^^^^^^^^^^
class HashPatternNode < Node
# attr_reader constant: Node?
attr_reader :constant
# attr_reader elements: Array[Node]
attr_reader :elements
# attr_reader rest: Node?
attr_reader :rest
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (constant: Node?, elements: Array[Node], rest: Node?, opening_loc: Location?, closing_loc: Location?, location: Location) -> void
def initialize(constant, elements, rest, opening_loc, closing_loc, location)
@constant = constant
@elements = elements
@rest = rest
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_hash_pattern_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[constant, *elements, rest]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << constant if constant
compact.concat(elements)
compact << rest if rest
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*constant, *elements, *rest, *opening_loc, *closing_loc]
end
# def copy: (**params) -> HashPatternNode
def copy(**params)
HashPatternNode.new(
params.fetch(:constant) { constant },
params.fetch(:elements) { elements },
params.fetch(:rest) { rest },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ constant: constant, elements: elements, rest: rest, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (constant = self.constant).nil?
inspector << "├── constant: ∅\n"
else
inspector << "├── constant:\n"
inspector << constant.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── elements: #{inspector.list("#{inspector.prefix}│ ", elements)}"
if (rest = self.rest).nil?
inspector << "├── rest: ∅\n"
else
inspector << "├── rest:\n"
inspector << rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:hash_pattern_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:hash_pattern_node
end
end
# Represents the use of the `if` keyword, either in the block form or the modifier form.
#
# bar if foo
# ^^^^^^^^^^
#
# if foo then bar end
# ^^^^^^^^^^^^^^^^^^^
class IfNode < Node
# attr_reader if_keyword_loc: Location?
attr_reader :if_keyword_loc
# attr_reader predicate: Node
attr_reader :predicate
# attr_reader then_keyword_loc: Location?
attr_reader :then_keyword_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader consequent: Node?
attr_reader :consequent
# attr_reader end_keyword_loc: Location?
attr_reader :end_keyword_loc
# def initialize: (if_keyword_loc: Location?, predicate: Node, then_keyword_loc: Location?, statements: StatementsNode?, consequent: Node?, end_keyword_loc: Location?, location: Location) -> void
def initialize(if_keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location)
@if_keyword_loc = if_keyword_loc
@predicate = predicate
@then_keyword_loc = then_keyword_loc
@statements = statements
@consequent = consequent
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_if_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
predicate.set_newline_flag(newline_marked)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, statements, consequent]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate
compact << statements if statements
compact << consequent if consequent
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*if_keyword_loc, predicate, *then_keyword_loc, *statements, *consequent, *end_keyword_loc]
end
# def copy: (**params) -> IfNode
def copy(**params)
IfNode.new(
params.fetch(:if_keyword_loc) { if_keyword_loc },
params.fetch(:predicate) { predicate },
params.fetch(:then_keyword_loc) { then_keyword_loc },
params.fetch(:statements) { statements },
params.fetch(:consequent) { consequent },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ if_keyword_loc: if_keyword_loc, predicate: predicate, then_keyword_loc: then_keyword_loc, statements: statements, consequent: consequent, end_keyword_loc: end_keyword_loc, location: location }
end
# def if_keyword: () -> String?
def if_keyword
if_keyword_loc&.slice
end
# def then_keyword: () -> String?
def then_keyword
then_keyword_loc&.slice
end
# def end_keyword: () -> String?
def end_keyword
end_keyword_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── if_keyword_loc: #{inspector.location(if_keyword_loc)}\n"
inspector << "├── predicate:\n"
inspector << inspector.child_node(predicate, "│ ")
inspector << "├── then_keyword_loc: #{inspector.location(then_keyword_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (consequent = self.consequent).nil?
inspector << "├── consequent: ∅\n"
else
inspector << "├── consequent:\n"
inspector << consequent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:if_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:if_node
end
end
# Represents an imaginary number literal.
#
# 1.0i
# ^^^^
class ImaginaryNode < Node
# attr_reader numeric: Node
attr_reader :numeric
# def initialize: (numeric: Node, location: Location) -> void
def initialize(numeric, location)
@numeric = numeric
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_imaginary_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[numeric]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[numeric]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[numeric]
end
# def copy: (**params) -> ImaginaryNode
def copy(**params)
ImaginaryNode.new(
params.fetch(:numeric) { numeric },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ numeric: numeric, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── numeric:\n"
inspector << inspector.child_node(numeric, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:imaginary_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:imaginary_node
end
end
# Represents a node that is implicitly being added to the tree but doesn't
# correspond directly to a node in the source.
#
# { foo: }
# ^^^^
#
# { Foo: }
# ^^^^
class ImplicitNode < Node
# attr_reader value: Node
attr_reader :value
# def initialize: (value: Node, location: Location) -> void
def initialize(value, location)
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_implicit_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[value]
end
# def copy: (**params) -> ImplicitNode
def copy(**params)
ImplicitNode.new(
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ value: value, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:implicit_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:implicit_node
end
end
# Represents using a trailing comma to indicate an implicit rest parameter.
#
# foo { |bar,| }
# ^
#
# foo in [bar,]
# ^
#
# for foo, in bar do end
# ^
#
# foo, = bar
# ^
class ImplicitRestNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_implicit_rest_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> ImplicitRestNode
def copy(**params)
ImplicitRestNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:implicit_rest_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:implicit_rest_node
end
end
# Represents the use of the `in` keyword in a case statement.
#
# case a; in b then c end
# ^^^^^^^^^^^
class InNode < Node
# attr_reader pattern: Node
attr_reader :pattern
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader in_loc: Location
attr_reader :in_loc
# attr_reader then_loc: Location?
attr_reader :then_loc
# def initialize: (pattern: Node, statements: StatementsNode?, in_loc: Location, then_loc: Location?, location: Location) -> void
def initialize(pattern, statements, in_loc, then_loc, location)
@pattern = pattern
@statements = statements
@in_loc = in_loc
@then_loc = then_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_in_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[pattern, statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << pattern
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[pattern, *statements, in_loc, *then_loc]
end
# def copy: (**params) -> InNode
def copy(**params)
InNode.new(
params.fetch(:pattern) { pattern },
params.fetch(:statements) { statements },
params.fetch(:in_loc) { in_loc },
params.fetch(:then_loc) { then_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ pattern: pattern, statements: statements, in_loc: in_loc, then_loc: then_loc, location: location }
end
# def in: () -> String
def in
in_loc.slice
end
# def then: () -> String?
def then
then_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── pattern:\n"
inspector << inspector.child_node(pattern, "│ ")
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── in_loc: #{inspector.location(in_loc)}\n"
inspector << "└── then_loc: #{inspector.location(then_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:in_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:in_node
end
end
# Represents the use of the `&&=` operator on a call to the `[]` method.
#
# foo.bar[baz] &&= value
# ^^^^^^^^^^^^^^^^^^^^^^
class IndexAndWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader block: Node?
attr_reader :block
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, opening_loc: Location, arguments: ArgumentsNode?, closing_loc: Location, block: Node?, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@opening_loc = opening_loc
@arguments = arguments
@closing_loc = closing_loc
@block = block
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_index_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, arguments, block, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << arguments if arguments
compact << block if block
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, opening_loc, *arguments, closing_loc, *block, operator_loc, value]
end
# def copy: (**params) -> IndexAndWriteNode
def copy(**params)
IndexAndWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:arguments) { arguments },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:block) { block },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, opening_loc: opening_loc, arguments: arguments, closing_loc: closing_loc, block: block, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (block = self.block).nil?
inspector << "├── block: ∅\n"
else
inspector << "├── block:\n"
inspector << block.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:index_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:index_and_write_node
end
end
# Represents the use of an assignment operator on a call to `[]`.
#
# foo.bar[baz] += value
# ^^^^^^^^^^^^^^^^^^^^^
class IndexOperatorWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader block: Node?
attr_reader :block
# attr_reader operator: Symbol
attr_reader :operator
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, opening_loc: Location, arguments: ArgumentsNode?, closing_loc: Location, block: Node?, operator: Symbol, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@opening_loc = opening_loc
@arguments = arguments
@closing_loc = closing_loc
@block = block
@operator = operator
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_index_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, arguments, block, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << arguments if arguments
compact << block if block
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, opening_loc, *arguments, closing_loc, *block, operator_loc, value]
end
# def copy: (**params) -> IndexOperatorWriteNode
def copy(**params)
IndexOperatorWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:arguments) { arguments },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:block) { block },
params.fetch(:operator) { operator },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, opening_loc: opening_loc, arguments: arguments, closing_loc: closing_loc, block: block, operator: operator, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (block = self.block).nil?
inspector << "├── block: ∅\n"
else
inspector << "├── block:\n"
inspector << block.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── operator: #{operator.inspect}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:index_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:index_operator_write_node
end
end
# Represents the use of the `||=` operator on a call to `[]`.
#
# foo.bar[baz] ||= value
# ^^^^^^^^^^^^^^^^^^^^^^
class IndexOrWriteNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node?
attr_reader :receiver
# attr_reader call_operator_loc: Location?
attr_reader :call_operator_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader block: Node?
attr_reader :block
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (flags: Integer, receiver: Node?, call_operator_loc: Location?, opening_loc: Location, arguments: ArgumentsNode?, closing_loc: Location, block: Node?, operator_loc: Location, value: Node, location: Location) -> void
def initialize(flags, receiver, call_operator_loc, opening_loc, arguments, closing_loc, block, operator_loc, value, location)
@flags = flags
@receiver = receiver
@call_operator_loc = call_operator_loc
@opening_loc = opening_loc
@arguments = arguments
@closing_loc = closing_loc
@block = block
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_index_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, arguments, block, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver if receiver
compact << arguments if arguments
compact << block if block
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*receiver, *call_operator_loc, opening_loc, *arguments, closing_loc, *block, operator_loc, value]
end
# def copy: (**params) -> IndexOrWriteNode
def copy(**params)
IndexOrWriteNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:call_operator_loc) { call_operator_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:arguments) { arguments },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:block) { block },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, call_operator_loc: call_operator_loc, opening_loc: opening_loc, arguments: arguments, closing_loc: closing_loc, block: block, operator_loc: operator_loc, value: value, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def call_operator: () -> String?
def call_operator
call_operator_loc&.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (receiver = self.receiver).nil?
inspector << "├── receiver: ∅\n"
else
inspector << "├── receiver:\n"
inspector << receiver.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── call_operator_loc: #{inspector.location(call_operator_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (block = self.block).nil?
inspector << "├── block: ∅\n"
else
inspector << "├── block:\n"
inspector << block.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:index_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:index_or_write_node
end
end
# Represents assigning to an index.
#
# foo[bar], = 1
# ^^^^^^^^
#
# begin
# rescue => foo[bar]
# ^^^^^^^^
# end
#
# for foo[bar] in baz do end
# ^^^^^^^^
class IndexTargetNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader receiver: Node
attr_reader :receiver
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader block: Node?
attr_reader :block
# def initialize: (flags: Integer, receiver: Node, opening_loc: Location, arguments: ArgumentsNode?, closing_loc: Location, block: Node?, location: Location) -> void
def initialize(flags, receiver, opening_loc, arguments, closing_loc, block, location)
@flags = flags
@receiver = receiver
@opening_loc = opening_loc
@arguments = arguments
@closing_loc = closing_loc
@block = block
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_index_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[receiver, arguments, block]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << receiver
compact << arguments if arguments
compact << block if block
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[receiver, opening_loc, *arguments, closing_loc, *block]
end
# def copy: (**params) -> IndexTargetNode
def copy(**params)
IndexTargetNode.new(
params.fetch(:flags) { flags },
params.fetch(:receiver) { receiver },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:arguments) { arguments },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:block) { block },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, receiver: receiver, opening_loc: opening_loc, arguments: arguments, closing_loc: closing_loc, block: block, location: location }
end
# def safe_navigation?: () -> bool
def safe_navigation?
flags.anybits?(CallNodeFlags::SAFE_NAVIGATION)
end
# def variable_call?: () -> bool
def variable_call?
flags.anybits?(CallNodeFlags::VARIABLE_CALL)
end
# def attribute_write?: () -> bool
def attribute_write?
flags.anybits?(CallNodeFlags::ATTRIBUTE_WRITE)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("safe_navigation" if safe_navigation?), ("variable_call" if variable_call?), ("attribute_write" if attribute_write?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── receiver:\n"
inspector << inspector.child_node(receiver, "│ ")
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (block = self.block).nil?
inspector << "└── block: ∅\n"
else
inspector << "└── block:\n"
inspector << block.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:index_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:index_target_node
end
end
# Represents the use of the `&&=` operator for assignment to an instance variable.
#
# @target &&= value
# ^^^^^^^^^^^^^^^^^
class InstanceVariableAndWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> InstanceVariableAndWriteNode
def copy(**params)
InstanceVariableAndWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_and_write_node
end
end
# Represents assigning to an instance variable using an operator that isn't `=`.
#
# @target += value
# ^^^^^^^^^^^^^^^^
class InstanceVariableOperatorWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator: Symbol
attr_reader :operator
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, operator: Symbol, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, operator, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@operator = operator
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> InstanceVariableOperatorWriteNode
def copy(**params)
InstanceVariableOperatorWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:operator) { operator },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, operator: operator, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator: #{operator.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to an instance variable.
#
# @target ||= value
# ^^^^^^^^^^^^^^^^^
class InstanceVariableOrWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> InstanceVariableOrWriteNode
def copy(**params)
InstanceVariableOrWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_or_write_node
end
end
# Represents referencing an instance variable.
#
# @foo
# ^^^^
class InstanceVariableReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> InstanceVariableReadNode
def copy(**params)
InstanceVariableReadNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_read_node
end
end
# Represents writing to an instance variable in a context that doesn't have an explicit value.
#
# @foo, @bar = baz
# ^^^^ ^^^^
class InstanceVariableTargetNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> InstanceVariableTargetNode
def copy(**params)
InstanceVariableTargetNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_target_node
end
end
# Represents writing to an instance variable.
#
# @foo = 1
# ^^^^^^^^
class InstanceVariableWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol, name_loc: Location, value: Node, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, value, operator_loc, location)
@name = name
@name_loc = name_loc
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_instance_variable_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value, operator_loc]
end
# def copy: (**params) -> InstanceVariableWriteNode
def copy(**params)
InstanceVariableWriteNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:instance_variable_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:instance_variable_write_node
end
end
# Represents an integer number literal.
#
# 1
# ^
class IntegerNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# def initialize: (flags: Integer, location: Location) -> void
def initialize(flags, location)
@flags = flags
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_integer_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> IntegerNode
def copy(**params)
IntegerNode.new(
params.fetch(:flags) { flags },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, location: location }
end
# def binary?: () -> bool
def binary?
flags.anybits?(IntegerBaseFlags::BINARY)
end
# def decimal?: () -> bool
def decimal?
flags.anybits?(IntegerBaseFlags::DECIMAL)
end
# def octal?: () -> bool
def octal?
flags.anybits?(IntegerBaseFlags::OCTAL)
end
# def hexadecimal?: () -> bool
def hexadecimal?
flags.anybits?(IntegerBaseFlags::HEXADECIMAL)
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("binary" if binary?), ("decimal" if decimal?), ("octal" if octal?), ("hexadecimal" if hexadecimal?)].compact
inspector << "└── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:integer_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:integer_node
end
end
# Represents a regular expression literal that contains interpolation that
# is being used in the predicate of a conditional to implicitly match
# against the last line read by an IO object.
#
# if /foo #{bar} baz/ then end
# ^^^^^^^^^^^^^^^^
class InterpolatedMatchLastLineNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader parts: Array[Node]
attr_reader :parts
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (flags: Integer, opening_loc: Location, parts: Array[Node], closing_loc: Location, location: Location) -> void
def initialize(flags, opening_loc, parts, closing_loc, location)
@flags = flags
@opening_loc = opening_loc
@parts = parts
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_interpolated_match_last_line_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
first = parts.first
first.set_newline_flag(newline_marked) if first
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*parts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*parts]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, *parts, closing_loc]
end
# def copy: (**params) -> InterpolatedMatchLastLineNode
def copy(**params)
InterpolatedMatchLastLineNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:parts) { parts },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, parts: parts, closing_loc: closing_loc, location: location }
end
# def ignore_case?: () -> bool
def ignore_case?
flags.anybits?(RegularExpressionFlags::IGNORE_CASE)
end
# def extended?: () -> bool
def extended?
flags.anybits?(RegularExpressionFlags::EXTENDED)
end
# def multi_line?: () -> bool
def multi_line?
flags.anybits?(RegularExpressionFlags::MULTI_LINE)
end
# def once?: () -> bool
def once?
flags.anybits?(RegularExpressionFlags::ONCE)
end
# def euc_jp?: () -> bool
def euc_jp?
flags.anybits?(RegularExpressionFlags::EUC_JP)
end
# def ascii_8bit?: () -> bool
def ascii_8bit?
flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
end
# def windows_31j?: () -> bool
def windows_31j?
flags.anybits?(RegularExpressionFlags::WINDOWS_31J)
end
# def utf_8?: () -> bool
def utf_8?
flags.anybits?(RegularExpressionFlags::UTF_8)
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_BINARY_ENCODING)
end
# def forced_us_ascii_encoding?: () -> bool
def forced_us_ascii_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_US_ASCII_ENCODING)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("ignore_case" if ignore_case?), ("extended" if extended?), ("multi_line" if multi_line?), ("once" if once?), ("euc_jp" if euc_jp?), ("ascii_8bit" if ascii_8bit?), ("windows_31j" if windows_31j?), ("utf_8" if utf_8?), ("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("forced_us_ascii_encoding" if forced_us_ascii_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── parts: #{inspector.list("#{inspector.prefix}│ ", parts)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:interpolated_match_last_line_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:interpolated_match_last_line_node
end
end
# Represents a regular expression literal that contains interpolation.
#
# /foo #{bar} baz/
# ^^^^^^^^^^^^^^^^
class InterpolatedRegularExpressionNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader parts: Array[Node]
attr_reader :parts
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (flags: Integer, opening_loc: Location, parts: Array[Node], closing_loc: Location, location: Location) -> void
def initialize(flags, opening_loc, parts, closing_loc, location)
@flags = flags
@opening_loc = opening_loc
@parts = parts
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_interpolated_regular_expression_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
first = parts.first
first.set_newline_flag(newline_marked) if first
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*parts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*parts]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, *parts, closing_loc]
end
# def copy: (**params) -> InterpolatedRegularExpressionNode
def copy(**params)
InterpolatedRegularExpressionNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:parts) { parts },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, parts: parts, closing_loc: closing_loc, location: location }
end
# def ignore_case?: () -> bool
def ignore_case?
flags.anybits?(RegularExpressionFlags::IGNORE_CASE)
end
# def extended?: () -> bool
def extended?
flags.anybits?(RegularExpressionFlags::EXTENDED)
end
# def multi_line?: () -> bool
def multi_line?
flags.anybits?(RegularExpressionFlags::MULTI_LINE)
end
# def once?: () -> bool
def once?
flags.anybits?(RegularExpressionFlags::ONCE)
end
# def euc_jp?: () -> bool
def euc_jp?
flags.anybits?(RegularExpressionFlags::EUC_JP)
end
# def ascii_8bit?: () -> bool
def ascii_8bit?
flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
end
# def windows_31j?: () -> bool
def windows_31j?
flags.anybits?(RegularExpressionFlags::WINDOWS_31J)
end
# def utf_8?: () -> bool
def utf_8?
flags.anybits?(RegularExpressionFlags::UTF_8)
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_BINARY_ENCODING)
end
# def forced_us_ascii_encoding?: () -> bool
def forced_us_ascii_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_US_ASCII_ENCODING)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("ignore_case" if ignore_case?), ("extended" if extended?), ("multi_line" if multi_line?), ("once" if once?), ("euc_jp" if euc_jp?), ("ascii_8bit" if ascii_8bit?), ("windows_31j" if windows_31j?), ("utf_8" if utf_8?), ("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("forced_us_ascii_encoding" if forced_us_ascii_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── parts: #{inspector.list("#{inspector.prefix}│ ", parts)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:interpolated_regular_expression_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:interpolated_regular_expression_node
end
end
# Represents a string literal that contains interpolation.
#
# "foo #{bar} baz"
# ^^^^^^^^^^^^^^^^
class InterpolatedStringNode < Node
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader parts: Array[Node]
attr_reader :parts
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (opening_loc: Location?, parts: Array[Node], closing_loc: Location?, location: Location) -> void
def initialize(opening_loc, parts, closing_loc, location)
@opening_loc = opening_loc
@parts = parts
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_interpolated_string_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
first = parts.first
first.set_newline_flag(newline_marked) if first
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*parts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*parts]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*opening_loc, *parts, *closing_loc]
end
# def copy: (**params) -> InterpolatedStringNode
def copy(**params)
InterpolatedStringNode.new(
params.fetch(:opening_loc) { opening_loc },
params.fetch(:parts) { parts },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ opening_loc: opening_loc, parts: parts, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── parts: #{inspector.list("#{inspector.prefix}│ ", parts)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:interpolated_string_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:interpolated_string_node
end
end
# Represents a symbol literal that contains interpolation.
#
# :"foo #{bar} baz"
# ^^^^^^^^^^^^^^^^^
class InterpolatedSymbolNode < Node
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader parts: Array[Node]
attr_reader :parts
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# def initialize: (opening_loc: Location?, parts: Array[Node], closing_loc: Location?, location: Location) -> void
def initialize(opening_loc, parts, closing_loc, location)
@opening_loc = opening_loc
@parts = parts
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_interpolated_symbol_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
first = parts.first
first.set_newline_flag(newline_marked) if first
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*parts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*parts]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*opening_loc, *parts, *closing_loc]
end
# def copy: (**params) -> InterpolatedSymbolNode
def copy(**params)
InterpolatedSymbolNode.new(
params.fetch(:opening_loc) { opening_loc },
params.fetch(:parts) { parts },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ opening_loc: opening_loc, parts: parts, closing_loc: closing_loc, location: location }
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── parts: #{inspector.list("#{inspector.prefix}│ ", parts)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:interpolated_symbol_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:interpolated_symbol_node
end
end
# Represents an xstring literal that contains interpolation.
#
# `foo #{bar} baz`
# ^^^^^^^^^^^^^^^^
class InterpolatedXStringNode < Node
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader parts: Array[Node]
attr_reader :parts
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (opening_loc: Location, parts: Array[Node], closing_loc: Location, location: Location) -> void
def initialize(opening_loc, parts, closing_loc, location)
@opening_loc = opening_loc
@parts = parts
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_interpolated_x_string_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
first = parts.first
first.set_newline_flag(newline_marked) if first
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*parts]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*parts]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, *parts, closing_loc]
end
# def copy: (**params) -> InterpolatedXStringNode
def copy(**params)
InterpolatedXStringNode.new(
params.fetch(:opening_loc) { opening_loc },
params.fetch(:parts) { parts },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ opening_loc: opening_loc, parts: parts, closing_loc: closing_loc, location: location }
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── parts: #{inspector.list("#{inspector.prefix}│ ", parts)}"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:interpolated_x_string_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:interpolated_x_string_node
end
end
# Represents a hash literal without opening and closing braces.
#
# foo(a: b)
# ^^^^
class KeywordHashNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader elements: Array[Node]
attr_reader :elements
# def initialize: (flags: Integer, elements: Array[Node], location: Location) -> void
def initialize(flags, elements, location)
@flags = flags
@elements = elements
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_keyword_hash_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*elements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*elements]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*elements]
end
# def copy: (**params) -> KeywordHashNode
def copy(**params)
KeywordHashNode.new(
params.fetch(:flags) { flags },
params.fetch(:elements) { elements },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, elements: elements, location: location }
end
# def static_keys?: () -> bool
def static_keys?
flags.anybits?(KeywordHashNodeFlags::STATIC_KEYS)
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("static_keys" if static_keys?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "└── elements: #{inspector.list("#{inspector.prefix} ", elements)}"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:keyword_hash_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:keyword_hash_node
end
end
# Represents a keyword rest parameter to a method, block, or lambda definition.
#
# def a(**b)
# ^^^
# end
class KeywordRestParameterNode < Node
# attr_reader name: Symbol?
attr_reader :name
# attr_reader name_loc: Location?
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol?, name_loc: Location?, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, operator_loc, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_keyword_rest_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*name_loc, operator_loc]
end
# def copy: (**params) -> KeywordRestParameterNode
def copy(**params)
KeywordRestParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (name = self.name).nil?
inspector << "├── name: ∅\n"
else
inspector << "├── name: #{name.inspect}\n"
end
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:keyword_rest_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:keyword_rest_parameter_node
end
end
# Represents using a lambda literal (not the lambda method call).
#
# ->(value) { value * 2 }
# ^^^^^^^^^^^^^^^^^^^^^^^
class LambdaNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader locals_body_index: Integer
attr_reader :locals_body_index
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader parameters: Node?
attr_reader :parameters
# attr_reader body: Node?
attr_reader :body
# def initialize: (locals: Array[Symbol], locals_body_index: Integer, operator_loc: Location, opening_loc: Location, closing_loc: Location, parameters: Node?, body: Node?, location: Location) -> void
def initialize(locals, locals_body_index, operator_loc, opening_loc, closing_loc, parameters, body, location)
@locals = locals
@locals_body_index = locals_body_index
@operator_loc = operator_loc
@opening_loc = opening_loc
@closing_loc = closing_loc
@parameters = parameters
@body = body
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_lambda_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[parameters, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << parameters if parameters
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[operator_loc, opening_loc, closing_loc, *parameters, *body]
end
# def copy: (**params) -> LambdaNode
def copy(**params)
LambdaNode.new(
params.fetch(:locals) { locals },
params.fetch(:locals_body_index) { locals_body_index },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:parameters) { parameters },
params.fetch(:body) { body },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, locals_body_index: locals_body_index, operator_loc: operator_loc, opening_loc: opening_loc, closing_loc: closing_loc, parameters: parameters, body: body, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── locals_body_index: #{locals_body_index.inspect}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
if (parameters = self.parameters).nil?
inspector << "├── parameters: ∅\n"
else
inspector << "├── parameters:\n"
inspector << parameters.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (body = self.body).nil?
inspector << "└── body: ∅\n"
else
inspector << "└── body:\n"
inspector << body.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:lambda_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:lambda_node
end
end
# Represents the use of the `&&=` operator for assignment to a local variable.
#
# target &&= value
# ^^^^^^^^^^^^^^^^
class LocalVariableAndWriteNode < Node
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader name: Symbol
attr_reader :name
# attr_reader depth: Integer
attr_reader :depth
# def initialize: (name_loc: Location, operator_loc: Location, value: Node, name: Symbol, depth: Integer, location: Location) -> void
def initialize(name_loc, operator_loc, value, name, depth, location)
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@name = name
@depth = depth
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_and_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> LocalVariableAndWriteNode
def copy(**params)
LocalVariableAndWriteNode.new(
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:name) { name },
params.fetch(:depth) { depth },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name_loc: name_loc, operator_loc: operator_loc, value: value, name: name, depth: depth, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── name: #{name.inspect}\n"
inspector << "└── depth: #{depth.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_and_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_and_write_node
end
end
# Represents assigning to a local variable using an operator that isn't `=`.
#
# target += value
# ^^^^^^^^^^^^^^^
class LocalVariableOperatorWriteNode < Node
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader name: Symbol
attr_reader :name
# attr_reader operator: Symbol
attr_reader :operator
# attr_reader depth: Integer
attr_reader :depth
# def initialize: (name_loc: Location, operator_loc: Location, value: Node, name: Symbol, operator: Symbol, depth: Integer, location: Location) -> void
def initialize(name_loc, operator_loc, value, name, operator, depth, location)
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@name = name
@operator = operator
@depth = depth
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_operator_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> LocalVariableOperatorWriteNode
def copy(**params)
LocalVariableOperatorWriteNode.new(
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:name) { name },
params.fetch(:operator) { operator },
params.fetch(:depth) { depth },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name_loc: name_loc, operator_loc: operator_loc, value: value, name: name, operator: operator, depth: depth, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── name: #{name.inspect}\n"
inspector << "├── operator: #{operator.inspect}\n"
inspector << "└── depth: #{depth.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_operator_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_operator_write_node
end
end
# Represents the use of the `||=` operator for assignment to a local variable.
#
# target ||= value
# ^^^^^^^^^^^^^^^^
class LocalVariableOrWriteNode < Node
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# attr_reader name: Symbol
attr_reader :name
# attr_reader depth: Integer
attr_reader :depth
# def initialize: (name_loc: Location, operator_loc: Location, value: Node, name: Symbol, depth: Integer, location: Location) -> void
def initialize(name_loc, operator_loc, value, name, depth, location)
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@name = name
@depth = depth
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_or_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> LocalVariableOrWriteNode
def copy(**params)
LocalVariableOrWriteNode.new(
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:name) { name },
params.fetch(:depth) { depth },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name_loc: name_loc, operator_loc: operator_loc, value: value, name: name, depth: depth, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── name: #{name.inspect}\n"
inspector << "└── depth: #{depth.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_or_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_or_write_node
end
end
# Represents reading a local variable. Note that this requires that a local
# variable of the same name has already been written to in the same scope,
# otherwise it is parsed as a method call.
#
# foo
# ^^^
class LocalVariableReadNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader depth: Integer
attr_reader :depth
# def initialize: (name: Symbol, depth: Integer, location: Location) -> void
def initialize(name, depth, location)
@name = name
@depth = depth
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> LocalVariableReadNode
def copy(**params)
LocalVariableReadNode.new(
params.fetch(:name) { name },
params.fetch(:depth) { depth },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, depth: depth, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "└── depth: #{depth.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_read_node
end
end
# Represents writing to a local variable in a context that doesn't have an explicit value.
#
# foo, bar = baz
# ^^^ ^^^
class LocalVariableTargetNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader depth: Integer
attr_reader :depth
# def initialize: (name: Symbol, depth: Integer, location: Location) -> void
def initialize(name, depth, location)
@name = name
@depth = depth
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> LocalVariableTargetNode
def copy(**params)
LocalVariableTargetNode.new(
params.fetch(:name) { name },
params.fetch(:depth) { depth },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, depth: depth, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "└── depth: #{depth.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_target_node
end
end
# Represents writing to a local variable.
#
# foo = 1
# ^^^^^^^
class LocalVariableWriteNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader depth: Integer
attr_reader :depth
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol, depth: Integer, name_loc: Location, value: Node, operator_loc: Location, location: Location) -> void
def initialize(name, depth, name_loc, value, operator_loc, location)
@name = name
@depth = depth
@name_loc = name_loc
@value = value
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_local_variable_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value, operator_loc]
end
# def copy: (**params) -> LocalVariableWriteNode
def copy(**params)
LocalVariableWriteNode.new(
params.fetch(:name) { name },
params.fetch(:depth) { depth },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, depth: depth, name_loc: name_loc, value: value, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── depth: #{depth.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:local_variable_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:local_variable_write_node
end
end
# Represents a regular expression literal used in the predicate of a
# conditional to implicitly match against the last line read by an IO
# object.
#
# if /foo/i then end
# ^^^^^^
class MatchLastLineNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader content_loc: Location
attr_reader :content_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader unescaped: String
attr_reader :unescaped
# def initialize: (flags: Integer, opening_loc: Location, content_loc: Location, closing_loc: Location, unescaped: String, location: Location) -> void
def initialize(flags, opening_loc, content_loc, closing_loc, unescaped, location)
@flags = flags
@opening_loc = opening_loc
@content_loc = content_loc
@closing_loc = closing_loc
@unescaped = unescaped
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_match_last_line_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, content_loc, closing_loc]
end
# def copy: (**params) -> MatchLastLineNode
def copy(**params)
MatchLastLineNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:content_loc) { content_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:unescaped) { unescaped },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, content_loc: content_loc, closing_loc: closing_loc, unescaped: unescaped, location: location }
end
# def ignore_case?: () -> bool
def ignore_case?
flags.anybits?(RegularExpressionFlags::IGNORE_CASE)
end
# def extended?: () -> bool
def extended?
flags.anybits?(RegularExpressionFlags::EXTENDED)
end
# def multi_line?: () -> bool
def multi_line?
flags.anybits?(RegularExpressionFlags::MULTI_LINE)
end
# def once?: () -> bool
def once?
flags.anybits?(RegularExpressionFlags::ONCE)
end
# def euc_jp?: () -> bool
def euc_jp?
flags.anybits?(RegularExpressionFlags::EUC_JP)
end
# def ascii_8bit?: () -> bool
def ascii_8bit?
flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
end
# def windows_31j?: () -> bool
def windows_31j?
flags.anybits?(RegularExpressionFlags::WINDOWS_31J)
end
# def utf_8?: () -> bool
def utf_8?
flags.anybits?(RegularExpressionFlags::UTF_8)
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_BINARY_ENCODING)
end
# def forced_us_ascii_encoding?: () -> bool
def forced_us_ascii_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_US_ASCII_ENCODING)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def content: () -> String
def content
content_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("ignore_case" if ignore_case?), ("extended" if extended?), ("multi_line" if multi_line?), ("once" if once?), ("euc_jp" if euc_jp?), ("ascii_8bit" if ascii_8bit?), ("windows_31j" if windows_31j?), ("utf_8" if utf_8?), ("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("forced_us_ascii_encoding" if forced_us_ascii_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── content_loc: #{inspector.location(content_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "└── unescaped: #{unescaped.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:match_last_line_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:match_last_line_node
end
end
# Represents the use of the modifier `in` operator.
#
# foo in bar
# ^^^^^^^^^^
class MatchPredicateNode < Node
# attr_reader value: Node
attr_reader :value
# attr_reader pattern: Node
attr_reader :pattern
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (value: Node, pattern: Node, operator_loc: Location, location: Location) -> void
def initialize(value, pattern, operator_loc, location)
@value = value
@pattern = pattern
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_match_predicate_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value, pattern]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value, pattern]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[value, pattern, operator_loc]
end
# def copy: (**params) -> MatchPredicateNode
def copy(**params)
MatchPredicateNode.new(
params.fetch(:value) { value },
params.fetch(:pattern) { pattern },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ value: value, pattern: pattern, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── pattern:\n"
inspector << inspector.child_node(pattern, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:match_predicate_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:match_predicate_node
end
end
# Represents the use of the `=>` operator.
#
# foo => bar
# ^^^^^^^^^^
class MatchRequiredNode < Node
# attr_reader value: Node
attr_reader :value
# attr_reader pattern: Node
attr_reader :pattern
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (value: Node, pattern: Node, operator_loc: Location, location: Location) -> void
def initialize(value, pattern, operator_loc, location)
@value = value
@pattern = pattern
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_match_required_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value, pattern]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value, pattern]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[value, pattern, operator_loc]
end
# def copy: (**params) -> MatchRequiredNode
def copy(**params)
MatchRequiredNode.new(
params.fetch(:value) { value },
params.fetch(:pattern) { pattern },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ value: value, pattern: pattern, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── value:\n"
inspector << inspector.child_node(value, "│ ")
inspector << "├── pattern:\n"
inspector << inspector.child_node(pattern, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:match_required_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:match_required_node
end
end
# Represents writing local variables using a regular expression match with
# named capture groups.
#
# /(?<foo>bar)/ =~ baz
# ^^^^^^^^^^^^^^^^^^^^
class MatchWriteNode < Node
# attr_reader call: CallNode
attr_reader :call
# attr_reader targets: Array[Node]
attr_reader :targets
# def initialize: (call: CallNode, targets: Array[Node], location: Location) -> void
def initialize(call, targets, location)
@call = call
@targets = targets
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_match_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[call, *targets]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[call, *targets]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[call, *targets]
end
# def copy: (**params) -> MatchWriteNode
def copy(**params)
MatchWriteNode.new(
params.fetch(:call) { call },
params.fetch(:targets) { targets },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ call: call, targets: targets, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── call:\n"
inspector << inspector.child_node(call, "│ ")
inspector << "└── targets: #{inspector.list("#{inspector.prefix} ", targets)}"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:match_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:match_write_node
end
end
# Represents a node that is missing from the source and results in a syntax
# error.
class MissingNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_missing_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> MissingNode
def copy(**params)
MissingNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:missing_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:missing_node
end
end
# Represents a module declaration involving the `module` keyword.
#
# module Foo end
# ^^^^^^^^^^^^^^
class ModuleNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader module_keyword_loc: Location
attr_reader :module_keyword_loc
# attr_reader constant_path: Node
attr_reader :constant_path
# attr_reader body: Node?
attr_reader :body
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# attr_reader name: Symbol
attr_reader :name
# def initialize: (locals: Array[Symbol], module_keyword_loc: Location, constant_path: Node, body: Node?, end_keyword_loc: Location, name: Symbol, location: Location) -> void
def initialize(locals, module_keyword_loc, constant_path, body, end_keyword_loc, name, location)
@locals = locals
@module_keyword_loc = module_keyword_loc
@constant_path = constant_path
@body = body
@end_keyword_loc = end_keyword_loc
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_module_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[constant_path, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << constant_path
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[module_keyword_loc, constant_path, *body, end_keyword_loc]
end
# def copy: (**params) -> ModuleNode
def copy(**params)
ModuleNode.new(
params.fetch(:locals) { locals },
params.fetch(:module_keyword_loc) { module_keyword_loc },
params.fetch(:constant_path) { constant_path },
params.fetch(:body) { body },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, module_keyword_loc: module_keyword_loc, constant_path: constant_path, body: body, end_keyword_loc: end_keyword_loc, name: name, location: location }
end
# def module_keyword: () -> String
def module_keyword
module_keyword_loc.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── module_keyword_loc: #{inspector.location(module_keyword_loc)}\n"
inspector << "├── constant_path:\n"
inspector << inspector.child_node(constant_path, "│ ")
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:module_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:module_node
end
end
# Represents a multi-target expression.
#
# a, (b, c) = 1, 2, 3
# ^^^^^^
class MultiTargetNode < Node
# attr_reader lefts: Array[Node]
attr_reader :lefts
# attr_reader rest: Node?
attr_reader :rest
# attr_reader rights: Array[Node]
attr_reader :rights
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# def initialize: (lefts: Array[Node], rest: Node?, rights: Array[Node], lparen_loc: Location?, rparen_loc: Location?, location: Location) -> void
def initialize(lefts, rest, rights, lparen_loc, rparen_loc, location)
@lefts = lefts
@rest = rest
@rights = rights
@lparen_loc = lparen_loc
@rparen_loc = rparen_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_multi_target_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*lefts, rest, *rights]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact.concat(lefts)
compact << rest if rest
compact.concat(rights)
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*lefts, *rest, *rights, *lparen_loc, *rparen_loc]
end
# def copy: (**params) -> MultiTargetNode
def copy(**params)
MultiTargetNode.new(
params.fetch(:lefts) { lefts },
params.fetch(:rest) { rest },
params.fetch(:rights) { rights },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ lefts: lefts, rest: rest, rights: rights, lparen_loc: lparen_loc, rparen_loc: rparen_loc, location: location }
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── lefts: #{inspector.list("#{inspector.prefix}│ ", lefts)}"
if (rest = self.rest).nil?
inspector << "├── rest: ∅\n"
else
inspector << "├── rest:\n"
inspector << rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── rights: #{inspector.list("#{inspector.prefix}│ ", rights)}"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
inspector << "└── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:multi_target_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:multi_target_node
end
end
# Represents a write to a multi-target expression.
#
# a, b, c = 1, 2, 3
# ^^^^^^^^^^^^^^^^^
class MultiWriteNode < Node
# attr_reader lefts: Array[Node]
attr_reader :lefts
# attr_reader rest: Node?
attr_reader :rest
# attr_reader rights: Array[Node]
attr_reader :rights
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (lefts: Array[Node], rest: Node?, rights: Array[Node], lparen_loc: Location?, rparen_loc: Location?, operator_loc: Location, value: Node, location: Location) -> void
def initialize(lefts, rest, rights, lparen_loc, rparen_loc, operator_loc, value, location)
@lefts = lefts
@rest = rest
@rights = rights
@lparen_loc = lparen_loc
@rparen_loc = rparen_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_multi_write_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*lefts, rest, *rights, value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact.concat(lefts)
compact << rest if rest
compact.concat(rights)
compact << value
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*lefts, *rest, *rights, *lparen_loc, *rparen_loc, operator_loc, value]
end
# def copy: (**params) -> MultiWriteNode
def copy(**params)
MultiWriteNode.new(
params.fetch(:lefts) { lefts },
params.fetch(:rest) { rest },
params.fetch(:rights) { rights },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ lefts: lefts, rest: rest, rights: rights, lparen_loc: lparen_loc, rparen_loc: rparen_loc, operator_loc: operator_loc, value: value, location: location }
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── lefts: #{inspector.list("#{inspector.prefix}│ ", lefts)}"
if (rest = self.rest).nil?
inspector << "├── rest: ∅\n"
else
inspector << "├── rest:\n"
inspector << rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── rights: #{inspector.list("#{inspector.prefix}│ ", rights)}"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
inspector << "├── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:multi_write_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:multi_write_node
end
end
# Represents the use of the `next` keyword.
#
# next 1
# ^^^^^^
class NextNode < Node
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (arguments: ArgumentsNode?, keyword_loc: Location, location: Location) -> void
def initialize(arguments, keyword_loc, location)
@arguments = arguments
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_next_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[arguments]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << arguments if arguments
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*arguments, keyword_loc]
end
# def copy: (**params) -> NextNode
def copy(**params)
NextNode.new(
params.fetch(:arguments) { arguments },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ arguments: arguments, keyword_loc: keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:next_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:next_node
end
end
# Represents the use of the `nil` keyword.
#
# nil
# ^^^
class NilNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_nil_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> NilNode
def copy(**params)
NilNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:nil_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:nil_node
end
end
# Represents the use of `**nil` inside method arguments.
#
# def a(**nil)
# ^^^^^
# end
class NoKeywordsParameterNode < Node
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (operator_loc: Location, keyword_loc: Location, location: Location) -> void
def initialize(operator_loc, keyword_loc, location)
@operator_loc = operator_loc
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_no_keywords_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[operator_loc, keyword_loc]
end
# def copy: (**params) -> NoKeywordsParameterNode
def copy(**params)
NoKeywordsParameterNode.new(
params.fetch(:operator_loc) { operator_loc },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ operator_loc: operator_loc, keyword_loc: keyword_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:no_keywords_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:no_keywords_parameter_node
end
end
# Represents an implicit set of parameters through the use of numbered
# parameters within a block or lambda.
#
# -> { _1 + _2 }
# ^^^^^^^^^^^^^^
class NumberedParametersNode < Node
# attr_reader maximum: Integer
attr_reader :maximum
# def initialize: (maximum: Integer, location: Location) -> void
def initialize(maximum, location)
@maximum = maximum
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_numbered_parameters_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> NumberedParametersNode
def copy(**params)
NumberedParametersNode.new(
params.fetch(:maximum) { maximum },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ maximum: maximum, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── maximum: #{maximum.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:numbered_parameters_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:numbered_parameters_node
end
end
# Represents reading a numbered reference to a capture in the previous match.
#
# $1
# ^^
class NumberedReferenceReadNode < Node
# attr_reader number: Integer
attr_reader :number
# def initialize: (number: Integer, location: Location) -> void
def initialize(number, location)
@number = number
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_numbered_reference_read_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> NumberedReferenceReadNode
def copy(**params)
NumberedReferenceReadNode.new(
params.fetch(:number) { number },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ number: number, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── number: #{number.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:numbered_reference_read_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:numbered_reference_read_node
end
end
# Represents an optional keyword parameter to a method, block, or lambda definition.
#
# def a(b: 1)
# ^^^^
# end
class OptionalKeywordParameterNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, value, location)
@name = name
@name_loc = name_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_optional_keyword_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, value]
end
# def copy: (**params) -> OptionalKeywordParameterNode
def copy(**params)
OptionalKeywordParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, value: value, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:optional_keyword_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:optional_keyword_parameter_node
end
end
# Represents an optional parameter to a method, block, or lambda definition.
#
# def a(b = 1)
# ^^^^^
# end
class OptionalParameterNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader value: Node
attr_reader :value
# def initialize: (name: Symbol, name_loc: Location, operator_loc: Location, value: Node, location: Location) -> void
def initialize(name, name_loc, operator_loc, value, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@value = value
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_optional_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[value]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[value]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc, operator_loc, value]
end
# def copy: (**params) -> OptionalParameterNode
def copy(**params)
OptionalParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:value) { value },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, value: value, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "└── value:\n"
inspector << inspector.child_node(value, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:optional_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:optional_parameter_node
end
end
# Represents the use of the `||` operator or the `or` keyword.
#
# left or right
# ^^^^^^^^^^^^^
class OrNode < Node
# attr_reader left: Node
attr_reader :left
# attr_reader right: Node
attr_reader :right
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (left: Node, right: Node, operator_loc: Location, location: Location) -> void
def initialize(left, right, operator_loc, location)
@left = left
@right = right
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_or_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[left, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[left, right]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[left, right, operator_loc]
end
# def copy: (**params) -> OrNode
def copy(**params)
OrNode.new(
params.fetch(:left) { left },
params.fetch(:right) { right },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ left: left, right: right, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── left:\n"
inspector << inspector.child_node(left, "│ ")
inspector << "├── right:\n"
inspector << inspector.child_node(right, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:or_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:or_node
end
end
# Represents the list of parameters on a method, block, or lambda definition.
#
# def a(b, c, d)
# ^^^^^^^
# end
class ParametersNode < Node
# attr_reader requireds: Array[Node]
attr_reader :requireds
# attr_reader optionals: Array[Node]
attr_reader :optionals
# attr_reader rest: Node?
attr_reader :rest
# attr_reader posts: Array[Node]
attr_reader :posts
# attr_reader keywords: Array[Node]
attr_reader :keywords
# attr_reader keyword_rest: Node?
attr_reader :keyword_rest
# attr_reader block: BlockParameterNode?
attr_reader :block
# def initialize: (requireds: Array[Node], optionals: Array[Node], rest: Node?, posts: Array[Node], keywords: Array[Node], keyword_rest: Node?, block: BlockParameterNode?, location: Location) -> void
def initialize(requireds, optionals, rest, posts, keywords, keyword_rest, block, location)
@requireds = requireds
@optionals = optionals
@rest = rest
@posts = posts
@keywords = keywords
@keyword_rest = keyword_rest
@block = block
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_parameters_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*requireds, *optionals, rest, *posts, *keywords, keyword_rest, block]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact.concat(requireds)
compact.concat(optionals)
compact << rest if rest
compact.concat(posts)
compact.concat(keywords)
compact << keyword_rest if keyword_rest
compact << block if block
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*requireds, *optionals, *rest, *posts, *keywords, *keyword_rest, *block]
end
# def copy: (**params) -> ParametersNode
def copy(**params)
ParametersNode.new(
params.fetch(:requireds) { requireds },
params.fetch(:optionals) { optionals },
params.fetch(:rest) { rest },
params.fetch(:posts) { posts },
params.fetch(:keywords) { keywords },
params.fetch(:keyword_rest) { keyword_rest },
params.fetch(:block) { block },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ requireds: requireds, optionals: optionals, rest: rest, posts: posts, keywords: keywords, keyword_rest: keyword_rest, block: block, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── requireds: #{inspector.list("#{inspector.prefix}│ ", requireds)}"
inspector << "├── optionals: #{inspector.list("#{inspector.prefix}│ ", optionals)}"
if (rest = self.rest).nil?
inspector << "├── rest: ∅\n"
else
inspector << "├── rest:\n"
inspector << rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── posts: #{inspector.list("#{inspector.prefix}│ ", posts)}"
inspector << "├── keywords: #{inspector.list("#{inspector.prefix}│ ", keywords)}"
if (keyword_rest = self.keyword_rest).nil?
inspector << "├── keyword_rest: ∅\n"
else
inspector << "├── keyword_rest:\n"
inspector << keyword_rest.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (block = self.block).nil?
inspector << "└── block: ∅\n"
else
inspector << "└── block:\n"
inspector << block.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:parameters_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:parameters_node
end
end
# Represents a parenthesized expression
#
# (10 + 34)
# ^^^^^^^^^
class ParenthesesNode < Node
# attr_reader body: Node?
attr_reader :body
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (body: Node?, opening_loc: Location, closing_loc: Location, location: Location) -> void
def initialize(body, opening_loc, closing_loc, location)
@body = body
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_parentheses_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
# Never mark ParenthesesNode with a newline flag, mark children instead
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*body, opening_loc, closing_loc]
end
# def copy: (**params) -> ParenthesesNode
def copy(**params)
ParenthesesNode.new(
params.fetch(:body) { body },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ body: body, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:parentheses_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:parentheses_node
end
end
# Represents the use of the `^` operator for pinning an expression in a
# pattern matching expression.
#
# foo in ^(bar)
# ^^^^^^
class PinnedExpressionNode < Node
# attr_reader expression: Node
attr_reader :expression
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader lparen_loc: Location
attr_reader :lparen_loc
# attr_reader rparen_loc: Location
attr_reader :rparen_loc
# def initialize: (expression: Node, operator_loc: Location, lparen_loc: Location, rparen_loc: Location, location: Location) -> void
def initialize(expression, operator_loc, lparen_loc, rparen_loc, location)
@expression = expression
@operator_loc = operator_loc
@lparen_loc = lparen_loc
@rparen_loc = rparen_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_pinned_expression_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[expression]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[expression]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[expression, operator_loc, lparen_loc, rparen_loc]
end
# def copy: (**params) -> PinnedExpressionNode
def copy(**params)
PinnedExpressionNode.new(
params.fetch(:expression) { expression },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ expression: expression, operator_loc: operator_loc, lparen_loc: lparen_loc, rparen_loc: rparen_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def lparen: () -> String
def lparen
lparen_loc.slice
end
# def rparen: () -> String
def rparen
rparen_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── expression:\n"
inspector << inspector.child_node(expression, "│ ")
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
inspector << "└── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:pinned_expression_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:pinned_expression_node
end
end
# Represents the use of the `^` operator for pinning a variable in a pattern
# matching expression.
#
# foo in ^bar
# ^^^^
class PinnedVariableNode < Node
# attr_reader variable: Node
attr_reader :variable
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (variable: Node, operator_loc: Location, location: Location) -> void
def initialize(variable, operator_loc, location)
@variable = variable
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_pinned_variable_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[variable]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[variable]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[variable, operator_loc]
end
# def copy: (**params) -> PinnedVariableNode
def copy(**params)
PinnedVariableNode.new(
params.fetch(:variable) { variable },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ variable: variable, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── variable:\n"
inspector << inspector.child_node(variable, "│ ")
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:pinned_variable_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:pinned_variable_node
end
end
# Represents the use of the `END` keyword.
#
# END { foo }
# ^^^^^^^^^^^
class PostExecutionNode < Node
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (statements: StatementsNode?, keyword_loc: Location, opening_loc: Location, closing_loc: Location, location: Location) -> void
def initialize(statements, keyword_loc, opening_loc, closing_loc, location)
@statements = statements
@keyword_loc = keyword_loc
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_post_execution_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*statements, keyword_loc, opening_loc, closing_loc]
end
# def copy: (**params) -> PostExecutionNode
def copy(**params)
PostExecutionNode.new(
params.fetch(:statements) { statements },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ statements: statements, keyword_loc: keyword_loc, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:post_execution_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:post_execution_node
end
end
# Represents the use of the `BEGIN` keyword.
#
# BEGIN { foo }
# ^^^^^^^^^^^^^
class PreExecutionNode < Node
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# def initialize: (statements: StatementsNode?, keyword_loc: Location, opening_loc: Location, closing_loc: Location, location: Location) -> void
def initialize(statements, keyword_loc, opening_loc, closing_loc, location)
@statements = statements
@keyword_loc = keyword_loc
@opening_loc = opening_loc
@closing_loc = closing_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_pre_execution_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*statements, keyword_loc, opening_loc, closing_loc]
end
# def copy: (**params) -> PreExecutionNode
def copy(**params)
PreExecutionNode.new(
params.fetch(:statements) { statements },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ statements: statements, keyword_loc: keyword_loc, opening_loc: opening_loc, closing_loc: closing_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "└── closing_loc: #{inspector.location(closing_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:pre_execution_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:pre_execution_node
end
end
# The top level node of any parse tree.
class ProgramNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader statements: StatementsNode
attr_reader :statements
# def initialize: (locals: Array[Symbol], statements: StatementsNode, location: Location) -> void
def initialize(locals, statements, location)
@locals = locals
@statements = statements
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_program_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[statements]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[statements]
end
# def copy: (**params) -> ProgramNode
def copy(**params)
ProgramNode.new(
params.fetch(:locals) { locals },
params.fetch(:statements) { statements },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, statements: statements, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "└── statements:\n"
inspector << inspector.child_node(statements, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:program_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:program_node
end
end
# Represents the use of the `..` or `...` operators.
#
# 1..2
# ^^^^
#
# c if a =~ /left/ ... b =~ /right/
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class RangeNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader left: Node?
attr_reader :left
# attr_reader right: Node?
attr_reader :right
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (flags: Integer, left: Node?, right: Node?, operator_loc: Location, location: Location) -> void
def initialize(flags, left, right, operator_loc, location)
@flags = flags
@left = left
@right = right
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_range_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[left, right]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << left if left
compact << right if right
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*left, *right, operator_loc]
end
# def copy: (**params) -> RangeNode
def copy(**params)
RangeNode.new(
params.fetch(:flags) { flags },
params.fetch(:left) { left },
params.fetch(:right) { right },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, left: left, right: right, operator_loc: operator_loc, location: location }
end
# def exclude_end?: () -> bool
def exclude_end?
flags.anybits?(RangeFlags::EXCLUDE_END)
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("exclude_end" if exclude_end?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
if (left = self.left).nil?
inspector << "├── left: ∅\n"
else
inspector << "├── left:\n"
inspector << left.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (right = self.right).nil?
inspector << "├── right: ∅\n"
else
inspector << "├── right:\n"
inspector << right.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:range_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:range_node
end
end
# Represents a rational number literal.
#
# 1.0r
# ^^^^
class RationalNode < Node
# attr_reader numeric: Node
attr_reader :numeric
# def initialize: (numeric: Node, location: Location) -> void
def initialize(numeric, location)
@numeric = numeric
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_rational_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[numeric]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[numeric]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[numeric]
end
# def copy: (**params) -> RationalNode
def copy(**params)
RationalNode.new(
params.fetch(:numeric) { numeric },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ numeric: numeric, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── numeric:\n"
inspector << inspector.child_node(numeric, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:rational_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:rational_node
end
end
# Represents the use of the `redo` keyword.
#
# redo
# ^^^^
class RedoNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_redo_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> RedoNode
def copy(**params)
RedoNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:redo_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:redo_node
end
end
# Represents a regular expression literal with no interpolation.
#
# /foo/i
# ^^^^^^
class RegularExpressionNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader content_loc: Location
attr_reader :content_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader unescaped: String
attr_reader :unescaped
# def initialize: (flags: Integer, opening_loc: Location, content_loc: Location, closing_loc: Location, unescaped: String, location: Location) -> void
def initialize(flags, opening_loc, content_loc, closing_loc, unescaped, location)
@flags = flags
@opening_loc = opening_loc
@content_loc = content_loc
@closing_loc = closing_loc
@unescaped = unescaped
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_regular_expression_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, content_loc, closing_loc]
end
# def copy: (**params) -> RegularExpressionNode
def copy(**params)
RegularExpressionNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:content_loc) { content_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:unescaped) { unescaped },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, content_loc: content_loc, closing_loc: closing_loc, unescaped: unescaped, location: location }
end
# def ignore_case?: () -> bool
def ignore_case?
flags.anybits?(RegularExpressionFlags::IGNORE_CASE)
end
# def extended?: () -> bool
def extended?
flags.anybits?(RegularExpressionFlags::EXTENDED)
end
# def multi_line?: () -> bool
def multi_line?
flags.anybits?(RegularExpressionFlags::MULTI_LINE)
end
# def once?: () -> bool
def once?
flags.anybits?(RegularExpressionFlags::ONCE)
end
# def euc_jp?: () -> bool
def euc_jp?
flags.anybits?(RegularExpressionFlags::EUC_JP)
end
# def ascii_8bit?: () -> bool
def ascii_8bit?
flags.anybits?(RegularExpressionFlags::ASCII_8BIT)
end
# def windows_31j?: () -> bool
def windows_31j?
flags.anybits?(RegularExpressionFlags::WINDOWS_31J)
end
# def utf_8?: () -> bool
def utf_8?
flags.anybits?(RegularExpressionFlags::UTF_8)
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_BINARY_ENCODING)
end
# def forced_us_ascii_encoding?: () -> bool
def forced_us_ascii_encoding?
flags.anybits?(RegularExpressionFlags::FORCED_US_ASCII_ENCODING)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def content: () -> String
def content
content_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("ignore_case" if ignore_case?), ("extended" if extended?), ("multi_line" if multi_line?), ("once" if once?), ("euc_jp" if euc_jp?), ("ascii_8bit" if ascii_8bit?), ("windows_31j" if windows_31j?), ("utf_8" if utf_8?), ("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("forced_us_ascii_encoding" if forced_us_ascii_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── content_loc: #{inspector.location(content_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "└── unescaped: #{unescaped.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:regular_expression_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:regular_expression_node
end
end
# Represents a required keyword parameter to a method, block, or lambda definition.
#
# def a(b: )
# ^^
# end
class RequiredKeywordParameterNode < Node
# attr_reader name: Symbol
attr_reader :name
# attr_reader name_loc: Location
attr_reader :name_loc
# def initialize: (name: Symbol, name_loc: Location, location: Location) -> void
def initialize(name, name_loc, location)
@name = name
@name_loc = name_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_required_keyword_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[name_loc]
end
# def copy: (**params) -> RequiredKeywordParameterNode
def copy(**params)
RequiredKeywordParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── name: #{name.inspect}\n"
inspector << "└── name_loc: #{inspector.location(name_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:required_keyword_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:required_keyword_parameter_node
end
end
# Represents a required parameter to a method, block, or lambda definition.
#
# def a(b)
# ^
# end
class RequiredParameterNode < Node
# attr_reader name: Symbol
attr_reader :name
# def initialize: (name: Symbol, location: Location) -> void
def initialize(name, location)
@name = name
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_required_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> RequiredParameterNode
def copy(**params)
RequiredParameterNode.new(
params.fetch(:name) { name },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── name: #{name.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:required_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:required_parameter_node
end
end
# Represents an expression modified with a rescue.
#
# foo rescue nil
# ^^^^^^^^^^^^^^
class RescueModifierNode < Node
# attr_reader expression: Node
attr_reader :expression
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader rescue_expression: Node
attr_reader :rescue_expression
# def initialize: (expression: Node, keyword_loc: Location, rescue_expression: Node, location: Location) -> void
def initialize(expression, keyword_loc, rescue_expression, location)
@expression = expression
@keyword_loc = keyword_loc
@rescue_expression = rescue_expression
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_rescue_modifier_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
expression.set_newline_flag(newline_marked)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[expression, rescue_expression]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[expression, rescue_expression]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[expression, keyword_loc, rescue_expression]
end
# def copy: (**params) -> RescueModifierNode
def copy(**params)
RescueModifierNode.new(
params.fetch(:expression) { expression },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:rescue_expression) { rescue_expression },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ expression: expression, keyword_loc: keyword_loc, rescue_expression: rescue_expression, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── expression:\n"
inspector << inspector.child_node(expression, "│ ")
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "└── rescue_expression:\n"
inspector << inspector.child_node(rescue_expression, " ")
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:rescue_modifier_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:rescue_modifier_node
end
end
# Represents a rescue statement.
#
# begin
# rescue Foo, *splat, Bar => ex
# foo
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# end
#
# `Foo, *splat, Bar` are in the `exceptions` field.
# `ex` is in the `exception` field.
class RescueNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader exceptions: Array[Node]
attr_reader :exceptions
# attr_reader operator_loc: Location?
attr_reader :operator_loc
# attr_reader reference: Node?
attr_reader :reference
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader consequent: RescueNode?
attr_reader :consequent
# def initialize: (keyword_loc: Location, exceptions: Array[Node], operator_loc: Location?, reference: Node?, statements: StatementsNode?, consequent: RescueNode?, location: Location) -> void
def initialize(keyword_loc, exceptions, operator_loc, reference, statements, consequent, location)
@keyword_loc = keyword_loc
@exceptions = exceptions
@operator_loc = operator_loc
@reference = reference
@statements = statements
@consequent = consequent
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_rescue_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*exceptions, reference, statements, consequent]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact.concat(exceptions)
compact << reference if reference
compact << statements if statements
compact << consequent if consequent
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *exceptions, *operator_loc, *reference, *statements, *consequent]
end
# def copy: (**params) -> RescueNode
def copy(**params)
RescueNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:exceptions) { exceptions },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:reference) { reference },
params.fetch(:statements) { statements },
params.fetch(:consequent) { consequent },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, exceptions: exceptions, operator_loc: operator_loc, reference: reference, statements: statements, consequent: consequent, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def operator: () -> String?
def operator
operator_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── exceptions: #{inspector.list("#{inspector.prefix}│ ", exceptions)}"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
if (reference = self.reference).nil?
inspector << "├── reference: ∅\n"
else
inspector << "├── reference:\n"
inspector << reference.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (consequent = self.consequent).nil?
inspector << "└── consequent: ∅\n"
else
inspector << "└── consequent:\n"
inspector << consequent.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:rescue_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:rescue_node
end
end
# Represents a rest parameter to a method, block, or lambda definition.
#
# def a(*b)
# ^^
# end
class RestParameterNode < Node
# attr_reader name: Symbol?
attr_reader :name
# attr_reader name_loc: Location?
attr_reader :name_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# def initialize: (name: Symbol?, name_loc: Location?, operator_loc: Location, location: Location) -> void
def initialize(name, name_loc, operator_loc, location)
@name = name
@name_loc = name_loc
@operator_loc = operator_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_rest_parameter_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*name_loc, operator_loc]
end
# def copy: (**params) -> RestParameterNode
def copy(**params)
RestParameterNode.new(
params.fetch(:name) { name },
params.fetch(:name_loc) { name_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ name: name, name_loc: name_loc, operator_loc: operator_loc, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
if (name = self.name).nil?
inspector << "├── name: ∅\n"
else
inspector << "├── name: #{name.inspect}\n"
end
inspector << "├── name_loc: #{inspector.location(name_loc)}\n"
inspector << "└── operator_loc: #{inspector.location(operator_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:rest_parameter_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:rest_parameter_node
end
end
# Represents the use of the `retry` keyword.
#
# retry
# ^^^^^
class RetryNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_retry_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> RetryNode
def copy(**params)
RetryNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:retry_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:retry_node
end
end
# Represents the use of the `return` keyword.
#
# return 1
# ^^^^^^^^
class ReturnNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# def initialize: (keyword_loc: Location, arguments: ArgumentsNode?, location: Location) -> void
def initialize(keyword_loc, arguments, location)
@keyword_loc = keyword_loc
@arguments = arguments
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_return_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[arguments]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << arguments if arguments
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *arguments]
end
# def copy: (**params) -> ReturnNode
def copy(**params)
ReturnNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:arguments) { arguments },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, arguments: arguments, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "└── arguments: ∅\n"
else
inspector << "└── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:return_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:return_node
end
end
# Represents the `self` keyword.
#
# self
# ^^^^
class SelfNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_self_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> SelfNode
def copy(**params)
SelfNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:self_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:self_node
end
end
# Represents a singleton class declaration involving the `class` keyword.
#
# class << self end
# ^^^^^^^^^^^^^^^^^
class SingletonClassNode < Node
# attr_reader locals: Array[Symbol]
attr_reader :locals
# attr_reader class_keyword_loc: Location
attr_reader :class_keyword_loc
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader expression: Node
attr_reader :expression
# attr_reader body: Node?
attr_reader :body
# attr_reader end_keyword_loc: Location
attr_reader :end_keyword_loc
# def initialize: (locals: Array[Symbol], class_keyword_loc: Location, operator_loc: Location, expression: Node, body: Node?, end_keyword_loc: Location, location: Location) -> void
def initialize(locals, class_keyword_loc, operator_loc, expression, body, end_keyword_loc, location)
@locals = locals
@class_keyword_loc = class_keyword_loc
@operator_loc = operator_loc
@expression = expression
@body = body
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_singleton_class_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[expression, body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << expression
compact << body if body
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[class_keyword_loc, operator_loc, expression, *body, end_keyword_loc]
end
# def copy: (**params) -> SingletonClassNode
def copy(**params)
SingletonClassNode.new(
params.fetch(:locals) { locals },
params.fetch(:class_keyword_loc) { class_keyword_loc },
params.fetch(:operator_loc) { operator_loc },
params.fetch(:expression) { expression },
params.fetch(:body) { body },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ locals: locals, class_keyword_loc: class_keyword_loc, operator_loc: operator_loc, expression: expression, body: body, end_keyword_loc: end_keyword_loc, location: location }
end
# def class_keyword: () -> String
def class_keyword
class_keyword_loc.slice
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def end_keyword: () -> String
def end_keyword
end_keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── locals: #{locals.inspect}\n"
inspector << "├── class_keyword_loc: #{inspector.location(class_keyword_loc)}\n"
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
inspector << "├── expression:\n"
inspector << inspector.child_node(expression, "│ ")
if (body = self.body).nil?
inspector << "├── body: ∅\n"
else
inspector << "├── body:\n"
inspector << body.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:singleton_class_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:singleton_class_node
end
end
# Represents the use of the `__ENCODING__` keyword.
#
# __ENCODING__
# ^^^^^^^^^^^^
class SourceEncodingNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_source_encoding_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> SourceEncodingNode
def copy(**params)
SourceEncodingNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:source_encoding_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:source_encoding_node
end
end
# Represents the use of the `__FILE__` keyword.
#
# __FILE__
# ^^^^^^^^
class SourceFileNode < Node
# attr_reader filepath: String
attr_reader :filepath
# def initialize: (filepath: String, location: Location) -> void
def initialize(filepath, location)
@filepath = filepath
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_source_file_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> SourceFileNode
def copy(**params)
SourceFileNode.new(
params.fetch(:filepath) { filepath },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ filepath: filepath, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── filepath: #{filepath.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:source_file_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:source_file_node
end
end
# Represents the use of the `__LINE__` keyword.
#
# __LINE__
# ^^^^^^^^
class SourceLineNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_source_line_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> SourceLineNode
def copy(**params)
SourceLineNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:source_line_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:source_line_node
end
end
# Represents the use of the splat operator.
#
# [*a]
# ^^
class SplatNode < Node
# attr_reader operator_loc: Location
attr_reader :operator_loc
# attr_reader expression: Node?
attr_reader :expression
# def initialize: (operator_loc: Location, expression: Node?, location: Location) -> void
def initialize(operator_loc, expression, location)
@operator_loc = operator_loc
@expression = expression
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_splat_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[expression]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << expression if expression
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[operator_loc, *expression]
end
# def copy: (**params) -> SplatNode
def copy(**params)
SplatNode.new(
params.fetch(:operator_loc) { operator_loc },
params.fetch(:expression) { expression },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ operator_loc: operator_loc, expression: expression, location: location }
end
# def operator: () -> String
def operator
operator_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── operator_loc: #{inspector.location(operator_loc)}\n"
if (expression = self.expression).nil?
inspector << "└── expression: ∅\n"
else
inspector << "└── expression:\n"
inspector << expression.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:splat_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:splat_node
end
end
# Represents a set of statements contained within some scope.
#
# foo; bar; baz
# ^^^^^^^^^^^^^
class StatementsNode < Node
# attr_reader body: Array[Node]
attr_reader :body
# def initialize: (body: Array[Node], location: Location) -> void
def initialize(body, location)
@body = body
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_statements_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*body]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*body]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*body]
end
# def copy: (**params) -> StatementsNode
def copy(**params)
StatementsNode.new(
params.fetch(:body) { body },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ body: body, location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "└── body: #{inspector.list("#{inspector.prefix} ", body)}"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:statements_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:statements_node
end
end
# Represents a string literal, a string contained within a `%w` list, or
# plain string content within an interpolated string.
#
# "foo"
# ^^^^^
#
# %w[foo]
# ^^^
#
# "foo #{bar} baz"
# ^^^^ ^^^^
class StringNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader content_loc: Location
attr_reader :content_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# attr_reader unescaped: String
attr_reader :unescaped
# def initialize: (flags: Integer, opening_loc: Location?, content_loc: Location, closing_loc: Location?, unescaped: String, location: Location) -> void
def initialize(flags, opening_loc, content_loc, closing_loc, unescaped, location)
@flags = flags
@opening_loc = opening_loc
@content_loc = content_loc
@closing_loc = closing_loc
@unescaped = unescaped
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_string_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*opening_loc, content_loc, *closing_loc]
end
# def copy: (**params) -> StringNode
def copy(**params)
StringNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:content_loc) { content_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:unescaped) { unescaped },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, content_loc: content_loc, closing_loc: closing_loc, unescaped: unescaped, location: location }
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(StringFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(StringFlags::FORCED_BINARY_ENCODING)
end
# def frozen?: () -> bool
def frozen?
flags.anybits?(StringFlags::FROZEN)
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def content: () -> String
def content
content_loc.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("frozen" if frozen?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── content_loc: #{inspector.location(content_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "└── unescaped: #{unescaped.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:string_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:string_node
end
end
# Represents the use of the `super` keyword with parentheses or arguments.
#
# super()
# ^^^^^^^
#
# super foo, bar
# ^^^^^^^^^^^^^^
class SuperNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# attr_reader block: Node?
attr_reader :block
# def initialize: (keyword_loc: Location, lparen_loc: Location?, arguments: ArgumentsNode?, rparen_loc: Location?, block: Node?, location: Location) -> void
def initialize(keyword_loc, lparen_loc, arguments, rparen_loc, block, location)
@keyword_loc = keyword_loc
@lparen_loc = lparen_loc
@arguments = arguments
@rparen_loc = rparen_loc
@block = block
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_super_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[arguments, block]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << arguments if arguments
compact << block if block
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *lparen_loc, *arguments, *rparen_loc, *block]
end
# def copy: (**params) -> SuperNode
def copy(**params)
SuperNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:arguments) { arguments },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:block) { block },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, lparen_loc: lparen_loc, arguments: arguments, rparen_loc: rparen_loc, block: block, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "├── rparen_loc: #{inspector.location(rparen_loc)}\n"
if (block = self.block).nil?
inspector << "└── block: ∅\n"
else
inspector << "└── block:\n"
inspector << block.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:super_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:super_node
end
end
# Represents a symbol literal or a symbol contained within a `%i` list.
#
# :foo
# ^^^^
#
# %i[foo]
# ^^^
class SymbolNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location?
attr_reader :opening_loc
# attr_reader value_loc: Location?
attr_reader :value_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# attr_reader unescaped: String
attr_reader :unescaped
# def initialize: (flags: Integer, opening_loc: Location?, value_loc: Location?, closing_loc: Location?, unescaped: String, location: Location) -> void
def initialize(flags, opening_loc, value_loc, closing_loc, unescaped, location)
@flags = flags
@opening_loc = opening_loc
@value_loc = value_loc
@closing_loc = closing_loc
@unescaped = unescaped
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_symbol_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*opening_loc, *value_loc, *closing_loc]
end
# def copy: (**params) -> SymbolNode
def copy(**params)
SymbolNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:value_loc) { value_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:unescaped) { unescaped },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, value_loc: value_loc, closing_loc: closing_loc, unescaped: unescaped, location: location }
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(SymbolFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(SymbolFlags::FORCED_BINARY_ENCODING)
end
# def forced_us_ascii_encoding?: () -> bool
def forced_us_ascii_encoding?
flags.anybits?(SymbolFlags::FORCED_US_ASCII_ENCODING)
end
# def opening: () -> String?
def opening
opening_loc&.slice
end
# def value: () -> String?
def value
value_loc&.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?), ("forced_us_ascii_encoding" if forced_us_ascii_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── value_loc: #{inspector.location(value_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "└── unescaped: #{unescaped.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:symbol_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:symbol_node
end
end
# Represents the use of the literal `true` keyword.
#
# true
# ^^^^
class TrueNode < Node
# def initialize: (location: Location) -> void
def initialize(location)
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_true_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[]
end
# def copy: (**params) -> TrueNode
def copy(**params)
TrueNode.new(
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ location: location }
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:true_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:true_node
end
end
# Represents the use of the `undef` keyword.
#
# undef :foo, :bar, :baz
# ^^^^^^^^^^^^^^^^^^^^^^
class UndefNode < Node
# attr_reader names: Array[Node]
attr_reader :names
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# def initialize: (names: Array[Node], keyword_loc: Location, location: Location) -> void
def initialize(names, keyword_loc, location)
@names = names
@keyword_loc = keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_undef_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*names]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[*names]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[*names, keyword_loc]
end
# def copy: (**params) -> UndefNode
def copy(**params)
UndefNode.new(
params.fetch(:names) { names },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ names: names, keyword_loc: keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── names: #{inspector.list("#{inspector.prefix}│ ", names)}"
inspector << "└── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:undef_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:undef_node
end
end
# Represents the use of the `unless` keyword, either in the block form or the modifier form.
#
# bar unless foo
# ^^^^^^^^^^^^^^
#
# unless foo then bar end
# ^^^^^^^^^^^^^^^^^^^^^^^
class UnlessNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader predicate: Node
attr_reader :predicate
# attr_reader then_keyword_loc: Location?
attr_reader :then_keyword_loc
# attr_reader statements: StatementsNode?
attr_reader :statements
# attr_reader consequent: ElseNode?
attr_reader :consequent
# attr_reader end_keyword_loc: Location?
attr_reader :end_keyword_loc
# def initialize: (keyword_loc: Location, predicate: Node, then_keyword_loc: Location?, statements: StatementsNode?, consequent: ElseNode?, end_keyword_loc: Location?, location: Location) -> void
def initialize(keyword_loc, predicate, then_keyword_loc, statements, consequent, end_keyword_loc, location)
@keyword_loc = keyword_loc
@predicate = predicate
@then_keyword_loc = then_keyword_loc
@statements = statements
@consequent = consequent
@end_keyword_loc = end_keyword_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_unless_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
predicate.set_newline_flag(newline_marked)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, statements, consequent]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate
compact << statements if statements
compact << consequent if consequent
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, predicate, *then_keyword_loc, *statements, *consequent, *end_keyword_loc]
end
# def copy: (**params) -> UnlessNode
def copy(**params)
UnlessNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:predicate) { predicate },
params.fetch(:then_keyword_loc) { then_keyword_loc },
params.fetch(:statements) { statements },
params.fetch(:consequent) { consequent },
params.fetch(:end_keyword_loc) { end_keyword_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, predicate: predicate, then_keyword_loc: then_keyword_loc, statements: statements, consequent: consequent, end_keyword_loc: end_keyword_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def then_keyword: () -> String?
def then_keyword
then_keyword_loc&.slice
end
# def end_keyword: () -> String?
def end_keyword
end_keyword_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── predicate:\n"
inspector << inspector.child_node(predicate, "│ ")
inspector << "├── then_keyword_loc: #{inspector.location(then_keyword_loc)}\n"
if (statements = self.statements).nil?
inspector << "├── statements: ∅\n"
else
inspector << "├── statements:\n"
inspector << statements.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
if (consequent = self.consequent).nil?
inspector << "├── consequent: ∅\n"
else
inspector << "├── consequent:\n"
inspector << consequent.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── end_keyword_loc: #{inspector.location(end_keyword_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:unless_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:unless_node
end
end
# Represents the use of the `until` keyword, either in the block form or the modifier form.
#
# bar until foo
# ^^^^^^^^^^^^^
#
# until foo do bar end
# ^^^^^^^^^^^^^^^^^^^^
class UntilNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# attr_reader predicate: Node
attr_reader :predicate
# attr_reader statements: StatementsNode?
attr_reader :statements
# def initialize: (flags: Integer, keyword_loc: Location, closing_loc: Location?, predicate: Node, statements: StatementsNode?, location: Location) -> void
def initialize(flags, keyword_loc, closing_loc, predicate, statements, location)
@flags = flags
@keyword_loc = keyword_loc
@closing_loc = closing_loc
@predicate = predicate
@statements = statements
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_until_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
predicate.set_newline_flag(newline_marked)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *closing_loc, predicate, *statements]
end
# def copy: (**params) -> UntilNode
def copy(**params)
UntilNode.new(
params.fetch(:flags) { flags },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:predicate) { predicate },
params.fetch(:statements) { statements },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, keyword_loc: keyword_loc, closing_loc: closing_loc, predicate: predicate, statements: statements, location: location }
end
# def begin_modifier?: () -> bool
def begin_modifier?
flags.anybits?(LoopFlags::BEGIN_MODIFIER)
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("begin_modifier" if begin_modifier?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "├── predicate:\n"
inspector << inspector.child_node(predicate, "│ ")
if (statements = self.statements).nil?
inspector << "└── statements: ∅\n"
else
inspector << "└── statements:\n"
inspector << statements.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:until_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:until_node
end
end
# Represents the use of the `when` keyword within a case statement.
#
# case true
# when true
# ^^^^^^^^^
# end
class WhenNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader conditions: Array[Node]
attr_reader :conditions
# attr_reader statements: StatementsNode?
attr_reader :statements
# def initialize: (keyword_loc: Location, conditions: Array[Node], statements: StatementsNode?, location: Location) -> void
def initialize(keyword_loc, conditions, statements, location)
@keyword_loc = keyword_loc
@conditions = conditions
@statements = statements
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_when_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[*conditions, statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact.concat(conditions)
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *conditions, *statements]
end
# def copy: (**params) -> WhenNode
def copy(**params)
WhenNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:conditions) { conditions },
params.fetch(:statements) { statements },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, conditions: conditions, statements: statements, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── conditions: #{inspector.list("#{inspector.prefix}│ ", conditions)}"
if (statements = self.statements).nil?
inspector << "└── statements: ∅\n"
else
inspector << "└── statements:\n"
inspector << statements.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:when_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:when_node
end
end
# Represents the use of the `while` keyword, either in the block form or the modifier form.
#
# bar while foo
# ^^^^^^^^^^^^^
#
# while foo do bar end
# ^^^^^^^^^^^^^^^^^^^^
class WhileNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader closing_loc: Location?
attr_reader :closing_loc
# attr_reader predicate: Node
attr_reader :predicate
# attr_reader statements: StatementsNode?
attr_reader :statements
# def initialize: (flags: Integer, keyword_loc: Location, closing_loc: Location?, predicate: Node, statements: StatementsNode?, location: Location) -> void
def initialize(flags, keyword_loc, closing_loc, predicate, statements, location)
@flags = flags
@keyword_loc = keyword_loc
@closing_loc = closing_loc
@predicate = predicate
@statements = statements
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_while_node(self)
end
def set_newline_flag(newline_marked) # :nodoc:
predicate.set_newline_flag(newline_marked)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[predicate, statements]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << predicate
compact << statements if statements
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *closing_loc, predicate, *statements]
end
# def copy: (**params) -> WhileNode
def copy(**params)
WhileNode.new(
params.fetch(:flags) { flags },
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:predicate) { predicate },
params.fetch(:statements) { statements },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, keyword_loc: keyword_loc, closing_loc: closing_loc, predicate: predicate, statements: statements, location: location }
end
# def begin_modifier?: () -> bool
def begin_modifier?
flags.anybits?(LoopFlags::BEGIN_MODIFIER)
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def closing: () -> String?
def closing
closing_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("begin_modifier" if begin_modifier?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "├── predicate:\n"
inspector << inspector.child_node(predicate, "│ ")
if (statements = self.statements).nil?
inspector << "└── statements: ∅\n"
else
inspector << "└── statements:\n"
inspector << statements.inspect(inspector.child_inspector(" ")).delete_prefix(inspector.prefix)
end
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:while_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:while_node
end
end
# Represents an xstring literal with no interpolation.
#
# `foo`
# ^^^^^
class XStringNode < Node
# attr_reader flags: Integer
private attr_reader :flags
# attr_reader opening_loc: Location
attr_reader :opening_loc
# attr_reader content_loc: Location
attr_reader :content_loc
# attr_reader closing_loc: Location
attr_reader :closing_loc
# attr_reader unescaped: String
attr_reader :unescaped
# def initialize: (flags: Integer, opening_loc: Location, content_loc: Location, closing_loc: Location, unescaped: String, location: Location) -> void
def initialize(flags, opening_loc, content_loc, closing_loc, unescaped, location)
@flags = flags
@opening_loc = opening_loc
@content_loc = content_loc
@closing_loc = closing_loc
@unescaped = unescaped
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_x_string_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
[]
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[opening_loc, content_loc, closing_loc]
end
# def copy: (**params) -> XStringNode
def copy(**params)
XStringNode.new(
params.fetch(:flags) { flags },
params.fetch(:opening_loc) { opening_loc },
params.fetch(:content_loc) { content_loc },
params.fetch(:closing_loc) { closing_loc },
params.fetch(:unescaped) { unescaped },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ flags: flags, opening_loc: opening_loc, content_loc: content_loc, closing_loc: closing_loc, unescaped: unescaped, location: location }
end
# def forced_utf8_encoding?: () -> bool
def forced_utf8_encoding?
flags.anybits?(EncodingFlags::FORCED_UTF8_ENCODING)
end
# def forced_binary_encoding?: () -> bool
def forced_binary_encoding?
flags.anybits?(EncodingFlags::FORCED_BINARY_ENCODING)
end
# def opening: () -> String
def opening
opening_loc.slice
end
# def content: () -> String
def content
content_loc.slice
end
# def closing: () -> String
def closing
closing_loc.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
flags = [("forced_utf8_encoding" if forced_utf8_encoding?), ("forced_binary_encoding" if forced_binary_encoding?)].compact
inspector << "├── flags: #{flags.empty? ? "∅" : flags.join(", ")}\n"
inspector << "├── opening_loc: #{inspector.location(opening_loc)}\n"
inspector << "├── content_loc: #{inspector.location(content_loc)}\n"
inspector << "├── closing_loc: #{inspector.location(closing_loc)}\n"
inspector << "└── unescaped: #{unescaped.inspect}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:x_string_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:x_string_node
end
end
# Represents the use of the `yield` keyword.
#
# yield 1
# ^^^^^^^
class YieldNode < Node
# attr_reader keyword_loc: Location
attr_reader :keyword_loc
# attr_reader lparen_loc: Location?
attr_reader :lparen_loc
# attr_reader arguments: ArgumentsNode?
attr_reader :arguments
# attr_reader rparen_loc: Location?
attr_reader :rparen_loc
# def initialize: (keyword_loc: Location, lparen_loc: Location?, arguments: ArgumentsNode?, rparen_loc: Location?, location: Location) -> void
def initialize(keyword_loc, lparen_loc, arguments, rparen_loc, location)
@keyword_loc = keyword_loc
@lparen_loc = lparen_loc
@arguments = arguments
@rparen_loc = rparen_loc
@location = location
end
# def accept: (visitor: Visitor) -> void
def accept(visitor)
visitor.visit_yield_node(self)
end
# def child_nodes: () -> Array[nil | Node]
def child_nodes
[arguments]
end
# def compact_child_nodes: () -> Array[Node]
def compact_child_nodes
compact = []
compact << arguments if arguments
compact
end
# def comment_targets: () -> Array[Node | Location]
def comment_targets
[keyword_loc, *lparen_loc, *arguments, *rparen_loc]
end
# def copy: (**params) -> YieldNode
def copy(**params)
YieldNode.new(
params.fetch(:keyword_loc) { keyword_loc },
params.fetch(:lparen_loc) { lparen_loc },
params.fetch(:arguments) { arguments },
params.fetch(:rparen_loc) { rparen_loc },
params.fetch(:location) { location },
)
end
# def deconstruct: () -> Array[nil | Node]
alias deconstruct child_nodes
# def deconstruct_keys: (keys: Array[Symbol]) -> Hash[Symbol, nil | Node | Array[Node] | String | Token | Array[Token] | Location]
def deconstruct_keys(keys)
{ keyword_loc: keyword_loc, lparen_loc: lparen_loc, arguments: arguments, rparen_loc: rparen_loc, location: location }
end
# def keyword: () -> String
def keyword
keyword_loc.slice
end
# def lparen: () -> String?
def lparen
lparen_loc&.slice
end
# def rparen: () -> String?
def rparen
rparen_loc&.slice
end
# def inspect(inspector: NodeInspector) -> String
def inspect(inspector = NodeInspector.new)
inspector << inspector.header(self)
inspector << "├── keyword_loc: #{inspector.location(keyword_loc)}\n"
inspector << "├── lparen_loc: #{inspector.location(lparen_loc)}\n"
if (arguments = self.arguments).nil?
inspector << "├── arguments: ∅\n"
else
inspector << "├── arguments:\n"
inspector << arguments.inspect(inspector.child_inspector("│ ")).delete_prefix(inspector.prefix)
end
inspector << "└── rparen_loc: #{inspector.location(rparen_loc)}\n"
inspector.to_str
end
# Sometimes you want to check an instance of a node against a list of
# classes to see what kind of behavior to perform. Usually this is done by
# calling `[cls1, cls2].include?(node.class)` or putting the node into a
# case statement and doing `case node; when cls1; when cls2; end`. Both of
# these approaches are relatively slow because of the constant lookups,
# method calls, and/or array allocations.
#
# Instead, you can call #type, which will return to you a symbol that you
# can use for comparison. This is faster than the other approaches because
# it uses a single integer comparison, but also because if you're on CRuby
# you can take advantage of the fact that case statements with all symbol
# keys will use a jump table.
#
# def type: () -> Symbol
def type
:yield_node
end
# Similar to #type, this method returns a symbol that you can use for
# splitting on the type of the node without having to do a long === chain.
# Note that like #type, it will still be slower than using == for a single
# class, but should be faster in a case statement or an array comparison.
#
# def self.type: () -> Symbol
def self.type
:yield_node
end
end
# Flags for arguments nodes.
module ArgumentsNodeFlags
# if arguments contain keyword splat
CONTAINS_KEYWORD_SPLAT = 1 << 0
end
# Flags for array nodes.
module ArrayNodeFlags
# if array contains splat nodes
CONTAINS_SPLAT = 1 << 0
end
# Flags for call nodes.
module CallNodeFlags
# &. operator
SAFE_NAVIGATION = 1 << 0
# a call that could have been a local variable
VARIABLE_CALL = 1 << 1
# a call that is an attribute write, so the value being written should be returned
ATTRIBUTE_WRITE = 1 << 2
end
# Flags for nodes that have unescaped content.
module EncodingFlags
# internal bytes forced the encoding to UTF-8
FORCED_UTF8_ENCODING = 1 << 0
# internal bytes forced the encoding to binary
FORCED_BINARY_ENCODING = 1 << 1
end
# Flags for integer nodes that correspond to the base of the integer.
module IntegerBaseFlags
# 0b prefix
BINARY = 1 << 0
# 0d or no prefix
DECIMAL = 1 << 1
# 0o or 0 prefix
OCTAL = 1 << 2
# 0x prefix
HEXADECIMAL = 1 << 3
end
# Flags for keyword hash nodes.
module KeywordHashNodeFlags
# a keyword hash which only has `AssocNode` elements all with static literal keys, which means the elements can be treated as keyword arguments
STATIC_KEYS = 1 << 0
end
# Flags for while and until loop nodes.
module LoopFlags
# a loop after a begin statement, so the body is executed first before the condition
BEGIN_MODIFIER = 1 << 0
end
# Flags for range and flip-flop nodes.
module RangeFlags
# ... operator
EXCLUDE_END = 1 << 0
end
# Flags for regular expression and match last line nodes.
module RegularExpressionFlags
# i - ignores the case of characters when matching
IGNORE_CASE = 1 << 0
# x - ignores whitespace and allows comments in regular expressions
EXTENDED = 1 << 1
# m - allows $ to match the end of lines within strings
MULTI_LINE = 1 << 2
# o - only interpolates values into the regular expression once
ONCE = 1 << 3
# e - forces the EUC-JP encoding
EUC_JP = 1 << 4
# n - forces the ASCII-8BIT encoding
ASCII_8BIT = 1 << 5
# s - forces the Windows-31J encoding
WINDOWS_31J = 1 << 6
# u - forces the UTF-8 encoding
UTF_8 = 1 << 7
# internal bytes forced the encoding to UTF-8
FORCED_UTF8_ENCODING = 1 << 8
# internal bytes forced the encoding to binary
FORCED_BINARY_ENCODING = 1 << 9
# internal bytes forced the encoding to US-ASCII
FORCED_US_ASCII_ENCODING = 1 << 10
end
# Flags for string nodes.
module StringFlags
# internal bytes forced the encoding to UTF-8
FORCED_UTF8_ENCODING = 1 << 0
# internal bytes forced the encoding to binary
FORCED_BINARY_ENCODING = 1 << 1
# frozen by virtue of a `frozen_string_literal` comment
FROZEN = 1 << 2
end
# Flags for symbol nodes.
module SymbolFlags
# internal bytes forced the encoding to UTF-8
FORCED_UTF8_ENCODING = 1 << 0
# internal bytes forced the encoding to binary
FORCED_BINARY_ENCODING = 1 << 1
# internal bytes forced the encoding to US-ASCII
FORCED_US_ASCII_ENCODING = 1 << 2
end
end
share/ruby/prism/desugar_compiler.rb 0000644 00000013333 15173517735 0013653 0 ustar 00 # frozen_string_literal: true
module Prism
# DesugarCompiler is a compiler that desugars Ruby code into a more primitive
# form. This is useful for consumers that want to deal with fewer node types.
class DesugarCompiler < MutationCompiler
# @@foo &&= bar
#
# becomes
#
# @@foo && @@foo = bar
def visit_class_variable_and_write_node(node)
desugar_and_write_node(node, ClassVariableReadNode, ClassVariableWriteNode, node.name)
end
# @@foo ||= bar
#
# becomes
#
# defined?(@@foo) ? @@foo : @@foo = bar
def visit_class_variable_or_write_node(node)
desugar_or_write_defined_node(node, ClassVariableReadNode, ClassVariableWriteNode, node.name)
end
# @@foo += bar
#
# becomes
#
# @@foo = @@foo + bar
def visit_class_variable_operator_write_node(node)
desugar_operator_write_node(node, ClassVariableReadNode, ClassVariableWriteNode, node.name)
end
# Foo &&= bar
#
# becomes
#
# Foo && Foo = bar
def visit_constant_and_write_node(node)
desugar_and_write_node(node, ConstantReadNode, ConstantWriteNode, node.name)
end
# Foo ||= bar
#
# becomes
#
# defined?(Foo) ? Foo : Foo = bar
def visit_constant_or_write_node(node)
desugar_or_write_defined_node(node, ConstantReadNode, ConstantWriteNode, node.name)
end
# Foo += bar
#
# becomes
#
# Foo = Foo + bar
def visit_constant_operator_write_node(node)
desugar_operator_write_node(node, ConstantReadNode, ConstantWriteNode, node.name)
end
# $foo &&= bar
#
# becomes
#
# $foo && $foo = bar
def visit_global_variable_and_write_node(node)
desugar_and_write_node(node, GlobalVariableReadNode, GlobalVariableWriteNode, node.name)
end
# $foo ||= bar
#
# becomes
#
# defined?($foo) ? $foo : $foo = bar
def visit_global_variable_or_write_node(node)
desugar_or_write_defined_node(node, GlobalVariableReadNode, GlobalVariableWriteNode, node.name)
end
# $foo += bar
#
# becomes
#
# $foo = $foo + bar
def visit_global_variable_operator_write_node(node)
desugar_operator_write_node(node, GlobalVariableReadNode, GlobalVariableWriteNode, node.name)
end
# @foo &&= bar
#
# becomes
#
# @foo && @foo = bar
def visit_instance_variable_and_write_node(node)
desugar_and_write_node(node, InstanceVariableReadNode, InstanceVariableWriteNode, node.name)
end
# @foo ||= bar
#
# becomes
#
# @foo || @foo = bar
def visit_instance_variable_or_write_node(node)
desugar_or_write_node(node, InstanceVariableReadNode, InstanceVariableWriteNode, node.name)
end
# @foo += bar
#
# becomes
#
# @foo = @foo + bar
def visit_instance_variable_operator_write_node(node)
desugar_operator_write_node(node, InstanceVariableReadNode, InstanceVariableWriteNode, node.name)
end
# foo &&= bar
#
# becomes
#
# foo && foo = bar
def visit_local_variable_and_write_node(node)
desugar_and_write_node(node, LocalVariableReadNode, LocalVariableWriteNode, node.name, node.depth)
end
# foo ||= bar
#
# becomes
#
# foo || foo = bar
def visit_local_variable_or_write_node(node)
desugar_or_write_node(node, LocalVariableReadNode, LocalVariableWriteNode, node.name, node.depth)
end
# foo += bar
#
# becomes
#
# foo = foo + bar
def visit_local_variable_operator_write_node(node)
desugar_operator_write_node(node, LocalVariableReadNode, LocalVariableWriteNode, node.name, node.depth)
end
private
# Desugar `x &&= y` to `x && x = y`
def desugar_and_write_node(node, read_class, write_class, *arguments)
AndNode.new(
read_class.new(*arguments, node.name_loc),
write_class.new(*arguments, node.name_loc, node.value, node.operator_loc, node.location),
node.operator_loc,
node.location
)
end
# Desugar `x += y` to `x = x + y`
def desugar_operator_write_node(node, read_class, write_class, *arguments)
write_class.new(
*arguments,
node.name_loc,
CallNode.new(
0,
read_class.new(*arguments, node.name_loc),
nil,
node.operator_loc.slice.chomp("="),
node.operator_loc.copy(length: node.operator_loc.length - 1),
nil,
ArgumentsNode.new(0, [node.value], node.value.location),
nil,
nil,
node.location
),
node.operator_loc.copy(start_offset: node.operator_loc.end_offset - 1, length: 1),
node.location
)
end
# Desugar `x ||= y` to `x || x = y`
def desugar_or_write_node(node, read_class, write_class, *arguments)
OrNode.new(
read_class.new(*arguments, node.name_loc),
write_class.new(*arguments, node.name_loc, node.value, node.operator_loc, node.location),
node.operator_loc,
node.location
)
end
# Desugar `x ||= y` to `defined?(x) ? x : x = y`
def desugar_or_write_defined_node(node, read_class, write_class, *arguments)
IfNode.new(
node.operator_loc,
DefinedNode.new(nil, read_class.new(*arguments, node.name_loc), nil, node.operator_loc, node.name_loc),
node.operator_loc,
StatementsNode.new([read_class.new(*arguments, node.name_loc)], node.location),
ElseNode.new(
node.operator_loc,
StatementsNode.new(
[write_class.new(*arguments, node.name_loc, node.value, node.operator_loc, node.location)],
node.location
),
node.operator_loc,
node.location
),
node.operator_loc,
node.location
)
end
end
end
share/ruby/prism/lex_compat.rb 0000644 00000076047 15173517735 0012475 0 ustar 00 # frozen_string_literal: true
require "delegate"
module Prism
# This class is responsible for lexing the source using prism and then
# converting those tokens to be compatible with Ripper. In the vast majority
# of cases, this is a one-to-one mapping of the token type. Everything else
# generally lines up. However, there are a few cases that require special
# handling.
class LexCompat # :nodoc:
# This is a mapping of prism token types to Ripper token types. This is a
# many-to-one mapping because we split up our token types, whereas Ripper
# tends to group them.
RIPPER = {
AMPERSAND: :on_op,
AMPERSAND_AMPERSAND: :on_op,
AMPERSAND_AMPERSAND_EQUAL: :on_op,
AMPERSAND_DOT: :on_op,
AMPERSAND_EQUAL: :on_op,
BACK_REFERENCE: :on_backref,
BACKTICK: :on_backtick,
BANG: :on_op,
BANG_EQUAL: :on_op,
BANG_TILDE: :on_op,
BRACE_LEFT: :on_lbrace,
BRACE_RIGHT: :on_rbrace,
BRACKET_LEFT: :on_lbracket,
BRACKET_LEFT_ARRAY: :on_lbracket,
BRACKET_LEFT_RIGHT: :on_op,
BRACKET_LEFT_RIGHT_EQUAL: :on_op,
BRACKET_RIGHT: :on_rbracket,
CARET: :on_op,
CARET_EQUAL: :on_op,
CHARACTER_LITERAL: :on_CHAR,
CLASS_VARIABLE: :on_cvar,
COLON: :on_op,
COLON_COLON: :on_op,
COMMA: :on_comma,
COMMENT: :on_comment,
CONSTANT: :on_const,
DOT: :on_period,
DOT_DOT: :on_op,
DOT_DOT_DOT: :on_op,
EMBDOC_BEGIN: :on_embdoc_beg,
EMBDOC_END: :on_embdoc_end,
EMBDOC_LINE: :on_embdoc,
EMBEXPR_BEGIN: :on_embexpr_beg,
EMBEXPR_END: :on_embexpr_end,
EMBVAR: :on_embvar,
EOF: :on_eof,
EQUAL: :on_op,
EQUAL_EQUAL: :on_op,
EQUAL_EQUAL_EQUAL: :on_op,
EQUAL_GREATER: :on_op,
EQUAL_TILDE: :on_op,
FLOAT: :on_float,
FLOAT_IMAGINARY: :on_imaginary,
FLOAT_RATIONAL: :on_rational,
FLOAT_RATIONAL_IMAGINARY: :on_imaginary,
GREATER: :on_op,
GREATER_EQUAL: :on_op,
GREATER_GREATER: :on_op,
GREATER_GREATER_EQUAL: :on_op,
GLOBAL_VARIABLE: :on_gvar,
HEREDOC_END: :on_heredoc_end,
HEREDOC_START: :on_heredoc_beg,
IDENTIFIER: :on_ident,
IGNORED_NEWLINE: :on_ignored_nl,
INTEGER: :on_int,
INTEGER_IMAGINARY: :on_imaginary,
INTEGER_RATIONAL: :on_rational,
INTEGER_RATIONAL_IMAGINARY: :on_imaginary,
INSTANCE_VARIABLE: :on_ivar,
INVALID: :INVALID,
KEYWORD___ENCODING__: :on_kw,
KEYWORD___LINE__: :on_kw,
KEYWORD___FILE__: :on_kw,
KEYWORD_ALIAS: :on_kw,
KEYWORD_AND: :on_kw,
KEYWORD_BEGIN: :on_kw,
KEYWORD_BEGIN_UPCASE: :on_kw,
KEYWORD_BREAK: :on_kw,
KEYWORD_CASE: :on_kw,
KEYWORD_CLASS: :on_kw,
KEYWORD_DEF: :on_kw,
KEYWORD_DEFINED: :on_kw,
KEYWORD_DO: :on_kw,
KEYWORD_DO_LOOP: :on_kw,
KEYWORD_ELSE: :on_kw,
KEYWORD_ELSIF: :on_kw,
KEYWORD_END: :on_kw,
KEYWORD_END_UPCASE: :on_kw,
KEYWORD_ENSURE: :on_kw,
KEYWORD_FALSE: :on_kw,
KEYWORD_FOR: :on_kw,
KEYWORD_IF: :on_kw,
KEYWORD_IF_MODIFIER: :on_kw,
KEYWORD_IN: :on_kw,
KEYWORD_MODULE: :on_kw,
KEYWORD_NEXT: :on_kw,
KEYWORD_NIL: :on_kw,
KEYWORD_NOT: :on_kw,
KEYWORD_OR: :on_kw,
KEYWORD_REDO: :on_kw,
KEYWORD_RESCUE: :on_kw,
KEYWORD_RESCUE_MODIFIER: :on_kw,
KEYWORD_RETRY: :on_kw,
KEYWORD_RETURN: :on_kw,
KEYWORD_SELF: :on_kw,
KEYWORD_SUPER: :on_kw,
KEYWORD_THEN: :on_kw,
KEYWORD_TRUE: :on_kw,
KEYWORD_UNDEF: :on_kw,
KEYWORD_UNLESS: :on_kw,
KEYWORD_UNLESS_MODIFIER: :on_kw,
KEYWORD_UNTIL: :on_kw,
KEYWORD_UNTIL_MODIFIER: :on_kw,
KEYWORD_WHEN: :on_kw,
KEYWORD_WHILE: :on_kw,
KEYWORD_WHILE_MODIFIER: :on_kw,
KEYWORD_YIELD: :on_kw,
LABEL: :on_label,
LABEL_END: :on_label_end,
LAMBDA_BEGIN: :on_tlambeg,
LESS: :on_op,
LESS_EQUAL: :on_op,
LESS_EQUAL_GREATER: :on_op,
LESS_LESS: :on_op,
LESS_LESS_EQUAL: :on_op,
METHOD_NAME: :on_ident,
MINUS: :on_op,
MINUS_EQUAL: :on_op,
MINUS_GREATER: :on_tlambda,
NEWLINE: :on_nl,
NUMBERED_REFERENCE: :on_backref,
PARENTHESIS_LEFT: :on_lparen,
PARENTHESIS_LEFT_PARENTHESES: :on_lparen,
PARENTHESIS_RIGHT: :on_rparen,
PERCENT: :on_op,
PERCENT_EQUAL: :on_op,
PERCENT_LOWER_I: :on_qsymbols_beg,
PERCENT_LOWER_W: :on_qwords_beg,
PERCENT_LOWER_X: :on_backtick,
PERCENT_UPPER_I: :on_symbols_beg,
PERCENT_UPPER_W: :on_words_beg,
PIPE: :on_op,
PIPE_EQUAL: :on_op,
PIPE_PIPE: :on_op,
PIPE_PIPE_EQUAL: :on_op,
PLUS: :on_op,
PLUS_EQUAL: :on_op,
QUESTION_MARK: :on_op,
RATIONAL_FLOAT: :on_rational,
RATIONAL_INTEGER: :on_rational,
REGEXP_BEGIN: :on_regexp_beg,
REGEXP_END: :on_regexp_end,
SEMICOLON: :on_semicolon,
SLASH: :on_op,
SLASH_EQUAL: :on_op,
STAR: :on_op,
STAR_EQUAL: :on_op,
STAR_STAR: :on_op,
STAR_STAR_EQUAL: :on_op,
STRING_BEGIN: :on_tstring_beg,
STRING_CONTENT: :on_tstring_content,
STRING_END: :on_tstring_end,
SYMBOL_BEGIN: :on_symbeg,
TILDE: :on_op,
UAMPERSAND: :on_op,
UCOLON_COLON: :on_op,
UDOT_DOT: :on_op,
UDOT_DOT_DOT: :on_op,
UMINUS: :on_op,
UMINUS_NUM: :on_op,
UPLUS: :on_op,
USTAR: :on_op,
USTAR_STAR: :on_op,
WORDS_SEP: :on_words_sep,
"__END__": :on___end__
}.freeze
# When we produce tokens, we produce the same arrays that Ripper does.
# However, we add a couple of convenience methods onto them to make them a
# little easier to work with. We delegate all other methods to the array.
class Token < SimpleDelegator
# The location of the token in the source.
def location
self[0]
end
# The type of the token.
def event
self[1]
end
# The slice of the source that this token represents.
def value
self[2]
end
# The state of the lexer when this token was produced.
def state
self[3]
end
end
# Ripper doesn't include the rest of the token in the event, so we need to
# trim it down to just the content on the first line when comparing.
class EndContentToken < Token
def ==(other) # :nodoc:
[self[0], self[1], self[2][0..self[2].index("\n")], self[3]] == other
end
end
# Tokens where state should be ignored
# used for :on_comment, :on_heredoc_end, :on_embexpr_end
class IgnoreStateToken < Token
def ==(other) # :nodoc:
self[0...-1] == other[0...-1]
end
end
# Ident tokens for the most part are exactly the same, except sometimes we
# know an ident is a local when ripper doesn't (when they are introduced
# through named captures in regular expressions). In that case we don't
# compare the state.
class IdentToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
(other[3] == Ripper::EXPR_LABEL | Ripper::EXPR_END) ||
(other[3] & Ripper::EXPR_ARG_ANY != 0)
)
end
end
# Ignored newlines can occasionally have a LABEL state attached to them, so
# we compare the state differently here.
class IgnoredNewlineToken < Token
def ==(other) # :nodoc:
return false unless self[0...-1] == other[0...-1]
if self[4] == Ripper::EXPR_ARG | Ripper::EXPR_LABELED
other[4] & Ripper::EXPR_ARG | Ripper::EXPR_LABELED > 0
else
self[4] == other[4]
end
end
end
# If we have an identifier that follows a method name like:
#
# def foo bar
#
# then Ripper will mark bar as END|LABEL if there is a local in a parent
# scope named bar because it hasn't pushed the local table yet. We do this
# more accurately, so we need to allow comparing against both END and
# END|LABEL.
class ParamToken < Token
def ==(other) # :nodoc:
(self[0...-1] == other[0...-1]) && (
(other[3] == Ripper::EXPR_END) ||
(other[3] == Ripper::EXPR_END | Ripper::EXPR_LABEL)
)
end
end
# A heredoc in this case is a list of tokens that belong to the body of the
# heredoc that should be appended onto the list of tokens when the heredoc
# closes.
module Heredoc # :nodoc:
# Heredocs that are no dash or tilde heredocs are just a list of tokens.
# We need to keep them around so that we can insert them in the correct
# order back into the token stream and set the state of the last token to
# the state that the heredoc was opened in.
class PlainHeredoc # :nodoc:
attr_reader :tokens
def initialize
@tokens = []
end
def <<(token)
tokens << token
end
def to_a
tokens
end
end
# Dash heredocs are a little more complicated. They are a list of tokens
# that need to be split on "\\\n" to mimic Ripper's behavior. We also need
# to keep track of the state that the heredoc was opened in.
class DashHeredoc # :nodoc:
attr_reader :split, :tokens
def initialize(split)
@split = split
@tokens = []
end
def <<(token)
tokens << token
end
def to_a
embexpr_balance = 0
tokens.each_with_object([]) do |token, results|
case token.event
when :on_embexpr_beg
embexpr_balance += 1
results << token
when :on_embexpr_end
embexpr_balance -= 1
results << token
when :on_tstring_content
if embexpr_balance == 0
lineno = token[0][0]
column = token[0][1]
if split
# Split on "\\\n" to mimic Ripper's behavior. Use a lookbehind
# to keep the delimiter in the result.
token.value.split(/(?<=[^\\]\\\n)|(?<=[^\\]\\\r\n)/).each_with_index do |value, index|
column = 0 if index > 0
results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
lineno += value.count("\n")
end
else
results << token
end
else
results << token
end
else
results << token
end
end
end
end
# Heredocs that are dedenting heredocs are a little more complicated.
# Ripper outputs on_ignored_sp tokens for the whitespace that is being
# removed from the output. prism only modifies the node itself and keeps
# the token the same. This simplifies prism, but makes comparing against
# Ripper much harder because there is a length mismatch.
#
# Fortunately, we already have to pull out the heredoc tokens in order to
# insert them into the stream in the correct order. As such, we can do
# some extra manipulation on the tokens to make them match Ripper's
# output by mirroring the dedent logic that Ripper uses.
class DedentingHeredoc # :nodoc:
TAB_WIDTH = 8
attr_reader :tokens, :dedent_next, :dedent, :embexpr_balance
def initialize
@tokens = []
@dedent_next = true
@dedent = nil
@embexpr_balance = 0
@ended_on_newline = false
end
# As tokens are coming in, we track the minimum amount of common leading
# whitespace on plain string content tokens. This allows us to later
# remove that amount of whitespace from the beginning of each line.
def <<(token)
case token.event
when :on_embexpr_beg, :on_heredoc_beg
@embexpr_balance += 1
@dedent = 0 if @dedent_next && @ended_on_newline
when :on_embexpr_end, :on_heredoc_end
@embexpr_balance -= 1
when :on_tstring_content
if embexpr_balance == 0
line = token.value
if dedent_next && !(line.strip.empty? && line.end_with?("\n"))
leading = line[/\A(\s*)\n?/, 1]
next_dedent = 0
leading.each_char do |char|
if char == "\t"
next_dedent = next_dedent - (next_dedent % TAB_WIDTH) + TAB_WIDTH
else
next_dedent += 1
end
end
@dedent = [dedent, next_dedent].compact.min
@dedent_next = true
@ended_on_newline = line.end_with?("\n")
tokens << token
return
end
end
end
@dedent_next = token.event == :on_tstring_content && embexpr_balance == 0
@ended_on_newline = false
tokens << token
end
def to_a
# If every line in the heredoc is blank, we still need to split up the
# string content token into multiple tokens.
if dedent.nil?
results = []
embexpr_balance = 0
tokens.each do |token|
case token.event
when :on_embexpr_beg, :on_heredoc_beg
embexpr_balance += 1
results << token
when :on_embexpr_end, :on_heredoc_end
embexpr_balance -= 1
results << token
when :on_tstring_content
if embexpr_balance == 0
lineno = token[0][0]
column = token[0][1]
token.value.split(/(?<=\n)/).each_with_index do |value, index|
column = 0 if index > 0
results << Token.new([[lineno, column], :on_tstring_content, value, token.state])
lineno += 1
end
else
results << token
end
else
results << token
end
end
return results
end
# If the minimum common whitespace is 0, then we need to concatenate
# string nodes together that are immediately adjacent.
if dedent == 0
results = []
embexpr_balance = 0
index = 0
max_index = tokens.length
while index < max_index
token = tokens[index]
results << token
index += 1
case token.event
when :on_embexpr_beg, :on_heredoc_beg
embexpr_balance += 1
when :on_embexpr_end, :on_heredoc_end
embexpr_balance -= 1
when :on_tstring_content
if embexpr_balance == 0
while index < max_index && tokens[index].event == :on_tstring_content
token.value << tokens[index].value
index += 1
end
end
end
end
return results
end
# Otherwise, we're going to run through each token in the list and
# insert on_ignored_sp tokens for the amount of dedent that we need to
# perform. We also need to remove the dedent from the beginning of
# each line of plain string content tokens.
results = []
dedent_next = true
embexpr_balance = 0
tokens.each do |token|
# Notice that the structure of this conditional largely matches the
# whitespace calculation we performed above. This is because
# checking if the subsequent token needs to be dedented is common to
# both the dedent calculation and the ignored_sp insertion.
case token.event
when :on_embexpr_beg
embexpr_balance += 1
results << token
when :on_embexpr_end
embexpr_balance -= 1
results << token
when :on_tstring_content
if embexpr_balance == 0
# Here we're going to split the string on newlines, but maintain
# the newlines in the resulting array. We'll do that with a look
# behind assertion.
splits = token.value.split(/(?<=\n)/)
index = 0
while index < splits.length
line = splits[index]
lineno = token[0][0] + index
column = token[0][1]
# Blank lines do not count toward common leading whitespace
# calculation and do not need to be dedented.
if dedent_next || index > 0
column = 0
end
# If the dedent is 0 and we're not supposed to dedent the next
# line or this line doesn't start with whitespace, then we
# should concatenate the rest of the string to match ripper.
if dedent == 0 && (!dedent_next || !line.start_with?(/\s/))
line = splits[index..].join
index = splits.length
end
# If we are supposed to dedent this line or if this is not the
# first line of the string and this line isn't entirely blank,
# then we need to insert an on_ignored_sp token and remove the
# dedent from the beginning of the line.
if (dedent > 0) && (dedent_next || index > 0)
deleting = 0
deleted_chars = []
# Gather up all of the characters that we're going to
# delete, stopping when you hit a character that would put
# you over the dedent amount.
line.each_char.with_index do |char, i|
case char
when "\r"
if line[i + 1] == "\n"
break
end
when "\n"
break
when "\t"
deleting = deleting - (deleting % TAB_WIDTH) + TAB_WIDTH
else
deleting += 1
end
break if deleting > dedent
deleted_chars << char
end
# If we have something to delete, then delete it from the
# string and insert an on_ignored_sp token.
if deleted_chars.any?
ignored = deleted_chars.join
line.delete_prefix!(ignored)
results << Token.new([[lineno, 0], :on_ignored_sp, ignored, token[3]])
column = ignored.length
end
end
results << Token.new([[lineno, column], token[1], line, token[3]]) unless line.empty?
index += 1
end
else
results << token
end
else
results << token
end
dedent_next =
((token.event == :on_tstring_content) || (token.event == :on_heredoc_end)) &&
embexpr_balance == 0
end
results
end
end
# Here we will split between the two types of heredocs and return the
# object that will store their tokens.
def self.build(opening)
case opening.value[2]
when "~"
DedentingHeredoc.new
when "-"
DashHeredoc.new(opening.value[3] != "'")
else
PlainHeredoc.new
end
end
end
private_constant :Heredoc
attr_reader :source, :options
def initialize(source, **options)
@source = source
@options = options
end
def result
tokens = []
state = :default
heredoc_stack = [[]]
result = Prism.lex(source, **options)
result_value = result.value
previous_state = nil
last_heredoc_end = nil
# In previous versions of Ruby, Ripper wouldn't flush the bom before the
# first token, so we had to have a hack in place to account for that. This
# checks for that behavior.
bom_flushed = Ripper.lex("\xEF\xBB\xBF# test")[0][0][1] == 0
bom = source.byteslice(0..2) == "\xEF\xBB\xBF"
result_value.each_with_index do |(token, lex_state), index|
lineno = token.location.start_line
column = token.location.start_column
# If there's a UTF-8 byte-order mark as the start of the file, then for
# certain tokens ripper sets the first token back by 3 bytes. It also
# keeps the byte order mark in the first token's value. This is weird,
# and I don't want to mirror that in our parser. So instead, we'll match
# up the columns and values here.
if bom && lineno == 1
column -= 3
if index == 0 && column == 0 && !bom_flushed
flushed =
case token.type
when :BACK_REFERENCE, :INSTANCE_VARIABLE, :CLASS_VARIABLE,
:GLOBAL_VARIABLE, :NUMBERED_REFERENCE, :PERCENT_LOWER_I,
:PERCENT_LOWER_X, :PERCENT_LOWER_W, :PERCENT_UPPER_I,
:PERCENT_UPPER_W, :STRING_BEGIN
true
when :REGEXP_BEGIN, :SYMBOL_BEGIN
token.value.start_with?("%")
else
false
end
unless flushed
column -= 3
value = token.value
value.prepend(String.new("\xEF\xBB\xBF", encoding: value.encoding))
end
end
end
event = RIPPER.fetch(token.type)
value = token.value
lex_state = Ripper::Lexer::State.new(lex_state)
token =
case event
when :on___end__
EndContentToken.new([[lineno, column], event, value, lex_state])
when :on_comment
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_heredoc_end
# Heredoc end tokens can be emitted in an odd order, so we don't
# want to bother comparing the state on them.
last_heredoc_end = token.location.end_offset
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_ident
if lex_state == Ripper::EXPR_END
# If we have an identifier that follows a method name like:
#
# def foo bar
#
# then Ripper will mark bar as END|LABEL if there is a local in a
# parent scope named bar because it hasn't pushed the local table
# yet. We do this more accurately, so we need to allow comparing
# against both END and END|LABEL.
ParamToken.new([[lineno, column], event, value, lex_state])
elsif lex_state == Ripper::EXPR_END | Ripper::EXPR_LABEL
# In the event that we're comparing identifiers, we're going to
# allow a little divergence. Ripper doesn't account for local
# variables introduced through named captures in regexes, and we
# do, which accounts for this difference.
IdentToken.new([[lineno, column], event, value, lex_state])
else
Token.new([[lineno, column], event, value, lex_state])
end
when :on_embexpr_end
IgnoreStateToken.new([[lineno, column], event, value, lex_state])
when :on_ignored_nl
# Ignored newlines can occasionally have a LABEL state attached to
# them which doesn't actually impact anything. We don't mirror that
# state so we ignored it.
IgnoredNewlineToken.new([[lineno, column], event, value, lex_state])
when :on_regexp_end
# On regex end, Ripper scans and then sets end state, so the ripper
# lexed output is begin, when it should be end. prism sets lex state
# correctly to end state, but we want to be able to compare against
# Ripper's lexed state. So here, if it's a regexp end token, we
# output the state as the previous state, solely for the sake of
# comparison.
previous_token = result_value[index - 1][0]
lex_state =
if RIPPER.fetch(previous_token.type) == :on_embexpr_end
# If the previous token is embexpr_end, then we have to do even
# more processing. The end of an embedded expression sets the
# state to the state that it had at the beginning of the
# embedded expression. So we have to go and find that state and
# set it here.
counter = 1
current_index = index - 1
until counter == 0
current_index -= 1
current_event = RIPPER.fetch(result_value[current_index][0].type)
counter += { on_embexpr_beg: -1, on_embexpr_end: 1 }[current_event] || 0
end
Ripper::Lexer::State.new(result_value[current_index][1])
else
previous_state
end
Token.new([[lineno, column], event, value, lex_state])
when :on_eof
previous_token = result_value[index - 1][0]
# If we're at the end of the file and the previous token was a
# comment and there is still whitespace after the comment, then
# Ripper will append a on_nl token (even though there isn't
# necessarily a newline). We mirror that here.
if previous_token.type == :COMMENT
# If the comment is at the start of a heredoc: <<HEREDOC # comment
# then the comment's end_offset is up near the heredoc_beg.
# This is not the correct offset to use for figuring out if
# there is trailing whitespace after the last token.
# Use the greater offset of the two to determine the start of
# the trailing whitespace.
start_offset = [previous_token.location.end_offset, last_heredoc_end].compact.max
end_offset = token.location.start_offset
if start_offset < end_offset
if bom
start_offset += 3
end_offset += 3
end
tokens << Token.new([[lineno, 0], :on_nl, source.byteslice(start_offset...end_offset), lex_state])
end
end
Token.new([[lineno, column], event, value, lex_state])
else
Token.new([[lineno, column], event, value, lex_state])
end
previous_state = lex_state
# The order in which tokens appear in our lexer is different from the
# order that they appear in Ripper. When we hit the declaration of a
# heredoc in prism, we skip forward and lex the rest of the content of
# the heredoc before going back and lexing at the end of the heredoc
# identifier.
#
# To match up to ripper, we keep a small state variable around here to
# track whether we're in the middle of a heredoc or not. In this way we
# can shuffle around the token to match Ripper's output.
case state
when :default
# The default state is when there are no heredocs at all. In this
# state we can append the token to the list of tokens and move on.
tokens << token
# If we get the declaration of a heredoc, then we open a new heredoc
# and move into the heredoc_opened state.
if event == :on_heredoc_beg
state = :heredoc_opened
heredoc_stack.last << Heredoc.build(token)
end
when :heredoc_opened
# The heredoc_opened state is when we've seen the declaration of a
# heredoc and are now lexing the body of the heredoc. In this state we
# push tokens onto the most recently created heredoc.
heredoc_stack.last.last << token
case event
when :on_heredoc_beg
# If we receive a heredoc declaration while lexing the body of a
# heredoc, this means we have nested heredocs. In this case we'll
# push a new heredoc onto the stack and stay in the heredoc_opened
# state since we're now lexing the body of the new heredoc.
heredoc_stack << [Heredoc.build(token)]
when :on_heredoc_end
# If we receive the end of a heredoc, then we're done lexing the
# body of the heredoc. In this case we now have a completed heredoc
# but need to wait for the next newline to push it into the token
# stream.
state = :heredoc_closed
end
when :heredoc_closed
if %i[on_nl on_ignored_nl on_comment].include?(event) || (event == :on_tstring_content && value.end_with?("\n"))
if heredoc_stack.size > 1
flushing = heredoc_stack.pop
heredoc_stack.last.last << token
flushing.each do |heredoc|
heredoc.to_a.each do |flushed_token|
heredoc_stack.last.last << flushed_token
end
end
state = :heredoc_opened
next
end
elsif event == :on_heredoc_beg
tokens << token
state = :heredoc_opened
heredoc_stack.last << Heredoc.build(token)
next
elsif heredoc_stack.size > 1
heredoc_stack[-2].last << token
next
end
heredoc_stack.last.each do |heredoc|
tokens.concat(heredoc.to_a)
end
heredoc_stack.last.clear
state = :default
tokens << token
end
end
# Drop the EOF token from the list
tokens = tokens[0...-1]
# We sort by location to compare against Ripper's output
tokens.sort_by!(&:location)
ParseResult.new(tokens, result.comments, result.magic_comments, result.data_loc, result.errors, result.warnings, [])
end
end
private_constant :LexCompat
# This is a class that wraps the Ripper lexer to produce almost exactly the
# same tokens.
class LexRipper # :nodoc:
attr_reader :source
def initialize(source)
@source = source
end
def result
previous = []
results = []
Ripper.lex(source, raise_errors: true).each do |token|
case token[1]
when :on_sp
# skip
when :on_tstring_content
if previous[1] == :on_tstring_content && (token[2].start_with?("\#$") || token[2].start_with?("\#@"))
previous[2] << token[2]
else
results << token
previous = token
end
when :on_words_sep
if previous[1] == :on_words_sep
previous[2] << token[2]
else
results << token
previous = token
end
else
results << token
previous = token
end
end
results
end
end
private_constant :LexRipper
end
share/ruby/prism/compiler.rb 0000644 00000035604 15173517735 0012146 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/compiler.rb.erb
if you are looking to modify the template
=end
module Prism
# A compiler is a visitor that returns the value of each node as it visits.
# This is as opposed to a visitor which will only walk the tree. This can be
# useful when you are trying to compile a tree into a different format.
#
# For example, to build a representation of the tree as s-expressions, you
# could write:
#
# class SExpressions < Prism::Compiler
# def visit_arguments_node(node) = [:arguments, super]
# def visit_call_node(node) = [:call, super]
# def visit_integer_node(node) = [:integer]
# def visit_program_node(node) = [:program, super]
# end
#
# Prism.parse("1 + 2").value.accept(SExpressions.new)
# # => [:program, [[[:call, [[:integer], [:arguments, [[:integer]]]]]]]]
#
class Compiler
# Visit an individual node.
def visit(node)
node&.accept(self)
end
# Visit a list of nodes.
def visit_all(nodes)
nodes.map { |node| node&.accept(self) }
end
# Visit the child nodes of the given node.
def visit_child_nodes(node)
node.compact_child_nodes.map { |node| node.accept(self) }
end
# Compile a AliasGlobalVariableNode node
alias visit_alias_global_variable_node visit_child_nodes
# Compile a AliasMethodNode node
alias visit_alias_method_node visit_child_nodes
# Compile a AlternationPatternNode node
alias visit_alternation_pattern_node visit_child_nodes
# Compile a AndNode node
alias visit_and_node visit_child_nodes
# Compile a ArgumentsNode node
alias visit_arguments_node visit_child_nodes
# Compile a ArrayNode node
alias visit_array_node visit_child_nodes
# Compile a ArrayPatternNode node
alias visit_array_pattern_node visit_child_nodes
# Compile a AssocNode node
alias visit_assoc_node visit_child_nodes
# Compile a AssocSplatNode node
alias visit_assoc_splat_node visit_child_nodes
# Compile a BackReferenceReadNode node
alias visit_back_reference_read_node visit_child_nodes
# Compile a BeginNode node
alias visit_begin_node visit_child_nodes
# Compile a BlockArgumentNode node
alias visit_block_argument_node visit_child_nodes
# Compile a BlockLocalVariableNode node
alias visit_block_local_variable_node visit_child_nodes
# Compile a BlockNode node
alias visit_block_node visit_child_nodes
# Compile a BlockParameterNode node
alias visit_block_parameter_node visit_child_nodes
# Compile a BlockParametersNode node
alias visit_block_parameters_node visit_child_nodes
# Compile a BreakNode node
alias visit_break_node visit_child_nodes
# Compile a CallAndWriteNode node
alias visit_call_and_write_node visit_child_nodes
# Compile a CallNode node
alias visit_call_node visit_child_nodes
# Compile a CallOperatorWriteNode node
alias visit_call_operator_write_node visit_child_nodes
# Compile a CallOrWriteNode node
alias visit_call_or_write_node visit_child_nodes
# Compile a CallTargetNode node
alias visit_call_target_node visit_child_nodes
# Compile a CapturePatternNode node
alias visit_capture_pattern_node visit_child_nodes
# Compile a CaseMatchNode node
alias visit_case_match_node visit_child_nodes
# Compile a CaseNode node
alias visit_case_node visit_child_nodes
# Compile a ClassNode node
alias visit_class_node visit_child_nodes
# Compile a ClassVariableAndWriteNode node
alias visit_class_variable_and_write_node visit_child_nodes
# Compile a ClassVariableOperatorWriteNode node
alias visit_class_variable_operator_write_node visit_child_nodes
# Compile a ClassVariableOrWriteNode node
alias visit_class_variable_or_write_node visit_child_nodes
# Compile a ClassVariableReadNode node
alias visit_class_variable_read_node visit_child_nodes
# Compile a ClassVariableTargetNode node
alias visit_class_variable_target_node visit_child_nodes
# Compile a ClassVariableWriteNode node
alias visit_class_variable_write_node visit_child_nodes
# Compile a ConstantAndWriteNode node
alias visit_constant_and_write_node visit_child_nodes
# Compile a ConstantOperatorWriteNode node
alias visit_constant_operator_write_node visit_child_nodes
# Compile a ConstantOrWriteNode node
alias visit_constant_or_write_node visit_child_nodes
# Compile a ConstantPathAndWriteNode node
alias visit_constant_path_and_write_node visit_child_nodes
# Compile a ConstantPathNode node
alias visit_constant_path_node visit_child_nodes
# Compile a ConstantPathOperatorWriteNode node
alias visit_constant_path_operator_write_node visit_child_nodes
# Compile a ConstantPathOrWriteNode node
alias visit_constant_path_or_write_node visit_child_nodes
# Compile a ConstantPathTargetNode node
alias visit_constant_path_target_node visit_child_nodes
# Compile a ConstantPathWriteNode node
alias visit_constant_path_write_node visit_child_nodes
# Compile a ConstantReadNode node
alias visit_constant_read_node visit_child_nodes
# Compile a ConstantTargetNode node
alias visit_constant_target_node visit_child_nodes
# Compile a ConstantWriteNode node
alias visit_constant_write_node visit_child_nodes
# Compile a DefNode node
alias visit_def_node visit_child_nodes
# Compile a DefinedNode node
alias visit_defined_node visit_child_nodes
# Compile a ElseNode node
alias visit_else_node visit_child_nodes
# Compile a EmbeddedStatementsNode node
alias visit_embedded_statements_node visit_child_nodes
# Compile a EmbeddedVariableNode node
alias visit_embedded_variable_node visit_child_nodes
# Compile a EnsureNode node
alias visit_ensure_node visit_child_nodes
# Compile a FalseNode node
alias visit_false_node visit_child_nodes
# Compile a FindPatternNode node
alias visit_find_pattern_node visit_child_nodes
# Compile a FlipFlopNode node
alias visit_flip_flop_node visit_child_nodes
# Compile a FloatNode node
alias visit_float_node visit_child_nodes
# Compile a ForNode node
alias visit_for_node visit_child_nodes
# Compile a ForwardingArgumentsNode node
alias visit_forwarding_arguments_node visit_child_nodes
# Compile a ForwardingParameterNode node
alias visit_forwarding_parameter_node visit_child_nodes
# Compile a ForwardingSuperNode node
alias visit_forwarding_super_node visit_child_nodes
# Compile a GlobalVariableAndWriteNode node
alias visit_global_variable_and_write_node visit_child_nodes
# Compile a GlobalVariableOperatorWriteNode node
alias visit_global_variable_operator_write_node visit_child_nodes
# Compile a GlobalVariableOrWriteNode node
alias visit_global_variable_or_write_node visit_child_nodes
# Compile a GlobalVariableReadNode node
alias visit_global_variable_read_node visit_child_nodes
# Compile a GlobalVariableTargetNode node
alias visit_global_variable_target_node visit_child_nodes
# Compile a GlobalVariableWriteNode node
alias visit_global_variable_write_node visit_child_nodes
# Compile a HashNode node
alias visit_hash_node visit_child_nodes
# Compile a HashPatternNode node
alias visit_hash_pattern_node visit_child_nodes
# Compile a IfNode node
alias visit_if_node visit_child_nodes
# Compile a ImaginaryNode node
alias visit_imaginary_node visit_child_nodes
# Compile a ImplicitNode node
alias visit_implicit_node visit_child_nodes
# Compile a ImplicitRestNode node
alias visit_implicit_rest_node visit_child_nodes
# Compile a InNode node
alias visit_in_node visit_child_nodes
# Compile a IndexAndWriteNode node
alias visit_index_and_write_node visit_child_nodes
# Compile a IndexOperatorWriteNode node
alias visit_index_operator_write_node visit_child_nodes
# Compile a IndexOrWriteNode node
alias visit_index_or_write_node visit_child_nodes
# Compile a IndexTargetNode node
alias visit_index_target_node visit_child_nodes
# Compile a InstanceVariableAndWriteNode node
alias visit_instance_variable_and_write_node visit_child_nodes
# Compile a InstanceVariableOperatorWriteNode node
alias visit_instance_variable_operator_write_node visit_child_nodes
# Compile a InstanceVariableOrWriteNode node
alias visit_instance_variable_or_write_node visit_child_nodes
# Compile a InstanceVariableReadNode node
alias visit_instance_variable_read_node visit_child_nodes
# Compile a InstanceVariableTargetNode node
alias visit_instance_variable_target_node visit_child_nodes
# Compile a InstanceVariableWriteNode node
alias visit_instance_variable_write_node visit_child_nodes
# Compile a IntegerNode node
alias visit_integer_node visit_child_nodes
# Compile a InterpolatedMatchLastLineNode node
alias visit_interpolated_match_last_line_node visit_child_nodes
# Compile a InterpolatedRegularExpressionNode node
alias visit_interpolated_regular_expression_node visit_child_nodes
# Compile a InterpolatedStringNode node
alias visit_interpolated_string_node visit_child_nodes
# Compile a InterpolatedSymbolNode node
alias visit_interpolated_symbol_node visit_child_nodes
# Compile a InterpolatedXStringNode node
alias visit_interpolated_x_string_node visit_child_nodes
# Compile a KeywordHashNode node
alias visit_keyword_hash_node visit_child_nodes
# Compile a KeywordRestParameterNode node
alias visit_keyword_rest_parameter_node visit_child_nodes
# Compile a LambdaNode node
alias visit_lambda_node visit_child_nodes
# Compile a LocalVariableAndWriteNode node
alias visit_local_variable_and_write_node visit_child_nodes
# Compile a LocalVariableOperatorWriteNode node
alias visit_local_variable_operator_write_node visit_child_nodes
# Compile a LocalVariableOrWriteNode node
alias visit_local_variable_or_write_node visit_child_nodes
# Compile a LocalVariableReadNode node
alias visit_local_variable_read_node visit_child_nodes
# Compile a LocalVariableTargetNode node
alias visit_local_variable_target_node visit_child_nodes
# Compile a LocalVariableWriteNode node
alias visit_local_variable_write_node visit_child_nodes
# Compile a MatchLastLineNode node
alias visit_match_last_line_node visit_child_nodes
# Compile a MatchPredicateNode node
alias visit_match_predicate_node visit_child_nodes
# Compile a MatchRequiredNode node
alias visit_match_required_node visit_child_nodes
# Compile a MatchWriteNode node
alias visit_match_write_node visit_child_nodes
# Compile a MissingNode node
alias visit_missing_node visit_child_nodes
# Compile a ModuleNode node
alias visit_module_node visit_child_nodes
# Compile a MultiTargetNode node
alias visit_multi_target_node visit_child_nodes
# Compile a MultiWriteNode node
alias visit_multi_write_node visit_child_nodes
# Compile a NextNode node
alias visit_next_node visit_child_nodes
# Compile a NilNode node
alias visit_nil_node visit_child_nodes
# Compile a NoKeywordsParameterNode node
alias visit_no_keywords_parameter_node visit_child_nodes
# Compile a NumberedParametersNode node
alias visit_numbered_parameters_node visit_child_nodes
# Compile a NumberedReferenceReadNode node
alias visit_numbered_reference_read_node visit_child_nodes
# Compile a OptionalKeywordParameterNode node
alias visit_optional_keyword_parameter_node visit_child_nodes
# Compile a OptionalParameterNode node
alias visit_optional_parameter_node visit_child_nodes
# Compile a OrNode node
alias visit_or_node visit_child_nodes
# Compile a ParametersNode node
alias visit_parameters_node visit_child_nodes
# Compile a ParenthesesNode node
alias visit_parentheses_node visit_child_nodes
# Compile a PinnedExpressionNode node
alias visit_pinned_expression_node visit_child_nodes
# Compile a PinnedVariableNode node
alias visit_pinned_variable_node visit_child_nodes
# Compile a PostExecutionNode node
alias visit_post_execution_node visit_child_nodes
# Compile a PreExecutionNode node
alias visit_pre_execution_node visit_child_nodes
# Compile a ProgramNode node
alias visit_program_node visit_child_nodes
# Compile a RangeNode node
alias visit_range_node visit_child_nodes
# Compile a RationalNode node
alias visit_rational_node visit_child_nodes
# Compile a RedoNode node
alias visit_redo_node visit_child_nodes
# Compile a RegularExpressionNode node
alias visit_regular_expression_node visit_child_nodes
# Compile a RequiredKeywordParameterNode node
alias visit_required_keyword_parameter_node visit_child_nodes
# Compile a RequiredParameterNode node
alias visit_required_parameter_node visit_child_nodes
# Compile a RescueModifierNode node
alias visit_rescue_modifier_node visit_child_nodes
# Compile a RescueNode node
alias visit_rescue_node visit_child_nodes
# Compile a RestParameterNode node
alias visit_rest_parameter_node visit_child_nodes
# Compile a RetryNode node
alias visit_retry_node visit_child_nodes
# Compile a ReturnNode node
alias visit_return_node visit_child_nodes
# Compile a SelfNode node
alias visit_self_node visit_child_nodes
# Compile a SingletonClassNode node
alias visit_singleton_class_node visit_child_nodes
# Compile a SourceEncodingNode node
alias visit_source_encoding_node visit_child_nodes
# Compile a SourceFileNode node
alias visit_source_file_node visit_child_nodes
# Compile a SourceLineNode node
alias visit_source_line_node visit_child_nodes
# Compile a SplatNode node
alias visit_splat_node visit_child_nodes
# Compile a StatementsNode node
alias visit_statements_node visit_child_nodes
# Compile a StringNode node
alias visit_string_node visit_child_nodes
# Compile a SuperNode node
alias visit_super_node visit_child_nodes
# Compile a SymbolNode node
alias visit_symbol_node visit_child_nodes
# Compile a TrueNode node
alias visit_true_node visit_child_nodes
# Compile a UndefNode node
alias visit_undef_node visit_child_nodes
# Compile a UnlessNode node
alias visit_unless_node visit_child_nodes
# Compile a UntilNode node
alias visit_until_node visit_child_nodes
# Compile a WhenNode node
alias visit_when_node visit_child_nodes
# Compile a WhileNode node
alias visit_while_node visit_child_nodes
# Compile a XStringNode node
alias visit_x_string_node visit_child_nodes
# Compile a YieldNode node
alias visit_yield_node visit_child_nodes
end
end
share/ruby/prism/mutation_compiler.rb 0000644 00000051475 15173517735 0014072 0 ustar 00 # frozen_string_literal: true
=begin
This file is generated by the templates/template.rb script and should not be
modified manually. See templates/lib/prism/mutation_compiler.rb.erb
if you are looking to modify the template
=end
module Prism
# This visitor walks through the tree and copies each node as it is being
# visited. This is useful for consumers that want to mutate the tree, as you
# can change subtrees in place without effecting the rest of the tree.
class MutationCompiler < Compiler
# Copy a AliasGlobalVariableNode node
def visit_alias_global_variable_node(node)
node.copy(new_name: visit(node.new_name), old_name: visit(node.old_name))
end
# Copy a AliasMethodNode node
def visit_alias_method_node(node)
node.copy(new_name: visit(node.new_name), old_name: visit(node.old_name))
end
# Copy a AlternationPatternNode node
def visit_alternation_pattern_node(node)
node.copy(left: visit(node.left), right: visit(node.right))
end
# Copy a AndNode node
def visit_and_node(node)
node.copy(left: visit(node.left), right: visit(node.right))
end
# Copy a ArgumentsNode node
def visit_arguments_node(node)
node.copy(arguments: visit_all(node.arguments))
end
# Copy a ArrayNode node
def visit_array_node(node)
node.copy(elements: visit_all(node.elements))
end
# Copy a ArrayPatternNode node
def visit_array_pattern_node(node)
node.copy(constant: visit(node.constant), requireds: visit_all(node.requireds), rest: visit(node.rest), posts: visit_all(node.posts))
end
# Copy a AssocNode node
def visit_assoc_node(node)
node.copy(key: visit(node.key), value: visit(node.value))
end
# Copy a AssocSplatNode node
def visit_assoc_splat_node(node)
node.copy(value: visit(node.value))
end
# Copy a BackReferenceReadNode node
def visit_back_reference_read_node(node)
node.copy
end
# Copy a BeginNode node
def visit_begin_node(node)
node.copy(statements: visit(node.statements), rescue_clause: visit(node.rescue_clause), else_clause: visit(node.else_clause), ensure_clause: visit(node.ensure_clause))
end
# Copy a BlockArgumentNode node
def visit_block_argument_node(node)
node.copy(expression: visit(node.expression))
end
# Copy a BlockLocalVariableNode node
def visit_block_local_variable_node(node)
node.copy
end
# Copy a BlockNode node
def visit_block_node(node)
node.copy(parameters: visit(node.parameters), body: visit(node.body))
end
# Copy a BlockParameterNode node
def visit_block_parameter_node(node)
node.copy
end
# Copy a BlockParametersNode node
def visit_block_parameters_node(node)
node.copy(parameters: visit(node.parameters), locals: visit_all(node.locals))
end
# Copy a BreakNode node
def visit_break_node(node)
node.copy(arguments: visit(node.arguments))
end
# Copy a CallAndWriteNode node
def visit_call_and_write_node(node)
node.copy(receiver: visit(node.receiver), value: visit(node.value))
end
# Copy a CallNode node
def visit_call_node(node)
node.copy(receiver: visit(node.receiver), arguments: visit(node.arguments), block: visit(node.block))
end
# Copy a CallOperatorWriteNode node
def visit_call_operator_write_node(node)
node.copy(receiver: visit(node.receiver), value: visit(node.value))
end
# Copy a CallOrWriteNode node
def visit_call_or_write_node(node)
node.copy(receiver: visit(node.receiver), value: visit(node.value))
end
# Copy a CallTargetNode node
def visit_call_target_node(node)
node.copy(receiver: visit(node.receiver))
end
# Copy a CapturePatternNode node
def visit_capture_pattern_node(node)
node.copy(value: visit(node.value), target: visit(node.target))
end
# Copy a CaseMatchNode node
def visit_case_match_node(node)
node.copy(predicate: visit(node.predicate), conditions: visit_all(node.conditions), consequent: visit(node.consequent))
end
# Copy a CaseNode node
def visit_case_node(node)
node.copy(predicate: visit(node.predicate), conditions: visit_all(node.conditions), consequent: visit(node.consequent))
end
# Copy a ClassNode node
def visit_class_node(node)
node.copy(constant_path: visit(node.constant_path), superclass: visit(node.superclass), body: visit(node.body))
end
# Copy a ClassVariableAndWriteNode node
def visit_class_variable_and_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ClassVariableOperatorWriteNode node
def visit_class_variable_operator_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ClassVariableOrWriteNode node
def visit_class_variable_or_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ClassVariableReadNode node
def visit_class_variable_read_node(node)
node.copy
end
# Copy a ClassVariableTargetNode node
def visit_class_variable_target_node(node)
node.copy
end
# Copy a ClassVariableWriteNode node
def visit_class_variable_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ConstantAndWriteNode node
def visit_constant_and_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ConstantOperatorWriteNode node
def visit_constant_operator_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ConstantOrWriteNode node
def visit_constant_or_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a ConstantPathAndWriteNode node
def visit_constant_path_and_write_node(node)
node.copy(target: visit(node.target), value: visit(node.value))
end
# Copy a ConstantPathNode node
def visit_constant_path_node(node)
node.copy(parent: visit(node.parent), child: visit(node.child))
end
# Copy a ConstantPathOperatorWriteNode node
def visit_constant_path_operator_write_node(node)
node.copy(target: visit(node.target), value: visit(node.value))
end
# Copy a ConstantPathOrWriteNode node
def visit_constant_path_or_write_node(node)
node.copy(target: visit(node.target), value: visit(node.value))
end
# Copy a ConstantPathTargetNode node
def visit_constant_path_target_node(node)
node.copy(parent: visit(node.parent), child: visit(node.child))
end
# Copy a ConstantPathWriteNode node
def visit_constant_path_write_node(node)
node.copy(target: visit(node.target), value: visit(node.value))
end
# Copy a ConstantReadNode node
def visit_constant_read_node(node)
node.copy
end
# Copy a ConstantTargetNode node
def visit_constant_target_node(node)
node.copy
end
# Copy a ConstantWriteNode node
def visit_constant_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a DefNode node
def visit_def_node(node)
node.copy(receiver: visit(node.receiver), parameters: visit(node.parameters), body: visit(node.body))
end
# Copy a DefinedNode node
def visit_defined_node(node)
node.copy(value: visit(node.value))
end
# Copy a ElseNode node
def visit_else_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a EmbeddedStatementsNode node
def visit_embedded_statements_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a EmbeddedVariableNode node
def visit_embedded_variable_node(node)
node.copy(variable: visit(node.variable))
end
# Copy a EnsureNode node
def visit_ensure_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a FalseNode node
def visit_false_node(node)
node.copy
end
# Copy a FindPatternNode node
def visit_find_pattern_node(node)
node.copy(constant: visit(node.constant), left: visit(node.left), requireds: visit_all(node.requireds), right: visit(node.right))
end
# Copy a FlipFlopNode node
def visit_flip_flop_node(node)
node.copy(left: visit(node.left), right: visit(node.right))
end
# Copy a FloatNode node
def visit_float_node(node)
node.copy
end
# Copy a ForNode node
def visit_for_node(node)
node.copy(index: visit(node.index), collection: visit(node.collection), statements: visit(node.statements))
end
# Copy a ForwardingArgumentsNode node
def visit_forwarding_arguments_node(node)
node.copy
end
# Copy a ForwardingParameterNode node
def visit_forwarding_parameter_node(node)
node.copy
end
# Copy a ForwardingSuperNode node
def visit_forwarding_super_node(node)
node.copy(block: visit(node.block))
end
# Copy a GlobalVariableAndWriteNode node
def visit_global_variable_and_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a GlobalVariableOperatorWriteNode node
def visit_global_variable_operator_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a GlobalVariableOrWriteNode node
def visit_global_variable_or_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a GlobalVariableReadNode node
def visit_global_variable_read_node(node)
node.copy
end
# Copy a GlobalVariableTargetNode node
def visit_global_variable_target_node(node)
node.copy
end
# Copy a GlobalVariableWriteNode node
def visit_global_variable_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a HashNode node
def visit_hash_node(node)
node.copy(elements: visit_all(node.elements))
end
# Copy a HashPatternNode node
def visit_hash_pattern_node(node)
node.copy(constant: visit(node.constant), elements: visit_all(node.elements), rest: visit(node.rest))
end
# Copy a IfNode node
def visit_if_node(node)
node.copy(predicate: visit(node.predicate), statements: visit(node.statements), consequent: visit(node.consequent))
end
# Copy a ImaginaryNode node
def visit_imaginary_node(node)
node.copy(numeric: visit(node.numeric))
end
# Copy a ImplicitNode node
def visit_implicit_node(node)
node.copy(value: visit(node.value))
end
# Copy a ImplicitRestNode node
def visit_implicit_rest_node(node)
node.copy
end
# Copy a InNode node
def visit_in_node(node)
node.copy(pattern: visit(node.pattern), statements: visit(node.statements))
end
# Copy a IndexAndWriteNode node
def visit_index_and_write_node(node)
node.copy(receiver: visit(node.receiver), arguments: visit(node.arguments), block: visit(node.block), value: visit(node.value))
end
# Copy a IndexOperatorWriteNode node
def visit_index_operator_write_node(node)
node.copy(receiver: visit(node.receiver), arguments: visit(node.arguments), block: visit(node.block), value: visit(node.value))
end
# Copy a IndexOrWriteNode node
def visit_index_or_write_node(node)
node.copy(receiver: visit(node.receiver), arguments: visit(node.arguments), block: visit(node.block), value: visit(node.value))
end
# Copy a IndexTargetNode node
def visit_index_target_node(node)
node.copy(receiver: visit(node.receiver), arguments: visit(node.arguments), block: visit(node.block))
end
# Copy a InstanceVariableAndWriteNode node
def visit_instance_variable_and_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a InstanceVariableOperatorWriteNode node
def visit_instance_variable_operator_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a InstanceVariableOrWriteNode node
def visit_instance_variable_or_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a InstanceVariableReadNode node
def visit_instance_variable_read_node(node)
node.copy
end
# Copy a InstanceVariableTargetNode node
def visit_instance_variable_target_node(node)
node.copy
end
# Copy a InstanceVariableWriteNode node
def visit_instance_variable_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a IntegerNode node
def visit_integer_node(node)
node.copy
end
# Copy a InterpolatedMatchLastLineNode node
def visit_interpolated_match_last_line_node(node)
node.copy(parts: visit_all(node.parts))
end
# Copy a InterpolatedRegularExpressionNode node
def visit_interpolated_regular_expression_node(node)
node.copy(parts: visit_all(node.parts))
end
# Copy a InterpolatedStringNode node
def visit_interpolated_string_node(node)
node.copy(parts: visit_all(node.parts))
end
# Copy a InterpolatedSymbolNode node
def visit_interpolated_symbol_node(node)
node.copy(parts: visit_all(node.parts))
end
# Copy a InterpolatedXStringNode node
def visit_interpolated_x_string_node(node)
node.copy(parts: visit_all(node.parts))
end
# Copy a KeywordHashNode node
def visit_keyword_hash_node(node)
node.copy(elements: visit_all(node.elements))
end
# Copy a KeywordRestParameterNode node
def visit_keyword_rest_parameter_node(node)
node.copy
end
# Copy a LambdaNode node
def visit_lambda_node(node)
node.copy(parameters: visit(node.parameters), body: visit(node.body))
end
# Copy a LocalVariableAndWriteNode node
def visit_local_variable_and_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a LocalVariableOperatorWriteNode node
def visit_local_variable_operator_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a LocalVariableOrWriteNode node
def visit_local_variable_or_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a LocalVariableReadNode node
def visit_local_variable_read_node(node)
node.copy
end
# Copy a LocalVariableTargetNode node
def visit_local_variable_target_node(node)
node.copy
end
# Copy a LocalVariableWriteNode node
def visit_local_variable_write_node(node)
node.copy(value: visit(node.value))
end
# Copy a MatchLastLineNode node
def visit_match_last_line_node(node)
node.copy
end
# Copy a MatchPredicateNode node
def visit_match_predicate_node(node)
node.copy(value: visit(node.value), pattern: visit(node.pattern))
end
# Copy a MatchRequiredNode node
def visit_match_required_node(node)
node.copy(value: visit(node.value), pattern: visit(node.pattern))
end
# Copy a MatchWriteNode node
def visit_match_write_node(node)
node.copy(call: visit(node.call), targets: visit_all(node.targets))
end
# Copy a MissingNode node
def visit_missing_node(node)
node.copy
end
# Copy a ModuleNode node
def visit_module_node(node)
node.copy(constant_path: visit(node.constant_path), body: visit(node.body))
end
# Copy a MultiTargetNode node
def visit_multi_target_node(node)
node.copy(lefts: visit_all(node.lefts), rest: visit(node.rest), rights: visit_all(node.rights))
end
# Copy a MultiWriteNode node
def visit_multi_write_node(node)
node.copy(lefts: visit_all(node.lefts), rest: visit(node.rest), rights: visit_all(node.rights), value: visit(node.value))
end
# Copy a NextNode node
def visit_next_node(node)
node.copy(arguments: visit(node.arguments))
end
# Copy a NilNode node
def visit_nil_node(node)
node.copy
end
# Copy a NoKeywordsParameterNode node
def visit_no_keywords_parameter_node(node)
node.copy
end
# Copy a NumberedParametersNode node
def visit_numbered_parameters_node(node)
node.copy
end
# Copy a NumberedReferenceReadNode node
def visit_numbered_reference_read_node(node)
node.copy
end
# Copy a OptionalKeywordParameterNode node
def visit_optional_keyword_parameter_node(node)
node.copy(value: visit(node.value))
end
# Copy a OptionalParameterNode node
def visit_optional_parameter_node(node)
node.copy(value: visit(node.value))
end
# Copy a OrNode node
def visit_or_node(node)
node.copy(left: visit(node.left), right: visit(node.right))
end
# Copy a ParametersNode node
def visit_parameters_node(node)
node.copy(requireds: visit_all(node.requireds), optionals: visit_all(node.optionals), rest: visit(node.rest), posts: visit_all(node.posts), keywords: visit_all(node.keywords), keyword_rest: visit(node.keyword_rest), block: visit(node.block))
end
# Copy a ParenthesesNode node
def visit_parentheses_node(node)
node.copy(body: visit(node.body))
end
# Copy a PinnedExpressionNode node
def visit_pinned_expression_node(node)
node.copy(expression: visit(node.expression))
end
# Copy a PinnedVariableNode node
def visit_pinned_variable_node(node)
node.copy(variable: visit(node.variable))
end
# Copy a PostExecutionNode node
def visit_post_execution_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a PreExecutionNode node
def visit_pre_execution_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a ProgramNode node
def visit_program_node(node)
node.copy(statements: visit(node.statements))
end
# Copy a RangeNode node
def visit_range_node(node)
node.copy(left: visit(node.left), right: visit(node.right))
end
# Copy a RationalNode node
def visit_rational_node(node)
node.copy(numeric: visit(node.numeric))
end
# Copy a RedoNode node
def visit_redo_node(node)
node.copy
end
# Copy a RegularExpressionNode node
def visit_regular_expression_node(node)
node.copy
end
# Copy a RequiredKeywordParameterNode node
def visit_required_keyword_parameter_node(node)
node.copy
end
# Copy a RequiredParameterNode node
def visit_required_parameter_node(node)
node.copy
end
# Copy a RescueModifierNode node
def visit_rescue_modifier_node(node)
node.copy(expression: visit(node.expression), rescue_expression: visit(node.rescue_expression))
end
# Copy a RescueNode node
def visit_rescue_node(node)
node.copy(exceptions: visit_all(node.exceptions), reference: visit(node.reference), statements: visit(node.statements), consequent: visit(node.consequent))
end
# Copy a RestParameterNode node
def visit_rest_parameter_node(node)
node.copy
end
# Copy a RetryNode node
def visit_retry_node(node)
node.copy
end
# Copy a ReturnNode node
def visit_return_node(node)
node.copy(arguments: visit(node.arguments))
end
# Copy a SelfNode node
def visit_self_node(node)
node.copy
end
# Copy a SingletonClassNode node
def visit_singleton_class_node(node)
node.copy(expression: visit(node.expression), body: visit(node.body))
end
# Copy a SourceEncodingNode node
def visit_source_encoding_node(node)
node.copy
end
# Copy a SourceFileNode node
def visit_source_file_node(node)
node.copy
end
# Copy a SourceLineNode node
def visit_source_line_node(node)
node.copy
end
# Copy a SplatNode node
def visit_splat_node(node)
node.copy(expression: visit(node.expression))
end
# Copy a StatementsNode node
def visit_statements_node(node)
node.copy(body: visit_all(node.body))
end
# Copy a StringNode node
def visit_string_node(node)
node.copy
end
# Copy a SuperNode node
def visit_super_node(node)
node.copy(arguments: visit(node.arguments), block: visit(node.block))
end
# Copy a SymbolNode node
def visit_symbol_node(node)
node.copy
end
# Copy a TrueNode node
def visit_true_node(node)
node.copy
end
# Copy a UndefNode node
def visit_undef_node(node)
node.copy(names: visit_all(node.names))
end
# Copy a UnlessNode node
def visit_unless_node(node)
node.copy(predicate: visit(node.predicate), statements: visit(node.statements), consequent: visit(node.consequent))
end
# Copy a UntilNode node
def visit_until_node(node)
node.copy(predicate: visit(node.predicate), statements: visit(node.statements))
end
# Copy a WhenNode node
def visit_when_node(node)
node.copy(conditions: visit_all(node.conditions), statements: visit(node.statements))
end
# Copy a WhileNode node
def visit_while_node(node)
node.copy(predicate: visit(node.predicate), statements: visit(node.statements))
end
# Copy a XStringNode node
def visit_x_string_node(node)
node.copy
end
# Copy a YieldNode node
def visit_yield_node(node)
node.copy(arguments: visit(node.arguments))
end
end
end
share/ruby/base64.rb 0000644 00000032341 15173517735 0010261 0 ustar 00 # frozen_string_literal: true
#
# \Module \Base64 provides methods for:
#
# - Encoding a binary string (containing non-ASCII characters)
# as a string of printable ASCII characters.
# - Decoding such an encoded string.
#
# \Base64 is commonly used in contexts where binary data
# is not allowed or supported:
#
# - Images in HTML or CSS files, or in URLs.
# - Email attachments.
#
# A \Base64-encoded string is about one-third larger that its source.
# See the {Wikipedia article}[https://en.wikipedia.org/wiki/Base64]
# for more information.
#
# This module provides three pairs of encode/decode methods.
# Your choices among these methods should depend on:
#
# - Which character set is to be used for encoding and decoding.
# - Whether "padding" is to be used.
# - Whether encoded strings are to contain newlines.
#
# Note: Examples on this page assume that the including program has executed:
#
# require 'base64'
#
# == Encoding Character Sets
#
# A \Base64-encoded string consists only of characters from a 64-character set:
#
# - <tt>('A'..'Z')</tt>.
# - <tt>('a'..'z')</tt>.
# - <tt>('0'..'9')</tt>.
# - <tt>=</tt>, the 'padding' character.
# - Either:
# - <tt>%w[+ /]</tt>:
# {RFC-2045-compliant}[https://datatracker.ietf.org/doc/html/rfc2045];
# _not_ safe for URLs.
# - <tt>%w[- _]</tt>:
# {RFC-4648-compliant}[https://datatracker.ietf.org/doc/html/rfc4648];
# safe for URLs.
#
# If you are working with \Base64-encoded strings that will come from
# or be put into URLs, you should choose this encoder-decoder pair
# of RFC-4648-compliant methods:
#
# - Base64.urlsafe_encode64 and Base64.urlsafe_decode64.
#
# Otherwise, you may choose any of the pairs in this module,
# including the pair above, or the RFC-2045-compliant pairs:
#
# - Base64.encode64 and Base64.decode64.
# - Base64.strict_encode64 and Base64.strict_decode64.
#
# == Padding
#
# \Base64-encoding changes a triplet of input bytes
# into a quartet of output characters.
#
# <b>Padding in Encode Methods</b>
#
# Padding -- extending an encoded string with zero, one, or two trailing
# <tt>=</tt> characters -- is performed by methods Base64.encode64,
# Base64.strict_encode64, and, by default, Base64.urlsafe_encode64:
#
# Base64.encode64('s') # => "cw==\n"
# Base64.strict_encode64('s') # => "cw=="
# Base64.urlsafe_encode64('s') # => "cw=="
# Base64.urlsafe_encode64('s', padding: false) # => "cw"
#
# When padding is performed, the encoded string is always of length <em>4n</em>,
# where +n+ is a non-negative integer:
#
# - Input bytes of length <em>3n</em> generate unpadded output characters
# of length <em>4n</em>:
#
# # n = 1: 3 bytes => 4 characters.
# Base64.strict_encode64('123') # => "MDEy"
# # n = 2: 6 bytes => 8 characters.
# Base64.strict_encode64('123456') # => "MDEyMzQ1"
#
# - Input bytes of length <em>3n+1</em> generate padded output characters
# of length <em>4(n+1)</em>, with two padding characters at the end:
#
# # n = 1: 4 bytes => 8 characters.
# Base64.strict_encode64('1234') # => "MDEyMw=="
# # n = 2: 7 bytes => 12 characters.
# Base64.strict_encode64('1234567') # => "MDEyMzQ1Ng=="
#
# - Input bytes of length <em>3n+2</em> generate padded output characters
# of length <em>4(n+1)</em>, with one padding character at the end:
#
# # n = 1: 5 bytes => 8 characters.
# Base64.strict_encode64('12345') # => "MDEyMzQ="
# # n = 2: 8 bytes => 12 characters.
# Base64.strict_encode64('12345678') # => "MDEyMzQ1Njc="
#
# When padding is suppressed, for a positive integer <em>n</em>:
#
# - Input bytes of length <em>3n</em> generate unpadded output characters
# of length <em>4n</em>:
#
# # n = 1: 3 bytes => 4 characters.
# Base64.urlsafe_encode64('123', padding: false) # => "MDEy"
# # n = 2: 6 bytes => 8 characters.
# Base64.urlsafe_encode64('123456', padding: false) # => "MDEyMzQ1"
#
# - Input bytes of length <em>3n+1</em> generate unpadded output characters
# of length <em>4n+2</em>, with two padding characters at the end:
#
# # n = 1: 4 bytes => 6 characters.
# Base64.urlsafe_encode64('1234', padding: false) # => "MDEyMw"
# # n = 2: 7 bytes => 10 characters.
# Base64.urlsafe_encode64('1234567', padding: false) # => "MDEyMzQ1Ng"
#
# - Input bytes of length <em>3n+2</em> generate unpadded output characters
# of length <em>4n+3</em>, with one padding character at the end:
#
# # n = 1: 5 bytes => 7 characters.
# Base64.urlsafe_encode64('12345', padding: false) # => "MDEyMzQ"
# # m = 2: 8 bytes => 11 characters.
# Base64.urlsafe_encode64('12345678', padding: false) # => "MDEyMzQ1Njc"
#
# <b>Padding in Decode Methods</b>
#
# All of the \Base64 decode methods support (but do not require) padding.
#
# \Method Base64.decode64 does not check the size of the padding:
#
# Base64.decode64("MDEyMzQ1Njc") # => "01234567"
# Base64.decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.decode64("MDEyMzQ1Njc==") # => "01234567"
#
# \Method Base64.strict_decode64 strictly enforces padding size:
#
# Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError
# Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError
#
# \Method Base64.urlsafe_decode64 allows padding in +str+,
# which if present, must be correct:
# see {Padding}[Base64.html#module-Base64-label-Padding], above:
#
# Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567"
# Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError.
#
# == Newlines
#
# An encoded string returned by Base64.encode64 or Base64.urlsafe_encode64
# has an embedded newline character
# after each 60-character sequence, and, if non-empty, at the end:
#
# # No newline if empty.
# encoded = Base64.encode64("\x00" * 0)
# encoded.index("\n") # => nil
#
# # Newline at end of short output.
# encoded = Base64.encode64("\x00" * 1)
# encoded.size # => 4
# encoded.index("\n") # => 4
#
# # Newline at end of longer output.
# encoded = Base64.encode64("\x00" * 45)
# encoded.size # => 60
# encoded.index("\n") # => 60
#
# # Newlines embedded and at end of still longer output.
# encoded = Base64.encode64("\x00" * 46)
# encoded.size # => 65
# encoded.rindex("\n") # => 65
# encoded.split("\n").map {|s| s.size } # => [60, 4]
#
# The string to be encoded may itself contain newlines,
# which are encoded as \Base64:
#
# # Base64.encode64("\n\n\n") # => "CgoK\n"
# s = "This is line 1\nThis is line 2\n"
# Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n"
#
module Base64
VERSION = "0.2.0"
module_function
# Returns a string containing the RFC-2045-compliant \Base64-encoding of +bin+.
#
# Per RFC 2045, the returned string may contain the URL-unsafe characters
# <tt>+</tt> or <tt>/</tt>;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
#
# Base64.encode64("\xFB\xEF\xBE") # => "++++\n"
# Base64.encode64("\xFF\xFF\xFF") # => "////\n"
#
# The returned string may include padding;
# see {Padding}[Base64.html#module-Base64-label-Padding] above.
#
# Base64.encode64('*') # => "Kg==\n"
#
# The returned string ends with a newline character, and if sufficiently long
# will have one or more embedded newline characters;
# see {Newlines}[Base64.html#module-Base64-label-Newlines] above:
#
# Base64.encode64('*') # => "Kg==\n"
# Base64.encode64('*' * 46)
# # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"
#
# The string to be encoded may itself contain newlines,
# which will be encoded as ordinary \Base64:
#
# Base64.encode64("\n\n\n") # => "CgoK\n"
# s = "This is line 1\nThis is line 2\n"
# Base64.encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n"
#
def encode64(bin)
[bin].pack("m")
end
# Returns a string containing the decoding of an RFC-2045-compliant
# \Base64-encoded string +str+:
#
# s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK\n"
# Base64.decode64(s) # => "This is line 1\nThis is line 2\n"
#
# Non-\Base64 characters in +str+ are ignored;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
# these include newline characters and characters <tt>-</tt> and <tt>/</tt>:
#
# Base64.decode64("\x00\n-_") # => ""
#
# Padding in +str+ (even if incorrect) is ignored:
#
# Base64.decode64("MDEyMzQ1Njc") # => "01234567"
# Base64.decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.decode64("MDEyMzQ1Njc==") # => "01234567"
#
def decode64(str)
str.unpack1("m")
end
# Returns a string containing the RFC-2045-compliant \Base64-encoding of +bin+.
#
# Per RFC 2045, the returned string may contain the URL-unsafe characters
# <tt>+</tt> or <tt>/</tt>;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
#
# Base64.strict_encode64("\xFB\xEF\xBE") # => "++++\n"
# Base64.strict_encode64("\xFF\xFF\xFF") # => "////\n"
#
# The returned string may include padding;
# see {Padding}[Base64.html#module-Base64-label-Padding] above.
#
# Base64.strict_encode64('*') # => "Kg==\n"
#
# The returned string will have no newline characters, regardless of its length;
# see {Newlines}[Base64.html#module-Base64-label-Newlines] above:
#
# Base64.strict_encode64('*') # => "Kg=="
# Base64.strict_encode64('*' * 46)
# # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg=="
#
# The string to be encoded may itself contain newlines,
# which will be encoded as ordinary \Base64:
#
# Base64.strict_encode64("\n\n\n") # => "CgoK"
# s = "This is line 1\nThis is line 2\n"
# Base64.strict_encode64(s) # => "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK"
#
def strict_encode64(bin)
[bin].pack("m0")
end
# Returns a string containing the decoding of an RFC-2045-compliant
# \Base64-encoded string +str+:
#
# s = "VGhpcyBpcyBsaW5lIDEKVGhpcyBpcyBsaW5lIDIK"
# Base64.strict_decode64(s) # => "This is line 1\nThis is line 2\n"
#
# Non-\Base64 characters in +str+ not allowed;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
# these include newline characters and characters <tt>-</tt> and <tt>/</tt>:
#
# Base64.strict_decode64("\n") # Raises ArgumentError
# Base64.strict_decode64('-') # Raises ArgumentError
# Base64.strict_decode64('_') # Raises ArgumentError
#
# Padding in +str+, if present, must be correct:
#
# Base64.strict_decode64("MDEyMzQ1Njc") # Raises ArgumentError
# Base64.strict_decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.strict_decode64("MDEyMzQ1Njc==") # Raises ArgumentError
#
def strict_decode64(str)
str.unpack1("m0")
end
# Returns the RFC-4648-compliant \Base64-encoding of +bin+.
#
# Per RFC 4648, the returned string will not contain the URL-unsafe characters
# <tt>+</tt> or <tt>/</tt>,
# but instead may contain the URL-safe characters
# <tt>-</tt> and <tt>_</tt>;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
#
# Base64.urlsafe_encode64("\xFB\xEF\xBE") # => "----"
# Base64.urlsafe_encode64("\xFF\xFF\xFF") # => "____"
#
# By default, the returned string may have padding;
# see {Padding}[Base64.html#module-Base64-label-Padding], above:
#
# Base64.urlsafe_encode64('*') # => "Kg=="
#
# Optionally, you can suppress padding:
#
# Base64.urlsafe_encode64('*', padding: false) # => "Kg"
#
# The returned string will have no newline characters, regardless of its length;
# see {Newlines}[Base64.html#module-Base64-label-Newlines] above:
#
# Base64.urlsafe_encode64('*') # => "Kg=="
# Base64.urlsafe_encode64('*' * 46)
# # => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKg=="
#
def urlsafe_encode64(bin, padding: true)
str = strict_encode64(bin)
str.chomp!("==") or str.chomp!("=") unless padding
str.tr!("+/", "-_")
str
end
# Returns the decoding of an RFC-4648-compliant \Base64-encoded string +str+:
#
# +str+ may not contain non-Base64 characters;
# see {Encoding Character Set}[Base64.html#module-Base64-label-Encoding+Character+Sets] above:
#
# Base64.urlsafe_decode64('+') # Raises ArgumentError.
# Base64.urlsafe_decode64('/') # Raises ArgumentError.
# Base64.urlsafe_decode64("\n") # Raises ArgumentError.
#
# Padding in +str+, if present, must be correct:
# see {Padding}[Base64.html#module-Base64-label-Padding], above:
#
# Base64.urlsafe_decode64("MDEyMzQ1Njc") # => "01234567"
# Base64.urlsafe_decode64("MDEyMzQ1Njc=") # => "01234567"
# Base64.urlsafe_decode64("MDEyMzQ1Njc==") # Raises ArgumentError.
#
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 00000012554 15173517735 0011635 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 the 3.4 development start:
class DeprecatedMapping # :nodoc:
def []=(key, value)
warn "Calling `DidYouMean::SPELL_CHECKERS[#{key.to_s}] = #{value.to_s}' has been deprecated. " \
"Please call `DidYouMean.correct_error(#{key.to_s}, #{value.to_s})' instead."
DidYouMean.correct_error(key, value)
end
def merge!(hash)
warn "Calling `DidYouMean::SPELL_CHECKERS.merge!(error_name => spell_checker)' has been deprecated. " \
"Please call `DidYouMean.correct_error(error_name, spell_checker)' instead."
hash.each do |error_class, spell_checker|
DidYouMean.correct_error(error_class, spell_checker)
end
end
end
# TODO: Remove on the 3.4 development start:
SPELL_CHECKERS = DeprecatedMapping.new
deprecate_constant :SPELL_CHECKERS
private_constant :DeprecatedMapping
# Returns the currently set formatter. By default, it is set to +DidYouMean::Formatter+.
def self.formatter
if defined?(Ractor)
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/securerandom.rb 0000644 00000004072 15173517735 0011664 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
# The version
VERSION = "0.3.1"
class << self
# Returns a random binary string containing +size+ bytes.
#
# See Random.bytes
def bytes(n)
return gen_random(n)
end
private
# :stopdoc:
# Implementation using OpenSSL
def gen_random_openssl(n)
return OpenSSL::Random.random_bytes(n)
end
# Implementation using system random device
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
# :startdoc:
# Generate random data bytes for Random::Formatter
public :gen_random
end
end
SecureRandom.extend(Random::Formatter)
share/ruby/kconv.rb 0000644 00000013345 15173517735 0010320 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 15173517735 0011717 0 ustar 00 # frozen_string_literal: false
require_relative 'optparse'
share/ruby/open-uri.rb 0000644 00000063541 15173517735 0010741 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
singleton_class.send(:ruby2_keywords, :open) if respond_to?(:ruby2_keywords, true)
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
VERSION = "0.4.1"
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,
:max_redirects => 64,
}
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 = {}
max_redirects = options[:max_redirects]
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
raise TooManyRedirects.new("Too many redirects", buf.io) if max_redirects && uri_set.size > max_redirects
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 TooManyRedirects < HTTPError
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 15173517735 0014676 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 15173517735 0014203 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 00000015405 15173517735 0010666 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.
#
require 'monitor.so'
#
# 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.
#
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 00000005021 15173517735 0010110 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
VERSION = "0.2.0"
#
# 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 00000061677 15173517735 0010006 0 ustar 00 # frozen_string_literal: true
# :markup: markdown
#
# set.rb - defines the Set class
#
# Copyright (c) 2002-2023 Akinori MUSHA <knu@iDaemons.org>
#
# Documentation by Akinori MUSHA and Gavin Sinclair.
#
# All rights reserved. You can redistribute and/or modify it under the same
# terms as Ruby.
##
# This library provides the Set class, which implements a collection
# of unordered values with no duplicates. It is a hybrid of Array's
# intuitive inter-operation facilities and Hash's fast lookup.
#
# The method `to_set` is added to Enumerable for convenience.
#
# Set is easy to use with Enumerable objects (implementing `each`).
# Most of the initializer methods and binary operators accept generic
# Enumerable objects besides sets and arrays. An Enumerable object
# can be converted to Set using the `to_set` method.
#
# Set uses Hash as storage, so you must note the following points:
#
# * Equality of elements is determined according to Object#eql? and
# Object#hash. Use Set#compare_by_identity to make a set compare
# its elements by their identity.
# * Set assumes that the identity of each element does not change
# while it is stored. Modifying an element of a set will render the
# set to an unreliable state.
# * When a string is to be stored, a frozen copy of the string is
# stored instead unless the original string is already frozen.
#
# ## Comparison
#
# The comparison operators `<`, `>`, `<=`, and `>=` are implemented as
# shorthand for the {proper_,}{subset?,superset?} methods. The `<=>`
# operator reflects this order, or return `nil` for sets that both
# have distinct elements (`{x, y}` vs. `{x, z}` for example).
#
# ## Example
#
# ```ruby
# require 'set'
# s1 = Set[1, 2] #=> #<Set: {1, 2}>
# s2 = [1, 2].to_set #=> #<Set: {1, 2}>
# s1 == s2 #=> true
# s1.add("foo") #=> #<Set: {1, 2, "foo"}>
# s1.merge([2, 6]) #=> #<Set: {1, 2, "foo", 6}>
# s1.subset?(s2) #=> false
# s2.subset?(s1) #=> true
# ```
#
# ## Contact
#
# - Akinori MUSHA <<knu@iDaemons.org>> (current maintainer)
#
# ## What's Here
#
# First, what's elsewhere. \Class \Set:
#
# - Inherits from {class Object}[rdoc-ref:Object@What-27s+Here].
# - Includes {module Enumerable}[rdoc-ref:Enumerable@What-27s+Here],
# which provides dozens of additional methods.
#
# In particular, class \Set does not have many methods of its own
# for fetching or for iterating.
# Instead, it relies on those in \Enumerable.
#
# Here, class \Set provides methods that are useful for:
#
# - [Creating a Set](#class-Set-label-Methods+for+Creating+a+Set)
# - [Set Operations](#class-Set-label-Methods+for+Set+Operations)
# - [Comparing](#class-Set-label-Methods+for+Comparing)
# - [Querying](#class-Set-label-Methods+for+Querying)
# - [Assigning](#class-Set-label-Methods+for+Assigning)
# - [Deleting](#class-Set-label-Methods+for+Deleting)
# - [Converting](#class-Set-label-Methods+for+Converting)
# - [Iterating](#class-Set-label-Methods+for+Iterating)
# - [And more....](#class-Set-label-Other+Methods)
#
# ### Methods for Creating a \Set
#
# - ::[]:
# Returns a new set containing the given objects.
# - ::new:
# Returns a new set containing either the given objects
# (if no block given) or the return values from the called block
# (if a block given).
#
# ### Methods for \Set Operations
#
# - [|](#method-i-7C) (aliased as #union and #+):
# Returns a new set containing all elements from +self+
# and all elements from a given enumerable (no duplicates).
# - [&](#method-i-26) (aliased as #intersection):
# Returns a new set containing all elements common to +self+
# and a given enumerable.
# - [-](#method-i-2D) (aliased as #difference):
# Returns a copy of +self+ with all elements
# in a given enumerable removed.
# - [\^](#method-i-5E):
# Returns a new set containing all elements from +self+
# and a given enumerable except those common to both.
#
# ### Methods for Comparing
#
# - [<=>](#method-i-3C-3D-3E):
# Returns -1, 0, or 1 as +self+ is less than, equal to,
# or greater than a given object.
# - [==](#method-i-3D-3D):
# Returns whether +self+ and a given enumerable are equal,
# as determined by Object#eql?.
# - \#compare_by_identity?:
# Returns whether the set considers only identity
# when comparing elements.
#
# ### Methods for Querying
#
# - \#length (aliased as #size):
# Returns the count of elements.
# - \#empty?:
# Returns whether the set has no elements.
# - \#include? (aliased as #member? and #===):
# Returns whether a given object is an element in the set.
# - \#subset? (aliased as [<=](#method-i-3C-3D)):
# Returns whether a given object is a subset of the set.
# - \#proper_subset? (aliased as [<](#method-i-3C)):
# Returns whether a given enumerable is a proper subset of the set.
# - \#superset? (aliased as [>=](#method-i-3E-3D])):
# Returns whether a given enumerable is a superset of the set.
# - \#proper_superset? (aliased as [>](#method-i-3E)):
# Returns whether a given enumerable is a proper superset of the set.
# - \#disjoint?:
# Returns +true+ if the set and a given enumerable
# have no common elements, +false+ otherwise.
# - \#intersect?:
# Returns +true+ if the set and a given enumerable:
# have any common elements, +false+ otherwise.
# - \#compare_by_identity?:
# Returns whether the set considers only identity
# when comparing elements.
#
# ### Methods for Assigning
#
# - \#add (aliased as #<<):
# Adds a given object to the set; returns +self+.
# - \#add?:
# If the given object is not an element in the set,
# adds it and returns +self+; otherwise, returns +nil+.
# - \#merge:
# Merges the elements of each given enumerable object to the set; returns +self+.
# - \#replace:
# Replaces the contents of the set with the contents
# of a given enumerable.
#
# ### Methods for Deleting
#
# - \#clear:
# Removes all elements in the set; returns +self+.
# - \#delete:
# Removes a given object from the set; returns +self+.
# - \#delete?:
# If the given object is an element in the set,
# removes it and returns +self+; otherwise, returns +nil+.
# - \#subtract:
# Removes each given object from the set; returns +self+.
# - \#delete_if - Removes elements specified by a given block.
# - \#select! (aliased as #filter!):
# Removes elements not specified by a given block.
# - \#keep_if:
# Removes elements not specified by a given block.
# - \#reject!
# Removes elements specified by a given block.
#
# ### Methods for Converting
#
# - \#classify:
# Returns a hash that classifies the elements,
# as determined by the given block.
# - \#collect! (aliased as #map!):
# Replaces each element with a block return-value.
# - \#divide:
# Returns a hash that classifies the elements,
# as determined by the given block;
# differs from #classify in that the block may accept
# either one or two arguments.
# - \#flatten:
# Returns a new set that is a recursive flattening of +self+.
# \#flatten!:
# Replaces each nested set in +self+ with the elements from that set.
# - \#inspect (aliased as #to_s):
# Returns a string displaying the elements.
# - \#join:
# Returns a string containing all elements, converted to strings
# as needed, and joined by the given record separator.
# - \#to_a:
# Returns an array containing all set elements.
# - \#to_set:
# Returns +self+ if given no arguments and no block;
# with a block given, returns a new set consisting of block
# return values.
#
# ### Methods for Iterating
#
# - \#each:
# Calls the block with each successive element; returns +self+.
#
# ### Other Methods
#
# - \#reset:
# Resets the internal state; useful if an object
# has been modified while an element in the set.
#
class Set
VERSION = "1.1.0"
include Enumerable
# Creates a new set containing the given objects.
#
# Set[1, 2] # => #<Set: {1, 2}>
# Set[1, 2, 1] # => #<Set: {1, 2}>
# Set[1, 'c', :s] # => #<Set: {1, "c", :s}>
def self.[](*ary)
new(ary)
end
# Creates a new set containing the elements of the given enumerable
# object.
#
# If a block is given, the elements of enum are preprocessed by the
# given block.
#
# Set.new([1, 2]) #=> #<Set: {1, 2}>
# Set.new([1, 2, 1]) #=> #<Set: {1, 2}>
# Set.new([1, 'c', :s]) #=> #<Set: {1, "c", :s}>
# Set.new(1..5) #=> #<Set: {1, 2, 3, 4, 5}>
# Set.new([1, 2, 3]) { |x| x * x } #=> #<Set: {1, 4, 9}>
def initialize(enum = nil, &block) # :yields: o
@hash ||= Hash.new(false)
enum.nil? and return
if block
do_with_enum(enum) { |o| add(block[o]) }
else
merge(enum)
end
end
# Makes the set compare its elements by their identity and returns
# self. This method may not be supported by all subclasses of Set.
def compare_by_identity
if @hash.respond_to?(:compare_by_identity)
@hash.compare_by_identity
self
else
raise NotImplementedError, "#{self.class.name}\##{__method__} is not implemented"
end
end
# Returns true if the set will compare its elements by their
# identity. Also see Set#compare_by_identity.
def compare_by_identity?
@hash.respond_to?(:compare_by_identity?) && @hash.compare_by_identity?
end
def do_with_enum(enum, &block) # :nodoc:
if enum.respond_to?(:each_entry)
enum.each_entry(&block) if block
elsif enum.respond_to?(:each)
enum.each(&block) if block
else
raise ArgumentError, "value must be enumerable"
end
end
private :do_with_enum
# Dup internal hash.
def initialize_dup(orig)
super
@hash = orig.instance_variable_get(:@hash).dup
end
# Clone internal hash.
def initialize_clone(orig, **options)
super
@hash = orig.instance_variable_get(:@hash).clone(**options)
end
def freeze # :nodoc:
@hash.freeze
super
end
# Returns the number of elements.
def size
@hash.size
end
alias length size
# Returns true if the set contains no elements.
def empty?
@hash.empty?
end
# Removes all elements and returns self.
#
# set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
# set.clear #=> #<Set: {}>
# set #=> #<Set: {}>
def clear
@hash.clear
self
end
# Replaces the contents of the set with the contents of the given
# enumerable object and returns self.
#
# set = Set[1, 'c', :s] #=> #<Set: {1, "c", :s}>
# set.replace([1, 2]) #=> #<Set: {1, 2}>
# set #=> #<Set: {1, 2}>
def replace(enum)
if enum.instance_of?(self.class)
@hash.replace(enum.instance_variable_get(:@hash))
self
else
do_with_enum(enum) # make sure enum is enumerable before calling clear
clear
merge(enum)
end
end
# 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?(Set)
end
# Returns true if the set contains the given object.
#
# Note that <code>include?</code> and <code>member?</code> do not test member
# equality using <code>==</code> as do other Enumerables.
#
# See also Enumerable#include?
def include?(o)
@hash[o]
end
alias member? include?
# Returns true if the set is a superset of the given set.
def superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>=)
@hash >= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size >= set.size && set.all?(self)
else
raise ArgumentError, "value must be a set"
end
end
alias >= superset?
# Returns true if the set is a proper superset of the given set.
def proper_superset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:>)
@hash > set.instance_variable_get(:@hash)
when set.is_a?(Set)
size > set.size && set.all?(self)
else
raise ArgumentError, "value must be a set"
end
end
alias > proper_superset?
# Returns true if the set is a subset of the given set.
def subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<=)
@hash <= set.instance_variable_get(:@hash)
when set.is_a?(Set)
size <= set.size && all?(set)
else
raise ArgumentError, "value must be a set"
end
end
alias <= subset?
# Returns true if the set is a proper subset of the given set.
def proper_subset?(set)
case
when set.instance_of?(self.class) && @hash.respond_to?(:<)
@hash < set.instance_variable_get(:@hash)
when set.is_a?(Set)
size < set.size && all?(set)
else
raise ArgumentError, "value must be a set"
end
end
alias < proper_subset?
# Returns 0 if the set are equal,
# -1 / +1 if the set is a proper subset / superset of the given set,
# or nil if they both have unique elements.
def <=>(set)
return unless set.is_a?(Set)
case size <=> set.size
when -1 then -1 if proper_subset?(set)
when +1 then +1 if proper_superset?(set)
else 0 if self.==(set)
end
end
# Returns true if the set and the given enumerable have at least one
# element in common.
#
# Set[1, 2, 3].intersect? Set[4, 5] #=> false
# Set[1, 2, 3].intersect? Set[3, 4] #=> true
# Set[1, 2, 3].intersect? 4..5 #=> false
# Set[1, 2, 3].intersect? [3, 4] #=> true
def intersect?(set)
case set
when Set
if size < set.size
any?(set)
else
set.any?(self)
end
when Enumerable
set.any?(self)
else
raise ArgumentError, "value must be enumerable"
end
end
# Returns true if the set and the given enumerable have
# no element in common. This method is the opposite of `intersect?`.
#
# Set[1, 2, 3].disjoint? Set[3, 4] #=> false
# Set[1, 2, 3].disjoint? Set[4, 5] #=> true
# Set[1, 2, 3].disjoint? [3, 4] #=> false
# Set[1, 2, 3].disjoint? 4..5 #=> true
def disjoint?(set)
!intersect?(set)
end
# Calls the given block once for each element in the set, passing
# the element as parameter. Returns an enumerator if no block is
# given.
def each(&block)
block_given? or return enum_for(__method__) { size }
@hash.each_key(&block)
self
end
# Adds the given object to the set and returns self. Use `merge` to
# add many elements at once.
#
# Set[1, 2].add(3) #=> #<Set: {1, 2, 3}>
# Set[1, 2].add([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
# Set[1, 2].add(2) #=> #<Set: {1, 2}>
def add(o)
@hash[o] = true
self
end
alias << add
# Adds the given object to the set and returns self. If the
# object is already in the set, returns nil.
#
# Set[1, 2].add?(3) #=> #<Set: {1, 2, 3}>
# Set[1, 2].add?([3, 4]) #=> #<Set: {1, 2, [3, 4]}>
# Set[1, 2].add?(2) #=> nil
def add?(o)
add(o) unless include?(o)
end
# Deletes the given object from the set and returns self. Use
# `subtract` to delete many items at once.
def delete(o)
@hash.delete(o)
self
end
# Deletes the given object from the set and returns self. If the
# object is not in the set, returns nil.
def delete?(o)
delete(o) if include?(o)
end
# Deletes every element of the set for which block evaluates to
# true, and returns self. Returns an enumerator if no block is
# given.
def delete_if
block_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_given? or return enum_for(__method__) { size }
n = size
delete_if(&block)
self if size != n
end
# Equivalent to Set#keep_if, but returns nil if no changes were
# made. Returns an enumerator if no block is given.
def select!(&block)
block_given? or return enum_for(__method__) { size }
n = size
keep_if(&block)
self if size != n
end
# Equivalent to Set#select!
alias filter! select!
# Merges the elements of the given enumerable objects to the set and
# returns self.
def merge(*enums, **nil)
enums.each do |enum|
if enum.instance_of?(self.class)
@hash.update(enum.instance_variable_get(:@hash))
else
do_with_enum(enum) { |o| add(o) }
end
end
self
end
# Deletes every element that appears in the given enumerable object
# and returns self.
def subtract(enum)
do_with_enum(enum) { |o| delete(o) }
self
end
# Returns a new set built by merging the set and the elements of the
# given enumerable object.
#
# Set[1, 2, 3] | Set[2, 4, 5] #=> #<Set: {1, 2, 3, 4, 5}>
# Set[1, 5, 'z'] | (1..6) #=> #<Set: {1, 5, "z", 2, 3, 4, 6}>
def |(enum)
dup.merge(enum)
end
alias + |
alias union |
# Returns a new set built by duplicating the set, removing every
# element that appears in the given enumerable object.
#
# Set[1, 3, 5] - Set[1, 5] #=> #<Set: {3}>
# Set['a', 'b', 'z'] - ['a', 'c'] #=> #<Set: {"b", "z"}>
def -(enum)
dup.subtract(enum)
end
alias difference -
# Returns a new set containing elements common to the set and the
# given enumerable object.
#
# Set[1, 3, 5] & Set[3, 2, 1] #=> #<Set: {3, 1}>
# Set['a', 'b', 'z'] & ['a', 'b', 'c'] #=> #<Set: {"a", "b"}>
def &(enum)
n = self.class.new
if enum.is_a?(Set)
if enum.size > size
each { |o| n.add(o) if enum.include?(o) }
else
enum.each { |o| n.add(o) if include?(o) }
end
else
do_with_enum(enum) { |o| n.add(o) if include?(o) }
end
n
end
alias intersection &
# Returns a new set containing elements exclusive between the set
# and the given enumerable object. `(set ^ enum)` is equivalent to
# `((set | enum) - (set & enum))`.
#
# Set[1, 2] ^ Set[2, 3] #=> #<Set: {3, 1}>
# Set[1, 'b', 'c'] ^ ['b', 'd'] #=> #<Set: {"d", 1, "c"}>
def ^(enum)
n = Set.new(enum)
each { |o| n.add(o) unless n.delete?(o) }
n
end
# Returns true if two sets are equal. The equality of each couple
# of elements is defined according to Object#eql?.
#
# Set[1, 2] == Set[2, 1] #=> true
# Set[1, 3, 5] == Set[1, 5] #=> false
# Set['a', 'b', 'c'] == Set['a', 'c', 'b'] #=> true
# Set['a', 'b', 'c'] == ['a', 'c', 'b'] #=> false
def ==(other)
if self.equal?(other)
true
elsif other.instance_of?(self.class)
@hash == other.instance_variable_get(:@hash)
elsif other.is_a?(Set) && self.size == other.size
other.all? { |o| @hash.include?(o) }
else
false
end
end
def hash # :nodoc:
@hash.hash
end
def eql?(o) # :nodoc:
return false unless o.is_a?(Set)
@hash.eql?(o.instance_variable_get(:@hash))
end
# Resets the internal state after modification to existing elements
# and returns self.
#
# Elements will be reindexed and deduplicated.
def reset
if @hash.respond_to?(:rehash)
@hash.rehash # This should perform frozenness check.
else
raise FrozenError, "can't modify frozen #{self.class.name}" if frozen?
end
self
end
# Returns true if the given object is a member of the set,
# and false otherwise.
#
# Used in case statements:
#
# require 'set'
#
# case :apple
# when Set[:potato, :carrot]
# "vegetable"
# when Set[:apple, :banana]
# "fruit"
# end
# # => "fruit"
#
# Or by itself:
#
# Set[1, 2, 3] === 2 #=> true
# Set[1, 2, 3] === 4 #=> false
#
alias === include?
# Classifies the set by the return value of the given block and
# returns a hash of {value => set of elements} pairs. The block is
# called once for each element of the set, passing the element as
# parameter.
#
# require 'set'
# files = Set.new(Dir.glob("*.rb"))
# hash = files.classify { |f| File.mtime(f).year }
# hash #=> {2000=>#<Set: {"a.rb", "b.rb"}>,
# # 2001=>#<Set: {"c.rb", "d.rb", "e.rb"}>,
# # 2002=>#<Set: {"f.rb"}>}
#
# Returns an enumerator if no block is given.
def classify # :yields: o
block_given? or return enum_for(__method__) { size }
h = {}
each { |i|
(h[yield(i)] ||= self.class.new).add(i)
}
h
end
# Divides the set into a set of subsets according to the commonality
# defined by the given block.
#
# If the arity of the block is 2, elements o1 and o2 are in common
# if block.call(o1, o2) is true. Otherwise, elements o1 and o2 are
# in common if block.call(o1) == block.call(o2).
#
# require 'set'
# numbers = Set[1, 3, 4, 6, 9, 10, 11]
# set = numbers.divide { |i,j| (i - j).abs == 1 }
# set #=> #<Set: {#<Set: {1}>,
# # #<Set: {11, 9, 10}>,
# # #<Set: {3, 4}>,
# # #<Set: {6}>}>
#
# Returns an enumerator if no block is given.
def divide(&func)
func or return enum_for(__method__) { size }
if func.arity == 2
require 'tsort'
class << dig = {} # :nodoc:
include TSort
alias tsort_each_node each_key
def tsort_each_child(node, &block)
fetch(node).each(&block)
end
end
each { |u|
dig[u] = a = []
each{ |v| func.call(u, v) and a << v }
}
set = Set.new()
dig.each_strongly_connected_component { |css|
set.add(self.class.new(css))
}
set
else
Set.new(classify(&func).values)
end
end
# Returns a string created by converting each element of the set to a string
# See also: Array#join
def join(separator=nil)
to_a.join(separator)
end
InspectKey = :__inspect_key__ # :nodoc:
# Returns a string containing a human-readable representation of the
# set ("#<Set: {element1, element2, ...}>").
def inspect
ids = (Thread.current[InspectKey] ||= [])
if ids.include?(object_id)
return sprintf('#<%s: {...}>', self.class.name)
end
ids << object_id
begin
return sprintf('#<%s: {%s}>', self.class, to_a.inspect[1..-2])
ensure
ids.pop
end
end
alias to_s inspect
def pretty_print(pp) # :nodoc:
pp.group(1, sprintf('#<%s:', self.class.name), '>') {
pp.breakable
pp.group(1, '{', '}') {
pp.seplist(self) { |o|
pp.pp o
}
}
}
end
def pretty_print_cycle(pp) # :nodoc:
pp.text sprintf('#<%s: {%s}>', self.class.name, empty? ? '' : '...')
end
end
module Enumerable
# Makes a set from the enumerable object with given arguments.
# Needs to `require "set"` to use this method.
def to_set(klass = Set, *args, &block)
klass.new(self, *args, &block)
end unless method_defined?(:to_set)
end
autoload :SortedSet, "#{__dir__}/set/sorted_set"
share/ruby/ipaddr.rb 0000644 00000051667 15173517735 0010454 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.6"
# 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
begin_addr <= other.begin_addr && end_addr >= other.end_addr
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. Private IPv4 addresses in the
# IPv4-mapped IPv6 address range are also 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 ||
(@addr & 0xffff_0000_0000 == 0xffff_0000_0000 && (
@addr & 0xff000000 == 0x0a000000 || # ::ffff:10.0.0.0/8
@addr & 0xfff00000 == 0xac100000 || # ::ffff::172.16.0.0/12
@addr & 0xffff0000 == 0xc0a80000 # ::ffff::192.168.0.0/16
))
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
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
def begin_addr
@addr & @mask_addr
end
def end_addr
case @family
when Socket::AF_INET
@addr | (IN4MASK ^ @mask_addr)
when Socket::AF_INET6
@addr | (IN6MASK ^ @mask_addr)
else
raise AddressFamilyError, "unsupported address family"
end
end
# 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/openssl.rb 0000644 00000002034 15173517735 0010654 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 15173517735 0012551 0 ustar 00 # frozen_string_literal: true
require 'digest/sha2.so'
share/ruby/digest/loader.rb 0000644 00000000063 15173517735 0011716 0 ustar 00 # frozen_string_literal: true
require 'digest.so'
share/ruby/digest/version.rb 0000644 00000000105 15173517735 0012132 0 ustar 00 # frozen_string_literal: true
module Digest
VERSION = "3.1.1"
end
share/ruby/digest/sha2.rb 0000644 00000007425 15173517735 0011316 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 00000050565 15173517735 0010521 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.3"
RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
# The error type thrown by all PStore methods.
class Error < StandardError
end
# Whether \PStore should do its best to prevent file corruptions,
# even when an unlikely error (such as memory-error or filesystem error) occurs:
#
# - +true+: changes are posted by creating a temporary file,
# writing the updated data to it, then renaming the file to the given #path.
# File integrity is maintained.
# Note: has effect only if the filesystem has atomic file rename
# (as do POSIX platforms Linux, MacOS, FreeBSD and others).
#
# - +false+ (the default): changes are posted by rewinding the open file
# and writing the updated data.
# File integrity is maintained if the filesystem raises
# no unexpected I/O error;
# if such an error occurs during a write to the store,
# the file may become corrupted.
#
attr_accessor :ultra_safe
# Returns a new \PStore object.
#
# Argument +file+ is the path to the file in which objects are to be stored;
# if the file exists, it should be one that was written by \PStore.
#
# path = 't.store'
# store = PStore.new(path)
#
# A \PStore object is
# {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
# If argument +thread_safe+ is given as +true+,
# the object is also thread-safe (at the cost of a small performance penalty):
#
# store = PStore.new(path, true)
#
def initialize(file, thread_safe = false)
dir = File::dirname(file)
unless File::directory? dir
raise PStore::Error, format("directory %s does not exist", dir)
end
if File::exist? file and not File::readable? file
raise PStore::Error, format("file %s not readable", file)
end
@filename = file
@abort = false
@ultra_safe = false
@thread_safe = thread_safe
@lock = Thread::Mutex.new
end
# Raises PStore::Error if the calling code is not in a PStore#transaction.
def in_transaction
raise PStore::Error, "not in transaction" unless @lock.locked?
end
#
# Raises PStore::Error if the calling code is not in a PStore#transaction or
# if the code is in a read-only PStore#transaction.
#
def in_transaction_wr
in_transaction
raise PStore::Error, "in read-only transaction" if @rdonly
end
private :in_transaction, :in_transaction_wr
# Returns the value for the given +key+ if the key exists.
# +nil+ otherwise;
# if not +nil+, the returned value is an object or a hierarchy of objects:
#
# example_store do |store|
# store.transaction do
# store[:foo] # => 0
# store[:nope] # => nil
# end
# end
#
# Returns +nil+ if there is no such key.
#
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
#
# Raises an exception if called outside a transaction block.
def [](key)
in_transaction
@table[key]
end
# Like #[], except that it accepts a default value for the store.
# If the +key+ does not exist:
#
# - Raises an exception if +default+ is +PStore::Error+.
# - Returns the value of +default+ otherwise:
#
# example_store do |store|
# store.transaction do
# store.fetch(:nope, nil) # => nil
# store.fetch(:nope) # Raises an exception.
# end
# end
#
# Raises an exception if called outside a transaction block.
def fetch(key, default=PStore::Error)
in_transaction
unless @table.key? key
if default == PStore::Error
raise PStore::Error, format("undefined key `%s'", key)
else
return default
end
end
@table[key]
end
# Creates or replaces the value for the given +key+:
#
# example_store do |store|
# temp.transaction do
# temp[:bat] = 3
# end
# end
#
# See also {Hierarchical Values}[rdoc-ref:PStore@Hierarchical+Values].
#
# Raises an exception if called outside a transaction block.
def []=(key, value)
in_transaction_wr
@table[key] = value
end
# Removes and returns the value at +key+ if it exists:
#
# example_store do |store|
# store.transaction do
# store[:bat] = 3
# store.delete(:bat)
# end
# end
#
# Returns +nil+ if there is no such key.
#
# Raises an exception if called outside a transaction block.
def delete(key)
in_transaction_wr
@table.delete key
end
# Returns an array of the existing keys:
#
# example_store do |store|
# store.transaction do
# store.keys # => [:foo, :bar, :baz]
# end
# end
#
# Raises an exception if called outside a transaction block.
def keys
in_transaction
@table.keys
end
alias roots keys
# Returns +true+ if +key+ exists, +false+ otherwise:
#
# example_store do |store|
# store.transaction do
# store.key?(:foo) # => true
# end
# end
#
# Raises an exception if called outside a transaction block.
def key?(key)
in_transaction
@table.key? key
end
alias root? key?
# Returns the string file path used to create the store:
#
# store.path # => "flat.store"
#
def path
@filename
end
# Exits the current transaction block, committing any changes
# specified in the
# {transaction block}[rdoc-ref:PStore@The+Transaction+Block].
#
# Raises an exception if called outside a transaction block.
def commit
in_transaction
@abort = false
throw :pstore_abort_transaction
end
# Exits the current transaction block, discarding any changes
# specified in the
# {transaction block}[rdoc-ref:PStore@The+Transaction+Block].
#
# Raises an exception if called outside a transaction block.
def abort
in_transaction
@abort = true
throw :pstore_abort_transaction
end
# Opens a transaction block for the store.
# See {Transactions}[rdoc-ref:PStore@Transactions].
#
# With argument +read_only+ as +false+, the block may both read from
# and write to the store.
#
# With argument +read_only+ as +true+, the block may not include calls
# to #transaction, #[]=, or #delete.
#
# Raises an exception if called within a transaction block.
def transaction(read_only = false) # :yields: pstore
value = nil
if !@thread_safe
raise PStore::Error, "nested transaction" unless @lock.try_lock
else
begin
@lock.lock
rescue ThreadError
raise PStore::Error, "nested transaction"
end
end
begin
@rdonly = read_only
@abort = false
file = open_and_lock_file(@filename, read_only)
if file
begin
@table, checksum, original_data_size = load_data(file, read_only)
catch(:pstore_abort_transaction) do
value = yield(self)
end
if !@abort && !read_only
save_data(checksum, original_data_size, file)
end
ensure
file.close
end
else
# This can only occur if read_only == true.
@table = {}
catch(:pstore_abort_transaction) do
value = yield(self)
end
end
ensure
@lock.unlock
end
value
end
private
# Constant for relieving Ruby's garbage collector.
CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo|
begin
break Digest(algo)
rescue LoadError
end
end
EMPTY_STRING = ""
EMPTY_MARSHAL_DATA = Marshal.dump({})
EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA)
#
# Open the specified filename (either in read-only mode or in
# read-write mode) and lock it for reading or writing.
#
# The opened File object will be returned. If _read_only_ is true,
# and the file does not exist, then nil will be returned.
#
# All exceptions are propagated.
#
def open_and_lock_file(filename, read_only)
if read_only
begin
file = File.new(filename, **RD_ACCESS)
begin
file.flock(File::LOCK_SH)
return file
rescue
file.close
raise
end
rescue Errno::ENOENT
return nil
end
else
file = File.new(filename, **RDWR_ACCESS)
file.flock(File::LOCK_EX)
return file
end
end
# Load the given PStore file.
# If +read_only+ is true, the unmarshalled Hash will be returned.
# If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
# Hash, a checksum of the data, and the size of the data.
def load_data(file, read_only)
if read_only
begin
table = load(file)
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
rescue EOFError
# This seems to be a newly-created file.
table = {}
end
table
else
data = file.read
if data.empty?
# This seems to be a newly-created file.
table = {}
checksum = empty_marshal_checksum
size = empty_marshal_data.bytesize
else
table = load(data)
checksum = CHECKSUM_ALGO.digest(data)
size = data.bytesize
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
end
data.replace(EMPTY_STRING)
[table, checksum, size]
end
end
def on_windows?
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
self.class.__send__(:define_method, :on_windows?) do
is_windows
end
is_windows
end
def save_data(original_checksum, original_file_size, file)
new_data = dump(@table)
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
if @ultra_safe && !on_windows?
# Windows doesn't support atomic file renames.
save_data_with_atomic_file_rename_strategy(new_data, file)
else
save_data_with_fast_strategy(new_data, file)
end
end
new_data.replace(EMPTY_STRING)
end
def save_data_with_atomic_file_rename_strategy(data, file)
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
temp_file = File.new(temp_filename, **WR_ACCESS)
begin
temp_file.flock(File::LOCK_EX)
temp_file.write(data)
temp_file.flush
File.rename(temp_filename, @filename)
rescue
File.unlink(temp_file) rescue nil
raise
ensure
temp_file.close
end
end
def save_data_with_fast_strategy(data, file)
file.rewind
file.write(data)
file.truncate(data.bytesize)
end
# This method is just a wrapped around Marshal.dump
# to allow subclass overriding used in YAML::Store.
def dump(table) # :nodoc:
Marshal::dump(table)
end
# This method is just a wrapped around Marshal.load.
# to allow subclass overriding used in YAML::Store.
def load(content) # :nodoc:
Marshal::load(content)
end
def empty_marshal_data
EMPTY_MARSHAL_DATA
end
def empty_marshal_checksum
EMPTY_MARSHAL_CHECKSUM
end
end
share/ruby/un.rb 0000644 00000026254 15173517735 0007625 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_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:
VERSION = "0.3.0"
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 15173517735 0012136 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 00000000327 15173517735 0010757 0 ustar 00 begin
require "readline.#{RbConfig::CONFIG["DLEXT"]}"
rescue LoadError
require 'reline' unless defined? Reline
Object.send(:remove_const, :Readline) if Object.const_defined?(:Readline)
Readline = Reline
end
share/gems/gems/json-2.7.2/lib/json 0000755 00000000000 15173517735 0012622 0 ustar 00 share/ruby/drb/timeridconv.rb 0000644 00000004245 15173517735 0012271 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 15173517735 0010345 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 00000027620 15173517735 0010551 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
#
# :SSLMinVersion ::
# This is the minimum SSL version to allow. See
# OpenSSL::SSL::SSLContext#min_version=.
#
# :SSLMaxVersion ::
# This is the maximum SSL version to allow. See
# OpenSSL::SSL::SSLContext#max_version=.
#
# :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.min_version = self[:SSLMinVersion]
ctx.max_version = self[:SSLMaxVersion]
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 15173517735 0011573 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 15173517735 0010357 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 15173517735 0011436 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 15173517735 0011630 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 15173517735 0010731 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 15173517735 0012100 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 15173517736 0011424 0 ustar 00 module DRb
VERSION = "2.2.0"
end
share/ruby/drb/invokemethod.rb 0000644 00000001411 15173517736 0012433 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 00000163200 15173517736 0010513 0 ustar 00 # frozen_string_literal: false
#
# = drb/drb.rb
#
# Distributed Ruby: _dRuby_
#
# 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'
require_relative 'version'
#
# == 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 15173517736 0010501 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 00000004556 15173517736 0010663 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.2.0"
Ractor.make_shareable(VERSION) if defined?(Ractor)
def Mutex_m.define_aliases(cl) # :nodoc:
cl.alias_method(:locked?, :mu_locked?)
cl.alias_method(:lock, :mu_lock)
cl.alias_method(:unlock, :mu_unlock)
cl.alias_method(:try_lock, :mu_try_lock)
cl.alias_method(: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 15173517736 0010465 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 15173517736 0010424 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 00000137013 15173517736 0010224 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 supports creating child processes
# with access to their $stdin, $stdout, and $stderr streams.
#
# == What's Here
#
# Each of these methods executes a given command in a new process or subshell,
# or multiple commands in new processes and/or subshells:
#
# - Each of these methods executes a single command in a process or subshell,
# accepts a string for input to $stdin,
# and returns string output from $stdout, $stderr, or both:
#
# - Open3.capture2: Executes the command;
# returns the string from $stdout.
# - Open3.capture2e: Executes the command;
# returns the string from merged $stdout and $stderr.
# - Open3.capture3: Executes the command;
# returns strings from $stdout and $stderr.
#
# - Each of these methods executes a single command in a process or subshell,
# and returns pipes for $stdin, $stdout, and/or $stderr:
#
# - Open3.popen2: Executes the command;
# returns pipes for $stdin and $stdout.
# - Open3.popen2e: Executes the command;
# returns pipes for $stdin and merged $stdout and $stderr.
# - Open3.popen3: Executes the command;
# returns pipes for $stdin, $stdout, and $stderr.
#
# - Each of these methods executes one or more commands in processes and/or subshells,
# returns pipes for the first $stdin, the last $stdout, or both:
#
# - Open3.pipeline_r: Returns a pipe for the last $stdout.
# - Open3.pipeline_rw: Returns pipes for the first $stdin and the last $stdout.
# - Open3.pipeline_w: Returns a pipe for the first $stdin.
# - Open3.pipeline_start: Does not wait for processes to complete.
# - Open3.pipeline: Waits for processes to complete.
#
# Each of the methods above accepts:
#
# - An optional hash of environment variable names and values;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
# - A required string argument that is a +command_line+ or +exe_path+;
# see {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
# - An optional hash of execution options;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
module Open3
# :call-seq:
# Open3.popen3([env, ] command_line, options = {}) -> [stdin, stdout, stderr, wait_thread]
# Open3.popen3([env, ] exe_path, *args, options = {}) -> [stdin, stdout, stderr, wait_thread]
# Open3.popen3([env, ] command_line, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
# Open3.popen3([env, ] exe_path, *args, options = {}) {|stdin, stdout, stderr, wait_thread| ... } -> object
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process, by calling Process.spawn with the given arguments.
# - Creates streams +stdin+, +stdout+, and +stderr+,
# which are the standard input, standard output, and standard error streams
# in the child process.
# - Creates thread +wait_thread+ that waits for the child process to exit;
# the thread has method +pid+, which returns the process ID
# of the child process.
#
# With no block given, returns the array
# <tt>[stdin, stdout, stderr, wait_thread]</tt>.
# The caller should close each of the three returned streams.
#
# stdin, stdout, stderr, wait_thread = Open3.popen3('echo')
# # => [#<IO:fd 8>, #<IO:fd 10>, #<IO:fd 12>, #<Process::Waiter:0x00007f58d5428f58 run>]
# stdin.close
# stdout.close
# stderr.close
# wait_thread.pid # => 2210481
# wait_thread.value # => #<Process::Status: pid 2210481 exit 0>
#
# With a block given, calls the block with the four variables
# (three streams and the wait thread)
# and returns the block's return value.
# The caller need not close the streams:
#
# Open3.popen3('echo') do |stdin, stdout, stderr, wait_thread|
# p stdin
# p stdout
# p stderr
# p wait_thread
# p wait_thread.pid
# p wait_thread.value
# end
#
# Output:
#
# #<IO:fd 6>
# #<IO:fd 7>
# #<IO:fd 9>
# #<Process::Waiter:0x00007f58d53606e8 sleep>
# 2211047
# #<Process::Status: pid 2211047 exit 0>
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.popen3('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
# Open3.popen3('echo') {|*args| p args } # Built-in.
# Open3.popen3('date > date.tmp') {|*args| p args } # Contains meta character.
#
# Output (similar for each call above):
#
# [#<IO:(closed)>, #<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f58d52f28c8 dead>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.popen3('echo "Foo"') { |i, o, e, t| o.gets }
# "Foo\n"
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.popen3('/usr/bin/date') { |i, o, e, t| o.gets }
# # => "Wed Sep 27 02:56:44 PM CDT 2023\n"
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.popen3('doesnt_exist') { |i, o, e, t| o.gets } # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.popen3('echo', 'C #') { |i, o, e, t| o.gets }
# # => "C #\n"
# Open3.popen3('echo', 'hello', 'world') { |i, o, e, t| o.gets }
# # => "hello world\n"
#
# Take care to avoid deadlocks.
# Output streams +stdout+ and +stderr+ have fixed-size buffers,
# so reading extensively from one but not the other can cause a deadlock
# when the unread buffer fills.
# To avoid that, +stdout+ and +stderr+ should be read simultaneously
# (using threads or IO.select).
#
# Related:
#
# - Open3.popen2: Makes the standard input and standard output streams
# of the child process available as separate streams,
# with no access to the standard error stream.
# - Open3.popen2e: Makes the standard input and the merge
# of the standard output and standard error streams
# of the child process available as separate streams.
#
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
# :call-seq:
# Open3.popen2([env, ] command_line, options = {}) -> [stdin, stdout, wait_thread]
# Open3.popen2([env, ] exe_path, *args, options = {}) -> [stdin, stdout, wait_thread]
# Open3.popen2([env, ] command_line, options = {}) {|stdin, stdout, wait_thread| ... } -> object
# Open3.popen2([env, ] exe_path, *args, options = {}) {|stdin, stdout, wait_thread| ... } -> object
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process, by calling Process.spawn with the given arguments.
# - Creates streams +stdin+ and +stdout+,
# which are the standard input and standard output streams
# in the child process.
# - Creates thread +wait_thread+ that waits for the child process to exit;
# the thread has method +pid+, which returns the process ID
# of the child process.
#
# With no block given, returns the array
# <tt>[stdin, stdout, wait_thread]</tt>.
# The caller should close each of the two returned streams.
#
# stdin, stdout, wait_thread = Open3.popen2('echo')
# # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f58d52dbe98 run>]
# stdin.close
# stdout.close
# wait_thread.pid # => 2263572
# wait_thread.value # => #<Process::Status: pid 2263572 exit 0>
#
# With a block given, calls the block with the three variables
# (two streams and the wait thread)
# and returns the block's return value.
# The caller need not close the streams:
#
# Open3.popen2('echo') do |stdin, stdout, wait_thread|
# p stdin
# p stdout
# p wait_thread
# p wait_thread.pid
# p wait_thread.value
# end
#
# Output:
#
# #<IO:fd 6>
# #<IO:fd 7>
# #<Process::Waiter:0x00007f58d59a34b0 sleep>
# 2263636
# #<Process::Status: pid 2263636 exit 0>
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.popen2('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
# Open3.popen2('echo') {|*args| p args } # Built-in.
# Open3.popen2('date > date.tmp') {|*args| p args } # Contains meta character.
#
# Output (similar for each call above):
#
# # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577dfe410 dead>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.popen2('echo "Foo"') { |i, o, t| o.gets }
# "Foo\n"
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.popen2('/usr/bin/date') { |i, o, t| o.gets }
# # => "Thu Sep 28 09:41:06 AM CDT 2023\n"
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.popen2('doesnt_exist') { |i, o, t| o.gets } # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.popen2('echo', 'C #') { |i, o, t| o.gets }
# # => "C #\n"
# Open3.popen2('echo', 'hello', 'world') { |i, o, t| o.gets }
# # => "hello world\n"
#
#
# Related:
#
# - Open3.popen2e: Makes the standard input and the merge
# of the standard output and standard error streams
# of the child process available as separate streams.
# - Open3.popen3: Makes the standard input, standard output,
# and standard error streams
# of the child process available as separate streams.
#
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
# :call-seq:
# Open3.popen2e([env, ] command_line, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
# Open3.popen2e([env, ] exe_path, *args, options = {}) -> [stdin, stdout_and_stderr, wait_thread]
# Open3.popen2e([env, ] command_line, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
# Open3.popen2e([env, ] exe_path, *args, options = {}) {|stdin, stdout_and_stderr, wait_thread| ... } -> object
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process, by calling Process.spawn with the given arguments.
# - Creates streams +stdin+, +stdout_and_stderr+,
# which are the standard input and the merge of the standard output
# and standard error streams in the child process.
# - Creates thread +wait_thread+ that waits for the child process to exit;
# the thread has method +pid+, which returns the process ID
# of the child process.
#
# With no block given, returns the array
# <tt>[stdin, stdout_and_stderr, wait_thread]</tt>.
# The caller should close each of the two returned streams.
#
# stdin, stdout_and_stderr, wait_thread = Open3.popen2e('echo')
# # => [#<IO:fd 6>, #<IO:fd 7>, #<Process::Waiter:0x00007f7577da4398 run>]
# stdin.close
# stdout_and_stderr.close
# wait_thread.pid # => 2274600
# wait_thread.value # => #<Process::Status: pid 2274600 exit 0>
#
# With a block given, calls the block with the three variables
# (two streams and the wait thread)
# and returns the block's return value.
# The caller need not close the streams:
#
# Open3.popen2e('echo') do |stdin, stdout_and_stderr, wait_thread|
# p stdin
# p stdout_and_stderr
# p wait_thread
# p wait_thread.pid
# p wait_thread.value
# end
#
# Output:
#
# #<IO:fd 6>
# #<IO:fd 7>
# #<Process::Waiter:0x00007f75777578c8 sleep>
# 2274763
# #<Process::Status: pid 2274763 exit 0>
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.popen2e('if true; then echo "Foo"; fi') {|*args| p args } # Shell reserved word.
# Open3.popen2e('echo') {|*args| p args } # Built-in.
# Open3.popen2e('date > date.tmp') {|*args| p args } # Contains meta character.
#
# Output (similar for each call above):
#
# # => [#<IO:(closed)>, #<IO:(closed)>, #<Process::Waiter:0x00007f7577d8a1f0 dead>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.popen2e('echo "Foo"') { |i, o_and_e, t| o_and_e.gets }
# "Foo\n"
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.popen2e('/usr/bin/date') { |i, o_and_e, t| o_and_e.gets }
# # => "Thu Sep 28 01:58:45 PM CDT 2023\n"
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.popen2e('doesnt_exist') { |i, o_and_e, t| o_and_e.gets } # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.popen2e('echo', 'C #') { |i, o_and_e, t| o_and_e.gets }
# # => "C #\n"
# Open3.popen2e('echo', 'hello', 'world') { |i, o_and_e, t| o_and_e.gets }
# # => "hello world\n"
#
# Related:
#
# - Open3.popen2: Makes the standard input and standard output streams
# of the child process available as separate streams,
# with no access to the standard error stream.
# - Open3.popen3: Makes the standard input, standard output,
# and standard error streams
# of the child process available as separate streams.
#
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
# :call-seq:
# Open3.capture3([env, ] command_line, options = {}) -> [stdout_s, stderr_s, status]
# Open3.capture3([env, ] exe_path, *args, options = {}) -> [stdout_s, stderr_s, status]
#
# Basically a wrapper for Open3.popen3 that:
#
# - Creates a child process, by calling Open3.popen3 with the given arguments
# (except for certain entries in hash +options+; see below).
# - Returns as strings +stdout_s+ and +stderr_s+ the standard output
# and standard error of the child process.
# - Returns as +status+ a <tt>Process::Status</tt> object
# that represents the exit status of the child process.
#
# Returns the array <tt>[stdout_s, stderr_s, status]</tt>:
#
# stdout_s, stderr_s, status = Open3.capture3('echo "Foo"')
# # => ["Foo\n", "", #<Process::Status: pid 2281954 exit 0>]
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Open3.popen3;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Open3.popen3;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The hash +options+ is given;
# two options have local effect in method Open3.capture3:
#
# - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
# and its string value is sent to the command's standard input:
#
# Open3.capture3('tee', stdin_data: 'Foo')
# # => ["Foo", "", #<Process::Status: pid 2319575 exit 0>]
#
# - If entry <tt>options[:binmode]</tt> exists, the entry is removed and
# the internal streams are set to binary mode.
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.capture3('if true; then echo "Foo"; fi') # Shell reserved word.
# # => ["Foo\n", "", #<Process::Status: pid 2282025 exit 0>]
# Open3.capture3('echo') # Built-in.
# # => ["\n", "", #<Process::Status: pid 2282092 exit 0>]
# Open3.capture3('date > date.tmp') # Contains meta character.
# # => ["", "", #<Process::Status: pid 2282110 exit 0>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.capture3('echo "Foo"')
# # => ["Foo\n", "", #<Process::Status: pid 2282092 exit 0>]
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.capture3('/usr/bin/date')
# # => ["Thu Sep 28 05:03:51 PM CDT 2023\n", "", #<Process::Status: pid 2282300 exit 0>]
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.capture3('doesnt_exist') # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.capture3('echo', 'C #')
# # => ["C #\n", "", #<Process::Status: pid 2282368 exit 0>]
# Open3.capture3('echo', 'hello', 'world')
# # => ["hello world\n", "", #<Process::Status: pid 2282372 exit 0>]
#
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
# :call-seq:
# Open3.capture2([env, ] command_line, options = {}) -> [stdout_s, status]
# Open3.capture2([env, ] exe_path, *args, options = {}) -> [stdout_s, status]
#
# Basically a wrapper for Open3.popen3 that:
#
# - Creates a child process, by calling Open3.popen3 with the given arguments
# (except for certain entries in hash +options+; see below).
# - Returns as string +stdout_s+ the standard output of the child process.
# - Returns as +status+ a <tt>Process::Status</tt> object
# that represents the exit status of the child process.
#
# Returns the array <tt>[stdout_s, status]</tt>:
#
# stdout_s, status = Open3.capture2('echo "Foo"')
# # => ["Foo\n", #<Process::Status: pid 2326047 exit 0>]
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Open3.popen3;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Open3.popen3;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The hash +options+ is given;
# two options have local effect in method Open3.capture2:
#
# - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
# and its string value is sent to the command's standard input:
#
# Open3.capture2('tee', stdin_data: 'Foo')
#
# # => ["Foo", #<Process::Status: pid 2326087 exit 0>]
#
# - If entry <tt>options[:binmode]</tt> exists, the entry is removed and
# the internal streams are set to binary mode.
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.capture2('if true; then echo "Foo"; fi') # Shell reserved word.
# # => ["Foo\n", #<Process::Status: pid 2326131 exit 0>]
# Open3.capture2('echo') # Built-in.
# # => ["\n", #<Process::Status: pid 2326139 exit 0>]
# Open3.capture2('date > date.tmp') # Contains meta character.
# # => ["", #<Process::Status: pid 2326174 exit 0>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.capture2('echo "Foo"')
# # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.capture2('/usr/bin/date')
# # => ["Fri Sep 29 01:00:39 PM CDT 2023\n", #<Process::Status: pid 2326222 exit 0>]
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.capture2('doesnt_exist') # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.capture2('echo', 'C #')
# # => ["C #\n", #<Process::Status: pid 2326267 exit 0>]
# Open3.capture2('echo', 'hello', 'world')
# # => ["hello world\n", #<Process::Status: pid 2326299 exit 0>]
#
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
# :call-seq:
# Open3.capture2e([env, ] command_line, options = {}) -> [stdout_and_stderr_s, status]
# Open3.capture2e([env, ] exe_path, *args, options = {}) -> [stdout_and_stderr_s, status]
#
# Basically a wrapper for Open3.popen3 that:
#
# - Creates a child process, by calling Open3.popen3 with the given arguments
# (except for certain entries in hash +options+; see below).
# - Returns as string +stdout_and_stderr_s+ the merged standard output
# and standard error of the child process.
# - Returns as +status+ a <tt>Process::Status</tt> object
# that represents the exit status of the child process.
#
# Returns the array <tt>[stdout_and_stderr_s, status]</tt>:
#
# stdout_and_stderr_s, status = Open3.capture2e('echo "Foo"')
# # => ["Foo\n", #<Process::Status: pid 2371692 exit 0>]
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# Unlike Process.spawn, this method waits for the child process to exit
# before returning, so the caller need not do so.
#
# If the first argument is a hash, it becomes leading argument +env+
# in the call to Open3.popen3;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in the call to Open3.popen3;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# The hash +options+ is given;
# two options have local effect in method Open3.capture2e:
#
# - If entry <tt>options[:stdin_data]</tt> exists, the entry is removed
# and its string value is sent to the command's standard input:
#
# Open3.capture2e('tee', stdin_data: 'Foo')
# # => ["Foo", #<Process::Status: pid 2371732 exit 0>]
#
# - If entry <tt>options[:binmode]</tt> exists, the entry is removed and
# the internal streams are set to binary mode.
#
# The single required argument is one of the following:
#
# - +command_line+ if it is a string,
# and if it begins with a shell reserved word or special built-in,
# or if it contains one or more metacharacters.
# - +exe_path+ otherwise.
#
# <b>Argument +command_line+</b>
#
# \String argument +command_line+ is a command line to be passed to a shell;
# it must begin with a shell reserved word, begin with a special built-in,
# or contain meta characters:
#
# Open3.capture2e('if true; then echo "Foo"; fi') # Shell reserved word.
# # => ["Foo\n", #<Process::Status: pid 2371740 exit 0>]
# Open3.capture2e('echo') # Built-in.
# # => ["\n", #<Process::Status: pid 2371774 exit 0>]
# Open3.capture2e('date > date.tmp') # Contains meta character.
# # => ["", #<Process::Status: pid 2371812 exit 0>]
#
# The command line may also contain arguments and options for the command:
#
# Open3.capture2e('echo "Foo"')
# # => ["Foo\n", #<Process::Status: pid 2326183 exit 0>]
#
# <b>Argument +exe_path+</b>
#
# Argument +exe_path+ is one of the following:
#
# - The string path to an executable to be called.
# - A 2-element array containing the path to an executable
# and the string to be used as the name of the executing process.
#
# Example:
#
# Open3.capture2e('/usr/bin/date')
# # => ["Sat Sep 30 09:01:46 AM CDT 2023\n", #<Process::Status: pid 2371820 exit 0>]
#
# Ruby invokes the executable directly, with no shell and no shell expansion:
#
# Open3.capture2e('doesnt_exist') # Raises Errno::ENOENT
#
# If one or more +args+ is given, each is an argument or option
# to be passed to the executable:
#
# Open3.capture2e('echo', 'C #')
# # => ["C #\n", #<Process::Status: pid 2371856 exit 0>]
# Open3.capture2e('echo', 'hello', 'world')
# # => ["hello world\n", #<Process::Status: pid 2371894 exit 0>]
#
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
# :call-seq:
# Open3.pipeline_rw([env, ] *cmds, options = {}) -> [first_stdin, last_stdout, wait_threads]
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process for each of the given +cmds+
# by calling Process.spawn.
# - Pipes the +stdout+ from each child to the +stdin+ of the next child,
# or, for the first child, from the caller's +stdin+,
# or, for the last child, to the caller's +stdout+.
#
# The method does not wait for child processes to exit,
# so the caller must do so.
#
# With no block given, returns a 3-element array containing:
#
# - The +stdin+ stream of the first child process.
# - The +stdout+ stream of the last child process.
# - An array of the wait threads for all of the child processes.
#
# Example:
#
# first_stdin, last_stdout, wait_threads = Open3.pipeline_rw('sort', 'cat -n')
# # => [#<IO:fd 20>, #<IO:fd 21>, [#<Process::Waiter:0x000055e8de29ab40 sleep>, #<Process::Waiter:0x000055e8de29a690 sleep>]]
# first_stdin.puts("foo\nbar\nbaz")
# first_stdin.close # Send EOF to sort.
# puts last_stdout.read
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
#
# Output:
#
# 1 bar
# 2 baz
# 3 foo
#
# With a block given, calls the block with the +stdin+ stream of the first child,
# the +stdout+ stream of the last child,
# and an array of the wait processes:
#
# Open3.pipeline_rw('sort', 'cat -n') do |first_stdin, last_stdout, wait_threads|
# first_stdin.puts "foo\nbar\nbaz"
# first_stdin.close # send EOF to sort.
# puts last_stdout.read
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
# end
#
# Output:
#
# 1 bar
# 2 baz
# 3 foo
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# If the first argument is a hash, it becomes leading argument +env+
# in each call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in each call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# Each remaining argument in +cmds+ is one of:
#
# - A +command_line+: a string that begins with a shell reserved word
# or special built-in, or contains one or more metacharacters.
# - An +exe_path+: the string path to an executable to be called.
# - An array containing a +command_line+ or an +exe_path+,
# along with zero or more string arguments for the command.
#
# See {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
#
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
# :call-seq:
# Open3.pipeline_r([env, ] *cmds, options = {}) -> [last_stdout, wait_threads]
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process for each of the given +cmds+
# by calling Process.spawn.
# - Pipes the +stdout+ from each child to the +stdin+ of the next child,
# or, for the last child, to the caller's +stdout+.
#
# The method does not wait for child processes to exit,
# so the caller must do so.
#
# With no block given, returns a 2-element array containing:
#
# - The +stdout+ stream of the last child process.
# - An array of the wait threads for all of the child processes.
#
# Example:
#
# last_stdout, wait_threads = Open3.pipeline_r('ls', 'grep R')
# # => [#<IO:fd 5>, [#<Process::Waiter:0x000055e8de2f9898 dead>, #<Process::Waiter:0x000055e8de2f94b0 sleep>]]
# puts last_stdout.read
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
#
# Output:
#
# Rakefile
# README.md
#
# With a block given, calls the block with the +stdout+ stream
# of the last child process,
# and an array of the wait processes:
#
# Open3.pipeline_r('ls', 'grep R') do |last_stdout, wait_threads|
# puts last_stdout.read
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
# end
#
# Output:
#
# Rakefile
# README.md
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# If the first argument is a hash, it becomes leading argument +env+
# in each call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in each call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# Each remaining argument in +cmds+ is one of:
#
# - A +command_line+: a string that begins with a shell reserved word
# or special built-in, or contains one or more metacharacters.
# - An +exe_path+: the string path to an executable to be called.
# - An array containing a +command_line+ or an +exe_path+,
# along with zero or more string arguments for the command.
#
# See {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
#
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
# :call-seq:
# Open3.pipeline_w([env, ] *cmds, options = {}) -> [first_stdin, wait_threads]
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process for each of the given +cmds+
# by calling Process.spawn.
# - Pipes the +stdout+ from each child to the +stdin+ of the next child,
# or, for the first child, pipes the caller's +stdout+ to the child's +stdin+.
#
# The method does not wait for child processes to exit,
# so the caller must do so.
#
# With no block given, returns a 2-element array containing:
#
# - The +stdin+ stream of the first child process.
# - An array of the wait threads for all of the child processes.
#
# Example:
#
# first_stdin, wait_threads = Open3.pipeline_w('sort', 'cat -n')
# # => [#<IO:fd 7>, [#<Process::Waiter:0x000055e8de928278 run>, #<Process::Waiter:0x000055e8de923e80 run>]]
# first_stdin.puts("foo\nbar\nbaz")
# first_stdin.close # Send EOF to sort.
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
#
# Output:
#
# 1 bar
# 2 baz
# 3 foo
#
# With a block given, calls the block with the +stdin+ stream
# of the first child process,
# and an array of the wait processes:
#
# Open3.pipeline_w('sort', 'cat -n') do |first_stdin, wait_threads|
# first_stdin.puts("foo\nbar\nbaz")
# first_stdin.close # Send EOF to sort.
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
# end
#
# Output:
#
# 1 bar
# 2 baz
# 3 foo
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# If the first argument is a hash, it becomes leading argument +env+
# in each call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in each call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# Each remaining argument in +cmds+ is one of:
#
# - A +command_line+: a string that begins with a shell reserved word
# or special built-in, or contains one or more metacharacters.
# - An +exe_path+: the string path to an executable to be called.
# - An array containing a +command_line+ or an +exe_path+,
# along with zero or more string arguments for the command.
#
# See {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
#
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
# :call-seq:
# Open3.pipeline_start([env, ] *cmds, options = {}) -> [wait_threads]
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process for each of the given +cmds+
# by calling Process.spawn.
# - Does not wait for child processes to exit.
#
# With no block given, returns an array of the wait threads
# for all of the child processes.
#
# Example:
#
# wait_threads = Open3.pipeline_start('ls', 'grep R')
# # => [#<Process::Waiter:0x000055e8de9d2bb0 run>, #<Process::Waiter:0x000055e8de9d2890 run>]
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
#
# Output:
#
# Rakefile
# README.md
#
# With a block given, calls the block with an array of the wait processes:
#
# Open3.pipeline_start('ls', 'grep R') do |wait_threads|
# wait_threads.each do |wait_thread|
# wait_thread.join
# end
# end
#
# Output:
#
# Rakefile
# README.md
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# If the first argument is a hash, it becomes leading argument +env+
# in each call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in each call to Process.spawn;
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# Each remaining argument in +cmds+ is one of:
#
# - A +command_line+: a string that begins with a shell reserved word
# or special built-in, or contains one or more metacharacters.
# - An +exe_path+: the string path to an executable to be called.
# - An array containing a +command_line+ or an +exe_path+,
# along with zero or more string arguments for the command.
#
# See {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
#
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
# :call-seq:
# Open3.pipeline([env, ] *cmds, options = {}) -> array_of_statuses
#
# Basically a wrapper for
# {Process.spawn}[rdoc-ref:Process.spawn]
# that:
#
# - Creates a child process for each of the given +cmds+
# by calling Process.spawn.
# - Pipes the +stdout+ from each child to the +stdin+ of the next child,
# or, for the last child, to the caller's +stdout+.
# - Waits for the child processes to exit.
# - Returns an array of Process::Status objects (one for each child).
#
# Example:
#
# wait_threads = Open3.pipeline('ls', 'grep R')
# # => [#<Process::Status: pid 2139200 exit 0>, #<Process::Status: pid 2139202 exit 0>]
#
# Output:
#
# Rakefile
# README.md
#
# Like Process.spawn, this method has potential security vulnerabilities
# if called with untrusted input;
# see {Command Injection}[rdoc-ref:command_injection.rdoc@Command+Injection].
#
# If the first argument is a hash, it becomes leading argument +env+
# in each call to Process.spawn;
# see {Execution Environment}[rdoc-ref:Process@Execution+Environment].
#
# If the last argument is a hash, it becomes trailing argument +options+
# in each call to Process.spawn'
# see {Execution Options}[rdoc-ref:Process@Execution+Options].
#
# Each remaining argument in +cmds+ is one of:
#
# - A +command_line+: a string that begins with a shell reserved word
# or special built-in, or contains one or more metacharacters.
# - An +exe_path+: the string path to an executable to be called.
# - An array containing a +command_line+ or an +exe_path+,
# along with zero or more string arguments for the command.
#
# See {Argument command_line or exe_path}[rdoc-ref:Process@Argument+command_line+or+exe_path].
#
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 00000043022 15173517736 0011460 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)
DH_ffdhe2048 = OpenSSL::PKey::DH.new <<-_end_of_pem_
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----
_end_of_pem_
private_constant :DH_ffdhe2048
DEFAULT_TMP_DH_CALLBACK = lambda { |ctx, is_export, keylen| # :nodoc:
warn "using default DH parameters." if $VERBOSE
DH_ffdhe2048
}
end
if !(OpenSSL::OPENSSL_VERSION.start_with?("OpenSSL") &&
OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10100000)
DEFAULT_PARAMS.merge!(
ciphers: %w{
ECDHE-ECDSA-AES128-GCM-SHA256
ECDHE-RSA-AES128-GCM-SHA256
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-RSA-AES256-GCM-SHA384
DHE-RSA-AES128-GCM-SHA256
DHE-DSS-AES128-GCM-SHA256
DHE-RSA-AES256-GCM-SHA384
DHE-DSS-AES256-GCM-SHA384
ECDHE-ECDSA-AES128-SHA256
ECDHE-RSA-AES128-SHA256
ECDHE-ECDSA-AES128-SHA
ECDHE-RSA-AES128-SHA
ECDHE-ECDSA-AES256-SHA384
ECDHE-RSA-AES256-SHA384
ECDHE-ECDSA-AES256-SHA
ECDHE-RSA-AES256-SHA
DHE-RSA-AES128-SHA256
DHE-RSA-AES256-SHA256
DHE-RSA-AES128-SHA
DHE-RSA-AES256-SHA
DHE-DSS-AES128-SHA256
DHE-DSS-AES256-SHA256
DHE-DSS-AES128-SHA
DHE-DSS-AES256-SHA
AES128-GCM-SHA256
AES256-GCM-SHA384
AES128-SHA256
AES256-SHA256
AES128-SHA
AES256-SHA
}.join(":"),
)
end
DEFAULT_CERT_STORE = OpenSSL::X509::Store.new # :nodoc:
DEFAULT_CERT_STORE.set_default_paths
DEFAULT_CERT_STORE.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL
# A callback invoked when DH parameters are required 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).unpack1('H*')
@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 15173517736 0011602 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 15173517736 0012303 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 15173517736 0011704 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 00000024236 15173517736 0012634 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
@rbuffer.slice!(0, size)
end
end
public
# call-seq:
# ssl.getbyte => 81
#
# Get the next 8bit byte from `ssl`. Returns `nil` on EOF
def getbyte
read(1)&.ord
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 00000003054 15173517736 0012137 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.
#
# === Example
#
# 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 15173517736 0012340 0 ustar 00 # frozen_string_literal: true
module OpenSSL
VERSION = "3.2.0"
end
share/ruby/openssl/cipher.rb 0000644 00000003320 15173517736 0012126 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 15173517736 0011637 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 15173517736 0011376 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 15173517736 0011252 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 00000173510 15173517736 0011037 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+ (need to require +optparse/date+)
# - DateTime -- Anything accepted by +DateTime.parse+ (need to require +optparse/date+)
# - Time -- Anything accepted by +Time.httpdate+ or +Time.parse+ (need to require +optparse/time+)
# - URI -- Anything accepted by +URI.parse+ (need to require +optparse/uri+)
# - Shellwords -- Anything accepted by +Shellwords.shellwords+ (need to require +optparse/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.4.0"
# :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
#
# Option +symbolize_names+ (boolean) specifies whether returned Hash keys should be Symbols; defaults to +false+ (use Strings).
#
# params = ARGV.getopts("ab:", "foo", "bar:", "zot:Z;zot option", symbolize_names: true)
# # 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, symbolize_names: false)
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(:[]=))
symbolize_names ? result.transform_keys(&:to_sym) : result
end
#
# See #getopts.
#
def self.getopts(*args, symbolize_names: false)
new.getopts(*args, symbolize_names: symbolize_names)
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, symbolize_names: false)
options.getopts(self, *args, symbolize_names: symbolize_names)
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 15173517736 0011772 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 15173517736 0012323 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 15173517736 0012515 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 15173517736 0013216 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 15173517736 0011627 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 15173517736 0011411 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 15173517736 0011746 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 00000016160 15173517736 0011365 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
VERSION = "0.2.0"
# 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 15173517736 0011465 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 15173517736 0012254 0 ustar 00 # frozen_string_literal: true
require_relative "syntax_suggest/core_ext"
share/ruby/singleton.rb 0000644 00000007703 15173517736 0011204 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.2.0"
# 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:
@singleton__instance__ || @singleton__mutex__.synchronize { @singleton__instance__ ||= new }
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.1.2/lib/psych.rb 0000644 00000060707 15173517736 0013575 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
class << self
if defined?(Ractor)
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 load_tags
config.load_tags
end
def dump_tags
config.dump_tags
end
def domain_types
config.domain_types
end
def load_tags=(value)
config.load_tags = value
end
def dump_tags=(value)
config.dump_tags = value
end
def domain_types=(value)
config.domain_types = value
end
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 15173517736 0011454 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 15173517736 0011617 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 15173517736 0011427 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 15173517736 0011762 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 15173517736 0010000 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 00000270732 15173517736 0010001 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}[https://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
#
# === \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
# The error thrown when the parser encounters invalid encoding in CSV.
class InvalidEncodingError < MalformedCSVError
attr_reader :encoding
def initialize(encoding, line_number)
@encoding = encoding
super("Invalid byte sequence in #{encoding}", 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 {Access Modes}[rdoc-ref:File@Access+Modes].
# * 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 {Access Modes}[rdoc-ref:File@Access+Modes].
# * 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 15173517736 0007745 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.4.2"
end
require 'cgi/core'
require 'cgi/cookie'
require 'cgi/util'
CGI.autoload(:HtmlExtension, 'cgi/html')
share/ruby/objspace.rb 0000644 00000010214 15173517736 0010757 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
# Dump the contents of a ruby object as JSON.
#
# _output_ can be one of: +:stdout+, +:file+, +:string+, or IO object.
#
# * +:file+ means dumping to a tempfile and returning corresponding File object;
# * +:stdout+ means printing the dump and returning +nil+;
# * +:string+ means returning a string with the dump;
# * if an instance of IO object is provided, the output goes there, and the object
# is returned.
#
# 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
# Dump the contents of the ruby heap as JSON.
#
# _output_ argument is the same as for #dump.
#
# _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
# Dump the contents of the ruby shape tree as JSON.
#
# _output_ argument is the same as for #dump.
#
# If _since_ 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.1.2/lib/psych 0000755 00000000000 15173517736 0013152 0 ustar 00 share/ruby/pp.rb 0000644 00000042372 15173517736 0007622 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.
# Note that <code>require 'pp'</code> is needed before redefining <code>#pretty_print(pp)</code>.
#
# <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
VERSION = "0.5.0"
# 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.
#
# 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 00000035043 15173517736 0007750 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', trim_mode: "", eoutvar: "@product").result b
# <%= PRODUCT[:name] %>
# <%= PRODUCT[:desc] %>
# END_PRODUCT
# ERB.new(<<~'END_PRICE', trim_mode: "", eoutvar: "@price").result b
# <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %>
# <%= PRODUCT[:desc] %>
# END_PRICE
# end
# end
#
# # 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.5/lib/bigdecimal 0000755 00000000000 15173517736 0015037 0 ustar 00 share/ruby/yaml.rb 0000644 00000004210 15173517736 0010132 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
LOADER_VERSION = "0.3.0"
end
share/ruby/bigdecimal.rb 0000644 00000000202 15173517736 0011245 0 ustar 00 if RUBY_ENGINE == 'jruby'
JRuby::Util.load_ext("org.jruby.ext.bigdecimal.BigDecimalLibrary")
else
require 'bigdecimal.so'
end
share/ruby/drb.rb 0000644 00000000062 15173517736 0007740 0 ustar 00 # frozen_string_literal: false
require 'drb/drb'
share/ruby/getoptlong.rb 0000644 00000050417 15173517736 0011364 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.1"
#
# 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 15173517736 0012121 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 00000035320 15173517736 0010454 0 ustar 00 require 'io/console'
require 'forwardable'
require 'reline/version'
require 'reline/config'
require 'reline/key_actor'
require 'reline/key_stroke'
require 'reline/line_editor'
require 'reline/history'
require 'reline/terminfo'
require 'reline/io'
require 'reline/face'
require 'rbconfig'
module Reline
# NOTE: For making compatible with the rb-readline gem
FILENAME_COMPLETION_PROC = nil
USERNAME_COMPLETION_PROC = nil
class ConfigEncodingConversionError < StandardError; end
Key = Struct.new(:char, :combined_char, :with_meta) do
# For dialog_proc `key.match?(dialog.name)`
def match?(sym)
combined_char.is_a?(Symbol) && combined_char == sym
end
end
CursorPos = Struct.new(:x, :y)
DialogRenderInfo = Struct.new(
:pos,
:contents,
:face,
:bg_color, # For the time being, this line should stay here for the compatibility with IRB.
:width,
:height,
:scrollbar,
keyword_init: true
)
class Core
ATTR_READER_NAMES = %i(
completion_append_character
basic_word_break_characters
completer_word_break_characters
basic_quote_characters
completer_quote_characters
filename_quote_characters
special_prefixes
completion_proc
output_modifier_proc
prompt_proc
auto_indent_proc
pre_input_hook
dig_perfect_match_proc
).each(&method(:attr_reader))
attr_accessor :config
attr_accessor :key_stroke
attr_accessor :line_editor
attr_accessor :last_incremental_search
attr_reader :output
extend Forwardable
def_delegators :config,
:autocompletion,
:autocompletion=
def initialize
self.output = STDOUT
@mutex = Mutex.new
@dialog_proc_list = {}
yield self
@completion_quote_character = nil
end
def io_gate
Reline::IOGate
end
def encoding
io_gate.encoding
end
def completion_append_character=(val)
if val.nil?
@completion_append_character = nil
elsif val.size == 1
@completion_append_character = val.encode(encoding)
elsif val.size > 1
@completion_append_character = val[0].encode(encoding)
else
@completion_append_character = nil
end
end
def basic_word_break_characters=(v)
@basic_word_break_characters = v.encode(encoding)
end
def completer_word_break_characters=(v)
@completer_word_break_characters = v.encode(encoding)
end
def basic_quote_characters=(v)
@basic_quote_characters = v.encode(encoding)
end
def completer_quote_characters=(v)
@completer_quote_characters = v.encode(encoding)
end
def filename_quote_characters=(v)
@filename_quote_characters = v.encode(encoding)
end
def special_prefixes=(v)
@special_prefixes = v.encode(encoding)
end
def completion_case_fold=(v)
@config.completion_ignore_case = v
end
def completion_case_fold
@config.completion_ignore_case
end
def completion_quote_character
@completion_quote_character
end
def completion_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@completion_proc = p
end
def output_modifier_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@output_modifier_proc = p
end
def prompt_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@prompt_proc = p
end
def auto_indent_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@auto_indent_proc = p
end
def pre_input_hook=(p)
@pre_input_hook = p
end
def dig_perfect_match_proc=(p)
raise ArgumentError unless p.respond_to?(:call) or p.nil?
@dig_perfect_match_proc = p
end
DialogProc = Struct.new(:dialog_proc, :context)
def add_dialog_proc(name_sym, p, context = nil)
raise ArgumentError unless name_sym.instance_of?(Symbol)
if p.nil?
@dialog_proc_list.delete(name_sym)
else
raise ArgumentError unless p.respond_to?(:call)
@dialog_proc_list[name_sym] = DialogProc.new(p, context)
end
end
def dialog_proc(name_sym)
@dialog_proc_list[name_sym]
end
def input=(val)
raise TypeError unless val.respond_to?(:getc) or val.nil?
if val.respond_to?(:getc) && io_gate.respond_to?(:input=)
io_gate.input = val
end
end
def output=(val)
raise TypeError unless val.respond_to?(:write) or val.nil?
@output = val
if io_gate.respond_to?(:output=)
io_gate.output = val
end
end
def vi_editing_mode
config.editing_mode = :vi_insert
nil
end
def emacs_editing_mode
config.editing_mode = :emacs
nil
end
def vi_editing_mode?
config.editing_mode_is?(:vi_insert, :vi_command)
end
def emacs_editing_mode?
config.editing_mode_is?(:emacs)
end
def get_screen_size
io_gate.get_screen_size
end
Reline::DEFAULT_DIALOG_PROC_AUTOCOMPLETE = ->() {
# autocomplete
return unless config.autocompletion
journey_data = completion_journey_data
return unless journey_data
target = journey_data.list.first
completed = journey_data.list[journey_data.pointer]
result = journey_data.list.drop(1)
pointer = journey_data.pointer - 1
return if completed.empty? || (result == [completed] && pointer < 0)
target_width = Reline::Unicode.calculate_width(target)
completed_width = Reline::Unicode.calculate_width(completed)
if cursor_pos.x <= completed_width - target_width
# When target is rendered on the line above cursor position
x = screen_width - completed_width
y = -1
else
x = [cursor_pos.x - completed_width, 0].max
y = 0
end
cursor_pos_to_render = Reline::CursorPos.new(x, y)
if context and context.is_a?(Array)
context.clear
context.push(cursor_pos_to_render, result, pointer, dialog)
end
dialog.pointer = pointer
DialogRenderInfo.new(
pos: cursor_pos_to_render,
contents: result,
scrollbar: true,
height: [15, preferred_dialog_height].min,
face: :completion_dialog
)
}
Reline::DEFAULT_DIALOG_CONTEXT = Array.new
def readmultiline(prompt = '', add_hist = false, &confirm_multiline_termination)
@mutex.synchronize do
unless confirm_multiline_termination
raise ArgumentError.new('#readmultiline needs block to confirm multiline termination')
end
io_gate.with_raw_input do
inner_readline(prompt, add_hist, true, &confirm_multiline_termination)
end
whole_buffer = line_editor.whole_buffer.dup
whole_buffer.taint if RUBY_VERSION < '2.7'
if add_hist and whole_buffer and whole_buffer.chomp("\n").size > 0
Reline::HISTORY << whole_buffer
end
if line_editor.eof?
line_editor.reset_line
# Return nil if the input is aborted by C-d.
nil
else
whole_buffer
end
end
end
def readline(prompt = '', add_hist = false)
@mutex.synchronize do
io_gate.with_raw_input do
inner_readline(prompt, add_hist, false)
end
line = line_editor.line.dup
line.taint if RUBY_VERSION < '2.7'
if add_hist and line and line.chomp("\n").size > 0
Reline::HISTORY << line.chomp("\n")
end
line_editor.reset_line if line_editor.line.nil?
line
end
end
private def inner_readline(prompt, add_hist, multiline, &confirm_multiline_termination)
if ENV['RELINE_STDERR_TTY']
if io_gate.win?
$stderr = File.open(ENV['RELINE_STDERR_TTY'], 'a')
else
$stderr.reopen(ENV['RELINE_STDERR_TTY'], 'w')
end
$stderr.sync = true
$stderr.puts "Reline is used by #{Process.pid}"
end
unless config.test_mode or config.loaded?
config.read
io_gate.set_default_key_bindings(config)
end
otio = io_gate.prep
may_req_ambiguous_char_width
line_editor.reset(prompt, encoding: 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
# Readline calls pre_input_hook just after printing the first prompt.
line_editor.print_nomultiline_prompt
pre_input_hook&.call
unless Reline::IOGate.dumb?
@dialog_proc_list.each_pair do |name_sym, d|
line_editor.add_dialog_proc(name_sym, d.dialog_proc, d.context)
end
end
line_editor.update_dialogs
line_editor.rerender
begin
line_editor.set_signal_handlers
loop do
read_io(config.keyseq_timeout) { |inputs|
line_editor.set_pasting_state(io_gate.in_pasting?)
inputs.each do |key|
if key.char == :bracketed_paste_start
text = io_gate.read_bracketed_paste
line_editor.insert_multiline_text(text)
line_editor.scroll_into_view
else
line_editor.update(key)
end
end
}
if line_editor.finished?
line_editor.render_finished
break
else
line_editor.set_pasting_state(io_gate.in_pasting?)
line_editor.rerender
end
end
io_gate.move_cursor_column(0)
rescue Errno::EIO
# Maybe the I/O has been closed.
ensure
line_editor.finalize
io_gate.deprep(otio)
end
end
# GNU Readline watis for "keyseq-timeout" milliseconds when the input is
# ambiguous whether it is matching or matched.
# If the next character does not arrive within the specified timeout, input
# is considered as matched.
# `ESC` is ambiguous because it can be a standalone ESC (matched) or part of
# `ESC char` or part of CSI sequence (matching).
private def read_io(keyseq_timeout, &block)
buffer = []
status = KeyStroke::MATCHING
loop do
timeout = status == KeyStroke::MATCHING_MATCHED ? keyseq_timeout.fdiv(1000) : Float::INFINITY
c = io_gate.getc(timeout)
if c.nil? || c == -1
if status == KeyStroke::MATCHING_MATCHED
status = KeyStroke::MATCHED
elsif buffer.empty?
# io_gate is closed and reached EOF
block.call([Key.new(nil, nil, false)])
return
else
status = KeyStroke::UNMATCHED
end
else
buffer << c
status = key_stroke.match_status(buffer)
end
if status == KeyStroke::MATCHED || status == KeyStroke::UNMATCHED
expanded, rest_bytes = key_stroke.expand(buffer)
rest_bytes.reverse_each { |c| io_gate.ungetc(c) }
block.call(expanded)
return
end
end
end
def ambiguous_width
may_req_ambiguous_char_width unless defined? @ambiguous_width
@ambiguous_width
end
private def may_req_ambiguous_char_width
@ambiguous_width = 2 if io_gate.dumb? || !STDIN.tty? || !STDOUT.tty?
return if defined? @ambiguous_width
io_gate.move_cursor_column(0)
begin
output.write "\u{25bd}"
rescue Encoding::UndefinedConversionError
# LANG=C
@ambiguous_width = 1
else
@ambiguous_width = io_gate.cursor_pos.x
end
io_gate.move_cursor_column(0)
io_gate.erase_after_cursor
end
end
extend Forwardable
extend SingleForwardable
#--------------------------------------------------------
# Documented API
#--------------------------------------------------------
(Core::ATTR_READER_NAMES).each { |name|
def_single_delegators :core, :"#{name}", :"#{name}="
}
def_single_delegators :core, :input=, :output=
def_single_delegators :core, :vi_editing_mode, :emacs_editing_mode
def_single_delegators :core, :readline
def_single_delegators :core, :completion_case_fold, :completion_case_fold=
def_single_delegators :core, :completion_quote_character
def_instance_delegators self, :readline
private :readline
#--------------------------------------------------------
# Undocumented API
#--------------------------------------------------------
# Testable in original
def_single_delegators :core, :get_screen_size
def_single_delegators :line_editor, :eof?
def_instance_delegators self, :eof?
def_single_delegators :line_editor, :delete_text
def_single_delegator :line_editor, :line, :line_buffer
def_single_delegator :line_editor, :byte_pointer, :point
def_single_delegator :line_editor, :byte_pointer=, :point=
def self.insert_text(text)
line_editor.insert_multiline_text(text)
self
end
# Untestable in original
def_single_delegator :line_editor, :rerender, :redisplay
def_single_delegators :core, :vi_editing_mode?, :emacs_editing_mode?
def_single_delegators :core, :ambiguous_width
def_single_delegators :core, :last_incremental_search
def_single_delegators :core, :last_incremental_search=
def_single_delegators :core, :add_dialog_proc
def_single_delegators :core, :dialog_proc
def_single_delegators :core, :autocompletion, :autocompletion=
def_single_delegators :core, :readmultiline
def_instance_delegators self, :readmultiline
private :readmultiline
def self.encoding_system_needs
self.core.encoding
end
def self.core
@core ||= Core.new { |core|
core.config = Reline::Config.new
core.key_stroke = Reline::KeyStroke.new(core.config)
core.line_editor = Reline::LineEditor.new(core.config, core.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)
core.io_gate.ungetc(c)
end
def self.line_editor
core.line_editor
end
end
Reline::IOGate = Reline::IO.decide_io_gate
# Deprecated
Reline::GeneralIO = Reline::Dumb.new
Reline::Face.load_initial_configs
Reline::HISTORY = Reline::History.new(Reline.core.config)
share/ruby/reline/kill_ring.rb 0000644 00000004575 15173517736 0012436 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)
equal?(other)
end
end
class RingBuffer
attr_reader :size
attr_reader :head
def initialize(max = 1024)
@max = max
@size = 0
@head = nil # reading head of ring-shaped tape
end
def <<(point)
if @size.zero?
@head = point
@head.backward = @head
@head.forward = @head
@size = 1
elsif @size >= @max
tail = @head.forward
new_tail = tail.forward
@head.forward = point
point.backward = @head
new_tail.backward = point
point.forward = new_tail
@head = point
else
tail = @head.forward
@head.forward = point
point.backward = @head
tail.backward = point
point.forward = tail
@head = point
@size += 1
end
end
def empty?
@size.zero?
end
end
def initialize(max = 1024)
@ring = RingBuffer.new(max)
@ring_pointer = nil
@buffer = nil
@state = State::FRESH
end
def append(string, before_p = false)
case @state
when State::FRESH, State::YANK
@ring << RingPoint.new(+string)
@state = State::CONTINUED
when State::CONTINUED, State::PROCESSED
if before_p
@ring.head.str.prepend(string)
else
@ring.head.str.concat(string)
end
@state = State::CONTINUED
end
end
def process
case @state
when State::FRESH
# nothing to do
when State::CONTINUED
@state = State::PROCESSED
when State::PROCESSED
@state = State::FRESH
when State::YANK
# nothing to do
end
end
def yank
unless @ring.empty?
@state = State::YANK
@ring_pointer = @ring.head
@ring_pointer.str
else
nil
end
end
def yank_pop
if @state == State::YANK
prev_yank = @ring_pointer.str
@ring_pointer = @ring_pointer.backward
[@ring_pointer.str, prev_yank]
else
nil
end
end
def each
start = head = @ring.head
loop do
break if head.nil?
yield head.str
head = head.backward
break if head == start
end
end
end
share/ruby/reline/unicode.rb 0000644 00000052204 15173517736 0012102 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\e]+)*(?:\a|\e\\)/
WIDTH_SCANNER = /\G(?:(#{NON_PRINTING_START})|(#{NON_PRINTING_END})|(#{CSI_REGEXP})|(#{OSC_REGEXP})|(\X))/o
def self.escape_for_print(str)
str.chars.map! { |gr|
case gr
when -"\n"
gr
when -"\t"
-' '
else
EscapedPairs[gr.ord] || gr
end
}.join
end
require 'reline/unicode/east_asian_width'
def self.get_mbchar_width(mbchar)
ord = mbchar.ord
if ord <= 0x1F # in EscapedPairs
return 2
elsif ord <= 0x7E # printable ASCII chars
return 1
end
utf8_mbchar = mbchar.encode(Encoding::UTF_8)
ord = utf8_mbchar.ord
chunk_index = EastAsianWidth::CHUNK_LAST.bsearch_index { |o| ord <= o }
size = EastAsianWidth::CHUNK_WIDTH[chunk_index]
if size == -1
Reline.ambiguous_width
elsif size == 1 && utf8_mbchar.size >= 2
second_char_ord = utf8_mbchar[1].ord
# Halfwidth Dakuten Handakuten
# Only these two character has Letter Modifier category and can be combined in a single grapheme cluster
(second_char_ord == 0xFF9E || second_char_ord == 0xFF9F) ? 2 : 1
else
size
end
end
def self.calculate_width(str, allow_escape_code = false)
if allow_escape_code
width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
when non_printing_start
in_zero_width = true
when non_printing_end
in_zero_width = false
when csi, osc
when gc
unless in_zero_width
width += get_mbchar_width(gc)
end
end
end
width
else
str.encode(Encoding::UTF_8).grapheme_clusters.inject(0) { |w, gc|
w + get_mbchar_width(gc)
}
end
end
def self.split_by_width(str, max_width, encoding = str.encoding, offset: 0)
lines = [String.new(encoding: encoding)]
height = 1
width = offset
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
seq = String.new(encoding: encoding)
rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
when non_printing_start
in_zero_width = true
lines.last << NON_PRINTING_START
when non_printing_end
in_zero_width = false
lines.last << NON_PRINTING_END
when csi
lines.last << csi
unless in_zero_width
if csi == -"\e[m" || csi == -"\e[0m"
seq.clear
else
seq << csi
end
end
when osc
lines.last << osc
seq << osc
when gc
unless in_zero_width
mbchar_width = get_mbchar_width(gc)
if (width += mbchar_width) > max_width
width = mbchar_width
lines << nil
lines << seq.dup
height += 1
end
end
lines.last << gc
end
end
# The cursor moves to next line in first
if width == max_width
lines << nil
lines << String.new(encoding: encoding)
height += 1
end
[lines, height]
end
# Take a chunk of a String cut by width with escape sequences.
def self.take_range(str, start_col, max_width)
take_mbchar_range(str, start_col, max_width).first
end
def self.take_mbchar_range(str, start_col, width, cover_begin: false, cover_end: false, padding: false)
chunk = String.new(encoding: str.encoding)
end_col = start_col + width
total_width = 0
rest = str.encode(Encoding::UTF_8)
in_zero_width = false
chunk_start_col = nil
chunk_end_col = nil
has_csi = false
rest.scan(WIDTH_SCANNER) do |non_printing_start, non_printing_end, csi, osc, gc|
case
when non_printing_start
in_zero_width = true
chunk << NON_PRINTING_START
when non_printing_end
in_zero_width = false
chunk << NON_PRINTING_END
when csi
has_csi = true
chunk << csi
when osc
chunk << osc
when gc
if in_zero_width
chunk << gc
next
end
mbchar_width = get_mbchar_width(gc)
prev_width = total_width
total_width += mbchar_width
if (cover_begin || padding ? total_width <= start_col : prev_width < start_col)
# Current character haven't reached start_col yet
next
elsif padding && !cover_begin && prev_width < start_col && start_col < total_width
# Add preceding padding. This padding might have background color.
chunk << ' '
chunk_start_col ||= start_col
chunk_end_col = total_width
next
elsif (cover_end ? prev_width < end_col : total_width <= end_col)
# Current character is in the range
chunk << gc
chunk_start_col ||= prev_width
chunk_end_col = total_width
break if total_width >= end_col
else
# Current character exceeds end_col
if padding && end_col < total_width
# Add succeeding padding. This padding might have background color.
chunk << ' '
chunk_start_col ||= prev_width
chunk_end_col = end_col
end
break
end
end
end
chunk_start_col ||= start_col
chunk_end_col ||= start_col
if padding && chunk_end_col < end_col
# Append padding. This padding should not include background color.
chunk << "\e[0m" if has_csi
chunk << ' ' * (end_col - chunk_end_col)
chunk_end_col = end_col
end
[chunk, chunk_start_col, chunk_end_col - chunk_start_col]
end
def self.get_next_mbchar_size(line, byte_pointer)
grapheme = line.byteslice(byte_pointer..-1).grapheme_clusters.first
grapheme ? grapheme.bytesize : 0
end
def self.get_prev_mbchar_size(line, byte_pointer)
if byte_pointer.zero?
0
else
grapheme = line.byteslice(0..(byte_pointer - 1)).grapheme_clusters.last
grapheme ? grapheme.bytesize : 0
end
end
def self.em_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_forward_word_with_capitalization(line, byte_pointer)
width = 0
byte_size = 0
new_str = String.new
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
new_str += mbchar
width += get_mbchar_width(mbchar)
byte_size += size
end
first = true
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
if first
new_str += mbchar.upcase
first = false
else
new_str += mbchar.downcase
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width, new_str]
end
def self.em_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.em_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.ed_transpose_words(line, byte_pointer)
right_word_start = nil
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if size.zero?
# ' aaa bbb [cursor]'
byte_size = 0
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
elsif mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
# ' aaa bb[cursor]b'
byte_size = 0
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
byte_size = 0
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
else
byte_size = 0
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size += size
end
if (byte_pointer + byte_size) == (line.bytesize - 1)
# ' aaa bbb [cursor] '
after_start = line.bytesize
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
right_word_start = byte_pointer + byte_size
else
# ' aaa [cursor] bbb '
right_word_start = byte_pointer + byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size += size
end
after_start = byte_pointer + byte_size
end
end
byte_size = right_word_start - byte_pointer
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\p{Word}/
byte_size -= size
end
middle_start = byte_pointer + byte_size
byte_size = middle_start - byte_pointer
while 0 < (byte_pointer + byte_size)
size = get_prev_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size - size, size)
break if mbchar.encode(Encoding::UTF_8) =~ /\P{Word}/
byte_size -= size
end
left_word_start = byte_pointer + byte_size
[left_word_start, middle_start, right_word_start, after_start]
end
def self.vi_big_forward_word(line, byte_pointer)
width = 0
byte_size = 0
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_big_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\s/
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_big_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
break if mbchar =~ /\s/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_word(line, byte_pointer, drop_terminate_spaces = false)
if line.bytesize > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :space
break if mbchar =~ /\S/
when :non_word_printable
break if mbchar =~ /\w|\s/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
return [byte_size, width] if drop_terminate_spaces
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
break if mbchar =~ /\S/
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_forward_end_word(line, byte_pointer)
if (line.bytesize - 1) > byte_pointer
size = get_next_mbchar_size(line, byte_pointer)
mbchar = line.byteslice(byte_pointer, size)
if mbchar =~ /\w/
started_by = :word
elsif mbchar =~ /\s/
started_by = :space
else
started_by = :non_word_printable
end
width = get_mbchar_width(mbchar)
byte_size = size
else
return [0, 0]
end
if (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\w/
second = :word
elsif mbchar =~ /\s/
second = :space
else
second = :non_word_printable
end
second_width = get_mbchar_width(mbchar)
second_byte_size = size
else
return [byte_size, width]
end
if second == :space
width += second_width
byte_size += second_byte_size
while (line.bytesize - 1) > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
else
case [started_by, second]
when [:word, :non_word_printable], [:non_word_printable, :word]
started_by = second
else
width += second_width
byte_size += second_byte_size
started_by = second
end
end
prev_width = width
prev_byte_size = byte_size
while line.bytesize > (byte_pointer + byte_size)
size = get_next_mbchar_size(line, byte_pointer + byte_size)
mbchar = line.byteslice(byte_pointer + byte_size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
prev_width = width
prev_byte_size = byte_size
width += get_mbchar_width(mbchar)
byte_size += size
end
[prev_byte_size, prev_width]
end
def self.vi_backward_word(line, byte_pointer)
width = 0
byte_size = 0
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
if mbchar =~ /\S/
if mbchar =~ /\w/
started_by = :word
else
started_by = :non_word_printable
end
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
while 0 < (byte_pointer - byte_size)
size = get_prev_mbchar_size(line, byte_pointer - byte_size)
mbchar = line.byteslice(byte_pointer - byte_size - size, size)
case started_by
when :word
break if mbchar =~ /\W/
when :non_word_printable
break if mbchar =~ /[\w\s]/
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
def self.vi_first_print(line)
width = 0
byte_size = 0
while (line.bytesize - 1) > byte_size
size = get_next_mbchar_size(line, byte_size)
mbchar = line.byteslice(byte_size, size)
if mbchar =~ /\S/
break
end
width += get_mbchar_width(mbchar)
byte_size += size
end
[byte_size, width]
end
end
share/ruby/reline/unicode/east_asian_width.rb 0000644 00000052453 15173517736 0015416 0 ustar 00 class Reline::Unicode::EastAsianWidth
# This is based on EastAsianWidth.txt
# UNICODE_VERSION = '15.1.0'
CHUNK_LAST, CHUNK_WIDTH = [
[0x1f, 2],
[0x7e, 1],
[0x7f, 2],
[0xa0, 1],
[0xa1, -1],
[0xa3, 1],
[0xa4, -1],
[0xa6, 1],
[0xa8, -1],
[0xa9, 1],
[0xaa, -1],
[0xac, 1],
[0xae, -1],
[0xaf, 1],
[0xb4, -1],
[0xb5, 1],
[0xba, -1],
[0xbb, 1],
[0xbf, -1],
[0xc5, 1],
[0xc6, -1],
[0xcf, 1],
[0xd0, -1],
[0xd6, 1],
[0xd8, -1],
[0xdd, 1],
[0xe1, -1],
[0xe5, 1],
[0xe6, -1],
[0xe7, 1],
[0xea, -1],
[0xeb, 1],
[0xed, -1],
[0xef, 1],
[0xf0, -1],
[0xf1, 1],
[0xf3, -1],
[0xf6, 1],
[0xfa, -1],
[0xfb, 1],
[0xfc, -1],
[0xfd, 1],
[0xfe, -1],
[0x100, 1],
[0x101, -1],
[0x110, 1],
[0x111, -1],
[0x112, 1],
[0x113, -1],
[0x11a, 1],
[0x11b, -1],
[0x125, 1],
[0x127, -1],
[0x12a, 1],
[0x12b, -1],
[0x130, 1],
[0x133, -1],
[0x137, 1],
[0x138, -1],
[0x13e, 1],
[0x142, -1],
[0x143, 1],
[0x144, -1],
[0x147, 1],
[0x14b, -1],
[0x14c, 1],
[0x14d, -1],
[0x151, 1],
[0x153, -1],
[0x165, 1],
[0x167, -1],
[0x16a, 1],
[0x16b, -1],
[0x1cd, 1],
[0x1ce, -1],
[0x1cf, 1],
[0x1d0, -1],
[0x1d1, 1],
[0x1d2, -1],
[0x1d3, 1],
[0x1d4, -1],
[0x1d5, 1],
[0x1d6, -1],
[0x1d7, 1],
[0x1d8, -1],
[0x1d9, 1],
[0x1da, -1],
[0x1db, 1],
[0x1dc, -1],
[0x250, 1],
[0x251, -1],
[0x260, 1],
[0x261, -1],
[0x2c3, 1],
[0x2c4, -1],
[0x2c6, 1],
[0x2c7, -1],
[0x2c8, 1],
[0x2cb, -1],
[0x2cc, 1],
[0x2cd, -1],
[0x2cf, 1],
[0x2d0, -1],
[0x2d7, 1],
[0x2db, -1],
[0x2dc, 1],
[0x2dd, -1],
[0x2de, 1],
[0x2df, -1],
[0x2ff, 1],
[0x36f, 0],
[0x390, 1],
[0x3a1, -1],
[0x3a2, 1],
[0x3a9, -1],
[0x3b0, 1],
[0x3c1, -1],
[0x3c2, 1],
[0x3c9, -1],
[0x400, 1],
[0x401, -1],
[0x40f, 1],
[0x44f, -1],
[0x450, 1],
[0x451, -1],
[0x482, 1],
[0x487, 0],
[0x590, 1],
[0x5bd, 0],
[0x5be, 1],
[0x5bf, 0],
[0x5c0, 1],
[0x5c2, 0],
[0x5c3, 1],
[0x5c5, 0],
[0x5c6, 1],
[0x5c7, 0],
[0x60f, 1],
[0x61a, 0],
[0x64a, 1],
[0x65f, 0],
[0x66f, 1],
[0x670, 0],
[0x6d5, 1],
[0x6dc, 0],
[0x6de, 1],
[0x6e4, 0],
[0x6e6, 1],
[0x6e8, 0],
[0x6e9, 1],
[0x6ed, 0],
[0x710, 1],
[0x711, 0],
[0x72f, 1],
[0x74a, 0],
[0x7a5, 1],
[0x7b0, 0],
[0x7ea, 1],
[0x7f3, 0],
[0x7fc, 1],
[0x7fd, 0],
[0x815, 1],
[0x819, 0],
[0x81a, 1],
[0x823, 0],
[0x824, 1],
[0x827, 0],
[0x828, 1],
[0x82d, 0],
[0x858, 1],
[0x85b, 0],
[0x897, 1],
[0x89f, 0],
[0x8c9, 1],
[0x8e1, 0],
[0x8e2, 1],
[0x902, 0],
[0x939, 1],
[0x93a, 0],
[0x93b, 1],
[0x93c, 0],
[0x940, 1],
[0x948, 0],
[0x94c, 1],
[0x94d, 0],
[0x950, 1],
[0x957, 0],
[0x961, 1],
[0x963, 0],
[0x980, 1],
[0x981, 0],
[0x9bb, 1],
[0x9bc, 0],
[0x9c0, 1],
[0x9c4, 0],
[0x9cc, 1],
[0x9cd, 0],
[0x9e1, 1],
[0x9e3, 0],
[0x9fd, 1],
[0x9fe, 0],
[0xa00, 1],
[0xa02, 0],
[0xa3b, 1],
[0xa3c, 0],
[0xa40, 1],
[0xa42, 0],
[0xa46, 1],
[0xa48, 0],
[0xa4a, 1],
[0xa4d, 0],
[0xa50, 1],
[0xa51, 0],
[0xa6f, 1],
[0xa71, 0],
[0xa74, 1],
[0xa75, 0],
[0xa80, 1],
[0xa82, 0],
[0xabb, 1],
[0xabc, 0],
[0xac0, 1],
[0xac5, 0],
[0xac6, 1],
[0xac8, 0],
[0xacc, 1],
[0xacd, 0],
[0xae1, 1],
[0xae3, 0],
[0xaf9, 1],
[0xaff, 0],
[0xb00, 1],
[0xb01, 0],
[0xb3b, 1],
[0xb3c, 0],
[0xb3e, 1],
[0xb3f, 0],
[0xb40, 1],
[0xb44, 0],
[0xb4c, 1],
[0xb4d, 0],
[0xb54, 1],
[0xb56, 0],
[0xb61, 1],
[0xb63, 0],
[0xb81, 1],
[0xb82, 0],
[0xbbf, 1],
[0xbc0, 0],
[0xbcc, 1],
[0xbcd, 0],
[0xbff, 1],
[0xc00, 0],
[0xc03, 1],
[0xc04, 0],
[0xc3b, 1],
[0xc3c, 0],
[0xc3d, 1],
[0xc40, 0],
[0xc45, 1],
[0xc48, 0],
[0xc49, 1],
[0xc4d, 0],
[0xc54, 1],
[0xc56, 0],
[0xc61, 1],
[0xc63, 0],
[0xc80, 1],
[0xc81, 0],
[0xcbb, 1],
[0xcbc, 0],
[0xcbe, 1],
[0xcbf, 0],
[0xcc5, 1],
[0xcc6, 0],
[0xccb, 1],
[0xccd, 0],
[0xce1, 1],
[0xce3, 0],
[0xcff, 1],
[0xd01, 0],
[0xd3a, 1],
[0xd3c, 0],
[0xd40, 1],
[0xd44, 0],
[0xd4c, 1],
[0xd4d, 0],
[0xd61, 1],
[0xd63, 0],
[0xd80, 1],
[0xd81, 0],
[0xdc9, 1],
[0xdca, 0],
[0xdd1, 1],
[0xdd4, 0],
[0xdd5, 1],
[0xdd6, 0],
[0xe30, 1],
[0xe31, 0],
[0xe33, 1],
[0xe3a, 0],
[0xe46, 1],
[0xe4e, 0],
[0xeb0, 1],
[0xeb1, 0],
[0xeb3, 1],
[0xebc, 0],
[0xec7, 1],
[0xece, 0],
[0xf17, 1],
[0xf19, 0],
[0xf34, 1],
[0xf35, 0],
[0xf36, 1],
[0xf37, 0],
[0xf38, 1],
[0xf39, 0],
[0xf70, 1],
[0xf7e, 0],
[0xf7f, 1],
[0xf84, 0],
[0xf85, 1],
[0xf87, 0],
[0xf8c, 1],
[0xf97, 0],
[0xf98, 1],
[0xfbc, 0],
[0xfc5, 1],
[0xfc6, 0],
[0x102c, 1],
[0x1030, 0],
[0x1031, 1],
[0x1037, 0],
[0x1038, 1],
[0x103a, 0],
[0x103c, 1],
[0x103e, 0],
[0x1057, 1],
[0x1059, 0],
[0x105d, 1],
[0x1060, 0],
[0x1070, 1],
[0x1074, 0],
[0x1081, 1],
[0x1082, 0],
[0x1084, 1],
[0x1086, 0],
[0x108c, 1],
[0x108d, 0],
[0x109c, 1],
[0x109d, 0],
[0x10ff, 1],
[0x115f, 2],
[0x135c, 1],
[0x135f, 0],
[0x1711, 1],
[0x1714, 0],
[0x1731, 1],
[0x1733, 0],
[0x1751, 1],
[0x1753, 0],
[0x1771, 1],
[0x1773, 0],
[0x17b3, 1],
[0x17b5, 0],
[0x17b6, 1],
[0x17bd, 0],
[0x17c5, 1],
[0x17c6, 0],
[0x17c8, 1],
[0x17d3, 0],
[0x17dc, 1],
[0x17dd, 0],
[0x180a, 1],
[0x180d, 0],
[0x180e, 1],
[0x180f, 0],
[0x1884, 1],
[0x1886, 0],
[0x18a8, 1],
[0x18a9, 0],
[0x191f, 1],
[0x1922, 0],
[0x1926, 1],
[0x1928, 0],
[0x1931, 1],
[0x1932, 0],
[0x1938, 1],
[0x193b, 0],
[0x1a16, 1],
[0x1a18, 0],
[0x1a1a, 1],
[0x1a1b, 0],
[0x1a55, 1],
[0x1a56, 0],
[0x1a57, 1],
[0x1a5e, 0],
[0x1a5f, 1],
[0x1a60, 0],
[0x1a61, 1],
[0x1a62, 0],
[0x1a64, 1],
[0x1a6c, 0],
[0x1a72, 1],
[0x1a7c, 0],
[0x1a7e, 1],
[0x1a7f, 0],
[0x1aaf, 1],
[0x1abd, 0],
[0x1abe, 1],
[0x1ace, 0],
[0x1aff, 1],
[0x1b03, 0],
[0x1b33, 1],
[0x1b34, 0],
[0x1b35, 1],
[0x1b3a, 0],
[0x1b3b, 1],
[0x1b3c, 0],
[0x1b41, 1],
[0x1b42, 0],
[0x1b6a, 1],
[0x1b73, 0],
[0x1b7f, 1],
[0x1b81, 0],
[0x1ba1, 1],
[0x1ba5, 0],
[0x1ba7, 1],
[0x1ba9, 0],
[0x1baa, 1],
[0x1bad, 0],
[0x1be5, 1],
[0x1be6, 0],
[0x1be7, 1],
[0x1be9, 0],
[0x1bec, 1],
[0x1bed, 0],
[0x1bee, 1],
[0x1bf1, 0],
[0x1c2b, 1],
[0x1c33, 0],
[0x1c35, 1],
[0x1c37, 0],
[0x1ccf, 1],
[0x1cd2, 0],
[0x1cd3, 1],
[0x1ce0, 0],
[0x1ce1, 1],
[0x1ce8, 0],
[0x1cec, 1],
[0x1ced, 0],
[0x1cf3, 1],
[0x1cf4, 0],
[0x1cf7, 1],
[0x1cf9, 0],
[0x1dbf, 1],
[0x1dff, 0],
[0x200f, 1],
[0x2010, -1],
[0x2012, 1],
[0x2016, -1],
[0x2017, 1],
[0x2019, -1],
[0x201b, 1],
[0x201d, -1],
[0x201f, 1],
[0x2022, -1],
[0x2023, 1],
[0x2027, -1],
[0x202f, 1],
[0x2030, -1],
[0x2031, 1],
[0x2033, -1],
[0x2034, 1],
[0x2035, -1],
[0x203a, 1],
[0x203b, -1],
[0x203d, 1],
[0x203e, -1],
[0x2073, 1],
[0x2074, -1],
[0x207e, 1],
[0x207f, -1],
[0x2080, 1],
[0x2084, -1],
[0x20ab, 1],
[0x20ac, -1],
[0x20cf, 1],
[0x20dc, 0],
[0x20e0, 1],
[0x20e1, 0],
[0x20e4, 1],
[0x20f0, 0],
[0x2102, 1],
[0x2103, -1],
[0x2104, 1],
[0x2105, -1],
[0x2108, 1],
[0x2109, -1],
[0x2112, 1],
[0x2113, -1],
[0x2115, 1],
[0x2116, -1],
[0x2120, 1],
[0x2122, -1],
[0x2125, 1],
[0x2126, -1],
[0x212a, 1],
[0x212b, -1],
[0x2152, 1],
[0x2154, -1],
[0x215a, 1],
[0x215e, -1],
[0x215f, 1],
[0x216b, -1],
[0x216f, 1],
[0x2179, -1],
[0x2188, 1],
[0x2189, -1],
[0x218f, 1],
[0x2199, -1],
[0x21b7, 1],
[0x21b9, -1],
[0x21d1, 1],
[0x21d2, -1],
[0x21d3, 1],
[0x21d4, -1],
[0x21e6, 1],
[0x21e7, -1],
[0x21ff, 1],
[0x2200, -1],
[0x2201, 1],
[0x2203, -1],
[0x2206, 1],
[0x2208, -1],
[0x220a, 1],
[0x220b, -1],
[0x220e, 1],
[0x220f, -1],
[0x2210, 1],
[0x2211, -1],
[0x2214, 1],
[0x2215, -1],
[0x2219, 1],
[0x221a, -1],
[0x221c, 1],
[0x2220, -1],
[0x2222, 1],
[0x2223, -1],
[0x2224, 1],
[0x2225, -1],
[0x2226, 1],
[0x222c, -1],
[0x222d, 1],
[0x222e, -1],
[0x2233, 1],
[0x2237, -1],
[0x223b, 1],
[0x223d, -1],
[0x2247, 1],
[0x2248, -1],
[0x224b, 1],
[0x224c, -1],
[0x2251, 1],
[0x2252, -1],
[0x225f, 1],
[0x2261, -1],
[0x2263, 1],
[0x2267, -1],
[0x2269, 1],
[0x226b, -1],
[0x226d, 1],
[0x226f, -1],
[0x2281, 1],
[0x2283, -1],
[0x2285, 1],
[0x2287, -1],
[0x2294, 1],
[0x2295, -1],
[0x2298, 1],
[0x2299, -1],
[0x22a4, 1],
[0x22a5, -1],
[0x22be, 1],
[0x22bf, -1],
[0x2311, 1],
[0x2312, -1],
[0x2319, 1],
[0x231b, 2],
[0x2328, 1],
[0x232a, 2],
[0x23e8, 1],
[0x23ec, 2],
[0x23ef, 1],
[0x23f0, 2],
[0x23f2, 1],
[0x23f3, 2],
[0x245f, 1],
[0x24e9, -1],
[0x24ea, 1],
[0x254b, -1],
[0x254f, 1],
[0x2573, -1],
[0x257f, 1],
[0x258f, -1],
[0x2591, 1],
[0x2595, -1],
[0x259f, 1],
[0x25a1, -1],
[0x25a2, 1],
[0x25a9, -1],
[0x25b1, 1],
[0x25b3, -1],
[0x25b5, 1],
[0x25b7, -1],
[0x25bb, 1],
[0x25bd, -1],
[0x25bf, 1],
[0x25c1, -1],
[0x25c5, 1],
[0x25c8, -1],
[0x25ca, 1],
[0x25cb, -1],
[0x25cd, 1],
[0x25d1, -1],
[0x25e1, 1],
[0x25e5, -1],
[0x25ee, 1],
[0x25ef, -1],
[0x25fc, 1],
[0x25fe, 2],
[0x2604, 1],
[0x2606, -1],
[0x2608, 1],
[0x2609, -1],
[0x260d, 1],
[0x260f, -1],
[0x2613, 1],
[0x2615, 2],
[0x261b, 1],
[0x261c, -1],
[0x261d, 1],
[0x261e, -1],
[0x263f, 1],
[0x2640, -1],
[0x2641, 1],
[0x2642, -1],
[0x2647, 1],
[0x2653, 2],
[0x265f, 1],
[0x2661, -1],
[0x2662, 1],
[0x2665, -1],
[0x2666, 1],
[0x266a, -1],
[0x266b, 1],
[0x266d, -1],
[0x266e, 1],
[0x266f, -1],
[0x267e, 1],
[0x267f, 2],
[0x2692, 1],
[0x2693, 2],
[0x269d, 1],
[0x269f, -1],
[0x26a0, 1],
[0x26a1, 2],
[0x26a9, 1],
[0x26ab, 2],
[0x26bc, 1],
[0x26be, 2],
[0x26bf, -1],
[0x26c3, 1],
[0x26c5, 2],
[0x26cd, -1],
[0x26ce, 2],
[0x26d3, -1],
[0x26d4, 2],
[0x26e1, -1],
[0x26e2, 1],
[0x26e3, -1],
[0x26e7, 1],
[0x26e9, -1],
[0x26ea, 2],
[0x26f1, -1],
[0x26f3, 2],
[0x26f4, -1],
[0x26f5, 2],
[0x26f9, -1],
[0x26fa, 2],
[0x26fc, -1],
[0x26fd, 2],
[0x26ff, -1],
[0x2704, 1],
[0x2705, 2],
[0x2709, 1],
[0x270b, 2],
[0x2727, 1],
[0x2728, 2],
[0x273c, 1],
[0x273d, -1],
[0x274b, 1],
[0x274c, 2],
[0x274d, 1],
[0x274e, 2],
[0x2752, 1],
[0x2755, 2],
[0x2756, 1],
[0x2757, 2],
[0x2775, 1],
[0x277f, -1],
[0x2794, 1],
[0x2797, 2],
[0x27af, 1],
[0x27b0, 2],
[0x27be, 1],
[0x27bf, 2],
[0x2b1a, 1],
[0x2b1c, 2],
[0x2b4f, 1],
[0x2b50, 2],
[0x2b54, 1],
[0x2b55, 2],
[0x2b59, -1],
[0x2cee, 1],
[0x2cf1, 0],
[0x2d7e, 1],
[0x2d7f, 0],
[0x2ddf, 1],
[0x2dff, 0],
[0x2e7f, 1],
[0x2e99, 2],
[0x2e9a, 1],
[0x2ef3, 2],
[0x2eff, 1],
[0x2fd5, 2],
[0x2fef, 1],
[0x3029, 2],
[0x302d, 0],
[0x303e, 2],
[0x3040, 1],
[0x3096, 2],
[0x3098, 1],
[0x309a, 0],
[0x30ff, 2],
[0x3104, 1],
[0x312f, 2],
[0x3130, 1],
[0x318e, 2],
[0x318f, 1],
[0x31e3, 2],
[0x31ee, 1],
[0x321e, 2],
[0x321f, 1],
[0x3247, 2],
[0x324f, -1],
[0x4dbf, 2],
[0x4dff, 1],
[0xa48c, 2],
[0xa48f, 1],
[0xa4c6, 2],
[0xa66e, 1],
[0xa66f, 0],
[0xa673, 1],
[0xa67d, 0],
[0xa69d, 1],
[0xa69f, 0],
[0xa6ef, 1],
[0xa6f1, 0],
[0xa801, 1],
[0xa802, 0],
[0xa805, 1],
[0xa806, 0],
[0xa80a, 1],
[0xa80b, 0],
[0xa824, 1],
[0xa826, 0],
[0xa82b, 1],
[0xa82c, 0],
[0xa8c3, 1],
[0xa8c5, 0],
[0xa8df, 1],
[0xa8f1, 0],
[0xa8fe, 1],
[0xa8ff, 0],
[0xa925, 1],
[0xa92d, 0],
[0xa946, 1],
[0xa951, 0],
[0xa95f, 1],
[0xa97c, 2],
[0xa97f, 1],
[0xa982, 0],
[0xa9b2, 1],
[0xa9b3, 0],
[0xa9b5, 1],
[0xa9b9, 0],
[0xa9bb, 1],
[0xa9bd, 0],
[0xa9e4, 1],
[0xa9e5, 0],
[0xaa28, 1],
[0xaa2e, 0],
[0xaa30, 1],
[0xaa32, 0],
[0xaa34, 1],
[0xaa36, 0],
[0xaa42, 1],
[0xaa43, 0],
[0xaa4b, 1],
[0xaa4c, 0],
[0xaa7b, 1],
[0xaa7c, 0],
[0xaaaf, 1],
[0xaab0, 0],
[0xaab1, 1],
[0xaab4, 0],
[0xaab6, 1],
[0xaab8, 0],
[0xaabd, 1],
[0xaabf, 0],
[0xaac0, 1],
[0xaac1, 0],
[0xaaeb, 1],
[0xaaed, 0],
[0xaaf5, 1],
[0xaaf6, 0],
[0xabe4, 1],
[0xabe5, 0],
[0xabe7, 1],
[0xabe8, 0],
[0xabec, 1],
[0xabed, 0],
[0xabff, 1],
[0xd7a3, 2],
[0xdfff, 1],
[0xf8ff, -1],
[0xfaff, 2],
[0xfb1d, 1],
[0xfb1e, 0],
[0xfdff, 1],
[0xfe0f, 0],
[0xfe19, 2],
[0xfe1f, 1],
[0xfe2f, 0],
[0xfe52, 2],
[0xfe53, 1],
[0xfe66, 2],
[0xfe67, 1],
[0xfe6b, 2],
[0xff00, 1],
[0xff60, 2],
[0xffdf, 1],
[0xffe6, 2],
[0xfffc, 1],
[0xfffd, -1],
[0x101fc, 1],
[0x101fd, 0],
[0x102df, 1],
[0x102e0, 0],
[0x10375, 1],
[0x1037a, 0],
[0x10a00, 1],
[0x10a03, 0],
[0x10a04, 1],
[0x10a06, 0],
[0x10a0b, 1],
[0x10a0f, 0],
[0x10a37, 1],
[0x10a3a, 0],
[0x10a3e, 1],
[0x10a3f, 0],
[0x10ae4, 1],
[0x10ae6, 0],
[0x10d23, 1],
[0x10d27, 0],
[0x10eaa, 1],
[0x10eac, 0],
[0x10efc, 1],
[0x10eff, 0],
[0x10f45, 1],
[0x10f50, 0],
[0x10f81, 1],
[0x10f85, 0],
[0x11000, 1],
[0x11001, 0],
[0x11037, 1],
[0x11046, 0],
[0x1106f, 1],
[0x11070, 0],
[0x11072, 1],
[0x11074, 0],
[0x1107e, 1],
[0x11081, 0],
[0x110b2, 1],
[0x110b6, 0],
[0x110b8, 1],
[0x110ba, 0],
[0x110c1, 1],
[0x110c2, 0],
[0x110ff, 1],
[0x11102, 0],
[0x11126, 1],
[0x1112b, 0],
[0x1112c, 1],
[0x11134, 0],
[0x11172, 1],
[0x11173, 0],
[0x1117f, 1],
[0x11181, 0],
[0x111b5, 1],
[0x111be, 0],
[0x111c8, 1],
[0x111cc, 0],
[0x111ce, 1],
[0x111cf, 0],
[0x1122e, 1],
[0x11231, 0],
[0x11233, 1],
[0x11234, 0],
[0x11235, 1],
[0x11237, 0],
[0x1123d, 1],
[0x1123e, 0],
[0x11240, 1],
[0x11241, 0],
[0x112de, 1],
[0x112df, 0],
[0x112e2, 1],
[0x112ea, 0],
[0x112ff, 1],
[0x11301, 0],
[0x1133a, 1],
[0x1133c, 0],
[0x1133f, 1],
[0x11340, 0],
[0x11365, 1],
[0x1136c, 0],
[0x1136f, 1],
[0x11374, 0],
[0x11437, 1],
[0x1143f, 0],
[0x11441, 1],
[0x11444, 0],
[0x11445, 1],
[0x11446, 0],
[0x1145d, 1],
[0x1145e, 0],
[0x114b2, 1],
[0x114b8, 0],
[0x114b9, 1],
[0x114ba, 0],
[0x114be, 1],
[0x114c0, 0],
[0x114c1, 1],
[0x114c3, 0],
[0x115b1, 1],
[0x115b5, 0],
[0x115bb, 1],
[0x115bd, 0],
[0x115be, 1],
[0x115c0, 0],
[0x115db, 1],
[0x115dd, 0],
[0x11632, 1],
[0x1163a, 0],
[0x1163c, 1],
[0x1163d, 0],
[0x1163e, 1],
[0x11640, 0],
[0x116aa, 1],
[0x116ab, 0],
[0x116ac, 1],
[0x116ad, 0],
[0x116af, 1],
[0x116b5, 0],
[0x116b6, 1],
[0x116b7, 0],
[0x1171c, 1],
[0x1171f, 0],
[0x11721, 1],
[0x11725, 0],
[0x11726, 1],
[0x1172b, 0],
[0x1182e, 1],
[0x11837, 0],
[0x11838, 1],
[0x1183a, 0],
[0x1193a, 1],
[0x1193c, 0],
[0x1193d, 1],
[0x1193e, 0],
[0x11942, 1],
[0x11943, 0],
[0x119d3, 1],
[0x119d7, 0],
[0x119d9, 1],
[0x119db, 0],
[0x119df, 1],
[0x119e0, 0],
[0x11a00, 1],
[0x11a0a, 0],
[0x11a32, 1],
[0x11a38, 0],
[0x11a3a, 1],
[0x11a3e, 0],
[0x11a46, 1],
[0x11a47, 0],
[0x11a50, 1],
[0x11a56, 0],
[0x11a58, 1],
[0x11a5b, 0],
[0x11a89, 1],
[0x11a96, 0],
[0x11a97, 1],
[0x11a99, 0],
[0x11c2f, 1],
[0x11c36, 0],
[0x11c37, 1],
[0x11c3d, 0],
[0x11c3e, 1],
[0x11c3f, 0],
[0x11c91, 1],
[0x11ca7, 0],
[0x11ca9, 1],
[0x11cb0, 0],
[0x11cb1, 1],
[0x11cb3, 0],
[0x11cb4, 1],
[0x11cb6, 0],
[0x11d30, 1],
[0x11d36, 0],
[0x11d39, 1],
[0x11d3a, 0],
[0x11d3b, 1],
[0x11d3d, 0],
[0x11d3e, 1],
[0x11d45, 0],
[0x11d46, 1],
[0x11d47, 0],
[0x11d8f, 1],
[0x11d91, 0],
[0x11d94, 1],
[0x11d95, 0],
[0x11d96, 1],
[0x11d97, 0],
[0x11ef2, 1],
[0x11ef4, 0],
[0x11eff, 1],
[0x11f01, 0],
[0x11f35, 1],
[0x11f3a, 0],
[0x11f3f, 1],
[0x11f40, 0],
[0x11f41, 1],
[0x11f42, 0],
[0x1343f, 1],
[0x13440, 0],
[0x13446, 1],
[0x13455, 0],
[0x16aef, 1],
[0x16af4, 0],
[0x16b2f, 1],
[0x16b36, 0],
[0x16f4e, 1],
[0x16f4f, 0],
[0x16f8e, 1],
[0x16f92, 0],
[0x16fdf, 1],
[0x16fe3, 2],
[0x16fe4, 0],
[0x16fef, 1],
[0x16ff1, 2],
[0x16fff, 1],
[0x187f7, 2],
[0x187ff, 1],
[0x18cd5, 2],
[0x18cff, 1],
[0x18d08, 2],
[0x1afef, 1],
[0x1aff3, 2],
[0x1aff4, 1],
[0x1affb, 2],
[0x1affc, 1],
[0x1affe, 2],
[0x1afff, 1],
[0x1b122, 2],
[0x1b131, 1],
[0x1b132, 2],
[0x1b14f, 1],
[0x1b152, 2],
[0x1b154, 1],
[0x1b155, 2],
[0x1b163, 1],
[0x1b167, 2],
[0x1b16f, 1],
[0x1b2fb, 2],
[0x1bc9c, 1],
[0x1bc9e, 0],
[0x1ceff, 1],
[0x1cf2d, 0],
[0x1cf2f, 1],
[0x1cf46, 0],
[0x1d166, 1],
[0x1d169, 0],
[0x1d17a, 1],
[0x1d182, 0],
[0x1d184, 1],
[0x1d18b, 0],
[0x1d1a9, 1],
[0x1d1ad, 0],
[0x1d241, 1],
[0x1d244, 0],
[0x1d9ff, 1],
[0x1da36, 0],
[0x1da3a, 1],
[0x1da6c, 0],
[0x1da74, 1],
[0x1da75, 0],
[0x1da83, 1],
[0x1da84, 0],
[0x1da9a, 1],
[0x1da9f, 0],
[0x1daa0, 1],
[0x1daaf, 0],
[0x1dfff, 1],
[0x1e006, 0],
[0x1e007, 1],
[0x1e018, 0],
[0x1e01a, 1],
[0x1e021, 0],
[0x1e022, 1],
[0x1e024, 0],
[0x1e025, 1],
[0x1e02a, 0],
[0x1e08e, 1],
[0x1e08f, 0],
[0x1e12f, 1],
[0x1e136, 0],
[0x1e2ad, 1],
[0x1e2ae, 0],
[0x1e2eb, 1],
[0x1e2ef, 0],
[0x1e4eb, 1],
[0x1e4ef, 0],
[0x1e8cf, 1],
[0x1e8d6, 0],
[0x1e943, 1],
[0x1e94a, 0],
[0x1f003, 1],
[0x1f004, 2],
[0x1f0ce, 1],
[0x1f0cf, 2],
[0x1f0ff, 1],
[0x1f10a, -1],
[0x1f10f, 1],
[0x1f12d, -1],
[0x1f12f, 1],
[0x1f169, -1],
[0x1f16f, 1],
[0x1f18d, -1],
[0x1f18e, 2],
[0x1f190, -1],
[0x1f19a, 2],
[0x1f1ac, -1],
[0x1f1ff, 1],
[0x1f202, 2],
[0x1f20f, 1],
[0x1f23b, 2],
[0x1f23f, 1],
[0x1f248, 2],
[0x1f24f, 1],
[0x1f251, 2],
[0x1f25f, 1],
[0x1f265, 2],
[0x1f2ff, 1],
[0x1f320, 2],
[0x1f32c, 1],
[0x1f335, 2],
[0x1f336, 1],
[0x1f37c, 2],
[0x1f37d, 1],
[0x1f393, 2],
[0x1f39f, 1],
[0x1f3ca, 2],
[0x1f3ce, 1],
[0x1f3d3, 2],
[0x1f3df, 1],
[0x1f3f0, 2],
[0x1f3f3, 1],
[0x1f3f4, 2],
[0x1f3f7, 1],
[0x1f43e, 2],
[0x1f43f, 1],
[0x1f440, 2],
[0x1f441, 1],
[0x1f4fc, 2],
[0x1f4fe, 1],
[0x1f53d, 2],
[0x1f54a, 1],
[0x1f54e, 2],
[0x1f54f, 1],
[0x1f567, 2],
[0x1f579, 1],
[0x1f57a, 2],
[0x1f594, 1],
[0x1f596, 2],
[0x1f5a3, 1],
[0x1f5a4, 2],
[0x1f5fa, 1],
[0x1f64f, 2],
[0x1f67f, 1],
[0x1f6c5, 2],
[0x1f6cb, 1],
[0x1f6cc, 2],
[0x1f6cf, 1],
[0x1f6d2, 2],
[0x1f6d4, 1],
[0x1f6d7, 2],
[0x1f6db, 1],
[0x1f6df, 2],
[0x1f6ea, 1],
[0x1f6ec, 2],
[0x1f6f3, 1],
[0x1f6fc, 2],
[0x1f7df, 1],
[0x1f7eb, 2],
[0x1f7ef, 1],
[0x1f7f0, 2],
[0x1f90b, 1],
[0x1f93a, 2],
[0x1f93b, 1],
[0x1f945, 2],
[0x1f946, 1],
[0x1f9ff, 2],
[0x1fa6f, 1],
[0x1fa7c, 2],
[0x1fa7f, 1],
[0x1fa88, 2],
[0x1fa8f, 1],
[0x1fabd, 2],
[0x1fabe, 1],
[0x1fac5, 2],
[0x1facd, 1],
[0x1fadb, 2],
[0x1fadf, 1],
[0x1fae8, 2],
[0x1faef, 1],
[0x1faf8, 2],
[0x1ffff, 1],
[0x2fffd, 2],
[0x2ffff, 1],
[0x3fffd, 2],
[0xe00ff, 1],
[0xe01ef, 0],
[0xeffff, 1],
[0xffffd, -1],
[0xfffff, 1],
[0x10fffd, -1],
[0x7fffffff, 1]
].transpose.map(&:freeze)
end
share/ruby/reline/face.rb 0000644 00000011444 15173517736 0011353 0 ustar 00 # frozen_string_literal: true
class Reline::Face
SGR_PARAMETERS = {
foreground: {
black: 30,
red: 31,
green: 32,
yellow: 33,
blue: 34,
magenta: 35,
cyan: 36,
white: 37,
bright_black: 90,
gray: 90,
bright_red: 91,
bright_green: 92,
bright_yellow: 93,
bright_blue: 94,
bright_magenta: 95,
bright_cyan: 96,
bright_white: 97
},
background: {
black: 40,
red: 41,
green: 42,
yellow: 43,
blue: 44,
magenta: 45,
cyan: 46,
white: 47,
bright_black: 100,
gray: 100,
bright_red: 101,
bright_green: 102,
bright_yellow: 103,
bright_blue: 104,
bright_magenta: 105,
bright_cyan: 106,
bright_white: 107,
},
style: {
reset: 0,
bold: 1,
faint: 2,
italicized: 3,
underlined: 4,
slowly_blinking: 5,
blinking: 5,
rapidly_blinking: 6,
negative: 7,
concealed: 8,
crossed_out: 9
}
}.freeze
class Config
ESSENTIAL_DEFINE_NAMES = %i(default enhanced scrollbar).freeze
RESET_SGR = "\e[0m".freeze
def initialize(name, &block)
@definition = {}
block.call(self)
ESSENTIAL_DEFINE_NAMES.each do |name|
@definition[name] ||= { style: :reset, escape_sequence: RESET_SGR }
end
end
attr_reader :definition
def define(name, **values)
values[:escape_sequence] = format_to_sgr(values.to_a).freeze
@definition[name] = values
end
def reconfigure
@definition.each_value do |values|
values.delete(:escape_sequence)
values[:escape_sequence] = format_to_sgr(values.to_a).freeze
end
end
def [](name)
@definition.dig(name, :escape_sequence) or raise ArgumentError, "unknown face: #{name}"
end
private
def sgr_rgb(key, value)
return nil unless rgb_expression?(value)
if Reline::Face.truecolor?
sgr_rgb_truecolor(key, value)
else
sgr_rgb_256color(key, value)
end
end
def sgr_rgb_truecolor(key, value)
case key
when :foreground
"38;2;"
when :background
"48;2;"
end + value[1, 6].scan(/../).map(&:hex).join(";")
end
def sgr_rgb_256color(key, value)
# 256 colors are
# 0..15: standard colors, high intensity colors
# 16..232: 216 colors (R, G, B each 6 steps)
# 233..255: grayscale colors (24 steps)
# This methods converts rgb_expression to 216 colors
rgb = value[1, 6].scan(/../).map(&:hex)
# Color steps are [0, 95, 135, 175, 215, 255]
r, g, b = rgb.map { |v| v <= 95 ? v / 48 : (v - 35) / 40 }
color = (16 + 36 * r + 6 * g + b)
case key
when :foreground
"38;5;#{color}"
when :background
"48;5;#{color}"
end
end
def format_to_sgr(ordered_values)
sgr = "\e[" + ordered_values.map do |key_value|
key, value = key_value
case key
when :foreground, :background
case value
when Symbol
SGR_PARAMETERS[key][value]
when String
sgr_rgb(key, value)
end
when :style
[ value ].flatten.map do |style_name|
SGR_PARAMETERS[:style][style_name]
end.then do |sgr_parameters|
sgr_parameters.include?(nil) ? nil : sgr_parameters
end
end.then do |rendition_expression|
unless rendition_expression
raise ArgumentError, "invalid SGR parameter: #{value.inspect}"
end
rendition_expression
end
end.join(';') + "m"
sgr == RESET_SGR ? RESET_SGR : RESET_SGR + sgr
end
def rgb_expression?(color)
color.respond_to?(:match?) and color.match?(/\A#[0-9a-fA-F]{6}\z/)
end
end
private_constant :SGR_PARAMETERS, :Config
def self.truecolor?
@force_truecolor || %w[truecolor 24bit].include?(ENV['COLORTERM'])
end
def self.force_truecolor
@force_truecolor = true
@configs&.each_value(&:reconfigure)
end
def self.[](name)
@configs[name]
end
def self.config(name, &block)
@configs ||= {}
@configs[name] = Config.new(name, &block)
end
def self.configs
@configs.transform_values(&:definition)
end
def self.load_initial_configs
config(:default) do |conf|
conf.define :default, style: :reset
conf.define :enhanced, style: :reset
conf.define :scrollbar, style: :reset
end
config(:completion_dialog) do |conf|
conf.define :default, foreground: :bright_white, background: :gray
conf.define :enhanced, foreground: :black, background: :white
conf.define :scrollbar, foreground: :white, background: :gray
end
end
def self.reset_to_initial_configs
@configs = {}
load_initial_configs
end
end
share/ruby/reline/key_actor.rb 0000644 00000000316 15173517736 0012431 0 ustar 00 module Reline::KeyActor
end
require 'reline/key_actor/base'
require 'reline/key_actor/composite'
require 'reline/key_actor/emacs'
require 'reline/key_actor/vi_command'
require 'reline/key_actor/vi_insert'
share/ruby/reline/key_stroke.rb 0000644 00000005541 15173517736 0012635 0 ustar 00 class Reline::KeyStroke
ESC_BYTE = 27
CSI_PARAMETER_BYTES_RANGE = 0x30..0x3f
CSI_INTERMEDIATE_BYTES_RANGE = (0x20..0x2f)
def initialize(config)
@config = config
end
# Input exactly matches to a key sequence
MATCHING = :matching
# Input partially matches to a key sequence
MATCHED = :matched
# Input matches to a key sequence and the key sequence is a prefix of another key sequence
MATCHING_MATCHED = :matching_matched
# Input does not match to any key sequence
UNMATCHED = :unmatched
def match_status(input)
matching = key_mapping.matching?(input)
matched = key_mapping.get(input)
# FIXME: Workaround for single byte. remove this after MAPPING is merged into KeyActor.
matched ||= input.size == 1
matching ||= input == [ESC_BYTE]
if matching && matched
MATCHING_MATCHED
elsif matching
MATCHING
elsif matched
MATCHED
elsif input[0] == ESC_BYTE
match_unknown_escape_sequence(input, vi_mode: @config.editing_mode_is?(:vi_insert, :vi_command))
elsif input.size == 1
MATCHED
else
UNMATCHED
end
end
def expand(input)
matched_bytes = nil
(1..input.size).each do |i|
bytes = input.take(i)
status = match_status(bytes)
matched_bytes = bytes if status == MATCHED || status == MATCHING_MATCHED
end
return [[], []] unless matched_bytes
func = key_mapping.get(matched_bytes)
if func.is_a?(Array)
keys = func.map { |c| Reline::Key.new(c, c, false) }
elsif func
keys = [Reline::Key.new(func, func, false)]
elsif matched_bytes.size == 1
keys = [Reline::Key.new(matched_bytes.first, matched_bytes.first, false)]
elsif matched_bytes.size == 2 && matched_bytes[0] == ESC_BYTE
keys = [Reline::Key.new(matched_bytes[1], matched_bytes[1] | 0b10000000, true)]
else
keys = []
end
[keys, input.drop(matched_bytes.size)]
end
private
# returns match status of CSI/SS3 sequence and matched length
def match_unknown_escape_sequence(input, vi_mode: false)
idx = 0
return UNMATCHED unless input[idx] == ESC_BYTE
idx += 1
idx += 1 if input[idx] == ESC_BYTE
case input[idx]
when nil
if idx == 1 # `ESC`
return MATCHING_MATCHED
else # `ESC ESC`
return MATCHING
end
when 91 # == '['.ord
# CSI sequence `ESC [ ... char`
idx += 1
idx += 1 while idx < input.size && CSI_PARAMETER_BYTES_RANGE.cover?(input[idx])
idx += 1 while idx < input.size && CSI_INTERMEDIATE_BYTES_RANGE.cover?(input[idx])
when 79 # == 'O'.ord
# SS3 sequence `ESC O char`
idx += 1
else
# `ESC char` or `ESC ESC char`
return UNMATCHED if vi_mode
end
case input.size
when idx
MATCHING
when idx + 1
MATCHED
else
UNMATCHED
end
end
def key_mapping
@config.key_bindings
end
end
share/ruby/reline/terminfo.rb 0000644 00000011071 15173517736 0012274 0 ustar 00 begin
# Ignore warning `Add fiddle to your Gemfile or gemspec` in Ruby 3.4.
# terminfo.rb and ansi.rb supports fiddle unavailable environment.
verbose, $VERBOSE = $VERBOSE, nil
require 'fiddle'
require 'fiddle/import'
rescue LoadError
module Reline::Terminfo
def self.curses_dl
false
end
end
ensure
$VERBOSE = verbose
end
module Reline::Terminfo
extend Fiddle::Importer
class TerminfoError < StandardError; end
def self.curses_dl_files
case RUBY_PLATFORM
when /mingw/, /mswin/
# aren't supported
[]
when /cygwin/
%w[cygncursesw-10.dll cygncurses-10.dll]
when /darwin/
%w[libncursesw.dylib libcursesw.dylib libncurses.dylib libcurses.dylib]
else
%w[libncursesw.so libcursesw.so libncurses.so libcurses.so]
end
end
@curses_dl = false
def self.curses_dl
return @curses_dl unless @curses_dl == false
if Fiddle.const_defined?(:TYPE_VARIADIC)
curses_dl_files.each do |curses_name|
result = Fiddle::Handle.new(curses_name)
rescue Fiddle::DLError
next
else
@curses_dl = result
break
end
end
@curses_dl = nil if @curses_dl == false
@curses_dl
end
end if not Reline.const_defined?(:Terminfo) or not Reline::Terminfo.respond_to?(:curses_dl)
module Reline::Terminfo
dlload curses_dl
#extern 'int setupterm(char *term, int fildes, int *errret)'
@setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
#extern 'char *tigetstr(char *capname)'
@tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
begin
#extern 'char *tiparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
rescue Fiddle::DLError
# OpenBSD lacks tiparm
#extern 'char *tparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)
end
begin
#extern 'int tigetflag(char *str)'
@tigetflag = Fiddle::Function.new(curses_dl['tigetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
rescue Fiddle::DLError
# OpenBSD lacks tigetflag
#extern 'int tgetflag(char *str)'
@tigetflag = Fiddle::Function.new(curses_dl['tgetflag'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
end
begin
#extern 'int tigetnum(char *str)'
@tigetnum = Fiddle::Function.new(curses_dl['tigetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
rescue Fiddle::DLError
# OpenBSD lacks tigetnum
#extern 'int tgetnum(char *str)'
@tigetnum = Fiddle::Function.new(curses_dl['tgetnum'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
end
def self.setupterm(term, fildes)
errret_int = Fiddle::Pointer.malloc(Fiddle::SIZEOF_INT, Fiddle::RUBY_FREE)
ret = @setupterm.(term, fildes, errret_int)
case ret
when 0 # OK
@term_supported = true
when -1 # ERR
@term_supported = false
end
end
class StringWithTiparm < String
def tiparm(*args) # for method chain
Reline::Terminfo.tiparm(self, *args)
end
end
def self.tigetstr(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
capability = @tigetstr.(capname)
case capability.to_i
when 0, -1
raise TerminfoError, "can't find capability: #{capname}"
end
StringWithTiparm.new(capability.to_s)
end
def self.tiparm(str, *args)
new_args = []
args.each do |a|
new_args << Fiddle::TYPE_INT << a
end
@tiparm.(str, *new_args).to_s
end
def self.tigetflag(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
flag = @tigetflag.(capname).to_i
case flag
when -1
raise TerminfoError, "not boolean capability: #{capname}"
when 0
raise TerminfoError, "can't find capability: #{capname}"
end
flag
end
def self.tigetnum(capname)
raise TerminfoError, "capname is not String: #{capname.inspect}" unless capname.is_a?(String)
num = @tigetnum.(capname).to_i
case num
when -2
raise TerminfoError, "not numeric capability: #{capname}"
when -1
raise TerminfoError, "can't find capability: #{capname}"
end
num
end
# NOTE: This means Fiddle and curses are enabled.
def self.enabled?
true
end
def self.term_supported?
@term_supported
end
end if Reline::Terminfo.curses_dl
module Reline::Terminfo
def self.enabled?
false
end
end unless Reline::Terminfo.curses_dl
share/ruby/reline/version.rb 0000644 00000000047 15173517736 0012137 0 ustar 00 module Reline
VERSION = '0.5.10'
end
share/ruby/reline/line_editor.rb 0000644 00000242662 15173517736 0012762 0 ustar 00 require 'reline/kill_ring'
require 'reline/unicode'
require 'tempfile'
class Reline::LineEditor
# TODO: Use "private alias_method" idiom after drop Ruby 2.5.
attr_reader :byte_pointer
attr_accessor :confirm_multiline_termination_proc
attr_accessor :completion_proc
attr_accessor :completion_append_character
attr_accessor :output_modifier_proc
attr_accessor :prompt_proc
attr_accessor :auto_indent_proc
attr_accessor :dig_perfect_match_proc
attr_writer :output
VI_MOTIONS = %i{
ed_prev_char
ed_next_char
vi_zero
ed_move_to_beg
ed_move_to_end
vi_to_column
vi_next_char
vi_prev_char
vi_next_word
vi_prev_word
vi_to_next_char
vi_to_prev_char
vi_end_word
vi_next_big_word
vi_prev_big_word
vi_end_big_word
}
module CompletionState
NORMAL = :normal
COMPLETION = :completion
MENU = :menu
MENU_WITH_PERFECT_MATCH = :menu_with_perfect_match
PERFECT_MATCH = :perfect_match
end
RenderedScreen = Struct.new(:base_y, :lines, :cursor_y, keyword_init: true)
CompletionJourneyState = Struct.new(:line_index, :pre, :target, :post, :list, :pointer)
NullActionState = [nil, nil].freeze
class MenuInfo
attr_reader :list
def initialize(list)
@list = list
end
def lines(screen_width)
return [] if @list.empty?
list = @list.sort
sizes = list.map { |item| Reline::Unicode.calculate_width(item) }
item_width = sizes.max + 2
num_cols = [screen_width / item_width, 1].max
num_rows = list.size.fdiv(num_cols).ceil
list_with_padding = list.zip(sizes).map { |item, size| item + ' ' * (item_width - size) }
aligned = (list_with_padding + [nil] * (num_rows * num_cols - list_with_padding.size)).each_slice(num_rows).to_a.transpose
aligned.map do |row|
row.join.rstrip
end
end
end
MINIMUM_SCROLLBAR_HEIGHT = 1
def initialize(config, encoding)
@config = config
@completion_append_character = ''
@screen_size = [0, 0] # Should be initialized with actual winsize in LineEditor#reset
reset_variables(encoding: encoding)
end
def io_gate
Reline::IOGate
end
def set_pasting_state(in_pasting)
# While pasting, text to be inserted is stored to @continuous_insertion_buffer.
# After pasting, this buffer should be force inserted.
process_insert(force: true) if @in_pasting && !in_pasting
@in_pasting = in_pasting
end
private def check_mode_string
if @config.show_mode_in_prompt
if @config.editing_mode_is?(:vi_command)
@config.vi_cmd_mode_string
elsif @config.editing_mode_is?(:vi_insert)
@config.vi_ins_mode_string
elsif @config.editing_mode_is?(:emacs)
@config.emacs_mode_string
else
'?'
end
end
end
private def check_multiline_prompt(buffer, mode_string)
if @vi_arg
prompt = "(arg: #{@vi_arg}) "
elsif @searching_prompt
prompt = @searching_prompt
else
prompt = @prompt
end
if !@is_multiline
mode_string = check_mode_string
prompt = mode_string + prompt if mode_string
[prompt] + [''] * (buffer.size - 1)
elsif @prompt_proc
prompt_list = @prompt_proc.(buffer).map { |pr| pr.gsub("\n", "\\n") }
prompt_list.map!{ prompt } if @vi_arg or @searching_prompt
prompt_list = [prompt] if prompt_list.empty?
prompt_list = prompt_list.map{ |pr| mode_string + pr } if mode_string
prompt = prompt_list[@line_index]
prompt = prompt_list[0] if prompt.nil?
prompt = prompt_list.last if prompt.nil?
if buffer.size > prompt_list.size
(buffer.size - prompt_list.size).times do
prompt_list << prompt_list.last
end
end
prompt_list
else
prompt = mode_string + prompt if mode_string
[prompt] * buffer.size
end
end
def reset(prompt = '', encoding:)
@screen_size = Reline::IOGate.get_screen_size
reset_variables(prompt, encoding: encoding)
@rendered_screen.base_y = Reline::IOGate.cursor_pos.y
if ENV.key?('RELINE_ALT_SCROLLBAR')
@full_block = '::'
@upper_half_block = "''"
@lower_half_block = '..'
@block_elem_width = 2
elsif Reline::IOGate.win?
@full_block = '█'
@upper_half_block = '▀'
@lower_half_block = '▄'
@block_elem_width = 1
elsif @encoding == Encoding::UTF_8
@full_block = '█'
@upper_half_block = '▀'
@lower_half_block = '▄'
@block_elem_width = Reline::Unicode.calculate_width('█')
else
@full_block = '::'
@upper_half_block = "''"
@lower_half_block = '..'
@block_elem_width = 2
end
end
def handle_signal
handle_interrupted
handle_resized
end
private def handle_resized
return unless @resized
@screen_size = Reline::IOGate.get_screen_size
@resized = false
scroll_into_view
Reline::IOGate.move_cursor_up @rendered_screen.cursor_y
@rendered_screen.base_y = Reline::IOGate.cursor_pos.y
clear_rendered_screen_cache
render
end
private def handle_interrupted
return unless @interrupted
@interrupted = false
clear_dialogs
render
cursor_to_bottom_offset = @rendered_screen.lines.size - @rendered_screen.cursor_y
Reline::IOGate.scroll_down cursor_to_bottom_offset
Reline::IOGate.move_cursor_column 0
clear_rendered_screen_cache
case @old_trap
when 'DEFAULT', 'SYSTEM_DEFAULT'
raise Interrupt
when 'IGNORE'
# Do nothing
when 'EXIT'
exit
else
@old_trap.call if @old_trap.respond_to?(:call)
end
end
def set_signal_handlers
Reline::IOGate.set_winch_handler do
@resized = true
end
@old_trap = Signal.trap('INT') do
@interrupted = true
end
end
def finalize
Signal.trap('INT', @old_trap)
end
def eof?
@eof
end
def reset_variables(prompt = '', encoding:)
@prompt = prompt.gsub("\n", "\\n")
@mark_pointer = nil
@encoding = encoding
@is_multiline = false
@finished = false
@history_pointer = nil
@kill_ring ||= Reline::KillRing.new
@vi_clipboard = ''
@vi_arg = nil
@waiting_proc = nil
@vi_waiting_operator = nil
@vi_waiting_operator_arg = nil
@completion_journey_state = nil
@completion_state = CompletionState::NORMAL
@perfect_matched = nil
@menu_info = nil
@searching_prompt = nil
@just_cursor_moving = false
@eof = false
@continuous_insertion_buffer = String.new(encoding: @encoding)
@scroll_partial_screen = 0
@drop_terminate_spaces = false
@in_pasting = false
@auto_indent_proc = nil
@dialogs = []
@interrupted = false
@resized = false
@cache = {}
@rendered_screen = RenderedScreen.new(base_y: 0, lines: [], cursor_y: 0)
@input_lines = [[[""], 0, 0]]
@input_lines_position = 0
@undoing = false
@prev_action_state = NullActionState
@next_action_state = NullActionState
reset_line
end
def reset_line
@byte_pointer = 0
@buffer_of_lines = [String.new(encoding: @encoding)]
@line_index = 0
@cache.clear
@line_backup_in_history = nil
@multibyte_buffer = String.new(encoding: 'ASCII-8BIT')
end
def multiline_on
@is_multiline = true
end
def multiline_off
@is_multiline = false
end
private def insert_new_line(cursor_line, next_line)
@buffer_of_lines.insert(@line_index + 1, String.new(next_line, encoding: @encoding))
@buffer_of_lines[@line_index] = cursor_line
@line_index += 1
@byte_pointer = 0
if @auto_indent_proc && !@in_pasting
if next_line.empty?
(
# For compatibility, use this calculation instead of just `process_auto_indent @line_index - 1, cursor_dependent: false`
indent1 = @auto_indent_proc.(@buffer_of_lines.take(@line_index - 1).push(''), @line_index - 1, 0, true)
indent2 = @auto_indent_proc.(@buffer_of_lines.take(@line_index), @line_index - 1, @buffer_of_lines[@line_index - 1].bytesize, false)
indent = indent2 || indent1
@buffer_of_lines[@line_index - 1] = ' ' * indent + @buffer_of_lines[@line_index - 1].gsub(/\A\s*/, '')
)
process_auto_indent @line_index, add_newline: true
else
process_auto_indent @line_index - 1, cursor_dependent: false
process_auto_indent @line_index, add_newline: true # Need for compatibility
process_auto_indent @line_index, cursor_dependent: false
end
end
end
private def split_by_width(str, max_width, offset: 0)
Reline::Unicode.split_by_width(str, max_width, @encoding, offset: offset)
end
def current_byte_pointer_cursor
calculate_width(current_line.byteslice(0, @byte_pointer))
end
private def calculate_nearest_cursor(cursor)
line_to_calc = current_line
new_cursor_max = calculate_width(line_to_calc)
new_cursor = 0
new_byte_pointer = 0
height = 1
max_width = screen_width
if @config.editing_mode_is?(:vi_command)
last_byte_size = Reline::Unicode.get_prev_mbchar_size(line_to_calc, line_to_calc.bytesize)
if last_byte_size > 0
last_mbchar = line_to_calc.byteslice(line_to_calc.bytesize - last_byte_size, last_byte_size)
last_width = Reline::Unicode.get_mbchar_width(last_mbchar)
end_of_line_cursor = new_cursor_max - last_width
else
end_of_line_cursor = new_cursor_max
end
else
end_of_line_cursor = new_cursor_max
end
line_to_calc.grapheme_clusters.each do |gc|
mbchar = gc.encode(Encoding::UTF_8)
mbchar_width = Reline::Unicode.get_mbchar_width(mbchar)
now = new_cursor + mbchar_width
if now > end_of_line_cursor or now > cursor
break
end
new_cursor += mbchar_width
if new_cursor > max_width * height
height += 1
end
new_byte_pointer += gc.bytesize
end
@byte_pointer = new_byte_pointer
end
def with_cache(key, *deps)
cached_deps, value = @cache[key]
if cached_deps != deps
@cache[key] = [deps, value = yield(*deps, cached_deps, value)]
end
value
end
def modified_lines
with_cache(__method__, whole_lines, finished?) do |whole, complete|
modify_lines(whole, complete)
end
end
def prompt_list
with_cache(__method__, whole_lines, check_mode_string, @vi_arg, @searching_prompt) do |lines, mode_string|
check_multiline_prompt(lines, mode_string)
end
end
def screen_height
@screen_size.first
end
def screen_width
@screen_size.last
end
def screen_scroll_top
@scroll_partial_screen
end
def wrapped_prompt_and_input_lines
with_cache(__method__, @buffer_of_lines.size, modified_lines, prompt_list, screen_width) do |n, lines, prompts, width, prev_cache_key, cached_value|
prev_n, prev_lines, prev_prompts, prev_width = prev_cache_key
cached_wraps = {}
if prev_width == width
prev_n.times do |i|
cached_wraps[[prev_prompts[i], prev_lines[i]]] = cached_value[i]
end
end
n.times.map do |i|
prompt = prompts[i] || ''
line = lines[i] || ''
if (cached = cached_wraps[[prompt, line]])
next cached
end
*wrapped_prompts, code_line_prompt = split_by_width(prompt, width).first.compact
wrapped_lines = split_by_width(line, width, offset: calculate_width(code_line_prompt, true)).first.compact
wrapped_prompts.map { |p| [p, ''] } + [[code_line_prompt, wrapped_lines.first]] + wrapped_lines.drop(1).map { |c| ['', c] }
end
end
end
def calculate_overlay_levels(overlay_levels)
levels = []
overlay_levels.each do |x, w, l|
levels.fill(l, x, w)
end
levels
end
def render_line_differential(old_items, new_items)
old_levels = calculate_overlay_levels(old_items.zip(new_items).each_with_index.map {|((x, w, c), (nx, _nw, nc)), i| [x, w, c == nc && x == nx ? i : -1] if x }.compact)
new_levels = calculate_overlay_levels(new_items.each_with_index.map { |(x, w), i| [x, w, i] if x }.compact).take(screen_width)
base_x = 0
new_levels.zip(old_levels).chunk { |n, o| n == o ? :skip : n || :blank }.each do |level, chunk|
width = chunk.size
if level == :skip
# do nothing
elsif level == :blank
Reline::IOGate.move_cursor_column base_x
@output.write "#{Reline::IOGate.reset_color_sequence}#{' ' * width}"
else
x, w, content = new_items[level]
cover_begin = base_x != 0 && new_levels[base_x - 1] == level
cover_end = new_levels[base_x + width] == level
pos = 0
unless x == base_x && w == width
content, pos = Reline::Unicode.take_mbchar_range(content, base_x - x, width, cover_begin: cover_begin, cover_end: cover_end, padding: true)
end
Reline::IOGate.move_cursor_column x + pos
@output.write "#{Reline::IOGate.reset_color_sequence}#{content}#{Reline::IOGate.reset_color_sequence}"
end
base_x += width
end
if old_levels.size > new_levels.size
Reline::IOGate.move_cursor_column new_levels.size
Reline::IOGate.erase_after_cursor
end
end
# Calculate cursor position in word wrapped content.
def wrapped_cursor_position
prompt_width = calculate_width(prompt_list[@line_index], true)
line_before_cursor = whole_lines[@line_index].byteslice(0, @byte_pointer)
wrapped_line_before_cursor = split_by_width(' ' * prompt_width + line_before_cursor, screen_width).first.compact
wrapped_cursor_y = wrapped_prompt_and_input_lines[0...@line_index].sum(&:size) + wrapped_line_before_cursor.size - 1
wrapped_cursor_x = calculate_width(wrapped_line_before_cursor.last)
[wrapped_cursor_x, wrapped_cursor_y]
end
def clear_dialogs
@dialogs.each do |dialog|
dialog.contents = nil
dialog.trap_key = nil
end
end
def update_dialogs(key = nil)
wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
@dialogs.each do |dialog|
dialog.trap_key = nil
update_each_dialog(dialog, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top, key)
end
end
def render_finished
render_differential([], 0, 0)
lines = @buffer_of_lines.size.times.map do |i|
line = prompt_list[i] + modified_lines[i]
wrapped_lines, = split_by_width(line, screen_width)
wrapped_lines.last.empty? ? "#{line} " : line
end
@output.puts lines.map { |l| "#{l}\r\n" }.join
end
def print_nomultiline_prompt
# Readline's test `TestRelineAsReadline#test_readline` requires first output to be prompt, not cursor reset escape sequence.
@output.write @prompt if @prompt && !@is_multiline
end
def render
wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
new_lines = wrapped_prompt_and_input_lines.flatten(1)[screen_scroll_top, screen_height].map do |prompt, line|
prompt_width = Reline::Unicode.calculate_width(prompt, true)
[[0, prompt_width, prompt], [prompt_width, Reline::Unicode.calculate_width(line, true), line]]
end
if @menu_info
@menu_info.lines(screen_width).each do |item|
new_lines << [[0, Reline::Unicode.calculate_width(item), item]]
end
@menu_info = nil # TODO: do not change state here
end
@dialogs.each_with_index do |dialog, index|
next unless dialog.contents
x_range, y_range = dialog_range dialog, wrapped_cursor_y - screen_scroll_top
y_range.each do |row|
next if row < 0 || row >= screen_height
dialog_rows = new_lines[row] ||= []
# index 0 is for prompt, index 1 is for line, index 2.. is for dialog
dialog_rows[index + 2] = [x_range.begin, dialog.width, dialog.contents[row - y_range.begin]]
end
end
render_differential new_lines, wrapped_cursor_x, wrapped_cursor_y - screen_scroll_top
end
# Reflects lines to be rendered and new cursor position to the screen
# by calculating the difference from the previous render.
private def render_differential(new_lines, new_cursor_x, new_cursor_y)
rendered_lines = @rendered_screen.lines
cursor_y = @rendered_screen.cursor_y
if new_lines != rendered_lines
# Hide cursor while rendering to avoid cursor flickering.
Reline::IOGate.hide_cursor
num_lines = [[new_lines.size, rendered_lines.size].max, screen_height].min
if @rendered_screen.base_y + num_lines > screen_height
Reline::IOGate.scroll_down(num_lines - cursor_y - 1)
@rendered_screen.base_y = screen_height - num_lines
cursor_y = num_lines - 1
end
num_lines.times do |i|
rendered_line = rendered_lines[i] || []
line_to_render = new_lines[i] || []
next if rendered_line == line_to_render
Reline::IOGate.move_cursor_down i - cursor_y
cursor_y = i
unless rendered_lines[i]
Reline::IOGate.move_cursor_column 0
Reline::IOGate.erase_after_cursor
end
render_line_differential(rendered_line, line_to_render)
end
@rendered_screen.lines = new_lines
Reline::IOGate.show_cursor
end
Reline::IOGate.move_cursor_column new_cursor_x
Reline::IOGate.move_cursor_down new_cursor_y - cursor_y
@rendered_screen.cursor_y = new_cursor_y
end
private def clear_rendered_screen_cache
@rendered_screen.lines = []
@rendered_screen.cursor_y = 0
end
def upper_space_height(wrapped_cursor_y)
wrapped_cursor_y - screen_scroll_top
end
def rest_height(wrapped_cursor_y)
screen_height - wrapped_cursor_y + screen_scroll_top - @rendered_screen.base_y - 1
end
def rerender
render unless @in_pasting
end
class DialogProcScope
CompletionJourneyData = Struct.new(:preposing, :postposing, :list, :pointer)
def initialize(line_editor, config, proc_to_exec, context)
@line_editor = line_editor
@config = config
@proc_to_exec = proc_to_exec
@context = context
@cursor_pos = Reline::CursorPos.new
end
def context
@context
end
def retrieve_completion_block(set_completion_quote_character = false)
@line_editor.retrieve_completion_block(set_completion_quote_character)
end
def call_completion_proc_with_checking_args(pre, target, post)
@line_editor.call_completion_proc_with_checking_args(pre, target, post)
end
def set_dialog(dialog)
@dialog = dialog
end
def dialog
@dialog
end
def set_cursor_pos(col, row)
@cursor_pos.x = col
@cursor_pos.y = row
end
def set_key(key)
@key = key
end
def key
@key
end
def cursor_pos
@cursor_pos
end
def just_cursor_moving
@line_editor.instance_variable_get(:@just_cursor_moving)
end
def screen_width
@line_editor.screen_width
end
def screen_height
@line_editor.screen_height
end
def preferred_dialog_height
_wrapped_cursor_x, wrapped_cursor_y = @line_editor.wrapped_cursor_position
[@line_editor.upper_space_height(wrapped_cursor_y), @line_editor.rest_height(wrapped_cursor_y), (screen_height + 6) / 5].max
end
def completion_journey_data
@line_editor.dialog_proc_scope_completion_journey_data
end
def config
@config
end
def call
instance_exec(&@proc_to_exec)
end
end
class Dialog
attr_reader :name, :contents, :width
attr_accessor :scroll_top, :pointer, :column, :vertical_offset, :trap_key
def initialize(name, config, proc_scope)
@name = name
@config = config
@proc_scope = proc_scope
@width = nil
@scroll_top = 0
@trap_key = nil
end
def set_cursor_pos(col, row)
@proc_scope.set_cursor_pos(col, row)
end
def width=(v)
@width = v
end
def contents=(contents)
@contents = contents
if contents and @width.nil?
@width = contents.map{ |line| Reline::Unicode.calculate_width(line, true) }.max
end
end
def call(key)
@proc_scope.set_dialog(self)
@proc_scope.set_key(key)
dialog_render_info = @proc_scope.call
if @trap_key
if @trap_key.any?{ |i| i.is_a?(Array) } # multiple trap
@trap_key.each do |t|
@config.add_oneshot_key_binding(t, @name)
end
else
@config.add_oneshot_key_binding(@trap_key, @name)
end
end
dialog_render_info
end
end
def add_dialog_proc(name, p, context = nil)
dialog = Dialog.new(name, @config, DialogProcScope.new(self, @config, p, context))
if index = @dialogs.find_index { |d| d.name == name }
@dialogs[index] = dialog
else
@dialogs << dialog
end
end
DIALOG_DEFAULT_HEIGHT = 20
private def dialog_range(dialog, dialog_y)
x_range = dialog.column...dialog.column + dialog.width
y_range = dialog_y + dialog.vertical_offset...dialog_y + dialog.vertical_offset + dialog.contents.size
[x_range, y_range]
end
private def update_each_dialog(dialog, cursor_column, cursor_row, key = nil)
dialog.set_cursor_pos(cursor_column, cursor_row)
dialog_render_info = dialog.call(key)
if dialog_render_info.nil? or dialog_render_info.contents.nil? or dialog_render_info.contents.empty?
dialog.contents = nil
dialog.trap_key = nil
return
end
contents = dialog_render_info.contents
pointer = dialog.pointer
if dialog_render_info.width
dialog.width = dialog_render_info.width
else
dialog.width = contents.map { |l| calculate_width(l, true) }.max
end
height = dialog_render_info.height || DIALOG_DEFAULT_HEIGHT
height = contents.size if contents.size < height
if contents.size > height
if dialog.pointer
if dialog.pointer < 0
dialog.scroll_top = 0
elsif (dialog.pointer - dialog.scroll_top) >= (height - 1)
dialog.scroll_top = dialog.pointer - (height - 1)
elsif (dialog.pointer - dialog.scroll_top) < 0
dialog.scroll_top = dialog.pointer
end
pointer = dialog.pointer - dialog.scroll_top
else
dialog.scroll_top = 0
end
contents = contents[dialog.scroll_top, height]
end
if dialog_render_info.scrollbar and dialog_render_info.contents.size > height
bar_max_height = height * 2
moving_distance = (dialog_render_info.contents.size - height) * 2
position_ratio = dialog.scroll_top.zero? ? 0.0 : ((dialog.scroll_top * 2).to_f / moving_distance)
bar_height = (bar_max_height * ((contents.size * 2).to_f / (dialog_render_info.contents.size * 2))).floor.to_i
bar_height = MINIMUM_SCROLLBAR_HEIGHT if bar_height < MINIMUM_SCROLLBAR_HEIGHT
scrollbar_pos = ((bar_max_height - bar_height) * position_ratio).floor.to_i
else
scrollbar_pos = nil
end
dialog.column = dialog_render_info.pos.x
dialog.width += @block_elem_width if scrollbar_pos
diff = (dialog.column + dialog.width) - screen_width
if diff > 0
dialog.column -= diff
end
if rest_height(screen_scroll_top + cursor_row) - dialog_render_info.pos.y >= height
dialog.vertical_offset = dialog_render_info.pos.y + 1
elsif cursor_row >= height
dialog.vertical_offset = dialog_render_info.pos.y - height
else
dialog.vertical_offset = dialog_render_info.pos.y + 1
end
if dialog.column < 0
dialog.column = 0
dialog.width = screen_width
end
face = Reline::Face[dialog_render_info.face || :default]
scrollbar_sgr = face[:scrollbar]
default_sgr = face[:default]
enhanced_sgr = face[:enhanced]
dialog.contents = contents.map.with_index do |item, i|
line_sgr = i == pointer ? enhanced_sgr : default_sgr
str_width = dialog.width - (scrollbar_pos.nil? ? 0 : @block_elem_width)
str, = Reline::Unicode.take_mbchar_range(item, 0, str_width, padding: true)
colored_content = "#{line_sgr}#{str}"
if scrollbar_pos
if scrollbar_pos <= (i * 2) and (i * 2 + 1) < (scrollbar_pos + bar_height)
colored_content + scrollbar_sgr + @full_block
elsif scrollbar_pos <= (i * 2) and (i * 2) < (scrollbar_pos + bar_height)
colored_content + scrollbar_sgr + @upper_half_block
elsif scrollbar_pos <= (i * 2 + 1) and (i * 2) < (scrollbar_pos + bar_height)
colored_content + scrollbar_sgr + @lower_half_block
else
colored_content + scrollbar_sgr + ' ' * @block_elem_width
end
else
colored_content
end
end
end
private def modify_lines(before, complete)
if after = @output_modifier_proc&.call("#{before.join("\n")}\n", complete: complete)
after.lines("\n").map { |l| l.chomp('') }
else
before.map { |l| Reline::Unicode.escape_for_print(l) }
end
end
def editing_mode
@config.editing_mode
end
private def menu(_target, list)
@menu_info = MenuInfo.new(list)
end
private def complete_internal_proc(list, is_menu)
preposing, target, postposing = retrieve_completion_block
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 perform_completion(list, just_show_list)
case @completion_state
when CompletionState::NORMAL
@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
perform_completion(list, true) if @config.show_all_if_ambiguous
end
@perfect_matched = completed
else
@completion_state = CompletionState::MENU
perform_completion(list, true) if @config.show_all_if_ambiguous
end
if not just_show_list and target < completed
@buffer_of_lines[@line_index] = (preposing + completed + completion_append_character.to_s + postposing).split("\n")[@line_index] || String.new(encoding: @encoding)
line_to_pointer = (preposing + completed + completion_append_character.to_s).split("\n")[@line_index] || String.new(encoding: @encoding)
@byte_pointer = line_to_pointer.bytesize
end
end
end
def dialog_proc_scope_completion_journey_data
return nil unless @completion_journey_state
line_index = @completion_journey_state.line_index
pre_lines = @buffer_of_lines[0...line_index].map { |line| line + "\n" }
post_lines = @buffer_of_lines[(line_index + 1)..-1].map { |line| line + "\n" }
DialogProcScope::CompletionJourneyData.new(
pre_lines.join + @completion_journey_state.pre,
@completion_journey_state.post + post_lines.join,
@completion_journey_state.list,
@completion_journey_state.pointer
)
end
private def move_completed_list(direction)
@completion_journey_state ||= retrieve_completion_journey_state
return false unless @completion_journey_state
if (delta = { up: -1, down: +1 }[direction])
@completion_journey_state.pointer = (@completion_journey_state.pointer + delta) % @completion_journey_state.list.size
end
completed = @completion_journey_state.list[@completion_journey_state.pointer]
set_current_line(@completion_journey_state.pre + completed + @completion_journey_state.post, @completion_journey_state.pre.bytesize + completed.bytesize)
true
end
private def retrieve_completion_journey_state
preposing, target, postposing = retrieve_completion_block
list = call_completion_proc
return unless list.is_a?(Array)
candidates = list.select{ |item| item.start_with?(target) }
return if candidates.empty?
pre = preposing.split("\n", -1).last || ''
post = postposing.split("\n", -1).first || ''
CompletionJourneyState.new(
@line_index, pre, target, post, [target] + candidates, 0
)
end
private def run_for_operators(key, method_symbol, &block)
if @vi_waiting_operator
if VI_MOTIONS.include?(method_symbol)
old_byte_pointer = @byte_pointer
@vi_arg = (@vi_arg || 1) * @vi_waiting_operator_arg
block.(true)
unless @waiting_proc
byte_pointer_diff = @byte_pointer - old_byte_pointer
@byte_pointer = old_byte_pointer
method_obj = method(@vi_waiting_operator)
wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
cleanup_waiting
end
else
# Ignores operator when not motion is given.
block.(false)
cleanup_waiting
end
@vi_arg = nil
else
block.(false)
end
end
private def argumentable?(method_obj)
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :arg }
end
private def inclusive?(method_obj)
# If a motion method with the keyword argument "inclusive" follows the
# operator, it must contain the character at the cursor position.
method_obj and method_obj.parameters.any? { |param| param[0] == :key and param[1] == :inclusive }
end
def wrap_method_call(method_symbol, method_obj, key, with_operator = false)
if @config.editing_mode_is?(:emacs, :vi_insert) and @vi_waiting_operator.nil?
not_insertion = method_symbol != :ed_insert
process_insert(force: not_insertion)
end
if @vi_arg and argumentable?(method_obj)
if with_operator and inclusive?(method_obj)
method_obj.(key, arg: @vi_arg, inclusive: true)
else
method_obj.(key, arg: @vi_arg)
end
else
if with_operator and inclusive?(method_obj)
method_obj.(key, inclusive: true)
else
method_obj.(key)
end
end
end
private def cleanup_waiting
@waiting_proc = nil
@vi_waiting_operator = nil
@vi_waiting_operator_arg = nil
@searching_prompt = nil
@drop_terminate_spaces = false
end
private def process_key(key, method_symbol)
if key.is_a?(Symbol)
cleanup_waiting
elsif @waiting_proc
old_byte_pointer = @byte_pointer
@waiting_proc.call(key)
if @vi_waiting_operator
byte_pointer_diff = @byte_pointer - old_byte_pointer
@byte_pointer = old_byte_pointer
method_obj = method(@vi_waiting_operator)
wrap_method_call(@vi_waiting_operator, method_obj, byte_pointer_diff)
cleanup_waiting
end
@kill_ring.process
return
end
if method_symbol and respond_to?(method_symbol, true)
method_obj = method(method_symbol)
end
if method_symbol and key.is_a?(Symbol)
if @vi_arg and argumentable?(method_obj)
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
else
wrap_method_call(method_symbol, method_obj, key) if method_obj
end
@kill_ring.process
if @vi_arg
@vi_arg = nil
end
elsif @vi_arg
if key.chr =~ /[0-9]/
ed_argument_digit(key)
else
if argumentable?(method_obj)
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
elsif method_obj
wrap_method_call(method_symbol, method_obj, key)
else
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
end
@kill_ring.process
if @vi_arg
@vi_arg = nil
end
end
elsif method_obj
if method_symbol == :ed_argument_digit
wrap_method_call(method_symbol, method_obj, key)
else
run_for_operators(key, method_symbol) do |with_operator|
wrap_method_call(method_symbol, method_obj, key, with_operator)
end
end
@kill_ring.process
else
ed_insert(key) unless @config.editing_mode_is?(:vi_command)
end
end
private def normal_char(key)
@multibyte_buffer << key.combined_char
if @multibyte_buffer.size > 1
if @multibyte_buffer.dup.force_encoding(@encoding).valid_encoding?
process_key(@multibyte_buffer.dup.force_encoding(@encoding), nil)
@multibyte_buffer.clear
else
# invalid
return
end
else # single byte
return if key.char >= 128 # maybe, first byte of multi byte
method_symbol = @config.editing_mode.get_method(key.combined_char)
process_key(key.combined_char, method_symbol)
@multibyte_buffer.clear
end
if @config.editing_mode_is?(:vi_command) and @byte_pointer > 0 and @byte_pointer == current_line.bytesize
byte_size = Reline::Unicode.get_prev_mbchar_size(@buffer_of_lines[@line_index], @byte_pointer)
@byte_pointer -= byte_size
end
end
def update(key)
modified = input_key(key)
unless @in_pasting
scroll_into_view
@just_cursor_moving = !modified
update_dialogs(key)
@just_cursor_moving = false
end
end
def input_key(key)
save_old_buffer
@config.reset_oneshot_key_bindings
@dialogs.each do |dialog|
if key.char.instance_of?(Symbol) and key.char == dialog.name
return
end
end
if key.char.nil?
process_insert(force: true)
@eof = buffer_empty?
finish
return
end
@completion_occurs = false
if key.char.is_a?(Symbol)
process_key(key.char, key.char)
else
normal_char(key)
end
@prev_action_state, @next_action_state = @next_action_state, NullActionState
unless @completion_occurs
@completion_state = CompletionState::NORMAL
@completion_journey_state = nil
end
push_input_lines unless @undoing
@undoing = false
if @in_pasting
clear_dialogs
return
end
modified = @old_buffer_of_lines != @buffer_of_lines
if !@completion_occurs && modified && !@config.disable_completion && @config.autocompletion
# Auto complete starts only when edited
process_insert(force: true)
@completion_journey_state = retrieve_completion_journey_state
end
modified
end
def save_old_buffer
@old_buffer_of_lines = @buffer_of_lines.dup
end
def push_input_lines
if @old_buffer_of_lines == @buffer_of_lines
@input_lines[@input_lines_position] = [@buffer_of_lines.dup, @byte_pointer, @line_index]
else
@input_lines = @input_lines[0..@input_lines_position]
@input_lines_position += 1
@input_lines.push([@buffer_of_lines.dup, @byte_pointer, @line_index])
end
trim_input_lines
end
MAX_INPUT_LINES = 100
def trim_input_lines
if @input_lines.size > MAX_INPUT_LINES
@input_lines.shift
@input_lines_position -= 1
end
end
def scroll_into_view
_wrapped_cursor_x, wrapped_cursor_y = wrapped_cursor_position
if wrapped_cursor_y < screen_scroll_top
@scroll_partial_screen = wrapped_cursor_y
end
if wrapped_cursor_y >= screen_scroll_top + screen_height
@scroll_partial_screen = wrapped_cursor_y - screen_height + 1
end
end
def call_completion_proc
result = retrieve_completion_block(true)
pre, target, post = result
result = call_completion_proc_with_checking_args(pre, target, post)
Reline.core.instance_variable_set(:@completion_quote_character, nil)
result
end
def call_completion_proc_with_checking_args(pre, target, post)
if @completion_proc and target
argnum = @completion_proc.parameters.inject(0) { |result, item|
case item.first
when :req, :opt
result + 1
when :rest
break 3
end
}
case argnum
when 1
result = @completion_proc.(target)
when 2
result = @completion_proc.(target, pre)
when 3..Float::INFINITY
result = @completion_proc.(target, pre, post)
end
end
result
end
private def process_auto_indent(line_index = @line_index, cursor_dependent: true, add_newline: false)
return if @in_pasting
return unless @auto_indent_proc
line = @buffer_of_lines[line_index]
byte_pointer = cursor_dependent && @line_index == line_index ? @byte_pointer : line.bytesize
new_indent = @auto_indent_proc.(@buffer_of_lines.take(line_index + 1).push(''), line_index, byte_pointer, add_newline)
return unless new_indent
new_line = ' ' * new_indent + line.lstrip
@buffer_of_lines[line_index] = new_line
if @line_index == line_index
indent_diff = new_line.bytesize - line.bytesize
@byte_pointer = [@byte_pointer + indent_diff, 0].max
end
end
def line()
@buffer_of_lines.join("\n") unless eof?
end
def current_line
@buffer_of_lines[@line_index]
end
def set_current_line(line, byte_pointer = nil)
cursor = current_byte_pointer_cursor
@buffer_of_lines[@line_index] = line
if byte_pointer
@byte_pointer = byte_pointer
else
calculate_nearest_cursor(cursor)
end
process_auto_indent
end
def set_current_lines(lines, byte_pointer = nil, line_index = 0)
cursor = current_byte_pointer_cursor
@buffer_of_lines = lines
@line_index = line_index
if byte_pointer
@byte_pointer = byte_pointer
else
calculate_nearest_cursor(cursor)
end
process_auto_indent
end
def retrieve_completion_block(set_completion_quote_character = false)
if Reline.completer_word_break_characters.empty?
word_break_regexp = nil
else
word_break_regexp = /\A[#{Regexp.escape(Reline.completer_word_break_characters)}]/
end
if Reline.completer_quote_characters.empty?
quote_characters_regexp = nil
else
quote_characters_regexp = /\A[#{Regexp.escape(Reline.completer_quote_characters)}]/
end
before = current_line.byteslice(0, @byte_pointer)
rest = nil
break_pointer = nil
quote = nil
closing_quote = nil
escaped_quote = nil
i = 0
while i < @byte_pointer do
slice = current_line.byteslice(i, @byte_pointer - i)
unless slice.valid_encoding?
i += 1
next
end
if quote and slice.start_with?(closing_quote)
quote = nil
i += 1
rest = nil
elsif quote and slice.start_with?(escaped_quote)
# skip
i += 2
elsif quote_characters_regexp and slice =~ quote_characters_regexp # find new "
rest = $'
quote = $&
closing_quote = /(?!\\)#{Regexp.escape(quote)}/
escaped_quote = /\\#{Regexp.escape(quote)}/
i += 1
break_pointer = i - 1
elsif word_break_regexp and not quote and slice =~ word_break_regexp
rest = $'
i += 1
before = current_line.byteslice(i, @byte_pointer - i)
break_pointer = i
else
i += 1
end
end
postposing = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
if rest
preposing = current_line.byteslice(0, break_pointer)
target = rest
if set_completion_quote_character and quote
Reline.core.instance_variable_set(:@completion_quote_character, quote)
if postposing !~ /(?!\\)#{Regexp.escape(quote)}/ # closing quote
insert_text(quote)
end
end
else
preposing = ''
if break_pointer
preposing = current_line.byteslice(0, break_pointer)
else
preposing = ''
end
target = before
end
lines = whole_lines
if @line_index > 0
preposing = lines[0..(@line_index - 1)].join("\n") + "\n" + preposing
end
if (lines.size - 1) > @line_index
postposing = postposing + "\n" + lines[(@line_index + 1)..-1].join("\n")
end
[preposing.encode(@encoding), target.encode(@encoding), postposing.encode(@encoding)]
end
def confirm_multiline_termination
temp_buffer = @buffer_of_lines.dup
@confirm_multiline_termination_proc.(temp_buffer.join("\n") + "\n")
end
def insert_multiline_text(text)
save_old_buffer
pre = @buffer_of_lines[@line_index].byteslice(0, @byte_pointer)
post = @buffer_of_lines[@line_index].byteslice(@byte_pointer..)
lines = (pre + text.gsub(/\r\n?/, "\n") + post).split("\n", -1)
lines << '' if lines.empty?
@buffer_of_lines[@line_index, 1] = lines
@line_index += lines.size - 1
@byte_pointer = @buffer_of_lines[@line_index].bytesize - post.bytesize
push_input_lines
end
def insert_text(text)
if @buffer_of_lines[@line_index].bytesize == @byte_pointer
@buffer_of_lines[@line_index] += text
else
@buffer_of_lines[@line_index] = byteinsert(@buffer_of_lines[@line_index], @byte_pointer, text)
end
@byte_pointer += text.bytesize
process_auto_indent
end
def delete_text(start = nil, length = nil)
if start.nil? and length.nil?
if @buffer_of_lines.size == 1
@buffer_of_lines[@line_index] = ''
@byte_pointer = 0
elsif @line_index == (@buffer_of_lines.size - 1) and @line_index > 0
@buffer_of_lines.pop
@line_index -= 1
@byte_pointer = 0
elsif @line_index < (@buffer_of_lines.size - 1)
@buffer_of_lines.delete_at(@line_index)
@byte_pointer = 0
end
elsif not start.nil? and not length.nil?
if current_line
before = current_line.byteslice(0, start)
after = current_line.byteslice(start + length, current_line.bytesize)
set_current_line(before + after)
end
elsif start.is_a?(Range)
range = start
first = range.first
last = range.last
last = current_line.bytesize - 1 if last > current_line.bytesize
last += current_line.bytesize if last < 0
first += current_line.bytesize if first < 0
range = range.exclude_end? ? first...last : first..last
line = current_line.bytes.reject.with_index{ |c, i| range.include?(i) }.map{ |c| c.chr(Encoding::ASCII_8BIT) }.join.force_encoding(@encoding)
set_current_line(line)
else
set_current_line(current_line.byteslice(0, start))
end
end
def byte_pointer=(val)
@byte_pointer = val
end
def whole_lines
@buffer_of_lines.dup
end
def whole_buffer
whole_lines.join("\n")
end
private def buffer_empty?
current_line.empty? and @buffer_of_lines.size == 1
end
def finished?
@finished
end
def finish
@finished = true
@config.reset
end
private def byteslice!(str, byte_pointer, size)
new_str = str.byteslice(0, byte_pointer)
new_str << str.byteslice(byte_pointer + size, str.bytesize)
[new_str, str.byteslice(byte_pointer, size)]
end
private def byteinsert(str, byte_pointer, other)
new_str = str.byteslice(0, byte_pointer)
new_str << other
new_str << str.byteslice(byte_pointer, str.bytesize)
new_str
end
private def calculate_width(str, allow_escape_code = false)
Reline::Unicode.calculate_width(str, allow_escape_code)
end
private def key_delete(key)
if @config.editing_mode_is?(:vi_insert)
ed_delete_next_char(key)
elsif @config.editing_mode_is?(:emacs)
em_delete(key)
end
end
private def key_newline(key)
if @is_multiline
next_line = current_line.byteslice(@byte_pointer, current_line.bytesize - @byte_pointer)
cursor_line = current_line.byteslice(0, @byte_pointer)
insert_new_line(cursor_line, next_line)
end
end
private def complete(_key)
return if @config.disable_completion
process_insert(force: true)
if @config.autocompletion
@completion_state = CompletionState::NORMAL
@completion_occurs = move_completed_list(:down)
else
@completion_journey_state = nil
result = call_completion_proc
if result.is_a?(Array)
@completion_occurs = true
perform_completion(result, false)
end
end
end
private def completion_journey_move(direction)
return if @config.disable_completion
process_insert(force: true)
@completion_state = CompletionState::NORMAL
@completion_occurs = move_completed_list(direction)
end
private def menu_complete(_key)
completion_journey_move(:down)
end
private def menu_complete_backward(_key)
completion_journey_move(:up)
end
private def completion_journey_up(_key)
completion_journey_move(:up) if @config.autocompletion
end
# Editline:: +ed-unassigned+ This editor command always results in an error.
# GNU Readline:: There is no corresponding macro.
private def ed_unassigned(key) end # do nothing
private def process_insert(force: false)
return if @continuous_insertion_buffer.empty? or (@in_pasting and not force)
insert_text(@continuous_insertion_buffer)
@continuous_insertion_buffer.clear
end
# Editline:: +ed-insert+ (vi input: almost all; emacs: printable characters)
# In insert mode, insert the input character left of the cursor
# position. In replace mode, overwrite the character at the
# cursor and move the cursor to the right by one character
# position. Accept an argument to do this repeatedly. It is an
# error if the input character is the NUL character (+Ctrl-@+).
# Failure to enlarge the edit buffer also results in an error.
# Editline:: +ed-digit+ (emacs: 0 to 9) If in argument input mode, append
# the input digit to the argument being read. Otherwise, call
# +ed-insert+. It is an error if the input character is not a
# digit or if the existing argument is already greater than a
# million.
# GNU Readline:: +self-insert+ (a, b, A, 1, !, …) Insert yourself.
private def ed_insert(key)
if key.instance_of?(String)
begin
key.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
str = key
else
begin
key.chr.encode(Encoding::UTF_8)
rescue Encoding::UndefinedConversionError
return
end
str = key.chr
end
if @in_pasting
@continuous_insertion_buffer << str
return
elsif not @continuous_insertion_buffer.empty?
process_insert
end
insert_text(str)
end
alias_method :ed_digit, :ed_insert
alias_method :self_insert, :ed_insert
private def ed_quoted_insert(str, arg: 1)
@waiting_proc = proc { |key|
arg.times do
if key == "\C-j".ord or key == "\C-m".ord
key_newline(key)
elsif key == 0
# Ignore NUL.
else
ed_insert(key)
end
end
@waiting_proc = nil
}
end
alias_method :quoted_insert, :ed_quoted_insert
private def ed_next_char(key, arg: 1)
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if (@byte_pointer < current_line.bytesize)
@byte_pointer += byte_size
elsif @config.editing_mode_is?(:emacs) and @byte_pointer == current_line.bytesize and @line_index < @buffer_of_lines.size - 1
@byte_pointer = 0
@line_index += 1
end
arg -= 1
ed_next_char(key, arg: arg) if arg > 0
end
alias_method :forward_char, :ed_next_char
private def ed_prev_char(key, arg: 1)
if @byte_pointer > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
elsif @config.editing_mode_is?(:emacs) and @byte_pointer == 0 and @line_index > 0
@line_index -= 1
@byte_pointer = current_line.bytesize
end
arg -= 1
ed_prev_char(key, arg: arg) if arg > 0
end
alias_method :backward_char, :ed_prev_char
private def vi_first_print(key)
@byte_pointer, = Reline::Unicode.vi_first_print(current_line)
end
private def ed_move_to_beg(key)
@byte_pointer = 0
end
alias_method :beginning_of_line, :ed_move_to_beg
alias_method :vi_zero, :ed_move_to_beg
private def ed_move_to_end(key)
@byte_pointer = current_line.bytesize
end
alias_method :end_of_line, :ed_move_to_end
private def generate_searcher(search_key)
search_word = String.new(encoding: @encoding)
multibyte_buf = String.new(encoding: 'ASCII-8BIT')
hit_pointer = nil
lambda do |key|
search_again = false
case key
when "\C-h".ord, "\C-?".ord
grapheme_clusters = search_word.grapheme_clusters
if grapheme_clusters.size > 0
grapheme_clusters.pop
search_word = grapheme_clusters.join
end
when "\C-r".ord, "\C-s".ord
search_again = true if search_key == key
search_key = key
else
multibyte_buf << key
if multibyte_buf.dup.force_encoding(@encoding).valid_encoding?
search_word << multibyte_buf.dup.force_encoding(@encoding)
multibyte_buf.clear
end
end
hit = nil
if not search_word.empty? and @line_backup_in_history&.include?(search_word)
hit_pointer = Reline::HISTORY.size
hit = @line_backup_in_history
else
if search_again
if search_word.empty? and Reline.last_incremental_search
search_word = Reline.last_incremental_search
end
if @history_pointer
case search_key
when "\C-r".ord
history_pointer_base = 0
history = Reline::HISTORY[0..(@history_pointer - 1)]
when "\C-s".ord
history_pointer_base = @history_pointer + 1
history = Reline::HISTORY[(@history_pointer + 1)..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
elsif @history_pointer
case search_key
when "\C-r".ord
history_pointer_base = 0
history = Reline::HISTORY[0..@history_pointer]
when "\C-s".ord
history_pointer_base = @history_pointer
history = Reline::HISTORY[@history_pointer..-1]
end
else
history_pointer_base = 0
history = Reline::HISTORY
end
case search_key
when "\C-r".ord
hit_index = history.rindex { |item|
item.include?(search_word)
}
when "\C-s".ord
hit_index = history.index { |item|
item.include?(search_word)
}
end
if hit_index
hit_pointer = history_pointer_base + hit_index
hit = Reline::HISTORY[hit_pointer]
end
end
case search_key
when "\C-r".ord
prompt_name = 'reverse-i-search'
when "\C-s".ord
prompt_name = 'i-search'
end
prompt_name = "failed #{prompt_name}" unless hit
[search_word, prompt_name, hit_pointer]
end
end
private def incremental_search_history(key)
unless @history_pointer
@line_backup_in_history = whole_buffer
end
searcher = generate_searcher(key)
@searching_prompt = "(reverse-i-search)`': "
termination_keys = ["\C-j".ord]
termination_keys.concat(@config.isearch_terminators&.chars&.map(&:ord)) if @config.isearch_terminators
@waiting_proc = ->(k) {
case k
when *termination_keys
if @history_pointer
buffer = Reline::HISTORY[@history_pointer]
else
buffer = @line_backup_in_history
end
@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
@searching_prompt = nil
@waiting_proc = nil
@byte_pointer = 0
when "\C-g".ord
@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
move_history(nil, line: :end, cursor: :end, save_buffer: false)
@searching_prompt = nil
@waiting_proc = nil
@byte_pointer = 0
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
search_word, prompt_name, hit_pointer = searcher.call(k)
Reline.last_incremental_search = search_word
@searching_prompt = "(%s)`%s'" % [prompt_name, search_word]
@searching_prompt += ': ' unless @is_multiline
move_history(hit_pointer, line: :end, cursor: :end, save_buffer: false) if hit_pointer
else
if @history_pointer
line = Reline::HISTORY[@history_pointer]
else
line = @line_backup_in_history
end
@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
@searching_prompt = nil
@waiting_proc = nil
@byte_pointer = 0
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 search_history(prefix, pointer_range)
pointer_range.each do |pointer|
lines = Reline::HISTORY[pointer].split("\n")
lines.each_with_index do |line, index|
return [pointer, index] if line.start_with?(prefix)
end
end
nil
end
private def ed_search_prev_history(key, arg: 1)
substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
return if @history_pointer == 0
return if @history_pointer.nil? && substr.empty? && !current_line.empty?
history_range = 0...(@history_pointer || Reline::HISTORY.size)
h_pointer, line_index = search_history(substr, history_range.reverse_each)
return unless h_pointer
move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
arg -= 1
set_next_action_state(:search_history, :empty) if substr.empty?
ed_search_prev_history(key, arg: arg) if arg > 0
end
alias_method :history_search_backward, :ed_search_prev_history
private def ed_search_next_history(key, arg: 1)
substr = prev_action_state_value(:search_history) == :empty ? '' : current_line.byteslice(0, @byte_pointer)
return if @history_pointer.nil?
history_range = @history_pointer + 1...Reline::HISTORY.size
h_pointer, line_index = search_history(substr, history_range)
return if h_pointer.nil? and not substr.empty?
move_history(h_pointer, line: line_index || :start, cursor: substr.empty? ? :end : @byte_pointer)
arg -= 1
set_next_action_state(:search_history, :empty) if substr.empty?
ed_search_next_history(key, arg: arg) if arg > 0
end
alias_method :history_search_forward, :ed_search_next_history
private def move_history(history_pointer, line:, cursor:, save_buffer: true)
history_pointer ||= Reline::HISTORY.size
return if history_pointer < 0 || history_pointer > Reline::HISTORY.size
old_history_pointer = @history_pointer || Reline::HISTORY.size
if old_history_pointer == Reline::HISTORY.size
@line_backup_in_history = save_buffer ? whole_buffer : ''
else
Reline::HISTORY[old_history_pointer] = whole_buffer if save_buffer
end
if history_pointer == Reline::HISTORY.size
buf = @line_backup_in_history
@history_pointer = @line_backup_in_history = nil
else
buf = Reline::HISTORY[history_pointer]
@history_pointer = history_pointer
end
@buffer_of_lines = buf.split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = line == :start ? 0 : line == :end ? @buffer_of_lines.size - 1 : line
@byte_pointer = cursor == :start ? 0 : cursor == :end ? current_line.bytesize : cursor
end
private def ed_prev_history(key, arg: 1)
if @line_index > 0
cursor = current_byte_pointer_cursor
@line_index -= 1
calculate_nearest_cursor(cursor)
return
end
move_history(
(@history_pointer || Reline::HISTORY.size) - 1,
line: :end,
cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
)
arg -= 1
ed_prev_history(key, arg: arg) if arg > 0
end
alias_method :previous_history, :ed_prev_history
private def ed_next_history(key, arg: 1)
if @line_index < (@buffer_of_lines.size - 1)
cursor = current_byte_pointer_cursor
@line_index += 1
calculate_nearest_cursor(cursor)
return
end
move_history(
(@history_pointer || Reline::HISTORY.size) + 1,
line: :start,
cursor: @config.editing_mode_is?(:vi_command) ? :start : :end,
)
arg -= 1
ed_next_history(key, arg: arg) if arg > 0
end
alias_method :next_history, :ed_next_history
private def ed_newline(key)
process_insert(force: true)
if @is_multiline
if @config.editing_mode_is?(:vi_command)
if @line_index < (@buffer_of_lines.size - 1)
ed_next_history(key) # means cursor down
else
# should check confirm_multiline_termination to finish?
finish
end
else
if @line_index == (@buffer_of_lines.size - 1)
if confirm_multiline_termination
finish
else
key_newline(key)
end
else
# should check confirm_multiline_termination to finish?
@line_index = @buffer_of_lines.size - 1
@byte_pointer = current_line.bytesize
finish
end
end
else
finish
end
end
private def em_delete_prev_char(key, arg: 1)
arg.times do
if @byte_pointer == 0 and @line_index > 0
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
elsif @byte_pointer > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
line, = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
set_current_line(line, @byte_pointer - byte_size)
end
end
process_auto_indent
end
alias_method :backward_delete_char, :em_delete_prev_char
# Editline:: +ed-kill-line+ (vi command: +D+, +Ctrl-K+; emacs: +Ctrl-K+,
# +Ctrl-U+) + Kill from the cursor to the end of the line.
# GNU Readline:: +kill-line+ (+C-k+) Kill the text from point to the end of
# the line. With a negative numeric argument, kill backward
# from the cursor to the beginning of the current line.
private def ed_kill_line(key)
if current_line.bytesize > @byte_pointer
line, deleted = byteslice!(current_line, @byte_pointer, current_line.bytesize - @byte_pointer)
set_current_line(line, line.bytesize)
@kill_ring.append(deleted)
elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :kill_line, :ed_kill_line
# Editline:: +vi_change_to_eol+ (vi command: +C+) + Kill and change from the cursor to the end of the line.
private def vi_change_to_eol(key)
ed_kill_line(key)
@config.editing_mode = :vi_insert
end
# Editline:: +vi-kill-line-prev+ (vi: +Ctrl-U+) Delete the string from the
# beginning of the edit buffer to the cursor and save it to the
# cut buffer.
# GNU Readline:: +unix-line-discard+ (+C-u+) Kill backward from the cursor
# to the beginning of the current line.
private def vi_kill_line_prev(key)
if @byte_pointer > 0
line, deleted = byteslice!(current_line, 0, @byte_pointer)
set_current_line(line, 0)
@kill_ring.append(deleted, true)
end
end
alias_method :unix_line_discard, :vi_kill_line_prev
# Editline:: +em-kill-line+ (not bound) Delete the entire contents of the
# edit buffer and save it to the cut buffer. +vi-kill-line-prev+
# GNU Readline:: +kill-whole-line+ (not bound) Kill all characters on the
# current line, no matter where point is.
private def em_kill_line(key)
if current_line.size > 0
@kill_ring.append(current_line.dup, true)
set_current_line('', 0)
end
end
alias_method :kill_whole_line, :em_kill_line
private def em_delete(key)
if buffer_empty? and key == "\C-d".ord
@eof = true
finish
elsif @byte_pointer < current_line.bytesize
splitted_last = current_line.byteslice(@byte_pointer, current_line.bytesize)
mbchar = splitted_last.grapheme_clusters.first
line, = byteslice!(current_line, @byte_pointer, mbchar.bytesize)
set_current_line(line)
elsif @byte_pointer == current_line.bytesize and @buffer_of_lines.size > @line_index + 1
set_current_line(current_line + @buffer_of_lines.delete_at(@line_index + 1), current_line.bytesize)
end
end
alias_method :delete_char, :em_delete
private def em_delete_or_list(key)
if current_line.empty? or @byte_pointer < current_line.bytesize
em_delete(key)
elsif !@config.autocompletion # show completed list
result = call_completion_proc
if result.is_a?(Array)
perform_completion(result, true)
end
end
end
alias_method :delete_char_or_list, :em_delete_or_list
private def em_yank(key)
yanked = @kill_ring.yank
insert_text(yanked) if yanked
end
alias_method :yank, :em_yank
private def em_yank_pop(key)
yanked, prev_yank = @kill_ring.yank_pop
if yanked
line, = byteslice!(current_line, @byte_pointer - prev_yank.bytesize, prev_yank.bytesize)
set_current_line(line, @byte_pointer - prev_yank.bytesize)
insert_text(yanked)
end
end
alias_method :yank_pop, :em_yank_pop
private def ed_clear_screen(key)
Reline::IOGate.clear_screen
@screen_size = Reline::IOGate.get_screen_size
@rendered_screen.base_y = 0
clear_rendered_screen_cache
end
alias_method :clear_screen, :ed_clear_screen
private def em_next_word(key)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
end
end
alias_method :forward_word, :em_next_word
private def ed_prev_word(key)
if @byte_pointer > 0
byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
end
end
alias_method :backward_word, :ed_prev_word
private def em_delete_next_word(key)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
line, word = byteslice!(current_line, @byte_pointer, byte_size)
set_current_line(line)
@kill_ring.append(word)
end
end
alias_method :kill_word, :em_delete_next_word
private def ed_delete_prev_word(key)
if @byte_pointer > 0
byte_size, _ = Reline::Unicode.em_backward_word(current_line, @byte_pointer)
line, word = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(word, true)
end
end
alias_method :backward_kill_word, :ed_delete_prev_word
private def ed_transpose_chars(key)
if @byte_pointer > 0
if @byte_pointer < current_line.bytesize
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
@byte_pointer += byte_size
end
back1_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
if (@byte_pointer - back1_byte_size) > 0
back2_byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer - back1_byte_size)
back2_pointer = @byte_pointer - back1_byte_size - back2_byte_size
line, back2_mbchar = byteslice!(current_line, back2_pointer, back2_byte_size)
set_current_line(byteinsert(line, @byte_pointer - back2_byte_size, back2_mbchar))
end
end
end
alias_method :transpose_chars, :ed_transpose_chars
private def ed_transpose_words(key)
left_word_start, middle_start, right_word_start, after_start = Reline::Unicode.ed_transpose_words(current_line, @byte_pointer)
before = current_line.byteslice(0, left_word_start)
left_word = current_line.byteslice(left_word_start, middle_start - left_word_start)
middle = current_line.byteslice(middle_start, right_word_start - middle_start)
right_word = current_line.byteslice(right_word_start, after_start - right_word_start)
after = current_line.byteslice(after_start, current_line.bytesize - after_start)
return if left_word.empty? or right_word.empty?
from_head_to_left_word = before + right_word + middle + left_word
set_current_line(from_head_to_left_word + after, from_head_to_left_word.bytesize)
end
alias_method :transpose_words, :ed_transpose_words
private def em_capitol_case(key)
if current_line.bytesize > @byte_pointer
byte_size, _, new_str = Reline::Unicode.em_forward_word_with_capitalization(current_line, @byte_pointer)
before = current_line.byteslice(0, @byte_pointer)
after = current_line.byteslice((@byte_pointer + byte_size)..-1)
set_current_line(before + new_str + after, @byte_pointer + new_str.bytesize)
end
end
alias_method :capitalize_word, :em_capitol_case
private def em_lower_case(key)
if current_line.bytesize > @byte_pointer
byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[A-Z]/ ? mbchar.downcase : mbchar
}.join
rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
line = current_line.byteslice(0, @byte_pointer) + part
set_current_line(line + rest, line.bytesize)
end
end
alias_method :downcase_word, :em_lower_case
private def em_upper_case(key)
if current_line.bytesize > @byte_pointer
byte_size, = Reline::Unicode.em_forward_word(current_line, @byte_pointer)
part = current_line.byteslice(@byte_pointer, byte_size).grapheme_clusters.map { |mbchar|
mbchar =~ /[a-z]/ ? mbchar.upcase : mbchar
}.join
rest = current_line.byteslice((@byte_pointer + byte_size)..-1)
line = current_line.byteslice(0, @byte_pointer) + part
set_current_line(line + rest, line.bytesize)
end
end
alias_method :upcase_word, :em_upper_case
private def em_kill_region(key)
if @byte_pointer > 0
byte_size, _ = Reline::Unicode.em_big_backward_word(current_line, @byte_pointer)
line, deleted = byteslice!(current_line, @byte_pointer - byte_size, byte_size)
set_current_line(line, @byte_pointer - byte_size)
@kill_ring.append(deleted, true)
end
end
alias_method :unix_word_rubout, :em_kill_region
private def copy_for_vi(text)
if @config.editing_mode_is?(:vi_insert) or @config.editing_mode_is?(:vi_command)
@vi_clipboard = text
end
end
private def vi_insert(key)
@config.editing_mode = :vi_insert
end
private def vi_add(key)
@config.editing_mode = :vi_insert
ed_next_char(key)
end
private def vi_command_mode(key)
ed_prev_char(key)
@config.editing_mode = :vi_command
end
alias_method :vi_movement_mode, :vi_command_mode
private def vi_next_word(key, arg: 1)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.vi_forward_word(current_line, @byte_pointer, @drop_terminate_spaces)
@byte_pointer += byte_size
end
arg -= 1
vi_next_word(key, arg: arg) if arg > 0
end
private def vi_prev_word(key, arg: 1)
if @byte_pointer > 0
byte_size, _ = Reline::Unicode.vi_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
end
arg -= 1
vi_prev_word(key, arg: arg) if arg > 0
end
private def vi_end_word(key, arg: 1, inclusive: false)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.vi_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
end
arg -= 1
if inclusive and arg.zero?
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
@byte_pointer += byte_size
end
end
vi_end_word(key, arg: arg) if arg > 0
end
private def vi_next_big_word(key, arg: 1)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.vi_big_forward_word(current_line, @byte_pointer)
@byte_pointer += byte_size
end
arg -= 1
vi_next_big_word(key, arg: arg) if arg > 0
end
private def vi_prev_big_word(key, arg: 1)
if @byte_pointer > 0
byte_size, _ = Reline::Unicode.vi_big_backward_word(current_line, @byte_pointer)
@byte_pointer -= byte_size
end
arg -= 1
vi_prev_big_word(key, arg: arg) if arg > 0
end
private def vi_end_big_word(key, arg: 1, inclusive: false)
if current_line.bytesize > @byte_pointer
byte_size, _ = Reline::Unicode.vi_big_forward_end_word(current_line, @byte_pointer)
@byte_pointer += byte_size
end
arg -= 1
if inclusive and arg.zero?
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
@byte_pointer += byte_size
end
end
vi_end_big_word(key, arg: arg) if arg > 0
end
private def vi_delete_prev_char(key)
if @byte_pointer == 0 and @line_index > 0
@byte_pointer = @buffer_of_lines[@line_index - 1].bytesize
@buffer_of_lines[@line_index - 1] += @buffer_of_lines.delete_at(@line_index)
@line_index -= 1
process_auto_indent cursor_dependent: false
elsif @byte_pointer > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
line, _ = byteslice!(current_line, @byte_pointer, byte_size)
set_current_line(line)
end
end
private def vi_insert_at_bol(key)
ed_move_to_beg(key)
@config.editing_mode = :vi_insert
end
private def vi_add_at_eol(key)
ed_move_to_end(key)
@config.editing_mode = :vi_insert
end
private def ed_delete_prev_char(key, arg: 1)
deleted = +''
arg.times do
if @byte_pointer > 0
byte_size = Reline::Unicode.get_prev_mbchar_size(current_line, @byte_pointer)
@byte_pointer -= byte_size
line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
set_current_line(line)
deleted.prepend(mbchar)
end
end
copy_for_vi(deleted)
end
private def vi_change_meta(key, arg: nil)
if @vi_waiting_operator
set_current_line('', 0) if @vi_waiting_operator == :vi_change_meta_confirm && arg.nil?
@vi_waiting_operator = nil
@vi_waiting_operator_arg = nil
else
@drop_terminate_spaces = true
@vi_waiting_operator = :vi_change_meta_confirm
@vi_waiting_operator_arg = arg || 1
end
end
private def vi_change_meta_confirm(byte_pointer_diff)
vi_delete_meta_confirm(byte_pointer_diff)
@config.editing_mode = :vi_insert
@drop_terminate_spaces = false
end
private def vi_delete_meta(key, arg: nil)
if @vi_waiting_operator
set_current_line('', 0) if @vi_waiting_operator == :vi_delete_meta_confirm && arg.nil?
@vi_waiting_operator = nil
@vi_waiting_operator_arg = nil
else
@vi_waiting_operator = :vi_delete_meta_confirm
@vi_waiting_operator_arg = arg || 1
end
end
private def vi_delete_meta_confirm(byte_pointer_diff)
if byte_pointer_diff > 0
line, cut = byteslice!(current_line, @byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
line, cut = byteslice!(current_line, @byte_pointer + byte_pointer_diff, -byte_pointer_diff)
else
return
end
copy_for_vi(cut)
set_current_line(line, @byte_pointer + (byte_pointer_diff < 0 ? byte_pointer_diff : 0))
end
private def vi_yank(key, arg: nil)
if @vi_waiting_operator
copy_for_vi(current_line) if @vi_waiting_operator == :vi_yank_confirm && arg.nil?
@vi_waiting_operator = nil
@vi_waiting_operator_arg = nil
else
@vi_waiting_operator = :vi_yank_confirm
@vi_waiting_operator_arg = arg || 1
end
end
private def vi_yank_confirm(byte_pointer_diff)
if byte_pointer_diff > 0
cut = current_line.byteslice(@byte_pointer, byte_pointer_diff)
elsif byte_pointer_diff < 0
cut = current_line.byteslice(@byte_pointer + byte_pointer_diff, -byte_pointer_diff)
else
return
end
copy_for_vi(cut)
end
private def vi_list_or_eof(key)
if buffer_empty?
@eof = true
finish
else
ed_newline(key)
end
end
alias_method :vi_end_of_transmission, :vi_list_or_eof
alias_method :vi_eof_maybe, :vi_list_or_eof
private def ed_delete_next_char(key, arg: 1)
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
unless current_line.empty? || byte_size == 0
line, mbchar = byteslice!(current_line, @byte_pointer, byte_size)
copy_for_vi(mbchar)
if @byte_pointer > 0 && current_line.bytesize == @byte_pointer + byte_size
byte_size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
set_current_line(line, @byte_pointer - byte_size)
else
set_current_line(line, @byte_pointer)
end
end
arg -= 1
ed_delete_next_char(key, arg: arg) if arg > 0
end
private def vi_to_history_line(key)
if Reline::HISTORY.empty?
return
end
move_history(0, line: :start, cursor: :start)
end
private def vi_histedit(key)
path = Tempfile.open { |fp|
fp.write whole_lines.join("\n")
fp.path
}
system("#{ENV['EDITOR']} #{path}")
@buffer_of_lines = File.read(path).split("\n")
@buffer_of_lines = [String.new(encoding: @encoding)] if @buffer_of_lines.empty?
@line_index = 0
finish
end
private def vi_paste_prev(key, arg: 1)
if @vi_clipboard.size > 0
cursor_point = @vi_clipboard.grapheme_clusters[0..-2].join
set_current_line(byteinsert(current_line, @byte_pointer, @vi_clipboard), @byte_pointer + cursor_point.bytesize)
end
arg -= 1
vi_paste_prev(key, arg: arg) if arg > 0
end
private def vi_paste_next(key, arg: 1)
if @vi_clipboard.size > 0
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
line = byteinsert(current_line, @byte_pointer + byte_size, @vi_clipboard)
set_current_line(line, @byte_pointer + @vi_clipboard.bytesize)
end
arg -= 1
vi_paste_next(key, arg: arg) if arg > 0
end
private def ed_argument_digit(key)
if @vi_arg.nil?
if key.chr.to_i.zero?
if key.anybits?(0b10000000)
unescaped_key = key ^ 0b10000000
unless unescaped_key.chr.to_i.zero?
@vi_arg = unescaped_key.chr.to_i
end
end
else
@vi_arg = key.chr.to_i
end
else
@vi_arg = @vi_arg * 10 + key.chr.to_i
end
end
private def vi_to_column(key, arg: 0)
# Implementing behavior of vi, not Readline's vi-mode.
@byte_pointer, = current_line.grapheme_clusters.inject([0, 0]) { |(total_byte_size, total_width), gc|
mbchar_width = Reline::Unicode.get_mbchar_width(gc)
break [total_byte_size, total_width] if (total_width + mbchar_width) >= arg
[total_byte_size + gc.bytesize, total_width + mbchar_width]
}
end
private def vi_replace_char(key, arg: 1)
@waiting_proc = ->(k) {
if arg == 1
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
set_current_line(before + k.chr + after)
@waiting_proc = nil
elsif arg > 1
byte_size = 0
arg.times do
byte_size += Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer + byte_size)
end
before = current_line.byteslice(0, @byte_pointer)
remaining_point = @byte_pointer + byte_size
after = current_line.byteslice(remaining_point, current_line.bytesize - remaining_point)
replaced = k.chr * arg
set_current_line(before + replaced + after, @byte_pointer + replaced.bytesize)
@waiting_proc = nil
end
}
end
private def vi_next_char(key, arg: 1, inclusive: false)
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, inclusive: inclusive) }
end
private def vi_to_next_char(key, arg: 1, inclusive: false)
@waiting_proc = ->(key_for_proc) { search_next_char(key_for_proc, arg, need_prev_char: true, inclusive: inclusive) }
end
private def search_next_char(key, arg, need_prev_char: false, inclusive: false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
prev_total = nil
total = nil
found = false
current_line.byteslice(@byte_pointer..-1).grapheme_clusters.each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
width = Reline::Unicode.get_mbchar_width(mbchar)
total = [mbchar.bytesize, width]
else
if 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, _ = total
@byte_pointer += byte_size
elsif need_prev_char and found and prev_total
byte_size, _ = prev_total
@byte_pointer += byte_size
end
if inclusive
byte_size = Reline::Unicode.get_next_mbchar_size(current_line, @byte_pointer)
if byte_size > 0
@byte_pointer += byte_size
end
end
@waiting_proc = nil
end
private def vi_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg) }
end
private def vi_to_prev_char(key, arg: 1)
@waiting_proc = ->(key_for_proc) { search_prev_char(key_for_proc, arg, true) }
end
private def search_prev_char(key, arg, need_next_char = false)
if key.instance_of?(String)
inputed_char = key
else
inputed_char = key.chr
end
prev_total = nil
total = nil
found = false
current_line.byteslice(0..@byte_pointer).grapheme_clusters.reverse_each do |mbchar|
# total has [byte_size, cursor]
unless total
# skip cursor point
width = Reline::Unicode.get_mbchar_width(mbchar)
total = [mbchar.bytesize, width]
else
if 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, _ = total
@byte_pointer -= byte_size
elsif need_next_char and found and prev_total
byte_size, _ = prev_total
@byte_pointer -= byte_size
end
@waiting_proc = nil
end
private def vi_join_lines(key, arg: 1)
if @buffer_of_lines.size > @line_index + 1
next_line = @buffer_of_lines.delete_at(@line_index + 1).lstrip
set_current_line(current_line + ' ' + next_line, current_line.bytesize)
end
arg -= 1
vi_join_lines(key, arg: arg) if arg > 0
end
private def em_set_mark(key)
@mark_pointer = [@byte_pointer, @line_index]
end
alias_method :set_mark, :em_set_mark
private def em_exchange_mark(key)
return unless @mark_pointer
new_pointer = [@byte_pointer, @line_index]
@byte_pointer, @line_index = @mark_pointer
@mark_pointer = new_pointer
end
alias_method :exchange_point_and_mark, :em_exchange_mark
private def emacs_editing_mode(key)
@config.editing_mode = :emacs
end
private def vi_editing_mode(key)
@config.editing_mode = :vi_insert
end
private def undo(_key)
@undoing = true
return if @input_lines_position <= 0
@input_lines_position -= 1
target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
end
private def redo(_key)
@undoing = true
return if @input_lines_position >= @input_lines.size - 1
@input_lines_position += 1
target_lines, target_cursor_x, target_cursor_y = @input_lines[@input_lines_position]
set_current_lines(target_lines.dup, target_cursor_x, target_cursor_y)
end
private def prev_action_state_value(type)
@prev_action_state[0] == type ? @prev_action_state[1] : nil
end
private def set_next_action_state(type, value)
@next_action_state = [type, value]
end
private def re_read_init_file(_key)
@config.reload
end
end
share/ruby/reline/io.rb 0000644 00000001266 15173517736 0011065 0 ustar 00
module Reline
class IO
RESET_COLOR = "\e[0m"
def self.decide_io_gate
if ENV['TERM'] == 'dumb'
Reline::Dumb.new
else
require 'reline/io/ansi'
case RbConfig::CONFIG['host_os']
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
require 'reline/io/windows'
io = Reline::Windows.new
if io.msys_tty?
Reline::ANSI.new
else
io
end
else
Reline::ANSI.new
end
end
end
def dumb?
false
end
def win?
false
end
def reset_color_sequence
self.class::RESET_COLOR
end
end
end
require 'reline/io/dumb'
share/ruby/reline/config.rb 0000644 00000024154 15173517736 0011724 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{
completion-ignore-case
convert-meta
disable-completion
history-size
keyseq-timeout
show-all-if-ambiguous
show-mode-in-prompt
vi-cmd-mode-string
vi-ins-mode-string
emacs-mode-string
enable-bracketed-paste
isearch-terminators
}
VARIABLE_NAME_SYMBOLS = VARIABLE_NAMES.map { |v| :"#{v.tr(?-, ?_)}" }
VARIABLE_NAME_SYMBOLS.each do |v|
attr_accessor v
end
attr_accessor :autocompletion
def initialize
reset_variables
end
def reset
if editing_mode_is?(:vi_command)
@editing_mode_label = :vi_insert
end
@oneshot_key_bindings.clear
end
def reset_variables
@additional_key_bindings = { # from inputrc
emacs: Reline::KeyActor::Base.new,
vi_insert: Reline::KeyActor::Base.new,
vi_command: Reline::KeyActor::Base.new
}
@oneshot_key_bindings = Reline::KeyActor::Base.new
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
@default_key_bindings = {
emacs: Reline::KeyActor::Base.new(Reline::KeyActor::EMACS_MAPPING),
vi_insert: Reline::KeyActor::Base.new(Reline::KeyActor::VI_INSERT_MAPPING),
vi_command: Reline::KeyActor::Base.new(Reline::KeyActor::VI_COMMAND_MAPPING)
}
@vi_cmd_mode_string = '(cmd)'
@vi_ins_mode_string = '(ins)'
@emacs_mode_string = '@'
# https://tiswww.case.edu/php/chet/readline/readline.html#IDX25
@history_size = -1 # unlimited
@keyseq_timeout = 500
@test_mode = false
@autocompletion = false
@convert_meta = seven_bit_encoding?(Reline::IOGate.encoding)
@loaded = false
@enable_bracketed_paste = true
@show_mode_in_prompt = false
@default_inputrc_path = nil
end
def editing_mode
@default_key_bindings[@editing_mode_label]
end
def editing_mode=(val)
@editing_mode_label = val
end
def editing_mode_is?(*val)
val.any?(@editing_mode_label)
end
def keymap
@default_key_bindings[@keymap_label]
end
def loaded?
@loaded
end
def inputrc_path
case ENV['INPUTRC']
when nil, ''
else
return File.expand_path(ENV['INPUTRC'])
end
# In the XDG Specification, if ~/.config/readline/inputrc exists, then
# ~/.inputrc should not be read, but for compatibility with GNU Readline,
# if ~/.inputrc exists, then it is given priority.
home_rc_path = File.expand_path('~/.inputrc')
return home_rc_path if File.exist?(home_rc_path)
case path = ENV['XDG_CONFIG_HOME']
when nil, ''
else
path = File.join(path, 'readline/inputrc')
return path if File.exist?(path) and path == File.expand_path(path)
end
path = File.expand_path('~/.config/readline/inputrc')
return path if File.exist?(path)
return home_rc_path
end
private def default_inputrc_path
@default_inputrc_path ||= inputrc_path
end
def read(file = nil)
@loaded = true
file ||= default_inputrc_path
begin
if file.respond_to?(:readlines)
lines = file.readlines
else
lines = File.readlines(file)
end
rescue Errno::ENOENT
return nil
end
read_lines(lines, file)
self
rescue InvalidInputrc => e
warn e.message
nil
end
def key_bindings
# The key bindings for each editing mode will be overwritten by the user-defined ones.
Reline::KeyActor::Composite.new([@oneshot_key_bindings, @additional_key_bindings[@editing_mode_label], @default_key_bindings[@editing_mode_label]])
end
def add_oneshot_key_binding(keystroke, target)
# IRB sets invalid keystroke [Reline::Key]. We should ignore it.
return unless keystroke.all? { |c| c.is_a?(Integer) }
@oneshot_key_bindings.add(keystroke, target)
end
def reset_oneshot_key_bindings
@oneshot_key_bindings.clear
end
def add_default_key_binding_by_keymap(keymap, keystroke, target)
@default_key_bindings[keymap].add(keystroke, target)
end
def add_default_key_binding(keystroke, target)
add_default_key_binding_by_keymap(@keymap_label, keystroke, target)
end
def read_lines(lines, file = nil)
if not lines.empty? and lines.first.encoding != Reline.encoding_system_needs
begin
lines = lines.map do |l|
l.encode(Reline.encoding_system_needs)
rescue Encoding::UndefinedConversionError
mes = "The inputrc encoded in #{lines.first.encoding.name} can't be converted to the locale #{Reline.encoding_system_needs.name}."
raise Reline::ConfigEncodingConversionError.new(mes)
end
end
end
if_stack = []
lines.each_with_index do |line, no|
next if line.match(/\A\s*#/)
no += 1
line = line.chomp.lstrip
if line.start_with?('$')
handle_directive(line[1..-1], file, no, if_stack)
next
end
next if if_stack.any? { |_no, skip| skip }
case line
when /^set +([^ ]+) +(.+)/i
# value ignores everything after a space, raw_value does not.
var, value, raw_value = $1.downcase, $2.partition(' ').first, $2
bind_variable(var, value, raw_value)
next
when /\s*("#{KEYSEQ_PATTERN}+")\s*:\s*(.*)\s*$/o
key, func_name = $1, $2
func_name = func_name.split.first
keystroke, func = bind_key(key, func_name)
next unless keystroke
@additional_key_bindings[@keymap_label].add(@keymap_prefix + keystroke, func)
end
end
unless if_stack.empty?
raise InvalidInputrc, "#{file}:#{if_stack.last[0]}: unclosed if"
end
end
def handle_directive(directive, file, no, if_stack)
directive, args = directive.split(' ')
case directive
when 'if'
condition = false
case args
when /^mode=(vi|emacs)$/i
mode = $1.downcase
# NOTE: mode=vi means vi-insert mode
mode = 'vi_insert' if mode == 'vi'
if @editing_mode_label == mode.to_sym
condition = true
end
when 'term'
when 'version'
else # application name
condition = true if args == 'Ruby'
condition = true if args == 'Reline'
end
if_stack << [no, !condition]
when 'else'
if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched else"
end
if_stack.last[1] = !if_stack.last[1]
when 'endif'
if if_stack.empty?
raise InvalidInputrc, "#{file}:#{no}: unmatched endif"
end
if_stack.pop
when 'include'
read(File.expand_path(args))
end
end
def bind_variable(name, value, raw_value)
case name
when 'history-size'
begin
@history_size = Integer(value)
rescue ArgumentError
@history_size = 500
end
when 'isearch-terminators'
@isearch_terminators = retrieve_string(raw_value)
when 'editing-mode'
case value
when 'emacs'
@editing_mode_label = :emacs
@keymap_label = :emacs
@keymap_prefix = []
when 'vi'
@editing_mode_label = :vi_insert
@keymap_label = :vi_insert
@keymap_prefix = []
end
when 'keymap'
case value
when 'emacs', 'emacs-standard'
@keymap_label = :emacs
@keymap_prefix = []
when 'emacs-ctlx'
@keymap_label = :emacs
@keymap_prefix = [?\C-x.ord]
when 'emacs-meta'
@keymap_label = :emacs
@keymap_prefix = [?\e.ord]
when 'vi', 'vi-move', 'vi-command'
@keymap_label = :vi_command
@keymap_prefix = []
when 'vi-insert'
@keymap_label = :vi_insert
@keymap_prefix = []
end
when 'keyseq-timeout'
@keyseq_timeout = value.to_i
when 'show-mode-in-prompt'
case value
when 'off'
@show_mode_in_prompt = false
when 'on'
@show_mode_in_prompt = true
else
@show_mode_in_prompt = false
end
when 'vi-cmd-mode-string'
@vi_cmd_mode_string = retrieve_string(raw_value)
when 'vi-ins-mode-string'
@vi_ins_mode_string = retrieve_string(raw_value)
when 'emacs-mode-string'
@emacs_mode_string = retrieve_string(raw_value)
when *VARIABLE_NAMES then
variable_name = :"@#{name.tr(?-, ?_)}"
instance_variable_set(variable_name, value.nil? || value == '1' || value == 'on')
end
end
def retrieve_string(str)
str = $1 if str =~ /\A"(.*)"\z/
parse_keyseq(str).map { |c| c.chr(Reline.encoding_system_needs) }.join
end
def bind_key(key, 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
def reload
reset_variables
read
end
private def seven_bit_encoding?(encoding)
encoding == Encoding::US_ASCII
end
end
share/ruby/reline/key_actor/emacs.rb 0000644 00000020050 15173517736 0013516 0 ustar 00 module Reline::KeyActor
EMACS_MAPPING = [
# 0 ^@
:em_set_mark,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_prev_char,
# 3 ^C
:ed_ignore,
# 4 ^D
:em_delete,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_next_char,
# 7 ^G
:ed_unassigned,
# 8 ^H
:em_delete_prev_char,
# 9 ^I
:complete,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_quoted_insert,
# 18 ^R
:vi_search_prev,
# 19 ^S
:vi_search_next,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
:unix_line_discard,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:em_kill_region,
# 24 ^X
:ed_unassigned,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_ignore,
# 27 ^[
:ed_unassigned,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_ignore,
# 30 ^^
:ed_unassigned,
# 31 ^_
:undo,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_digit,
# 49 1
:ed_digit,
# 50 2
:ed_digit,
# 51 3
:ed_digit,
# 52 4
:ed_digit,
# 53 5
:ed_digit,
# 54 6
:ed_digit,
# 55 7
:ed_digit,
# 56 8
:ed_digit,
# 57 9
:ed_digit,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_delete_prev_word,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:key_newline,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_clear_screen,
# 141 M-^M
:key_newline,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:em_yank_pop,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:redo,
# 160 M-SPACE
:em_set_mark,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_argument_digit,
# 177 M-1
:ed_argument_digit,
# 178 M-2
:ed_argument_digit,
# 179 M-3
:ed_argument_digit,
# 180 M-4
:ed_argument_digit,
# 181 M-5
:ed_argument_digit,
# 182 M-6
:ed_argument_digit,
# 183 M-7
:ed_argument_digit,
# 184 M-8
:ed_argument_digit,
# 185 M-9
:ed_argument_digit,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_prev_word,
# 195 M-C
:em_capitol_case,
# 196 M-D
:em_delete_next_word,
# 197 M-E
:ed_unassigned,
# 198 M-F
:em_next_word,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:em_lower_case,
# 205 M-M
:ed_unassigned,
# 206 M-N
:vi_search_next,
# 207 M-O
:ed_unassigned,
# 208 M-P
:vi_search_prev,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:em_upper_case,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:em_yank_pop,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_prev_word,
# 227 M-c
:em_capitol_case,
# 228 M-d
:em_delete_next_word,
# 229 M-e
:ed_unassigned,
# 230 M-f
:em_next_word,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:em_lower_case,
# 237 M-m
:ed_unassigned,
# 238 M-n
:vi_search_next,
# 239 M-o
:ed_unassigned,
# 240 M-p
:vi_search_prev,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_transpose_words,
# 245 M-u
:em_upper_case,
# 246 M-v
:ed_unassigned,
# 247 M-w
:ed_unassigned,
# 248 M-x
:ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_delete_prev_word
# EOF
]
end
share/ruby/reline/key_actor/composite.rb 0000644 00000000504 15173517736 0014432 0 ustar 00 class Reline::KeyActor::Composite
def initialize(key_actors)
@key_actors = key_actors
end
def matching?(key)
@key_actors.any? { |key_actor| key_actor.matching?(key) }
end
def get(key)
@key_actors.each do |key_actor|
func = key_actor.get(key)
return func if func
end
nil
end
end
share/ruby/reline/key_actor/vi_insert.rb 0000644 00000017746 15173517736 0014452 0 ustar 00 module Reline::KeyActor
VI_INSERT_MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_insert,
# 2 ^B
:ed_insert,
# 3 ^C
:ed_insert,
# 4 ^D
:vi_list_or_eof,
# 5 ^E
:ed_insert,
# 6 ^F
:ed_insert,
# 7 ^G
:ed_insert,
# 8 ^H
:vi_delete_prev_char,
# 9 ^I
:complete,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_insert,
# 12 ^L
:ed_insert,
# 13 ^M
:ed_newline,
# 14 ^N
:menu_complete,
# 15 ^O
:ed_insert,
# 16 ^P
:menu_complete_backward,
# 17 ^Q
:ed_ignore,
# 18 ^R
:vi_search_prev,
# 19 ^S
:vi_search_next,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_insert,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_insert,
# 27 ^[
:vi_command_mode,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_insert,
# 30 ^^
:ed_insert,
# 31 ^_
:ed_insert,
# 32 SPACE
:ed_insert,
# 33 !
:ed_insert,
# 34 "
:ed_insert,
# 35 #
:ed_insert,
# 36 $
:ed_insert,
# 37 %
:ed_insert,
# 38 &
:ed_insert,
# 39 '
:ed_insert,
# 40 (
:ed_insert,
# 41 )
:ed_insert,
# 42 *
:ed_insert,
# 43 +
:ed_insert,
# 44 ,
:ed_insert,
# 45 -
:ed_insert,
# 46 .
:ed_insert,
# 47 /
:ed_insert,
# 48 0
:ed_insert,
# 49 1
:ed_insert,
# 50 2
:ed_insert,
# 51 3
:ed_insert,
# 52 4
:ed_insert,
# 53 5
:ed_insert,
# 54 6
:ed_insert,
# 55 7
:ed_insert,
# 56 8
:ed_insert,
# 57 9
:ed_insert,
# 58 :
:ed_insert,
# 59 ;
:ed_insert,
# 60 <
:ed_insert,
# 61 =
:ed_insert,
# 62 >
:ed_insert,
# 63 ?
:ed_insert,
# 64 @
:ed_insert,
# 65 A
:ed_insert,
# 66 B
:ed_insert,
# 67 C
:ed_insert,
# 68 D
:ed_insert,
# 69 E
:ed_insert,
# 70 F
:ed_insert,
# 71 G
:ed_insert,
# 72 H
:ed_insert,
# 73 I
:ed_insert,
# 74 J
:ed_insert,
# 75 K
:ed_insert,
# 76 L
:ed_insert,
# 77 M
:ed_insert,
# 78 N
:ed_insert,
# 79 O
:ed_insert,
# 80 P
:ed_insert,
# 81 Q
:ed_insert,
# 82 R
:ed_insert,
# 83 S
:ed_insert,
# 84 T
:ed_insert,
# 85 U
:ed_insert,
# 86 V
:ed_insert,
# 87 W
:ed_insert,
# 88 X
:ed_insert,
# 89 Y
:ed_insert,
# 90 Z
:ed_insert,
# 91 [
:ed_insert,
# 92 \
:ed_insert,
# 93 ]
:ed_insert,
# 94 ^
:ed_insert,
# 95 _
:ed_insert,
# 96 `
:ed_insert,
# 97 a
:ed_insert,
# 98 b
:ed_insert,
# 99 c
:ed_insert,
# 100 d
:ed_insert,
# 101 e
:ed_insert,
# 102 f
:ed_insert,
# 103 g
:ed_insert,
# 104 h
:ed_insert,
# 105 i
:ed_insert,
# 106 j
:ed_insert,
# 107 k
:ed_insert,
# 108 l
:ed_insert,
# 109 m
:ed_insert,
# 110 n
:ed_insert,
# 111 o
:ed_insert,
# 112 p
:ed_insert,
# 113 q
:ed_insert,
# 114 r
:ed_insert,
# 115 s
:ed_insert,
# 116 t
:ed_insert,
# 117 u
:ed_insert,
# 118 v
:ed_insert,
# 119 w
:ed_insert,
# 120 x
:ed_insert,
# 121 y
:ed_insert,
# 122 z
:ed_insert,
# 123 {
:ed_insert,
# 124 |
:ed_insert,
# 125 }
:ed_insert,
# 126 ~
:ed_insert,
# 127 ^?
:vi_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_unassigned,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:key_newline,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_unassigned,
# 141 M-^M
:key_newline,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:ed_unassigned,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_unassigned,
# 177 M-1
:ed_unassigned,
# 178 M-2
:ed_unassigned,
# 179 M-3
:ed_unassigned,
# 180 M-4
:ed_unassigned,
# 181 M-5
:ed_unassigned,
# 182 M-6
:ed_unassigned,
# 183 M-7
:ed_unassigned,
# 184 M-8
:ed_unassigned,
# 185 M-9
:ed_unassigned,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_unassigned,
# 195 M-C
:ed_unassigned,
# 196 M-D
:ed_unassigned,
# 197 M-E
:ed_unassigned,
# 198 M-F
:ed_unassigned,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:ed_unassigned,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_unassigned,
# 207 M-O
:ed_unassigned,
# 208 M-P
:ed_unassigned,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:ed_unassigned,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_unassigned,
# 227 M-c
:ed_unassigned,
# 228 M-d
:ed_unassigned,
# 229 M-e
:ed_unassigned,
# 230 M-f
:ed_unassigned,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:ed_unassigned,
# 237 M-m
:ed_unassigned,
# 238 M-n
:ed_unassigned,
# 239 M-o
:ed_unassigned,
# 240 M-p
:ed_unassigned,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_unassigned,
# 245 M-u
:ed_unassigned,
# 246 M-v
:ed_unassigned,
# 247 M-w
:ed_unassigned,
# 248 M-x
:ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_unassigned
# EOF
]
end
share/ruby/reline/key_actor/vi_command.rb 0000644 00000020705 15173517736 0014551 0 ustar 00 module Reline::KeyActor
VI_COMMAND_MAPPING = [
# 0 ^@
:ed_unassigned,
# 1 ^A
:ed_move_to_beg,
# 2 ^B
:ed_unassigned,
# 3 ^C
:ed_ignore,
# 4 ^D
:vi_end_of_transmission,
# 5 ^E
:ed_move_to_end,
# 6 ^F
:ed_unassigned,
# 7 ^G
:ed_unassigned,
# 8 ^H
:ed_prev_char,
# 9 ^I
:ed_unassigned,
# 10 ^J
:ed_newline,
# 11 ^K
:ed_kill_line,
# 12 ^L
:ed_clear_screen,
# 13 ^M
:ed_newline,
# 14 ^N
:ed_next_history,
# 15 ^O
:ed_ignore,
# 16 ^P
:ed_prev_history,
# 17 ^Q
:ed_ignore,
# 18 ^R
:vi_search_prev,
# 19 ^S
:ed_ignore,
# 20 ^T
:ed_transpose_chars,
# 21 ^U
:vi_kill_line_prev,
# 22 ^V
:ed_quoted_insert,
# 23 ^W
:ed_delete_prev_word,
# 24 ^X
:ed_unassigned,
# 25 ^Y
:em_yank,
# 26 ^Z
:ed_unassigned,
# 27 ^[
:ed_unassigned,
# 28 ^\
:ed_ignore,
# 29 ^]
:ed_unassigned,
# 30 ^^
:ed_unassigned,
# 31 ^_
:ed_unassigned,
# 32 SPACE
:ed_next_char,
# 33 !
:ed_unassigned,
# 34 "
:ed_unassigned,
# 35 #
:vi_comment_out,
# 36 $
:ed_move_to_end,
# 37 %
:ed_unassigned,
# 38 &
:ed_unassigned,
# 39 '
:ed_unassigned,
# 40 (
:ed_unassigned,
# 41 )
:ed_unassigned,
# 42 *
:ed_unassigned,
# 43 +
:ed_next_history,
# 44 ,
:ed_unassigned,
# 45 -
:ed_prev_history,
# 46 .
:ed_unassigned,
# 47 /
:vi_search_prev,
# 48 0
:vi_zero,
# 49 1
:ed_argument_digit,
# 50 2
:ed_argument_digit,
# 51 3
:ed_argument_digit,
# 52 4
:ed_argument_digit,
# 53 5
:ed_argument_digit,
# 54 6
:ed_argument_digit,
# 55 7
:ed_argument_digit,
# 56 8
:ed_argument_digit,
# 57 9
:ed_argument_digit,
# 58 :
:ed_unassigned,
# 59 ;
:ed_unassigned,
# 60 <
:ed_unassigned,
# 61 =
:ed_unassigned,
# 62 >
:ed_unassigned,
# 63 ?
:vi_search_next,
# 64 @
:vi_alias,
# 65 A
:vi_add_at_eol,
# 66 B
:vi_prev_big_word,
# 67 C
:vi_change_to_eol,
# 68 D
:ed_kill_line,
# 69 E
:vi_end_big_word,
# 70 F
:vi_prev_char,
# 71 G
:vi_to_history_line,
# 72 H
:ed_unassigned,
# 73 I
:vi_insert_at_bol,
# 74 J
:vi_join_lines,
# 75 K
:vi_search_prev,
# 76 L
:ed_unassigned,
# 77 M
:ed_unassigned,
# 78 N
:ed_unassigned,
# 79 O
:ed_unassigned,
# 80 P
:vi_paste_prev,
# 81 Q
:ed_unassigned,
# 82 R
:ed_unassigned,
# 83 S
:ed_unassigned,
# 84 T
:vi_to_prev_char,
# 85 U
:ed_unassigned,
# 86 V
:ed_unassigned,
# 87 W
:vi_next_big_word,
# 88 X
:ed_delete_prev_char,
# 89 Y
:ed_unassigned,
# 90 Z
:ed_unassigned,
# 91 [
:ed_unassigned,
# 92 \
:ed_unassigned,
# 93 ]
:ed_unassigned,
# 94 ^
:vi_first_print,
# 95 _
:ed_unassigned,
# 96 `
:ed_unassigned,
# 97 a
:vi_add,
# 98 b
:vi_prev_word,
# 99 c
:vi_change_meta,
# 100 d
:vi_delete_meta,
# 101 e
:vi_end_word,
# 102 f
:vi_next_char,
# 103 g
:ed_unassigned,
# 104 h
:ed_prev_char,
# 105 i
:vi_insert,
# 106 j
:ed_next_history,
# 107 k
:ed_prev_history,
# 108 l
:ed_next_char,
# 109 m
:ed_unassigned,
# 110 n
:ed_unassigned,
# 111 o
:ed_unassigned,
# 112 p
:vi_paste_next,
# 113 q
:ed_unassigned,
# 114 r
:vi_replace_char,
# 115 s
:ed_unassigned,
# 116 t
:vi_to_next_char,
# 117 u
:ed_unassigned,
# 118 v
:vi_histedit,
# 119 w
:vi_next_word,
# 120 x
:ed_delete_next_char,
# 121 y
:vi_yank,
# 122 z
:ed_unassigned,
# 123 {
:ed_unassigned,
# 124 |
:vi_to_column,
# 125 }
:ed_unassigned,
# 126 ~
:ed_unassigned,
# 127 ^?
:em_delete_prev_char,
# 128 M-^@
:ed_unassigned,
# 129 M-^A
:ed_unassigned,
# 130 M-^B
:ed_unassigned,
# 131 M-^C
:ed_unassigned,
# 132 M-^D
:ed_unassigned,
# 133 M-^E
:ed_unassigned,
# 134 M-^F
:ed_unassigned,
# 135 M-^G
:ed_unassigned,
# 136 M-^H
:ed_unassigned,
# 137 M-^I
:ed_unassigned,
# 138 M-^J
:ed_unassigned,
# 139 M-^K
:ed_unassigned,
# 140 M-^L
:ed_unassigned,
# 141 M-^M
:ed_unassigned,
# 142 M-^N
:ed_unassigned,
# 143 M-^O
:ed_unassigned,
# 144 M-^P
:ed_unassigned,
# 145 M-^Q
:ed_unassigned,
# 146 M-^R
:ed_unassigned,
# 147 M-^S
:ed_unassigned,
# 148 M-^T
:ed_unassigned,
# 149 M-^U
:ed_unassigned,
# 150 M-^V
:ed_unassigned,
# 151 M-^W
:ed_unassigned,
# 152 M-^X
:ed_unassigned,
# 153 M-^Y
:ed_unassigned,
# 154 M-^Z
:ed_unassigned,
# 155 M-^[
:ed_unassigned,
# 156 M-^\
:ed_unassigned,
# 157 M-^]
:ed_unassigned,
# 158 M-^^
:ed_unassigned,
# 159 M-^_
:ed_unassigned,
# 160 M-SPACE
:ed_unassigned,
# 161 M-!
:ed_unassigned,
# 162 M-"
:ed_unassigned,
# 163 M-#
:ed_unassigned,
# 164 M-$
:ed_unassigned,
# 165 M-%
:ed_unassigned,
# 166 M-&
:ed_unassigned,
# 167 M-'
:ed_unassigned,
# 168 M-(
:ed_unassigned,
# 169 M-)
:ed_unassigned,
# 170 M-*
:ed_unassigned,
# 171 M-+
:ed_unassigned,
# 172 M-,
:ed_unassigned,
# 173 M--
:ed_unassigned,
# 174 M-.
:ed_unassigned,
# 175 M-/
:ed_unassigned,
# 176 M-0
:ed_unassigned,
# 177 M-1
:ed_unassigned,
# 178 M-2
:ed_unassigned,
# 179 M-3
:ed_unassigned,
# 180 M-4
:ed_unassigned,
# 181 M-5
:ed_unassigned,
# 182 M-6
:ed_unassigned,
# 183 M-7
:ed_unassigned,
# 184 M-8
:ed_unassigned,
# 185 M-9
:ed_unassigned,
# 186 M-:
:ed_unassigned,
# 187 M-;
:ed_unassigned,
# 188 M-<
:ed_unassigned,
# 189 M-=
:ed_unassigned,
# 190 M->
:ed_unassigned,
# 191 M-?
:ed_unassigned,
# 192 M-@
:ed_unassigned,
# 193 M-A
:ed_unassigned,
# 194 M-B
:ed_unassigned,
# 195 M-C
:ed_unassigned,
# 196 M-D
:ed_unassigned,
# 197 M-E
:ed_unassigned,
# 198 M-F
:ed_unassigned,
# 199 M-G
:ed_unassigned,
# 200 M-H
:ed_unassigned,
# 201 M-I
:ed_unassigned,
# 202 M-J
:ed_unassigned,
# 203 M-K
:ed_unassigned,
# 204 M-L
:ed_unassigned,
# 205 M-M
:ed_unassigned,
# 206 M-N
:ed_unassigned,
# 207 M-O
:ed_unassigned,
# 208 M-P
:ed_unassigned,
# 209 M-Q
:ed_unassigned,
# 210 M-R
:ed_unassigned,
# 211 M-S
:ed_unassigned,
# 212 M-T
:ed_unassigned,
# 213 M-U
:ed_unassigned,
# 214 M-V
:ed_unassigned,
# 215 M-W
:ed_unassigned,
# 216 M-X
:ed_unassigned,
# 217 M-Y
:ed_unassigned,
# 218 M-Z
:ed_unassigned,
# 219 M-[
:ed_unassigned,
# 220 M-\
:ed_unassigned,
# 221 M-]
:ed_unassigned,
# 222 M-^
:ed_unassigned,
# 223 M-_
:ed_unassigned,
# 224 M-`
:ed_unassigned,
# 225 M-a
:ed_unassigned,
# 226 M-b
:ed_unassigned,
# 227 M-c
:ed_unassigned,
# 228 M-d
:ed_unassigned,
# 229 M-e
:ed_unassigned,
# 230 M-f
:ed_unassigned,
# 231 M-g
:ed_unassigned,
# 232 M-h
:ed_unassigned,
# 233 M-i
:ed_unassigned,
# 234 M-j
:ed_unassigned,
# 235 M-k
:ed_unassigned,
# 236 M-l
:ed_unassigned,
# 237 M-m
:ed_unassigned,
# 238 M-n
:ed_unassigned,
# 239 M-o
:ed_unassigned,
# 240 M-p
:ed_unassigned,
# 241 M-q
:ed_unassigned,
# 242 M-r
:ed_unassigned,
# 243 M-s
:ed_unassigned,
# 244 M-t
:ed_unassigned,
# 245 M-u
:ed_unassigned,
# 246 M-v
:ed_unassigned,
# 247 M-w
:ed_unassigned,
# 248 M-x
:ed_unassigned,
# 249 M-y
:ed_unassigned,
# 250 M-z
:ed_unassigned,
# 251 M-{
:ed_unassigned,
# 252 M-|
:ed_unassigned,
# 253 M-}
:ed_unassigned,
# 254 M-~
:ed_unassigned,
# 255 M-^?
:ed_unassigned
# EOF
]
end
share/ruby/reline/key_actor/base.rb 0000644 00000000764 15173517736 0013352 0 ustar 00 class Reline::KeyActor::Base
def initialize(mapping = [])
@mapping = mapping
@matching_bytes = {}
@key_bindings = {}
end
def get_method(key)
@mapping[key]
end
def add(key, func)
(1...key.size).each do |size|
@matching_bytes[key.take(size)] = true
end
@key_bindings[key] = func
end
def matching?(key)
@matching_bytes[key]
end
def get(key)
@key_bindings[key]
end
def clear
@matching_bytes.clear
@key_bindings.clear
end
end
share/ruby/reline/history.rb 0000644 00000003572 15173517736 0012161 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/reline/io/dumb.rb 0000644 00000002643 15173517736 0012014 0 ustar 00 require 'io/wait'
class Reline::Dumb < Reline::IO
RESET_COLOR = '' # Do not send color reset sequence
def initialize(encoding: nil)
@input = STDIN
@buf = []
@pasting = false
@encoding = encoding
@screen_size = [24, 80]
end
def dumb?
true
end
def encoding
if @encoding
@encoding
elsif RUBY_PLATFORM =~ /mswin|mingw/
Encoding::UTF_8
else
Encoding::default_external
end
end
def set_default_key_bindings(_)
end
def input=(val)
@input = val
end
def with_raw_input
yield
end
def getc(_timeout_second)
unless @buf.empty?
return @buf.shift
end
c = nil
loop do
Reline.core.line_editor.handle_signal
result = @input.wait_readable(0.1)
next if result.nil?
c = @input.read(1)
break
end
c&.ord
end
def ungetc(c)
@buf.unshift(c)
end
def get_screen_size
@screen_size
end
def cursor_pos
Reline::CursorPos.new(1, 1)
end
def hide_cursor
end
def show_cursor
end
def move_cursor_column(val)
end
def move_cursor_up(val)
end
def move_cursor_down(val)
end
def erase_after_cursor
end
def scroll_down(val)
end
def clear_screen
end
def set_screen_size(rows, columns)
@screen_size = [rows, columns]
end
def set_winch_handler(&handler)
end
def in_pasting?
@pasting
end
def prep
end
def deprep(otio)
end
end
share/ruby/reline/io/ansi.rb 0000644 00000023604 15173517736 0012017 0 ustar 00 require 'io/console'
require 'io/wait'
class Reline::ANSI < Reline::IO
CAPNAME_KEY_BINDINGS = {
'khome' => :ed_move_to_beg,
'kend' => :ed_move_to_end,
'kdch1' => :key_delete,
'kpp' => :ed_search_prev_history,
'knp' => :ed_search_next_history,
'kcuu1' => :ed_prev_history,
'kcud1' => :ed_next_history,
'kcuf1' => :ed_next_char,
'kcub1' => :ed_prev_char,
}
ANSI_CURSOR_KEY_BINDINGS = {
# Up
'A' => [:ed_prev_history, {}],
# Down
'B' => [:ed_next_history, {}],
# Right
'C' => [:ed_next_char, { ctrl: :em_next_word, meta: :em_next_word }],
# Left
'D' => [:ed_prev_char, { ctrl: :ed_prev_word, meta: :ed_prev_word }],
# End
'F' => [:ed_move_to_end, {}],
# Home
'H' => [:ed_move_to_beg, {}],
}
if Reline::Terminfo.enabled?
Reline::Terminfo.setupterm(0, 2)
end
def initialize
@input = STDIN
@output = STDOUT
@buf = []
@old_winch_handler = nil
end
def encoding
Encoding.default_external
end
def set_default_key_bindings(config, allow_terminfo: true)
set_bracketed_paste_key_bindings(config)
set_default_key_bindings_ansi_cursor(config)
if allow_terminfo && Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
else
set_default_key_bindings_comprehensive_list(config)
end
{
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
end
{
# default bindings
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
end
def set_bracketed_paste_key_bindings(config)
[:emacs, :vi_insert, :vi_command].each do |keymap|
config.add_default_key_binding_by_keymap(keymap, START_BRACKETED_PASTE.bytes, :bracketed_paste_start)
end
end
def set_default_key_bindings_ansi_cursor(config)
ANSI_CURSOR_KEY_BINDINGS.each do |char, (default_func, modifiers)|
bindings = [["\e[#{char}", default_func]] # CSI + char
if modifiers[:ctrl]
# CSI + ctrl_key_modifier + char
bindings << ["\e[1;5#{char}", modifiers[:ctrl]]
end
if modifiers[:meta]
# CSI + meta_key_modifier + char
bindings << ["\e[1;3#{char}", modifiers[:meta]]
# Meta(ESC) + CSI + char
bindings << ["\e\e[#{char}", modifiers[:meta]]
end
bindings.each do |sequence, func|
key = sequence.bytes
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end
end
def set_default_key_bindings_terminfo(config)
key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
begin
key_code = Reline::Terminfo.tigetstr(capname)
[ key_code.bytes, key_binding ]
rescue Reline::Terminfo::TerminfoError
# capname is undefined
end
end.compact.to_h
key_bindings.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end
def set_default_key_bindings_comprehensive_list(config)
{
# xterm
[27, 91, 51, 126] => :key_delete, # kdch1
[27, 91, 53, 126] => :ed_search_prev_history, # kpp
[27, 91, 54, 126] => :ed_search_next_history, # knp
# Console (80x25)
[27, 91, 49, 126] => :ed_move_to_beg, # Home
[27, 91, 52, 126] => :ed_move_to_end, # End
# KDE
# 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
[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
def input=(val)
@input = val
end
def output=(val)
@output = val
end
def with_raw_input
if @input.tty?
@input.raw(intr: true) { yield }
else
yield
end
end
def inner_getc(timeout_second)
unless @buf.empty?
return @buf.shift
end
until @input.wait_readable(0.01)
timeout_second -= 0.01
return nil if timeout_second <= 0
Reline.core.line_editor.handle_signal
end
c = @input.getbyte
(c == 0x16 && @input.tty? && @input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
# Maybe the I/O has been closed.
nil
end
START_BRACKETED_PASTE = String.new("\e[200~", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE = String.new("\e[201~", encoding: Encoding::ASCII_8BIT)
def read_bracketed_paste
buffer = String.new(encoding: Encoding::ASCII_8BIT)
until buffer.end_with?(END_BRACKETED_PASTE)
c = inner_getc(Float::INFINITY)
break unless c
buffer << c
end
string = buffer.delete_suffix(END_BRACKETED_PASTE).force_encoding(encoding)
string.valid_encoding? ? string : ''
end
# if the usage expects to wait indefinitely, use Float::INFINITY for timeout_second
def getc(timeout_second)
inner_getc(timeout_second)
end
def in_pasting?
not empty_buffer?
end
def empty_buffer?
unless @buf.empty?
return false
end
!@input.wait_readable(0)
end
def ungetc(c)
@buf.unshift(c)
end
def retrieve_keybuffer
begin
return unless @input.wait_readable(0.001)
str = @input.read_nonblock(1024)
str.bytes.each do |c|
@buf.push(c)
end
rescue EOFError
end
end
def get_screen_size
s = @input.winsize
return s if s[0] > 0 && s[1] > 0
s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
return s if s[0] > 0 && s[1] > 0
[24, 80]
rescue Errno::ENOTTY, Errno::ENODEV
[24, 80]
end
def set_screen_size(rows, columns)
@input.winsize = [rows, columns]
self
rescue Errno::ENOTTY, Errno::ENODEV
self
end
def cursor_pos
if both_tty?
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
else
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, IOError
# 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 both_tty?
@input.tty? && @output.tty?
end
def move_cursor_column(x)
@output.write "\e[#{x + 1}G"
end
def move_cursor_up(x)
if x > 0
@output.write "\e[#{x}A"
elsif x < 0
move_cursor_down(-x)
end
end
def move_cursor_down(x)
if x > 0
@output.write "\e[#{x}B"
elsif x < 0
move_cursor_up(-x)
end
end
def hide_cursor
seq = "\e[?25l"
if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
seq = Reline::Terminfo.tigetstr('civis')
rescue Reline::Terminfo::TerminfoError
# civis is undefined
end
end
@output.write seq
end
def show_cursor
seq = "\e[?25h"
if Reline::Terminfo.enabled? && Reline::Terminfo.term_supported?
begin
seq = Reline::Terminfo.tigetstr('cnorm')
rescue Reline::Terminfo::TerminfoError
# cnorm is undefined
end
end
@output.write seq
end
def erase_after_cursor
@output.write "\e[K"
end
# This only works when the cursor is at the bottom of the scroll range
# For more details, see https://github.com/ruby/reline/pull/577#issuecomment-1646679623
def scroll_down(x)
return if x.zero?
# We use `\n` instead of CSI + S because CSI + S would cause https://github.com/ruby/reline/issues/576
@output.write "\n" * x
end
def clear_screen
@output.write "\e[2J"
@output.write "\e[1;1H"
end
def set_winch_handler(&handler)
@old_winch_handler = Signal.trap('WINCH', &handler)
@old_cont_handler = Signal.trap('CONT') do
@input.raw!(intr: true) if @input.tty?
# Rerender the screen. Note that screen size might be changed while suspended.
handler.call
end
rescue ArgumentError
# Signal.trap may raise an ArgumentError if the platform doesn't support the signal.
end
def prep
# Enable bracketed paste
@output.write "\e[?2004h" if Reline.core.config.enable_bracketed_paste && both_tty?
retrieve_keybuffer
nil
end
def deprep(otio)
# Disable bracketed paste
@output.write "\e[?2004l" if Reline.core.config.enable_bracketed_paste && both_tty?
Signal.trap('WINCH', @old_winch_handler) if @old_winch_handler
Signal.trap('CONT', @old_cont_handler) if @old_cont_handler
end
end
share/ruby/reline/io/windows.rb 0000644 00000041504 15173517736 0012556 0 ustar 00 require 'fiddle/import'
class Reline::Windows < Reline::IO
def initialize
@input_buf = []
@output_buf = []
@output = STDOUT
@hsg = nil
@getwch = Win32API.new('msvcrt', '_getwch', [], 'I')
@kbhit = Win32API.new('msvcrt', '_kbhit', [], 'I')
@GetKeyState = Win32API.new('user32', 'GetKeyState', ['L'], 'L')
@GetConsoleScreenBufferInfo = Win32API.new('kernel32', 'GetConsoleScreenBufferInfo', ['L', 'P'], 'L')
@SetConsoleCursorPosition = Win32API.new('kernel32', 'SetConsoleCursorPosition', ['L', 'L'], 'L')
@GetStdHandle = Win32API.new('kernel32', 'GetStdHandle', ['L'], 'L')
@FillConsoleOutputCharacter = Win32API.new('kernel32', 'FillConsoleOutputCharacter', ['L', 'L', 'L', 'L', 'P'], 'L')
@ScrollConsoleScreenBuffer = Win32API.new('kernel32', 'ScrollConsoleScreenBuffer', ['L', 'P', 'P', 'L', 'P'], 'L')
@hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
@hConsoleInputHandle = @GetStdHandle.call(STD_INPUT_HANDLE)
@GetNumberOfConsoleInputEvents = Win32API.new('kernel32', 'GetNumberOfConsoleInputEvents', ['L', 'P'], 'L')
@ReadConsoleInputW = Win32API.new('kernel32', 'ReadConsoleInputW', ['L', 'P', 'L', 'P'], 'L')
@GetFileType = Win32API.new('kernel32', 'GetFileType', ['L'], 'L')
@GetFileInformationByHandleEx = Win32API.new('kernel32', 'GetFileInformationByHandleEx', ['L', 'I', 'P', 'L'], 'I')
@FillConsoleOutputAttribute = Win32API.new('kernel32', 'FillConsoleOutputAttribute', ['L', 'L', 'L', 'L', 'P'], 'L')
@SetConsoleCursorInfo = Win32API.new('kernel32', 'SetConsoleCursorInfo', ['L', 'P'], 'L')
@GetConsoleMode = Win32API.new('kernel32', 'GetConsoleMode', ['L', 'P'], 'L')
@SetConsoleMode = Win32API.new('kernel32', 'SetConsoleMode', ['L', 'L'], 'L')
@WaitForSingleObject = Win32API.new('kernel32', 'WaitForSingleObject', ['L', 'L'], 'L')
@legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0
end
def encoding
Encoding::UTF_8
end
def win?
true
end
def win_legacy_console?
@legacy_console
end
def set_default_key_bindings(config)
{
[224, 72] => :ed_prev_history, # ↑
[224, 80] => :ed_next_history, # ↓
[224, 77] => :ed_next_char, # →
[224, 75] => :ed_prev_char, # ←
[224, 83] => :key_delete, # Del
[224, 71] => :ed_move_to_beg, # Home
[224, 79] => :ed_move_to_end, # End
[ 0, 41] => :ed_unassigned, # input method on/off
[ 0, 72] => :ed_prev_history, # ↑
[ 0, 80] => :ed_next_history, # ↓
[ 0, 77] => :ed_next_char, # →
[ 0, 75] => :ed_prev_char, # ←
[ 0, 83] => :key_delete, # Del
[ 0, 71] => :ed_move_to_beg, # Home
[ 0, 79] => :ed_move_to_end # End
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
# Emulate ANSI key sequence.
{
[27, 91, 90] => :completion_journey_up, # S-Tab
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
end
end
if defined? JRUBY_VERSION
require 'win32api'
else
class Win32API
DLL = {}
TYPEMAP = {"0" => Fiddle::TYPE_VOID, "S" => Fiddle::TYPE_VOIDP, "I" => Fiddle::TYPE_LONG}
POINTER_TYPE = Fiddle::SIZEOF_VOIDP == Fiddle::SIZEOF_LONG_LONG ? 'q*' : 'l!*'
WIN32_TYPES = "VPpNnLlIi"
DL_TYPES = "0SSI"
def initialize(dllname, func, import, export = "0", calltype = :stdcall)
@proto = [import].join.tr(WIN32_TYPES, DL_TYPES).sub(/^(.)0*$/, '\1')
import = @proto.chars.map {|win_type| TYPEMAP[win_type.tr(WIN32_TYPES, DL_TYPES)]}
export = TYPEMAP[export.tr(WIN32_TYPES, DL_TYPES)]
calltype = Fiddle::Importer.const_get(:CALL_TYPE_TO_ABI)[calltype]
handle = DLL[dllname] ||=
begin
Fiddle.dlopen(dllname)
rescue Fiddle::DLError
raise unless File.extname(dllname).empty?
Fiddle.dlopen(dllname + ".dll")
end
@func = Fiddle::Function.new(handle[func], import, export, calltype)
rescue Fiddle::DLError => e
raise LoadError, e.message, e.backtrace
end
def call(*args)
import = @proto.split("")
args.each_with_index do |x, i|
args[i], = [x == 0 ? nil : +x].pack("p").unpack(POINTER_TYPE) if import[i] == "S"
args[i], = [x].pack("I").unpack("i") if import[i] == "I"
end
ret, = @func.call(*args)
return ret || 0
end
end
end
VK_RETURN = 0x0D
VK_MENU = 0x12 # ALT key
VK_LMENU = 0xA4
VK_CONTROL = 0x11
VK_SHIFT = 0x10
VK_DIVIDE = 0x6F
KEY_EVENT = 0x01
WINDOW_BUFFER_SIZE_EVENT = 0x04
CAPSLOCK_ON = 0x0080
ENHANCED_KEY = 0x0100
LEFT_ALT_PRESSED = 0x0002
LEFT_CTRL_PRESSED = 0x0008
NUMLOCK_ON = 0x0020
RIGHT_ALT_PRESSED = 0x0001
RIGHT_CTRL_PRESSED = 0x0004
SCROLLLOCK_ON = 0x0040
SHIFT_PRESSED = 0x0010
VK_TAB = 0x09
VK_END = 0x23
VK_HOME = 0x24
VK_LEFT = 0x25
VK_UP = 0x26
VK_RIGHT = 0x27
VK_DOWN = 0x28
VK_DELETE = 0x2E
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
FILE_TYPE_PIPE = 0x0003
FILE_NAME_INFO = 2
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4
# Calling Win32API with console handle is reported to fail after executing some external command.
# We need to refresh console handle and retry the call again.
private def call_with_console_handle(win32func, *args)
val = win32func.call(@hConsoleHandle, *args)
return val if val != 0
@hConsoleHandle = @GetStdHandle.call(STD_OUTPUT_HANDLE)
win32func.call(@hConsoleHandle, *args)
end
private def getconsolemode
mode = "\000\000\000\000"
call_with_console_handle(@GetConsoleMode, mode)
mode.unpack1('L')
end
private def setconsolemode(mode)
call_with_console_handle(@SetConsoleMode, mode)
end
#if @legacy_console
# setconsolemode(getconsolemode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
# @legacy_console = (getconsolemode() & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0)
#end
def msys_tty?(io = @hConsoleInputHandle)
# check if fd is a pipe
if @GetFileType.call(io) != FILE_TYPE_PIPE
return false
end
bufsize = 1024
p_buffer = "\0" * bufsize
res = @GetFileInformationByHandleEx.call(io, FILE_NAME_INFO, p_buffer, bufsize - 2)
return false if res == 0
# get pipe name: p_buffer layout is:
# struct _FILE_NAME_INFO {
# DWORD FileNameLength;
# WCHAR FileName[1];
# } FILE_NAME_INFO
len = p_buffer[0, 4].unpack1("L")
name = p_buffer[4, len].encode(Encoding::UTF_8, Encoding::UTF_16LE, invalid: :replace)
# Check if this could be a MSYS2 pty pipe ('\msys-XXXX-ptyN-XX')
# or a cygwin pty pipe ('\cygwin-XXXX-ptyN-XX')
name =~ /(msys-|cygwin-).*-pty/ ? true : false
end
KEY_MAP = [
# It's treated as Meta+Enter on Windows.
[ { control_keys: :CTRL, virtual_key_code: 0x0D }, "\e\r".bytes ],
[ { control_keys: :SHIFT, virtual_key_code: 0x0D }, "\e\r".bytes ],
# It's treated as Meta+Space on Windows.
[ { control_keys: :CTRL, char_code: 0x20 }, "\e ".bytes ],
# Emulate getwch() key sequences.
[ { control_keys: [], virtual_key_code: VK_UP }, [0, 72] ],
[ { control_keys: [], virtual_key_code: VK_DOWN }, [0, 80] ],
[ { control_keys: [], virtual_key_code: VK_RIGHT }, [0, 77] ],
[ { control_keys: [], virtual_key_code: VK_LEFT }, [0, 75] ],
[ { control_keys: [], virtual_key_code: VK_DELETE }, [0, 83] ],
[ { control_keys: [], virtual_key_code: VK_HOME }, [0, 71] ],
[ { control_keys: [], virtual_key_code: VK_END }, [0, 79] ],
# Emulate ANSI key sequence.
[ { control_keys: :SHIFT, virtual_key_code: VK_TAB }, [27, 91, 90] ],
]
def process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
# high-surrogate
if 0xD800 <= char_code and char_code <= 0xDBFF
@hsg = char_code
return
end
# low-surrogate
if 0xDC00 <= char_code and char_code <= 0xDFFF
if @hsg
char_code = 0x10000 + (@hsg - 0xD800) * 0x400 + char_code - 0xDC00
@hsg = nil
else
# no high-surrogate. ignored.
return
end
else
# ignore high-surrogate without low-surrogate if there
@hsg = nil
end
key = KeyEventRecord.new(virtual_key_code, char_code, control_key_state)
match = KEY_MAP.find { |args,| key.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 check_input_event
num_of_events = 0.chr * 8
while @output_buf.empty?
Reline.core.line_editor.handle_signal
if @WaitForSingleObject.(@hConsoleInputHandle, 100) != 0 # max 0.1 sec
# prevent for background consolemode change
@legacy_console = getconsolemode & ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0
next
end
next if @GetNumberOfConsoleInputEvents.(@hConsoleInputHandle, num_of_events) == 0 or num_of_events.unpack1('L') == 0
input_records = 0.chr * 20 * 80
read_event = 0.chr * 4
if @ReadConsoleInputW.(@hConsoleInputHandle, input_records, 80, read_event) != 0
read_events = read_event.unpack1('L')
0.upto(read_events) do |idx|
input_record = input_records[idx * 20, 20]
event = input_record[0, 2].unpack1('s*')
case event
when WINDOW_BUFFER_SIZE_EVENT
@winch_handler.()
when KEY_EVENT
key_down = input_record[4, 4].unpack1('l*')
repeat_count = input_record[8, 2].unpack1('s*')
virtual_key_code = input_record[10, 2].unpack1('s*')
virtual_scan_code = input_record[12, 2].unpack1('s*')
char_code = input_record[14, 2].unpack1('S*')
control_key_state = input_record[16, 2].unpack1('S*')
is_key_down = key_down.zero? ? false : true
if is_key_down
process_key_event(repeat_count, virtual_key_code, virtual_scan_code, char_code, control_key_state)
end
end
end
end
end
end
def with_raw_input
yield
end
def getc(_timeout_second)
check_input_event
@output_buf.shift
end
def ungetc(c)
@output_buf.unshift(c)
end
def in_pasting?
not empty_buffer?
end
def empty_buffer?
if not @output_buf.empty?
false
elsif @kbhit.call == 0
true
else
false
end
end
def get_console_screen_buffer_info
# CONSOLE_SCREEN_BUFFER_INFO
# [ 0,2] dwSize.X
# [ 2,2] dwSize.Y
# [ 4,2] dwCursorPositions.X
# [ 6,2] dwCursorPositions.Y
# [ 8,2] wAttributes
# [10,2] srWindow.Left
# [12,2] srWindow.Top
# [14,2] srWindow.Right
# [16,2] srWindow.Bottom
# [18,2] dwMaximumWindowSize.X
# [20,2] dwMaximumWindowSize.Y
csbi = 0.chr * 22
return if call_with_console_handle(@GetConsoleScreenBufferInfo, csbi) == 0
csbi
end
def get_screen_size
unless csbi = get_console_screen_buffer_info
return [1, 1]
end
csbi[0, 4].unpack('SS').reverse
end
def cursor_pos
unless csbi = get_console_screen_buffer_info
return Reline::CursorPos.new(0, 0)
end
x = csbi[4, 2].unpack1('s')
y = csbi[6, 2].unpack1('s')
Reline::CursorPos.new(x, y)
end
def move_cursor_column(val)
call_with_console_handle(@SetConsoleCursorPosition, cursor_pos.y * 65536 + val)
end
def move_cursor_up(val)
if val > 0
y = cursor_pos.y - val
y = 0 if y < 0
call_with_console_handle(@SetConsoleCursorPosition, y * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_down(-val)
end
end
def move_cursor_down(val)
if val > 0
return unless csbi = get_console_screen_buffer_info
screen_height = get_screen_size.first
y = cursor_pos.y + val
y = screen_height - 1 if y > (screen_height - 1)
call_with_console_handle(@SetConsoleCursorPosition, (cursor_pos.y + val) * 65536 + cursor_pos.x)
elsif val < 0
move_cursor_up(-val)
end
end
def erase_after_cursor
return unless csbi = get_console_screen_buffer_info
attributes = csbi[8, 2].unpack1('S')
cursor = csbi[4, 4].unpack1('L')
written = 0.chr * 4
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, get_screen_size.last - cursor_pos.x, cursor, written)
call_with_console_handle(@FillConsoleOutputAttribute, attributes, get_screen_size.last - cursor_pos.x, cursor, written)
end
def scroll_down(val)
return if val < 0
return unless csbi = get_console_screen_buffer_info
buffer_width, buffer_lines, x, y, attributes, window_left, window_top, window_bottom = csbi.unpack('ssssSssx2s')
screen_height = window_bottom - window_top + 1
val = screen_height if val > screen_height
if @legacy_console || window_left != 0
# unless ENABLE_VIRTUAL_TERMINAL,
# if srWindow.Left != 0 then it's conhost.exe hosted console
# and puts "\n" causes horizontal scroll. its glitch.
# FYI irb write from culumn 1, so this gives no gain.
scroll_rectangle = [0, val, buffer_width, buffer_lines - val].pack('s4')
destination_origin = 0 # y * 65536 + x
fill = [' '.ord, attributes].pack('SS')
call_with_console_handle(@ScrollConsoleScreenBuffer, scroll_rectangle, nil, destination_origin, fill)
else
origin_x = x + 1
origin_y = y - window_top + 1
@output.write [
(origin_y != screen_height) ? "\e[#{screen_height};H" : nil,
"\n" * val,
(origin_y != screen_height or !x.zero?) ? "\e[#{origin_y};#{origin_x}H" : nil
].join
end
end
def clear_screen
if @legacy_console
return unless csbi = get_console_screen_buffer_info
buffer_width, _buffer_lines, attributes, window_top, window_bottom = csbi.unpack('ss@8S@12sx2s')
fill_length = buffer_width * (window_bottom - window_top + 1)
screen_topleft = window_top * 65536
written = 0.chr * 4
call_with_console_handle(@FillConsoleOutputCharacter, 0x20, fill_length, screen_topleft, written)
call_with_console_handle(@FillConsoleOutputAttribute, attributes, fill_length, screen_topleft, written)
call_with_console_handle(@SetConsoleCursorPosition, screen_topleft)
else
@output.write "\e[2J" "\e[H"
end
end
def set_screen_size(rows, columns)
raise NotImplementedError
end
def hide_cursor
size = 100
visible = 0 # 0 means false
cursor_info = [size, visible].pack('Li')
call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
end
def show_cursor
size = 100
visible = 1 # 1 means true
cursor_info = [size, visible].pack('Li')
call_with_console_handle(@SetConsoleCursorInfo, cursor_info)
end
def set_winch_handler(&handler)
@winch_handler = handler
end
def prep
# do nothing
nil
end
def deprep(otio)
# do nothing
end
class KeyEventRecord
attr_reader :virtual_key_code, :char_code, :control_key_state, :control_keys
def initialize(virtual_key_code, char_code, control_key_state)
@virtual_key_code = virtual_key_code
@char_code = char_code
@control_key_state = control_key_state
@enhanced = control_key_state & ENHANCED_KEY != 0
(@control_keys = []).tap do |control_keys|
# symbols must be sorted to make comparison is easier later on
control_keys << :ALT if control_key_state & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED) != 0
control_keys << :CTRL if control_key_state & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) != 0
control_keys << :SHIFT if control_key_state & SHIFT_PRESSED != 0
end.freeze
end
def char
@char_code.chr(Encoding::UTF_8)
end
def enhanced?
@enhanced
end
# Verifies if the arguments match with this key event.
# Nil arguments are ignored, but at least one must be passed as non-nil.
# To verify that no control keys were pressed, pass an empty array: `control_keys: []`.
def 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/timeout.rb 0000644 00000013301 15173517736 0010657 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.4.1"
# Internal error raised to when a timeout is triggered.
class ExitException < Exception
def exception(*)
self
end
end
# Raised by Timeout.timeout when the block times out.
class Error < RuntimeError
def self.handle_timeout(message)
exc = ExitException.new(message)
begin
yield exc
rescue ExitException => e
raise new(message) if exc.equal?(e)
raise
end
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) unless watcher.group.enclosed?
watcher.name = "Timeout stdlib thread"
watcher.thread_variable_set(:"\0__detached_thread__", true)
watcher
end
private_class_method :create_timeout_thread
def self.ensure_timeout_thread_created
unless @timeout_thread and @timeout_thread.alive?
TIMEOUT_THREAD_MUTEX.synchronize do
unless @timeout_thread and @timeout_thread.alive?
@timeout_thread = create_timeout_thread
end
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
Error.handle_timeout(message, &perform)
end
end
module_function :timeout
end
share/ruby/csv/table.rb 0000644 00000112420 15173517736 0011055 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 15173517736 0011310 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 15173517736 0013102 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 15173517736 0012713 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 15173517737 0010612 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 15173517737 0013330 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 15173517737 0014545 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 15173517737 0011453 0 ustar 00 # frozen_string_literal: true
class CSV
# The version of the installed library.
VERSION = "3.2.8"
end
share/ruby/csv/parser.rb 0000644 00000111216 15173517737 0011265 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, 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
# trace(__method__, pattern, :done, :nil) if value.nil?
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 - @scanner.pos - 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
raise InvalidEncodingError.new(@encoding, 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
@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
raise InvalidEncodingError.new(@encoding, @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/rinda/ring.rb 0000644 00000031061 15173517737 0011231 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 15173517737 0012441 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 00000015150 15173517737 0011370 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
VERSION = "0.2.0"
##
# 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 00000111744 15173517737 0011417 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.userinfo = userinfo
self.hostname = host
self.port = port
self.path = path
self.query = query
self.opaque = opaque
self.fragment = fragment
else
self.set_scheme(scheme)
self.set_userinfo(userinfo)
self.set_host(host)
self.set_port(port)
self.set_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 if 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, @password)
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 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
#
# == 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)
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)
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.userinfo || rel.host || rel.port
# 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_userinfo(rel.userinfo)
base.set_host(rel.host)
base.set_port(rel.port || base.default_port)
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
alias to_str to_s
#
# 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 15173517737 0010603 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 15173517737 0011137 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 15173517737 0010567 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 00000011630 15173517737 0012454 0 ustar 00 # frozen_string_literal: true
module URI
class RFC3986_Parser # :nodoc:
# URI defined in RFC3986
HOST = %r[
(?<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-9:;=A-Z_a-z~]++)
)\])
| \g<IPv4address>
| (?<reg-name>(?:%\h\h|[!$&-.0-9;=A-Z_a-z~])*+)
]x
USERINFO = /(?:%\h\h|[!$&-.0-9:;=A-Z_a-z~])*+/
SCHEME = %r[[A-Za-z][+\-.0-9A-Za-z]*+].source
SEG = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/])].source
SEG_NC = %r[(?:%\h\h|[!$&-.0-9;=@A-Z_a-z~])].source
FRAGMENT = %r[(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+].source
RFC3986_URI = %r[\A
(?<seg>#{SEG}){0}
(?<URI>
(?<scheme>#{SCHEME}):
(?<hier-part>//
(?<authority>
(?:(?<userinfo>#{USERINFO.source})@)?
(?<host>#{HOST.source.delete(" \n")})
(?::(?<port>\d*+))?
)
(?<path-abempty>(?:/\g<seg>*+)?)
| (?<path-absolute>/((?!/)\g<seg>++)?)
| (?<path-rootless>(?!/)\g<seg>++)
| (?<path-empty>)
)
(?:\?(?<query>[^\#]*+))?
(?:\#(?<fragment>#{FRAGMENT}))?
)\z]x
RFC3986_relative_ref = %r[\A
(?<seg>#{SEG}){0}
(?<relative-ref>
(?<relative-part>//
(?<authority>
(?:(?<userinfo>#{USERINFO.source})@)?
(?<host>#{HOST.source.delete(" \n")}(?<!/))?
(?::(?<port>\d*+))?
)
(?<path-abempty>(?:/\g<seg>*+)?)
| (?<path-absolute>/\g<seg>*+)
| (?<path-noscheme>#{SEG_NC}++(?:/\g<seg>*+)?)
| (?<path-empty>)
)
(?:\?(?<query>[^#]*+))?
(?:\#(?<fragment>#{FRAGMENT}))?
)\z]x
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"]
scheme = m["scheme"]
opaque = m["path-rootless"]
if opaque
opaque << "?#{query}" if query
[ scheme,
nil, # userinfo
nil, # host
nil, # port
nil, # registry
nil, # path
opaque,
nil, # query
m["fragment"]
]
else # normal
[ scheme,
m["userinfo"],
m["host"],
m["port"],
nil, # registry
(m["path-abempty"] ||
m["path-absolute"] ||
m["path-empty"]),
nil, # opaque
query,
m["fragment"]
]
end
elsif m = RFC3986_relative_ref.match(uri)
[ nil, # scheme
m["userinfo"],
m["host"],
m["port"],
nil, # registry,
(m["path-abempty"] ||
m["path-absolute"] ||
m["path-noscheme"] ||
m["path-empty"]),
nil, # opaque
m["query"],
m["fragment"]
]
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: %r[\A#{SCHEME}\z]o,
USERINFO: %r[\A#{USERINFO}\z]o,
HOST: %r[\A#{HOST}\z]o,
ABS_PATH: %r[\A/#{SEG}*+\z]o,
REL_PATH: %r[\A(?!/)#{SEG}++\z]o,
QUERY: %r[\A(?:%\h\h|[!$&-.0-9:;=@A-Z_a-z~/?])*+\z],
FRAGMENT: %r[\A#{FRAGMENT}\z]o,
OPAQUE: %r[\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 15173517737 0012453 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 00000062541 15173517737 0011273 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
# Registers the given +klass+ as the class to be instantiated
# when parsing a \URI with the given +scheme+:
#
# URI.register_scheme('MS_SEARCH', URI::Generic) # => URI::Generic
# URI.scheme_list['MS_SEARCH'] # => URI::Generic
#
# Note that after calling String#upcase on +scheme+, it must be a valid
# constant name.
def self.register_scheme(scheme, klass)
Schemes.const_set(scheme.to_s.upcase, klass)
end
# Returns a hash of the defined schemes:
#
# URI.scheme_list
# # =>
# {"MAILTO"=>URI::MailTo,
# "LDAPS"=>URI::LDAPS,
# "WS"=>URI::WS,
# "HTTP"=>URI::HTTP,
# "HTTPS"=>URI::HTTPS,
# "LDAP"=>URI::LDAP,
# "FILE"=>URI::File,
# "FTP"=>URI::FTP}
#
# Related: URI.register_scheme.
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)
# Returns a new object constructed from the given +scheme+, +arguments+,
# and +default+:
#
# - The new object is an instance of <tt>URI.scheme_list[scheme.upcase]</tt>.
# - The object is initialized by calling the class initializer
# using +scheme+ and +arguments+.
# See URI::Generic.new.
#
# Examples:
#
# values = ['john.doe', 'www.example.com', '123', nil, '/forum/questions/', nil, 'tag=networking&order=newest', 'top']
# URI.for('https', *values)
# # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
# URI.for('foo', *values, default: URI::HTTP)
# # => #<URI::HTTP foo://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
def self.for(scheme, *arguments, default: Generic)
const_name = scheme.to_s.upcase
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
# Returns a 9-element array representing the parts of the \URI
# formed from the string +uri+;
# each array element is a string or +nil+:
#
# names = %w[scheme userinfo host port registry path opaque query fragment]
# values = URI.split('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# names.zip(values)
# # =>
# [["scheme", "https"],
# ["userinfo", "john.doe"],
# ["host", "www.example.com"],
# ["port", "123"],
# ["registry", nil],
# ["path", "/forum/questions/"],
# ["opaque", nil],
# ["query", "tag=networking&order=newest"],
# ["fragment", "top"]]
#
def self.split(uri)
RFC3986_PARSER.split(uri)
end
# Returns a new \URI object constructed from the given string +uri+:
#
# URI.parse('https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<URI::HTTPS https://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
# URI.parse('http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top')
# # => #<URI::HTTP http://john.doe@www.example.com:123/forum/questions/?tag=networking&order=newest#top>
#
# It's recommended to first ::escape string +uri+
# if it may contain invalid URI characters.
#
def self.parse(uri)
RFC3986_PARSER.parse(uri)
end
# Merges the given URI strings +str+
# per {RFC 2396}[https://www.rfc-editor.org/rfc/rfc2396.html].
#
# Each string in +str+ is converted to an
# {RFC3986 URI}[https://www.rfc-editor.org/rfc/rfc3986.html] before being merged.
#
# Examples:
#
# 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) # :nodoc:
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)# :nodoc:
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
# Returns a URL-encoded string derived from the given string +str+.
#
# The returned string:
#
# - Preserves:
#
# - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
# - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
# and <tt>'0'..'9'</tt>.
#
# Example:
#
# URI.encode_www_form_component('*.-_azAZ09')
# # => "*.-_azAZ09"
#
# - Converts:
#
# - Character <tt>' '</tt> to character <tt>'+'</tt>.
# - Any other character to "percent notation";
# the percent notation for character <i>c</i> is <tt>'%%%X' % c.ord</tt>.
#
# Example:
#
# URI.encode_www_form_component('Here are some punctuation characters: ,;?:')
# # => "Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A"
#
# Encoding:
#
# - If +str+ has encoding Encoding::ASCII_8BIT, argument +enc+ is ignored.
# - Otherwise +str+ is converted first to Encoding::UTF_8
# (with suitable character replacements),
# and then to encoding +enc+.
#
# In either case, the returned string has forced encoding Encoding::US_ASCII.
#
# Related: URI.encode_uri_component (encodes <tt>' '</tt> as <tt>'%20'</tt>).
def self.encode_www_form_component(str, enc=nil)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCWWWCOMP_, str, enc)
end
# Returns a string decoded from the given \URL-encoded string +str+.
#
# The given string is first encoded as Encoding::ASCII-8BIT (using String#b),
# then decoded (as below), and finally force-encoded to the given encoding +enc+.
#
# The returned string:
#
# - Preserves:
#
# - Characters <tt>'*'</tt>, <tt>'.'</tt>, <tt>'-'</tt>, and <tt>'_'</tt>.
# - Character in ranges <tt>'a'..'z'</tt>, <tt>'A'..'Z'</tt>,
# and <tt>'0'..'9'</tt>.
#
# Example:
#
# URI.decode_www_form_component('*.-_azAZ09')
# # => "*.-_azAZ09"
#
# - Converts:
#
# - Character <tt>'+'</tt> to character <tt>' '</tt>.
# - Each "percent notation" to an ASCII character.
#
# Example:
#
# URI.decode_www_form_component('Here+are+some+punctuation+characters%3A+%2C%3B%3F%3A')
# # => "Here are some punctuation characters: ,;?:"
#
# Related: URI.decode_uri_component (preserves <tt>'+'</tt>).
def self.decode_www_form_component(str, enc=Encoding::UTF_8)
_decode_uri_component(/\+|%\h\h/, str, enc)
end
# Like URI.encode_www_form_component, except that <tt>' '</tt> (space)
# is encoded as <tt>'%20'</tt> (instead of <tt>'+'</tt>).
def self.encode_uri_component(str, enc=nil)
_encode_uri_component(/[^*\-.0-9A-Z_a-z]/, TBLENCURICOMP_, str, enc)
end
# Like URI.decode_www_form_component, except that <tt>'+'</tt> is preserved.
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
# Returns a URL-encoded string derived from the given
# {Enumerable}[rdoc-ref:Enumerable@Enumerable+in+Ruby+Classes]
# +enum+.
#
# The result is suitable for use as form data
# for an \HTTP request whose <tt>Content-Type</tt> is
# <tt>'application/x-www-form-urlencoded'</tt>.
#
# The returned string consists of the elements of +enum+,
# each converted to one or more URL-encoded strings,
# and all joined with character <tt>'&'</tt>.
#
# Simple examples:
#
# URI.encode_www_form([['foo', 0], ['bar', 1], ['baz', 2]])
# # => "foo=0&bar=1&baz=2"
# URI.encode_www_form({foo: 0, bar: 1, baz: 2})
# # => "foo=0&bar=1&baz=2"
#
# The returned string is formed using method URI.encode_www_form_component,
# which converts certain characters:
#
# URI.encode_www_form('f#o': '/', 'b-r': '$', 'b z': '@')
# # => "f%23o=%2F&b-r=%24&b+z=%40"
#
# When +enum+ is Array-like, each element +ele+ is converted to a field:
#
# - If +ele+ is an array of two or more elements,
# the field is formed from its first two elements
# (and any additional elements are ignored):
#
# name = URI.encode_www_form_component(ele[0], enc)
# value = URI.encode_www_form_component(ele[1], enc)
# "#{name}=#{value}"
#
# Examples:
#
# URI.encode_www_form([%w[foo bar], %w[baz bat bah]])
# # => "foo=bar&baz=bat"
# URI.encode_www_form([['foo', 0], ['bar', :baz, 'bat']])
# # => "foo=0&bar=baz"
#
# - If +ele+ is an array of one element,
# the field is formed from <tt>ele[0]</tt>:
#
# URI.encode_www_form_component(ele[0])
#
# Example:
#
# URI.encode_www_form([['foo'], [:bar], [0]])
# # => "foo&bar&0"
#
# - Otherwise the field is formed from +ele+:
#
# URI.encode_www_form_component(ele)
#
# Example:
#
# URI.encode_www_form(['foo', :bar, 0])
# # => "foo&bar&0"
#
# The elements of an Array-like +enum+ may be mixture:
#
# URI.encode_www_form([['foo', 0], ['bar', 1, 2], ['baz'], :bat])
# # => "foo=0&bar=1&baz&bat"
#
# When +enum+ is Hash-like,
# each +key+/+value+ pair is converted to one or more fields:
#
# - If +value+ is
# {Array-convertible}[rdoc-ref:implicit_conversion.rdoc@Array-Convertible+Objects],
# each element +ele+ in +value+ is paired with +key+ to form a field:
#
# name = URI.encode_www_form_component(key, enc)
# value = URI.encode_www_form_component(ele, enc)
# "#{name}=#{value}"
#
# Example:
#
# URI.encode_www_form({foo: [:bar, 1], baz: [:bat, :bam, 2]})
# # => "foo=bar&foo=1&baz=bat&baz=bam&baz=2"
#
# - Otherwise, +key+ and +value+ are paired to form a field:
#
# name = URI.encode_www_form_component(key, enc)
# value = URI.encode_www_form_component(value, enc)
# "#{name}=#{value}"
#
# Example:
#
# URI.encode_www_form({foo: 0, bar: 1, baz: 2})
# # => "foo=0&bar=1&baz=2"
#
# The elements of a Hash-like +enum+ may be mixture:
#
# URI.encode_www_form({foo: [0, 1], bar: 2})
# # => "foo=0&foo=1&bar=2"
#
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
# Returns name/value pairs derived from the given string +str+,
# which must be an ASCII string.
#
# The method may be used to decode the body of Net::HTTPResponse object +res+
# for which <tt>res['Content-Type']</tt> is <tt>'application/x-www-form-urlencoded'</tt>.
#
# The returned data is an array of 2-element subarrays;
# each subarray is a name/value pair (both are strings).
# Each returned string has encoding +enc+,
# and has had invalid characters removed via
# {String#scrub}[rdoc-ref:String#scrub].
#
# A simple example:
#
# URI.decode_www_form('foo=0&bar=1&baz')
# # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
#
# The returned strings have certain conversions,
# similar to those performed in URI.decode_www_form_component:
#
# URI.decode_www_form('f%23o=%2F&b-r=%24&b+z=%40')
# # => [["f#o", "/"], ["b-r", "$"], ["b z", "@"]]
#
# The given string may contain consecutive separators:
#
# URI.decode_www_form('foo=0&&bar=1&&baz=2')
# # => [["foo", "0"], ["", ""], ["bar", "1"], ["", ""], ["baz", "2"]]
#
# A different separator may be specified:
#
# URI.decode_www_form('foo=0--bar=1--baz', separator: '--')
# # => [["foo", "0"], ["bar", "1"], ["baz", ""]]
#
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 a \URI object derived from the given +uri+,
# which may be a \URI string or an existing \URI object:
#
# # Returns a new URI.
# uri = URI('http://github.com/ruby/ruby')
# # => #<URI::HTTP http://github.com/ruby/ruby>
# # Returns the given URI.
# URI(uri)
# # => #<URI::HTTP http://github.com/ruby/ruby>
#
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 15173517737 0011460 0 ustar 00 module URI
# :stopdoc:
VERSION_CODE = '001302'.freeze
VERSION = VERSION_CODE.scan(/../).collect{|n| n.to_i}.join('.').freeze
# :startdoc:
end
share/ruby/uri/ldap.rb 0000644 00000013437 15173517737 0010723 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 15173517737 0010434 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 15173517737 0011111 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 15173517737 0011266 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 15173517737 0010716 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 15173517737 0010757 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 15173517737 0011704 0 ustar 00 module Open3
VERSION = "0.2.1"
end
share/ruby/ripper.rb 0000644 00000004676 15173517737 0010512 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 15173517737 0011120 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 15173517737 0012625 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 15173517737 0012443 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 15173517737 0014125 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 15173517737 0012273 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 15173517737 0013275 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 15173517737 0012162 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 15173517737 0013123 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 15173517737 0012612 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 15173517737 0012522 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 15173517737 0013003 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 15173517737 0011641 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.2"
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 15173517737 0010760 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 00000261304 15173517737 0010134 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"].shellsplit
if arg = ENV["CONFIGURE_ARGS"]
args.push(*arg.shellsplit)
end
args.delete_if {|a| /\A--(?:top(?:src)?|src|cur)dir(?=\z|=)/ =~ a}
for arg in args.concat(ARGV)
arg, val = arg.split('=', 2)
next unless arg
arg.tr!('_', '-')
if arg.sub!(/\A(?!--)/, '--')
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)
if f = caller_locations(1, 1).first.base_label and /\A\w/ =~ f
f += ": "
else
f = ""
end
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)
key = [target, idefault, ldefault].compact.join("\0")
if conf = $config_dirs[key]
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)
if conf = $arg_config.assoc("--with-#{target}-include")
conf[1] ||= "${#{target}-dir}/include"
end
ldir = with_config(target + "-lib", ldefault)
if conf = $arg_config.assoc("--with-#{target}-lib")
conf[1] ||= "${#{target}-dir}/#{_libdir_basename}"
end
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[key] = [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)
fmt = "not found"
def fmt.%(x)
x ? x.inspect : self
end
checking_for "pkg-config for #{pkg}", fmt do
_, 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
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 = []
verbose = with_config('verbose') ? "1" : (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 = " 2> #{File::NULL} || #{$mswin ? 'exit /b0' : '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 15173517737 0010706 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 00000003611 15173517737 0011273 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.respond_to?(:unsafe_load) ? YAML.unsafe_load(content) : YAML.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 15173517737 0011751 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 15173517737 0012107 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 15173517737 0012245 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 00000021520 15173517737 0011735 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
when TYPE_BOOL
return SIZEOF_BOOL
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 15173517737 0011576 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 15173517737 0012110 0 ustar 00 module Fiddle
VERSION = "1.1.2"
end
share/ruby/fiddle/cparser.rb 0000644 00000022426 15173517737 0012070 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 /\Aunsigned\s+long(?:\s+int\s+)?(?:\s+\w+)?\z/,
/\Aunsigned\s+int\s+long(?:\s+\w+)?\z/,
/\Along(?:\s+int)?\s+unsigned(?:\s+\w+)?\z/,
/\Aint\s+unsigned\s+long(?:\s+\w+)?\z/,
/\A(?:int\s+)?long\s+unsigned(?:\s+\w+)?\z/
return TYPE_ULONG
when /\A(?:signed\s+)?long(?:\s+int\s+)?(?:\s+\w+)?\z/,
/\A(?:signed\s+)?int\s+long(?:\s+\w+)?\z/,
/\Along(?:\s+int)?\s+signed(?:\s+\w+)?\z/
return TYPE_LONG
when /\Aunsigned\s+short(?:\s+int\s+)?(?:\s+\w+)?\z/,
/\Aunsigned\s+int\s+short(?:\s+\w+)?\z/,
/\Ashort(?:\s+int)?\s+unsigned(?:\s+\w+)?\z/,
/\Aint\s+unsigned\s+short(?:\s+\w+)?\z/,
/\A(?:int\s+)?short\s+unsigned(?:\s+\w+)?\z/
return TYPE_USHORT
when /\A(?:signed\s+)?short(?:\s+int\s+)?(?:\s+\w+)?\z/,
/\A(?:signed\s+)?int\s+short(?:\s+\w+)?\z/,
/\Aint\s+(?:signed\s+)?short(?:\s+\w+)?\z/
return TYPE_SHORT
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+)?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 "bool"
return TYPE_BOOL
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 00000007011 15173517737 0011340 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,
TYPE_BOOL => ALIGN_BOOL,
}
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!",
}
case SIZEOF_BOOL
when SIZEOF_CHAR
PACK_MAP[TYPE_BOOL] = PACK_MAP[TYPE_UCHAR]
when SIZEOF_SHORT
PACK_MAP[TYPE_BOOL] = PACK_MAP[TYPE_USHORT]
when SIZEOF_INT
PACK_MAP[TYPE_BOOL] = PACK_MAP[TYPE_UINT]
when SIZEOF_LONG
PACK_MAP[TYPE_BOOL] = PACK_MAP[TYPE_ULONG]
end
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,
TYPE_BOOL => SIZEOF_BOOL,
}
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 00000005474 15173517737 0011551 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").unpack1("C")
when TYPE_SHORT
[val].pack("s!").unpack1("S!")
when TYPE_INT
[val].pack("i!").unpack1("I!")
when TYPE_LONG
[val].pack("l!").unpack1("L!")
else
if defined?(TYPE_LONG_LONG) and
ty.abs == TYPE_LONG_LONG
[val].pack("q").unpack1("Q")
else
val
end
end
end
def signed_value(val, ty)
case ty.abs
when TYPE_CHAR
[val].pack("C").unpack1("c")
when TYPE_SHORT
[val].pack("S!").unpack1("s!")
when TYPE_INT
[val].pack("I!").unpack1("i!")
when TYPE_LONG
[val].pack("L!").unpack1("l!")
else
if defined?(TYPE_LONG_LONG) and
ty.abs == TYPE_LONG_LONG
[val].pack("Q").unpack1("q")
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").unpack1("l!")
else
if defined?(SIZEOF_LONG_LONG) and
SIZEOF_VOIDP == SIZEOF_LONG_LONG
return [arg].pack("p").unpack1("q")
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
end
return arg
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/rjit/context.rb 0000644 00000026223 15173517737 0013320 0 ustar 00 module RubyVM::RJIT
# Maximum number of temp value types we keep track of
MAX_TEMP_TYPES = 8
# Maximum number of local variable types we keep track of
MAX_LOCAL_TYPES = 8
# Operand to a YARV bytecode instruction
SelfOpnd = :SelfOpnd # The value is self
StackOpnd = Data.define(:index) # Temporary stack operand with stack index
# Potential mapping of a value on the temporary stack to self,
# a local variable, or constant so that we can track its type
MapToStack = :MapToStack # Normal stack value
MapToSelf = :MapToSelf # Temp maps to the self operand
MapToLocal = Data.define(:local_index) # Temp maps to a local variable with index
class Context < Struct.new(
:stack_size, # @param [Integer] The number of values on the stack
:sp_offset, # @param [Integer] JIT sp offset relative to the interpreter's sp
:chain_depth, # @param [Integer] jit_chain_guard depth
:local_types, # @param [Array<RubyVM::RJIT::Type>] Local variable types we keep track of
:temp_types, # @param [Array<RubyVM::RJIT::Type>] Temporary variable types we keep track of
:self_type, # @param [RubyVM::RJIT::Type] Type we track for self
:temp_mapping, # @param [Array<Symbol>] Mapping of temp stack entries to types we track
)
def initialize(
stack_size: 0,
sp_offset: 0,
chain_depth: 0,
local_types: [Type::Unknown] * MAX_LOCAL_TYPES,
temp_types: [Type::Unknown] * MAX_TEMP_TYPES,
self_type: Type::Unknown,
temp_mapping: [MapToStack] * MAX_TEMP_TYPES
) = super
# Deep dup by default for safety
def dup
ctx = super
ctx.local_types = ctx.local_types.dup
ctx.temp_types = ctx.temp_types.dup
ctx.temp_mapping = ctx.temp_mapping.dup
ctx
end
# Create a new Context instance with a given stack_size and sp_offset adjusted
# accordingly. This is useful when you want to virtually rewind a stack_size for
# generating a side exit while considering past sp_offset changes on gen_save_sp.
def with_stack_size(stack_size)
ctx = self.dup
ctx.sp_offset -= ctx.stack_size - stack_size
ctx.stack_size = stack_size
ctx
end
def stack_opnd(depth_from_top)
[SP, C.VALUE.size * (self.sp_offset - 1 - depth_from_top)]
end
def sp_opnd(offset_bytes = 0)
[SP, (C.VALUE.size * self.sp_offset) + offset_bytes]
end
# Push one new value on the temp stack with an explicit mapping
# Return a pointer to the new stack top
def stack_push_mapping(mapping_temp_type)
stack_size = self.stack_size
# Keep track of the type and mapping of the value
if stack_size < MAX_TEMP_TYPES
mapping, temp_type = mapping_temp_type
self.temp_mapping[stack_size] = mapping
self.temp_types[stack_size] = temp_type
case mapping
in MapToLocal[idx]
assert(idx < MAX_LOCAL_TYPES)
else
end
end
self.stack_size += 1
self.sp_offset += 1
return self.stack_opnd(0)
end
# Push one new value on the temp stack
# Return a pointer to the new stack top
def stack_push(val_type)
return self.stack_push_mapping([MapToStack, val_type])
end
# Push the self value on the stack
def stack_push_self
return self.stack_push_mapping([MapToStack, Type::Unknown])
end
# Push a local variable on the stack
def stack_push_local(local_idx)
if local_idx >= MAX_LOCAL_TYPES
return self.stack_push(Type::Unknown)
end
return self.stack_push_mapping([MapToLocal[local_idx], Type::Unknown])
end
# Pop N values off the stack
# Return a pointer to the stack top before the pop operation
def stack_pop(n = 1)
assert(n <= self.stack_size)
top = self.stack_opnd(0)
# Clear the types of the popped values
n.times do |i|
idx = self.stack_size - i - 1
if idx < MAX_TEMP_TYPES
self.temp_types[idx] = Type::Unknown
self.temp_mapping[idx] = MapToStack
end
end
self.stack_size -= n
self.sp_offset -= n
return top
end
def shift_stack(argc)
assert(argc < self.stack_size)
method_name_index = self.stack_size - argc - 1
(method_name_index...(self.stack_size - 1)).each do |i|
if i + 1 < MAX_TEMP_TYPES
self.temp_types[i] = self.temp_types[i + 1]
self.temp_mapping[i] = self.temp_mapping[i + 1]
end
end
self.stack_pop(1)
end
# Get the type of an instruction operand
def get_opnd_type(opnd)
case opnd
in SelfOpnd
self.self_type
in StackOpnd[idx]
assert(idx < self.stack_size)
stack_idx = self.stack_size - 1 - idx
# If outside of tracked range, do nothing
if stack_idx >= MAX_TEMP_TYPES
return Type::Unknown
end
mapping = self.temp_mapping[stack_idx]
case mapping
in MapToSelf
self.self_type
in MapToStack
self.temp_types[self.stack_size - 1 - idx]
in MapToLocal[idx]
assert(idx < MAX_LOCAL_TYPES)
self.local_types[idx]
end
end
end
# Get the currently tracked type for a local variable
def get_local_type(idx)
self.local_types[idx] || Type::Unknown
end
# Upgrade (or "learn") the type of an instruction operand
# This value must be compatible and at least as specific as the previously known type.
# If this value originated from self, or an lvar, the learned type will be
# propagated back to its source.
def upgrade_opnd_type(opnd, opnd_type)
case opnd
in SelfOpnd
self.self_type = self.self_type.upgrade(opnd_type)
in StackOpnd[idx]
assert(idx < self.stack_size)
stack_idx = self.stack_size - 1 - idx
# If outside of tracked range, do nothing
if stack_idx >= MAX_TEMP_TYPES
return
end
mapping = self.temp_mapping[stack_idx]
case mapping
in MapToSelf
self.self_type = self.self_type.upgrade(opnd_type)
in MapToStack
self.temp_types[stack_idx] = self.temp_types[stack_idx].upgrade(opnd_type)
in MapToLocal[idx]
assert(idx < MAX_LOCAL_TYPES)
self.local_types[idx] = self.local_types[idx].upgrade(opnd_type)
end
end
end
# Get both the type and mapping (where the value originates) of an operand.
# This is can be used with stack_push_mapping or set_opnd_mapping to copy
# a stack value's type while maintaining the mapping.
def get_opnd_mapping(opnd)
opnd_type = self.get_opnd_type(opnd)
case opnd
in SelfOpnd
return [MapToSelf, opnd_type]
in StackOpnd[idx]
assert(idx < self.stack_size)
stack_idx = self.stack_size - 1 - idx
if stack_idx < MAX_TEMP_TYPES
return [self.temp_mapping[stack_idx], opnd_type]
else
# We can't know the source of this stack operand, so we assume it is
# a stack-only temporary. type will be UNKNOWN
assert(opnd_type == Type::Unknown)
return [MapToStack, opnd_type]
end
end
end
# Overwrite both the type and mapping of a stack operand.
def set_opnd_mapping(opnd, mapping_opnd_type)
case opnd
in SelfOpnd
raise 'self always maps to self'
in StackOpnd[idx]
assert(idx < self.stack_size)
stack_idx = self.stack_size - 1 - idx
# If outside of tracked range, do nothing
if stack_idx >= MAX_TEMP_TYPES
return
end
mapping, opnd_type = mapping_opnd_type
self.temp_mapping[stack_idx] = mapping
# Only used when mapping == MAP_STACK
self.temp_types[stack_idx] = opnd_type
end
end
# Set the type of a local variable
def set_local_type(local_idx, local_type)
if local_idx >= MAX_LOCAL_TYPES
return
end
# If any values on the stack map to this local we must detach them
MAX_TEMP_TYPES.times do |stack_idx|
case self.temp_mapping[stack_idx]
in MapToStack
# noop
in MapToSelf
# noop
in MapToLocal[idx]
if idx == local_idx
self.temp_types[stack_idx] = self.local_types[idx]
self.temp_mapping[stack_idx] = MapToStack
else
# noop
end
end
end
self.local_types[local_idx] = local_type
end
# Erase local variable type information
# eg: because of a call we can't track
def clear_local_types
# When clearing local types we must detach any stack mappings to those
# locals. Even if local values may have changed, stack values will not.
MAX_TEMP_TYPES.times do |stack_idx|
case self.temp_mapping[stack_idx]
in MapToStack
# noop
in MapToSelf
# noop
in MapToLocal[local_idx]
self.temp_types[stack_idx] = self.local_types[local_idx]
self.temp_mapping[stack_idx] = MapToStack
end
end
# Clear the local types
self.local_types = [Type::Unknown] * MAX_LOCAL_TYPES
end
# Compute a difference score for two context objects
def diff(dst)
# Self is the source context (at the end of the predecessor)
src = self
# Can only lookup the first version in the chain
if dst.chain_depth != 0
return TypeDiff::Incompatible
end
# Blocks with depth > 0 always produce new versions
# Sidechains cannot overlap
if src.chain_depth != 0
return TypeDiff::Incompatible
end
if dst.stack_size != src.stack_size
return TypeDiff::Incompatible
end
if dst.sp_offset != src.sp_offset
return TypeDiff::Incompatible
end
# Difference sum
diff = 0
# Check the type of self
diff += case src.self_type.diff(dst.self_type)
in TypeDiff::Compatible[diff] then diff
in TypeDiff::Incompatible then return TypeDiff::Incompatible
end
# For each local type we track
src.local_types.size.times do |i|
t_src = src.local_types[i]
t_dst = dst.local_types[i]
diff += case t_src.diff(t_dst)
in TypeDiff::Compatible[diff] then diff
in TypeDiff::Incompatible then return TypeDiff::Incompatible
end
end
# For each value on the temp stack
src.stack_size.times do |i|
src_mapping, src_type = src.get_opnd_mapping(StackOpnd[i])
dst_mapping, dst_type = dst.get_opnd_mapping(StackOpnd[i])
# If the two mappings aren't the same
if src_mapping != dst_mapping
if dst_mapping == MapToStack
# We can safely drop information about the source of the temp
# stack operand.
diff += 1
else
return TypeDiff::Incompatible
end
end
diff += case src_type.diff(dst_type)
in TypeDiff::Compatible[diff] then diff
in TypeDiff::Incompatible then return TypeDiff::Incompatible
end
end
return TypeDiff::Compatible[diff]
end
private
def assert(cond)
unless cond
raise "'#{cond.inspect}' was not true"
end
end
end
end
share/ruby/ruby_vm/rjit/c_pointer.rb 0000644 00000025266 15173517737 0013624 0 ustar 00 module RubyVM::RJIT
# Every class under this namespace is a pointer. Even if the type is
# immediate, it shouldn't be dereferenced until `*` is called.
module CPointer
# Note: We'd like to avoid alphabetic method names to avoid a conflict
# with member methods. to_i and to_s are considered an exception.
class Struct
# @param name [String]
# @param sizeof [Integer]
# @param members [Hash{ Symbol => [RubyVM::RJIT::CType::*, Integer, TrueClass] }]
def initialize(addr, sizeof, members)
@addr = addr
@sizeof = sizeof
@members = members
end
# Get a raw address
def to_i
@addr
end
# Serialized address for generated code
def to_s
"0x#{@addr.to_s(16)}"
end
# Pointer diff
def -(struct)
raise ArgumentError if self.class != struct.class
(@addr - struct.to_i) / @sizeof
end
# Primitive API that does no automatic dereference
# TODO: remove this?
# @param member [Symbol]
def [](member)
type, offset = @members.fetch(member)
type.new(@addr + offset / 8)
end
private
# @param member [Symbol]
# @param value [Object]
def []=(member, value)
type, offset = @members.fetch(member)
type[@addr + offset / 8] = value
end
# @param size [Integer]
# @param members [Hash{ Symbol => [Integer, RubyVM::RJIT::CType::*] }]
def self.define(size, members)
Class.new(self) do
# Return the size of this type
define_singleton_method(:size) { size }
# Return the offset to a field
define_singleton_method(:offsetof) do |field, *fields|
member, offset = members.fetch(field)
offset /= 8
unless fields.empty?
offset += member.offsetof(*fields)
end
offset
end
# Return member names
define_singleton_method(:members) { members.keys }
define_method(:initialize) do |addr = nil|
if addr.nil? # TODO: get rid of this feature later
addr = Fiddle.malloc(size)
end
super(addr, size, members)
end
members.each do |member, (type, offset, to_ruby)|
# Intelligent API that does automatic dereference
define_method(member) do
value = self[member]
if value.respond_to?(:*)
value = value.*
end
if to_ruby
value = C.to_ruby(value)
end
value
end
define_method("#{member}=") do |value|
if to_ruby
value = C.to_value(value)
end
self[member] = value
end
end
end
end
end
# Note: We'd like to avoid alphabetic method names to avoid a conflict
# with member methods. to_i is considered an exception.
class Union
# @param _name [String] To be used when it starts defining a union pointer class
# @param sizeof [Integer]
# @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }]
def initialize(addr, sizeof, members)
@addr = addr
@sizeof = sizeof
@members = members
end
# Get a raw address
def to_i
@addr
end
# Move addr to access this pointer like an array
def +(index)
raise ArgumentError unless index.is_a?(Integer)
self.class.new(@addr + index * @sizeof)
end
# Pointer diff
def -(union)
raise ArgumentError if self.class != union.class
(@addr - union.instance_variable_get(:@addr)) / @sizeof
end
# @param sizeof [Integer]
# @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }]
def self.define(sizeof, members)
Class.new(self) do
# Return the size of this type
define_singleton_method(:sizeof) { sizeof }
# Part of Struct's offsetof implementation
define_singleton_method(:offsetof) do |field, *fields|
member = members.fetch(field)
offset = 0
unless fields.empty?
offset += member.offsetof(*fields)
end
offset
end
define_method(:initialize) do |addr|
super(addr, sizeof, members)
end
members.each do |member, type|
# Intelligent API that does automatic dereference
define_method(member) do
value = type.new(@addr)
if value.respond_to?(:*)
value = value.*
end
value
end
end
end
end
end
class Immediate
# @param addr [Integer]
# @param size [Integer]
# @param pack [String]
def initialize(addr, size, pack)
@addr = addr
@size = size
@pack = pack
end
# Get a raw address
def to_i
@addr
end
# Move addr to addess this pointer like an array
def +(index)
Immediate.new(@addr + index * @size, @size, @pack)
end
# Dereference
def *
self[0]
end
# Array access
def [](index)
return nil if @addr == 0
Fiddle::Pointer.new(@addr + index * @size)[0, @size].unpack1(@pack)
end
# Array set
def []=(index, value)
Fiddle::Pointer.new(@addr + index * @size)[0, @size] = [value].pack(@pack)
end
# Serialized address for generated code. Used for embedding things like body->iseq_encoded.
def to_s
"0x#{Integer(@addr).to_s(16)}"
end
# @param fiddle_type [Integer] Fiddle::TYPE_*
def self.define(fiddle_type)
size = Fiddle::PackInfo::SIZE_MAP.fetch(fiddle_type)
pack = Fiddle::PackInfo::PACK_MAP.fetch(fiddle_type)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, size, pack)
end
define_singleton_method(:size) do
size
end
# Type-level []=: Used by struct fields
define_singleton_method(:[]=) do |addr, value|
Fiddle::Pointer.new(addr)[0, size] = [value].pack(pack)
end
end
end
end
# -Fiddle::TYPE_CHAR Immediate with special handling of true/false
class Bool < Immediate.define(-Fiddle::TYPE_CHAR)
# Dereference
def *
return nil if @addr == 0
super != 0
end
def self.[]=(addr, value)
super(addr, value ? 1 : 0)
end
end
# Basically Immediate but without #* to skip auto-dereference of structs.
class Array
attr_reader :type
# @param addr [Integer]
# @param type [Class] RubyVM::RJIT::CType::*
def initialize(addr, type)
@addr = addr
@type = type
end
# Array access
def [](index)
@type.new(@addr)[index]
end
# Array set
# @param index [Integer]
# @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself or an object that return an address with to_i
def []=(index, value)
@type.new(@addr)[index] = value
end
private
def self.define(block)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, block.call)
end
end
end
end
class Pointer
attr_reader :type
# @param addr [Integer]
# @param type [Class] RubyVM::RJIT::CType::*
def initialize(addr, type)
@addr = addr
@type = type
end
# Move addr to addess this pointer like an array
def +(index)
raise ArgumentError unless index.is_a?(Integer)
Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP, @type)
end
# Dereference
def *
return nil if dest_addr == 0
@type.new(dest_addr)
end
# Array access
def [](index)
(self + index).*
end
# Array set
# @param index [Integer]
# @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself or an object that return an address with to_i
def []=(index, value)
Fiddle::Pointer.new(@addr + index * Fiddle::SIZEOF_VOIDP)[0, Fiddle::SIZEOF_VOIDP] =
[value.to_i].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
# Get a raw address
def to_i
@addr
end
private
def dest_addr
Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_VOIDP].unpack1(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
def self.define(block)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, block.call)
end
# Type-level []=: Used by struct fields
# @param addr [Integer]
# @param value [Integer, RubyVM::RJIT::CPointer::Struct] an address itself, or an object that return an address with to_i
define_singleton_method(:[]=) do |addr, value|
value = value.to_i
Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP] = [value].pack(Fiddle::PackInfo::PACK_MAP[Fiddle::TYPE_VOIDP])
end
end
end
end
class BitField
# @param addr [Integer]
# @param width [Integer]
# @param offset [Integer]
def initialize(addr, width, offset)
@addr = addr
@width = width
@offset = offset
end
# Dereference
def *
byte = Fiddle::Pointer.new(@addr)[0, Fiddle::SIZEOF_CHAR].unpack('c').first
if @width == 1
bit = (1 & (byte >> @offset))
bit == 1
elsif @width <= 8 && @offset == 0
bitmask = @width.times.map { |i| 1 << i }.sum
byte & bitmask
else
raise NotImplementedError.new("not-implemented bit field access: width=#{@width} offset=#{@offset}")
end
end
# @param width [Integer]
# @param offset [Integer]
def self.define(width, offset)
Class.new(self) do
define_method(:initialize) do |addr|
super(addr, width, offset)
end
end
end
end
# Give a name to a dynamic CPointer class to see it on inspect
def self.with_class_name(prefix, name, cache: false, &block)
return block.call if !name.nil? && name.empty?
# Use a cached result only if cache: true
class_name = "#{prefix}_#{name}"
klass =
if cache && self.const_defined?(class_name)
self.const_get(class_name)
else
block.call
end
# Give it a name unless it's already defined
unless self.const_defined?(class_name)
self.const_set(class_name, klass)
end
klass
end
end
end
share/ruby/ruby_vm/rjit/invariants.rb 0000644 00000011346 15173517737 0014012 0 ustar 00 require 'set'
module RubyVM::RJIT
class Invariants
class << self
# Called by RubyVM::RJIT::Compiler to lazily initialize this
# @param cb [CodeBlock]
# @param ocb [CodeBlock]
# @param compiler [RubyVM::RJIT::Compiler]
# @param exit_compiler [RubyVM::RJIT::ExitCompiler]
def initialize(cb, ocb, compiler, exit_compiler)
@cb = cb
@ocb = ocb
@compiler = compiler
@exit_compiler = exit_compiler
@bop_blocks = Set.new # TODO: actually invalidate this
@cme_blocks = Hash.new { |h, k| h[k] = Set.new }
@const_blocks = Hash.new { |h, k| h[k] = Set.new }
@patches = {}
# freeze # workaround a binding.irb issue. TODO: resurrect this
end
# @param jit [RubyVM::RJIT::JITState]
# @param klass [Integer]
# @param op [Integer]
def assume_bop_not_redefined(jit, klass, op)
return false unless C.BASIC_OP_UNREDEFINED_P(klass, op)
ensure_block_entry_exit(jit, cause: 'assume_bop_not_redefined')
@bop_blocks << jit.block
true
end
# @param jit [RubyVM::RJIT::JITState]
def assume_method_lookup_stable(jit, cme)
ensure_block_entry_exit(jit, cause: 'assume_method_lookup_stable')
@cme_blocks[cme.to_i] << jit.block
end
# @param jit [RubyVM::RJIT::JITState]
def assume_method_basic_definition(jit, klass, mid)
if C.rb_method_basic_definition_p(klass, mid)
cme = C.rb_callable_method_entry(klass, mid)
assume_method_lookup_stable(jit, cme)
true
else
false
end
end
def assume_stable_constant_names(jit, idlist)
(0..).each do |i|
break if (id = idlist[i]) == 0
@const_blocks[id] << jit.block
end
end
# @param asm [RubyVM::RJIT::Assembler]
def record_global_inval_patch(asm, target)
asm.pos_marker do |address|
if @patches.key?(address)
raise 'multiple patches in the same address'
end
@patches[address] = target
end
end
def on_cme_invalidate(cme)
@cme_blocks.fetch(cme.to_i, []).each do |block|
@cb.with_write_addr(block.start_addr) do
asm = Assembler.new
asm.comment('on_cme_invalidate')
asm.jmp(block.entry_exit)
@cb.write(asm)
end
# TODO: re-generate branches that refer to this block
end
@cme_blocks.delete(cme.to_i)
end
def on_constant_ic_update(iseq, ic, insn_idx)
# TODO: check multi ractor as well
if ic.entry.ic_cref
# No need to recompile the slowpath
return
end
pc = iseq.body.iseq_encoded + insn_idx
insn_name = Compiler.decode_insn(pc.*).name
if insn_name != :opt_getconstant_path && insn_name != :trace_opt_getconstant_path
raise 'insn_idx was not at opt_getconstant_path'
end
if ic.to_i != pc[1]
raise 'insn_idx + 1 was not at the updated IC'
end
@compiler.invalidate_blocks(iseq, pc.to_i)
end
def on_constant_state_changed(id)
@const_blocks.fetch(id, []).each do |block|
@compiler.invalidate_block(block)
end
end
def on_tracing_invalidate_all
invalidate_all
end
def on_update_references
# Give up. In order to support GC.compact, you'd have to update ISEQ
# addresses in BranchStub, etc. Ideally, we'd need to update moved
# pointers in JITed code here, but we just invalidate all for now.
invalidate_all
end
# @param jit [RubyVM::RJIT::JITState]
# @param block [RubyVM::RJIT::Block]
def ensure_block_entry_exit(jit, cause:)
block = jit.block
if block.entry_exit.nil?
block.entry_exit = Assembler.new.then do |asm|
@exit_compiler.compile_entry_exit(block.pc, block.ctx, asm, cause:)
@ocb.write(asm)
end
end
end
private
def invalidate_all
# On-Stack Replacement
@patches.each do |address, target|
# TODO: assert patches don't overlap each other
@cb.with_write_addr(address) do
asm = Assembler.new
asm.comment('on_tracing_invalidate_all')
asm.jmp(target)
@cb.write(asm)
end
end
@patches.clear
C.rjit_for_each_iseq do |iseq|
# Avoid entering past code
iseq.body.jit_entry = 0
# Avoid reusing past code
iseq.body.rjit_blocks.clear if iseq.body.rjit_blocks
# Compile this again if not converted to trace_* insns
iseq.body.jit_entry_calls = 0
end
end
end
end
end
share/ruby/ruby_vm/rjit/type.rb 0000644 00000013642 15173517737 0012616 0 ustar 00 module RubyVM::RJIT
# Represent the type of a value (local/stack/self) in RJIT
Type = Data.define(:type) do
# Check if the type is an immediate
def imm?
case self
in Type::UnknownImm then true
in Type::Nil then true
in Type::True then true
in Type::False then true
in Type::Fixnum then true
in Type::Flonum then true
in Type::ImmSymbol then true
else false
end
end
# Returns true when the type is not specific.
def unknown?
case self
in Type::Unknown | Type::UnknownImm | Type::UnknownHeap then true
else false
end
end
# Returns true when we know the VALUE is a specific handle type,
# such as a static symbol ([Type::ImmSymbol], i.e. true from RB_STATIC_SYM_P()).
# Opposite of [Self::is_unknown].
def specific?
!self.unknown?
end
# Check if the type is a heap object
def heap?
case self
in Type::UnknownHeap then true
in Type::TArray then true
in Type::Hash then true
in Type::HeapSymbol then true
in Type::TString then true
in Type::CString then true
in Type::BlockParamProxy then true
else false
end
end
# Check if it's a T_ARRAY object
def array?
case self
in Type::TArray then true
else false
end
end
# Check if it's a T_STRING object (both TString and CString are T_STRING)
def string?
case self
in Type::TString then true
in Type::CString then true
else false
end
end
# Returns the class if it is known, otherwise nil
def known_class
case self
in Type::Nil then C.rb_cNilClass
in Type::True then C.rb_cTrueClass
in Type::False then C.rb_cFalseClass
in Type::Fixnum then C.rb_cInteger
in Type::Flonum then C.rb_cFloat
in Type::ImmSymbol | Type::HeapSymbol then C.rb_cSymbol
in Type::CString then C.rb_cString
else nil
end
end
# Returns a boolean representing whether the value is truthy if known, otherwise nil
def known_truthy
case self
in Type::Nil then false
in Type::False then false
in Type::UnknownHeap then false
in Type::Unknown | Type::UnknownImm then nil
else true
end
end
# Returns a boolean representing whether the value is equal to nil if known, otherwise nil
def known_nil
case [self, self.known_truthy]
in Type::Nil, _ then true
in Type::False, _ then false # Qfalse is not nil
in _, true then false # if truthy, can't be nil
in _, _ then nil # otherwise unknown
end
end
def diff(dst)
# Perfect match, difference is zero
if self == dst
return TypeDiff::Compatible[0]
end
# Any type can flow into an unknown type
if dst == Type::Unknown
return TypeDiff::Compatible[1]
end
# A CString is also a TString.
if self == Type::CString && dst == Type::TString
return TypeDiff::Compatible[1]
end
# Specific heap type into unknown heap type is imperfect but valid
if self.heap? && dst == Type::UnknownHeap
return TypeDiff::Compatible[1]
end
# Specific immediate type into unknown immediate type is imperfect but valid
if self.imm? && dst == Type::UnknownImm
return TypeDiff::Compatible[1]
end
# Incompatible types
return TypeDiff::Incompatible
end
def upgrade(new_type)
assert(new_type.diff(self) != TypeDiff::Incompatible)
new_type
end
private
def assert(cond)
unless cond
raise "'#{cond.inspect}' was not true"
end
end
end
# This returns an appropriate Type based on a known value
class << Type
def from(val)
if C::SPECIAL_CONST_P(val)
if fixnum?(val)
Type::Fixnum
elsif val.nil?
Type::Nil
elsif val == true
Type::True
elsif val == false
Type::False
elsif static_symbol?(val)
Type::ImmSymbol
elsif flonum?(val)
Type::Flonum
else
raise "Illegal value: #{val.inspect}"
end
else
val_class = C.to_value(C.rb_class_of(val))
if val_class == C.rb_cString && C.rb_obj_frozen_p(val)
return Type::CString
end
if C.to_value(val) == C.rb_block_param_proxy
return Type::BlockParamProxy
end
case C::BUILTIN_TYPE(val)
in C::RUBY_T_ARRAY
Type::TArray
in C::RUBY_T_HASH
Type::Hash
in C::RUBY_T_STRING
Type::TString
else
Type::UnknownHeap
end
end
end
private
def fixnum?(obj)
(C.to_value(obj) & C::RUBY_FIXNUM_FLAG) == C::RUBY_FIXNUM_FLAG
end
def flonum?(obj)
(C.to_value(obj) & C::RUBY_FLONUM_MASK) == C::RUBY_FLONUM_FLAG
end
def static_symbol?(obj)
(C.to_value(obj) & 0xff) == C::RUBY_SYMBOL_FLAG
end
end
# List of types
Type::Unknown = Type[:Unknown]
Type::UnknownImm = Type[:UnknownImm]
Type::UnknownHeap = Type[:UnknownHeap]
Type::Nil = Type[:Nil]
Type::True = Type[:True]
Type::False = Type[:False]
Type::Fixnum = Type[:Fixnum]
Type::Flonum = Type[:Flonum]
Type::Hash = Type[:Hash]
Type::ImmSymbol = Type[:ImmSymbol]
Type::HeapSymbol = Type[:HeapSymbol]
Type::TString = Type[:TString] # An object with the T_STRING flag set, possibly an rb_cString
Type::CString = Type[:CString] # An un-subclassed string of type rb_cString (can have instance vars in some cases)
Type::TArray = Type[:TArray] # An object with the T_ARRAY flag set, possibly an rb_cArray
Type::BlockParamProxy = Type[:BlockParamProxy] # A special sentinel value indicating the block parameter should be read from
module TypeDiff
Compatible = Data.define(:diversion) # The smaller, the more compatible.
Incompatible = :Incompatible
end
end
share/ruby/ruby_vm/rjit/stats.rb 0000644 00000016632 15173517737 0012775 0 ustar 00 # frozen_string_literal: true
module RubyVM::RJIT
# Return a Hash for \RJIT statistics. \--rjit-stats makes more information available.
def self.runtime_stats
stats = {}
# Insn exits
INSNS.each_value do |insn|
exits = C.rjit_insn_exits[insn.bin]
if exits > 0
stats[:"exit_#{insn.name}"] = exits
end
end
# Runtime stats
C.rb_rjit_runtime_counters.members.each do |member|
stats[member] = C.rb_rjit_counters.public_send(member)
end
stats[:vm_insns_count] = C.rb_vm_insns_count
# Other stats are calculated here
stats[:side_exit_count] = stats.select { |name, _count| name.start_with?('exit_') }.sum(&:last)
if stats[:vm_insns_count] > 0
retired_in_rjit = stats[:rjit_insns_count] - stats[:side_exit_count]
stats[:total_insns_count] = retired_in_rjit + stats[:vm_insns_count]
stats[:ratio_in_rjit] = 100.0 * retired_in_rjit / stats[:total_insns_count]
else
stats.delete(:vm_insns_count)
end
stats
end
# :nodoc: all
class << self
private
# --yjit-stats at_exit
def print_stats
stats = runtime_stats
$stderr.puts("***RJIT: Printing RJIT statistics on exit***")
print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons')
print_counters(stats, prefix: 'invokeblock_', prompt: 'invokeblock exit reasons')
print_counters(stats, prefix: 'invokesuper_', prompt: 'invokesuper exit reasons')
print_counters(stats, prefix: 'getblockpp_', prompt: 'getblockparamproxy exit reasons')
print_counters(stats, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons')
print_counters(stats, prefix: 'setivar_', prompt: 'setinstancevariable exit reasons')
print_counters(stats, prefix: 'optaref_', prompt: 'opt_aref exit reasons')
print_counters(stats, prefix: 'optgetconst_', prompt: 'opt_getconstant_path exit reasons')
print_counters(stats, prefix: 'expandarray_', prompt: 'expandarray exit reasons')
$stderr.puts "compiled_block_count: #{format_number(13, stats[:compiled_block_count])}"
$stderr.puts "side_exit_count: #{format_number(13, stats[:side_exit_count])}"
$stderr.puts "total_insns_count: #{format_number(13, stats[:total_insns_count])}" if stats.key?(:total_insns_count)
$stderr.puts "vm_insns_count: #{format_number(13, stats[:vm_insns_count])}" if stats.key?(:vm_insns_count)
$stderr.puts "rjit_insns_count: #{format_number(13, stats[:rjit_insns_count])}"
$stderr.puts "ratio_in_rjit: #{format('%12.1f', stats[:ratio_in_rjit])}%" if stats.key?(:ratio_in_rjit)
print_exit_counts(stats)
end
def print_counters(stats, prefix:, prompt:)
$stderr.puts("#{prompt}: ")
counters = stats.filter { |key, _| key.start_with?(prefix) }
counters.filter! { |_, value| value != 0 }
counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) }
if counters.empty?
$stderr.puts(" (all relevant counters are zero)")
return
end
counters = counters.to_a
counters.sort_by! { |(_, counter_value)| counter_value }
longest_name_length = counters.max_by { |(name, _)| name.length }.first.length
total = counters.sum { |(_, counter_value)| counter_value }
counters.reverse_each do |(name, value)|
percentage = value.fdiv(total) * 100
$stderr.printf(" %*s %s (%4.1f%%)\n", longest_name_length, name, format_number(10, value), percentage)
end
end
def print_exit_counts(stats, how_many: 20, padding: 2)
exits = stats.filter_map { |name, count| [name.to_s.delete_prefix('exit_'), count] if name.start_with?('exit_') }.to_h
return if exits.empty?
top_exits = exits.sort_by { |_name, count| -count }.first(how_many).to_h
total_exits = exits.values.sum
$stderr.puts "Top-#{top_exits.size} most frequent exit ops (#{format("%.1f", 100.0 * top_exits.values.sum / total_exits)}% of exits):"
name_width = top_exits.map { |name, _count| name.length }.max + padding
count_width = top_exits.map { |_name, count| format_number(10, count).length }.max + padding
top_exits.each do |name, count|
ratio = 100.0 * count / total_exits
$stderr.puts "#{format("%#{name_width}s", name)}: #{format_number(count_width, count)} (#{format('%4.1f', ratio)}%)"
end
end
# Format large numbers with comma separators for readability
def format_number(pad, number)
integer, decimal = number.to_s.split('.')
d_groups = integer.chars.reverse.each_slice(3)
with_commas = d_groups.map(&:join).join(',').reverse
[with_commas, decimal].compact.join('.').rjust(pad, ' ')
end
# --yjit-trace-exits at_exit
def dump_trace_exits
filename = "#{Dir.pwd}/rjit_exit_locations.dump"
File.binwrite(filename, Marshal.dump(exit_traces))
$stderr.puts("RJIT exit locations dumped to:\n#{filename}")
end
# Convert rb_rjit_raw_samples and rb_rjit_line_samples into a StackProf format.
def exit_traces
results = C.rjit_exit_traces
raw_samples = results[:raw].dup
line_samples = results[:lines].dup
frames = results[:frames].dup
samples_count = 0
# Loop through the instructions and set the frame hash with the data.
# We use nonexistent.def for the file name, otherwise insns.def will be displayed
# and that information isn't useful in this context.
RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id|
frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} }
results[:frames][frame_id] = frame_hash
frames[frame_id] = frame_hash
end
# Loop through the raw_samples and build the hashes for StackProf.
# The loop is based off an example in the StackProf documentation and therefore
# this functionality can only work with that library.
#
# Raw Samples:
# [ length, frame1, frame2, frameN, ..., instruction, count
#
# Line Samples
# [ length, line_1, line_2, line_n, ..., dummy value, count
i = 0
while i < raw_samples.length
stack_length = raw_samples[i] + 1
i += 1 # consume the stack length
prev_frame_id = nil
stack_length.times do |idx|
idx += i
frame_id = raw_samples[idx]
if prev_frame_id
prev_frame = frames[prev_frame_id]
prev_frame[:edges][frame_id] ||= 0
prev_frame[:edges][frame_id] += 1
end
frame_info = frames[frame_id]
frame_info[:total_samples] += 1
frame_info[:lines][line_samples[idx]] ||= [0, 0]
frame_info[:lines][line_samples[idx]][0] += 1
prev_frame_id = frame_id
end
i += stack_length # consume the stack
top_frame_id = prev_frame_id
top_frame_line = 1
sample_count = raw_samples[i]
frames[top_frame_id][:samples] += sample_count
frames[top_frame_id][:lines] ||= {}
frames[top_frame_id][:lines][top_frame_line] ||= [0, 0]
frames[top_frame_id][:lines][top_frame_line][1] += sample_count
samples_count += sample_count
i += 1
end
results[:samples] = samples_count
# Set missed_samples and gc_samples to 0 as their values
# don't matter to us in this context.
results[:missed_samples] = 0
results[:gc_samples] = 0
results
end
end
end
share/ruby/ruby_vm/rjit/c_type.rb 0000644 00000005235 15173517737 0013117 0 ustar 00 require 'fiddle'
require 'fiddle/pack'
require_relative 'c_pointer'
module RubyVM::RJIT
module CType
module Struct
# @param name [String]
# @param members [Hash{ Symbol => [Integer, RubyVM::RJIT::CType::*] }]
def self.new(name, sizeof, **members)
name = members.keys.join('_') if name.empty?
CPointer.with_class_name('Struct', name) do
CPointer::Struct.define(sizeof, members)
end
end
end
module Union
# @param name [String]
# @param members [Hash{ Symbol => RubyVM::RJIT::CType::* }]
def self.new(name, sizeof, **members)
name = members.keys.join('_') if name.empty?
CPointer.with_class_name('Union', name) do
CPointer::Union.define(sizeof, members)
end
end
end
module Immediate
# @param fiddle_type [Integer]
def self.new(fiddle_type)
name = Fiddle.constants.find do |const|
const.start_with?('TYPE_') && Fiddle.const_get(const) == fiddle_type.abs
end&.to_s
name.delete_prefix!('TYPE_')
if fiddle_type.negative?
name.prepend('U')
end
CPointer.with_class_name('Immediate', name, cache: true) do
CPointer::Immediate.define(fiddle_type)
end
end
# @param type [String]
def self.parse(ctype)
new(Fiddle::Importer.parse_ctype(ctype))
end
def self.find(size, signed)
fiddle_type = TYPE_MAP.fetch(size)
fiddle_type = -fiddle_type unless signed
new(fiddle_type)
end
TYPE_MAP = Fiddle::PackInfo::SIZE_MAP.map { |type, size| [size, type.abs] }.to_h
private_constant :TYPE_MAP
end
module Bool
def self.new
CPointer::Bool
end
end
class Array
def self.new(&block)
CPointer.with_class_name('Array', block.object_id.to_s) do
CPointer::Array.define(block)
end
end
end
class Pointer
# This takes a block to avoid "stack level too deep" on a cyclic reference
# @param block [Proc]
def self.new(&block)
CPointer.with_class_name('Pointer', block.object_id.to_s) do
CPointer::Pointer.define(block)
end
end
end
module BitField
# @param width [Integer]
# @param offset [Integer]
def self.new(width, offset)
CPointer.with_class_name('BitField', "#{offset}_#{width}") do
CPointer::BitField.define(width, offset)
end
end
end
# Types that are referenced but not part of code generation targets
Stub = ::Struct.new(:name)
# Types that it failed to figure out from the header
Unknown = Module.new
end
end
share/ruby/ruby_vm/rjit/jit_state.rb 0000644 00000003727 15173517737 0013626 0 ustar 00 module RubyVM::RJIT
class JITState < Struct.new(
:iseq, # @param `RubyVM::RJIT::CPointer::Struct_rb_iseq_t`
:pc, # @param [Integer] The JIT target PC
:cfp, # @param `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t` The JIT source CFP (before RJIT is called)
:block, # @param [RubyVM::RJIT::Block]
:stack_size_for_pc, # @param [Integer]
:side_exit_for_pc, # @param [Hash{ Integer => Integer }] { sp_offset => address }
:record_boundary_patch_point, # @param [TrueClass,FalseClass]
)
def initialize(side_exit_for_pc: {}, record_boundary_patch_point: false, **) = super
def insn
Compiler.decode_insn(C.VALUE.new(pc).*)
end
def operand(index, signed: false, ruby: false)
addr = pc + (index + 1) * Fiddle::SIZEOF_VOIDP
value = Fiddle::Pointer.new(addr)[0, Fiddle::SIZEOF_VOIDP].unpack(signed ? 'q' : 'Q')[0]
if ruby
value = C.to_ruby(value)
end
value
end
def at_current_insn?
pc == cfp.pc.to_i
end
def peek_at_local(n)
local_table_size = iseq.body.local_table_size
offset = -C::VM_ENV_DATA_SIZE - local_table_size + n + 1
value = (cfp.ep + offset).*
C.to_ruby(value)
end
def peek_at_stack(depth_from_top)
raise 'not at current insn' unless at_current_insn?
offset = -(1 + depth_from_top)
# rb_rjit_branch_stub_hit updates SP, so you don't need to worry about sp_offset
value = (cfp.sp + offset).*
C.to_ruby(value)
end
def peek_at_self
C.to_ruby(cfp.self)
end
def peek_at_block_handler(level)
ep = ep_at_level(cfp, level:)
ep[C::VM_ENV_DATA_INDEX_SPECVAL]
end
private
def ep_at_level(cfp, level:)
ep = cfp.ep
level.times do
# VM_ENV_PREV_EP
ep = C.VALUE.new(ep[C::VM_ENV_DATA_INDEX_SPECVAL] & ~0x03)
end
ep
end
end
end
share/ruby/ruby_vm/rjit/branch_stub.rb 0000644 00000001557 15173517737 0014131 0 ustar 00 module RubyVM::RJIT
# Branch shapes
Next0 = :Next0 # target0 is a fallthrough
Next1 = :Next1 # target1 is a fallthrough
Default = :Default # neither targets is a fallthrough
class BranchStub < Struct.new(
:iseq, # @param [RubyVM::RJIT::CPointer::Struct_rb_iseq_struct] Branch target ISEQ
:shape, # @param [Symbol] Next0, Next1, or Default
:target0, # @param [RubyVM::RJIT::BranchTarget] First branch target
:target1, # @param [RubyVM::RJIT::BranchTarget,NilClass] Second branch target (optional)
:compile, # @param [Proc] A callback to (re-)generate this branch stub
:start_addr, # @param [Integer] Stub source start address to be re-generated
:end_addr, # @param [Integer] Stub source end address to be re-generated
)
end
class BranchTarget < Struct.new(
:pc,
:ctx,
:address,
)
end
end
share/ruby/ruby_vm/rjit/insn_compiler.rb 0000644 00000625267 15173517737 0014512 0 ustar 00 # frozen_string_literal: true
module RubyVM::RJIT
class InsnCompiler
# struct rb_calling_info. Storing flags instead of ci.
CallingInfo = Struct.new(:argc, :flags, :kwarg, :ci_addr, :send_shift, :block_handler) do
def kw_splat = flags & C::VM_CALL_KW_SPLAT != 0
end
# @param ocb [CodeBlock]
# @param exit_compiler [RubyVM::RJIT::ExitCompiler]
def initialize(cb, ocb, exit_compiler)
@ocb = ocb
@exit_compiler = exit_compiler
@cfunc_codegen_table = {}
register_cfunc_codegen_funcs
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
# @param insn `RubyVM::RJIT::Instruction`
def compile(jit, ctx, asm, insn)
asm.incr_counter(:rjit_insns_count)
stack = ctx.stack_size.times.map do |stack_idx|
ctx.get_opnd_type(StackOpnd[ctx.stack_size - stack_idx - 1]).type
end
locals = jit.iseq.body.local_table_size.times.map do |local_idx|
(ctx.local_types[local_idx] || Type::Unknown).type
end
insn_idx = format('%04d', (jit.pc.to_i - jit.iseq.body.iseq_encoded.to_i) / C.VALUE.size)
asm.comment("Insn: #{insn_idx} #{insn.name} (stack: [#{stack.join(', ')}], locals: [#{locals.join(', ')}])")
# 83/102
case insn.name
when :nop then nop(jit, ctx, asm)
when :getlocal then getlocal(jit, ctx, asm)
when :setlocal then setlocal(jit, ctx, asm)
when :getblockparam then getblockparam(jit, ctx, asm)
# setblockparam
when :getblockparamproxy then getblockparamproxy(jit, ctx, asm)
when :getspecial then getspecial(jit, ctx, asm)
# setspecial
when :getinstancevariable then getinstancevariable(jit, ctx, asm)
when :setinstancevariable then setinstancevariable(jit, ctx, asm)
when :getclassvariable then getclassvariable(jit, ctx, asm)
when :setclassvariable then setclassvariable(jit, ctx, asm)
when :opt_getconstant_path then opt_getconstant_path(jit, ctx, asm)
when :getconstant then getconstant(jit, ctx, asm)
# setconstant
when :getglobal then getglobal(jit, ctx, asm)
# setglobal
when :putnil then putnil(jit, ctx, asm)
when :putself then putself(jit, ctx, asm)
when :putobject then putobject(jit, ctx, asm)
when :putspecialobject then putspecialobject(jit, ctx, asm)
when :putstring then putstring(jit, ctx, asm)
when :concatstrings then concatstrings(jit, ctx, asm)
when :anytostring then anytostring(jit, ctx, asm)
when :toregexp then toregexp(jit, ctx, asm)
when :intern then intern(jit, ctx, asm)
when :newarray then newarray(jit, ctx, asm)
# newarraykwsplat
when :duparray then duparray(jit, ctx, asm)
# duphash
when :expandarray then expandarray(jit, ctx, asm)
when :concatarray then concatarray(jit, ctx, asm)
when :splatarray then splatarray(jit, ctx, asm)
when :newhash then newhash(jit, ctx, asm)
when :newrange then newrange(jit, ctx, asm)
when :pop then pop(jit, ctx, asm)
when :dup then dup(jit, ctx, asm)
when :dupn then dupn(jit, ctx, asm)
when :swap then swap(jit, ctx, asm)
# opt_reverse
when :topn then topn(jit, ctx, asm)
when :setn then setn(jit, ctx, asm)
when :adjuststack then adjuststack(jit, ctx, asm)
when :defined then defined(jit, ctx, asm)
when :definedivar then definedivar(jit, ctx, asm)
# checkmatch
when :checkkeyword then checkkeyword(jit, ctx, asm)
# checktype
# defineclass
# definemethod
# definesmethod
when :send then send(jit, ctx, asm)
when :opt_send_without_block then opt_send_without_block(jit, ctx, asm)
when :objtostring then objtostring(jit, ctx, asm)
when :opt_str_freeze then opt_str_freeze(jit, ctx, asm)
when :opt_nil_p then opt_nil_p(jit, ctx, asm)
# opt_str_uminus
when :opt_newarray_send then opt_newarray_send(jit, ctx, asm)
when :invokesuper then invokesuper(jit, ctx, asm)
when :invokeblock then invokeblock(jit, ctx, asm)
when :leave then leave(jit, ctx, asm)
when :throw then throw(jit, ctx, asm)
when :jump then jump(jit, ctx, asm)
when :branchif then branchif(jit, ctx, asm)
when :branchunless then branchunless(jit, ctx, asm)
when :branchnil then branchnil(jit, ctx, asm)
# once
when :opt_case_dispatch then opt_case_dispatch(jit, ctx, asm)
when :opt_plus then opt_plus(jit, ctx, asm)
when :opt_minus then opt_minus(jit, ctx, asm)
when :opt_mult then opt_mult(jit, ctx, asm)
when :opt_div then opt_div(jit, ctx, asm)
when :opt_mod then opt_mod(jit, ctx, asm)
when :opt_eq then opt_eq(jit, ctx, asm)
when :opt_neq then opt_neq(jit, ctx, asm)
when :opt_lt then opt_lt(jit, ctx, asm)
when :opt_le then opt_le(jit, ctx, asm)
when :opt_gt then opt_gt(jit, ctx, asm)
when :opt_ge then opt_ge(jit, ctx, asm)
when :opt_ltlt then opt_ltlt(jit, ctx, asm)
when :opt_and then opt_and(jit, ctx, asm)
when :opt_or then opt_or(jit, ctx, asm)
when :opt_aref then opt_aref(jit, ctx, asm)
when :opt_aset then opt_aset(jit, ctx, asm)
# opt_aset_with
# opt_aref_with
when :opt_length then opt_length(jit, ctx, asm)
when :opt_size then opt_size(jit, ctx, asm)
when :opt_empty_p then opt_empty_p(jit, ctx, asm)
when :opt_succ then opt_succ(jit, ctx, asm)
when :opt_not then opt_not(jit, ctx, asm)
when :opt_regexpmatch2 then opt_regexpmatch2(jit, ctx, asm)
# invokebuiltin
when :opt_invokebuiltin_delegate then opt_invokebuiltin_delegate(jit, ctx, asm)
when :opt_invokebuiltin_delegate_leave then opt_invokebuiltin_delegate_leave(jit, ctx, asm)
when :getlocal_WC_0 then getlocal_WC_0(jit, ctx, asm)
when :getlocal_WC_1 then getlocal_WC_1(jit, ctx, asm)
when :setlocal_WC_0 then setlocal_WC_0(jit, ctx, asm)
when :setlocal_WC_1 then setlocal_WC_1(jit, ctx, asm)
when :putobject_INT2FIX_0_ then putobject_INT2FIX_0_(jit, ctx, asm)
when :putobject_INT2FIX_1_ then putobject_INT2FIX_1_(jit, ctx, asm)
else CantCompile
end
end
private
#
# Insns
#
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def nop(jit, ctx, asm)
# Do nothing
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getlocal(jit, ctx, asm)
idx = jit.operand(0)
level = jit.operand(1)
jit_getlocal_generic(jit, ctx, asm, idx:, level:)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getlocal_WC_0(jit, ctx, asm)
idx = jit.operand(0)
jit_getlocal_generic(jit, ctx, asm, idx:, level: 0)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getlocal_WC_1(jit, ctx, asm)
idx = jit.operand(0)
jit_getlocal_generic(jit, ctx, asm, idx:, level: 1)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setlocal(jit, ctx, asm)
idx = jit.operand(0)
level = jit.operand(1)
jit_setlocal_generic(jit, ctx, asm, idx:, level:)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setlocal_WC_0(jit, ctx, asm)
idx = jit.operand(0)
jit_setlocal_generic(jit, ctx, asm, idx:, level: 0)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setlocal_WC_1(jit, ctx, asm)
idx = jit.operand(0)
jit_setlocal_generic(jit, ctx, asm, idx:, level: 1)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getblockparam(jit, ctx, asm)
# EP level
level = jit.operand(1)
# Save the PC and SP because we might allocate
jit_prepare_routine_call(jit, ctx, asm)
# A mirror of the interpreter code. Checking for the case
# where it's pushing rb_block_param_proxy.
side_exit = side_exit(jit, ctx)
# Load environment pointer EP from CFP
ep_reg = :rax
jit_get_ep(asm, level, reg: ep_reg)
# Bail when VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) is non zero
# FIXME: This is testing bits in the same place that the WB check is testing.
# We should combine these at some point
asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)
# If the frame flag has been modified, then the actual proc value is
# already in the EP and we should just use the value.
frame_flag_modified = asm.new_label('frame_flag_modified')
asm.jnz(frame_flag_modified)
# This instruction writes the block handler to the EP. If we need to
# fire a write barrier for the write, then exit (we'll let the
# interpreter handle it so it can fire the write barrier).
# flags & VM_ENV_FLAG_WB_REQUIRED
asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_ENV_FLAG_WB_REQUIRED)
# if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0
asm.jnz(side_exit)
# Convert the block handler in to a proc
# call rb_vm_bh_to_procval(const rb_execution_context_t *ec, VALUE block_handler)
asm.mov(C_ARGS[0], EC)
# The block handler for the current frame
# note, VM_ASSERT(VM_ENV_LOCAL_P(ep))
asm.mov(C_ARGS[1], [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL])
asm.call(C.rb_vm_bh_to_procval)
# Load environment pointer EP from CFP (again)
ep_reg = :rcx
jit_get_ep(asm, level, reg: ep_reg)
# Write the value at the environment pointer
idx = jit.operand(0)
offs = -(C.VALUE.size * idx)
asm.mov([ep_reg, offs], C_RET);
# Set the frame modified flag
asm.mov(:rax, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS]) # flag_check
asm.or(:rax, C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) # modified_flag
asm.mov([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], :rax)
asm.write_label(frame_flag_modified)
# Push the proc on the stack
stack_ret = ctx.stack_push(Type::Unknown)
ep_reg = :rax
jit_get_ep(asm, level, reg: ep_reg)
asm.mov(:rax, [ep_reg, offs])
asm.mov(stack_ret, :rax)
KeepCompiling
end
# setblockparam
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getblockparamproxy(jit, ctx, asm)
# To get block_handler
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
starting_context = ctx.dup # make a copy for use with jit_chain_guard
# A mirror of the interpreter code. Checking for the case
# where it's pushing rb_block_param_proxy.
side_exit = side_exit(jit, ctx)
# EP level
level = jit.operand(1)
# Peek at the block handler so we can check whether it's nil
comptime_handler = jit.peek_at_block_handler(level)
# When a block handler is present, it should always be a GC-guarded
# pointer (VM_BH_ISEQ_BLOCK_P)
if comptime_handler != 0 && comptime_handler & 0x3 != 0x1
asm.incr_counter(:getblockpp_not_gc_guarded)
return CantCompile
end
# Load environment pointer EP from CFP
ep_reg = :rax
jit_get_ep(asm, level, reg: ep_reg)
# Bail when VM_ENV_FLAGS(ep, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM) is non zero
asm.test([ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS], C::VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)
asm.jnz(counted_exit(side_exit, :getblockpp_block_param_modified))
# Load the block handler for the current frame
# note, VM_ASSERT(VM_ENV_LOCAL_P(ep))
block_handler = :rax
asm.mov(block_handler, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL])
# Specialize compilation for the case where no block handler is present
if comptime_handler == 0
# Bail if there is a block handler
asm.cmp(block_handler, 0)
jit_chain_guard(:jnz, jit, starting_context, asm, counted_exit(side_exit, :getblockpp_block_handler_none))
putobject(jit, ctx, asm, val: Qnil)
else
# Block handler is a tagged pointer. Look at the tag. 0x03 is from VM_BH_ISEQ_BLOCK_P().
asm.and(block_handler, 0x3)
# Bail unless VM_BH_ISEQ_BLOCK_P(bh). This also checks for null.
asm.cmp(block_handler, 0x1)
jit_chain_guard(:jnz, jit, starting_context, asm, counted_exit(side_exit, :getblockpp_not_iseq_block))
# Push rb_block_param_proxy. It's a root, so no need to use jit_mov_gc_ptr.
top = ctx.stack_push(Type::BlockParamProxy)
asm.mov(:rax, C.rb_block_param_proxy)
asm.mov(top, :rax)
end
jump_to_next_insn(jit, ctx, asm)
EndBlock
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getspecial(jit, ctx, asm)
# This takes two arguments, key and type
# key is only used when type == 0
# A non-zero type determines which type of backref to fetch
#rb_num_t key = jit.jit_get_arg(0);
rtype = jit.operand(1)
if rtype == 0
# not yet implemented
return CantCompile;
elsif rtype & 0x01 != 0
# Fetch a "special" backref based on a char encoded by shifting by 1
# Can raise if matchdata uninitialized
jit_prepare_routine_call(jit, ctx, asm)
# call rb_backref_get()
asm.comment('rb_backref_get')
asm.call(C.rb_backref_get)
asm.mov(C_ARGS[0], C_RET) # backref
case [rtype >> 1].pack('c')
in ?&
asm.comment("rb_reg_last_match")
asm.call(C.rb_reg_last_match)
in ?`
asm.comment("rb_reg_match_pre")
asm.call(C.rb_reg_match_pre)
in ?'
asm.comment("rb_reg_match_post")
asm.call(C.rb_reg_match_post)
in ?+
asm.comment("rb_reg_match_last")
asm.call(C.rb_reg_match_last)
end
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
else
# Fetch the N-th match from the last backref based on type shifted by 1
# Can raise if matchdata uninitialized
jit_prepare_routine_call(jit, ctx, asm)
# call rb_backref_get()
asm.comment('rb_backref_get')
asm.call(C.rb_backref_get)
# rb_reg_nth_match((int)(type >> 1), backref);
asm.comment('rb_reg_nth_match')
asm.mov(C_ARGS[0], rtype >> 1)
asm.mov(C_ARGS[1], C_RET) # backref
asm.call(C.rb_reg_nth_match)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
end
# setspecial
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getinstancevariable(jit, ctx, asm)
# Specialize on a compile-time receiver, and split a block for chain guards
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
id = jit.operand(0)
comptime_obj = jit.peek_at_self
jit_getivar(jit, ctx, asm, comptime_obj, id, nil, SelfOpnd)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setinstancevariable(jit, ctx, asm)
starting_context = ctx.dup # make a copy for use with jit_chain_guard
# Defer compilation so we can specialize on a runtime `self`
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
ivar_name = jit.operand(0)
comptime_receiver = jit.peek_at_self
# If the comptime receiver is frozen, writing an IV will raise an exception
# and we don't want to JIT code to deal with that situation.
if C.rb_obj_frozen_p(comptime_receiver)
asm.incr_counter(:setivar_frozen)
return CantCompile
end
# Check if the comptime receiver is a T_OBJECT
receiver_t_object = C::BUILTIN_TYPE(comptime_receiver) == C::T_OBJECT
# If the receiver isn't a T_OBJECT, or uses a custom allocator,
# then just write out the IV write as a function call.
# too-complex shapes can't use index access, so we use rb_ivar_get for them too.
if !receiver_t_object || shape_too_complex?(comptime_receiver) || ctx.chain_depth >= 10
asm.comment('call rb_vm_setinstancevariable')
ic = jit.operand(1)
# The function could raise exceptions.
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm)
# Get the operands from the stack
val_opnd = ctx.stack_pop(1)
# Call rb_vm_setinstancevariable(iseq, obj, id, val, ic);
asm.mov(:rdi, jit.iseq.to_i)
asm.mov(:rsi, [CFP, C.rb_control_frame_t.offsetof(:self)])
asm.mov(:rdx, ivar_name)
asm.mov(:rcx, val_opnd)
asm.mov(:r8, ic)
asm.call(C.rb_vm_setinstancevariable)
else
# Get the iv index
shape_id = C.rb_shape_get_shape_id(comptime_receiver)
ivar_index = C.rb_shape_get_iv_index(shape_id, ivar_name)
# Get the receiver
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)])
# Generate a side exit
side_exit = side_exit(jit, ctx)
# Upgrade type
guard_object_is_heap(jit, ctx, asm, :rax, SelfOpnd, :setivar_not_heap)
asm.comment('guard shape')
asm.cmp(DwordPtr[:rax, C.rb_shape_id_offset], shape_id)
megamorphic_side_exit = counted_exit(side_exit, :setivar_megamorphic)
jit_chain_guard(:jne, jit, starting_context, asm, megamorphic_side_exit)
# If we don't have an instance variable index, then we need to
# transition out of the current shape.
if ivar_index.nil?
shape = C.rb_shape_get_shape_by_id(shape_id)
current_capacity = shape.capacity
dest_shape = C.rb_shape_get_next_no_warnings(shape, comptime_receiver, ivar_name)
new_shape_id = C.rb_shape_id(dest_shape)
if new_shape_id == C::OBJ_TOO_COMPLEX_SHAPE_ID
asm.incr_counter(:setivar_too_complex)
return CantCompile
end
ivar_index = shape.next_iv_index
# If the new shape has a different capacity, we need to
# reallocate the object.
needs_extension = dest_shape.capacity != shape.capacity
if needs_extension
# Generate the C call so that runtime code will increase
# the capacity and set the buffer.
asm.mov(C_ARGS[0], :rax)
asm.mov(C_ARGS[1], current_capacity)
asm.mov(C_ARGS[2], dest_shape.capacity)
asm.call(C.rb_ensure_iv_list_size)
# Load the receiver again after the function call
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)])
end
write_val = ctx.stack_pop(1)
jit_write_iv(asm, comptime_receiver, :rax, :rcx, ivar_index, write_val, needs_extension)
# Store the new shape
asm.comment('write shape')
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) # reload after jit_write_iv
asm.mov(DwordPtr[:rax, C.rb_shape_id_offset], new_shape_id)
else
# If the iv index already exists, then we don't need to
# transition to a new shape. The reason is because we find
# the iv index by searching up the shape tree. If we've
# made the transition already, then there's no reason to
# update the shape on the object. Just set the IV.
write_val = ctx.stack_pop(1)
jit_write_iv(asm, comptime_receiver, :rax, :rcx, ivar_index, write_val, false)
end
skip_wb = asm.new_label('skip_wb')
# If the value we're writing is an immediate, we don't need to WB
asm.test(write_val, C::RUBY_IMMEDIATE_MASK)
asm.jnz(skip_wb)
# If the value we're writing is nil or false, we don't need to WB
asm.cmp(write_val, Qnil)
asm.jbe(skip_wb)
asm.comment('write barrier')
asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:self)]) # reload after jit_write_iv
asm.mov(C_ARGS[1], write_val)
asm.call(C.rb_gc_writebarrier)
asm.write_label(skip_wb)
end
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getclassvariable(jit, ctx, asm)
# rb_vm_getclassvariable can raise exceptions.
jit_prepare_routine_call(jit, ctx, asm)
asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:iseq)])
asm.mov(C_ARGS[1], CFP)
asm.mov(C_ARGS[2], jit.operand(0))
asm.mov(C_ARGS[3], jit.operand(1))
asm.call(C.rb_vm_getclassvariable)
top = ctx.stack_push(Type::Unknown)
asm.mov(top, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setclassvariable(jit, ctx, asm)
# rb_vm_setclassvariable can raise exceptions.
jit_prepare_routine_call(jit, ctx, asm)
asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:iseq)])
asm.mov(C_ARGS[1], CFP)
asm.mov(C_ARGS[2], jit.operand(0))
asm.mov(C_ARGS[3], ctx.stack_pop(1))
asm.mov(C_ARGS[4], jit.operand(1))
asm.call(C.rb_vm_setclassvariable)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_getconstant_path(jit, ctx, asm)
# Cut the block for invalidation
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
ic = C.iseq_inline_constant_cache.new(jit.operand(0))
idlist = ic.segments
# Make sure there is an exit for this block as the interpreter might want
# to invalidate this block from rb_rjit_constant_ic_update().
# For now, we always take an entry exit even if it was a side exit.
Invariants.ensure_block_entry_exit(jit, cause: 'opt_getconstant_path')
# See vm_ic_hit_p(). The same conditions are checked in yjit_constant_ic_update().
ice = ic.entry
if ice.nil?
# In this case, leave a block that unconditionally side exits
# for the interpreter to invalidate.
asm.incr_counter(:optgetconst_not_cached)
return CantCompile
end
if ice.ic_cref # with cref
# Cache is keyed on a certain lexical scope. Use the interpreter's cache.
side_exit = side_exit(jit, ctx)
# Call function to verify the cache. It doesn't allocate or call methods.
asm.mov(C_ARGS[0], ic.to_i)
asm.mov(C_ARGS[1], [CFP, C.rb_control_frame_t.offsetof(:ep)])
asm.call(C.rb_vm_ic_hit_p)
# Check the result. SysV only specifies one byte for _Bool return values,
# so it's important we only check one bit to ignore the higher bits in the register.
asm.test(C_RET, 1)
asm.jz(counted_exit(side_exit, :optgetconst_cache_miss))
asm.mov(:rax, ic.to_i) # inline_cache
asm.mov(:rax, [:rax, C.iseq_inline_constant_cache.offsetof(:entry)]) # ic_entry
asm.mov(:rax, [:rax, C.iseq_inline_constant_cache_entry.offsetof(:value)]) # ic_entry_val
# Push ic->entry->value
stack_top = ctx.stack_push(Type::Unknown)
asm.mov(stack_top, :rax)
else # without cref
# TODO: implement this
# Optimize for single ractor mode.
# if !assume_single_ractor_mode(jit, ocb)
# return CantCompile
# end
# Invalidate output code on any constant writes associated with
# constants referenced within the current block.
Invariants.assume_stable_constant_names(jit, idlist)
putobject(jit, ctx, asm, val: ice.value)
end
jump_to_next_insn(jit, ctx, asm)
EndBlock
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getconstant(jit, ctx, asm)
id = jit.operand(0)
# vm_get_ev_const can raise exceptions.
jit_prepare_routine_call(jit, ctx, asm)
allow_nil_opnd = ctx.stack_pop(1)
klass_opnd = ctx.stack_pop(1)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], klass_opnd)
asm.mov(C_ARGS[2], id)
asm.mov(C_ARGS[3], allow_nil_opnd)
asm.call(C.rb_vm_get_ev_const)
top = ctx.stack_push(Type::Unknown)
asm.mov(top, C_RET)
KeepCompiling
end
# setconstant
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def getglobal(jit, ctx, asm)
gid = jit.operand(0)
# Save the PC and SP because we might make a Ruby call for warning
jit_prepare_routine_call(jit, ctx, asm)
asm.mov(C_ARGS[0], gid)
asm.call(C.rb_gvar_get)
top = ctx.stack_push(Type::Unknown)
asm.mov(top, C_RET)
KeepCompiling
end
# setglobal
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putnil(jit, ctx, asm)
putobject(jit, ctx, asm, val: Qnil)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putself(jit, ctx, asm)
stack_top = ctx.stack_push_self
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)])
asm.mov(stack_top, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putobject(jit, ctx, asm, val: jit.operand(0))
# Push it to the stack
val_type = Type.from(C.to_ruby(val))
stack_top = ctx.stack_push(val_type)
if asm.imm32?(val)
asm.mov(stack_top, val)
else # 64-bit immediates can't be directly written to memory
asm.mov(:rax, val)
asm.mov(stack_top, :rax)
end
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putspecialobject(jit, ctx, asm)
object_type = jit.operand(0)
if object_type == C::VM_SPECIAL_OBJECT_VMCORE
stack_top = ctx.stack_push(Type::UnknownHeap)
asm.mov(:rax, C.rb_mRubyVMFrozenCore)
asm.mov(stack_top, :rax)
KeepCompiling
else
# TODO: implement for VM_SPECIAL_OBJECT_CBASE and
# VM_SPECIAL_OBJECT_CONST_BASE
CantCompile
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putstring(jit, ctx, asm)
put_val = jit.operand(0, ruby: true)
# Save the PC and SP because the callee will allocate
jit_prepare_routine_call(jit, ctx, asm)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], to_value(put_val))
asm.call(C.rb_ec_str_resurrect)
stack_top = ctx.stack_push(Type::TString)
asm.mov(stack_top, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def concatstrings(jit, ctx, asm)
n = jit.operand(0)
# Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, ctx, asm)
asm.lea(:rax, ctx.sp_opnd(-C.VALUE.size * n))
# call rb_str_concat_literals(size_t n, const VALUE *strings);
asm.mov(C_ARGS[0], n)
asm.mov(C_ARGS[1], :rax)
asm.call(C.rb_str_concat_literals)
ctx.stack_pop(n)
stack_ret = ctx.stack_push(Type::TString)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def anytostring(jit, ctx, asm)
# Save the PC and SP since we might call #to_s
jit_prepare_routine_call(jit, ctx, asm)
str = ctx.stack_pop(1)
val = ctx.stack_pop(1)
asm.mov(C_ARGS[0], str)
asm.mov(C_ARGS[1], val)
asm.call(C.rb_obj_as_string_result)
# Push the return value
stack_ret = ctx.stack_push(Type::TString)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def toregexp(jit, ctx, asm)
opt = jit.operand(0, signed: true)
cnt = jit.operand(1)
# Save the PC and SP because this allocates an object and could
# raise an exception.
jit_prepare_routine_call(jit, ctx, asm)
asm.lea(:rax, ctx.sp_opnd(-C.VALUE.size * cnt)) # values_ptr
ctx.stack_pop(cnt)
asm.mov(C_ARGS[0], 0)
asm.mov(C_ARGS[1], cnt)
asm.mov(C_ARGS[2], :rax) # values_ptr
asm.call(C.rb_ary_tmp_new_from_values)
# Save the array so we can clear it later
asm.push(C_RET)
asm.push(C_RET) # Alignment
asm.mov(C_ARGS[0], C_RET)
asm.mov(C_ARGS[1], opt)
asm.call(C.rb_reg_new_ary)
# The actual regex is in RAX now. Pop the temp array from
# rb_ary_tmp_new_from_values into C arg regs so we can clear it
asm.pop(:rcx) # Alignment
asm.pop(:rcx) # ary
# The value we want to push on the stack is in RAX right now
stack_ret = ctx.stack_push(Type::UnknownHeap)
asm.mov(stack_ret, C_RET)
# Clear the temp array.
asm.mov(C_ARGS[0], :rcx) # ary
asm.call(C.rb_ary_clear)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def intern(jit, ctx, asm)
# Save the PC and SP because we might allocate
jit_prepare_routine_call(jit, ctx, asm);
str = ctx.stack_pop(1)
asm.mov(C_ARGS[0], str)
asm.call(C.rb_str_intern)
# Push the return value
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def newarray(jit, ctx, asm)
n = jit.operand(0)
# Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, ctx, asm)
# If n is 0, then elts is never going to be read, so we can just pass null
if n == 0
values_ptr = 0
else
asm.comment('load pointer to array elts')
offset_magnitude = C.VALUE.size * n
values_opnd = ctx.sp_opnd(-(offset_magnitude))
asm.lea(:rax, values_opnd)
values_ptr = :rax
end
# call rb_ec_ary_new_from_values(struct rb_execution_context_struct *ec, long n, const VALUE *elts);
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], n)
asm.mov(C_ARGS[2], values_ptr)
asm.call(C.rb_ec_ary_new_from_values)
ctx.stack_pop(n)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# newarraykwsplat
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def duparray(jit, ctx, asm)
ary = jit.operand(0)
# Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, ctx, asm)
# call rb_ary_resurrect(VALUE ary);
asm.comment('call rb_ary_resurrect')
asm.mov(C_ARGS[0], ary)
asm.call(C.rb_ary_resurrect)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# duphash
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def expandarray(jit, ctx, asm)
# Both arguments are rb_num_t which is unsigned
num = jit.operand(0)
flag = jit.operand(1)
# If this instruction has the splat flag, then bail out.
if flag & 0x01 != 0
asm.incr_counter(:expandarray_splat)
return CantCompile
end
# If this instruction has the postarg flag, then bail out.
if flag & 0x02 != 0
asm.incr_counter(:expandarray_postarg)
return CantCompile
end
side_exit = side_exit(jit, ctx)
array_opnd = ctx.stack_opnd(0)
array_stack_opnd = StackOpnd[0]
# num is the number of requested values. If there aren't enough in the
# array then we're going to push on nils.
if ctx.get_opnd_type(array_stack_opnd) == Type::Nil
ctx.stack_pop(1) # pop after using the type info
# special case for a, b = nil pattern
# push N nils onto the stack
num.times do
push_opnd = ctx.stack_push(Type::Nil)
asm.mov(push_opnd, Qnil)
end
return KeepCompiling
end
# Move the array from the stack and check that it's an array.
asm.mov(:rax, array_opnd)
guard_object_is_array(jit, ctx, asm, :rax, :rcx, array_stack_opnd, :expandarray_not_array)
ctx.stack_pop(1) # pop after using the type info
# If we don't actually want any values, then just return.
if num == 0
return KeepCompiling
end
jit_array_len(asm, :rax, :rcx)
# Only handle the case where the number of values in the array is greater
# than or equal to the number of values requested.
asm.cmp(:rcx, num)
asm.jl(counted_exit(side_exit, :expandarray_rhs_too_small))
# Conditionally load the address of the heap array into REG1.
# (struct RArray *)(obj)->as.heap.ptr
#asm.mov(:rax, array_opnd)
asm.mov(:rcx, [:rax, C.RBasic.offsetof(:flags)])
asm.test(:rcx, C::RARRAY_EMBED_FLAG);
asm.mov(:rcx, [:rax, C.RArray.offsetof(:as, :heap, :ptr)])
# Load the address of the embedded array into REG1.
# (struct RArray *)(obj)->as.ary
asm.lea(:rax, [:rax, C.RArray.offsetof(:as, :ary)])
asm.cmovnz(:rcx, :rax)
# Loop backward through the array and push each element onto the stack.
(num - 1).downto(0).each do |i|
top = ctx.stack_push(Type::Unknown)
asm.mov(:rax, [:rcx, i * C.VALUE.size])
asm.mov(top, :rax)
end
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def concatarray(jit, ctx, asm)
# Save the PC and SP because the callee may allocate
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm)
# Get the operands from the stack
ary2st_opnd = ctx.stack_pop(1)
ary1_opnd = ctx.stack_pop(1)
# Call rb_vm_concat_array(ary1, ary2st)
asm.mov(C_ARGS[0], ary1_opnd)
asm.mov(C_ARGS[1], ary2st_opnd)
asm.call(C.rb_vm_concat_array)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def splatarray(jit, ctx, asm)
flag = jit.operand(0)
# Save the PC and SP because the callee may allocate
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm)
# Get the operands from the stack
ary_opnd = ctx.stack_pop(1)
# Call rb_vm_splat_array(flag, ary)
asm.mov(C_ARGS[0], flag)
asm.mov(C_ARGS[1], ary_opnd)
asm.call(C.rb_vm_splat_array)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def newhash(jit, ctx, asm)
num = jit.operand(0)
# Save the PC and SP because we are allocating
jit_prepare_routine_call(jit, ctx, asm)
if num != 0
# val = rb_hash_new_with_size(num / 2);
asm.mov(C_ARGS[0], num / 2)
asm.call(C.rb_hash_new_with_size)
# Save the allocated hash as we want to push it after insertion
asm.push(C_RET)
asm.push(C_RET) # x86 alignment
# Get a pointer to the values to insert into the hash
asm.lea(:rcx, ctx.stack_opnd(num - 1))
# rb_hash_bulk_insert(num, STACK_ADDR_FROM_TOP(num), val);
asm.mov(C_ARGS[0], num)
asm.mov(C_ARGS[1], :rcx)
asm.mov(C_ARGS[2], C_RET)
asm.call(C.rb_hash_bulk_insert)
asm.pop(:rax)
asm.pop(:rax)
ctx.stack_pop(num)
stack_ret = ctx.stack_push(Type::Hash)
asm.mov(stack_ret, :rax)
else
# val = rb_hash_new();
asm.call(C.rb_hash_new)
stack_ret = ctx.stack_push(Type::Hash)
asm.mov(stack_ret, C_RET)
end
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def newrange(jit, ctx, asm)
flag = jit.operand(0)
# rb_range_new() allocates and can raise
jit_prepare_routine_call(jit, ctx, asm)
# val = rb_range_new(low, high, (int)flag);
asm.mov(C_ARGS[0], ctx.stack_opnd(1))
asm.mov(C_ARGS[1], ctx.stack_opnd(0))
asm.mov(C_ARGS[2], flag)
asm.call(C.rb_range_new)
ctx.stack_pop(2)
stack_ret = ctx.stack_push(Type::UnknownHeap)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def pop(jit, ctx, asm)
ctx.stack_pop
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def dup(jit, ctx, asm)
dup_val = ctx.stack_opnd(0)
mapping, tmp_type = ctx.get_opnd_mapping(StackOpnd[0])
loc0 = ctx.stack_push_mapping([mapping, tmp_type])
asm.mov(:rax, dup_val)
asm.mov(loc0, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def dupn(jit, ctx, asm)
n = jit.operand(0)
# In practice, seems to be only used for n==2
if n != 2
return CantCompile
end
opnd1 = ctx.stack_opnd(1)
opnd0 = ctx.stack_opnd(0)
mapping1 = ctx.get_opnd_mapping(StackOpnd[1])
mapping0 = ctx.get_opnd_mapping(StackOpnd[0])
dst1 = ctx.stack_push_mapping(mapping1)
asm.mov(:rax, opnd1)
asm.mov(dst1, :rax)
dst0 = ctx.stack_push_mapping(mapping0)
asm.mov(:rax, opnd0)
asm.mov(dst0, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def swap(jit, ctx, asm)
stack_swap(jit, ctx, asm, 0, 1)
KeepCompiling
end
# opt_reverse
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def topn(jit, ctx, asm)
n = jit.operand(0)
top_n_val = ctx.stack_opnd(n)
mapping = ctx.get_opnd_mapping(StackOpnd[n])
loc0 = ctx.stack_push_mapping(mapping)
asm.mov(:rax, top_n_val)
asm.mov(loc0, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def setn(jit, ctx, asm)
n = jit.operand(0)
top_val = ctx.stack_pop(0)
dst_opnd = ctx.stack_opnd(n)
asm.mov(:rax, top_val)
asm.mov(dst_opnd, :rax)
mapping = ctx.get_opnd_mapping(StackOpnd[0])
ctx.set_opnd_mapping(StackOpnd[n], mapping)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def adjuststack(jit, ctx, asm)
n = jit.operand(0)
ctx.stack_pop(n)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def defined(jit, ctx, asm)
op_type = jit.operand(0)
obj = jit.operand(1, ruby: true)
pushval = jit.operand(2, ruby: true)
# Save the PC and SP because the callee may allocate
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm)
# Get the operands from the stack
v_opnd = ctx.stack_pop(1)
# Call vm_defined(ec, reg_cfp, op_type, obj, v)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], CFP)
asm.mov(C_ARGS[2], op_type)
asm.mov(C_ARGS[3], to_value(obj))
asm.mov(C_ARGS[4], v_opnd)
asm.call(C.rb_vm_defined)
asm.test(C_RET, 255)
asm.mov(:rax, Qnil)
asm.mov(:rcx, to_value(pushval))
asm.cmovnz(:rax, :rcx)
# Push the return value onto the stack
out_type = if C::SPECIAL_CONST_P(pushval)
Type::UnknownImm
else
Type::Unknown
end
stack_ret = ctx.stack_push(out_type)
asm.mov(stack_ret, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def definedivar(jit, ctx, asm)
# Defer compilation so we can specialize base on a runtime receiver
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
ivar_name = jit.operand(0)
# Value that will be pushed on the stack if the ivar is defined. In practice this is always the
# string "instance-variable". If the ivar is not defined, nil will be pushed instead.
pushval = jit.operand(2, ruby: true)
# Get the receiver
recv = :rcx
asm.mov(recv, [CFP, C.rb_control_frame_t.offsetof(:self)])
# Specialize base on compile time values
comptime_receiver = jit.peek_at_self
if shape_too_complex?(comptime_receiver)
# Fall back to calling rb_ivar_defined
# Save the PC and SP because the callee may allocate
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm) # clobbers :rax
# Call rb_ivar_defined(recv, ivar_name)
asm.mov(C_ARGS[0], recv)
asm.mov(C_ARGS[1], ivar_name)
asm.call(C.rb_ivar_defined)
# if (rb_ivar_defined(recv, ivar_name)) {
# val = pushval;
# }
asm.test(C_RET, 255)
asm.mov(:rax, Qnil)
asm.mov(:rcx, to_value(pushval))
asm.cmovnz(:rax, :rcx)
# Push the return value onto the stack
out_type = C::SPECIAL_CONST_P(pushval) ? Type::UnknownImm : Type::Unknown
stack_ret = ctx.stack_push(out_type)
asm.mov(stack_ret, :rax)
return KeepCompiling
end
shape_id = C.rb_shape_get_shape_id(comptime_receiver)
ivar_exists = C.rb_shape_get_iv_index(shape_id, ivar_name)
side_exit = side_exit(jit, ctx)
# Guard heap object (recv_opnd must be used before stack_pop)
guard_object_is_heap(jit, ctx, asm, recv, SelfOpnd)
shape_opnd = DwordPtr[recv, C.rb_shape_id_offset]
asm.comment('guard shape')
asm.cmp(shape_opnd, shape_id)
jit_chain_guard(:jne, jit, ctx, asm, side_exit)
result = ivar_exists ? C.to_value(pushval) : Qnil
putobject(jit, ctx, asm, val: result)
# Jump to next instruction. This allows guard chains to share the same successor.
jump_to_next_insn(jit, ctx, asm)
return EndBlock
end
# checkmatch
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def checkkeyword(jit, ctx, asm)
# When a keyword is unspecified past index 32, a hash will be used
# instead. This can only happen in iseqs taking more than 32 keywords.
if jit.iseq.body.param.keyword.num >= 32
return CantCompile
end
# The EP offset to the undefined bits local
bits_offset = jit.operand(0)
# The index of the keyword we want to check
index = jit.operand(1, signed: true)
# Load environment pointer EP
ep_reg = :rax
jit_get_ep(asm, 0, reg: ep_reg)
# VALUE kw_bits = *(ep - bits)
bits_opnd = [ep_reg, C.VALUE.size * -bits_offset]
# unsigned int b = (unsigned int)FIX2ULONG(kw_bits);
# if ((b & (0x01 << idx))) {
#
# We can skip the FIX2ULONG conversion by shifting the bit we test
bit_test = 0x01 << (index + 1)
asm.test(bits_opnd, bit_test)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmovz(:rax, :rcx)
stack_ret = ctx.stack_push(Type::UnknownImm)
asm.mov(stack_ret, :rax)
KeepCompiling
end
# checktype
# defineclass
# definemethod
# definesmethod
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def send(jit, ctx, asm)
# Specialize on a compile-time receiver, and split a block for chain guards
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
cd = C.rb_call_data.new(jit.operand(0))
blockiseq = jit.operand(1)
# calling->ci
mid = C.vm_ci_mid(cd.ci)
calling = build_calling(ci: cd.ci, block_handler: blockiseq)
# vm_sendish
cme, comptime_recv_klass = jit_search_method(jit, ctx, asm, mid, calling)
if cme == CantCompile
return CantCompile
end
jit_call_general(jit, ctx, asm, mid, calling, cme, comptime_recv_klass)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_send_without_block(jit, ctx, asm, cd: C.rb_call_data.new(jit.operand(0)))
# Specialize on a compile-time receiver, and split a block for chain guards
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
# calling->ci
mid = C.vm_ci_mid(cd.ci)
calling = build_calling(ci: cd.ci, block_handler: C::VM_BLOCK_HANDLER_NONE)
# vm_sendish
cme, comptime_recv_klass = jit_search_method(jit, ctx, asm, mid, calling)
if cme == CantCompile
return CantCompile
end
jit_call_general(jit, ctx, asm, mid, calling, cme, comptime_recv_klass)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def objtostring(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
recv = ctx.stack_opnd(0)
comptime_recv = jit.peek_at_stack(0)
if C.RB_TYPE_P(comptime_recv, C::RUBY_T_STRING)
side_exit = side_exit(jit, ctx)
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[0], comptime_recv, side_exit)
# No work needed. The string value is already on the top of the stack.
KeepCompiling
else
cd = C.rb_call_data.new(jit.operand(0))
opt_send_without_block(jit, ctx, asm, cd:)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_str_freeze(jit, ctx, asm)
unless Invariants.assume_bop_not_redefined(jit, C::STRING_REDEFINED_OP_FLAG, C::BOP_FREEZE)
return CantCompile;
end
str = jit.operand(0, ruby: true)
# Push the return value onto the stack
stack_ret = ctx.stack_push(Type::CString)
asm.mov(:rax, to_value(str))
asm.mov(stack_ret, :rax)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_nil_p(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# opt_str_uminus
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_newarray_send(jit, ctx, asm)
type = C.ID2SYM jit.operand(1)
case type
when :min then opt_newarray_min(jit, ctx, asm)
when :max then opt_newarray_max(jit, ctx, asm)
when :hash then opt_newarray_hash(jit, ctx, asm)
else
return CantCompile
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_newarray_min(jit, ctx, asm)
num = jit.operand(0)
# Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, ctx, asm)
offset_magnitude = C.VALUE.size * num
values_opnd = ctx.sp_opnd(-offset_magnitude)
asm.lea(:rax, values_opnd)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], num)
asm.mov(C_ARGS[2], :rax)
asm.call(C.rb_vm_opt_newarray_min)
ctx.stack_pop(num)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_newarray_max(jit, ctx, asm)
num = jit.operand(0)
# Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, ctx, asm)
offset_magnitude = C.VALUE.size * num
values_opnd = ctx.sp_opnd(-offset_magnitude)
asm.lea(:rax, values_opnd)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], num)
asm.mov(C_ARGS[2], :rax)
asm.call(C.rb_vm_opt_newarray_max)
ctx.stack_pop(num)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_newarray_hash(jit, ctx, asm)
num = jit.operand(0)
# Save the PC and SP because we may allocate
jit_prepare_routine_call(jit, ctx, asm)
offset_magnitude = C.VALUE.size * num
values_opnd = ctx.sp_opnd(-offset_magnitude)
asm.lea(:rax, values_opnd)
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], num)
asm.mov(C_ARGS[2], :rax)
asm.call(C.rb_vm_opt_newarray_hash)
ctx.stack_pop(num)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def invokesuper(jit, ctx, asm)
cd = C.rb_call_data.new(jit.operand(0))
block = jit.operand(1)
# Defer compilation so we can specialize on class of receiver
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
me = C.rb_vm_frame_method_entry(jit.cfp)
if me.nil?
return CantCompile
end
# FIXME: We should track and invalidate this block when this cme is invalidated
current_defined_class = me.defined_class
mid = me.def.original_id
if me.to_i != C.rb_callable_method_entry(current_defined_class, me.called_id).to_i
# Though we likely could generate this call, as we are only concerned
# with the method entry remaining valid, assume_method_lookup_stable
# below requires that the method lookup matches as well
return CantCompile
end
# vm_search_normal_superclass
rbasic_klass = C.to_ruby(C.RBasic.new(C.to_value(current_defined_class)).klass)
if C::BUILTIN_TYPE(current_defined_class) == C::RUBY_T_ICLASS && C::BUILTIN_TYPE(rbasic_klass) == C::RUBY_T_MODULE && \
C::FL_TEST_RAW(rbasic_klass, C::RMODULE_IS_REFINEMENT)
return CantCompile
end
comptime_superclass = C.rb_class_get_superclass(C.RCLASS_ORIGIN(current_defined_class))
ci = cd.ci
argc = C.vm_ci_argc(ci)
ci_flags = C.vm_ci_flag(ci)
# Don't JIT calls that aren't simple
# Note, not using VM_CALL_ARGS_SIMPLE because sometimes we pass a block.
if ci_flags & C::VM_CALL_KWARG != 0
asm.incr_counter(:send_keywords)
return CantCompile
end
if ci_flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_kw_splat)
return CantCompile
end
if ci_flags & C::VM_CALL_ARGS_BLOCKARG != 0
asm.incr_counter(:send_block_arg)
return CantCompile
end
# Ensure we haven't rebound this method onto an incompatible class.
# In the interpreter we try to avoid making this check by performing some
# cheaper calculations first, but since we specialize on the method entry
# and so only have to do this once at compile time this is fine to always
# check and side exit.
comptime_recv = jit.peek_at_stack(argc)
unless C.obj_is_kind_of(comptime_recv, current_defined_class)
return CantCompile
end
# Do method lookup
cme = C.rb_callable_method_entry(comptime_superclass, mid)
if cme.nil?
return CantCompile
end
# Check that we'll be able to write this method dispatch before generating checks
cme_def_type = cme.def.type
if cme_def_type != C::VM_METHOD_TYPE_ISEQ && cme_def_type != C::VM_METHOD_TYPE_CFUNC
# others unimplemented
return CantCompile
end
asm.comment('guard known me')
lep_opnd = :rax
jit_get_lep(jit, asm, reg: lep_opnd)
ep_me_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_ME_CREF]
asm.mov(:rcx, me.to_i)
asm.cmp(ep_me_opnd, :rcx)
asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_me_changed))
if block == C::VM_BLOCK_HANDLER_NONE
# Guard no block passed
# rb_vm_frame_block_handler(GET_EC()->cfp) == VM_BLOCK_HANDLER_NONE
# note, we assume VM_ASSERT(VM_ENV_LOCAL_P(ep))
#
# TODO: this could properly forward the current block handler, but
# would require changes to gen_send_*
asm.comment('guard no block given')
ep_specval_opnd = [lep_opnd, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]
asm.cmp(ep_specval_opnd, C::VM_BLOCK_HANDLER_NONE)
asm.jne(counted_exit(side_exit(jit, ctx), :invokesuper_block))
end
# We need to assume that both our current method entry and the super
# method entry we invoke remain stable
Invariants.assume_method_lookup_stable(jit, me)
Invariants.assume_method_lookup_stable(jit, cme)
# Method calls may corrupt types
ctx.clear_local_types
calling = build_calling(ci:, block_handler: block)
case cme_def_type
in C::VM_METHOD_TYPE_ISEQ
iseq = def_iseq_ptr(cme.def)
frame_type = C::VM_FRAME_MAGIC_METHOD | C::VM_ENV_FLAG_LOCAL
jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type:)
in C::VM_METHOD_TYPE_CFUNC
jit_call_cfunc(jit, ctx, asm, cme, calling)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def invokeblock(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
# Get call info
cd = C.rb_call_data.new(jit.operand(0))
calling = build_calling(ci: cd.ci, block_handler: :captured)
# Get block_handler
cfp = jit.cfp
lep = C.rb_vm_ep_local_ep(cfp.ep)
comptime_handler = lep[C::VM_ENV_DATA_INDEX_SPECVAL]
# Handle each block_handler type
if comptime_handler == C::VM_BLOCK_HANDLER_NONE # no block given
asm.incr_counter(:invokeblock_none)
CantCompile
elsif comptime_handler & 0x3 == 0x1 # VM_BH_ISEQ_BLOCK_P
asm.comment('get local EP')
ep_reg = :rax
jit_get_lep(jit, asm, reg: ep_reg)
asm.mov(:rax, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler_opnd
asm.comment('guard block_handler type')
side_exit = side_exit(jit, ctx)
asm.mov(:rcx, :rax)
asm.and(:rcx, 0x3) # block_handler is a tagged pointer
asm.cmp(:rcx, 0x1) # VM_BH_ISEQ_BLOCK_P
tag_changed_exit = counted_exit(side_exit, :invokeblock_tag_changed)
jit_chain_guard(:jne, jit, ctx, asm, tag_changed_exit)
comptime_captured = C.rb_captured_block.new(comptime_handler & ~0x3)
comptime_iseq = comptime_captured.code.iseq
asm.comment('guard known ISEQ')
asm.and(:rax, ~0x3) # captured
asm.mov(:rax, [:rax, C.VALUE.size * 2]) # captured->iseq
asm.mov(:rcx, comptime_iseq.to_i)
asm.cmp(:rax, :rcx)
block_changed_exit = counted_exit(side_exit, :invokeblock_iseq_block_changed)
jit_chain_guard(:jne, jit, ctx, asm, block_changed_exit)
jit_call_iseq(jit, ctx, asm, nil, calling, comptime_iseq, frame_type: C::VM_FRAME_MAGIC_BLOCK)
elsif comptime_handler & 0x3 == 0x3 # VM_BH_IFUNC_P
# We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet.
if calling.flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:invokeblock_ifunc_args_splat)
return CantCompile
end
if calling.flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:invokeblock_ifunc_kw_splat)
return CantCompile
end
asm.comment('get local EP')
jit_get_lep(jit, asm, reg: :rax)
asm.mov(:rcx, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler_opnd
asm.comment('guard block_handler type');
side_exit = side_exit(jit, ctx)
asm.mov(:rax, :rcx) # block_handler_opnd
asm.and(:rax, 0x3) # tag_opnd: block_handler is a tagged pointer
asm.cmp(:rax, 0x3) # VM_BH_IFUNC_P
tag_changed_exit = counted_exit(side_exit, :invokeblock_tag_changed)
jit_chain_guard(:jne, jit, ctx, asm, tag_changed_exit)
# The cfunc may not be leaf
jit_prepare_routine_call(jit, ctx, asm) # clobbers :rax
asm.comment('call ifunc')
asm.and(:rcx, ~0x3) # captured_opnd
asm.lea(:rax, ctx.sp_opnd(-calling.argc * C.VALUE.size)) # argv
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], :rcx) # captured_opnd
asm.mov(C_ARGS[2], calling.argc)
asm.mov(C_ARGS[3], :rax) # argv
asm.call(C.rb_vm_yield_with_cfunc)
ctx.stack_pop(calling.argc)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
# cfunc calls may corrupt types
ctx.clear_local_types
# Share the successor with other chains
jump_to_next_insn(jit, ctx, asm)
EndBlock
elsif symbol?(comptime_handler)
asm.incr_counter(:invokeblock_symbol)
CantCompile
else # Proc
asm.incr_counter(:invokeblock_proc)
CantCompile
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def leave(jit, ctx, asm)
assert_equal(ctx.stack_size, 1)
jit_check_ints(jit, ctx, asm)
asm.comment('pop stack frame')
asm.lea(:rax, [CFP, C.rb_control_frame_t.size])
asm.mov(CFP, :rax)
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], :rax)
# Return a value (for compile_leave_exit)
ret_opnd = ctx.stack_pop
asm.mov(:rax, ret_opnd)
# Set caller's SP and push a value to its stack (for JIT)
asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # Note: SP is in the position after popping a receiver and arguments
asm.mov([SP], :rax)
# Jump to cfp->jit_return
asm.jmp([CFP, -C.rb_control_frame_t.size + C.rb_control_frame_t.offsetof(:jit_return)])
EndBlock
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def throw(jit, ctx, asm)
throw_state = jit.operand(0)
asm.mov(:rcx, ctx.stack_pop(1)) # throwobj
# THROW_DATA_NEW allocates. Save SP for GC and PC for allocation tracing as
# well as handling the catch table. However, not using jit_prepare_routine_call
# since we don't need a patch point for this implementation.
jit_save_pc(jit, asm) # clobbers rax
jit_save_sp(ctx, asm)
# rb_vm_throw verifies it's a valid throw, sets ec->tag->state, and returns throw
# data, which is throwobj or a vm_throw_data wrapping it. When ec->tag->state is
# set, JIT code callers will handle the throw with vm_exec_handle_exception.
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], CFP)
asm.mov(C_ARGS[2], throw_state)
# asm.mov(C_ARGS[3], :rcx) # same reg
asm.call(C.rb_vm_throw)
asm.comment('exit from throw')
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
# return C_RET as C_RET
asm.ret
EndBlock
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jump(jit, ctx, asm)
# Check for interrupts, but only on backward branches that may create loops
jump_offset = jit.operand(0, signed: true)
if jump_offset < 0
jit_check_ints(jit, ctx, asm)
end
pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset)
jit_direct_jump(jit.iseq, pc, ctx, asm)
EndBlock
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def branchif(jit, ctx, asm)
# Check for interrupts, but only on backward branches that may create loops
jump_offset = jit.operand(0, signed: true)
if jump_offset < 0
jit_check_ints(jit, ctx, asm)
end
# Get the branch target instruction offsets
next_pc = jit.pc + C.VALUE.size * jit.insn.len
jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset)
val_type = ctx.get_opnd_type(StackOpnd[0])
val_opnd = ctx.stack_pop(1)
if (result = val_type.known_truthy) != nil
target_pc = result ? jump_pc : next_pc
jit_direct_jump(jit.iseq, target_pc, ctx, asm)
else
# This `test` sets ZF only for Qnil and Qfalse, which let jz jump.
asm.test(val_opnd, ~Qnil)
# Set stubs
branch_stub = BranchStub.new(
iseq: jit.iseq,
shape: Default,
target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target
target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.target1.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false)
@ocb.write(ocb_asm)
end
# Jump to target0 on jnz
branch_stub.compile = compile_branchif(branch_stub)
branch_stub.compile.call(asm)
end
EndBlock
end
def compile_branchif(branch_stub) # Proc escapes arguments in memory
proc do |branch_asm|
branch_asm.comment("branchif #{branch_stub.shape}")
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.jnz(branch_stub.target0.address)
branch_asm.jmp(branch_stub.target1.address)
in Next0
branch_asm.jz(branch_stub.target1.address)
in Next1
branch_asm.jnz(branch_stub.target0.address)
end
end
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def branchunless(jit, ctx, asm)
# Check for interrupts, but only on backward branches that may create loops
jump_offset = jit.operand(0, signed: true)
if jump_offset < 0
jit_check_ints(jit, ctx, asm)
end
# Get the branch target instruction offsets
next_pc = jit.pc + C.VALUE.size * jit.insn.len
jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset)
val_type = ctx.get_opnd_type(StackOpnd[0])
val_opnd = ctx.stack_pop(1)
if (result = val_type.known_truthy) != nil
target_pc = result ? next_pc : jump_pc
jit_direct_jump(jit.iseq, target_pc, ctx, asm)
else
# This `test` sets ZF only for Qnil and Qfalse, which let jz jump.
asm.test(val_opnd, ~Qnil)
# Set stubs
branch_stub = BranchStub.new(
iseq: jit.iseq,
shape: Default,
target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target
target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.target1.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false)
@ocb.write(ocb_asm)
end
# Jump to target0 on jz
branch_stub.compile = compile_branchunless(branch_stub)
branch_stub.compile.call(asm)
end
EndBlock
end
def compile_branchunless(branch_stub) # Proc escapes arguments in memory
proc do |branch_asm|
branch_asm.comment("branchunless #{branch_stub.shape}")
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.jz(branch_stub.target0.address)
branch_asm.jmp(branch_stub.target1.address)
in Next0
branch_asm.jnz(branch_stub.target1.address)
in Next1
branch_asm.jz(branch_stub.target0.address)
end
end
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def branchnil(jit, ctx, asm)
# Check for interrupts, but only on backward branches that may create loops
jump_offset = jit.operand(0, signed: true)
if jump_offset < 0
jit_check_ints(jit, ctx, asm)
end
# Get the branch target instruction offsets
next_pc = jit.pc + C.VALUE.size * jit.insn.len
jump_pc = jit.pc + C.VALUE.size * (jit.insn.len + jump_offset)
val_type = ctx.get_opnd_type(StackOpnd[0])
val_opnd = ctx.stack_pop(1)
if (result = val_type.known_nil) != nil
target_pc = result ? jump_pc : next_pc
jit_direct_jump(jit.iseq, target_pc, ctx, asm)
else
asm.cmp(val_opnd, Qnil)
# Set stubs
branch_stub = BranchStub.new(
iseq: jit.iseq,
shape: Default,
target0: BranchTarget.new(ctx:, pc: jump_pc), # branch target
target1: BranchTarget.new(ctx:, pc: next_pc), # fallthrough
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.target1.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, false)
@ocb.write(ocb_asm)
end
# Jump to target0 on je
branch_stub.compile = compile_branchnil(branch_stub)
branch_stub.compile.call(asm)
end
EndBlock
end
def compile_branchnil(branch_stub) # Proc escapes arguments in memory
proc do |branch_asm|
branch_asm.comment("branchnil #{branch_stub.shape}")
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.je(branch_stub.target0.address)
branch_asm.jmp(branch_stub.target1.address)
in Next0
branch_asm.jne(branch_stub.target1.address)
in Next1
branch_asm.je(branch_stub.target0.address)
end
end
end
end
# once
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_case_dispatch(jit, ctx, asm)
# Normally this instruction would lookup the key in a hash and jump to an
# offset based on that.
# Instead we can take the fallback case and continue with the next
# instruction.
# We'd hope that our jitted code will be sufficiently fast without the
# hash lookup, at least for small hashes, but it's worth revisiting this
# assumption in the future.
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
starting_context = ctx.dup
case_hash = jit.operand(0, ruby: true)
else_offset = jit.operand(1)
# Try to reorder case/else branches so that ones that are actually used come first.
# Supporting only Fixnum for now so that the implementation can be an equality check.
key_opnd = ctx.stack_pop(1)
comptime_key = jit.peek_at_stack(0)
# Check that all cases are fixnums to avoid having to register BOP assumptions on
# all the types that case hashes support. This spends compile time to save memory.
if fixnum?(comptime_key) && comptime_key <= 2**32 && C.rb_hash_keys(case_hash).all? { |key| fixnum?(key) }
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_EQQ)
return CantCompile
end
# Check if the key is the same value
asm.cmp(key_opnd, to_value(comptime_key))
side_exit = side_exit(jit, starting_context)
jit_chain_guard(:jne, jit, starting_context, asm, side_exit)
# Get the offset for the compile-time key
offset = C.rb_hash_stlike_lookup(case_hash, comptime_key)
# NOTE: If we hit the else branch with various values, it could negatively impact the performance.
jump_offset = offset || else_offset
# Jump to the offset of case or else
target_pc = jit.pc + (jit.insn.len + jump_offset) * C.VALUE.size
jit_direct_jump(jit.iseq, target_pc, ctx, asm)
EndBlock
else
KeepCompiling # continue with === branches
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_plus(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
comptime_recv = jit.peek_at_stack(1)
comptime_obj = jit.peek_at_stack(0)
if fixnum?(comptime_recv) && fixnum?(comptime_obj)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_PLUS)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
obj_opnd = ctx.stack_pop
recv_opnd = ctx.stack_pop
asm.mov(:rax, recv_opnd)
asm.sub(:rax, 1) # untag
asm.mov(:rcx, obj_opnd)
asm.add(:rax, :rcx)
asm.jo(side_exit(jit, ctx))
dst_opnd = ctx.stack_push(Type::Fixnum)
asm.mov(dst_opnd, :rax)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_minus(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
comptime_recv = jit.peek_at_stack(1)
comptime_obj = jit.peek_at_stack(0)
if fixnum?(comptime_recv) && fixnum?(comptime_obj)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_MINUS)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
obj_opnd = ctx.stack_pop
recv_opnd = ctx.stack_pop
asm.mov(:rax, recv_opnd)
asm.mov(:rcx, obj_opnd)
asm.sub(:rax, :rcx)
asm.jo(side_exit(jit, ctx))
asm.add(:rax, 1) # re-tag
dst_opnd = ctx.stack_push(Type::Fixnum)
asm.mov(dst_opnd, :rax)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_mult(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_div(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_mod(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
if two_fixnums_on_stack?(jit)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_MOD)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
# Get the operands and destination from the stack
arg1 = ctx.stack_pop(1)
arg0 = ctx.stack_pop(1)
# Check for arg0 % 0
asm.cmp(arg1, 0)
asm.je(side_exit(jit, ctx))
# Call rb_fix_mod_fix(VALUE recv, VALUE obj)
asm.mov(C_ARGS[0], arg0)
asm.mov(C_ARGS[1], arg1)
asm.call(C.rb_fix_mod_fix)
# Push the return value onto the stack
stack_ret = ctx.stack_push(Type::Fixnum)
asm.mov(stack_ret, C_RET)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_eq(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
if jit_equality_specialized(jit, ctx, asm, true)
jump_to_next_insn(jit, ctx, asm)
EndBlock
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_neq(jit, ctx, asm)
# opt_neq is passed two rb_call_data as arguments:
# first for ==, second for !=
neq_cd = C.rb_call_data.new(jit.operand(1))
opt_send_without_block(jit, ctx, asm, cd: neq_cd)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_lt(jit, ctx, asm)
jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovl, bop: C::BOP_LT)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_le(jit, ctx, asm)
jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovle, bop: C::BOP_LE)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_gt(jit, ctx, asm)
jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovg, bop: C::BOP_GT)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_ge(jit, ctx, asm)
jit_fixnum_cmp(jit, ctx, asm, opcode: :cmovge, bop: C::BOP_GE)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_ltlt(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_and(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
if two_fixnums_on_stack?(jit)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_AND)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
# Get the operands and destination from the stack
arg1 = ctx.stack_pop(1)
arg0 = ctx.stack_pop(1)
asm.comment('bitwise and')
asm.mov(:rax, arg0)
asm.and(:rax, arg1)
# Push the return value onto the stack
dst = ctx.stack_push(Type::Fixnum)
asm.mov(dst, :rax)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_or(jit, ctx, asm)
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
if two_fixnums_on_stack?(jit)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_OR)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
# Get the operands and destination from the stack
asm.comment('bitwise or')
arg1 = ctx.stack_pop(1)
arg0 = ctx.stack_pop(1)
# Do the bitwise or arg0 | arg1
asm.mov(:rax, arg0)
asm.or(:rax, arg1)
# Push the return value onto the stack
dst = ctx.stack_push(Type::Fixnum)
asm.mov(dst, :rax)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_aref(jit, ctx, asm)
cd = C.rb_call_data.new(jit.operand(0))
argc = C.vm_ci_argc(cd.ci)
if argc != 1
asm.incr_counter(:optaref_argc_not_one)
return CantCompile
end
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
comptime_recv = jit.peek_at_stack(1)
comptime_obj = jit.peek_at_stack(0)
side_exit = side_exit(jit, ctx)
if C.rb_class_of(comptime_recv) == Array && fixnum?(comptime_obj)
unless Invariants.assume_bop_not_redefined(jit, C::ARRAY_REDEFINED_OP_FLAG, C::BOP_AREF)
return CantCompile
end
idx_opnd = ctx.stack_opnd(0)
recv_opnd = ctx.stack_opnd(1)
not_array_exit = counted_exit(side_exit, :optaref_recv_not_array)
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv_opnd, StackOpnd[1], comptime_recv, not_array_exit)
# Bail if idx is not a FIXNUM
asm.mov(:rax, idx_opnd)
asm.test(:rax, C::RUBY_FIXNUM_FLAG)
asm.jz(counted_exit(side_exit, :optaref_arg_not_fixnum))
# Call VALUE rb_ary_entry_internal(VALUE ary, long offset).
# It never raises or allocates, so we don't need to write to cfp->pc.
asm.sar(:rax, 1) # Convert fixnum to int
asm.mov(C_ARGS[0], recv_opnd)
asm.mov(C_ARGS[1], :rax)
asm.call(C.rb_ary_entry_internal)
# Pop the argument and the receiver
ctx.stack_pop(2)
# Push the return value onto the stack
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
# Let guard chains share the same successor
jump_to_next_insn(jit, ctx, asm)
EndBlock
elsif C.rb_class_of(comptime_recv) == Hash
unless Invariants.assume_bop_not_redefined(jit, C::HASH_REDEFINED_OP_FLAG, C::BOP_AREF)
return CantCompile
end
recv_opnd = ctx.stack_opnd(1)
# Guard that the receiver is a Hash
not_hash_exit = counted_exit(side_exit, :optaref_recv_not_hash)
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv_opnd, StackOpnd[1], comptime_recv, not_hash_exit)
# Prepare to call rb_hash_aref(). It might call #hash on the key.
jit_prepare_routine_call(jit, ctx, asm)
asm.comment('call rb_hash_aref')
key_opnd = ctx.stack_opnd(0)
recv_opnd = ctx.stack_opnd(1)
asm.mov(:rdi, recv_opnd)
asm.mov(:rsi, key_opnd)
asm.call(C.rb_hash_aref)
# Pop the key and the receiver
ctx.stack_pop(2)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
# Let guard chains share the same successor
jump_to_next_insn(jit, ctx, asm)
EndBlock
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_aset(jit, ctx, asm)
# Defer compilation so we can specialize on a runtime `self`
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
comptime_recv = jit.peek_at_stack(2)
comptime_key = jit.peek_at_stack(1)
# Get the operands from the stack
recv = ctx.stack_opnd(2)
key = ctx.stack_opnd(1)
_val = ctx.stack_opnd(0)
if C.rb_class_of(comptime_recv) == Array && fixnum?(comptime_key)
side_exit = side_exit(jit, ctx)
# Guard receiver is an Array
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[2], comptime_recv, side_exit)
# Guard key is a fixnum
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_key), key, StackOpnd[1], comptime_key, side_exit)
# We might allocate or raise
jit_prepare_routine_call(jit, ctx, asm)
asm.comment('call rb_ary_store')
recv = ctx.stack_opnd(2)
key = ctx.stack_opnd(1)
val = ctx.stack_opnd(0)
asm.mov(:rax, key)
asm.sar(:rax, 1) # FIX2LONG(key)
asm.mov(C_ARGS[0], recv)
asm.mov(C_ARGS[1], :rax)
asm.mov(C_ARGS[2], val)
asm.call(C.rb_ary_store)
# rb_ary_store returns void
# stored value should still be on stack
val = ctx.stack_opnd(0)
# Push the return value onto the stack
ctx.stack_pop(3)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(:rax, val)
asm.mov(stack_ret, :rax)
jump_to_next_insn(jit, ctx, asm)
EndBlock
elsif C.rb_class_of(comptime_recv) == Hash
side_exit = side_exit(jit, ctx)
# Guard receiver is a Hash
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_recv), recv, StackOpnd[2], comptime_recv, side_exit)
# We might allocate or raise
jit_prepare_routine_call(jit, ctx, asm)
# Call rb_hash_aset
recv = ctx.stack_opnd(2)
key = ctx.stack_opnd(1)
val = ctx.stack_opnd(0)
asm.mov(C_ARGS[0], recv)
asm.mov(C_ARGS[1], key)
asm.mov(C_ARGS[2], val)
asm.call(C.rb_hash_aset)
# Push the return value onto the stack
ctx.stack_pop(3)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
jump_to_next_insn(jit, ctx, asm)
EndBlock
else
opt_send_without_block(jit, ctx, asm)
end
end
# opt_aset_with
# opt_aref_with
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_length(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_size(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_empty_p(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_succ(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_not(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_regexpmatch2(jit, ctx, asm)
opt_send_without_block(jit, ctx, asm)
end
# invokebuiltin
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_invokebuiltin_delegate(jit, ctx, asm)
bf = C.rb_builtin_function.new(jit.operand(0))
bf_argc = bf.argc
start_index = jit.operand(1)
# ec, self, and arguments
if bf_argc + 2 > C_ARGS.size
return CantCompile
end
# If the calls don't allocate, do they need up to date PC, SP?
jit_prepare_routine_call(jit, ctx, asm)
# Call the builtin func (ec, recv, arg1, arg2, ...)
asm.comment('call builtin func')
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], [CFP, C.rb_control_frame_t.offsetof(:self)])
# Copy arguments from locals
if bf_argc > 0
# Load environment pointer EP from CFP
asm.mov(:rax, [CFP, C.rb_control_frame_t.offsetof(:ep)])
bf_argc.times do |i|
table_size = jit.iseq.body.local_table_size
offs = -table_size - C::VM_ENV_DATA_SIZE + 1 + start_index + i
asm.mov(C_ARGS[2 + i], [:rax, offs * C.VALUE.size])
end
end
asm.call(bf.func_ptr)
# Push the return value
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
KeepCompiling
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def opt_invokebuiltin_delegate_leave(jit, ctx, asm)
opt_invokebuiltin_delegate(jit, ctx, asm)
# opt_invokebuiltin_delegate is always followed by leave insn
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putobject_INT2FIX_0_(jit, ctx, asm)
putobject(jit, ctx, asm, val: C.to_value(0))
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def putobject_INT2FIX_1_(jit, ctx, asm)
putobject(jit, ctx, asm, val: C.to_value(1))
end
#
# C func
#
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_true(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 0
asm.comment('nil? == true')
ctx.stack_pop(1)
stack_ret = ctx.stack_push(Type::True)
asm.mov(stack_ret, Qtrue)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_false(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 0
asm.comment('nil? == false')
ctx.stack_pop(1)
stack_ret = ctx.stack_push(Type::False)
asm.mov(stack_ret, Qfalse)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_kernel_is_a(jit, ctx, asm, argc, known_recv_class)
if argc != 1
return false
end
# If this is a super call we might not know the class
if known_recv_class.nil?
return false
end
# Important note: The output code will simply `return true/false`.
# Correctness follows from:
# - `known_recv_class` implies there is a guard scheduled before here
# for a particular `CLASS_OF(lhs)`.
# - We guard that rhs is identical to the compile-time sample
# - In general, for any two Class instances A, B, `A < B` does not change at runtime.
# Class#superclass is stable.
sample_rhs = jit.peek_at_stack(0)
sample_lhs = jit.peek_at_stack(1)
# We are not allowing module here because the module hierarchy can change at runtime.
if C.RB_TYPE_P(sample_rhs, C::RUBY_T_CLASS)
return false
end
sample_is_a = C.obj_is_kind_of(sample_lhs, sample_rhs)
side_exit = side_exit(jit, ctx)
asm.comment('Kernel#is_a?')
asm.mov(:rax, to_value(sample_rhs))
asm.cmp(ctx.stack_opnd(0), :rax)
asm.jne(counted_exit(side_exit, :send_is_a_class_mismatch))
ctx.stack_pop(2)
if sample_is_a
stack_ret = ctx.stack_push(Type::True)
asm.mov(stack_ret, Qtrue)
else
stack_ret = ctx.stack_push(Type::False)
asm.mov(stack_ret, Qfalse)
end
return true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_kernel_instance_of(jit, ctx, asm, argc, known_recv_class)
if argc != 1
return false
end
# If this is a super call we might not know the class
if known_recv_class.nil?
return false
end
# Important note: The output code will simply `return true/false`.
# Correctness follows from:
# - `known_recv_class` implies there is a guard scheduled before here
# for a particular `CLASS_OF(lhs)`.
# - We guard that rhs is identical to the compile-time sample
# - For a particular `CLASS_OF(lhs)`, `rb_obj_class(lhs)` does not change.
# (because for any singleton class `s`, `s.superclass.equal?(s.attached_object.class)`)
sample_rhs = jit.peek_at_stack(0)
sample_lhs = jit.peek_at_stack(1)
# Filters out cases where the C implementation raises
unless C.RB_TYPE_P(sample_rhs, C::RUBY_T_CLASS) || C.RB_TYPE_P(sample_rhs, C::RUBY_T_MODULE)
return false
end
# We need to grab the class here to deal with singleton classes.
# Instance of grabs the "real class" of the object rather than the
# singleton class.
sample_lhs_real_class = C.rb_obj_class(sample_lhs)
sample_instance_of = (sample_lhs_real_class == sample_rhs)
side_exit = side_exit(jit, ctx)
asm.comment('Kernel#instance_of?')
asm.mov(:rax, to_value(sample_rhs))
asm.cmp(ctx.stack_opnd(0), :rax)
asm.jne(counted_exit(side_exit, :send_instance_of_class_mismatch))
ctx.stack_pop(2)
if sample_instance_of
stack_ret = ctx.stack_push(Type::True)
asm.mov(stack_ret, Qtrue)
else
stack_ret = ctx.stack_push(Type::False)
asm.mov(stack_ret, Qfalse)
end
return true;
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_obj_not(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 0
recv_type = ctx.get_opnd_type(StackOpnd[0])
case recv_type.known_truthy
in false
asm.comment('rb_obj_not(nil_or_false)')
ctx.stack_pop(1)
out_opnd = ctx.stack_push(Type::True)
asm.mov(out_opnd, Qtrue)
in true
# Note: recv_type != Type::Nil && recv_type != Type::False.
asm.comment('rb_obj_not(truthy)')
ctx.stack_pop(1)
out_opnd = ctx.stack_push(Type::False)
asm.mov(out_opnd, Qfalse)
in nil
asm.comment('rb_obj_not')
recv = ctx.stack_pop
# This `test` sets ZF only for Qnil and Qfalse, which let cmovz set.
asm.test(recv, ~Qnil)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmovz(:rax, :rcx)
stack_ret = ctx.stack_push(Type::UnknownImm)
asm.mov(stack_ret, :rax)
end
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_obj_equal(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
asm.comment('equal?')
obj1 = ctx.stack_pop(1)
obj2 = ctx.stack_pop(1)
asm.mov(:rax, obj1)
asm.mov(:rcx, obj2)
asm.cmp(:rax, :rcx)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmove(:rax, :rcx)
stack_ret = ctx.stack_push(Type::UnknownImm)
asm.mov(stack_ret, :rax)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_obj_not_equal(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
jit_equality_specialized(jit, ctx, asm, false)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_mod_eqq(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
asm.comment('Module#===')
# By being here, we know that the receiver is a T_MODULE or a T_CLASS, because Module#=== can
# only live on these objects. With that, we can call rb_obj_is_kind_of() without
# jit_prepare_routine_call() or a control frame push because it can't raise, allocate, or call
# Ruby methods with these inputs.
# Note the difference in approach from Kernel#is_a? because we don't get a free guard for the
# right hand side.
lhs = ctx.stack_opnd(1) # the module
rhs = ctx.stack_opnd(0)
asm.mov(C_ARGS[0], rhs);
asm.mov(C_ARGS[1], lhs);
asm.call(C.rb_obj_is_kind_of)
# Return the result
ctx.stack_pop(2)
stack_ret = ctx.stack_push(Type::UnknownImm)
asm.mov(stack_ret, C_RET)
return true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_int_equal(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
return false unless two_fixnums_on_stack?(jit)
guard_two_fixnums(jit, ctx, asm)
# Compare the arguments
asm.comment('rb_int_equal')
arg1 = ctx.stack_pop(1)
arg0 = ctx.stack_pop(1)
asm.mov(:rax, arg1)
asm.cmp(arg0, :rax)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmove(:rax, :rcx)
stack_ret = ctx.stack_push(Type::UnknownImm)
asm.mov(stack_ret, :rax)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_int_mul(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
return false unless two_fixnums_on_stack?(jit)
guard_two_fixnums(jit, ctx, asm)
asm.comment('rb_int_mul')
y_opnd = ctx.stack_pop
x_opnd = ctx.stack_pop
asm.mov(C_ARGS[0], x_opnd)
asm.mov(C_ARGS[1], y_opnd)
asm.call(C.rb_fix_mul_fix)
ret_opnd = ctx.stack_push(Type::Unknown)
asm.mov(ret_opnd, C_RET)
true
end
def jit_rb_int_div(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
return false unless two_fixnums_on_stack?(jit)
guard_two_fixnums(jit, ctx, asm)
asm.comment('rb_int_div')
y_opnd = ctx.stack_pop
x_opnd = ctx.stack_pop
asm.mov(:rax, y_opnd)
asm.cmp(:rax, C.to_value(0))
asm.je(side_exit(jit, ctx))
asm.mov(C_ARGS[0], x_opnd)
asm.mov(C_ARGS[1], :rax)
asm.call(C.rb_fix_div_fix)
ret_opnd = ctx.stack_push(Type::Unknown)
asm.mov(ret_opnd, C_RET)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_int_aref(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
return false unless two_fixnums_on_stack?(jit)
guard_two_fixnums(jit, ctx, asm)
asm.comment('rb_int_aref')
y_opnd = ctx.stack_pop
x_opnd = ctx.stack_pop
asm.mov(C_ARGS[0], x_opnd)
asm.mov(C_ARGS[1], y_opnd)
asm.call(C.rb_fix_aref)
ret_opnd = ctx.stack_push(Type::UnknownImm)
asm.mov(ret_opnd, C_RET)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_empty_p(jit, ctx, asm, argc, known_recv_class)
recv_opnd = ctx.stack_pop(1)
out_opnd = ctx.stack_push(Type::UnknownImm)
asm.comment('get string length')
asm.mov(:rax, recv_opnd)
str_len_opnd = [:rax, C.RString.offsetof(:len)]
asm.cmp(str_len_opnd, 0)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmove(:rax, :rcx)
asm.mov(out_opnd, :rax)
return true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_to_s(jit, ctx, asm, argc, known_recv_class)
return false if argc != 0
if known_recv_class == String
asm.comment('to_s on plain string')
# The method returns the receiver, which is already on the stack.
# No stack movement.
return true
end
false
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_bytesize(jit, ctx, asm, argc, known_recv_class)
asm.comment('String#bytesize')
recv = ctx.stack_pop(1)
asm.mov(C_ARGS[0], recv)
asm.call(C.rb_str_bytesize)
out_opnd = ctx.stack_push(Type::Fixnum)
asm.mov(out_opnd, C_RET)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_concat(jit, ctx, asm, argc, known_recv_class)
# The << operator can accept integer codepoints for characters
# as the argument. We only specially optimise string arguments.
# If the peeked-at compile time argument is something other than
# a string, assume it won't be a string later either.
comptime_arg = jit.peek_at_stack(0)
unless C.RB_TYPE_P(comptime_arg, C::RUBY_T_STRING)
return false
end
# Guard that the concat argument is a string
asm.mov(:rax, ctx.stack_opnd(0))
guard_object_is_string(jit, ctx, asm, :rax, :rcx, StackOpnd[0])
# Guard buffers from GC since rb_str_buf_append may allocate. During the VM lock on GC,
# other Ractors may trigger global invalidation, so we need ctx.clear_local_types.
# PC is used on errors like Encoding::CompatibilityError raised by rb_str_buf_append.
jit_prepare_routine_call(jit, ctx, asm)
concat_arg = ctx.stack_pop(1)
recv = ctx.stack_pop(1)
# Test if string encodings differ. If different, use rb_str_append. If the same,
# use rb_yjit_str_simple_append, which calls rb_str_cat.
asm.comment('<< on strings')
# Take receiver's object flags XOR arg's flags. If any
# string-encoding flags are different between the two,
# the encodings don't match.
recv_reg = :rax
asm.mov(recv_reg, recv)
concat_arg_reg = :rcx
asm.mov(concat_arg_reg, concat_arg)
asm.mov(recv_reg, [recv_reg, C.RBasic.offsetof(:flags)])
asm.mov(concat_arg_reg, [concat_arg_reg, C.RBasic.offsetof(:flags)])
asm.xor(recv_reg, concat_arg_reg)
asm.test(recv_reg, C::RUBY_ENCODING_MASK)
# Push once, use the resulting operand in both branches below.
stack_ret = ctx.stack_push(Type::TString)
enc_mismatch = asm.new_label('enc_mismatch')
asm.jnz(enc_mismatch)
# If encodings match, call the simple append function and jump to return
asm.mov(C_ARGS[0], recv)
asm.mov(C_ARGS[1], concat_arg)
asm.call(C.rjit_str_simple_append)
ret_label = asm.new_label('func_return')
asm.mov(stack_ret, C_RET)
asm.jmp(ret_label)
# If encodings are different, use a slower encoding-aware concatenate
asm.write_label(enc_mismatch)
asm.mov(C_ARGS[0], recv)
asm.mov(C_ARGS[1], concat_arg)
asm.call(C.rb_str_buf_append)
asm.mov(stack_ret, C_RET)
# Drop through to return
asm.write_label(ret_label)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_uplus(jit, ctx, asm, argc, _known_recv_class)
if argc != 0
return false
end
# We allocate when we dup the string
jit_prepare_routine_call(jit, ctx, asm)
asm.comment('Unary plus on string')
asm.mov(:rax, ctx.stack_pop(1)) # recv_opnd
asm.mov(:rcx, [:rax, C.RBasic.offsetof(:flags)]) # flags_opnd
asm.test(:rcx, C::RUBY_FL_FREEZE)
ret_label = asm.new_label('stack_ret')
# String#+@ can only exist on T_STRING
stack_ret = ctx.stack_push(Type::TString)
# If the string isn't frozen, we just return it.
asm.mov(stack_ret, :rax) # recv_opnd
asm.jz(ret_label)
# Str is frozen - duplicate it
asm.mov(C_ARGS[0], :rax) # recv_opnd
asm.call(C.rb_str_dup)
asm.mov(stack_ret, C_RET)
asm.write_label(ret_label)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_str_getbyte(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
asm.comment('rb_str_getbyte')
index_opnd = ctx.stack_pop
str_opnd = ctx.stack_pop
asm.mov(C_ARGS[0], str_opnd)
asm.mov(C_ARGS[1], index_opnd)
asm.call(C.rb_str_getbyte)
ret_opnd = ctx.stack_push(Type::Fixnum)
asm.mov(ret_opnd, C_RET)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_ary_empty_p(jit, ctx, asm, argc, _known_recv_class)
array_reg = :rax
asm.mov(array_reg, ctx.stack_pop(1))
jit_array_len(asm, array_reg, :rcx)
asm.test(:rcx, :rcx)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmovz(:rax, :rcx)
out_opnd = ctx.stack_push(Type::UnknownImm)
asm.mov(out_opnd, :rax)
return true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_ary_push(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 1
asm.comment('rb_ary_push')
jit_prepare_routine_call(jit, ctx, asm)
item_opnd = ctx.stack_pop
ary_opnd = ctx.stack_pop
asm.mov(C_ARGS[0], ary_opnd)
asm.mov(C_ARGS[1], item_opnd)
asm.call(C.rb_ary_push)
ret_opnd = ctx.stack_push(Type::TArray)
asm.mov(ret_opnd, C_RET)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_obj_respond_to(jit, ctx, asm, argc, known_recv_class)
# respond_to(:sym) or respond_to(:sym, true)
if argc != 1 && argc != 2
return false
end
if known_recv_class.nil?
return false
end
recv_class = known_recv_class
# Get the method_id from compile time. We will later add a guard against it.
mid_sym = jit.peek_at_stack(argc - 1)
unless static_symbol?(mid_sym)
return false
end
mid = C.rb_sym2id(mid_sym)
# This represents the value of the "include_all" argument and whether it's known
allow_priv = if argc == 1
# Default is false
false
else
# Get value from type information (may or may not be known)
ctx.get_opnd_type(StackOpnd[0]).known_truthy
end
target_cme = C.rb_callable_method_entry_or_negative(recv_class, mid)
# Should never be null, as in that case we will be returned a "negative CME"
assert_equal(false, target_cme.nil?)
cme_def_type = C.UNDEFINED_METHOD_ENTRY_P(target_cme) ? C::VM_METHOD_TYPE_UNDEF : target_cme.def.type
if cme_def_type == C::VM_METHOD_TYPE_REFINED
return false
end
visibility = if cme_def_type == C::VM_METHOD_TYPE_UNDEF
C::METHOD_VISI_UNDEF
else
C.METHOD_ENTRY_VISI(target_cme)
end
result =
case [visibility, allow_priv]
in C::METHOD_VISI_UNDEF, _ then Qfalse # No method => false
in C::METHOD_VISI_PUBLIC, _ then Qtrue # Public method => true regardless of include_all
in _, true then Qtrue # include_all => always true
else return false # not public and include_all not known, can't compile
end
if result != Qtrue
# Only if respond_to_missing? hasn't been overridden
# In the future, we might want to jit the call to respond_to_missing?
unless Invariants.assume_method_basic_definition(jit, recv_class, C.idRespond_to_missing)
return false
end
end
# Invalidate this block if method lookup changes for the method being queried. This works
# both for the case where a method does or does not exist, as for the latter we asked for a
# "negative CME" earlier.
Invariants.assume_method_lookup_stable(jit, target_cme)
# Generate a side exit
side_exit = side_exit(jit, ctx)
if argc == 2
# pop include_all argument (we only use its type info)
ctx.stack_pop(1)
end
sym_opnd = ctx.stack_pop(1)
_recv_opnd = ctx.stack_pop(1)
# This is necessary because we have no guarantee that sym_opnd is a constant
asm.comment('guard known mid')
asm.mov(:rax, to_value(mid_sym))
asm.cmp(sym_opnd, :rax)
asm.jne(side_exit)
putobject(jit, ctx, asm, val: result)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_rb_f_block_given_p(jit, ctx, asm, argc, _known_recv_class)
asm.comment('block_given?')
# Same as rb_vm_frame_block_handler
jit_get_lep(jit, asm, reg: :rax)
asm.mov(:rax, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler
ctx.stack_pop(1)
out_opnd = ctx.stack_push(Type::UnknownImm)
# Return `block_handler != VM_BLOCK_HANDLER_NONE`
asm.cmp(:rax, C::VM_BLOCK_HANDLER_NONE)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.cmovne(:rax, :rcx) # block_given
asm.mov(out_opnd, :rax)
true
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_thread_s_current(jit, ctx, asm, argc, _known_recv_class)
return false if argc != 0
asm.comment('Thread.current')
ctx.stack_pop(1)
# ec->thread_ptr
asm.mov(:rax, [EC, C.rb_execution_context_t.offsetof(:thread_ptr)])
# thread->self
asm.mov(:rax, [:rax, C.rb_thread_struct.offsetof(:self)])
stack_ret = ctx.stack_push(Type::UnknownHeap)
asm.mov(stack_ret, :rax)
true
end
#
# Helpers
#
def register_cfunc_codegen_funcs
# Specialization for C methods. See register_cfunc_method for details.
register_cfunc_method(BasicObject, :!, :jit_rb_obj_not)
register_cfunc_method(NilClass, :nil?, :jit_rb_true)
register_cfunc_method(Kernel, :nil?, :jit_rb_false)
register_cfunc_method(Kernel, :is_a?, :jit_rb_kernel_is_a)
register_cfunc_method(Kernel, :kind_of?, :jit_rb_kernel_is_a)
register_cfunc_method(Kernel, :instance_of?, :jit_rb_kernel_instance_of)
register_cfunc_method(BasicObject, :==, :jit_rb_obj_equal)
register_cfunc_method(BasicObject, :equal?, :jit_rb_obj_equal)
register_cfunc_method(BasicObject, :!=, :jit_rb_obj_not_equal)
register_cfunc_method(Kernel, :eql?, :jit_rb_obj_equal)
register_cfunc_method(Module, :==, :jit_rb_obj_equal)
register_cfunc_method(Module, :===, :jit_rb_mod_eqq)
register_cfunc_method(Symbol, :==, :jit_rb_obj_equal)
register_cfunc_method(Symbol, :===, :jit_rb_obj_equal)
register_cfunc_method(Integer, :==, :jit_rb_int_equal)
register_cfunc_method(Integer, :===, :jit_rb_int_equal)
# rb_str_to_s() methods in string.c
register_cfunc_method(String, :empty?, :jit_rb_str_empty_p)
register_cfunc_method(String, :to_s, :jit_rb_str_to_s)
register_cfunc_method(String, :to_str, :jit_rb_str_to_s)
register_cfunc_method(String, :bytesize, :jit_rb_str_bytesize)
register_cfunc_method(String, :<<, :jit_rb_str_concat)
register_cfunc_method(String, :+@, :jit_rb_str_uplus)
# rb_ary_empty_p() method in array.c
register_cfunc_method(Array, :empty?, :jit_rb_ary_empty_p)
register_cfunc_method(Kernel, :respond_to?, :jit_obj_respond_to)
register_cfunc_method(Kernel, :block_given?, :jit_rb_f_block_given_p)
# Thread.current
register_cfunc_method(C.rb_singleton_class(Thread), :current, :jit_thread_s_current)
#---
register_cfunc_method(Array, :<<, :jit_rb_ary_push)
register_cfunc_method(Integer, :*, :jit_rb_int_mul)
register_cfunc_method(Integer, :/, :jit_rb_int_div)
register_cfunc_method(Integer, :[], :jit_rb_int_aref)
register_cfunc_method(String, :getbyte, :jit_rb_str_getbyte)
end
def register_cfunc_method(klass, mid_sym, func)
mid = C.rb_intern(mid_sym.to_s)
me = C.rb_method_entry_at(klass, mid)
assert_equal(false, me.nil?)
# Only cfuncs are supported
method_serial = me.def.method_serial
@cfunc_codegen_table[method_serial] = method(func)
end
def lookup_cfunc_codegen(cme_def)
@cfunc_codegen_table[cme_def.method_serial]
end
def stack_swap(_jit, ctx, asm, offset0, offset1)
stack0_mem = ctx.stack_opnd(offset0)
stack1_mem = ctx.stack_opnd(offset1)
mapping0 = ctx.get_opnd_mapping(StackOpnd[offset0])
mapping1 = ctx.get_opnd_mapping(StackOpnd[offset1])
asm.mov(:rax, stack0_mem)
asm.mov(:rcx, stack1_mem)
asm.mov(stack0_mem, :rcx)
asm.mov(stack1_mem, :rax)
ctx.set_opnd_mapping(StackOpnd[offset0], mapping1)
ctx.set_opnd_mapping(StackOpnd[offset1], mapping0)
end
def jit_getlocal_generic(jit, ctx, asm, idx:, level:)
# Load environment pointer EP (level 0) from CFP
ep_reg = :rax
jit_get_ep(asm, level, reg: ep_reg)
# Load the local from the block
# val = *(vm_get_ep(GET_EP(), level) - idx);
asm.mov(:rax, [ep_reg, -idx * C.VALUE.size])
# Write the local at SP
stack_top = if level == 0
local_idx = ep_offset_to_local_idx(jit.iseq, idx)
ctx.stack_push_local(local_idx)
else
ctx.stack_push(Type::Unknown)
end
asm.mov(stack_top, :rax)
KeepCompiling
end
def jit_setlocal_generic(jit, ctx, asm, idx:, level:)
value_type = ctx.get_opnd_type(StackOpnd[0])
# Load environment pointer EP at level
ep_reg = :rax
jit_get_ep(asm, level, reg: ep_reg)
# Write barriers may be required when VM_ENV_FLAG_WB_REQUIRED is set, however write barriers
# only affect heap objects being written. If we know an immediate value is being written we
# can skip this check.
unless value_type.imm?
# flags & VM_ENV_FLAG_WB_REQUIRED
flags_opnd = [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_FLAGS]
asm.test(flags_opnd, C::VM_ENV_FLAG_WB_REQUIRED)
# if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0
asm.jnz(side_exit(jit, ctx))
end
if level == 0
local_idx = ep_offset_to_local_idx(jit.iseq, idx)
ctx.set_local_type(local_idx, value_type)
end
# Pop the value to write from the stack
stack_top = ctx.stack_pop(1)
# Write the value at the environment pointer
asm.mov(:rcx, stack_top)
asm.mov([ep_reg, -(C.VALUE.size * idx)], :rcx)
KeepCompiling
end
# Compute the index of a local variable from its slot index
def ep_offset_to_local_idx(iseq, ep_offset)
# Layout illustration
# This is an array of VALUE
# | VM_ENV_DATA_SIZE |
# v v
# low addr <+-------+-------+-------+-------+------------------+
# |local 0|local 1| ... |local n| .... |
# +-------+-------+-------+-------+------------------+
# ^ ^ ^ ^
# +-------+---local_table_size----+ cfp->ep--+
# | |
# +------------------ep_offset---------------+
#
# See usages of local_var_name() from iseq.c for similar calculation.
# Equivalent of iseq->body->local_table_size
local_table_size = iseq.body.local_table_size
op = ep_offset - C::VM_ENV_DATA_SIZE
local_idx = local_table_size - op - 1
assert_equal(true, local_idx >= 0 && local_idx < local_table_size)
local_idx
end
# Compute the index of a local variable from its slot index
def slot_to_local_idx(iseq, slot_idx)
# Layout illustration
# This is an array of VALUE
# | VM_ENV_DATA_SIZE |
# v v
# low addr <+-------+-------+-------+-------+------------------+
# |local 0|local 1| ... |local n| .... |
# +-------+-------+-------+-------+------------------+
# ^ ^ ^ ^
# +-------+---local_table_size----+ cfp->ep--+
# | |
# +------------------slot_idx----------------+
#
# See usages of local_var_name() from iseq.c for similar calculation.
local_table_size = iseq.body.local_table_size
op = slot_idx - C::VM_ENV_DATA_SIZE
local_table_size - op - 1
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def guard_object_is_heap(jit, ctx, asm, object, object_opnd, counter = nil)
object_type = ctx.get_opnd_type(object_opnd)
if object_type.heap?
return
end
side_exit = side_exit(jit, ctx)
side_exit = counted_exit(side_exit, counter) if counter
asm.comment('guard object is heap')
# Test that the object is not an immediate
asm.test(object, C::RUBY_IMMEDIATE_MASK)
asm.jnz(side_exit)
# Test that the object is not false
asm.cmp(object, Qfalse)
asm.je(side_exit)
if object_type.diff(Type::UnknownHeap) != TypeDiff::Incompatible
ctx.upgrade_opnd_type(object_opnd, Type::UnknownHeap)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def guard_object_is_array(jit, ctx, asm, object_reg, flags_reg, object_opnd, counter = nil)
object_type = ctx.get_opnd_type(object_opnd)
if object_type.array?
return
end
guard_object_is_heap(jit, ctx, asm, object_reg, object_opnd, counter)
side_exit = side_exit(jit, ctx)
side_exit = counted_exit(side_exit, counter) if counter
asm.comment('guard object is array')
# Pull out the type mask
asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)])
asm.and(flags_reg, C::RUBY_T_MASK)
# Compare the result with T_ARRAY
asm.cmp(flags_reg, C::RUBY_T_ARRAY)
asm.jne(side_exit)
if object_type.diff(Type::TArray) != TypeDiff::Incompatible
ctx.upgrade_opnd_type(object_opnd, Type::TArray)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def guard_object_is_string(jit, ctx, asm, object_reg, flags_reg, object_opnd, counter = nil)
object_type = ctx.get_opnd_type(object_opnd)
if object_type.string?
return
end
guard_object_is_heap(jit, ctx, asm, object_reg, object_opnd, counter)
side_exit = side_exit(jit, ctx)
side_exit = counted_exit(side_exit, counter) if counter
asm.comment('guard object is string')
# Pull out the type mask
asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)])
asm.and(flags_reg, C::RUBY_T_MASK)
# Compare the result with T_STRING
asm.cmp(flags_reg, C::RUBY_T_STRING)
asm.jne(side_exit)
if object_type.diff(Type::TString) != TypeDiff::Incompatible
ctx.upgrade_opnd_type(object_opnd, Type::TString)
end
end
# clobbers object_reg
def guard_object_is_not_ruby2_keyword_hash(asm, object_reg, flags_reg, side_exit)
asm.comment('guard object is not ruby2 keyword hash')
not_ruby2_keyword = asm.new_label('not_ruby2_keyword')
asm.test(object_reg, C::RUBY_IMMEDIATE_MASK)
asm.jnz(not_ruby2_keyword)
asm.cmp(object_reg, Qfalse)
asm.je(not_ruby2_keyword)
asm.mov(flags_reg, [object_reg, C.RBasic.offsetof(:flags)])
type_reg = object_reg
asm.mov(type_reg, flags_reg)
asm.and(type_reg, C::RUBY_T_MASK)
asm.cmp(type_reg, C::RUBY_T_HASH)
asm.jne(not_ruby2_keyword)
asm.test(flags_reg, C::RHASH_PASS_AS_KEYWORDS)
asm.jnz(side_exit)
asm.write_label(not_ruby2_keyword)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_chain_guard(opcode, jit, ctx, asm, side_exit, limit: 20)
opcode => :je | :jne | :jnz | :jz
if ctx.chain_depth < limit
deeper = ctx.dup
deeper.chain_depth += 1
branch_stub = BranchStub.new(
iseq: jit.iseq,
shape: Default,
target0: BranchTarget.new(ctx: deeper, pc: jit.pc),
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(deeper, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.compile = compile_jit_chain_guard(branch_stub, opcode:)
branch_stub.compile.call(asm)
else
asm.public_send(opcode, side_exit)
end
end
def compile_jit_chain_guard(branch_stub, opcode:) # Proc escapes arguments in memory
proc do |branch_asm|
# Not using `asm.comment` here since it's usually put before cmp/test before this.
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.public_send(opcode, branch_stub.target0.address)
end
end
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_guard_known_klass(jit, ctx, asm, known_klass, obj_opnd, insn_opnd, comptime_obj, side_exit, limit: 10)
# Only memory operand is supported for now
assert_equal(true, obj_opnd.is_a?(Array))
known_klass = C.to_value(known_klass)
val_type = ctx.get_opnd_type(insn_opnd)
if val_type.known_class == known_klass
# We already know from type information that this is a match
return
end
# Touching this as Ruby could crash for FrozenCore
if known_klass == C.rb_cNilClass
assert(!val_type.heap?)
assert(val_type.unknown?)
asm.comment('guard object is nil')
asm.cmp(obj_opnd, Qnil)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::Nil)
elsif known_klass == C.rb_cTrueClass
assert(!val_type.heap?)
assert(val_type.unknown?)
asm.comment('guard object is true')
asm.cmp(obj_opnd, Qtrue)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::True)
elsif known_klass == C.rb_cFalseClass
assert(!val_type.heap?)
assert(val_type.unknown?)
asm.comment('guard object is false')
asm.cmp(obj_opnd, Qfalse)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::False)
elsif known_klass == C.rb_cInteger && fixnum?(comptime_obj)
# We will guard fixnum and bignum as though they were separate classes
# BIGNUM can be handled by the general else case below
assert(val_type.unknown?)
asm.comment('guard object is fixnum')
asm.test(obj_opnd, C::RUBY_FIXNUM_FLAG)
jit_chain_guard(:jz, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::Fixnum)
elsif known_klass == C.rb_cSymbol && static_symbol?(comptime_obj)
assert(!val_type.heap?)
# We will guard STATIC vs DYNAMIC as though they were separate classes
# DYNAMIC symbols can be handled by the general else case below
if val_type != Type::ImmSymbol || !val_type.imm?
assert(val_type.unknown?)
asm.comment('guard object is static symbol')
assert_equal(8, C::RUBY_SPECIAL_SHIFT)
asm.cmp(BytePtr[*obj_opnd], C::RUBY_SYMBOL_FLAG)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::ImmSymbol)
end
elsif known_klass == C.rb_cFloat && flonum?(comptime_obj)
assert(!val_type.heap?)
if val_type != Type::Flonum || !val_type.imm?
assert(val_type.unknown?)
# We will guard flonum vs heap float as though they were separate classes
asm.comment('guard object is flonum')
asm.mov(:rax, obj_opnd)
asm.and(:rax, C::RUBY_FLONUM_MASK)
asm.cmp(:rax, C::RUBY_FLONUM_FLAG)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
ctx.upgrade_opnd_type(insn_opnd, Type::Flonum)
end
elsif C.FL_TEST(known_klass, C::RUBY_FL_SINGLETON) && comptime_obj == C.rb_class_attached_object(known_klass)
# Singleton classes are attached to one specific object, so we can
# avoid one memory access (and potentially the is_heap check) by
# looking for the expected object directly.
# Note that in case the sample instance has a singleton class that
# doesn't attach to the sample instance, it means the sample instance
# has an empty singleton class that hasn't been materialized yet. In
# this case, comparing against the sample instance doesn't guarantee
# that its singleton class is empty, so we can't avoid the memory
# access. As an example, `Object.new.singleton_class` is an object in
# this situation.
asm.comment('guard known object with singleton class')
asm.mov(:rax, to_value(comptime_obj))
asm.cmp(obj_opnd, :rax)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
elsif val_type == Type::CString && known_klass == C.rb_cString
# guard elided because the context says we've already checked
assert_equal(C.to_value(C.rb_class_of(comptime_obj)), C.rb_cString)
else
assert(!val_type.imm?)
# Load memory to a register
asm.mov(:rax, obj_opnd)
obj_opnd = :rax
# Check that the receiver is a heap object
# Note: if we get here, the class doesn't have immediate instances.
unless val_type.heap?
asm.comment('guard not immediate')
asm.test(obj_opnd, C::RUBY_IMMEDIATE_MASK)
jit_chain_guard(:jnz, jit, ctx, asm, side_exit, limit:)
asm.cmp(obj_opnd, Qfalse)
jit_chain_guard(:je, jit, ctx, asm, side_exit, limit:)
end
# Bail if receiver class is different from known_klass
klass_opnd = [obj_opnd, C.RBasic.offsetof(:klass)]
asm.comment("guard known class #{known_klass}")
asm.mov(:rcx, known_klass)
asm.cmp(klass_opnd, :rcx)
jit_chain_guard(:jne, jit, ctx, asm, side_exit, limit:)
if known_klass == C.rb_cString
# Upgrading to Type::CString here is incorrect.
# The guard we put only checks RBASIC_CLASS(obj),
# which adding a singleton class can change. We
# additionally need to know the string is frozen
# to claim Type::CString.
ctx.upgrade_opnd_type(insn_opnd, Type::TString)
elsif known_klass == C.rb_cArray
ctx.upgrade_opnd_type(insn_opnd, Type::TArray)
end
end
end
# @param jit [RubyVM::RJIT::JITState]
def two_fixnums_on_stack?(jit)
comptime_recv = jit.peek_at_stack(1)
comptime_arg = jit.peek_at_stack(0)
return fixnum?(comptime_recv) && fixnum?(comptime_arg)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def guard_two_fixnums(jit, ctx, asm)
# Get stack operands without popping them
arg1 = ctx.stack_opnd(0)
arg0 = ctx.stack_opnd(1)
# Get the stack operand types
arg1_type = ctx.get_opnd_type(StackOpnd[0])
arg0_type = ctx.get_opnd_type(StackOpnd[1])
if arg0_type.heap? || arg1_type.heap?
asm.comment('arg is heap object')
asm.jmp(side_exit(jit, ctx))
return
end
if arg0_type != Type::Fixnum && arg0_type.specific?
asm.comment('arg0 not fixnum')
asm.jmp(side_exit(jit, ctx))
return
end
if arg1_type != Type::Fixnum && arg1_type.specific?
asm.comment('arg1 not fixnum')
asm.jmp(side_exit(jit, ctx))
return
end
assert(!arg0_type.heap?)
assert(!arg1_type.heap?)
assert(arg0_type == Type::Fixnum || arg0_type.unknown?)
assert(arg1_type == Type::Fixnum || arg1_type.unknown?)
# If not fixnums at run-time, fall back
if arg0_type != Type::Fixnum
asm.comment('guard arg0 fixnum')
asm.test(arg0, C::RUBY_FIXNUM_FLAG)
jit_chain_guard(:jz, jit, ctx, asm, side_exit(jit, ctx))
end
if arg1_type != Type::Fixnum
asm.comment('guard arg1 fixnum')
asm.test(arg1, C::RUBY_FIXNUM_FLAG)
jit_chain_guard(:jz, jit, ctx, asm, side_exit(jit, ctx))
end
# Set stack types in context
ctx.upgrade_opnd_type(StackOpnd[0], Type::Fixnum)
ctx.upgrade_opnd_type(StackOpnd[1], Type::Fixnum)
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_fixnum_cmp(jit, ctx, asm, opcode:, bop:)
opcode => :cmovl | :cmovle | :cmovg | :cmovge
unless jit.at_current_insn?
defer_compilation(jit, ctx, asm)
return EndBlock
end
comptime_recv = jit.peek_at_stack(1)
comptime_obj = jit.peek_at_stack(0)
if fixnum?(comptime_recv) && fixnum?(comptime_obj)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, bop)
return CantCompile
end
# Check that both operands are fixnums
guard_two_fixnums(jit, ctx, asm)
obj_opnd = ctx.stack_pop
recv_opnd = ctx.stack_pop
asm.mov(:rax, obj_opnd)
asm.cmp(recv_opnd, :rax)
asm.mov(:rax, Qfalse)
asm.mov(:rcx, Qtrue)
asm.public_send(opcode, :rax, :rcx)
dst_opnd = ctx.stack_push(Type::UnknownImm)
asm.mov(dst_opnd, :rax)
KeepCompiling
else
opt_send_without_block(jit, ctx, asm)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_equality_specialized(jit, ctx, asm, gen_eq)
# Create a side-exit to fall back to the interpreter
side_exit = side_exit(jit, ctx)
a_opnd = ctx.stack_opnd(1)
b_opnd = ctx.stack_opnd(0)
comptime_a = jit.peek_at_stack(1)
comptime_b = jit.peek_at_stack(0)
if two_fixnums_on_stack?(jit)
unless Invariants.assume_bop_not_redefined(jit, C::INTEGER_REDEFINED_OP_FLAG, C::BOP_EQ)
return false
end
guard_two_fixnums(jit, ctx, asm)
asm.comment('check fixnum equality')
asm.mov(:rax, a_opnd)
asm.mov(:rcx, b_opnd)
asm.cmp(:rax, :rcx)
asm.mov(:rax, gen_eq ? Qfalse : Qtrue)
asm.mov(:rcx, gen_eq ? Qtrue : Qfalse)
asm.cmove(:rax, :rcx)
# Push the output on the stack
ctx.stack_pop(2)
dst = ctx.stack_push(Type::UnknownImm)
asm.mov(dst, :rax)
true
elsif C.rb_class_of(comptime_a) == String && C.rb_class_of(comptime_b) == String
unless Invariants.assume_bop_not_redefined(jit, C::STRING_REDEFINED_OP_FLAG, C::BOP_EQ)
# if overridden, emit the generic version
return false
end
# Guard that a is a String
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_a), a_opnd, StackOpnd[1], comptime_a, side_exit)
equal_label = asm.new_label(:equal)
ret_label = asm.new_label(:ret)
# If they are equal by identity, return true
asm.mov(:rax, a_opnd)
asm.mov(:rcx, b_opnd)
asm.cmp(:rax, :rcx)
asm.je(equal_label)
# Otherwise guard that b is a T_STRING (from type info) or String (from runtime guard)
btype = ctx.get_opnd_type(StackOpnd[0])
unless btype.string?
# Note: any T_STRING is valid here, but we check for a ::String for simplicity
# To pass a mutable static variable (rb_cString) requires an unsafe block
jit_guard_known_klass(jit, ctx, asm, C.rb_class_of(comptime_b), b_opnd, StackOpnd[0], comptime_b, side_exit)
end
asm.comment('call rb_str_eql_internal')
asm.mov(C_ARGS[0], a_opnd)
asm.mov(C_ARGS[1], b_opnd)
asm.call(gen_eq ? C.rb_str_eql_internal : C.rjit_str_neq_internal)
# Push the output on the stack
ctx.stack_pop(2)
dst = ctx.stack_push(Type::UnknownImm)
asm.mov(dst, C_RET)
asm.jmp(ret_label)
asm.write_label(equal_label)
asm.mov(dst, gen_eq ? Qtrue : Qfalse)
asm.write_label(ret_label)
true
else
false
end
end
# NOTE: This clobbers :rax
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_prepare_routine_call(jit, ctx, asm)
jit.record_boundary_patch_point = true
jit_save_pc(jit, asm)
jit_save_sp(ctx, asm)
# In case the routine calls Ruby methods, it can set local variables
# through Kernel#binding and other means.
ctx.clear_local_types
end
# NOTE: This clobbers :rax
# @param jit [RubyVM::RJIT::JITState]
# @param asm [RubyVM::RJIT::Assembler]
def jit_save_pc(jit, asm, comment: 'save PC to CFP')
next_pc = jit.pc + jit.insn.len * C.VALUE.size # Use the next one for backtrace and side exits
asm.comment(comment)
asm.mov(:rax, next_pc)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax)
end
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_save_sp(ctx, asm)
if ctx.sp_offset != 0
asm.comment('save SP to CFP')
asm.lea(SP, ctx.sp_opnd)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP)
ctx.sp_offset = 0
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jump_to_next_insn(jit, ctx, asm)
reset_depth = ctx.dup
reset_depth.chain_depth = 0
next_pc = jit.pc + jit.insn.len * C.VALUE.size
# We are at the end of the current instruction. Record the boundary.
if jit.record_boundary_patch_point
exit_pos = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_side_exit(next_pc, ctx, ocb_asm)
@ocb.write(ocb_asm)
end
Invariants.record_global_inval_patch(asm, exit_pos)
jit.record_boundary_patch_point = false
end
jit_direct_jump(jit.iseq, next_pc, reset_depth, asm, comment: 'jump_to_next_insn')
end
# rb_vm_check_ints
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_check_ints(jit, ctx, asm)
asm.comment('RUBY_VM_CHECK_INTS(ec)')
asm.mov(:eax, DwordPtr[EC, C.rb_execution_context_t.offsetof(:interrupt_flag)])
asm.test(:eax, :eax)
asm.jnz(side_exit(jit, ctx))
end
# See get_lvar_level in compile.c
def get_lvar_level(iseq)
level = 0
while iseq.to_i != iseq.body.local_iseq.to_i
level += 1
iseq = iseq.body.parent_iseq
end
return level
end
# GET_LEP
# @param jit [RubyVM::RJIT::JITState]
# @param asm [RubyVM::RJIT::Assembler]
def jit_get_lep(jit, asm, reg:)
level = get_lvar_level(jit.iseq)
jit_get_ep(asm, level, reg:)
end
# vm_get_ep
# @param asm [RubyVM::RJIT::Assembler]
def jit_get_ep(asm, level, reg:)
asm.mov(reg, [CFP, C.rb_control_frame_t.offsetof(:ep)])
level.times do
# GET_PREV_EP: ep[VM_ENV_DATA_INDEX_SPECVAL] & ~0x03
asm.mov(reg, [reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL])
asm.and(reg, ~0x03)
end
end
# vm_getivar
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_getivar(jit, ctx, asm, comptime_obj, ivar_id, obj_opnd, obj_yarv_opnd)
side_exit = side_exit(jit, ctx)
starting_ctx = ctx.dup # copy for jit_chain_guard
# Guard not special const
if C::SPECIAL_CONST_P(comptime_obj)
asm.incr_counter(:getivar_special_const)
return CantCompile
end
case C::BUILTIN_TYPE(comptime_obj)
when C::T_OBJECT
# This is the only supported case for now (ROBJECT_IVPTR)
else
# General case. Call rb_ivar_get().
# VALUE rb_ivar_get(VALUE obj, ID id)
asm.comment('call rb_ivar_get()')
asm.mov(C_ARGS[0], obj_opnd ? obj_opnd : [CFP, C.rb_control_frame_t.offsetof(:self)])
asm.mov(C_ARGS[1], ivar_id)
# The function could raise exceptions.
jit_prepare_routine_call(jit, ctx, asm) # clobbers obj_opnd and :rax
asm.call(C.rb_ivar_get)
if obj_opnd # attr_reader
ctx.stack_pop
end
# Push the ivar on the stack
out_opnd = ctx.stack_push(Type::Unknown)
asm.mov(out_opnd, C_RET)
# Jump to next instruction. This allows guard chains to share the same successor.
jump_to_next_insn(jit, ctx, asm)
return EndBlock
end
asm.mov(:rax, obj_opnd ? obj_opnd : [CFP, C.rb_control_frame_t.offsetof(:self)])
guard_object_is_heap(jit, ctx, asm, :rax, obj_yarv_opnd, :getivar_not_heap)
shape_id = C.rb_shape_get_shape_id(comptime_obj)
if shape_id == C::OBJ_TOO_COMPLEX_SHAPE_ID
asm.incr_counter(:getivar_too_complex)
return CantCompile
end
asm.comment('guard shape')
asm.cmp(DwordPtr[:rax, C.rb_shape_id_offset], shape_id)
jit_chain_guard(:jne, jit, starting_ctx, asm, counted_exit(side_exit, :getivar_megamorphic))
if obj_opnd
ctx.stack_pop # pop receiver for attr_reader
end
index = C.rb_shape_get_iv_index(shape_id, ivar_id)
# If there is no IVAR index, then the ivar was undefined
# when we entered the compiler. That means we can just return
# nil for this shape + iv name
if index.nil?
stack_opnd = ctx.stack_push(Type::Nil)
val_opnd = Qnil
else
asm.comment('ROBJECT_IVPTR')
if C::FL_TEST_RAW(comptime_obj, C::ROBJECT_EMBED)
# Access embedded array
asm.mov(:rax, [:rax, C.RObject.offsetof(:as, :ary) + (index * C.VALUE.size)])
else
# Pull out an ivar table on heap
asm.mov(:rax, [:rax, C.RObject.offsetof(:as, :heap, :ivptr)])
# Read the table
asm.mov(:rax, [:rax, index * C.VALUE.size])
end
stack_opnd = ctx.stack_push(Type::Unknown)
val_opnd = :rax
end
asm.mov(stack_opnd, val_opnd)
# Let guard chains share the same successor
jump_to_next_insn(jit, ctx, asm)
EndBlock
end
def jit_write_iv(asm, comptime_receiver, recv_reg, temp_reg, ivar_index, set_value, needs_extension)
# Compile time self is embedded and the ivar index lands within the object
embed_test_result = C::FL_TEST_RAW(comptime_receiver, C::ROBJECT_EMBED) && !needs_extension
if embed_test_result
# Find the IV offset
offs = C.RObject.offsetof(:as, :ary) + ivar_index * C.VALUE.size
# Write the IV
asm.comment('write IV')
asm.mov(temp_reg, set_value)
asm.mov([recv_reg, offs], temp_reg)
else
# Compile time value is *not* embedded.
# Get a pointer to the extended table
asm.mov(recv_reg, [recv_reg, C.RObject.offsetof(:as, :heap, :ivptr)])
# Write the ivar in to the extended table
asm.comment("write IV");
asm.mov(temp_reg, set_value)
asm.mov([recv_reg, C.VALUE.size * ivar_index], temp_reg)
end
end
# vm_caller_setup_arg_block: Handle VM_CALL_ARGS_BLOCKARG cases.
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def guard_block_arg(jit, ctx, asm, calling)
if calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0
block_arg_type = ctx.get_opnd_type(StackOpnd[0])
case block_arg_type
in Type::Nil
calling.block_handler = C::VM_BLOCK_HANDLER_NONE
in Type::BlockParamProxy
calling.block_handler = C.rb_block_param_proxy
else
asm.incr_counter(:send_block_arg)
return CantCompile
end
end
end
# vm_search_method
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_search_method(jit, ctx, asm, mid, calling)
assert_equal(true, jit.at_current_insn?)
# Generate a side exit
side_exit = side_exit(jit, ctx)
# kw_splat is not supported yet
if calling.flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_kw_splat)
return CantCompile
end
# Get a compile-time receiver and its class
recv_idx = calling.argc + (calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 ? 1 : 0) # blockarg is not popped yet
recv_idx += calling.send_shift
comptime_recv = jit.peek_at_stack(recv_idx)
comptime_recv_klass = C.rb_class_of(comptime_recv)
# Guard the receiver class (part of vm_search_method_fastpath)
recv_opnd = ctx.stack_opnd(recv_idx)
megamorphic_exit = counted_exit(side_exit, :send_klass_megamorphic)
jit_guard_known_klass(jit, ctx, asm, comptime_recv_klass, recv_opnd, StackOpnd[recv_idx], comptime_recv, megamorphic_exit)
# Do method lookup (vm_cc_cme(cc) != NULL)
cme = C.rb_callable_method_entry(comptime_recv_klass, mid)
if cme.nil?
asm.incr_counter(:send_missing_cme)
return CantCompile # We don't support vm_call_method_name
end
# Invalidate on redefinition (part of vm_search_method_fastpath)
Invariants.assume_method_lookup_stable(jit, cme)
return cme, comptime_recv_klass
end
# vm_call_general
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_general(jit, ctx, asm, mid, calling, cme, known_recv_class)
jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class)
end
# vm_call_method
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
# @param send_shift [Integer] The number of shifts needed for VM_CALL_OPT_SEND
def jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class)
# The main check of vm_call_method before vm_call_method_each_type
case C::METHOD_ENTRY_VISI(cme)
in C::METHOD_VISI_PUBLIC
# You can always call public methods
in C::METHOD_VISI_PRIVATE
# Allow only callsites without a receiver
if calling.flags & C::VM_CALL_FCALL == 0
asm.incr_counter(:send_private)
return CantCompile
end
in C::METHOD_VISI_PROTECTED
# If the method call is an FCALL, it is always valid
if calling.flags & C::VM_CALL_FCALL == 0
# otherwise we need an ancestry check to ensure the receiver is valid to be called as protected
jit_protected_callee_ancestry_guard(asm, cme, side_exit(jit, ctx))
end
end
# Get a compile-time receiver
recv_idx = calling.argc + (calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0 ? 1 : 0) # blockarg is not popped yet
recv_idx += calling.send_shift
comptime_recv = jit.peek_at_stack(recv_idx)
recv_opnd = ctx.stack_opnd(recv_idx)
jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
end
# Generate ancestry guard for protected callee.
# Calls to protected callees only go through when self.is_a?(klass_that_defines_the_callee).
def jit_protected_callee_ancestry_guard(asm, cme, side_exit)
# See vm_call_method().
def_class = cme.defined_class
# Note: PC isn't written to current control frame as rb_is_kind_of() shouldn't raise.
# VALUE rb_obj_is_kind_of(VALUE obj, VALUE klass);
asm.mov(C_ARGS[0], [CFP, C.rb_control_frame_t.offsetof(:self)])
asm.mov(C_ARGS[1], to_value(def_class))
asm.call(C.rb_obj_is_kind_of)
asm.test(C_RET, C_RET)
asm.jz(counted_exit(side_exit, :send_protected_check_failed))
end
# vm_call_method_each_type
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
case cme.def.type
in C::VM_METHOD_TYPE_ISEQ
iseq = def_iseq_ptr(cme.def)
jit_call_iseq(jit, ctx, asm, cme, calling, iseq)
in C::VM_METHOD_TYPE_NOTIMPLEMENTED
asm.incr_counter(:send_notimplemented)
return CantCompile
in C::VM_METHOD_TYPE_CFUNC
jit_call_cfunc(jit, ctx, asm, cme, calling, known_recv_class:)
in C::VM_METHOD_TYPE_ATTRSET
jit_call_attrset(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd)
in C::VM_METHOD_TYPE_IVAR
jit_call_ivar(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd)
in C::VM_METHOD_TYPE_MISSING
asm.incr_counter(:send_missing)
return CantCompile
in C::VM_METHOD_TYPE_BMETHOD
jit_call_bmethod(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
in C::VM_METHOD_TYPE_ALIAS
jit_call_alias(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
in C::VM_METHOD_TYPE_OPTIMIZED
jit_call_optimized(jit, ctx, asm, cme, calling, known_recv_class)
in C::VM_METHOD_TYPE_UNDEF
asm.incr_counter(:send_undef)
return CantCompile
in C::VM_METHOD_TYPE_ZSUPER
asm.incr_counter(:send_zsuper)
return CantCompile
in C::VM_METHOD_TYPE_REFINED
asm.incr_counter(:send_refined)
return CantCompile
end
end
# vm_call_iseq_setup
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_iseq(jit, ctx, asm, cme, calling, iseq, frame_type: nil, prev_ep: nil)
argc = calling.argc
flags = calling.flags
send_shift = calling.send_shift
# When you have keyword arguments, there is an extra object that gets
# placed on the stack the represents a bitmap of the keywords that were not
# specified at the call site. We need to keep track of the fact that this
# value is present on the stack in order to properly set up the callee's
# stack pointer.
doing_kw_call = iseq.body.param.flags.has_kw
supplying_kws = flags & C::VM_CALL_KWARG != 0
if flags & C::VM_CALL_TAILCALL != 0
# We can't handle tailcalls
asm.incr_counter(:send_tailcall)
return CantCompile
end
# No support for callees with these parameters yet as they require allocation
# or complex handling.
if iseq.body.param.flags.has_post
asm.incr_counter(:send_iseq_has_opt)
return CantCompile
end
if iseq.body.param.flags.has_kwrest
asm.incr_counter(:send_iseq_has_kwrest)
return CantCompile
end
# In order to handle backwards compatibility between ruby 3 and 2
# ruby2_keywords was introduced. It is called only on methods
# with splat and changes they way they handle them.
# We are just going to not compile these.
# https://www.rubydoc.info/stdlib/core/Proc:ruby2_keywords
if iseq.body.param.flags.ruby2_keywords && flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_iseq_ruby2_keywords)
return CantCompile
end
iseq_has_rest = iseq.body.param.flags.has_rest
if iseq_has_rest && calling.block_handler == :captured
asm.incr_counter(:send_iseq_has_rest_and_captured)
return CantCompile
end
if iseq_has_rest && iseq.body.param.flags.has_kw && supplying_kws
asm.incr_counter(:send_iseq_has_rest_and_kw_supplied)
return CantCompile
end
# If we have keyword arguments being passed to a callee that only takes
# positionals, then we need to allocate a hash. For now we're going to
# call that too complex and bail.
if supplying_kws && !iseq.body.param.flags.has_kw
asm.incr_counter(:send_iseq_has_no_kw)
return CantCompile
end
# If we have a method accepting no kwargs (**nil), exit if we have passed
# it any kwargs.
if supplying_kws && iseq.body.param.flags.accepts_no_kwarg
asm.incr_counter(:send_iseq_accepts_no_kwarg)
return CantCompile
end
# For computing number of locals to set up for the callee
num_params = iseq.body.param.size
# Block parameter handling. This mirrors setup_parameters_complex().
if iseq.body.param.flags.has_block
if iseq.body.local_iseq.to_i == iseq.to_i
num_params -= 1
else
# In this case (param.flags.has_block && local_iseq != iseq),
# the block argument is setup as a local variable and requires
# materialization (allocation). Bail.
asm.incr_counter(:send_iseq_materialized_block)
return CantCompile
end
end
if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_ZSUPER != 0
# zsuper methods are super calls without any arguments.
# They are also marked as splat, but don't actually have an array
# they pull arguments from, instead we need to change to call
# a different method with the current stack.
asm.incr_counter(:send_iseq_zsuper)
return CantCompile
end
start_pc_offset = 0
required_num = iseq.body.param.lead_num
# This struct represents the metadata about the caller-specified
# keyword arguments.
kw_arg = calling.kwarg
kw_arg_num = if kw_arg.nil?
0
else
kw_arg.keyword_len
end
# Arity handling and optional parameter setup
opts_filled = argc - required_num - kw_arg_num
opt_num = iseq.body.param.opt_num
opts_missing = opt_num - opts_filled
if doing_kw_call && flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_iseq_splat_with_kw)
return CantCompile
end
if iseq_has_rest && opt_num != 0
asm.incr_counter(:send_iseq_has_rest_and_optional)
return CantCompile
end
if opts_filled < 0 && flags & C::VM_CALL_ARGS_SPLAT == 0
# Too few arguments and no splat to make up for it
asm.incr_counter(:send_iseq_arity_error)
return CantCompile
end
if opts_filled > opt_num && !iseq_has_rest
# Too many arguments and no place to put them (i.e. rest arg)
asm.incr_counter(:send_iseq_arity_error)
return CantCompile
end
block_arg = flags & C::VM_CALL_ARGS_BLOCKARG != 0
# Guard block_arg_type
if guard_block_arg(jit, ctx, asm, calling) == CantCompile
return CantCompile
end
# If we have unfilled optional arguments and keyword arguments then we
# would need to adjust the arguments location to account for that.
# For now we aren't handling this case.
if doing_kw_call && opts_missing > 0
asm.incr_counter(:send_iseq_missing_optional_kw)
return CantCompile
end
# We will handle splat case later
if opt_num > 0 && flags & C::VM_CALL_ARGS_SPLAT == 0
num_params -= opts_missing
start_pc_offset = iseq.body.param.opt_table[opts_filled]
end
if doing_kw_call
# Here we're calling a method with keyword arguments and specifying
# keyword arguments at this call site.
# This struct represents the metadata about the callee-specified
# keyword parameters.
keyword = iseq.body.param.keyword
keyword_num = keyword.num
keyword_required_num = keyword.required_num
required_kwargs_filled = 0
if keyword_num > 30
# We have so many keywords that (1 << num) encoded as a FIXNUM
# (which shifts it left one more) no longer fits inside a 32-bit
# immediate.
asm.incr_counter(:send_iseq_too_many_kwargs)
return CantCompile
end
# Check that the kwargs being passed are valid
if supplying_kws
# This is the list of keyword arguments that the callee specified
# in its initial declaration.
# SAFETY: see compile.c for sizing of this slice.
callee_kwargs = keyword_num.times.map { |i| keyword.table[i] }
# Here we're going to build up a list of the IDs that correspond to
# the caller-specified keyword arguments. If they're not in the
# same order as the order specified in the callee declaration, then
# we're going to need to generate some code to swap values around
# on the stack.
caller_kwargs = []
kw_arg.keyword_len.times do |kwarg_idx|
sym = C.to_ruby(kw_arg[:keywords][kwarg_idx])
caller_kwargs << C.rb_sym2id(sym)
end
# First, we're going to be sure that the names of every
# caller-specified keyword argument correspond to a name in the
# list of callee-specified keyword parameters.
caller_kwargs.each do |caller_kwarg|
search_result = callee_kwargs.map.with_index.find { |kwarg, _| kwarg == caller_kwarg }
case search_result
in nil
# If the keyword was never found, then we know we have a
# mismatch in the names of the keyword arguments, so we need to
# bail.
asm.incr_counter(:send_iseq_kwargs_mismatch)
return CantCompile
in _, callee_idx if callee_idx < keyword_required_num
# Keep a count to ensure all required kwargs are specified
required_kwargs_filled += 1
else
end
end
end
assert_equal(true, required_kwargs_filled <= keyword_required_num)
if required_kwargs_filled != keyword_required_num
asm.incr_counter(:send_iseq_kwargs_mismatch)
return CantCompile
end
end
# Check if we need the arg0 splat handling of vm_callee_setup_block_arg
arg_setup_block = (calling.block_handler == :captured) # arg_setup_type: arg_setup_block (invokeblock)
block_arg0_splat = arg_setup_block && argc == 1 &&
(iseq.body.param.flags.has_lead || opt_num > 1) &&
!iseq.body.param.flags.ambiguous_param0
if block_arg0_splat
# If block_arg0_splat, we still need side exits after splat, but
# doing push_splat_args here disallows it. So bail out.
if flags & C::VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest
asm.incr_counter(:invokeblock_iseq_arg0_args_splat)
return CantCompile
end
# The block_arg0_splat implementation is for the rb_simple_iseq_p case,
# but doing_kw_call means it's not a simple ISEQ.
if doing_kw_call
asm.incr_counter(:invokeblock_iseq_arg0_has_kw)
return CantCompile
end
# The block_arg0_splat implementation cannot deal with optional parameters.
# This is a setup_parameters_complex() situation and interacts with the
# starting position of the callee.
if opt_num > 1
asm.incr_counter(:invokeblock_iseq_arg0_optional)
return CantCompile
end
end
if flags & C::VM_CALL_ARGS_SPLAT != 0 && !iseq_has_rest
array = jit.peek_at_stack(block_arg ? 1 : 0)
splat_array_length = if array.nil?
0
else
array.length
end
if opt_num == 0 && required_num != splat_array_length + argc - 1
asm.incr_counter(:send_iseq_splat_arity_error)
return CantCompile
end
end
# We will not have CantCompile from here.
if block_arg
ctx.stack_pop(1)
end
if calling.block_handler == C::VM_BLOCK_HANDLER_NONE && iseq.body.builtin_attrs & C::BUILTIN_ATTR_LEAF != 0
if jit_leaf_builtin_func(jit, ctx, asm, flags, iseq)
return KeepCompiling
end
end
# Number of locals that are not parameters
num_locals = iseq.body.local_table_size - num_params
# Stack overflow check
# Note that vm_push_frame checks it against a decremented cfp, hence the multiply by 2.
# #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin)
asm.comment('stack overflow check')
locals_offs = C.VALUE.size * (num_locals + iseq.body.stack_max) + 2 * C.rb_control_frame_t.size
asm.lea(:rax, ctx.sp_opnd(locals_offs))
asm.cmp(CFP, :rax)
asm.jbe(counted_exit(side_exit(jit, ctx), :send_stackoverflow))
# push_splat_args does stack manipulation so we can no longer side exit
if splat_array_length
remaining_opt = (opt_num + required_num) - (splat_array_length + (argc - 1))
if opt_num > 0
# We are going to jump to the correct offset based on how many optional
# params are remaining.
offset = opt_num - remaining_opt
start_pc_offset = iseq.body.param.opt_table[offset]
end
# We are going to assume that the splat fills
# all the remaining arguments. In the generated code
# we test if this is true and if not side exit.
argc = argc - 1 + splat_array_length + remaining_opt
push_splat_args(splat_array_length, jit, ctx, asm)
remaining_opt.times do
# We need to push nil for the optional arguments
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, Qnil)
end
end
# This is a .send call and we need to adjust the stack
if flags & C::VM_CALL_OPT_SEND != 0
handle_opt_send_shift_stack(asm, argc, ctx, send_shift:)
end
if iseq_has_rest
# We are going to allocate so setting pc and sp.
jit_save_pc(jit, asm) # clobbers rax
jit_save_sp(ctx, asm)
if flags & C::VM_CALL_ARGS_SPLAT != 0
non_rest_arg_count = argc - 1
# We start by dupping the array because someone else might have
# a reference to it.
array = ctx.stack_pop(1)
asm.mov(C_ARGS[0], array)
asm.call(C.rb_ary_dup)
array = C_RET
if non_rest_arg_count > required_num
# If we have more arguments than required, we need to prepend
# the items from the stack onto the array.
diff = (non_rest_arg_count - required_num)
# diff is >0 so no need to worry about null pointer
asm.comment('load pointer to array elements')
offset_magnitude = C.VALUE.size * diff
values_opnd = ctx.sp_opnd(-offset_magnitude)
values_ptr = :rcx
asm.lea(values_ptr, values_opnd)
asm.comment('prepend stack values to rest array')
asm.mov(C_ARGS[0], diff)
asm.mov(C_ARGS[1], values_ptr)
asm.mov(C_ARGS[2], array)
asm.call(C.rb_ary_unshift_m)
ctx.stack_pop(diff)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
# We now should have the required arguments
# and an array of all the rest arguments
argc = required_num + 1
elsif non_rest_arg_count < required_num
# If we have fewer arguments than required, we need to take some
# from the array and move them to the stack.
diff = (required_num - non_rest_arg_count)
# This moves the arguments onto the stack. But it doesn't modify the array.
move_rest_args_to_stack(array, diff, jit, ctx, asm)
# We will now slice the array to give us a new array of the correct size
asm.mov(C_ARGS[0], array)
asm.mov(C_ARGS[1], diff)
asm.call(C.rjit_rb_ary_subseq_length)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
# We now should have the required arguments
# and an array of all the rest arguments
argc = required_num + 1
else
# The arguments are equal so we can just push to the stack
assert_equal(non_rest_arg_count, required_num)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, array)
end
else
assert_equal(true, argc >= required_num)
n = (argc - required_num)
argc = required_num + 1
# If n is 0, then elts is never going to be read, so we can just pass null
if n == 0
values_ptr = 0
else
asm.comment('load pointer to array elements')
offset_magnitude = C.VALUE.size * n
values_opnd = ctx.sp_opnd(-offset_magnitude)
values_ptr = :rcx
asm.lea(values_ptr, values_opnd)
end
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], n)
asm.mov(C_ARGS[2], values_ptr)
asm.call(C.rb_ec_ary_new_from_values)
ctx.stack_pop(n)
stack_ret = ctx.stack_push(Type::TArray)
asm.mov(stack_ret, C_RET)
end
end
if doing_kw_call
# Here we're calling a method with keyword arguments and specifying
# keyword arguments at this call site.
# Number of positional arguments the callee expects before the first
# keyword argument
args_before_kw = required_num + opt_num
# This struct represents the metadata about the caller-specified
# keyword arguments.
ci_kwarg = calling.kwarg
caller_keyword_len = if ci_kwarg.nil?
0
else
ci_kwarg.keyword_len
end
# This struct represents the metadata about the callee-specified
# keyword parameters.
keyword = iseq.body.param.keyword
asm.comment('keyword args')
# This is the list of keyword arguments that the callee specified
# in its initial declaration.
callee_kwargs = keyword.table
total_kwargs = keyword.num
# Here we're going to build up a list of the IDs that correspond to
# the caller-specified keyword arguments. If they're not in the
# same order as the order specified in the callee declaration, then
# we're going to need to generate some code to swap values around
# on the stack.
caller_kwargs = []
caller_keyword_len.times do |kwarg_idx|
sym = C.to_ruby(ci_kwarg[:keywords][kwarg_idx])
caller_kwargs << C.rb_sym2id(sym)
end
kwarg_idx = caller_keyword_len
unspecified_bits = 0
keyword_required_num = keyword.required_num
(keyword_required_num...total_kwargs).each do |callee_idx|
already_passed = false
callee_kwarg = callee_kwargs[callee_idx]
caller_keyword_len.times do |caller_idx|
if caller_kwargs[caller_idx] == callee_kwarg
already_passed = true
break
end
end
unless already_passed
# Reserve space on the stack for each default value we'll be
# filling in (which is done in the next loop). Also increments
# argc so that the callee's SP is recorded correctly.
argc += 1
default_arg = ctx.stack_push(Type::Unknown)
# callee_idx - keyword->required_num is used in a couple of places below.
req_num = keyword.required_num
extra_args = callee_idx - req_num
# VALUE default_value = keyword->default_values[callee_idx - keyword->required_num];
default_value = keyword.default_values[extra_args]
if default_value == Qundef
# Qundef means that this value is not constant and must be
# recalculated at runtime, so we record it in unspecified_bits
# (Qnil is then used as a placeholder instead of Qundef).
unspecified_bits |= 0x01 << extra_args
default_value = Qnil
end
asm.mov(:rax, default_value)
asm.mov(default_arg, :rax)
caller_kwargs[kwarg_idx] = callee_kwarg
kwarg_idx += 1
end
end
assert_equal(kwarg_idx, total_kwargs)
# Next, we're going to loop through every keyword that was
# specified by the caller and make sure that it's in the correct
# place. If it's not we're going to swap it around with another one.
total_kwargs.times do |kwarg_idx|
callee_kwarg = callee_kwargs[kwarg_idx]
# If the argument is already in the right order, then we don't
# need to generate any code since the expected value is already
# in the right place on the stack.
if callee_kwarg == caller_kwargs[kwarg_idx]
next
end
# In this case the argument is not in the right place, so we
# need to find its position where it _should_ be and swap with
# that location.
((kwarg_idx + 1)...total_kwargs).each do |swap_idx|
if callee_kwarg == caller_kwargs[swap_idx]
# First we're going to generate the code that is going
# to perform the actual swapping at runtime.
offset0 = argc - 1 - swap_idx - args_before_kw
offset1 = argc - 1 - kwarg_idx - args_before_kw
stack_swap(jit, ctx, asm, offset0, offset1)
# Next we're going to do some bookkeeping on our end so
# that we know the order that the arguments are
# actually in now.
caller_kwargs[kwarg_idx], caller_kwargs[swap_idx] =
caller_kwargs[swap_idx], caller_kwargs[kwarg_idx]
break
end
end
end
# Keyword arguments cause a special extra local variable to be
# pushed onto the stack that represents the parameters that weren't
# explicitly given a value and have a non-constant default.
asm.mov(ctx.stack_opnd(-1), C.to_value(unspecified_bits))
end
# Same as vm_callee_setup_block_arg_arg0_check and vm_callee_setup_block_arg_arg0_splat
# on vm_callee_setup_block_arg for arg_setup_block. This is done after CALLER_SETUP_ARG
# and CALLER_REMOVE_EMPTY_KW_SPLAT, so this implementation is put here. This may need
# side exits, so you still need to allow side exits here if block_arg0_splat is true.
# Note that you can't have side exits after this arg0 splat.
if block_arg0_splat
asm.incr_counter(:send_iseq_block_arg0_splat)
return CantCompile
end
# Create a context for the callee
callee_ctx = Context.new
# Set the argument types in the callee's context
argc.times do |arg_idx|
stack_offs = argc - arg_idx - 1
arg_type = ctx.get_opnd_type(StackOpnd[stack_offs])
callee_ctx.set_local_type(arg_idx, arg_type)
end
recv_type = if calling.block_handler == :captured
Type::Unknown # we don't track the type information of captured->self for now
else
ctx.get_opnd_type(StackOpnd[argc])
end
callee_ctx.upgrade_opnd_type(SelfOpnd, recv_type)
# Setup the new frame
frame_type ||= C::VM_FRAME_MAGIC_METHOD | C::VM_ENV_FLAG_LOCAL
jit_push_frame(
jit, ctx, asm, cme, flags, argc, frame_type, calling.block_handler,
iseq: iseq,
local_size: num_locals,
stack_max: iseq.body.stack_max,
prev_ep:,
doing_kw_call:,
)
# Directly jump to the entry point of the callee
pc = (iseq.body.iseq_encoded + start_pc_offset).to_i
jit_direct_jump(iseq, pc, callee_ctx, asm)
EndBlock
end
def jit_leaf_builtin_func(jit, ctx, asm, flags, iseq)
builtin_func = builtin_function(iseq)
if builtin_func.nil?
return false
end
# this is a .send call not currently supported for builtins
if flags & C::VM_CALL_OPT_SEND != 0
return false
end
builtin_argc = builtin_func.argc
if builtin_argc + 1 >= C_ARGS.size
return false
end
asm.comment('inlined leaf builtin')
# Skip this if it doesn't trigger GC
if iseq.body.builtin_attrs & C::BUILTIN_ATTR_NO_GC == 0
# The callee may allocate, e.g. Integer#abs on a Bignum.
# Save SP for GC, save PC for allocation tracing, and prepare
# for global invalidation after GC's VM lock contention.
jit_prepare_routine_call(jit, ctx, asm)
end
# Call the builtin func (ec, recv, arg1, arg2, ...)
asm.mov(C_ARGS[0], EC)
# Copy self and arguments
(0..builtin_argc).each do |i|
stack_opnd = ctx.stack_opnd(builtin_argc - i)
asm.mov(C_ARGS[i + 1], stack_opnd)
end
ctx.stack_pop(builtin_argc + 1)
asm.call(builtin_func.func_ptr)
# Push the return value
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
return true
end
# vm_call_cfunc
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_cfunc(jit, ctx, asm, cme, calling, known_recv_class: nil)
argc = calling.argc
flags = calling.flags
cfunc = cme.def.body.cfunc
cfunc_argc = cfunc.argc
# If the function expects a Ruby array of arguments
if cfunc_argc < 0 && cfunc_argc != -1
asm.incr_counter(:send_cfunc_ruby_array_varg)
return CantCompile
end
# We aren't handling a vararg cfuncs with splat currently.
if flags & C::VM_CALL_ARGS_SPLAT != 0 && cfunc_argc == -1
asm.incr_counter(:send_args_splat_cfunc_var_args)
return CantCompile
end
if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_ZSUPER != 0
# zsuper methods are super calls without any arguments.
# They are also marked as splat, but don't actually have an array
# they pull arguments from, instead we need to change to call
# a different method with the current stack.
asm.incr_counter(:send_args_splat_cfunc_zuper)
return CantCompile;
end
# In order to handle backwards compatibility between ruby 3 and 2
# ruby2_keywords was introduced. It is called only on methods
# with splat and changes they way they handle them.
# We are just going to not compile these.
# https://docs.ruby-lang.org/en/3.2/Module.html#method-i-ruby2_keywords
if jit.iseq.body.param.flags.ruby2_keywords && flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_args_splat_cfunc_ruby2_keywords)
return CantCompile;
end
kw_arg = calling.kwarg
kw_arg_num = if kw_arg.nil?
0
else
kw_arg.keyword_len
end
if kw_arg_num != 0 && flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_cfunc_splat_with_kw)
return CantCompile
end
if c_method_tracing_currently_enabled?
# Don't JIT if tracing c_call or c_return
asm.incr_counter(:send_cfunc_tracing)
return CantCompile
end
# Delegate to codegen for C methods if we have it.
if kw_arg.nil? && flags & C::VM_CALL_OPT_SEND == 0 && flags & C::VM_CALL_ARGS_SPLAT == 0 && (cfunc_argc == -1 || argc == cfunc_argc)
known_cfunc_codegen = lookup_cfunc_codegen(cme.def)
if known_cfunc_codegen&.call(jit, ctx, asm, argc, known_recv_class)
# cfunc codegen generated code. Terminate the block so
# there isn't multiple calls in the same block.
jump_to_next_insn(jit, ctx, asm)
return EndBlock
end
end
# Check for interrupts
jit_check_ints(jit, ctx, asm)
# Stack overflow check
# #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin)
# REG_CFP <= REG_SP + 4 * SIZEOF_VALUE + sizeof(rb_control_frame_t)
asm.comment('stack overflow check')
asm.lea(:rax, ctx.sp_opnd(C.VALUE.size * 4 + 2 * C.rb_control_frame_t.size))
asm.cmp(CFP, :rax)
asm.jbe(counted_exit(side_exit(jit, ctx), :send_stackoverflow))
# Number of args which will be passed through to the callee
# This is adjusted by the kwargs being combined into a hash.
passed_argc = if kw_arg.nil?
argc
else
argc - kw_arg_num + 1
end
# If the argument count doesn't match
if cfunc_argc >= 0 && cfunc_argc != passed_argc && flags & C::VM_CALL_ARGS_SPLAT == 0
asm.incr_counter(:send_cfunc_argc_mismatch)
return CantCompile
end
# Don't JIT functions that need C stack arguments for now
if cfunc_argc >= 0 && passed_argc + 1 > C_ARGS.size
asm.incr_counter(:send_cfunc_toomany_args)
return CantCompile
end
block_arg = flags & C::VM_CALL_ARGS_BLOCKARG != 0
# Guard block_arg_type
if guard_block_arg(jit, ctx, asm, calling) == CantCompile
return CantCompile
end
if block_arg
ctx.stack_pop(1)
end
# push_splat_args does stack manipulation so we can no longer side exit
if flags & C::VM_CALL_ARGS_SPLAT != 0
assert_equal(true, cfunc_argc >= 0)
required_args = cfunc_argc - (argc - 1)
# + 1 because we pass self
if required_args + 1 >= C_ARGS.size
asm.incr_counter(:send_cfunc_toomany_args)
return CantCompile
end
# We are going to assume that the splat fills
# all the remaining arguments. So the number of args
# should just equal the number of args the cfunc takes.
# In the generated code we test if this is true
# and if not side exit.
argc = cfunc_argc
passed_argc = argc
push_splat_args(required_args, jit, ctx, asm)
end
# This is a .send call and we need to adjust the stack
if flags & C::VM_CALL_OPT_SEND != 0
handle_opt_send_shift_stack(asm, argc, ctx, send_shift: calling.send_shift)
end
# Points to the receiver operand on the stack
# Store incremented PC into current control frame in case callee raises.
jit_save_pc(jit, asm)
# Increment the stack pointer by 3 (in the callee)
# sp += 3
frame_type = C::VM_FRAME_MAGIC_CFUNC | C::VM_FRAME_FLAG_CFRAME | C::VM_ENV_FLAG_LOCAL
if kw_arg
frame_type |= C::VM_FRAME_FLAG_CFRAME_KW
end
jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, calling.block_handler)
if kw_arg
# Build a hash from all kwargs passed
asm.comment('build_kwhash')
imemo_ci = calling.ci_addr
# we assume all callinfos with kwargs are on the GC heap
assert_equal(true, C.imemo_type_p(imemo_ci, C.imemo_callinfo))
asm.mov(C_ARGS[0], imemo_ci)
asm.lea(C_ARGS[1], ctx.sp_opnd(0))
asm.call(C.rjit_build_kwhash)
# Replace the stack location at the start of kwargs with the new hash
stack_opnd = ctx.stack_opnd(argc - passed_argc)
asm.mov(stack_opnd, C_RET)
end
# Copy SP because REG_SP will get overwritten
sp = :rax
asm.lea(sp, ctx.sp_opnd(0))
# Pop the C function arguments from the stack (in the caller)
ctx.stack_pop(argc + 1)
# Write interpreter SP into CFP.
# Needed in case the callee yields to the block.
jit_save_sp(ctx, asm)
# Non-variadic method
case cfunc_argc
in (0..) # Non-variadic method
# Copy the arguments from the stack to the C argument registers
# self is the 0th argument and is at index argc from the stack top
(0..passed_argc).each do |i|
asm.mov(C_ARGS[i], [sp, -(argc + 1 - i) * C.VALUE.size])
end
in -1 # Variadic method: rb_f_puts(int argc, VALUE *argv, VALUE recv)
# The method gets a pointer to the first argument
# rb_f_puts(int argc, VALUE *argv, VALUE recv)
asm.mov(C_ARGS[0], passed_argc)
asm.lea(C_ARGS[1], [sp, -argc * C.VALUE.size]) # argv
asm.mov(C_ARGS[2], [sp, -(argc + 1) * C.VALUE.size]) # recv
end
# Call the C function
# VALUE ret = (cfunc->func)(recv, argv[0], argv[1]);
# cfunc comes from compile-time cme->def, which we assume to be stable.
# Invalidation logic is in yjit_method_lookup_change()
asm.comment('call C function')
asm.mov(:rax, cfunc.func)
asm.call(:rax) # TODO: use rel32 if close enough
# Record code position for TracePoint patching. See full_cfunc_return().
Invariants.record_global_inval_patch(asm, full_cfunc_return)
# Push the return value on the Ruby stack
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
# Pop the stack frame (ec->cfp++)
# Instead of recalculating, we can reuse the previous CFP, which is stored in a callee-saved
# register
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], CFP)
# cfunc calls may corrupt types
ctx.clear_local_types
# Note: the return block of jit_call_iseq has ctx->sp_offset == 1
# which allows for sharing the same successor.
# Jump (fall through) to the call continuation block
# We do this to end the current block after the call
assert_equal(1, ctx.sp_offset)
jump_to_next_insn(jit, ctx, asm)
EndBlock
end
# vm_call_attrset
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_attrset(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd)
argc = calling.argc
flags = calling.flags
send_shift = calling.send_shift
if flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_attrset_splat)
return CantCompile
end
if flags & C::VM_CALL_KWARG != 0
asm.incr_counter(:send_attrset_kwarg)
return CantCompile
elsif argc != 1 || !C.RB_TYPE_P(comptime_recv, C::RUBY_T_OBJECT)
asm.incr_counter(:send_attrset_method)
return CantCompile
elsif c_method_tracing_currently_enabled?
# Can't generate code for firing c_call and c_return events
# See :attr-tracing:
asm.incr_counter(:send_c_tracingg)
return CantCompile
elsif flags & C::VM_CALL_ARGS_BLOCKARG != 0
asm.incr_counter(:send_block_arg)
return CantCompile
end
ivar_name = cme.def.body.attr.id
# This is a .send call and we need to adjust the stack
if flags & C::VM_CALL_OPT_SEND != 0
handle_opt_send_shift_stack(asm, argc, ctx, send_shift:)
end
# Save the PC and SP because the callee may allocate
# Note that this modifies REG_SP, which is why we do it first
jit_prepare_routine_call(jit, ctx, asm)
# Get the operands from the stack
val_opnd = ctx.stack_pop(1)
recv_opnd = ctx.stack_pop(1)
# Call rb_vm_set_ivar_id with the receiver, the ivar name, and the value
asm.mov(C_ARGS[0], recv_opnd)
asm.mov(C_ARGS[1], ivar_name)
asm.mov(C_ARGS[2], val_opnd)
asm.call(C.rb_vm_set_ivar_id)
out_opnd = ctx.stack_push(Type::Unknown)
asm.mov(out_opnd, C_RET)
KeepCompiling
end
# vm_call_ivar (+ part of vm_call_method_each_type)
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_ivar(jit, ctx, asm, cme, calling, comptime_recv, recv_opnd)
argc = calling.argc
flags = calling.flags
if flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_ivar_splat)
return CantCompile
end
if argc != 0
asm.incr_counter(:send_arity)
return CantCompile
end
# We don't support handle_opt_send_shift_stack for this yet.
if flags & C::VM_CALL_OPT_SEND != 0
asm.incr_counter(:send_ivar_opt_send)
return CantCompile
end
ivar_id = cme.def.body.attr.id
# Not handling block_handler
if flags & C::VM_CALL_ARGS_BLOCKARG != 0
asm.incr_counter(:send_block_arg)
return CantCompile
end
jit_getivar(jit, ctx, asm, comptime_recv, ivar_id, recv_opnd, StackOpnd[0])
end
# vm_call_bmethod
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_bmethod(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
proc_addr = cme.def.body.bmethod.proc
proc_t = C.rb_yjit_get_proc_ptr(proc_addr)
proc_block = proc_t.block
if proc_block.type != C.block_type_iseq
asm.incr_counter(:send_bmethod_not_iseq)
return CantCompile
end
capture = proc_block.as.captured
iseq = capture.code.iseq
# TODO: implement this
# Optimize for single ractor mode and avoid runtime check for
# "defined with an un-shareable Proc in a different Ractor"
# if !assume_single_ractor_mode(jit, ocb)
# return CantCompile;
# end
# Passing a block to a block needs logic different from passing
# a block to a method and sometimes requires allocation. Bail for now.
if calling.block_handler != C::VM_BLOCK_HANDLER_NONE
asm.incr_counter(:send_bmethod_blockarg)
return CantCompile
end
jit_call_iseq(
jit, ctx, asm, cme, calling, iseq,
frame_type: C::VM_FRAME_MAGIC_BLOCK | C::VM_FRAME_FLAG_BMETHOD | C::VM_FRAME_FLAG_LAMBDA,
prev_ep: capture.ep,
)
end
# vm_call_alias
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_alias(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
cme = C.rb_aliased_callable_method_entry(cme)
jit_call_method_each_type(jit, ctx, asm, calling, cme, comptime_recv, recv_opnd, known_recv_class)
end
# vm_call_optimized
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_optimized(jit, ctx, asm, cme, calling, known_recv_class)
if calling.flags & C::VM_CALL_ARGS_BLOCKARG != 0
# Not working yet
asm.incr_counter(:send_block_arg)
return CantCompile
end
case cme.def.body.optimized.type
in C::OPTIMIZED_METHOD_TYPE_SEND
jit_call_opt_send(jit, ctx, asm, cme, calling, known_recv_class)
in C::OPTIMIZED_METHOD_TYPE_CALL
jit_call_opt_call(jit, ctx, asm, cme, calling.flags, calling.argc, calling.block_handler, known_recv_class, send_shift: calling.send_shift)
in C::OPTIMIZED_METHOD_TYPE_BLOCK_CALL
asm.incr_counter(:send_optimized_block_call)
return CantCompile
in C::OPTIMIZED_METHOD_TYPE_STRUCT_AREF
jit_call_opt_struct_aref(jit, ctx, asm, cme, calling.flags, calling.argc, calling.block_handler, known_recv_class, send_shift: calling.send_shift)
in C::OPTIMIZED_METHOD_TYPE_STRUCT_ASET
asm.incr_counter(:send_optimized_struct_aset)
return CantCompile
end
end
# vm_call_opt_send
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_opt_send(jit, ctx, asm, cme, calling, known_recv_class)
if jit_caller_setup_arg(jit, ctx, asm, calling.flags) == CantCompile
return CantCompile
end
if calling.argc == 0
asm.incr_counter(:send_optimized_send_no_args)
return CantCompile
end
calling.argc -= 1
# We aren't handling `send(:send, ...)` yet. This might work, but not tested yet.
if calling.send_shift > 0
asm.incr_counter(:send_optimized_send_send)
return CantCompile
end
# Lazily handle stack shift in handle_opt_send_shift_stack
calling.send_shift += 1
jit_call_symbol(jit, ctx, asm, cme, calling, known_recv_class, C::VM_CALL_FCALL)
end
# vm_call_opt_call
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_opt_call(jit, ctx, asm, cme, flags, argc, block_handler, known_recv_class, send_shift:)
if block_handler != C::VM_BLOCK_HANDLER_NONE
asm.incr_counter(:send_optimized_call_block)
return CantCompile
end
if flags & C::VM_CALL_KWARG != 0
asm.incr_counter(:send_optimized_call_kwarg)
return CantCompile
end
if flags & C::VM_CALL_ARGS_SPLAT != 0
asm.incr_counter(:send_optimized_call_splat)
return CantCompile
end
# TODO: implement this
# Optimize for single ractor mode and avoid runtime check for
# "defined with an un-shareable Proc in a different Ractor"
# if !assume_single_ractor_mode(jit, ocb)
# return CantCompile
# end
# If this is a .send call we need to adjust the stack
if flags & C::VM_CALL_OPT_SEND != 0
handle_opt_send_shift_stack(asm, argc, ctx, send_shift:)
end
# About to reset the SP, need to load this here
recv_idx = argc # blockarg is not supported. send_shift is already handled.
asm.mov(:rcx, ctx.stack_opnd(recv_idx)) # recv
# Save the PC and SP because the callee can make Ruby calls
jit_prepare_routine_call(jit, ctx, asm) # NOTE: clobbers rax
asm.lea(:rax, ctx.sp_opnd(0)) # sp
kw_splat = flags & C::VM_CALL_KW_SPLAT
asm.mov(C_ARGS[0], :rcx)
asm.mov(C_ARGS[1], EC)
asm.mov(C_ARGS[2], argc)
asm.lea(C_ARGS[3], [:rax, -argc * C.VALUE.size]) # stack_argument_pointer. NOTE: C_ARGS[3] is rcx
asm.mov(C_ARGS[4], kw_splat)
asm.mov(C_ARGS[5], C::VM_BLOCK_HANDLER_NONE)
asm.call(C.rjit_optimized_call)
ctx.stack_pop(argc + 1)
stack_ret = ctx.stack_push(Type::Unknown)
asm.mov(stack_ret, C_RET)
return KeepCompiling
end
# vm_call_opt_struct_aref
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_opt_struct_aref(jit, ctx, asm, cme, flags, argc, block_handler, known_recv_class, send_shift:)
if argc != 0
asm.incr_counter(:send_optimized_struct_aref_error)
return CantCompile
end
off = cme.def.body.optimized.index
recv_idx = argc # blockarg is not supported
recv_idx += send_shift
comptime_recv = jit.peek_at_stack(recv_idx)
# This is a .send call and we need to adjust the stack
if flags & C::VM_CALL_OPT_SEND != 0
handle_opt_send_shift_stack(asm, argc, ctx, send_shift:)
end
# All structs from the same Struct class should have the same
# length. So if our comptime_recv is embedded all runtime
# structs of the same class should be as well, and the same is
# true of the converse.
embedded = C::FL_TEST_RAW(comptime_recv, C::RSTRUCT_EMBED_LEN_MASK)
asm.comment('struct aref')
asm.mov(:rax, ctx.stack_pop(1)) # recv
if embedded
asm.mov(:rax, [:rax, C.RStruct.offsetof(:as, :ary) + (C.VALUE.size * off)])
else
asm.mov(:rax, [:rax, C.RStruct.offsetof(:as, :heap, :ptr)])
asm.mov(:rax, [:rax, C.VALUE.size * off])
end
ret = ctx.stack_push(Type::Unknown)
asm.mov(ret, :rax)
jump_to_next_insn(jit, ctx, asm)
EndBlock
end
# vm_call_opt_send (lazy part)
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def handle_opt_send_shift_stack(asm, argc, ctx, send_shift:)
# We don't support `send(:send, ...)` for now.
assert_equal(1, send_shift)
asm.comment('shift stack')
(0...argc).reverse_each do |i|
opnd = ctx.stack_opnd(i)
opnd2 = ctx.stack_opnd(i + 1)
asm.mov(:rax, opnd)
asm.mov(opnd2, :rax)
end
ctx.shift_stack(argc)
end
# vm_call_symbol
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_call_symbol(jit, ctx, asm, cme, calling, known_recv_class, flags)
flags |= C::VM_CALL_OPT_SEND | (calling.kw_splat ? C::VM_CALL_KW_SPLAT : 0)
comptime_symbol = jit.peek_at_stack(calling.argc)
if comptime_symbol.class != String && !static_symbol?(comptime_symbol)
asm.incr_counter(:send_optimized_send_not_sym_or_str)
return CantCompile
end
mid = C.get_symbol_id(comptime_symbol)
if mid == 0
asm.incr_counter(:send_optimized_send_null_mid)
return CantCompile
end
asm.comment("Guard #{comptime_symbol.inspect} is on stack")
class_changed_exit = counted_exit(side_exit(jit, ctx), :send_optimized_send_mid_class_changed)
jit_guard_known_klass(
jit, ctx, asm, C.rb_class_of(comptime_symbol), ctx.stack_opnd(calling.argc),
StackOpnd[calling.argc], comptime_symbol, class_changed_exit,
)
asm.mov(C_ARGS[0], ctx.stack_opnd(calling.argc))
asm.call(C.rb_get_symbol_id)
asm.cmp(C_RET, mid)
id_changed_exit = counted_exit(side_exit(jit, ctx), :send_optimized_send_mid_id_changed)
jit_chain_guard(:jne, jit, ctx, asm, id_changed_exit)
# rb_callable_method_entry_with_refinements
calling.flags = flags
cme, _ = jit_search_method(jit, ctx, asm, mid, calling)
if cme == CantCompile
return CantCompile
end
if flags & C::VM_CALL_FCALL != 0
return jit_call_method(jit, ctx, asm, mid, calling, cme, known_recv_class)
end
raise NotImplementedError # unreachable for now
end
# vm_push_frame
#
# Frame structure:
# | args | locals | cme/cref | block_handler/prev EP | frame type (EP here) | stack bottom (SP here)
#
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_push_frame(jit, ctx, asm, cme, flags, argc, frame_type, block_handler, iseq: nil, local_size: 0, stack_max: 0, prev_ep: nil, doing_kw_call: nil)
# Save caller SP and PC before pushing a callee frame for backtrace and side exits
asm.comment('save SP to caller CFP')
recv_idx = argc # blockarg is already popped
recv_idx += (block_handler == :captured) ? 0 : 1 # receiver is not on stack when captured->self is used
if iseq
# Skip setting this to SP register. This cfp->sp will be copied to SP on leave insn.
asm.lea(:rax, ctx.sp_opnd(C.VALUE.size * -recv_idx)) # Pop receiver and arguments to prepare for side exits
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], :rax)
else
asm.lea(SP, ctx.sp_opnd(C.VALUE.size * -recv_idx))
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP)
ctx.sp_offset = recv_idx
end
jit_save_pc(jit, asm, comment: 'save PC to caller CFP')
sp_offset = ctx.sp_offset + 3 + local_size + (doing_kw_call ? 1 : 0) # callee_sp
local_size.times do |i|
asm.comment('set local variables') if i == 0
local_index = sp_offset + i - local_size - 3
asm.mov([SP, C.VALUE.size * local_index], Qnil)
end
asm.comment('set up EP with managing data')
ep_offset = sp_offset - 1
# ep[-2]: cref_or_me
asm.mov(:rax, cme.to_i)
asm.mov([SP, C.VALUE.size * (ep_offset - 2)], :rax)
# ep[-1]: block handler or prev env ptr (specval)
if prev_ep
asm.mov(:rax, prev_ep.to_i | 1) # tagged prev ep
asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax)
elsif block_handler == :captured
# Set captured->ep, saving captured in :rcx for captured->self
ep_reg = :rcx
jit_get_lep(jit, asm, reg: ep_reg)
asm.mov(:rcx, [ep_reg, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # block_handler
asm.and(:rcx, ~0x3) # captured
asm.mov(:rax, [:rcx, C.VALUE.size]) # captured->ep
asm.or(:rax, 0x1) # GC_GUARDED_PTR
asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax)
elsif block_handler == C::VM_BLOCK_HANDLER_NONE
asm.mov([SP, C.VALUE.size * (ep_offset - 1)], C::VM_BLOCK_HANDLER_NONE)
elsif block_handler == C.rb_block_param_proxy
# vm_caller_setup_arg_block: block_code == rb_block_param_proxy
jit_get_lep(jit, asm, reg: :rax) # VM_CF_BLOCK_HANDLER: VM_CF_LEP
asm.mov(:rax, [:rax, C.VALUE.size * C::VM_ENV_DATA_INDEX_SPECVAL]) # VM_CF_BLOCK_HANDLER: VM_ENV_BLOCK_HANDLER
asm.mov([CFP, C.rb_control_frame_t.offsetof(:block_code)], :rax) # reg_cfp->block_code = handler
asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax) # return handler;
else # assume blockiseq
asm.mov(:rax, block_handler)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:block_code)], :rax)
asm.lea(:rax, [CFP, C.rb_control_frame_t.offsetof(:self)]) # VM_CFP_TO_CAPTURED_BLOCK
asm.or(:rax, 1) # VM_BH_FROM_ISEQ_BLOCK
asm.mov([SP, C.VALUE.size * (ep_offset - 1)], :rax)
end
# ep[-0]: ENV_FLAGS
asm.mov([SP, C.VALUE.size * (ep_offset - 0)], frame_type)
asm.comment('set up new frame')
cfp_offset = -C.rb_control_frame_t.size # callee CFP
# For ISEQ, JIT code will set it as needed. However, C func needs 0 there for svar frame detection.
if iseq.nil?
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:pc)], 0)
end
asm.mov(:rax, iseq.to_i)
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:iseq)], :rax)
if block_handler == :captured
asm.mov(:rax, [:rcx]) # captured->self
else
self_index = ctx.sp_offset - (1 + argc) # blockarg has been popped
asm.mov(:rax, [SP, C.VALUE.size * self_index])
end
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:self)], :rax)
asm.lea(:rax, [SP, C.VALUE.size * ep_offset])
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:ep)], :rax)
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:block_code)], 0)
# Update SP register only for ISEQ calls. SP-relative operations should be done above this.
sp_reg = iseq ? SP : :rax
asm.lea(sp_reg, [SP, C.VALUE.size * sp_offset])
asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:sp)], sp_reg)
# cfp->jit_return is used only for ISEQs
if iseq
# The callee might change locals through Kernel#binding and other means.
ctx.clear_local_types
# Stub cfp->jit_return
return_ctx = ctx.dup
return_ctx.stack_pop(argc + ((block_handler == :captured) ? 0 : 1)) # Pop args and receiver. blockarg has been popped
return_ctx.stack_push(Type::Unknown) # push callee's return value
return_ctx.sp_offset = 1 # SP is in the position after popping a receiver and arguments
return_ctx.chain_depth = 0
branch_stub = BranchStub.new(
iseq: jit.iseq,
shape: Default,
target0: BranchTarget.new(ctx: return_ctx, pc: jit.pc + jit.insn.len * C.VALUE.size),
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(return_ctx, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.compile = compile_jit_return(branch_stub, cfp_offset:)
branch_stub.compile.call(asm)
end
asm.comment('switch to callee CFP')
# Update CFP register only for ISEQ calls
cfp_reg = iseq ? CFP : :rax
asm.lea(cfp_reg, [CFP, cfp_offset])
asm.mov([EC, C.rb_execution_context_t.offsetof(:cfp)], cfp_reg)
end
def compile_jit_return(branch_stub, cfp_offset:) # Proc escapes arguments in memory
proc do |branch_asm|
branch_asm.comment('set jit_return to callee CFP')
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.mov(:rax, branch_stub.target0.address)
branch_asm.mov([CFP, cfp_offset + C.rb_control_frame_t.offsetof(:jit_return)], :rax)
end
end
end
end
# CALLER_SETUP_ARG: Return CantCompile if not supported
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def jit_caller_setup_arg(jit, ctx, asm, flags)
if flags & C::VM_CALL_ARGS_SPLAT != 0 && flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_args_splat_kw_splat)
return CantCompile
elsif flags & C::VM_CALL_ARGS_SPLAT != 0
# splat is not supported in this path
asm.incr_counter(:send_args_splat)
return CantCompile
elsif flags & C::VM_CALL_KW_SPLAT != 0
asm.incr_counter(:send_args_kw_splat)
return CantCompile
elsif flags & C::VM_CALL_KWARG != 0
asm.incr_counter(:send_kwarg)
return CantCompile
end
end
# Pushes arguments from an array to the stack. Differs from push splat because
# the array can have items left over.
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def move_rest_args_to_stack(array, num_args, jit, ctx, asm)
side_exit = side_exit(jit, ctx)
asm.comment('move_rest_args_to_stack')
# array is :rax
array_len_opnd = :rcx
jit_array_len(asm, array, array_len_opnd)
asm.comment('Side exit if length is less than required')
asm.cmp(array_len_opnd, num_args)
asm.jl(counted_exit(side_exit, :send_iseq_has_rest_and_splat_not_equal))
asm.comment('Push arguments from array')
# Load the address of the embedded array
# (struct RArray *)(obj)->as.ary
array_reg = array
# Conditionally load the address of the heap array
# (struct RArray *)(obj)->as.heap.ptr
flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]
# Load the address of the embedded array
# (struct RArray *)(obj)->as.ary
ary_opnd = :rdx # NOTE: array :rax is used after move_rest_args_to_stack too
asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)])
asm.mov(ary_opnd, heap_ptr_opnd)
asm.cmovnz(ary_opnd, :rcx)
num_args.times do |i|
top = ctx.stack_push(Type::Unknown)
asm.mov(:rcx, [ary_opnd, i * C.VALUE.size])
asm.mov(top, :rcx)
end
end
# vm_caller_setup_arg_splat (+ CALLER_SETUP_ARG):
# Pushes arguments from an array to the stack that are passed with a splat (i.e. *args).
# It optimistically compiles to a static size that is the exact number of arguments needed for the function.
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def push_splat_args(required_args, jit, ctx, asm)
side_exit = side_exit(jit, ctx)
asm.comment('push_splat_args')
array_opnd = ctx.stack_opnd(0)
array_stack_opnd = StackOpnd[0]
array_reg = :rax
asm.mov(array_reg, array_opnd)
guard_object_is_array(jit, ctx, asm, array_reg, :rcx, array_stack_opnd, :send_args_splat_not_array)
array_len_opnd = :rcx
jit_array_len(asm, array_reg, array_len_opnd)
asm.comment('Side exit if length is not equal to remaining args')
asm.cmp(array_len_opnd, required_args)
asm.jne(counted_exit(side_exit, :send_args_splat_length_not_equal))
asm.comment('Check last argument is not ruby2keyword hash')
ary_opnd = :rcx
jit_array_ptr(asm, array_reg, ary_opnd) # clobbers array_reg
last_array_value = :rax
asm.mov(last_array_value, [ary_opnd, (required_args - 1) * C.VALUE.size])
ruby2_exit = counted_exit(side_exit, :send_args_splat_ruby2_hash);
guard_object_is_not_ruby2_keyword_hash(asm, last_array_value, :rcx, ruby2_exit) # clobbers :rax
asm.comment('Push arguments from array')
array_opnd = ctx.stack_pop(1)
if required_args > 0
# Load the address of the embedded array
# (struct RArray *)(obj)->as.ary
array_reg = :rax
asm.mov(array_reg, array_opnd)
# Conditionally load the address of the heap array
# (struct RArray *)(obj)->as.heap.ptr
flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
heap_ptr_opnd = [array_reg, C.RArray.offsetof(:as, :heap, :ptr)]
# Load the address of the embedded array
# (struct RArray *)(obj)->as.ary
asm.lea(:rcx, [array_reg, C.RArray.offsetof(:as, :ary)])
asm.mov(:rax, heap_ptr_opnd)
asm.cmovnz(:rax, :rcx)
ary_opnd = :rax
(0...required_args).each do |i|
top = ctx.stack_push(Type::Unknown)
asm.mov(:rcx, [ary_opnd, i * C.VALUE.size])
asm.mov(top, :rcx)
end
asm.comment('end push_each')
end
end
# Generate RARRAY_LEN. For array_opnd, use Opnd::Reg to reduce memory access,
# and use Opnd::Mem to save registers.
def jit_array_len(asm, array_reg, len_reg)
asm.comment('get array length for embedded or heap')
# Pull out the embed flag to check if it's an embedded array.
asm.mov(len_reg, [array_reg, C.RBasic.offsetof(:flags)])
# Get the length of the array
asm.and(len_reg, C::RARRAY_EMBED_LEN_MASK)
asm.sar(len_reg, C::RARRAY_EMBED_LEN_SHIFT)
# Conditionally move the length of the heap array
asm.test([array_reg, C.RBasic.offsetof(:flags)], C::RARRAY_EMBED_FLAG)
# Select the array length value
asm.cmovz(len_reg, [array_reg, C.RArray.offsetof(:as, :heap, :len)])
end
# Generate RARRAY_CONST_PTR (part of RARRAY_AREF)
def jit_array_ptr(asm, array_reg, ary_opnd) # clobbers array_reg
asm.comment('get array pointer for embedded or heap')
flags_opnd = [array_reg, C.RBasic.offsetof(:flags)]
asm.test(flags_opnd, C::RARRAY_EMBED_FLAG)
# Load the address of the embedded array
# (struct RArray *)(obj)->as.ary
asm.mov(ary_opnd, [array_reg, C.RArray.offsetof(:as, :heap, :ptr)])
asm.lea(array_reg, [array_reg, C.RArray.offsetof(:as, :ary)]) # clobbers array_reg
asm.cmovnz(ary_opnd, array_reg)
end
def assert(cond)
assert_equal(cond, true)
end
def assert_equal(left, right)
if left != right
raise "'#{left.inspect}' was not '#{right.inspect}'"
end
end
def fixnum?(obj)
(C.to_value(obj) & C::RUBY_FIXNUM_FLAG) == C::RUBY_FIXNUM_FLAG
end
def flonum?(obj)
(C.to_value(obj) & C::RUBY_FLONUM_MASK) == C::RUBY_FLONUM_FLAG
end
def symbol?(obj)
static_symbol?(obj) || dynamic_symbol?(obj)
end
def static_symbol?(obj)
(C.to_value(obj) & 0xff) == C::RUBY_SYMBOL_FLAG
end
def dynamic_symbol?(obj)
return false if C::SPECIAL_CONST_P(obj)
C.RB_TYPE_P(obj, C::RUBY_T_SYMBOL)
end
def shape_too_complex?(obj)
C.rb_shape_get_shape_id(obj) == C::OBJ_TOO_COMPLEX_SHAPE_ID
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def defer_compilation(jit, ctx, asm)
# Make a stub to compile the current insn
if ctx.chain_depth != 0
raise "double defer!"
end
ctx.chain_depth += 1
jit_direct_jump(jit.iseq, jit.pc, ctx, asm, comment: 'defer_compilation')
end
def jit_direct_jump(iseq, pc, ctx, asm, comment: 'jit_direct_jump')
branch_stub = BranchStub.new(
iseq:,
shape: Default,
target0: BranchTarget.new(ctx:, pc:),
)
branch_stub.target0.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(ctx, ocb_asm, branch_stub, true)
@ocb.write(ocb_asm)
end
branch_stub.compile = compile_jit_direct_jump(branch_stub, comment:)
branch_stub.compile.call(asm)
end
def compile_jit_direct_jump(branch_stub, comment:) # Proc escapes arguments in memory
proc do |branch_asm|
branch_asm.comment(comment)
branch_asm.stub(branch_stub) do
case branch_stub.shape
in Default
branch_asm.jmp(branch_stub.target0.address)
in Next0
# Just write the block without a jump
end
end
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
def side_exit(jit, ctx)
# We use the latest ctx.sp_offset to generate a side exit to tolerate sp_offset changes by jit_save_sp.
# However, we want to simulate an old stack_size when we take a side exit. We do that by adjusting the
# sp_offset because gen_outlined_exit uses ctx.sp_offset to move SP.
ctx = ctx.with_stack_size(jit.stack_size_for_pc)
jit.side_exit_for_pc[ctx.sp_offset] ||= Assembler.new.then do |asm|
@exit_compiler.compile_side_exit(jit.pc, ctx, asm)
@ocb.write(asm)
end
end
def counted_exit(side_exit, name)
asm = Assembler.new
asm.incr_counter(name)
asm.jmp(side_exit)
@ocb.write(asm)
end
def def_iseq_ptr(cme_def)
C.rb_iseq_check(cme_def.body.iseq.iseqptr)
end
def to_value(obj)
GC_REFS << obj
C.to_value(obj)
end
def full_cfunc_return
@full_cfunc_return ||= Assembler.new.then do |asm|
@exit_compiler.compile_full_cfunc_return(asm)
@ocb.write(asm)
end
end
def c_method_tracing_currently_enabled?
C.rb_rjit_global_events & (C::RUBY_EVENT_C_CALL | C::RUBY_EVENT_C_RETURN) != 0
end
# Return a builtin function if a given iseq consists of only that builtin function
def builtin_function(iseq)
opt_invokebuiltin_delegate_leave = INSNS.values.find { |i| i.name == :opt_invokebuiltin_delegate_leave }
leave = INSNS.values.find { |i| i.name == :leave }
if iseq.body.iseq_size == opt_invokebuiltin_delegate_leave.len + leave.len &&
C.rb_vm_insn_decode(iseq.body.iseq_encoded[0]) == opt_invokebuiltin_delegate_leave.bin &&
C.rb_vm_insn_decode(iseq.body.iseq_encoded[opt_invokebuiltin_delegate_leave.len]) == leave.bin
C.rb_builtin_function.new(iseq.body.iseq_encoded[1])
end
end
def build_calling(ci:, block_handler:)
CallingInfo.new(
argc: C.vm_ci_argc(ci),
flags: C.vm_ci_flag(ci),
kwarg: C.vm_ci_kwarg(ci),
ci_addr: ci.to_i,
send_shift: 0,
block_handler:,
)
end
end
end
share/ruby/ruby_vm/rjit/hooks.rb 0000644 00000001665 15173517737 0012762 0 ustar 00 module RubyVM::RJIT
module Hooks # :nodoc: all
def self.on_bop_redefined(_redefined_flag, _bop)
# C.rjit_cancel_all("BOP is redefined")
end
def self.on_cme_invalidate(cme)
cme = C.rb_callable_method_entry_struct.new(cme)
Invariants.on_cme_invalidate(cme)
end
def self.on_ractor_spawn
# C.rjit_cancel_all("Ractor is spawned")
end
# Global constant changes like const_set
def self.on_constant_state_changed(id)
Invariants.on_constant_state_changed(id)
end
# ISEQ-specific constant invalidation
def self.on_constant_ic_update(iseq, ic, insn_idx)
iseq = C.rb_iseq_t.new(iseq)
ic = C.IC.new(ic)
Invariants.on_constant_ic_update(iseq, ic, insn_idx)
end
def self.on_tracing_invalidate_all(_new_iseq_events)
Invariants.on_tracing_invalidate_all
end
def self.on_update_references
Invariants.on_update_references
end
end
end
share/ruby/ruby_vm/rjit/entry_stub.rb 0000644 00000000342 15173517737 0014024 0 ustar 00 module RubyVM::RJIT
class EntryStub < Struct.new(
:start_addr, # @param [Integer] Stub source start address to be re-generated
:end_addr, # @param [Integer] Stub source end address to be re-generated
)
end
end
share/ruby/ruby_vm/rjit/code_block.rb 0000644 00000004433 15173517737 0013717 0 ustar 00 module RubyVM::RJIT
class CodeBlock
# @param mem_block [Integer] JIT buffer address
# @param mem_size [Integer] JIT buffer size
# @param outliend [TrueClass,FalseClass] true for outlined CodeBlock
def initialize(mem_block:, mem_size:, outlined: false)
@comments = Hash.new { |h, k| h[k] = [] } if dump_disasm?
@mem_block = mem_block
@mem_size = mem_size
@write_pos = 0
@outlined = outlined
end
# @param asm [RubyVM::RJIT::Assembler]
def write(asm)
return 0 if @write_pos + asm.size >= @mem_size
start_addr = write_addr
# Write machine code
C.mprotect_write(@mem_block, @mem_size)
@write_pos += asm.assemble(start_addr)
C.mprotect_exec(@mem_block, @mem_size)
end_addr = write_addr
# Convert comment indexes to addresses
asm.comments.each do |index, comments|
@comments[start_addr + index] += comments if dump_disasm?
end
asm.comments.clear
# Dump disasm if --rjit-dump-disasm
if C.rjit_opts.dump_disasm && start_addr < end_addr
dump_disasm(start_addr, end_addr)
end
start_addr
end
def set_write_addr(addr)
@write_pos = addr - @mem_block
@comments.delete(addr) if dump_disasm?
end
def with_write_addr(addr)
old_write_pos = @write_pos
set_write_addr(addr)
yield
ensure
@write_pos = old_write_pos
end
def write_addr
@mem_block + @write_pos
end
def include?(addr)
(@mem_block...(@mem_block + @mem_size)).include?(addr)
end
def dump_disasm(from, to, io: STDOUT, color: true, test: false)
C.dump_disasm(from, to, test:).each do |address, mnemonic, op_str|
@comments.fetch(address, []).each do |comment|
io.puts colorize(" # #{comment}", bold: true, color:)
end
io.puts colorize(" 0x#{format("%x", address)}: #{mnemonic} #{op_str}", color:)
end
io.puts
end
private
def colorize(text, bold: false, color:)
return text unless color
buf = +''
buf << "\e[1m" if bold
buf << "\e[34m" if @outlined
buf << text
buf << "\e[0m"
buf
end
def bold(text)
"\e[1m#{text}\e[0m"
end
def dump_disasm?
C.rjit_opts.dump_disasm
end
end
end
share/ruby/ruby_vm/rjit/block.rb 0000644 00000001070 15173517737 0012717 0 ustar 00 class RubyVM::RJIT::Block < Struct.new(
:iseq, # @param ``
:pc, # @param [Integer] Starting PC
:ctx, # @param [RubyVM::RJIT::Context] **Starting** Context (TODO: freeze?)
:start_addr, # @param [Integer] Starting address of this block's JIT code
:entry_exit, # @param [Integer] Address of entry exit (optional)
:incoming, # @param [Array<RubyVM::RJIT::BranchStub>] Incoming branches
:invalidated, # @param [TrueClass,FalseClass] true if already invalidated
)
def initialize(incoming: [], invalidated: false, **) = super
end
share/ruby/ruby_vm/rjit/compiler.rb 0000644 00000041335 15173517737 0013447 0 ustar 00 require 'ruby_vm/rjit/assembler'
require 'ruby_vm/rjit/block'
require 'ruby_vm/rjit/branch_stub'
require 'ruby_vm/rjit/code_block'
require 'ruby_vm/rjit/context'
require 'ruby_vm/rjit/entry_stub'
require 'ruby_vm/rjit/exit_compiler'
require 'ruby_vm/rjit/insn_compiler'
require 'ruby_vm/rjit/instruction'
require 'ruby_vm/rjit/invariants'
require 'ruby_vm/rjit/jit_state'
require 'ruby_vm/rjit/type'
module RubyVM::RJIT
# Compilation status
KeepCompiling = :KeepCompiling
CantCompile = :CantCompile
EndBlock = :EndBlock
# Ruby constants
Qtrue = Fiddle::Qtrue
Qfalse = Fiddle::Qfalse
Qnil = Fiddle::Qnil
Qundef = Fiddle::Qundef
# Callee-saved registers
# TODO: support using r12/r13 here
EC = :r14
CFP = :r15
SP = :rbx
# Scratch registers: rax, rcx, rdx
# Mark objects in this Array during GC
GC_REFS = []
# Maximum number of versions per block
# 1 means always create generic versions
MAX_VERSIONS = 4
class Compiler
attr_accessor :write_pos
def self.decode_insn(encoded)
INSNS.fetch(C.rb_vm_insn_decode(encoded))
end
def initialize
mem_size = C.rjit_opts.exec_mem_size * 1024 * 1024
mem_block = C.mmap(mem_size)
@cb = CodeBlock.new(mem_block: mem_block, mem_size: mem_size / 2)
@ocb = CodeBlock.new(mem_block: mem_block + mem_size / 2, mem_size: mem_size / 2, outlined: true)
@exit_compiler = ExitCompiler.new
@insn_compiler = InsnCompiler.new(@cb, @ocb, @exit_compiler)
Invariants.initialize(@cb, @ocb, self, @exit_compiler)
end
# Compile an ISEQ from its entry point.
# @param iseq `RubyVM::RJIT::CPointer::Struct_rb_iseq_t`
# @param cfp `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t`
def compile(iseq, cfp)
return unless supported_platform?
pc = cfp.pc.to_i
jit = JITState.new(iseq:, cfp:)
asm = Assembler.new
compile_prologue(asm, iseq, pc)
compile_block(asm, jit:, pc:)
iseq.body.jit_entry = @cb.write(asm)
rescue Exception => e
STDERR.puts "#{e.class}: #{e.message}"
STDERR.puts e.backtrace
exit 1
end
# Compile an entry.
# @param entry [RubyVM::RJIT::EntryStub]
def entry_stub_hit(entry_stub, cfp)
# Compile a new entry guard as a next entry
pc = cfp.pc.to_i
next_entry = Assembler.new.then do |asm|
compile_entry_chain_guard(asm, cfp.iseq, pc)
@cb.write(asm)
end
# Try to find an existing compiled version of this block
ctx = Context.new
block = find_block(cfp.iseq, pc, ctx)
if block
# If an existing block is found, generate a jump to the block.
asm = Assembler.new
asm.jmp(block.start_addr)
@cb.write(asm)
else
# If this block hasn't yet been compiled, generate blocks after the entry guard.
asm = Assembler.new
jit = JITState.new(iseq: cfp.iseq, cfp:)
compile_block(asm, jit:, pc:, ctx:)
@cb.write(asm)
block = jit.block
end
# Regenerate the previous entry
@cb.with_write_addr(entry_stub.start_addr) do
# The last instruction of compile_entry_chain_guard is jne
asm = Assembler.new
asm.jne(next_entry)
@cb.write(asm)
end
return block.start_addr
rescue Exception => e
STDERR.puts e.full_message
exit 1
end
# Compile a branch stub.
# @param branch_stub [RubyVM::RJIT::BranchStub]
# @param cfp `RubyVM::RJIT::CPointer::Struct_rb_control_frame_t`
# @param target0_p [TrueClass,FalseClass]
# @return [Integer] The starting address of the compiled branch stub
def branch_stub_hit(branch_stub, cfp, target0_p)
# Update cfp->pc for `jit.at_current_insn?`
target = target0_p ? branch_stub.target0 : branch_stub.target1
cfp.pc = target.pc
# Reuse an existing block if it already exists
block = find_block(branch_stub.iseq, target.pc, target.ctx)
# If the branch stub's jump is the last code, allow overwriting part of
# the old branch code with the new block code.
fallthrough = block.nil? && @cb.write_addr == branch_stub.end_addr
if fallthrough
# If the branch stub's jump is the last code, allow overwriting part of
# the old branch code with the new block code.
@cb.set_write_addr(branch_stub.start_addr)
branch_stub.shape = target0_p ? Next0 : Next1
Assembler.new.tap do |branch_asm|
branch_stub.compile.call(branch_asm)
@cb.write(branch_asm)
end
end
# Reuse or generate a block
if block
target.address = block.start_addr
else
jit = JITState.new(iseq: branch_stub.iseq, cfp:)
target.address = Assembler.new.then do |asm|
compile_block(asm, jit:, pc: target.pc, ctx: target.ctx.dup)
@cb.write(asm)
end
block = jit.block
end
block.incoming << branch_stub # prepare for invalidate_block
# Re-generate the branch code for non-fallthrough cases
unless fallthrough
@cb.with_write_addr(branch_stub.start_addr) do
branch_asm = Assembler.new
branch_stub.compile.call(branch_asm)
@cb.write(branch_asm)
end
end
return target.address
rescue Exception => e
STDERR.puts e.full_message
exit 1
end
# @param iseq `RubyVM::RJIT::CPointer::Struct_rb_iseq_t`
# @param pc [Integer]
def invalidate_blocks(iseq, pc)
list_blocks(iseq, pc).each do |block|
invalidate_block(block)
end
# If they were the ISEQ's first blocks, re-compile RJIT entry as well
if iseq.body.iseq_encoded.to_i == pc
iseq.body.jit_entry = 0
iseq.body.jit_entry_calls = 0
end
end
def invalidate_block(block)
iseq = block.iseq
# Avoid touching GCed ISEQs. We assume it won't be re-entered.
return unless C.imemo_type_p(iseq, C.imemo_iseq)
# Remove this block from the version array
remove_block(iseq, block)
# Invalidate the block with entry exit
unless block.invalidated
@cb.with_write_addr(block.start_addr) do
asm = Assembler.new
asm.comment('invalidate_block')
asm.jmp(block.entry_exit)
@cb.write(asm)
end
block.invalidated = true
end
# Re-stub incoming branches
block.incoming.each do |branch_stub|
target = [branch_stub.target0, branch_stub.target1].compact.find do |target|
target.pc == block.pc && target.ctx == block.ctx
end
next if target.nil?
# TODO: Could target.address be a stub address? Is invalidation not needed in that case?
# If the target being re-generated is currently a fallthrough block,
# the fallthrough code must be rewritten with a jump to the stub.
if target.address == branch_stub.end_addr
branch_stub.shape = Default
end
target.address = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_branch_stub(block.ctx, ocb_asm, branch_stub, target == branch_stub.target0)
@ocb.write(ocb_asm)
end
@cb.with_write_addr(branch_stub.start_addr) do
branch_asm = Assembler.new
branch_stub.compile.call(branch_asm)
@cb.write(branch_asm)
end
end
end
private
# Callee-saved: rbx, rsp, rbp, r12, r13, r14, r15
# Caller-saved: rax, rdi, rsi, rdx, rcx, r8, r9, r10, r11
#
# @param asm [RubyVM::RJIT::Assembler]
def compile_prologue(asm, iseq, pc)
asm.comment('RJIT entry point')
# Save callee-saved registers used by JITed code
asm.push(CFP)
asm.push(EC)
asm.push(SP)
# Move arguments EC and CFP to dedicated registers
asm.mov(EC, :rdi)
asm.mov(CFP, :rsi)
# Load sp to a dedicated register
asm.mov(SP, [CFP, C.rb_control_frame_t.offsetof(:sp)]) # rbx = cfp->sp
# Setup cfp->jit_return
asm.mov(:rax, leave_exit)
asm.mov([CFP, C.rb_control_frame_t.offsetof(:jit_return)], :rax)
# We're compiling iseqs that we *expect* to start at `insn_idx`. But in
# the case of optional parameters, the interpreter can set the pc to a
# different location depending on the optional parameters. If an iseq
# has optional parameters, we'll add a runtime check that the PC we've
# compiled for is the same PC that the interpreter wants us to run with.
# If they don't match, then we'll take a side exit.
if iseq.body.param.flags.has_opt
compile_entry_chain_guard(asm, iseq, pc)
end
end
def compile_entry_chain_guard(asm, iseq, pc)
entry_stub = EntryStub.new
stub_addr = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_entry_stub(ocb_asm, entry_stub)
@ocb.write(ocb_asm)
end
asm.comment('guard expected PC')
asm.mov(:rax, pc)
asm.cmp([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax)
asm.stub(entry_stub) do
asm.jne(stub_addr)
end
end
# @param asm [RubyVM::RJIT::Assembler]
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
def compile_block(asm, jit:, pc:, ctx: Context.new)
# Mark the block start address and prepare an exit code storage
ctx = limit_block_versions(jit.iseq, pc, ctx)
block = Block.new(iseq: jit.iseq, pc:, ctx: ctx.dup)
jit.block = block
asm.block(block)
iseq = jit.iseq
asm.comment("Block: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, pc)}")
# Compile each insn
index = (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size
while index < iseq.body.iseq_size
# Set the current instruction
insn = self.class.decode_insn(iseq.body.iseq_encoded[index])
jit.pc = (iseq.body.iseq_encoded + index).to_i
jit.stack_size_for_pc = ctx.stack_size
jit.side_exit_for_pc.clear
# If previous instruction requested to record the boundary
if jit.record_boundary_patch_point
# Generate an exit to this instruction and record it
exit_pos = Assembler.new.then do |ocb_asm|
@exit_compiler.compile_side_exit(jit.pc, ctx, ocb_asm)
@ocb.write(ocb_asm)
end
Invariants.record_global_inval_patch(asm, exit_pos)
jit.record_boundary_patch_point = false
end
# In debug mode, verify our existing assumption
if C.rjit_opts.verify_ctx && jit.at_current_insn?
verify_ctx(jit, ctx)
end
case status = @insn_compiler.compile(jit, ctx, asm, insn)
when KeepCompiling
# For now, reset the chain depth after each instruction as only the
# first instruction in the block can concern itself with the depth.
ctx.chain_depth = 0
index += insn.len
when EndBlock
# TODO: pad nops if entry exit exists (not needed for x86_64?)
break
when CantCompile
# Rewind stack_size using ctx.with_stack_size to allow stack_size changes
# before you return CantCompile.
@exit_compiler.compile_side_exit(jit.pc, ctx.with_stack_size(jit.stack_size_for_pc), asm)
# If this is the first instruction, this block never needs to be invalidated.
if block.pc == iseq.body.iseq_encoded.to_i + index * C.VALUE.size
block.invalidated = true
end
break
else
raise "compiling #{insn.name} returned unexpected status: #{status.inspect}"
end
end
incr_counter(:compiled_block_count)
add_block(iseq, block)
end
def leave_exit
@leave_exit ||= Assembler.new.then do |asm|
@exit_compiler.compile_leave_exit(asm)
@ocb.write(asm)
end
end
def incr_counter(name)
if C.rjit_opts.stats
C.rb_rjit_counters[name][0] += 1
end
end
# Produce a generic context when the block version limit is hit for the block
def limit_block_versions(iseq, pc, ctx)
# Guard chains implement limits separately, do nothing
if ctx.chain_depth > 0
return ctx.dup
end
# If this block version we're about to add will hit the version limit
if list_blocks(iseq, pc).size + 1 >= MAX_VERSIONS
# Produce a generic context that stores no type information,
# but still respects the stack_size and sp_offset constraints.
# This new context will then match all future requests.
generic_ctx = Context.new
generic_ctx.stack_size = ctx.stack_size
generic_ctx.sp_offset = ctx.sp_offset
if ctx.diff(generic_ctx) == TypeDiff::Incompatible
raise 'should substitute a compatible context'
end
return generic_ctx
end
return ctx.dup
end
def list_blocks(iseq, pc)
rjit_blocks(iseq)[pc]
end
# @param [Integer] pc
# @param [RubyVM::RJIT::Context] ctx
# @return [RubyVM::RJIT::Block,NilClass]
def find_block(iseq, pc, ctx)
versions = rjit_blocks(iseq)[pc]
best_version = nil
best_diff = Float::INFINITY
versions.each do |block|
# Note that we always prefer the first matching
# version found because of inline-cache chains
case ctx.diff(block.ctx)
in TypeDiff::Compatible[diff] if diff < best_diff
best_version = block
best_diff = diff
else
end
end
return best_version
end
# @param [RubyVM::RJIT::Block] block
def add_block(iseq, block)
rjit_blocks(iseq)[block.pc] << block
end
# @param [RubyVM::RJIT::Block] block
def remove_block(iseq, block)
rjit_blocks(iseq)[block.pc].delete(block)
end
def rjit_blocks(iseq)
# Guard against ISEQ GC at random moments
unless C.imemo_type_p(iseq, C.imemo_iseq)
return Hash.new { |h, k| h[k] = [] }
end
unless iseq.body.rjit_blocks
iseq.body.rjit_blocks = Hash.new { |blocks, pc| blocks[pc] = [] }
# For some reason, rb_rjit_iseq_mark didn't protect this Hash
# from being freed. So we rely on GC_REFS to keep the Hash.
GC_REFS << iseq.body.rjit_blocks
end
iseq.body.rjit_blocks
end
def iseq_lineno(iseq, pc)
C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size)
rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError)
-1
end
# Verify the ctx's types and mappings against the compile-time stack, self, and locals.
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
def verify_ctx(jit, ctx)
# Only able to check types when at current insn
assert(jit.at_current_insn?)
self_val = jit.peek_at_self
self_val_type = Type.from(self_val)
# Verify self operand type
assert_compatible(self_val_type, ctx.get_opnd_type(SelfOpnd))
# Verify stack operand types
[ctx.stack_size, MAX_TEMP_TYPES].min.times do |i|
learned_mapping, learned_type = ctx.get_opnd_mapping(StackOpnd[i])
stack_val = jit.peek_at_stack(i)
val_type = Type.from(stack_val)
case learned_mapping
in MapToSelf
if C.to_value(self_val) != C.to_value(stack_val)
raise "verify_ctx: stack value was mapped to self, but values did not match:\n"\
"stack: #{stack_val.inspect}, self: #{self_val.inspect}"
end
in MapToLocal[local_idx]
local_val = jit.peek_at_local(local_idx)
if C.to_value(local_val) != C.to_value(stack_val)
raise "verify_ctx: stack value was mapped to local, but values did not match:\n"\
"stack: #{stack_val.inspect}, local: #{local_val.inspect}"
end
in MapToStack
# noop
end
# If the actual type differs from the learned type
assert_compatible(val_type, learned_type)
end
# Verify local variable types
local_table_size = jit.iseq.body.local_table_size
[local_table_size, MAX_TEMP_TYPES].min.times do |i|
learned_type = ctx.get_local_type(i)
local_val = jit.peek_at_local(i)
local_type = Type.from(local_val)
assert_compatible(local_type, learned_type)
end
end
def assert_compatible(actual_type, ctx_type)
if actual_type.diff(ctx_type) == TypeDiff::Incompatible
raise "verify_ctx: ctx type (#{ctx_type.type.inspect}) is incompatible with actual type (#{actual_type.type.inspect})"
end
end
def assert(cond)
unless cond
raise "'#{cond.inspect}' was not true"
end
end
def supported_platform?
return @supported_platform if defined?(@supported_platform)
@supported_platform = RUBY_PLATFORM.match?(/x86_64/).tap do |supported|
warn "warning: RJIT does not support #{RUBY_PLATFORM} yet" unless supported
end
end
end
end
share/ruby/ruby_vm/rjit/instruction.rb 0000644 00000107121 15173517737 0014212 0 ustar 00 module RubyVM::RJIT # :nodoc: all
Instruction = Data.define(:name, :bin, :len, :operands)
INSNS = {
0 => Instruction.new(
name: :nop,
bin: 0, # BIN(nop)
len: 1, # insn_len
operands: [],
),
1 => Instruction.new(
name: :getlocal,
bin: 1, # BIN(getlocal)
len: 3, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
),
2 => Instruction.new(
name: :setlocal,
bin: 2, # BIN(setlocal)
len: 3, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
),
3 => Instruction.new(
name: :getblockparam,
bin: 3, # BIN(getblockparam)
len: 3, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
),
4 => Instruction.new(
name: :setblockparam,
bin: 4, # BIN(setblockparam)
len: 3, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
),
5 => Instruction.new(
name: :getblockparamproxy,
bin: 5, # BIN(getblockparamproxy)
len: 3, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}, {:decl=>"rb_num_t level", :type=>"rb_num_t", :name=>"level"}],
),
6 => Instruction.new(
name: :getspecial,
bin: 6, # BIN(getspecial)
len: 3, # insn_len
operands: [{:decl=>"rb_num_t key", :type=>"rb_num_t", :name=>"key"}, {:decl=>"rb_num_t type", :type=>"rb_num_t", :name=>"type"}],
),
7 => Instruction.new(
name: :setspecial,
bin: 7, # BIN(setspecial)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t key", :type=>"rb_num_t", :name=>"key"}],
),
8 => Instruction.new(
name: :getinstancevariable,
bin: 8, # BIN(getinstancevariable)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"IVC ic", :type=>"IVC", :name=>"ic"}],
),
9 => Instruction.new(
name: :setinstancevariable,
bin: 9, # BIN(setinstancevariable)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"IVC ic", :type=>"IVC", :name=>"ic"}],
),
10 => Instruction.new(
name: :getclassvariable,
bin: 10, # BIN(getclassvariable)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ICVARC ic", :type=>"ICVARC", :name=>"ic"}],
),
11 => Instruction.new(
name: :setclassvariable,
bin: 11, # BIN(setclassvariable)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ICVARC ic", :type=>"ICVARC", :name=>"ic"}],
),
12 => Instruction.new(
name: :opt_getconstant_path,
bin: 12, # BIN(opt_getconstant_path)
len: 2, # insn_len
operands: [{:decl=>"IC ic", :type=>"IC", :name=>"ic"}],
),
13 => Instruction.new(
name: :getconstant,
bin: 13, # BIN(getconstant)
len: 2, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}],
),
14 => Instruction.new(
name: :setconstant,
bin: 14, # BIN(setconstant)
len: 2, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}],
),
15 => Instruction.new(
name: :getglobal,
bin: 15, # BIN(getglobal)
len: 2, # insn_len
operands: [{:decl=>"ID gid", :type=>"ID", :name=>"gid"}],
),
16 => Instruction.new(
name: :setglobal,
bin: 16, # BIN(setglobal)
len: 2, # insn_len
operands: [{:decl=>"ID gid", :type=>"ID", :name=>"gid"}],
),
17 => Instruction.new(
name: :putnil,
bin: 17, # BIN(putnil)
len: 1, # insn_len
operands: [],
),
18 => Instruction.new(
name: :putself,
bin: 18, # BIN(putself)
len: 1, # insn_len
operands: [],
),
19 => Instruction.new(
name: :putobject,
bin: 19, # BIN(putobject)
len: 2, # insn_len
operands: [{:decl=>"VALUE val", :type=>"VALUE", :name=>"val"}],
),
20 => Instruction.new(
name: :putspecialobject,
bin: 20, # BIN(putspecialobject)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t value_type", :type=>"rb_num_t", :name=>"value_type"}],
),
21 => Instruction.new(
name: :putstring,
bin: 21, # BIN(putstring)
len: 2, # insn_len
operands: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}],
),
22 => Instruction.new(
name: :concatstrings,
bin: 22, # BIN(concatstrings)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
),
23 => Instruction.new(
name: :anytostring,
bin: 23, # BIN(anytostring)
len: 1, # insn_len
operands: [],
),
24 => Instruction.new(
name: :toregexp,
bin: 24, # BIN(toregexp)
len: 3, # insn_len
operands: [{:decl=>"rb_num_t opt", :type=>"rb_num_t", :name=>"opt"}, {:decl=>"rb_num_t cnt", :type=>"rb_num_t", :name=>"cnt"}],
),
25 => Instruction.new(
name: :intern,
bin: 25, # BIN(intern)
len: 1, # insn_len
operands: [],
),
26 => Instruction.new(
name: :newarray,
bin: 26, # BIN(newarray)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
),
27 => Instruction.new(
name: :newarraykwsplat,
bin: 27, # BIN(newarraykwsplat)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
),
28 => Instruction.new(
name: :duparray,
bin: 28, # BIN(duparray)
len: 2, # insn_len
operands: [{:decl=>"VALUE ary", :type=>"VALUE", :name=>"ary"}],
),
29 => Instruction.new(
name: :duphash,
bin: 29, # BIN(duphash)
len: 2, # insn_len
operands: [{:decl=>"VALUE hash", :type=>"VALUE", :name=>"hash"}],
),
30 => Instruction.new(
name: :expandarray,
bin: 30, # BIN(expandarray)
len: 3, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}, {:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
),
31 => Instruction.new(
name: :concatarray,
bin: 31, # BIN(concatarray)
len: 1, # insn_len
operands: [],
),
32 => Instruction.new(
name: :splatarray,
bin: 32, # BIN(splatarray)
len: 2, # insn_len
operands: [{:decl=>"VALUE flag", :type=>"VALUE", :name=>"flag"}],
),
33 => Instruction.new(
name: :splatkw,
bin: 33, # BIN(splatkw)
len: 1, # insn_len
operands: [],
),
34 => Instruction.new(
name: :newhash,
bin: 34, # BIN(newhash)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}],
),
35 => Instruction.new(
name: :newrange,
bin: 35, # BIN(newrange)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
),
36 => Instruction.new(
name: :pop,
bin: 36, # BIN(pop)
len: 1, # insn_len
operands: [],
),
37 => Instruction.new(
name: :dup,
bin: 37, # BIN(dup)
len: 1, # insn_len
operands: [],
),
38 => Instruction.new(
name: :dupn,
bin: 38, # BIN(dupn)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
),
39 => Instruction.new(
name: :swap,
bin: 39, # BIN(swap)
len: 1, # insn_len
operands: [],
),
40 => Instruction.new(
name: :opt_reverse,
bin: 40, # BIN(opt_reverse)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
),
41 => Instruction.new(
name: :topn,
bin: 41, # BIN(topn)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
),
42 => Instruction.new(
name: :setn,
bin: 42, # BIN(setn)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
),
43 => Instruction.new(
name: :adjuststack,
bin: 43, # BIN(adjuststack)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t n", :type=>"rb_num_t", :name=>"n"}],
),
44 => Instruction.new(
name: :defined,
bin: 44, # BIN(defined)
len: 4, # insn_len
operands: [{: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"}],
),
45 => Instruction.new(
name: :definedivar,
bin: 45, # BIN(definedivar)
len: 4, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"IVC ic", :type=>"IVC", :name=>"ic"}, {:decl=>"VALUE pushval", :type=>"VALUE", :name=>"pushval"}],
),
46 => Instruction.new(
name: :checkmatch,
bin: 46, # BIN(checkmatch)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t flag", :type=>"rb_num_t", :name=>"flag"}],
),
47 => Instruction.new(
name: :checkkeyword,
bin: 47, # BIN(checkkeyword)
len: 3, # insn_len
operands: [{:decl=>"lindex_t kw_bits_index", :type=>"lindex_t", :name=>"kw_bits_index"}, {:decl=>"lindex_t keyword_index", :type=>"lindex_t", :name=>"keyword_index"}],
),
48 => Instruction.new(
name: :checktype,
bin: 48, # BIN(checktype)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t type", :type=>"rb_num_t", :name=>"type"}],
),
49 => Instruction.new(
name: :defineclass,
bin: 49, # BIN(defineclass)
len: 4, # insn_len
operands: [{: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"}],
),
50 => Instruction.new(
name: :definemethod,
bin: 50, # BIN(definemethod)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}],
),
51 => Instruction.new(
name: :definesmethod,
bin: 51, # BIN(definesmethod)
len: 3, # insn_len
operands: [{:decl=>"ID id", :type=>"ID", :name=>"id"}, {:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}],
),
52 => Instruction.new(
name: :send,
bin: 52, # BIN(send)
len: 3, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}, {:decl=>"ISEQ blockiseq", :type=>"ISEQ", :name=>"blockiseq"}],
),
53 => Instruction.new(
name: :opt_send_without_block,
bin: 53, # BIN(opt_send_without_block)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
54 => Instruction.new(
name: :objtostring,
bin: 54, # BIN(objtostring)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
55 => Instruction.new(
name: :opt_str_freeze,
bin: 55, # BIN(opt_str_freeze)
len: 3, # insn_len
operands: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
56 => Instruction.new(
name: :opt_nil_p,
bin: 56, # BIN(opt_nil_p)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
57 => Instruction.new(
name: :opt_str_uminus,
bin: 57, # BIN(opt_str_uminus)
len: 3, # insn_len
operands: [{:decl=>"VALUE str", :type=>"VALUE", :name=>"str"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
58 => Instruction.new(
name: :opt_newarray_send,
bin: 58, # BIN(opt_newarray_send)
len: 3, # insn_len
operands: [{:decl=>"rb_num_t num", :type=>"rb_num_t", :name=>"num"}, {:decl=>"ID method", :type=>"ID", :name=>"method"}],
),
59 => Instruction.new(
name: :invokesuper,
bin: 59, # BIN(invokesuper)
len: 3, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}, {:decl=>"ISEQ blockiseq", :type=>"ISEQ", :name=>"blockiseq"}],
),
60 => Instruction.new(
name: :invokeblock,
bin: 60, # BIN(invokeblock)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
61 => Instruction.new(
name: :leave,
bin: 61, # BIN(leave)
len: 1, # insn_len
operands: [],
),
62 => Instruction.new(
name: :throw,
bin: 62, # BIN(throw)
len: 2, # insn_len
operands: [{:decl=>"rb_num_t throw_state", :type=>"rb_num_t", :name=>"throw_state"}],
),
63 => Instruction.new(
name: :jump,
bin: 63, # BIN(jump)
len: 2, # insn_len
operands: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
),
64 => Instruction.new(
name: :branchif,
bin: 64, # BIN(branchif)
len: 2, # insn_len
operands: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
),
65 => Instruction.new(
name: :branchunless,
bin: 65, # BIN(branchunless)
len: 2, # insn_len
operands: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
),
66 => Instruction.new(
name: :branchnil,
bin: 66, # BIN(branchnil)
len: 2, # insn_len
operands: [{:decl=>"OFFSET dst", :type=>"OFFSET", :name=>"dst"}],
),
67 => Instruction.new(
name: :once,
bin: 67, # BIN(once)
len: 3, # insn_len
operands: [{:decl=>"ISEQ iseq", :type=>"ISEQ", :name=>"iseq"}, {:decl=>"ISE ise", :type=>"ISE", :name=>"ise"}],
),
68 => Instruction.new(
name: :opt_case_dispatch,
bin: 68, # BIN(opt_case_dispatch)
len: 3, # insn_len
operands: [{:decl=>"CDHASH hash", :type=>"CDHASH", :name=>"hash"}, {:decl=>"OFFSET else_offset", :type=>"OFFSET", :name=>"else_offset"}],
),
69 => Instruction.new(
name: :opt_plus,
bin: 69, # BIN(opt_plus)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
70 => Instruction.new(
name: :opt_minus,
bin: 70, # BIN(opt_minus)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
71 => Instruction.new(
name: :opt_mult,
bin: 71, # BIN(opt_mult)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
72 => Instruction.new(
name: :opt_div,
bin: 72, # BIN(opt_div)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
73 => Instruction.new(
name: :opt_mod,
bin: 73, # BIN(opt_mod)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
74 => Instruction.new(
name: :opt_eq,
bin: 74, # BIN(opt_eq)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
75 => Instruction.new(
name: :opt_neq,
bin: 75, # BIN(opt_neq)
len: 3, # insn_len
operands: [{:decl=>"CALL_DATA cd_eq", :type=>"CALL_DATA", :name=>"cd_eq"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
76 => Instruction.new(
name: :opt_lt,
bin: 76, # BIN(opt_lt)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
77 => Instruction.new(
name: :opt_le,
bin: 77, # BIN(opt_le)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
78 => Instruction.new(
name: :opt_gt,
bin: 78, # BIN(opt_gt)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
79 => Instruction.new(
name: :opt_ge,
bin: 79, # BIN(opt_ge)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
80 => Instruction.new(
name: :opt_ltlt,
bin: 80, # BIN(opt_ltlt)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
81 => Instruction.new(
name: :opt_and,
bin: 81, # BIN(opt_and)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
82 => Instruction.new(
name: :opt_or,
bin: 82, # BIN(opt_or)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
83 => Instruction.new(
name: :opt_aref,
bin: 83, # BIN(opt_aref)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
84 => Instruction.new(
name: :opt_aset,
bin: 84, # BIN(opt_aset)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
85 => Instruction.new(
name: :opt_aset_with,
bin: 85, # BIN(opt_aset_with)
len: 3, # insn_len
operands: [{:decl=>"VALUE key", :type=>"VALUE", :name=>"key"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
86 => Instruction.new(
name: :opt_aref_with,
bin: 86, # BIN(opt_aref_with)
len: 3, # insn_len
operands: [{:decl=>"VALUE key", :type=>"VALUE", :name=>"key"}, {:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
87 => Instruction.new(
name: :opt_length,
bin: 87, # BIN(opt_length)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
88 => Instruction.new(
name: :opt_size,
bin: 88, # BIN(opt_size)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
89 => Instruction.new(
name: :opt_empty_p,
bin: 89, # BIN(opt_empty_p)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
90 => Instruction.new(
name: :opt_succ,
bin: 90, # BIN(opt_succ)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
91 => Instruction.new(
name: :opt_not,
bin: 91, # BIN(opt_not)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
92 => Instruction.new(
name: :opt_regexpmatch2,
bin: 92, # BIN(opt_regexpmatch2)
len: 2, # insn_len
operands: [{:decl=>"CALL_DATA cd", :type=>"CALL_DATA", :name=>"cd"}],
),
93 => Instruction.new(
name: :invokebuiltin,
bin: 93, # BIN(invokebuiltin)
len: 2, # insn_len
operands: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}],
),
94 => Instruction.new(
name: :opt_invokebuiltin_delegate,
bin: 94, # BIN(opt_invokebuiltin_delegate)
len: 3, # insn_len
operands: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}, {:decl=>"rb_num_t index", :type=>"rb_num_t", :name=>"index"}],
),
95 => Instruction.new(
name: :opt_invokebuiltin_delegate_leave,
bin: 95, # BIN(opt_invokebuiltin_delegate_leave)
len: 3, # insn_len
operands: [{:decl=>"RB_BUILTIN bf", :type=>"RB_BUILTIN", :name=>"bf"}, {:decl=>"rb_num_t index", :type=>"rb_num_t", :name=>"index"}],
),
96 => Instruction.new(
name: :getlocal_WC_0,
bin: 96, # BIN(getlocal_WC_0)
len: 2, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
),
97 => Instruction.new(
name: :getlocal_WC_1,
bin: 97, # BIN(getlocal_WC_1)
len: 2, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
),
98 => Instruction.new(
name: :setlocal_WC_0,
bin: 98, # BIN(setlocal_WC_0)
len: 2, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
),
99 => Instruction.new(
name: :setlocal_WC_1,
bin: 99, # BIN(setlocal_WC_1)
len: 2, # insn_len
operands: [{:decl=>"lindex_t idx", :type=>"lindex_t", :name=>"idx"}],
),
100 => Instruction.new(
name: :putobject_INT2FIX_0_,
bin: 100, # BIN(putobject_INT2FIX_0_)
len: 1, # insn_len
operands: [],
),
101 => Instruction.new(
name: :putobject_INT2FIX_1_,
bin: 101, # BIN(putobject_INT2FIX_1_)
len: 1, # insn_len
operands: [],
),
102 => Instruction.new(
name: :trace_nop,
bin: 102, # BIN(trace_nop)
len: 1, # insn_len
operands: nil,
),
103 => Instruction.new(
name: :trace_getlocal,
bin: 103, # BIN(trace_getlocal)
len: 3, # insn_len
operands: nil,
),
104 => Instruction.new(
name: :trace_setlocal,
bin: 104, # BIN(trace_setlocal)
len: 3, # insn_len
operands: nil,
),
105 => Instruction.new(
name: :trace_getblockparam,
bin: 105, # BIN(trace_getblockparam)
len: 3, # insn_len
operands: nil,
),
106 => Instruction.new(
name: :trace_setblockparam,
bin: 106, # BIN(trace_setblockparam)
len: 3, # insn_len
operands: nil,
),
107 => Instruction.new(
name: :trace_getblockparamproxy,
bin: 107, # BIN(trace_getblockparamproxy)
len: 3, # insn_len
operands: nil,
),
108 => Instruction.new(
name: :trace_getspecial,
bin: 108, # BIN(trace_getspecial)
len: 3, # insn_len
operands: nil,
),
109 => Instruction.new(
name: :trace_setspecial,
bin: 109, # BIN(trace_setspecial)
len: 2, # insn_len
operands: nil,
),
110 => Instruction.new(
name: :trace_getinstancevariable,
bin: 110, # BIN(trace_getinstancevariable)
len: 3, # insn_len
operands: nil,
),
111 => Instruction.new(
name: :trace_setinstancevariable,
bin: 111, # BIN(trace_setinstancevariable)
len: 3, # insn_len
operands: nil,
),
112 => Instruction.new(
name: :trace_getclassvariable,
bin: 112, # BIN(trace_getclassvariable)
len: 3, # insn_len
operands: nil,
),
113 => Instruction.new(
name: :trace_setclassvariable,
bin: 113, # BIN(trace_setclassvariable)
len: 3, # insn_len
operands: nil,
),
114 => Instruction.new(
name: :trace_opt_getconstant_path,
bin: 114, # BIN(trace_opt_getconstant_path)
len: 2, # insn_len
operands: nil,
),
115 => Instruction.new(
name: :trace_getconstant,
bin: 115, # BIN(trace_getconstant)
len: 2, # insn_len
operands: nil,
),
116 => Instruction.new(
name: :trace_setconstant,
bin: 116, # BIN(trace_setconstant)
len: 2, # insn_len
operands: nil,
),
117 => Instruction.new(
name: :trace_getglobal,
bin: 117, # BIN(trace_getglobal)
len: 2, # insn_len
operands: nil,
),
118 => Instruction.new(
name: :trace_setglobal,
bin: 118, # BIN(trace_setglobal)
len: 2, # insn_len
operands: nil,
),
119 => Instruction.new(
name: :trace_putnil,
bin: 119, # BIN(trace_putnil)
len: 1, # insn_len
operands: nil,
),
120 => Instruction.new(
name: :trace_putself,
bin: 120, # BIN(trace_putself)
len: 1, # insn_len
operands: nil,
),
121 => Instruction.new(
name: :trace_putobject,
bin: 121, # BIN(trace_putobject)
len: 2, # insn_len
operands: nil,
),
122 => Instruction.new(
name: :trace_putspecialobject,
bin: 122, # BIN(trace_putspecialobject)
len: 2, # insn_len
operands: nil,
),
123 => Instruction.new(
name: :trace_putstring,
bin: 123, # BIN(trace_putstring)
len: 2, # insn_len
operands: nil,
),
124 => Instruction.new(
name: :trace_concatstrings,
bin: 124, # BIN(trace_concatstrings)
len: 2, # insn_len
operands: nil,
),
125 => Instruction.new(
name: :trace_anytostring,
bin: 125, # BIN(trace_anytostring)
len: 1, # insn_len
operands: nil,
),
126 => Instruction.new(
name: :trace_toregexp,
bin: 126, # BIN(trace_toregexp)
len: 3, # insn_len
operands: nil,
),
127 => Instruction.new(
name: :trace_intern,
bin: 127, # BIN(trace_intern)
len: 1, # insn_len
operands: nil,
),
128 => Instruction.new(
name: :trace_newarray,
bin: 128, # BIN(trace_newarray)
len: 2, # insn_len
operands: nil,
),
129 => Instruction.new(
name: :trace_newarraykwsplat,
bin: 129, # BIN(trace_newarraykwsplat)
len: 2, # insn_len
operands: nil,
),
130 => Instruction.new(
name: :trace_duparray,
bin: 130, # BIN(trace_duparray)
len: 2, # insn_len
operands: nil,
),
131 => Instruction.new(
name: :trace_duphash,
bin: 131, # BIN(trace_duphash)
len: 2, # insn_len
operands: nil,
),
132 => Instruction.new(
name: :trace_expandarray,
bin: 132, # BIN(trace_expandarray)
len: 3, # insn_len
operands: nil,
),
133 => Instruction.new(
name: :trace_concatarray,
bin: 133, # BIN(trace_concatarray)
len: 1, # insn_len
operands: nil,
),
134 => Instruction.new(
name: :trace_splatarray,
bin: 134, # BIN(trace_splatarray)
len: 2, # insn_len
operands: nil,
),
135 => Instruction.new(
name: :trace_splatkw,
bin: 135, # BIN(trace_splatkw)
len: 1, # insn_len
operands: nil,
),
136 => Instruction.new(
name: :trace_newhash,
bin: 136, # BIN(trace_newhash)
len: 2, # insn_len
operands: nil,
),
137 => Instruction.new(
name: :trace_newrange,
bin: 137, # BIN(trace_newrange)
len: 2, # insn_len
operands: nil,
),
138 => Instruction.new(
name: :trace_pop,
bin: 138, # BIN(trace_pop)
len: 1, # insn_len
operands: nil,
),
139 => Instruction.new(
name: :trace_dup,
bin: 139, # BIN(trace_dup)
len: 1, # insn_len
operands: nil,
),
140 => Instruction.new(
name: :trace_dupn,
bin: 140, # BIN(trace_dupn)
len: 2, # insn_len
operands: nil,
),
141 => Instruction.new(
name: :trace_swap,
bin: 141, # BIN(trace_swap)
len: 1, # insn_len
operands: nil,
),
142 => Instruction.new(
name: :trace_opt_reverse,
bin: 142, # BIN(trace_opt_reverse)
len: 2, # insn_len
operands: nil,
),
143 => Instruction.new(
name: :trace_topn,
bin: 143, # BIN(trace_topn)
len: 2, # insn_len
operands: nil,
),
144 => Instruction.new(
name: :trace_setn,
bin: 144, # BIN(trace_setn)
len: 2, # insn_len
operands: nil,
),
145 => Instruction.new(
name: :trace_adjuststack,
bin: 145, # BIN(trace_adjuststack)
len: 2, # insn_len
operands: nil,
),
146 => Instruction.new(
name: :trace_defined,
bin: 146, # BIN(trace_defined)
len: 4, # insn_len
operands: nil,
),
147 => Instruction.new(
name: :trace_definedivar,
bin: 147, # BIN(trace_definedivar)
len: 4, # insn_len
operands: nil,
),
148 => Instruction.new(
name: :trace_checkmatch,
bin: 148, # BIN(trace_checkmatch)
len: 2, # insn_len
operands: nil,
),
149 => Instruction.new(
name: :trace_checkkeyword,
bin: 149, # BIN(trace_checkkeyword)
len: 3, # insn_len
operands: nil,
),
150 => Instruction.new(
name: :trace_checktype,
bin: 150, # BIN(trace_checktype)
len: 2, # insn_len
operands: nil,
),
151 => Instruction.new(
name: :trace_defineclass,
bin: 151, # BIN(trace_defineclass)
len: 4, # insn_len
operands: nil,
),
152 => Instruction.new(
name: :trace_definemethod,
bin: 152, # BIN(trace_definemethod)
len: 3, # insn_len
operands: nil,
),
153 => Instruction.new(
name: :trace_definesmethod,
bin: 153, # BIN(trace_definesmethod)
len: 3, # insn_len
operands: nil,
),
154 => Instruction.new(
name: :trace_send,
bin: 154, # BIN(trace_send)
len: 3, # insn_len
operands: nil,
),
155 => Instruction.new(
name: :trace_opt_send_without_block,
bin: 155, # BIN(trace_opt_send_without_block)
len: 2, # insn_len
operands: nil,
),
156 => Instruction.new(
name: :trace_objtostring,
bin: 156, # BIN(trace_objtostring)
len: 2, # insn_len
operands: nil,
),
157 => Instruction.new(
name: :trace_opt_str_freeze,
bin: 157, # BIN(trace_opt_str_freeze)
len: 3, # insn_len
operands: nil,
),
158 => Instruction.new(
name: :trace_opt_nil_p,
bin: 158, # BIN(trace_opt_nil_p)
len: 2, # insn_len
operands: nil,
),
159 => Instruction.new(
name: :trace_opt_str_uminus,
bin: 159, # BIN(trace_opt_str_uminus)
len: 3, # insn_len
operands: nil,
),
160 => Instruction.new(
name: :trace_opt_newarray_send,
bin: 160, # BIN(trace_opt_newarray_send)
len: 3, # insn_len
operands: nil,
),
161 => Instruction.new(
name: :trace_invokesuper,
bin: 161, # BIN(trace_invokesuper)
len: 3, # insn_len
operands: nil,
),
162 => Instruction.new(
name: :trace_invokeblock,
bin: 162, # BIN(trace_invokeblock)
len: 2, # insn_len
operands: nil,
),
163 => Instruction.new(
name: :trace_leave,
bin: 163, # BIN(trace_leave)
len: 1, # insn_len
operands: nil,
),
164 => Instruction.new(
name: :trace_throw,
bin: 164, # BIN(trace_throw)
len: 2, # insn_len
operands: nil,
),
165 => Instruction.new(
name: :trace_jump,
bin: 165, # BIN(trace_jump)
len: 2, # insn_len
operands: nil,
),
166 => Instruction.new(
name: :trace_branchif,
bin: 166, # BIN(trace_branchif)
len: 2, # insn_len
operands: nil,
),
167 => Instruction.new(
name: :trace_branchunless,
bin: 167, # BIN(trace_branchunless)
len: 2, # insn_len
operands: nil,
),
168 => Instruction.new(
name: :trace_branchnil,
bin: 168, # BIN(trace_branchnil)
len: 2, # insn_len
operands: nil,
),
169 => Instruction.new(
name: :trace_once,
bin: 169, # BIN(trace_once)
len: 3, # insn_len
operands: nil,
),
170 => Instruction.new(
name: :trace_opt_case_dispatch,
bin: 170, # BIN(trace_opt_case_dispatch)
len: 3, # insn_len
operands: nil,
),
171 => Instruction.new(
name: :trace_opt_plus,
bin: 171, # BIN(trace_opt_plus)
len: 2, # insn_len
operands: nil,
),
172 => Instruction.new(
name: :trace_opt_minus,
bin: 172, # BIN(trace_opt_minus)
len: 2, # insn_len
operands: nil,
),
173 => Instruction.new(
name: :trace_opt_mult,
bin: 173, # BIN(trace_opt_mult)
len: 2, # insn_len
operands: nil,
),
174 => Instruction.new(
name: :trace_opt_div,
bin: 174, # BIN(trace_opt_div)
len: 2, # insn_len
operands: nil,
),
175 => Instruction.new(
name: :trace_opt_mod,
bin: 175, # BIN(trace_opt_mod)
len: 2, # insn_len
operands: nil,
),
176 => Instruction.new(
name: :trace_opt_eq,
bin: 176, # BIN(trace_opt_eq)
len: 2, # insn_len
operands: nil,
),
177 => Instruction.new(
name: :trace_opt_neq,
bin: 177, # BIN(trace_opt_neq)
len: 3, # insn_len
operands: nil,
),
178 => Instruction.new(
name: :trace_opt_lt,
bin: 178, # BIN(trace_opt_lt)
len: 2, # insn_len
operands: nil,
),
179 => Instruction.new(
name: :trace_opt_le,
bin: 179, # BIN(trace_opt_le)
len: 2, # insn_len
operands: nil,
),
180 => Instruction.new(
name: :trace_opt_gt,
bin: 180, # BIN(trace_opt_gt)
len: 2, # insn_len
operands: nil,
),
181 => Instruction.new(
name: :trace_opt_ge,
bin: 181, # BIN(trace_opt_ge)
len: 2, # insn_len
operands: nil,
),
182 => Instruction.new(
name: :trace_opt_ltlt,
bin: 182, # BIN(trace_opt_ltlt)
len: 2, # insn_len
operands: nil,
),
183 => Instruction.new(
name: :trace_opt_and,
bin: 183, # BIN(trace_opt_and)
len: 2, # insn_len
operands: nil,
),
184 => Instruction.new(
name: :trace_opt_or,
bin: 184, # BIN(trace_opt_or)
len: 2, # insn_len
operands: nil,
),
185 => Instruction.new(
name: :trace_opt_aref,
bin: 185, # BIN(trace_opt_aref)
len: 2, # insn_len
operands: nil,
),
186 => Instruction.new(
name: :trace_opt_aset,
bin: 186, # BIN(trace_opt_aset)
len: 2, # insn_len
operands: nil,
),
187 => Instruction.new(
name: :trace_opt_aset_with,
bin: 187, # BIN(trace_opt_aset_with)
len: 3, # insn_len
operands: nil,
),
188 => Instruction.new(
name: :trace_opt_aref_with,
bin: 188, # BIN(trace_opt_aref_with)
len: 3, # insn_len
operands: nil,
),
189 => Instruction.new(
name: :trace_opt_length,
bin: 189, # BIN(trace_opt_length)
len: 2, # insn_len
operands: nil,
),
190 => Instruction.new(
name: :trace_opt_size,
bin: 190, # BIN(trace_opt_size)
len: 2, # insn_len
operands: nil,
),
191 => Instruction.new(
name: :trace_opt_empty_p,
bin: 191, # BIN(trace_opt_empty_p)
len: 2, # insn_len
operands: nil,
),
192 => Instruction.new(
name: :trace_opt_succ,
bin: 192, # BIN(trace_opt_succ)
len: 2, # insn_len
operands: nil,
),
193 => Instruction.new(
name: :trace_opt_not,
bin: 193, # BIN(trace_opt_not)
len: 2, # insn_len
operands: nil,
),
194 => Instruction.new(
name: :trace_opt_regexpmatch2,
bin: 194, # BIN(trace_opt_regexpmatch2)
len: 2, # insn_len
operands: nil,
),
195 => Instruction.new(
name: :trace_invokebuiltin,
bin: 195, # BIN(trace_invokebuiltin)
len: 2, # insn_len
operands: nil,
),
196 => Instruction.new(
name: :trace_opt_invokebuiltin_delegate,
bin: 196, # BIN(trace_opt_invokebuiltin_delegate)
len: 3, # insn_len
operands: nil,
),
197 => Instruction.new(
name: :trace_opt_invokebuiltin_delegate_leave,
bin: 197, # BIN(trace_opt_invokebuiltin_delegate_leave)
len: 3, # insn_len
operands: nil,
),
198 => Instruction.new(
name: :trace_getlocal_WC_0,
bin: 198, # BIN(trace_getlocal_WC_0)
len: 2, # insn_len
operands: nil,
),
199 => Instruction.new(
name: :trace_getlocal_WC_1,
bin: 199, # BIN(trace_getlocal_WC_1)
len: 2, # insn_len
operands: nil,
),
200 => Instruction.new(
name: :trace_setlocal_WC_0,
bin: 200, # BIN(trace_setlocal_WC_0)
len: 2, # insn_len
operands: nil,
),
201 => Instruction.new(
name: :trace_setlocal_WC_1,
bin: 201, # BIN(trace_setlocal_WC_1)
len: 2, # insn_len
operands: nil,
),
202 => Instruction.new(
name: :trace_putobject_INT2FIX_0_,
bin: 202, # BIN(trace_putobject_INT2FIX_0_)
len: 1, # insn_len
operands: nil,
),
203 => Instruction.new(
name: :trace_putobject_INT2FIX_1_,
bin: 203, # BIN(trace_putobject_INT2FIX_1_)
len: 1, # insn_len
operands: nil,
),
}
end
share/ruby/ruby_vm/rjit/exit_compiler.rb 0000644 00000011516 15173517737 0014476 0 ustar 00 module RubyVM::RJIT
class ExitCompiler
def initialize = freeze
# Used for invalidating a block on entry.
# @param pc [Integer]
# @param asm [RubyVM::RJIT::Assembler]
def compile_entry_exit(pc, ctx, asm, cause:)
# Fix pc/sp offsets for the interpreter
save_pc_and_sp(pc, ctx, asm, reset_sp_offset: false)
# Increment per-insn exit counter
count_insn_exit(pc, asm)
# Restore callee-saved registers
asm.comment("#{cause}: entry exit")
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
asm.mov(C_RET, Qundef)
asm.ret
end
# Set to cfp->jit_return by default for leave insn
# @param asm [RubyVM::RJIT::Assembler]
def compile_leave_exit(asm)
asm.comment('default cfp->jit_return')
# Restore callee-saved registers
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
# :rax is written by #leave
asm.ret
end
# Fire cfunc events on invalidation by TracePoint
# @param asm [RubyVM::RJIT::Assembler]
def compile_full_cfunc_return(asm)
# This chunk of code expects REG_EC to be filled properly and
# RAX to contain the return value of the C method.
asm.comment('full cfunc return')
asm.mov(C_ARGS[0], EC)
asm.mov(C_ARGS[1], :rax)
asm.call(C.rjit_full_cfunc_return)
# TODO: count the exit
# Restore callee-saved registers
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
asm.mov(C_RET, Qundef)
asm.ret
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def compile_side_exit(pc, ctx, asm)
# Fix pc/sp offsets for the interpreter
save_pc_and_sp(pc, ctx.dup, asm) # dup to avoid sp_offset update
# Increment per-insn exit counter
count_insn_exit(pc, asm)
# Restore callee-saved registers
asm.comment("exit to interpreter on #{pc_to_insn(pc).name}")
asm.pop(SP)
asm.pop(EC)
asm.pop(CFP)
asm.mov(C_RET, Qundef)
asm.ret
end
# @param asm [RubyVM::RJIT::Assembler]
# @param entry_stub [RubyVM::RJIT::EntryStub]
def compile_entry_stub(asm, entry_stub)
# Call rb_rjit_entry_stub_hit
asm.comment('entry stub hit')
asm.mov(C_ARGS[0], to_value(entry_stub))
asm.call(C.rb_rjit_entry_stub_hit)
# Jump to the address returned by rb_rjit_entry_stub_hit
asm.jmp(:rax)
end
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
# @param branch_stub [RubyVM::RJIT::BranchStub]
# @param target0_p [TrueClass,FalseClass]
def compile_branch_stub(ctx, asm, branch_stub, target0_p)
# Call rb_rjit_branch_stub_hit
iseq = branch_stub.iseq
if C.rjit_opts.dump_disasm && C.imemo_type_p(iseq, C.imemo_iseq) # Guard against ISEQ GC at random moments
asm.comment("branch stub hit: #{iseq.body.location.label}@#{C.rb_iseq_path(iseq)}:#{iseq_lineno(iseq, target0_p ? branch_stub.target0.pc : branch_stub.target1.pc)}")
end
asm.mov(:rdi, to_value(branch_stub))
asm.mov(:esi, ctx.sp_offset)
asm.mov(:edx, target0_p ? 1 : 0)
asm.call(C.rb_rjit_branch_stub_hit)
# Jump to the address returned by rb_rjit_branch_stub_hit
asm.jmp(:rax)
end
private
def pc_to_insn(pc)
Compiler.decode_insn(C.VALUE.new(pc).*)
end
# @param pc [Integer]
# @param asm [RubyVM::RJIT::Assembler]
def count_insn_exit(pc, asm)
if C.rjit_opts.stats
insn = Compiler.decode_insn(C.VALUE.new(pc).*)
asm.comment("increment insn exit: #{insn.name}")
asm.mov(:rax, (C.rjit_insn_exits + insn.bin).to_i)
asm.add([:rax], 1) # TODO: lock
end
if C.rjit_opts.trace_exits
asm.comment('rjit_record_exit_stack')
asm.mov(C_ARGS[0], pc)
asm.call(C.rjit_record_exit_stack)
end
end
# @param jit [RubyVM::RJIT::JITState]
# @param ctx [RubyVM::RJIT::Context]
# @param asm [RubyVM::RJIT::Assembler]
def save_pc_and_sp(pc, ctx, asm, reset_sp_offset: true)
# Update pc (TODO: manage PC offset?)
asm.comment("save PC#{' and SP' if ctx.sp_offset != 0} to CFP")
asm.mov(:rax, pc) # rax = jit.pc
asm.mov([CFP, C.rb_control_frame_t.offsetof(:pc)], :rax) # cfp->pc = rax
# Update sp
if ctx.sp_offset != 0
asm.add(SP, C.VALUE.size * ctx.sp_offset) # sp += stack_size
asm.mov([CFP, C.rb_control_frame_t.offsetof(:sp)], SP) # cfp->sp = sp
if reset_sp_offset
ctx.sp_offset = 0
end
end
end
def to_value(obj)
GC_REFS << obj
C.to_value(obj)
end
def iseq_lineno(iseq, pc)
C.rb_iseq_line_no(iseq, (pc - iseq.body.iseq_encoded.to_i) / C.VALUE.size)
rescue RangeError # bignum too big to convert into `unsigned long long' (RangeError)
-1
end
end
end
share/ruby/ruby_vm/rjit/assembler.rb 0000644 00000100113 15173517737 0013600 0 ustar 00 # frozen_string_literal: true
module RubyVM::RJIT
# 8-bit memory access
class BytePtr < Data.define(:reg, :disp); end
# 32-bit memory access
class DwordPtr < Data.define(:reg, :disp); end
# 64-bit memory access
QwordPtr = Array
# SystemV x64 calling convention
C_ARGS = [:rdi, :rsi, :rdx, :rcx, :r8, :r9]
C_RET = :rax
# https://cdrdv2.intel.com/v1/dl/getContent/671110
# Mostly an x86_64 assembler, but this also has some stuff that is useful for any architecture.
class Assembler
# rel8 jumps are made with labels
class Label < Data.define(:id, :name); end
# rel32 is inserted as [Rel32, Rel32Pad..] and converted on #resolve_rel32
class Rel32 < Data.define(:addr); end
Rel32Pad = Object.new
# A set of ModR/M values encoded on #insn
class ModRM < Data.define(:mod, :reg, :rm); end
Mod00 = 0b00 # Mod 00: [reg]
Mod01 = 0b01 # Mod 01: [reg]+disp8
Mod10 = 0b10 # Mod 10: [reg]+disp32
Mod11 = 0b11 # Mod 11: reg
# REX = 0100WR0B
REX_B = 0b01000001
REX_R = 0b01000100
REX_W = 0b01001000
# Operand matchers
R32 = -> (op) { op.is_a?(Symbol) && r32?(op) }
R64 = -> (op) { op.is_a?(Symbol) && r64?(op) }
IMM8 = -> (op) { op.is_a?(Integer) && imm8?(op) }
IMM32 = -> (op) { op.is_a?(Integer) && imm32?(op) }
IMM64 = -> (op) { op.is_a?(Integer) && imm64?(op) }
def initialize
@bytes = []
@labels = {}
@label_id = 0
@comments = Hash.new { |h, k| h[k] = [] }
@blocks = Hash.new { |h, k| h[k] = [] }
@stub_starts = Hash.new { |h, k| h[k] = [] }
@stub_ends = Hash.new { |h, k| h[k] = [] }
@pos_markers = Hash.new { |h, k| h[k] = [] }
end
def assemble(addr)
set_code_addrs(addr)
resolve_rel32(addr)
resolve_labels
write_bytes(addr)
@pos_markers.each do |write_pos, markers|
markers.each { |marker| marker.call(addr + write_pos) }
end
@bytes.size
ensure
@bytes.clear
end
def size
@bytes.size
end
#
# Instructions
#
def add(dst, src)
case [dst, src]
# ADD r/m64, imm8 (Mod 00: [reg])
in [QwordPtr[R64 => dst_reg], IMM8 => src_imm]
# REX.W + 83 /0 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
imm: imm8(src_imm),
)
# ADD r/m64, imm8 (Mod 11: reg)
in [R64 => dst_reg, IMM8 => src_imm]
# REX.W + 83 /0 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
imm: imm8(src_imm),
)
# ADD r/m64 imm32 (Mod 11: reg)
in [R64 => dst_reg, IMM32 => src_imm]
# REX.W + 81 /0 id
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x81,
mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
imm: imm32(src_imm),
)
# ADD r/m64, r64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 01 /r
# MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x01,
mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
)
end
end
def and(dst, src)
case [dst, src]
# AND r/m64, imm8 (Mod 11: reg)
in [R64 => dst_reg, IMM8 => src_imm]
# REX.W + 83 /4 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg],
imm: imm8(src_imm),
)
# AND r/m64, imm32 (Mod 11: reg)
in [R64 => dst_reg, IMM32 => src_imm]
# REX.W + 81 /4 id
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x81,
mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg],
imm: imm32(src_imm),
)
# AND r64, r/m64 (Mod 01: [reg]+disp8)
in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
# REX.W + 23 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x23,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: imm8(src_disp),
)
end
end
def call(dst)
case dst
# CALL rel32
in Integer => dst_addr
# E8 cd
# D: Operand 1: Offset
insn(opcode: 0xe8, imm: rel32(dst_addr))
# CALL r/m64 (Mod 11: reg)
in R64 => dst_reg
# FF /2
# M: Operand 1: ModRM:r/m (r)
insn(
opcode: 0xff,
mod_rm: ModRM[mod: Mod11, reg: 2, rm: dst_reg],
)
end
end
def cmove(dst, src)
case [dst, src]
# CMOVE r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 44 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x44],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovg(dst, src)
case [dst, src]
# CMOVG r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 4F /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x4f],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovge(dst, src)
case [dst, src]
# CMOVGE r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 4D /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x4d],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovl(dst, src)
case [dst, src]
# CMOVL r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 4C /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x4c],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovle(dst, src)
case [dst, src]
# CMOVLE r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 4E /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x4e],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovne(dst, src)
case [dst, src]
# CMOVNE r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 45 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x45],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovnz(dst, src)
case [dst, src]
# CMOVNZ r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 45 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x45],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
end
end
def cmovz(dst, src)
case [dst, src]
# CMOVZ r64, r/m64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 0F 44 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x44],
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
# CMOVZ r64, r/m64 (Mod 01: [reg]+disp8)
in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
# REX.W + 0F 44 /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: [0x0f, 0x44],
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: imm8(src_disp),
)
end
end
def cmp(left, right)
case [left, right]
# CMP r/m8, imm8 (Mod 01: [reg]+disp8)
in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
# 80 /7 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
opcode: 0x80,
mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
disp: left_disp,
imm: imm8(right_imm),
)
# CMP r/m32, imm32 (Mod 01: [reg]+disp8)
in [DwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
# 81 /7 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
opcode: 0x81,
mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
disp: left_disp,
imm: imm32(right_imm),
)
# CMP r/m64, imm8 (Mod 01: [reg]+disp8)
in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
# REX.W + 83 /7 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
disp: left_disp,
imm: imm8(right_imm),
)
# CMP r/m64, imm32 (Mod 01: [reg]+disp8)
in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
# REX.W + 81 /7 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x81,
mod_rm: ModRM[mod: Mod01, reg: 7, rm: left_reg],
disp: left_disp,
imm: imm32(right_imm),
)
# CMP r/m64, imm8 (Mod 10: [reg]+disp32)
in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM8 => right_imm]
# REX.W + 83 /7 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod10, reg: 7, rm: left_reg],
disp: imm32(left_disp),
imm: imm8(right_imm),
)
# CMP r/m64, imm8 (Mod 11: reg)
in [R64 => left_reg, IMM8 => right_imm]
# REX.W + 83 /7 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg],
imm: imm8(right_imm),
)
# CMP r/m64, imm32 (Mod 11: reg)
in [R64 => left_reg, IMM32 => right_imm]
# REX.W + 81 /7 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x81,
mod_rm: ModRM[mod: Mod11, reg: 7, rm: left_reg],
imm: imm32(right_imm),
)
# CMP r/m64, r64 (Mod 01: [reg]+disp8)
in [QwordPtr[R64 => left_reg, IMM8 => left_disp], R64 => right_reg]
# REX.W + 39 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x39,
mod_rm: ModRM[mod: Mod01, reg: right_reg, rm: left_reg],
disp: left_disp,
)
# CMP r/m64, r64 (Mod 10: [reg]+disp32)
in [QwordPtr[R64 => left_reg, IMM32 => left_disp], R64 => right_reg]
# REX.W + 39 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x39,
mod_rm: ModRM[mod: Mod10, reg: right_reg, rm: left_reg],
disp: imm32(left_disp),
)
# CMP r/m64, r64 (Mod 11: reg)
in [R64 => left_reg, R64 => right_reg]
# REX.W + 39 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x39,
mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
)
end
end
def jbe(dst)
case dst
# JBE rel8
in Label => dst_label
# 76 cb
insn(opcode: 0x76, imm: dst_label)
# JBE rel32
in Integer => dst_addr
# 0F 86 cd
insn(opcode: [0x0f, 0x86], imm: rel32(dst_addr))
end
end
def je(dst)
case dst
# JE rel8
in Label => dst_label
# 74 cb
insn(opcode: 0x74, imm: dst_label)
# JE rel32
in Integer => dst_addr
# 0F 84 cd
insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr))
end
end
def jl(dst)
case dst
# JL rel32
in Integer => dst_addr
# 0F 8C cd
insn(opcode: [0x0f, 0x8c], imm: rel32(dst_addr))
end
end
def jmp(dst)
case dst
# JZ rel8
in Label => dst_label
# EB cb
insn(opcode: 0xeb, imm: dst_label)
# JMP rel32
in Integer => dst_addr
# E9 cd
insn(opcode: 0xe9, imm: rel32(dst_addr))
# JMP r/m64 (Mod 01: [reg]+disp8)
in QwordPtr[R64 => dst_reg, IMM8 => dst_disp]
# FF /4
insn(opcode: 0xff, mod_rm: ModRM[mod: Mod01, reg: 4, rm: dst_reg], disp: dst_disp)
# JMP r/m64 (Mod 11: reg)
in R64 => dst_reg
# FF /4
insn(opcode: 0xff, mod_rm: ModRM[mod: Mod11, reg: 4, rm: dst_reg])
end
end
def jne(dst)
case dst
# JNE rel8
in Label => dst_label
# 75 cb
insn(opcode: 0x75, imm: dst_label)
# JNE rel32
in Integer => dst_addr
# 0F 85 cd
insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
end
end
def jnz(dst)
case dst
# JE rel8
in Label => dst_label
# 75 cb
insn(opcode: 0x75, imm: dst_label)
# JNZ rel32
in Integer => dst_addr
# 0F 85 cd
insn(opcode: [0x0f, 0x85], imm: rel32(dst_addr))
end
end
def jo(dst)
case dst
# JO rel32
in Integer => dst_addr
# 0F 80 cd
insn(opcode: [0x0f, 0x80], imm: rel32(dst_addr))
end
end
def jz(dst)
case dst
# JZ rel8
in Label => dst_label
# 74 cb
insn(opcode: 0x74, imm: dst_label)
# JZ rel32
in Integer => dst_addr
# 0F 84 cd
insn(opcode: [0x0f, 0x84], imm: rel32(dst_addr))
end
end
def lea(dst, src)
case [dst, src]
# LEA r64,m (Mod 01: [reg]+disp8)
in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
# REX.W + 8D /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8d,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: imm8(src_disp),
)
# LEA r64,m (Mod 10: [reg]+disp32)
in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM32 => src_disp]]
# REX.W + 8D /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8d,
mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg],
disp: imm32(src_disp),
)
end
end
def mov(dst, src)
case dst
in R32 => dst_reg
case src
# MOV r32 r/m32 (Mod 01: [reg]+disp8)
in DwordPtr[R64 => src_reg, IMM8 => src_disp]
# 8B /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
opcode: 0x8b,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: src_disp,
)
# MOV r32, imm32 (Mod 11: reg)
in IMM32 => src_imm
# B8+ rd id
# OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64
insn(
opcode: 0xb8,
rd: dst_reg,
imm: imm32(src_imm),
)
end
in R64 => dst_reg
case src
# MOV r64, r/m64 (Mod 00: [reg])
in QwordPtr[R64 => src_reg]
# REX.W + 8B /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8b,
mod_rm: ModRM[mod: Mod00, reg: dst_reg, rm: src_reg],
)
# MOV r64, r/m64 (Mod 01: [reg]+disp8)
in QwordPtr[R64 => src_reg, IMM8 => src_disp]
# REX.W + 8B /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8b,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: src_disp,
)
# MOV r64, r/m64 (Mod 10: [reg]+disp32)
in QwordPtr[R64 => src_reg, IMM32 => src_disp]
# REX.W + 8B /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8b,
mod_rm: ModRM[mod: Mod10, reg: dst_reg, rm: src_reg],
disp: imm32(src_disp),
)
# MOV r64, r/m64 (Mod 11: reg)
in R64 => src_reg
# REX.W + 8B /r
# RM: Operand 1: ModRM:reg (w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x8b,
mod_rm: ModRM[mod: Mod11, reg: dst_reg, rm: src_reg],
)
# MOV r/m64, imm32 (Mod 11: reg)
in IMM32 => src_imm
# REX.W + C7 /0 id
# MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
insn(
prefix: REX_W,
opcode: 0xc7,
mod_rm: ModRM[mod: Mod11, reg: 0, rm: dst_reg],
imm: imm32(src_imm),
)
# MOV r64, imm64
in IMM64 => src_imm
# REX.W + B8+ rd io
# OI: Operand 1: opcode + rd (w), Operand 2: imm8/16/32/64
insn(
prefix: REX_W,
opcode: 0xb8,
rd: dst_reg,
imm: imm64(src_imm),
)
end
in DwordPtr[R64 => dst_reg, IMM8 => dst_disp]
case src
# MOV r/m32, imm32 (Mod 01: [reg]+disp8)
in IMM32 => src_imm
# C7 /0 id
# MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
insn(
opcode: 0xc7,
mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg],
disp: dst_disp,
imm: imm32(src_imm),
)
end
in QwordPtr[R64 => dst_reg]
case src
# MOV r/m64, imm32 (Mod 00: [reg])
in IMM32 => src_imm
# REX.W + C7 /0 id
# MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
insn(
prefix: REX_W,
opcode: 0xc7,
mod_rm: ModRM[mod: Mod00, reg: 0, rm: dst_reg],
imm: imm32(src_imm),
)
# MOV r/m64, r64 (Mod 00: [reg])
in R64 => src_reg
# REX.W + 89 /r
# MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x89,
mod_rm: ModRM[mod: Mod00, reg: src_reg, rm: dst_reg],
)
end
in QwordPtr[R64 => dst_reg, IMM8 => dst_disp]
# Optimize encoding when disp is 0
return mov([dst_reg], src) if dst_disp == 0
case src
# MOV r/m64, imm32 (Mod 01: [reg]+disp8)
in IMM32 => src_imm
# REX.W + C7 /0 id
# MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
insn(
prefix: REX_W,
opcode: 0xc7,
mod_rm: ModRM[mod: Mod01, reg: 0, rm: dst_reg],
disp: dst_disp,
imm: imm32(src_imm),
)
# MOV r/m64, r64 (Mod 01: [reg]+disp8)
in R64 => src_reg
# REX.W + 89 /r
# MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x89,
mod_rm: ModRM[mod: Mod01, reg: src_reg, rm: dst_reg],
disp: dst_disp,
)
end
in QwordPtr[R64 => dst_reg, IMM32 => dst_disp]
case src
# MOV r/m64, imm32 (Mod 10: [reg]+disp32)
in IMM32 => src_imm
# REX.W + C7 /0 id
# MI: Operand 1: ModRM:r/m (w), Operand 2: imm8/16/32/64
insn(
prefix: REX_W,
opcode: 0xc7,
mod_rm: ModRM[mod: Mod10, reg: 0, rm: dst_reg],
disp: imm32(dst_disp),
imm: imm32(src_imm),
)
# MOV r/m64, r64 (Mod 10: [reg]+disp32)
in R64 => src_reg
# REX.W + 89 /r
# MR: Operand 1: ModRM:r/m (w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x89,
mod_rm: ModRM[mod: Mod10, reg: src_reg, rm: dst_reg],
disp: imm32(dst_disp),
)
end
end
end
def or(dst, src)
case [dst, src]
# OR r/m64, imm8 (Mod 11: reg)
in [R64 => dst_reg, IMM8 => src_imm]
# REX.W + 83 /1 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg],
imm: imm8(src_imm),
)
# OR r/m64, imm32 (Mod 11: reg)
in [R64 => dst_reg, IMM32 => src_imm]
# REX.W + 81 /1 id
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x81,
mod_rm: ModRM[mod: Mod11, reg: 1, rm: dst_reg],
imm: imm32(src_imm),
)
# OR r64, r/m64 (Mod 01: [reg]+disp8)
in [R64 => dst_reg, QwordPtr[R64 => src_reg, IMM8 => src_disp]]
# REX.W + 0B /r
# RM: Operand 1: ModRM:reg (r, w), Operand 2: ModRM:r/m (r)
insn(
prefix: REX_W,
opcode: 0x0b,
mod_rm: ModRM[mod: Mod01, reg: dst_reg, rm: src_reg],
disp: imm8(src_disp),
)
end
end
def push(src)
case src
# PUSH r64
in R64 => src_reg
# 50+rd
# O: Operand 1: opcode + rd (r)
insn(opcode: 0x50, rd: src_reg)
end
end
def pop(dst)
case dst
# POP r64
in R64 => dst_reg
# 58+ rd
# O: Operand 1: opcode + rd (r)
insn(opcode: 0x58, rd: dst_reg)
end
end
def ret
# RET
# Near return: A return to a procedure within the current code segment
insn(opcode: 0xc3)
end
def sar(dst, src)
case [dst, src]
in [R64 => dst_reg, IMM8 => src_imm]
# REX.W + C1 /7 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8
insn(
prefix: REX_W,
opcode: 0xc1,
mod_rm: ModRM[mod: Mod11, reg: 7, rm: dst_reg],
imm: imm8(src_imm),
)
end
end
def sub(dst, src)
case [dst, src]
# SUB r/m64, imm8 (Mod 11: reg)
in [R64 => dst_reg, IMM8 => src_imm]
# REX.W + 83 /5 ib
# MI: Operand 1: ModRM:r/m (r, w), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0x83,
mod_rm: ModRM[mod: Mod11, reg: 5, rm: dst_reg],
imm: imm8(src_imm),
)
# SUB r/m64, r64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 29 /r
# MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x29,
mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
)
end
end
def test(left, right)
case [left, right]
# TEST r/m8*, imm8 (Mod 01: [reg]+disp8)
in [BytePtr[R64 => left_reg, IMM8 => left_disp], IMM8 => right_imm]
# REX + F6 /0 ib
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
opcode: 0xf6,
mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg],
disp: left_disp,
imm: imm8(right_imm),
)
# TEST r/m64, imm32 (Mod 01: [reg]+disp8)
in [QwordPtr[R64 => left_reg, IMM8 => left_disp], IMM32 => right_imm]
# REX.W + F7 /0 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0xf7,
mod_rm: ModRM[mod: Mod01, reg: 0, rm: left_reg],
disp: left_disp,
imm: imm32(right_imm),
)
# TEST r/m64, imm32 (Mod 10: [reg]+disp32)
in [QwordPtr[R64 => left_reg, IMM32 => left_disp], IMM32 => right_imm]
# REX.W + F7 /0 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0xf7,
mod_rm: ModRM[mod: Mod10, reg: 0, rm: left_reg],
disp: imm32(left_disp),
imm: imm32(right_imm),
)
# TEST r/m64, imm32 (Mod 11: reg)
in [R64 => left_reg, IMM32 => right_imm]
# REX.W + F7 /0 id
# MI: Operand 1: ModRM:r/m (r), Operand 2: imm8/16/32
insn(
prefix: REX_W,
opcode: 0xf7,
mod_rm: ModRM[mod: Mod11, reg: 0, rm: left_reg],
imm: imm32(right_imm),
)
# TEST r/m32, r32 (Mod 11: reg)
in [R32 => left_reg, R32 => right_reg]
# 85 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
opcode: 0x85,
mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
)
# TEST r/m64, r64 (Mod 11: reg)
in [R64 => left_reg, R64 => right_reg]
# REX.W + 85 /r
# MR: Operand 1: ModRM:r/m (r), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x85,
mod_rm: ModRM[mod: Mod11, reg: right_reg, rm: left_reg],
)
end
end
def xor(dst, src)
case [dst, src]
# XOR r/m64, r64 (Mod 11: reg)
in [R64 => dst_reg, R64 => src_reg]
# REX.W + 31 /r
# MR: Operand 1: ModRM:r/m (r, w), Operand 2: ModRM:reg (r)
insn(
prefix: REX_W,
opcode: 0x31,
mod_rm: ModRM[mod: Mod11, reg: src_reg, rm: dst_reg],
)
end
end
#
# Utilities
#
attr_reader :comments
def comment(message)
@comments[@bytes.size] << message
end
# Mark the starting address of a block
def block(block)
@blocks[@bytes.size] << block
end
# Mark the starting/ending addresses of a stub
def stub(stub)
@stub_starts[@bytes.size] << stub
yield
ensure
@stub_ends[@bytes.size] << stub
end
def pos_marker(&block)
@pos_markers[@bytes.size] << block
end
def new_label(name)
Label.new(id: @label_id += 1, name:)
end
# @param [RubyVM::RJIT::Assembler::Label] label
def write_label(label)
@labels[label] = @bytes.size
end
def incr_counter(name)
if C.rjit_opts.stats
comment("increment counter #{name}")
mov(:rax, C.rb_rjit_counters[name].to_i)
add([:rax], 1) # TODO: lock
end
end
private
def insn(prefix: 0, opcode:, rd: nil, mod_rm: nil, disp: nil, imm: nil)
# Determine prefix
if rd
prefix |= REX_B if extended_reg?(rd)
opcode += reg_code(rd)
end
if mod_rm
prefix |= REX_R if mod_rm.reg.is_a?(Symbol) && extended_reg?(mod_rm.reg)
prefix |= REX_B if mod_rm.rm.is_a?(Symbol) && extended_reg?(mod_rm.rm)
end
# Encode insn
if prefix > 0
@bytes.push(prefix)
end
@bytes.push(*Array(opcode))
if mod_rm
mod_rm_byte = encode_mod_rm(
mod: mod_rm.mod,
reg: mod_rm.reg.is_a?(Symbol) ? reg_code(mod_rm.reg) : mod_rm.reg,
rm: mod_rm.rm.is_a?(Symbol) ? reg_code(mod_rm.rm) : mod_rm.rm,
)
@bytes.push(mod_rm_byte)
end
if disp
@bytes.push(*Array(disp))
end
if imm
@bytes.push(*imm)
end
end
def reg_code(reg)
reg_code_extended(reg).first
end
# Table 2-2. 32-Bit Addressing Forms with the ModR/M Byte
#
# 7 6 5 4 3 2 1 0
# +--+--+--+--+--+--+--+--+
# | Mod | Reg/ | R/M |
# | | Opcode | |
# +--+--+--+--+--+--+--+--+
#
# The r/m field can specify a register as an operand or it can be combined
# with the mod field to encode an addressing mode.
#
# /0: R/M is 0 (not used)
# /r: R/M is a register
def encode_mod_rm(mod:, reg: 0, rm: 0)
if mod > 0b11
raise ArgumentError, "too large Mod: #{mod}"
end
if reg > 0b111
raise ArgumentError, "too large Reg/Opcode: #{reg}"
end
if rm > 0b111
raise ArgumentError, "too large R/M: #{rm}"
end
(mod << 6) + (reg << 3) + rm
end
# ib: 1 byte
def imm8(imm)
unless imm8?(imm)
raise ArgumentError, "unexpected imm8: #{imm}"
end
[imm].pack('c').unpack('c*') # TODO: consider uimm
end
# id: 4 bytes
def imm32(imm)
unless imm32?(imm)
raise ArgumentError, "unexpected imm32: #{imm}"
end
[imm].pack('l').unpack('c*') # TODO: consider uimm
end
# io: 8 bytes
def imm64(imm)
unless imm64?(imm)
raise ArgumentError, "unexpected imm64: #{imm}"
end
imm_bytes(imm, 8)
end
def imm_bytes(imm, num_bytes)
bytes = []
bits = imm
num_bytes.times do
bytes << (bits & 0xff)
bits >>= 8
end
if bits != 0
raise ArgumentError, "unexpected imm with #{num_bytes} bytes: #{imm}"
end
bytes
end
def rel32(addr)
[Rel32.new(addr), Rel32Pad, Rel32Pad, Rel32Pad]
end
def set_code_addrs(write_addr)
(@bytes.size + 1).times do |index|
@blocks.fetch(index, []).each do |block|
block.start_addr = write_addr + index
end
@stub_starts.fetch(index, []).each do |stub|
stub.start_addr = write_addr + index
end
@stub_ends.fetch(index, []).each do |stub|
stub.end_addr = write_addr + index
end
end
end
def resolve_rel32(write_addr)
@bytes.each_with_index do |byte, index|
if byte.is_a?(Rel32)
src_addr = write_addr + index + 4 # offset 4 bytes for rel32 itself
dst_addr = byte.addr
rel32 = dst_addr - src_addr
raise "unexpected offset: #{rel32}" unless imm32?(rel32)
imm32(rel32).each_with_index do |rel_byte, rel_index|
@bytes[index + rel_index] = rel_byte
end
end
end
end
def resolve_labels
@bytes.each_with_index do |byte, index|
if byte.is_a?(Label)
src_index = index + 1 # offset 1 byte for rel8 itself
dst_index = @labels.fetch(byte)
rel8 = dst_index - src_index
raise "unexpected offset: #{rel8}" unless imm8?(rel8)
@bytes[index] = rel8
end
end
end
def write_bytes(addr)
Fiddle::Pointer.new(addr)[0, @bytes.size] = @bytes.pack('c*')
end
end
module OperandMatcher
def imm8?(imm)
(-0x80..0x7f).include?(imm)
end
def imm32?(imm)
(-0x8000_0000..0x7fff_ffff).include?(imm) # TODO: consider uimm
end
def imm64?(imm)
(-0x8000_0000_0000_0000..0xffff_ffff_ffff_ffff).include?(imm)
end
def r32?(reg)
if extended_reg?(reg)
reg.end_with?('d')
else
reg.start_with?('e')
end
end
def r64?(reg)
if extended_reg?(reg)
reg.match?(/\Ar\d+\z/)
else
reg.start_with?('r')
end
end
def extended_reg?(reg)
reg_code_extended(reg).last
end
def reg_code_extended(reg)
case reg
# Not extended
when :al, :ax, :eax, :rax then [0, false]
when :cl, :cx, :ecx, :rcx then [1, false]
when :dl, :dx, :edx, :rdx then [2, false]
when :bl, :bx, :ebx, :rbx then [3, false]
when :ah, :sp, :esp, :rsp then [4, false]
when :ch, :bp, :ebp, :rbp then [5, false]
when :dh, :si, :esi, :rsi then [6, false]
when :bh, :di, :edi, :rdi then [7, false]
# Extended
when :r8b, :r8w, :r8d, :r8 then [0, true]
when :r9b, :r9w, :r9d, :r9 then [1, true]
when :r10b, :r10w, :r10d, :r10 then [2, true]
when :r11b, :r11w, :r11d, :r11 then [3, true]
when :r12b, :r12w, :r12d, :r12 then [4, true]
when :r13b, :r13w, :r13d, :r13 then [5, true]
when :r14b, :r14w, :r14d, :r14 then [6, true]
when :r15b, :r15w, :r15d, :r15 then [7, true]
else raise ArgumentError, "unexpected reg: #{reg.inspect}"
end
end
end
class Assembler
include OperandMatcher
extend OperandMatcher
end
end
share/ruby/pathname.rb 0000644 00000041547 15173517737 0011004 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
VERSION = "0.3.0"
# :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 15173517737 0010465 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 15173517737 0014042 0 ustar 00 module ErrorHighlight
VERSION = "0.6.0"
end
share/ruby/error_highlight/core_ext.rb 0000644 00000002610 15173517737 0014163 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 15173517740 0014356 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 00000036116 15173517740 0013267 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
OPT_GETCONSTANT_PATH = (RUBY_VERSION.split(".").map {|s| s.to_i } <=> [3, 2]) >= 0
private_constant :OPT_GETCONSTANT_PATH
def spot
return nil unless @node
if OPT_GETCONSTANT_PATH && @node.type == :COLON2
# In Ruby 3.2 or later, a nested constant access (like `Foo::Bar::Baz`)
# is compiled to one instruction (opt_getconstant_path).
# @node points to the node of the whole `Foo::Bar::Baz` even if `Foo`
# or `Foo::Bar` causes NameError.
# So we try to spot the sub-node that causes the NameError by using
# `NameError#name`.
subnodes = []
node = @node
while node.type == :COLON2
node2, const = node.children
subnodes << node if const == @name
node = node2
end
if node.type == :CONST || node.type == :COLON3
if node.children.first == @name
subnodes << node
end
# If we found only one sub-node whose name is equal to @name, use it
return nil if subnodes.size != 1
@node = subnodes.first
else
# Do nothing; opt_getconstant_path is used only when the const base is
# NODE_CONST (`Foo`) or NODE_COLON3 (`::Foo`)
end
end
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 00000013050 15173517740 0010556 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 <code>$_</code> 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:: $~
# $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 <code>-F</code> flag.
alias $FS $;
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.
# 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 $,
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 $/
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 $\
alias $OUTPUT_RECORD_SEPARATOR $\
# The number of the last line read from the current input file.
alias $NR $.
alias $INPUT_LINE_NUMBER $.
# The last line read by Kernel#gets or
# Kernel#readline. Many string-related functions in the
# Kernel module operate on <code>$_</code> 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
# <code>$stdout</code>.
alias $DEFAULT_OUTPUT $>
# An object that provides access to the concatenation
# of the contents of all the files
# given as command-line arguments, or <code>$stdin</code>
# (in the case where there are no
# arguments). <code>$<</code> supports methods similar to a
# File object:
# +inmode+, +close+,
# <code>closed?</code>, +each+,
# <code>each_byte</code>, <code>each_line</code>,
# +eof+, <code>eof?</code>, +file+,
# +filename+, +fileno+,
# +getc+, +gets+, +lineno+,
# <code>lineno=</code>, +path+,
# +pos+, <code>pos=</code>,
# +read+, +readchar+,
# +readline+, +readlines+,
# +rewind+, +seek+, +skip+,
# +tell+, <code>to_a</code>, <code>to_i</code>,
# <code>to_io</code>, <code>to_s</code>, along with the
# methods in Enumerable. The method +file+
# returns a File object for the file currently
# being read. This may change as <code>$<</code> 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 $$
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 <code>$&</code>, <code>$`</code>, <code>$'</code>,
# and <code>$1</code> to <code>$9</code> are all derived from
# <code>$~</code>. Assigning to <code>$~</code> changes the values of these
# derived variables. This variable is local to the current
# scope.
alias $LAST_MATCH_INFO $~
# 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 <code>"cat" =~ /(c|a)(t|z)/</code>,
# <code>$+</code> 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 15173517740 0012054 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 00000011672 15173517740 0010474 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
# Class variables are inaccessible from non-main Ractor.
# And instance variables too, in Ruby 3.0.
# System-wide temporary directory path
SYSTMPDIR = (defined?(Etc.systmpdir) ? Etc.systmpdir.freeze : '/tmp')
private_constant :SYSTMPDIR
##
# Returns the operating system's temporary file path.
def self.tmpdir
['TMPDIR', 'TMP', 'TEMP', ['system temporary path', SYSTMPDIR], ['/tmp']*2, ['.']*2].find do |name, dir|
unless dir
next if !(dir = ENV[name] rescue next) 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 = Object.new
class << RANDOM # :nodoc:
# Maximum random number
MAX = 36**6 # < 0x100000000
# Returns new random string upto 6 bytes
def next
(::Random.urandom(4).unpack1("L")%MAX).to_s(36)
end
end
RANDOM.freeze
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 00000002647 15173517740 0010724 0 ustar 00 # frozen_string_literal: true
#--
# ERB::Escape
#
# A subset of ERB::Util. Unlike ERB::Util#html_escape, we expect/hope
# Rails will not monkey-patch ERB::Escape#html_escape.
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 00000001725 15173517740 0012041 0 ustar 00 # frozen_string_literal: true
#--
# 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 15173517740 0011421 0 ustar 00 # frozen_string_literal: true
class ERB
VERSION = '4.0.3'
private_constant :VERSION
end
share/ruby/erb/compiler.rb 0000644 00000026560 15173517740 0011561 0 ustar 00 # frozen_string_literal: true
#--
# 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 15173517740 0010753 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.1"
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 00000003405 15173517740 0016767 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 15173517740 0016466 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 00000015635 15173517740 0015735 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 aggressive 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
# Manageable rspec errors
def inspect
"#<#{self.class}:0x0000123843lol >"
end
end
end
share/ruby/syntax_suggest/block_expand.rb 0000644 00000011600 15173517740 0014704 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
# Manageable rspec errors
def inspect
"#<SyntaxSuggest::CodeBlock:0x0000123843lol >"
end
end
end
share/ruby/syntax_suggest/pathname_from_message.rb 0000644 00000002660 15173517740 0016605 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 00000003001 15173517740 0020311 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 # misspelled `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 00000014114 15173517740 0013027 0 ustar 00 # frozen_string_literal: true
require_relative "version"
require "tmpdir"
require "stringio"
require "pathname"
require "timeout"
# We need Ripper loaded for `Prism.lex_compat` even if we're using Prism
# for lexing and parsing
require "ripper"
# Prism is the new parser, replacing Ripper
#
# We need to "dual boot" both for now because syntax_suggest
# supports older rubies that do not ship with syntax suggest.
#
# We also need the ability to control loading of this library
# so we can test that both modes work correctly in CI.
if (value = ENV["SYNTAX_SUGGEST_DISABLE_PRISM"])
warn "Skipping loading prism due to SYNTAX_SUGGEST_DISABLE_PRISM=#{value}"
else
begin
require "prism"
rescue LoadError
end
end
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.use_prism_parser? [Private]
#
# Tells us if the prism parser is available for use
# or if we should fallback to `Ripper`
def self.use_prism_parser?
defined?(Prism)
end
# 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?`
if defined?(Prism)
def self.invalid?(source)
source = source.join if source.is_a?(Array)
source = source.to_s
Prism.parse(source).failure?
end
else
def self.invalid?(source)
source = source.join if source.is_a?(Array)
source = source.to_s
Ripper.new(source).tap(&:parse).error?
end
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 "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 00000005304 15173517740 0015325 0 ustar 00 # frozen_string_literal: true
require_relative "left_right_lex_count"
if !SyntaxSuggest.use_prism_parser?
require_relative "ripper_errors"
end
module SyntaxSuggest
class GetParseErrors
def self.errors(source)
if SyntaxSuggest.use_prism_parser?
Prism.parse(source).errors.map(&:message)
else
RipperErrors.new(source).call.errors
end
end
end
# 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 the parser is run against the input and the raw
# errors are 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 error messages
def errors
if missing.empty?
return GetParseErrors.errors(@code_lines.map(&:original).join).uniq
end
missing.map { |miss| why(miss) }
end
end
end
share/ruby/syntax_suggest/cli.rb 0000644 00000006166 15173517740 0013035 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 15173517740 0013736 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
VERSION = "2.0.1"
end
share/ruby/syntax_suggest/core_ext.rb 0000644 00000006025 15173517740 0014070 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.module_for_detailed_message [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 00000002775 15173517740 0014254 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 00000003250 15173517740 0013675 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Ripper.lex is not guaranteed to lex the entire source document
#
# This class guarantees the whole document is lex-ed by iteratively
# lexing the document where ripper stopped.
#
# Prism likely doesn't have the same problem. Once ripper support is removed
# we can likely reduce the complexity here if not remove the whole concept.
#
# Example usage:
#
# lex = LexAll.new(source: source)
# lex.each do |value|
# puts value.line
# end
class LexAll
include Enumerable
def initialize(source:, source_lines: nil)
@lex = self.class.lex(source, 1)
lineno = @lex.last[0][0] + 1
source_lines ||= source.lines
last_lineno = source_lines.length
until lineno >= last_lineno
lines = source_lines[lineno..]
@lex.concat(
self.class.lex(lines.join, lineno + 1)
)
lineno = @lex.last[0].first + 1
end
last_lex = nil
@lex.map! { |elem|
last_lex = LexValue.new(elem[0].first, elem[1], elem[2], elem[3], last_lex)
}
end
if SyntaxSuggest.use_prism_parser?
def self.lex(source, line_number)
Prism.lex_compat(source, line: line_number).value.sort_by { |values| values[0] }
end
else
def self.lex(source, line_number)
Ripper::Lexer.new(source, "-", line_number).parse.sort_by(&:pos)
end
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 15173517740 0020514 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 00000005655 15173517740 0014775 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..] || []
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 15173517740 0016706 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 15173517740 0021124 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 15173517740 0020072 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 00000015176 15173517740 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)
#
if SyntaxSuggest.use_prism_parser?
def trailing_slash?
last = @lex.last
last&.type == :on_tstring_end
end
else
def trailing_slash?
last = @lex.last
return false unless last
return false unless last.type == :on_sp
last.token == TRAILING_SLASH
end
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 00000004207 15173517740 0014344 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 the parser (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 00000001700 15173517740 0015150 0 ustar 00 # frozen_string_literal: true
module SyntaxSuggest
# Capture parse errors from Ripper
#
# Prism returns the errors with their messages, but Ripper
# does not. To get them we must make a custom subclass.
#
# 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 00000007501 15173517740 0014517 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
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 00000013163 15173517740 0015103 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
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 00000021356 15173517740 0015244 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, the parser 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..) 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..) { |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 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..].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..)
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 00000015251 15173517740 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 ambiguity 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 15173517740 0015454 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 15173517740 0015357 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/tsort.rb 0000644 00000034445 15173517740 0010353 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
VERSION = "0.2.0"
class Cyclic < StandardError
end
# Returns a topologically sorted array of nodes.
# The array is sorted from children to parents, i.e.
# the first element has no child and the last node has no parent.
#
# If there is a cycle, TSort::Cyclic is raised.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# p graph.tsort #=> [4, 2, 3, 1]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# p graph.tsort # raises TSort::Cyclic
#
def tsort
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.tsort(each_node, each_child)
end
# Returns a topologically sorted array of nodes.
# The array is sorted from children to parents, i.e.
# the first element has no child and the last node has no parent.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# If there is a cycle, TSort::Cyclic is raised.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1]
#
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.tsort(each_node, each_child) # raises TSort::Cyclic
#
def self.tsort(each_node, each_child)
tsort_each(each_node, each_child).to_a
end
# The iterator version of the #tsort method.
# <tt><em>obj</em>.tsort_each</tt> is similar to <tt><em>obj</em>.tsort.each</tt>, but
# modification of _obj_ during the iteration may lead to unexpected results.
#
# #tsort_each returns +nil+.
# If there is a cycle, TSort::Cyclic is raised.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.tsort_each {|n| p n }
# #=> 4
# # 2
# # 3
# # 1
#
def tsort_each(&block) # :yields: node
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.tsort_each(each_node, each_child, &block)
end
# The iterator version of the TSort.tsort method.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.tsort_each(each_node, each_child) {|n| p n }
# #=> 4
# # 2
# # 3
# # 1
#
def self.tsort_each(each_node, each_child) # :yields: node
return to_enum(__method__, each_node, each_child) unless block_given?
each_strongly_connected_component(each_node, each_child) {|component|
if component.size == 1
yield component.first
else
raise Cyclic.new("topological sort failed: #{component.inspect}")
end
}
end
# Returns strongly connected components as an array of arrays of nodes.
# The array is sorted from children to parents.
# Each elements of the array represents a strongly connected component.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# p graph.strongly_connected_components #=> [[4], [2], [3], [1]]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# p graph.strongly_connected_components #=> [[4], [2, 3], [1]]
#
def strongly_connected_components
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.strongly_connected_components(each_node, each_child)
end
# Returns strongly connected components as an array of arrays of nodes.
# The array is sorted from children to parents.
# Each elements of the array represents a strongly connected component.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.strongly_connected_components(each_node, each_child)
# #=> [[4], [2], [3], [1]]
#
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# p TSort.strongly_connected_components(each_node, each_child)
# #=> [[4], [2, 3], [1]]
#
def self.strongly_connected_components(each_node, each_child)
each_strongly_connected_component(each_node, each_child).to_a
end
# The iterator version of the #strongly_connected_components method.
# <tt><em>obj</em>.each_strongly_connected_component</tt> is similar to
# <tt><em>obj</em>.strongly_connected_components.each</tt>, but
# modification of _obj_ during the iteration may lead to unexpected results.
#
# #each_strongly_connected_component returns +nil+.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.each_strongly_connected_component {|scc| p scc }
# #=> [4]
# # [2]
# # [3]
# # [1]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# graph.each_strongly_connected_component {|scc| p scc }
# #=> [4]
# # [2, 3]
# # [1]
#
def each_strongly_connected_component(&block) # :yields: nodes
each_node = method(:tsort_each_node)
each_child = method(:tsort_each_child)
TSort.each_strongly_connected_component(each_node, each_child, &block)
end
# The iterator version of the TSort.strongly_connected_components method.
#
# The graph is represented by _each_node_ and _each_child_.
# _each_node_ should have +call+ method which yields for each node in the graph.
# _each_child_ should have +call+ method which takes a node argument and yields for each child node.
#
# g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
# #=> [4]
# # [2]
# # [3]
# # [1]
#
# g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_node = lambda {|&b| g.each_key(&b) }
# each_child = lambda {|n, &b| g[n].each(&b) }
# TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc }
# #=> [4]
# # [2, 3]
# # [1]
#
def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes
return to_enum(__method__, each_node, each_child) unless block_given?
id_map = {}
stack = []
each_node.call {|node|
unless id_map.include? node
each_strongly_connected_component_from(node, each_child, id_map, stack) {|c|
yield c
}
end
}
nil
end
# Iterates over strongly connected component in the subgraph reachable from
# _node_.
#
# Return value is unspecified.
#
# #each_strongly_connected_component_from doesn't call #tsort_each_node.
#
# class G
# include TSort
# def initialize(g)
# @g = g
# end
# def tsort_each_child(n, &b) @g[n].each(&b) end
# def tsort_each_node(&b) @g.each_key(&b) end
# end
#
# graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]})
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
# #=> [4]
# # [2]
#
# graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]})
# graph.each_strongly_connected_component_from(2) {|scc| p scc }
# #=> [4]
# # [2, 3]
#
def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes
TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block)
end
# Iterates over strongly connected components in a graph.
# The graph is represented by _node_ and _each_child_.
#
# _node_ is the first node.
# _each_child_ should have +call+ method which takes a node argument
# and yields for each child node.
#
# Return value is unspecified.
#
# #TSort.each_strongly_connected_component_from is a class method and
# it doesn't need a class to represent a graph which includes TSort.
#
# graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}
# each_child = lambda {|n, &b| graph[n].each(&b) }
# TSort.each_strongly_connected_component_from(1, each_child) {|scc|
# p scc
# }
# #=> [4]
# # [2, 3]
# # [1]
#
def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes
return to_enum(__method__, node, each_child, id_map, stack) unless block_given?
minimum_id = node_id = id_map[node] = id_map.size
stack_length = stack.length
stack << node
each_child.call(node) {|child|
if id_map.include? child
child_id = id_map[child]
minimum_id = child_id if child_id && child_id < minimum_id
else
sub_minimum_id =
each_strongly_connected_component_from(child, each_child, id_map, stack) {|c|
yield c
}
minimum_id = sub_minimum_id if sub_minimum_id < minimum_id
end
}
if node_id == minimum_id
component = stack.slice!(stack_length .. -1)
component.each {|n| id_map[n] = nil}
yield component
end
minimum_id
end
# Should be implemented by a extended class.
#
# #tsort_each_node is used to iterate for all nodes over a graph.
#
def tsort_each_node # :yields: node
raise NotImplementedError.new
end
# Should be implemented by a extended class.
#
# #tsort_each_child is used to iterate for child nodes of _node_.
#
def tsort_each_child(node) # :yields: child
raise NotImplementedError.new
end
end
share/ruby/resolv.rb 0000644 00000252033 15173517740 0010505 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
VERSION = "0.3.0"
##
# 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=nil, use_ipv6: nil)
@resolvers = resolvers || [Hosts.new, DNS.new(DNS::Config.default_config_hash.merge(use_ipv6: use_ipv6))]
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].concat(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.
# :raise_timeout_errors can be used to raise timeout errors
# as exceptions instead of treating the same as an NXDOMAIN response.
#
# 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:
use_ipv6 = @config.use_ipv6?
unless use_ipv6.nil?
return use_ipv6
end
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, Errno::EPROTONOSUPPORT
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.concat(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 = []
@use_ipv6 = nil
@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
if config_hash.include? :use_ipv6
@use_ipv6 = config_hash[:use_ipv6]
end
@search = config_hash[:search] if config_hash.include? :search
@ndots = config_hash[:ndots] if config_hash.include? :ndots
@raise_timeout_errors = config_hash[:raise_timeout_errors]
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 use_ipv6?
@use_ipv6
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
timeout_error = false
begin
candidates.each {|candidate|
begin
timeouts.each {|tout|
@nameserver_port.each {|nameserver, port|
begin
yield candidate, tout, nameserver, port
rescue ResolvTimeout
end
}
}
timeout_error = true
raise ResolvError.new("DNS resolv timeout: #{name}")
rescue NXDomain
end
}
rescue ResolvError
raise if @raise_timeout_errors && timeout_error
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, compress: true)
put_labels(d.to_a, compress: compress)
end
def put_labels(d, compress: true)
d.each_index {|i|
domain = d[i..-1]
if compress && 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.tc = (flag >> 9) & 1
o.rcode = flag & 15
return o unless o.tc.zero?
o.qr = (flag >> 15) & 1
o.opcode = (flag >> 11) & 15
o.aa = (flag >> 10) & 1
o.rd = (flag >> 8) & 1
o.ra = (flag >> 7) & 1
(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_list
[].tap do |values|
while @index < @limit
values << yield
end
end
end
def get_name
return Name.new(self.get_labels)
end
def get_labels
prev_index = @index
save_index = nil
d = []
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
d << self.get_label
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
##
# SvcParams for service binding RRs. [RFC9460]
class SvcParams
include Enumerable
##
# Create a list of SvcParams with the given initial content.
#
# +params+ has to be an enumerable of +SvcParam+s.
# If its content has +SvcParam+s with the duplicate key,
# the one appears last takes precedence.
def initialize(params = [])
@params = {}
params.each do |param|
add param
end
end
##
# Get SvcParam for the given +key+ in this list.
def [](key)
@params[canonical_key(key)]
end
##
# Get the number of SvcParams in this list.
def count
@params.count
end
##
# Get whether this list is empty.
def empty?
@params.empty?
end
##
# Add the SvcParam +param+ to this list, overwriting the existing one with the same key.
def add(param)
@params[param.class.key_number] = param
end
##
# Remove the +SvcParam+ with the given +key+ and return it.
def delete(key)
@params.delete(canonical_key(key))
end
##
# Enumerate the +SvcParam+s in this list.
def each(&block)
return enum_for(:each) unless block
@params.each_value(&block)
end
def encode(msg) # :nodoc:
@params.keys.sort.each do |key|
msg.put_pack('n', key)
msg.put_length16 do
@params.fetch(key).encode(msg)
end
end
end
def self.decode(msg) # :nodoc:
params = msg.get_list do
key, = msg.get_unpack('n')
msg.get_length16 do
SvcParam::ClassHash[key].decode(msg)
end
end
return self.new(params)
end
private
def canonical_key(key) # :nodoc:
case key
when Integer
key
when /\Akey(\d+)\z/
Integer($1)
when Symbol
SvcParam::ClassHash[key].key_number
else
raise TypeError, 'key must be either String or Symbol'
end
end
end
##
# Base class for SvcParam. [RFC9460]
class SvcParam
##
# Get the presentation name of the SvcParamKey.
def self.key_name
const_get(:KeyName)
end
##
# Get the registered number of the SvcParamKey.
def self.key_number
const_get(:KeyNumber)
end
ClassHash = Hash.new do |h, key| # :nodoc:
case key
when Integer
Generic.create(key)
when /\Akey(?<key>\d+)\z/
Generic.create(key.to_int)
when Symbol
raise KeyError, "unknown key #{key}"
else
raise TypeError, 'key must be either String or Symbol'
end
end
##
# Generic SvcParam abstract class.
class Generic < SvcParam
##
# SvcParamValue in wire-format byte string.
attr_reader :value
##
# Create generic SvcParam
def initialize(value)
@value = value
end
def encode(msg) # :nodoc:
msg.put_bytes(@value)
end
def self.decode(msg) # :nodoc:
return self.new(msg.get_bytes)
end
def self.create(key_number)
c = Class.new(Generic)
key_name = :"key#{key_number}"
c.const_set(:KeyName, key_name)
c.const_set(:KeyNumber, key_number)
self.const_set(:"Key#{key_number}", c)
ClassHash[key_name] = ClassHash[key_number] = c
return c
end
end
##
# "mandatory" SvcParam -- Mandatory keys in service binding RR
class Mandatory < SvcParam
KeyName = :mandatory
KeyNumber = 0
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# Mandatory keys.
attr_reader :keys
##
# Initialize "mandatory" ScvParam.
def initialize(keys)
@keys = keys.map(&:to_int)
end
def encode(msg) # :nodoc:
@keys.sort.each do |key|
msg.put_pack('n', key)
end
end
def self.decode(msg) # :nodoc:
keys = msg.get_list { msg.get_unpack('n')[0] }
return self.new(keys)
end
end
##
# "alpn" SvcParam -- Additional supported protocols
class ALPN < SvcParam
KeyName = :alpn
KeyNumber = 1
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# Supported protocol IDs.
attr_reader :protocol_ids
##
# Initialize "alpn" ScvParam.
def initialize(protocol_ids)
@protocol_ids = protocol_ids.map(&:to_str)
end
def encode(msg) # :nodoc:
msg.put_string_list(@protocol_ids)
end
def self.decode(msg) # :nodoc:
return self.new(msg.get_string_list)
end
end
##
# "no-default-alpn" SvcParam -- No support for default protocol
class NoDefaultALPN < SvcParam
KeyName = :'no-default-alpn'
KeyNumber = 2
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
def encode(msg) # :nodoc:
# no payload
end
def self.decode(msg) # :nodoc:
return self.new
end
end
##
# "port" SvcParam -- Port for alternative endpoint
class Port < SvcParam
KeyName = :port
KeyNumber = 3
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# Port number.
attr_reader :port
##
# Initialize "port" ScvParam.
def initialize(port)
@port = port.to_int
end
def encode(msg) # :nodoc:
msg.put_pack('n', @port)
end
def self.decode(msg) # :nodoc:
port, = msg.get_unpack('n')
return self.new(port)
end
end
##
# "ipv4hint" SvcParam -- IPv4 address hints
class IPv4Hint < SvcParam
KeyName = :ipv4hint
KeyNumber = 4
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# Set of IPv4 addresses.
attr_reader :addresses
##
# Initialize "ipv4hint" ScvParam.
def initialize(addresses)
@addresses = addresses.map {|address| IPv4.create(address) }
end
def encode(msg) # :nodoc:
@addresses.each do |address|
msg.put_bytes(address.address)
end
end
def self.decode(msg) # :nodoc:
addresses = msg.get_list { IPv4.new(msg.get_bytes(4)) }
return self.new(addresses)
end
end
##
# "ipv6hint" SvcParam -- IPv6 address hints
class IPv6Hint < SvcParam
KeyName = :ipv6hint
KeyNumber = 6
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# Set of IPv6 addresses.
attr_reader :addresses
##
# Initialize "ipv6hint" ScvParam.
def initialize(addresses)
@addresses = addresses.map {|address| IPv6.create(address) }
end
def encode(msg) # :nodoc:
@addresses.each do |address|
msg.put_bytes(address.address)
end
end
def self.decode(msg) # :nodoc:
addresses = msg.get_list { IPv6.new(msg.get_bytes(16)) }
return self.new(addresses)
end
end
##
# "dohpath" SvcParam -- DNS over HTTPS path template [RFC9461]
class DoHPath < SvcParam
KeyName = :dohpath
KeyNumber = 7
ClassHash[KeyName] = ClassHash[KeyNumber] = self # :nodoc:
##
# URI template for DoH queries.
attr_reader :template
##
# Initialize "dohpath" ScvParam.
def initialize(template)
@template = template.encode('utf-8')
end
def encode(msg) # :nodoc:
msg.put_bytes(@template)
end
def self.decode(msg) # :nodoc:
template = msg.get_bytes.force_encoding('utf-8')
return self.new(template)
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, compress: false)
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
##
# Common implementation for SVCB-compatible resource records.
class ServiceBinding
##
# Create a service binding resource record.
def initialize(priority, target, params = [])
@priority = priority.to_int
@target = Name.create(target)
@params = SvcParams.new(params)
end
##
# The priority of this target host.
#
# The range is 0-65535.
# If set to 0, this RR is in AliasMode. Otherwise, it is in ServiceMode.
attr_reader :priority
##
# The domain name of the target host.
attr_reader :target
##
# The service paramters for the target host.
attr_reader :params
##
# Whether this RR is in AliasMode.
def alias_mode?
self.priority == 0
end
##
# Whether this RR is in ServiceMode.
def service_mode?
!alias_mode?
end
def encode_rdata(msg) # :nodoc:
msg.put_pack("n", @priority)
msg.put_name(@target, compress: false)
@params.encode(msg)
end
def self.decode_rdata(msg) # :nodoc:
priority, = msg.get_unpack("n")
target = msg.get_name
params = SvcParams.decode(msg)
return self.new(priority, target, params)
end
end
##
# SVCB resource record [RFC9460]
class SVCB < ServiceBinding
TypeValue = 64
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
end
##
# HTTPS resource record [RFC9460]
class HTTPS < ServiceBinding
TypeValue = 65
ClassValue = IN::ClassValue
ClassHash[[TypeValue, ClassValue]] = self # :nodoc:
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:
sprintf("%x:%x:%x:%x:%x:%x:%x:%x", *@address.unpack("nnnnnnnn")).sub(/(^|:)0(:0)+(:|$)/, '::')
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 15173517740 0010103 0 ustar 00 # frozen_string_literal: true
# date.rb: Written by Tadayoshi Funaba 1998-2011
require 'date_core'
class Date
VERSION = "3.3.4" # :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 00000001556 15173517740 0012326 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
LEVELS = {
"debug" => DEBUG,
"info" => INFO,
"warn" => WARN,
"error" => ERROR,
"fatal" => FATAL,
"unknown" => UNKNOWN,
}
private_constant :LEVELS
def self.coerce(severity)
if severity.is_a?(Integer)
severity
else
key = severity.to_s.downcase
LEVELS[key] || raise(ArgumentError, "invalid log level: #{severity}")
end
end
end
end
share/ruby/logger/log_device.rb 0000644 00000013216 15173517740 0012550 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 15173517740 0012125 0 ustar 00 # frozen_string_literal: true
class Logger
VERSION = "1.6.0"
end
share/ruby/logger/formatter.rb 0000644 00000001426 15173517740 0012453 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 15173517740 0011735 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 15173517740 0011765 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 00000130055 15173517740 0010462 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 nil.
# In most cases it means the connection was closed, but for UDP connections
# it may mean an empty packet was received, as the underlying API makes
# it impossible to distinguish these two cases.
#
# === 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#recv_nonblock returns nil.
# In most cases it means the connection was closed, but for UDP connections
# it may mean an empty packet was received, as the underlying API makes
# it impossible to distinguish these two cases.
#
# === 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#recv_nonblock returns nil.
# In most cases it means the connection was closed, but it may also mean
# an empty packet was received, as the underlying API makes
# it impossible to distinguish these two cases.
#
# === 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 0013304 0 ustar 00 module DidYouMean
VERSION = "1.6.3".freeze
end
share/ruby/did_you_mean/jaro_winkler.rb 0000644 00000003451 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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 15173517740 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.7.1/lib/io 0000755 00000000000 15173517740 0013347 0 ustar 00 share/ruby/tempfile.rb 0000644 00000035347 15173517740 0011007 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.create('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)
VERSION = "0.2.1"
# 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
@finalizer_obj = Object.new
tmpfile = nil
::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(@finalizer_obj, Remover.new(tmpfile.path))
ObjectSpace.define_finalizer(self, Closer.new(tmpfile))
super(tmpfile)
end
def initialize_dup(other)
initialize_copy_iv(other)
super(other)
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
end
def initialize_clone(other)
initialize_copy_iv(other)
super(other)
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
end
private def initialize_copy_iv(other)
@unlinked = other.unlinked
@mode = other.mode
@opts = other.opts
@finalizer_obj = other.finalizer_obj
end
# Opens or reopens the file with mode "r+".
def open
_close
ObjectSpace.undefine_finalizer(self)
mode = @mode & ~(File::CREAT|File::EXCL)
__setobj__(File.open(__getobj__.path, mode, **@opts))
ObjectSpace.define_finalizer(self, Closer.new(__getobj__))
__getobj__
end
def _close # :nodoc:
__getobj__.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(__getobj__.path)
rescue Errno::ENOENT
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
return
end
ObjectSpace.undefine_finalizer(@finalizer_obj)
@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 : __getobj__.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 !__getobj__.closed?
__getobj__.size # File#size calls rb_io_flush_raw()
else
File.size(__getobj__.path)
end
end
alias length size
# :stopdoc:
def inspect
if __getobj__.closed?
"#<#{self.class}:#{path} (closed)>"
else
"#<#{self.class}:#{path}>"
end
end
alias to_s inspect
protected
attr_reader :unlinked, :mode, :opts, :finalizer_obj
class Closer # :nodoc:
def initialize(tmpfile)
@tmpfile = tmpfile
end
def call(*args)
@tmpfile.close
end
end
class Remover # :nodoc:
def initialize(path)
@pid = Process.pid
@path = path
end
def call(*args)
return if @pid != Process.pid
$stderr.puts "removing #{@path}..." if $DEBUG
begin
File.unlink(@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 00000034335 15173517740 0010701 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.6.0"
HAS_PERFORMANCE_WARNINGS = begin
Warning[:performance]
true
rescue NoMethodError, ArgumentError
false
end
private_constant :HAS_PERFORMANCE_WARNINGS
#
# Creates a new OpenStruct object. By default, the resulting OpenStruct
# object will have no attributes.
#
# The optional +hash+, if given, will generate attributes and values
# (can be a Hash, an OpenStruct or a Struct).
# For example:
#
# require "ostruct"
# hash = { "country" => "Australia", :capital => "Canberra" }
# data = OpenStruct.new(hash)
#
# data # => #<OpenStruct country="Australia", capital="Canberra">
#
def initialize(hash=nil)
if HAS_PERFORMANCE_WARNINGS && Warning[:performance]
warn "OpenStruct use is discouraged for performance reasons", uplevel: 1, category: :performance
end
if hash
update_to_values!(hash)
else
@table = {}
end
end
# Duplicates an OpenStruct object's Hash table.
private def initialize_clone(orig) # :nodoc:
super # clones the singleton class for us
@table = @table.dup unless @table.frozen?
end
private def initialize_dup(orig) # :nodoc:
super
update_to_values!(@table)
end
private def update_to_values!(hash) # :nodoc:
@table = {}
hash.each_pair do |k, v|
set_ostruct_member_value!(k, v)
end
end
#
# call-seq:
# ostruct.to_h -> hash
# ostruct.to_h {|name, value| block } -> hash
#
# Converts the OpenStruct to a hash with keys representing
# each attribute (as symbols) and their corresponding values.
#
# If a block is given, the results of the block on each pair of
# the receiver will be used as pairs.
#
# require "ostruct"
# data = OpenStruct.new("country" => "Australia", :capital => "Canberra")
# data.to_h # => {:country => "Australia", :capital => "Canberra" }
# data.to_h {|name, value| [name.to_s, value.upcase] }
# # => {"country" => "AUSTRALIA", "capital" => "CANBERRA" }
#
if {test: :to_h}.to_h{ [:works, true] }[:works] # RUBY_VERSION < 2.6 compatibility
def to_h(&block)
if block
@table.to_h(&block)
else
@table.dup
end
end
else
def to_h(&block)
if block
@table.map(&block).to_h
else
@table.dup
end
end
end
#
# :call-seq:
# ostruct.each_pair {|name, value| block } -> ostruct
# ostruct.each_pair -> Enumerator
#
# Yields all attributes (as symbols) along with the corresponding values
# or returns an enumerator if no block is given.
#
# require "ostruct"
# data = OpenStruct.new("country" => "Australia", :capital => "Canberra")
# data.each_pair.to_a # => [[:country, "Australia"], [:capital, "Canberra"]]
#
def each_pair
return to_enum(__method__) { @table.size } unless defined?(yield)
@table.each_pair{|p| yield p}
self
end
#
# Provides marshalling support for use by the Marshal library.
#
def marshal_dump # :nodoc:
@table
end
#
# Provides marshalling support for use by the Marshal library.
#
alias_method :marshal_load, :update_to_values! # :nodoc:
#
# Used internally to defined properties on the
# OpenStruct. It does this by using the metaprogramming function
# define_singleton_method for both the getter method and the setter method.
#
def new_ostruct_member!(name) # :nodoc:
unless @table.key?(name) || is_method_protected!(name)
if defined?(::Ractor)
getter_proc = nil.instance_eval{ Proc.new { @table[name] } }
setter_proc = nil.instance_eval{ Proc.new {|x| @table[name] = x} }
::Ractor.make_shareable(getter_proc)
::Ractor.make_shareable(setter_proc)
else
getter_proc = Proc.new { @table[name] }
setter_proc = Proc.new {|x| @table[name] = x}
end
define_singleton_method!(name, &getter_proc)
define_singleton_method!("#{name}=", &setter_proc)
end
end
private :new_ostruct_member!
private def is_method_protected!(name) # :nodoc:
if !respond_to?(name, true)
false
elsif name.match?(/!$/)
true
else
owner = method!(name).owner
if owner.class == ::Class
owner < ::OpenStruct
else
self.class!.ancestors.any? do |mod|
return false if mod == ::OpenStruct
mod == owner
end
end
end
end
def freeze
@table.freeze
super
end
private def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise! ArgumentError, "wrong number of arguments (given #{len}, expected 1)", caller(1)
end
set_ostruct_member_value!(mname, args[0])
elsif len == 0
@table[mid]
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise!
end
end
end
#
# :call-seq:
# ostruct[name] -> object
#
# Returns the value of an attribute, or +nil+ if there is no such attribute.
#
# require "ostruct"
# person = OpenStruct.new("name" => "John Smith", "age" => 70)
# person[:age] # => 70, same as person.age
#
def [](name)
@table[name.to_sym]
end
#
# :call-seq:
# ostruct[name] = obj -> obj
#
# Sets the value of an attribute.
#
# require "ostruct"
# person = OpenStruct.new("name" => "John Smith", "age" => 70)
# person[:age] = 42 # equivalent to person.age = 42
# person.age # => 42
#
def []=(name, value)
name = name.to_sym
new_ostruct_member!(name)
@table[name] = value
end
alias_method :set_ostruct_member_value!, :[]=
private :set_ostruct_member_value!
# :call-seq:
# ostruct.dig(name, *identifiers) -> object
#
# Finds and returns the object in nested objects
# that is specified by +name+ and +identifiers+.
# The nested objects may be instances of various classes.
# See {Dig Methods}[rdoc-ref:dig_methods.rdoc].
#
# Examples:
# require "ostruct"
# address = OpenStruct.new("city" => "Anytown NC", "zip" => 12345)
# person = OpenStruct.new("name" => "John Smith", "address" => address)
# person.dig(:address, "zip") # => 12345
# person.dig(:business_address, "zip") # => nil
def dig(name, *names)
begin
name = name.to_sym
rescue NoMethodError
raise! TypeError, "#{name} is not a symbol nor a string"
end
@table.dig(name, *names)
end
#
# Removes the named field from the object and returns the value the field
# contained if it was defined. You may optionally provide a block.
# If the field is not defined, the result of the block is returned,
# or a NameError is raised if no block was given.
#
# require "ostruct"
#
# person = OpenStruct.new(name: "John", age: 70, pension: 300)
#
# person.delete_field!("age") # => 70
# person # => #<OpenStruct name="John", pension=300>
#
# Setting the value to +nil+ will not remove the attribute:
#
# person.pension = nil
# person # => #<OpenStruct name="John", pension=nil>
#
# person.delete_field('number') # => NameError
#
# person.delete_field('number') { 8675_309 } # => 8675309
#
def delete_field(name, &block)
sym = name.to_sym
begin
singleton_class.remove_method(sym, "#{sym}=")
rescue NameError
end
@table.delete(sym) do
return yield if block
raise! NameError.new("no field `#{sym}' in #{self}", sym)
end
end
InspectKey = :__inspect_key__ # :nodoc:
#
# Returns a string containing a detailed summary of the keys and values.
#
def inspect
ids = (Thread.current[InspectKey] ||= [])
if ids.include?(object_id)
detail = ' ...'
else
ids << object_id
begin
detail = @table.map do |key, value|
" #{key}=#{value.inspect}"
end.join(',')
ensure
ids.pop
end
end
['#<', self.class!, detail, '>'].join
end
alias :to_s :inspect
attr_reader :table # :nodoc:
alias table! table
protected :table!
#
# Compares this object and +other+ for equality. An OpenStruct is equal to
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
# equal.
#
# require "ostruct"
# first_pet = OpenStruct.new("name" => "Rowdy")
# second_pet = OpenStruct.new(:name => "Rowdy")
# third_pet = OpenStruct.new("name" => "Rowdy", :age => nil)
#
# first_pet == second_pet # => true
# first_pet == third_pet # => false
#
def ==(other)
return false unless other.kind_of?(OpenStruct)
@table == other.table!
end
#
# Compares this object and +other+ for equality. An OpenStruct is eql? to
# +other+ when +other+ is an OpenStruct and the two objects' Hash tables are
# eql?.
#
def eql?(other)
return false unless other.kind_of?(OpenStruct)
@table.eql?(other.table!)
end
# Computes a hash code for this OpenStruct.
def hash # :nodoc:
@table.hash
end
#
# Provides marshalling support for use by the YAML library.
#
def encode_with(coder) # :nodoc:
@table.each_pair do |key, value|
coder[key.to_s] = value
end
if @table.size == 1 && @table.key?(:table) # support for legacy format
# in the very unlikely case of a single entry called 'table'
coder['legacy_support!'] = true # add a bogus second entry
end
end
#
# Provides marshalling support for use by the YAML library.
#
def init_with(coder) # :nodoc:
h = coder.map
if h.size == 1 # support for legacy format
key, val = h.first
if key == 'table'
h = val
end
end
update_to_values!(h)
end
# Make all public methods (builtin or our own) accessible with <code>!</code>:
give_access = instance_methods
# See https://github.com/ruby/ostruct/issues/30
give_access -= %i[instance_exec instance_eval eval] if RUBY_ENGINE == 'jruby'
give_access.each do |method|
next if method.match(/\W$/)
new_name = "#{method}!"
alias_method new_name, method
end
# Other builtin private methods we use:
alias_method :raise!, :raise
private :raise!
# See https://github.com/ruby/ostruct/issues/40
if RUBY_ENGINE != 'jruby'
alias_method :block_given!, :block_given?
private :block_given!
end
end
share/doc/alt-ruby33-libs/NEWS.md 0000644 00000051271 15173517740 0012365 0 ustar 00 # NEWS for Ruby 3.3.0
This document is a list of user-visible feature changes
since the **3.2.0** release, except for bug fixes.
Note that each entry is kept to a minimum, see links for details.
## Command line options
* A new `performance` warning category was introduced.
They are not displayed by default even in verbose mode.
Turn them on with `-W:performance` or `Warning[:performance] = true`. [[Feature #19538]]
* A new `RUBY_CRASH_REPORT` environment variable was introduced to allow
redirecting Ruby crash reports to a file or sub command. See the `BUG REPORT ENVIRONMENT`
section of the ruby manpage for further details. [[Feature #19790]]
## Core classes updates
Note: We're only listing outstanding class updates.
* Array
* Array#pack now raises ArgumentError for unknown directives. [[Bug #19150]]
* Dir
* Dir.for_fd added for returning a Dir object for the directory specified
by the provided directory file descriptor. [[Feature #19347]]
* Dir.fchdir added for changing the directory to the directory specified
by the provided directory file descriptor. [[Feature #19347]]
* Dir#chdir added for changing the directory to the directory specified by
the provided `Dir` object. [[Feature #19347]]
* Encoding
* `Encoding#replicate` has been removed, it was already deprecated. [[Feature #18949]]
* Fiber
* Introduce Fiber#kill. [[Bug #595]]
```ruby
fiber = Fiber.new do
while true
puts "Yielding..."
Fiber.yield
end
ensure
puts "Exiting..."
end
fiber.resume
# Yielding...
fiber.kill
# Exiting...
```
* MatchData
* MatchData#named_captures now accepts optional `symbolize_names`
keyword. [[Feature #19591]]
* Module
* Module#set_temporary_name added for setting a temporary name for a
module. [[Feature #19521]]
* ObjectSpace::WeakKeyMap
* New core class to build collections with weak references.
The class use equality semantic to lookup keys like a regular hash,
but it doesn't hold strong references on the keys. [[Feature #18498]]
* ObjectSpace::WeakMap
* ObjectSpace::WeakMap#delete was added to eagerly clear weak map
entries. [[Feature #19561]]
* Proc
* Now Proc#dup and Proc#clone call `#initialize_dup` and `#initialize_clone`
hooks respectively. [[Feature #19362]]
* Process
* New Process.warmup method that notify the Ruby virtual machine that the boot sequence is finished,
and that now is a good time to optimize the application. This is useful
for long-running applications. The actual optimizations performed are entirely
implementation-specific and may change in the future without notice. [[Feature #18885]]
* Process::Status
* Process::Status#& and Process::Status#>> are deprecated. [[Bug #19868]]
* Range
* Range#reverse_each can now process beginless ranges with an Integer endpoint. [[Feature #18515]]
* Range#reverse_each now raises TypeError for endless ranges. [[Feature #18551]]
* Range#overlap? added for checking if two ranges overlap. [[Feature #19839]]
* Refinement
* Add Refinement#target as an alternative of Refinement#refined_class.
Refinement#refined_class is deprecated and will be removed in Ruby
3.4. [[Feature #19714]]
* Regexp
* The cache-based optimization now supports lookarounds and atomic groupings. That is, match
for Regexp containing these extensions can now also be performed in linear time to the length
of the input string. However, these cannot contain captures and cannot be nested. [[Feature #19725]]
* String
* String#unpack now raises ArgumentError for unknown directives. [[Bug #19150]]
* String#bytesplice now accepts new arguments index/length or range of the
source string to be copied. [[Feature #19314]]
* Thread::Queue
* Thread::Queue#freeze now raises TypeError. [[Bug #17146]]
* Thread::SizedQueue
* Thread::SizedQueue#freeze now raises TypeError. [[Bug #17146]]
* Time
* Time.new with a string argument became stricter. [[Bug #19293]]
```ruby
Time.new('2023-12-20')
# no time information (ArgumentError)
```
* TracePoint
* TracePoint supports `rescue` event. When the raised exception was rescued,
the TracePoint will fire the hook. `rescue` event only supports Ruby-level
`rescue`. [[Feature #19572]]
## Stdlib updates
* RubyGems and Bundler warn if users do `require` the following gems without adding them to Gemfile or gemspec.
This is because they will become the bundled gems in the future version of Ruby. This warning is suppressed
if you use bootsnap gem. We recommend to run your application with `DISABLE_BOOTSNAP=1` environmental variable
at least once. This is limitation of this version.
[[Feature #19351]] [[Feature #19776]] [[Feature #19843]]
* abbrev
* base64
* bigdecimal
* csv
* drb
* getoptlong
* mutex_m
* nkf
* observer
* racc
* resolv-replace
* rinda
* syslog
* Socket#recv and Socket#recv_nonblock returns `nil` instead of an empty string on closed
connections. Socket#recvmsg and Socket#recvmsg_nonblock returns `nil` instead of an empty packet on closed
connections. [[Bug #19012]]
* Name resolution such as Socket.getaddrinfo, Socket.getnameinfo, Addrinfo.getaddrinfo, etc.
can now be interrupted. [[Feature #19965]]
* Random::Formatter#alphanumeric is extended to accept optional `chars`
keyword argument. [[Feature #18183]]
The following default gem is added.
* prism 0.19.0
The following default gems are updated.
* RubyGems 3.5.3
* abbrev 0.1.2
* base64 0.2.0
* benchmark 0.3.0
* bigdecimal 3.1.5
* bundler 2.5.3
* cgi 0.4.1
* csv 3.2.8
* date 3.3.4
* delegate 0.3.1
* drb 2.2.0
* english 0.8.0
* erb 4.0.3
* error_highlight 0.6.0
* etc 1.4.3
* fcntl 1.1.0
* fiddle 1.1.2
* fileutils 1.7.2
* find 0.2.0
* getoptlong 0.2.1
* io-console 0.7.1
* io-nonblock 0.3.0
* io-wait 0.3.1
* ipaddr 1.2.6
* irb 1.11.0
* json 2.7.1
* logger 1.6.0
* mutex_m 0.2.0
* net-http 0.4.0
* net-protocol 0.2.2
* nkf 0.1.3
* observer 0.1.2
* open-uri 0.4.1
* open3 0.2.1
* openssl 3.2.0
* optparse 0.4.0
* ostruct 0.6.0
* pathname 0.3.0
* pp 0.5.0
* prettyprint 0.2.0
* pstore 0.1.3
* psych 5.1.2
* rdoc 6.6.2
* readline 0.0.4
* reline 0.4.1
* resolv 0.3.0
* rinda 0.2.0
* securerandom 0.3.1
* set 1.1.0
* shellwords 0.2.0
* singleton 0.2.0
* stringio 3.1.0
* strscan 3.0.7
* syntax_suggest 2.0.0
* syslog 0.1.2
* tempfile 0.2.1
* time 0.3.0
* timeout 0.4.1
* tmpdir 0.2.0
* tsort 0.2.0
* un 0.3.0
* uri 0.13.0
* weakref 0.1.3
* win32ole 1.8.10
* yaml 0.3.0
* zlib 3.1.0
The following bundled gem is promoted from default gems.
* racc 1.7.3
The following bundled gems are updated.
* minitest 5.20.0
* rake 13.1.0
* test-unit 3.6.1
* rexml 3.2.6
* rss 0.3.0
* net-ftp 0.3.3
* net-imap 0.4.9
* net-smtp 0.4.0
* rbs 3.4.0
* typeprof 0.21.9
* debug 1.9.1
See GitHub releases like [Logger](https://github.com/ruby/logger/releases) or
changelog for details of the default gems or bundled gems.
### Prism
* Introduced [the Prism parser](https://github.com/ruby/prism) as a default gem
* Prism is a portable, error tolerant, and maintainable recursive descent parser for the Ruby language
* Prism is production ready and actively maintained, you can use it in place of Ripper
* There is [extensive documentation](https://ruby.github.io/prism/) on how to use Prism
* Prism is both a C library that will be used internally by CRuby and a Ruby gem that can be used by any tooling which needs to parse Ruby code
* Notable methods in the Prism API are:
* `Prism.parse(source)` which returns the AST as part of a parse result object
* `Prism.parse_comments(source)` which returns the comments
* `Prism.parse_success?(source)` which returns true if there are no errors
* You can make pull requests or issues directly on [the Prism repository](https://github.com/ruby/prism) if you are interested in contributing
* You can now use `ruby --parser=prism` or `RUBYOPT="--parser=prism"` to experiment with the Prism compiler. Please note that this flag is for debugging only.
## Compatibility issues
* Subprocess creation/forking via the following file open methods is deprecated. [[Feature #19630]]
* Kernel#open
* URI.open
* IO.binread
* IO.foreach
* IO.readlines
* IO.read
* IO.write
* When given a non-lambda, non-literal block, Kernel#lambda with now raises
ArgumentError instead of returning it unmodified. These usages have been
issuing warnings under the `Warning[:deprecated]` category since Ruby 3.0.0.
[[Feature #19777]]
* The `RUBY_GC_HEAP_INIT_SLOTS` environment variable has been deprecated and
removed. Environment variables `RUBY_GC_HEAP_%d_INIT_SLOTS` should be
used instead. [[Feature #19785]]
* `it` calls without arguments in a block with no ordinary parameters are
deprecated. `it` will be a reference to the first block parameter in Ruby 3.4.
[[Feature #18980]]
* Error message for NoMethodError have changed to not use the target object's `#inspect`
for efficiency, and says "instance of ClassName" instead. [[Feature #18285]]
```ruby
([1] * 100).nonexisting
# undefined method `nonexisting' for an instance of Array (NoMethodError)
```
* Now anonymous parameters forwarding is disallowed inside a block
that uses anonymous parameters. [[Feature #19370]]
## Stdlib compatibility issues
* `racc` is promoted to bundled gems.
* You need to add `racc` to your `Gemfile` if you use `racc` under bundler environment.
* `ext/readline` is retired
* We have `reline` that is pure Ruby implementation compatible with `ext/readline` API.
We rely on `reline` in the future. If you need to use `ext/readline`, you can install
`ext/readline` via rubygems.org with `gem install readline-ext`.
* We no longer need to install libraries like `libreadline` or `libedit`.
## C API updates
* `rb_postponed_job` updates
* New APIs and deprecated APIs (see comments for details)
* added: `rb_postponed_job_preregister()`
* added: `rb_postponed_job_trigger()`
* deprecated: `rb_postponed_job_register()` (and semantic change. see below)
* deprecated: `rb_postponed_job_register_one()`
* The postponed job APIs have been changed to address some rare crashes.
To solve the issue, we introduced new two APIs and deprecated current APIs.
The semantics of these functions have also changed slightly; `rb_postponed_job_register`
now behaves like the `once` variant in that multiple calls with the same
`func` might be coalesced into a single execution of the `func`
[[Feature #20057]]
* Some updates for internal thread event hook APIs
* `rb_internal_thread_event_data_t` with a target Ruby thread (VALUE)
and callback functions (`rb_internal_thread_event_callback`) receive it.
https://github.com/ruby/ruby/pull/8885
* The following functions are introduced to manipulate Ruby thread local data
from internal thread event hook APIs (they are introduced since Ruby 3.2).
https://github.com/ruby/ruby/pull/8936
* `rb_internal_thread_specific_key_create()`
* `rb_internal_thread_specific_get()`
* `rb_internal_thread_specific_set()`
* `rb_profile_thread_frames()` is introduced to get a frames from
a specific thread.
[[Feature #10602]]
* `rb_data_define()` is introduced to define `Data`. [[Feature #19757]]
* `rb_ext_resolve_symbol()` is introduced to search a function from
extension libraries. [[Feature #20005]]
* IO related updates:
* The details of `rb_io_t` will be hidden and deprecated attributes
are added for each members. [[Feature #19057]]
* `rb_io_path(VALUE io)` is introduced to get a path of `io`.
* `rb_io_closed_p(VALUE io)` to get opening or closing of `io`.
* `rb_io_mode(VALUE io)` to get the mode of `io`.
* `rb_io_open_descriptor()` is introduced to make an IO object from a file
descriptor.
## Implementation improvements
### Parser
* Replace Bison with [Lrama LALR parser generator](https://github.com/ruby/lrama).
No need to install Bison to build Ruby from source code anymore.
We will no longer suffer bison compatibility issues and we can use new features by just implementing it to Lrama. [[Feature #19637]]
* See [The future vision of Ruby Parser](https://rubykaigi.org/2023/presentations/spikeolaf.html) for detail.
* Lrama internal parser is a LR parser generated by Racc for maintainability.
* Parameterizing Rules `(?, *, +)` are supported, it will be used in Ruby parse.y.
### GC / Memory management
* Major performance improvements over Ruby 3.2
* Young objects referenced by old objects are no longer immediately
promoted to the old generation. This significantly reduces the frequency of
major GC collections. [[Feature #19678]]
* A new `REMEMBERED_WB_UNPROTECTED_OBJECTS_LIMIT_RATIO` tuning variable was
introduced to control the number of unprotected objects cause a major GC
collection to trigger. The default is set to `0.01` (1%). This significantly
reduces the frequency of major GC collection. [[Feature #19571]]
* Write Barriers were implemented for many core types that were missing them,
notably `Time`, `Enumerator`, `MatchData`, `Method`, `File::Stat`, `BigDecimal`
and several others. This significantly reduces minor GC collection time and major
GC collection frequency.
* Most core classes are now using Variable Width Allocation, notably `Hash`, `Time`,
`Thread::Backtrace`, `Thread::Backtrace::Location`, `File::Stat`, `Method`.
This makes these classes faster to allocate and free, use less memory and reduce
heap fragmentation.
* `defined?(@ivar)` is optimized with Object Shapes.
### YJIT
* Major performance improvements over Ruby 3.2
* Support for splat and rest arguments has been improved.
* Registers are allocated for stack operations of the virtual machine.
* More calls with optional arguments are compiled. Exception handlers are also compiled.
* Unsupported call types and megamorphic call sites no longer exit to the interpreter.
* Basic methods like Rails `#blank?` and
[specialized `#present?`](https://github.com/rails/rails/pull/49909) are inlined.
* `Integer#*`, `Integer#!=`, `String#!=`, `String#getbyte`,
`Kernel#block_given?`, `Kernel#is_a?`, `Kernel#instance_of?`, and `Module#===`
are specially optimized.
* Compilation speed is now slightly faster than Ruby 3.2.
* Now more than 3x faster than the interpreter on Optcarrot!
* Significantly improved memory usage over Ruby 3.2
* Metadata for compiled code uses a lot less memory.
* `--yjit-call-threshold` is automatically raised from 30 to 120
when the application has more than 40,000 ISEQs.
* `--yjit-cold-threshold` is added to skip compiling cold ISEQs.
* More compact code is generated on Arm64.
* Code GC is now disabled by default
* `--yjit-exec-mem-size` is treated as a hard limit where compilation of new code stops.
* No sudden drops in performance due to code GC.
Better copy-on-write behavior on servers reforking with
[Pitchfork](https://github.com/shopify/pitchfork).
* You can still enable code GC if desired with `--yjit-code-gc`
* Add `RubyVM::YJIT.enable` that can enable YJIT at run-time
* You can start YJIT without modifying command-line arguments or environment variables.
Rails 7.2 will [enable YJIT by default](https://github.com/rails/rails/pull/49947)
using this method.
* This can also be used to enable YJIT only once your application is
done booting. `--yjit-disable` can be used if you want to use other
YJIT options while disabling YJIT at boot.
* More YJIT stats are available by default
* `yjit_alloc_size` and several more metadata-related stats are now available by default.
* `ratio_in_yjit` stat produced by `--yjit-stats` is now available in release builds,
a special stats or dev build is no longer required to access most stats.
* Add more profiling capabilities
* `--yjit-perf` is added to facilitate profiling with Linux perf.
* `--yjit-trace-exits` now supports sampling with `--yjit-trace-exits-sample-rate=N`
* More thorough testing and multiple bug fixes
* `--yjit-stats=quiet` is added to avoid printing stats on exit.
### MJIT
* MJIT is removed.
* `--disable-jit-support` is removed. Consider using `--disable-yjit --disable-rjit` instead.
### RJIT
* Introduced a pure-Ruby JIT compiler RJIT.
* RJIT supports only x86\_64 architecture on Unix platforms.
* Unlike MJIT, it doesn't require a C compiler at runtime.
* RJIT exists only for experimental purposes.
* You should keep using YJIT in production.
### M:N Thread scheduler
* M:N Thread scheduler is introduced. [[Feature #19842]]
* Background: Ruby 1.8 and before, M:1 thread scheduler (M Ruby threads
with 1 native thread. Called as User level threads or Green threads)
is used. Ruby 1.9 and later, 1:1 thread scheduler (1 Ruby thread with
1 native thread). M:1 threads takes lower resources compare with 1:1
threads because it needs only 1 native threads. However it is difficult
to support context switching for all of blocking operation so 1:1
threads are employed from Ruby 1.9. M:N thread scheduler uses N native
threads for M Ruby threads (N is small number in general). It doesn't
need same number of native threads as Ruby threads (similar to the M:1
thread scheduler). Also our M:N threads supports blocking operations
well same as 1:1 threads. See the ticket for more details.
Our M:N thread scheduler refers on the goroutine scheduler in the
Go language.
* In a ractor, only 1 thread can run in a same time because of
implementation. Therefore, applications that use only one Ractor
(most applications) M:N thread scheduler works as M:1 thread scheduler
with further extension from Ruby 1.8.
* M:N thread scheduler can introduce incompatibility for C-extensions,
so it is disabled by default on the main Ractors.
`RUBY_MN_THREADS=1` environment variable will enable it.
On non-main Ractors, M:N thread scheduler is enabled (and can not
disable it now).
* `N` (the number of native threads) can be specified with `RUBY_MAX_CPU`
environment variable. The default is 8.
Note that more than `N` native threads are used to support many kind of
blocking operations.
[Bug #595]: https://bugs.ruby-lang.org/issues/595
[Feature #10602]: https://bugs.ruby-lang.org/issues/10602
[Bug #17146]: https://bugs.ruby-lang.org/issues/17146
[Feature #18183]: https://bugs.ruby-lang.org/issues/18183
[Feature #18285]: https://bugs.ruby-lang.org/issues/18285
[Feature #18498]: https://bugs.ruby-lang.org/issues/18498
[Feature #18515]: https://bugs.ruby-lang.org/issues/18515
[Feature #18551]: https://bugs.ruby-lang.org/issues/18551
[Feature #18885]: https://bugs.ruby-lang.org/issues/18885
[Feature #18949]: https://bugs.ruby-lang.org/issues/18949
[Feature #18980]: https://bugs.ruby-lang.org/issues/18980
[Bug #19012]: https://bugs.ruby-lang.org/issues/19012
[Feature #19057]: https://bugs.ruby-lang.org/issues/19057
[Bug #19150]: https://bugs.ruby-lang.org/issues/19150
[Bug #19293]: https://bugs.ruby-lang.org/issues/19293
[Feature #19314]: https://bugs.ruby-lang.org/issues/19314
[Feature #19347]: https://bugs.ruby-lang.org/issues/19347
[Feature #19351]: https://bugs.ruby-lang.org/issues/19351
[Feature #19362]: https://bugs.ruby-lang.org/issues/19362
[Feature #19370]: https://bugs.ruby-lang.org/issues/19370
[Feature #19521]: https://bugs.ruby-lang.org/issues/19521
[Feature #19538]: https://bugs.ruby-lang.org/issues/19538
[Feature #19561]: https://bugs.ruby-lang.org/issues/19561
[Feature #19571]: https://bugs.ruby-lang.org/issues/19571
[Feature #19572]: https://bugs.ruby-lang.org/issues/19572
[Feature #19591]: https://bugs.ruby-lang.org/issues/19591
[Feature #19630]: https://bugs.ruby-lang.org/issues/19630
[Feature #19637]: https://bugs.ruby-lang.org/issues/19637
[Feature #19678]: https://bugs.ruby-lang.org/issues/19678
[Feature #19714]: https://bugs.ruby-lang.org/issues/19714
[Feature #19725]: https://bugs.ruby-lang.org/issues/19725
[Feature #19757]: https://bugs.ruby-lang.org/issues/19757
[Feature #19776]: https://bugs.ruby-lang.org/issues/19776
[Feature #19777]: https://bugs.ruby-lang.org/issues/19777
[Feature #19785]: https://bugs.ruby-lang.org/issues/19785
[Feature #19790]: https://bugs.ruby-lang.org/issues/19790
[Feature #19839]: https://bugs.ruby-lang.org/issues/19839
[Feature #19842]: https://bugs.ruby-lang.org/issues/19842
[Feature #19843]: https://bugs.ruby-lang.org/issues/19843
[Bug #19868]: https://bugs.ruby-lang.org/issues/19868
[Feature #19965]: https://bugs.ruby-lang.org/issues/19965
[Feature #20005]: https://bugs.ruby-lang.org/issues/20005
[Feature #20057]: https://bugs.ruby-lang.org/issues/20057
share/doc/alt-ruby33-libs/README.md 0000644 00000007216 15173517740 0012546 0 ustar 00 [](https://github.com/ruby/ruby/actions?query=workflow%3A"MinGW")
[](https://github.com/ruby/ruby/actions?query=workflow%3A"RJIT")
[](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://docs.ruby-lang.org/en/master/maintainers_md.html#label-Platform+Maintainers
## How to get Ruby
For a complete list of ways to install Ruby, including using third-party tools
like rvm, see:
https://www.ruby-lang.org/en/downloads/
You can download release packages and the snapshot of the repository. If you want to
download whole versions of Ruby, please visit https://www.ruby-lang.org/en/downloads/releases/.
### Download with Git
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](https://docs.ruby-lang.org/en/master/contributing/building_ruby_md.html)
## 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:
join
in the mail subject (not body) to the address [ruby-talk-request@ml.ruby-lang.org].
[ruby-talk-request@ml.ruby-lang.org]: mailto:ruby-talk-request@ml.ruby-lang.org?subject=join
## 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-ruby33/COPYING 0000644 00000004573 15173517740 0012436 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-ruby33/COPYING.ja 0000644 00000004776 15173517740 0013034 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-ruby33/BSDL 0000644 00000002413 15173517740 0012041 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-ruby33/LEGAL 0000644 00000125255 15173517740 0012153 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.
>>>
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/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-ruby33/GPL 0000644 00000043254 15173517740 0011747 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-ruby33-libs/COPYING 0000644 00000004573 15173517740 0013365 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-ruby33-libs/COPYING.ja 0000644 00000004776 15173517740 0013763 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-ruby33-libs/LEGAL 0000644 00000125255 15173517740 0013102 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.
>>>
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/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-ruby33-libs/GPL 0000644 00000043254 15173517740 0012676 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-ruby33-devel/COPYING 0000644 00000004573 15173517740 0013533 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-ruby33-devel/COPYING.ja 0000644 00000004776 15173517740 0014131 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-ruby33-devel/BSDL 0000644 00000002413 15173517740 0013136 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-ruby33-devel/LEGAL 0000644 00000125255 15173517740 0013250 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.
>>>
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/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-ruby33-devel/GPL 0000644 00000043254 15173517740 0013044 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.3.stp 0000644 00000020726 15173517740 0014342 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/ruby33/lib*\/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").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/ruby33/lib*/libruby.so.3.3").mark("string__create")
{
size = $arg1
file = user_string($arg2)
line = $arg3
}
share/gems/cache/rackup-2.1.0.gem 0000644 00000037000 15173517740 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[����<