ARGV
The ARGF object works with the array at global variable ARGV
to make $stdin
and file streams available in the Ruby program:
ARGV may be thought of as the argument vector array.
Initially, it contains the command-line arguments and options that are passed to the Ruby program; the program can modify that array as it likes.
ARGF may be thought of as the argument files object.
It can access file streams and/or the $stdin
stream, based on what it finds in ARGV
. This provides a convenient way for the command line to specify streams for a Ruby program to read.
ARGF may read from source streams, which at any particular time are determined by the content of ARGV
.
When the very first ARGF read occurs with an empty ARGV
([]
), the source is $stdin
:
File t.rb
:
p ['ARGV', ARGV] p ['ARGF.read', ARGF.read]
Commands and outputs (see below for the content of files foo.txt
and bar.txt
):
$ echo "Open the pod bay doors, Hal." | ruby t.rb ["ARGV", []] ["ARGF.read", "Open the pod bay doors, Hal.\n"] $ cat foo.txt bar.txt | ruby t.rb ["ARGV", []] ["ARGF.read", "Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"]
Many examples here assume the existence of files foo.txt
and bar.txt
:
$ cat foo.txt Foo 0 Foo 1 $ cat bar.txt Bar 0 Bar 1 Bar 2 Bar 3
ARGV
For any ARGF read except the simplest case (that is, except for the very first ARGF read with an empty ARGV
), the sources are found in ARGV
.
ARGF assumes that each element in array ARGV
is a potential source, and is one of:
The string path to a file that may be opened as a stream.
The character '-'
, meaning stream $stdin
.
Each element that is not one of these should be removed from ARGV
before ARGF accesses that source.
In the following example:
Filepaths foo.txt
and bar.txt
may be retained as potential sources.
Options --xyzzy
and --mojo
should be removed.
Example:
File t.rb
:
# Print arguments (and options, if any) found on command line. p ['ARGV', ARGV]
Command and output:
$ ruby t.rb --xyzzy --mojo foo.txt bar.txt ["ARGV", ["--xyzzy", "--mojo", "foo.txt", "bar.txt"]]
ARGF’s stream access considers the elements of ARGV
, left to right:
File t.rb
:
p "ARGV: #{ARGV}" p "Line: #{ARGF.read}" # Read everything from all specified streams.
Command and output:
$ ruby t.rb foo.txt bar.txt "ARGV: [\"foo.txt\", \"bar.txt\"]" "Read: Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"
Because the value at ARGV
is an ordinary array, you can manipulate it to control which sources ARGF considers:
If you remove an element from ARGV
, ARGF will not consider the corresponding source.
If you add an element to ARGV
, ARGF will consider the corresponding source.
Each element in ARGV
is removed when its corresponding source is accessed; when all sources have been accessed, the array is empty:
File t.rb
:
until ARGV.empty? && ARGF.eof? p "ARGV: #{ARGV}" p "Line: #{ARGF.readline}" # Read each line from each specified stream. end
Command and output:
$ ruby t.rb foo.txt bar.txt "ARGV: [\"foo.txt\", \"bar.txt\"]" "Line: Foo 0\n" "ARGV: [\"bar.txt\"]" "Line: Foo 1\n" "ARGV: [\"bar.txt\"]" "Line: Bar 0\n" "ARGV: []" "Line: Bar 1\n" "ARGV: []" "Line: Bar 2\n" "ARGV: []" "Line: Bar 3\n"
ARGV
The ARGV
array may contain filepaths the specify sources for ARGF reading.
This program prints what it reads from files at the paths specified on the command line:
File t.rb
:
p ['ARGV', ARGV] # Read and print all content from the specified sources. p ['ARGF.read', ARGF.read]
Command and output:
$ ruby t.rb foo.txt bar.txt ["ARGV", [foo.txt, bar.txt] ["ARGF.read", "Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"]
$stdin
in ARGV
To specify stream $stdin
in ARGV
, us the character '-'
:
File t.rb
:
p ['ARGV', ARGV] p ['ARGF.read', ARGF.read]
Command and output:
$ echo "Open the pod bay doors, Hal." | ruby t.rb - ["ARGV", ["-"]] ["ARGF.read", "Open the pod bay doors, Hal.\n"]
When no character '-'
is given, stream $stdin
is ignored (exception: see Specifying $stdin in ARGV):
Command and output:
$ echo "Open the pod bay doors, Hal." | ruby t.rb foo.txt bar.txt "ARGV: [\"foo.txt\", \"bar.txt\"]" "Read: Foo 0\nFoo 1\nBar 0\nBar 1\nBar 2\nBar 3\n"
ARGV
For an ARGF reader, ARGV
may contain any mixture of filepaths and character '-'
, including repetitions.
ARGV
The running Ruby program may make any modifications to the ARGV
array; the current value of ARGV
affects ARGF reading.
ARGV
For an empty ARGV
, an ARGF read method either returns nil
or raises an exception, depending on the specific method.
As seen above, method ARGF#read
reads the content of all sources into a single string. Other ARGF methods provide other ways to access that content; these include:
Codepoint access: each_codepoint
.
Source access: read
, read_nonblock
, readpartial
.
ARGF includes module Enumerable
. Virtually all methods in Enumerable call method #each
in the including class.
Note well: In ARGF, method each
returns data from the sources, not from ARGV
; therefore, for example, ARGF#entries
returns an array of lines from the sources, not an array of the strings from ARGV
:
File t.rb
:
p ['ARGV', ARGV] p ['ARGF.entries', ARGF.entries]
Command and output:
$ ruby t.rb foo.txt bar.txt ["ARGV", ["foo.txt", "bar.txt"]] ["ARGF.entries", ["Foo 0\n", "Foo 1\n", "Bar 0\n", "Bar 1\n", "Bar 2\n", "Bar 3\n"]]
If inplace mode is in effect, ARGF may write to target streams, which at any particular time are determined by the content of ARGV.
Methods about inplace mode:
Methods for writing:
IPAddr
provides a set of methods to manipulate an IP address. Both IPv4 and IPv6 are supported.
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>
The Warning
module contains a single method named warn
, and the module extends itself, making Warning.warn
available. Warning.warn
is called for all warnings issued by Ruby. By default, warnings are printed to $stderr.
Changing the behavior of Warning.warn
is useful to customize how warnings are handled by Ruby, for instance by filtering some warnings, and/or outputting warnings somewhere other than $stderr
.
If you want to change the behavior of Warning.warn
you should use Warning.extend(MyNewModuleWithWarnMethod)
and you can use super
to get the default behavior of printing the warning to $stderr
.
Example:
module MyWarningFilter def warn(message, category: nil, **kwargs) if /some warning I want to ignore/.match?(message) # ignore else super end end end Warning.extend MyWarningFilter
You should never redefine Warning#warn
(the instance method), as that will then no longer provide a way to use the default behavior.
The warning gem provides convenient ways to customize Warning.warn
.
JSON is a lightweight data-interchange format.
A JSON value is one of the following:
Double-quoted text: "foo"
.
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]]
To make module JSON available in your code, begin with:
require 'json'
All examples here assume that this has been done.
You can parse a String containing JSON data using either of two methods:
JSON.parse(source, opts)
JSON.parse!(source, opts)
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.
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]]
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]}
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
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]
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.
To generate a Ruby String containing JSON data, use method JSON.generate(source, opts)
, where
source
is a Ruby object.
opts
is a Hash object containing options that control both input allowed and output formatting.
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}]'
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"}'
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>"'
Option allow_nan
(boolean) specifies whether NaN
, Infinity
, and -Infinity
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)
Options script_safe
(boolean) specifies wether '\u2028'
, '\u2029'
and '/'
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.
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, ''
.
Option object_nl
(String) specifies a string (usually a newline) to be inserted after each JSON object; defaults to the empty String, ''
.
Option indent
(String) specifies the string (usually spaces) to be used for indentation; defaults to the empty String, ''
; defaults to the empty String, ''
; 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, ''
.
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, ''
.
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 } }
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.
The JSON module includes additions for certain classes. To use an addition, require
its source:
BigDecimal: require 'json/add/bigdecimal'
Complex: require 'json/add/complex'
Date: require 'json/add/date'
DateTime: require 'json/add/date_time'
Exception: require 'json/add/exception'
OpenStruct: require 'json/add/ostruct'
Range: require 'json/add/range'
Rational: require 'json/add/rational'
Regexp: require 'json/add/regexp'
Set: require 'json/add/set'
Struct: require 'json/add/struct'
Symbol: require 'json/add/symbol'
Time: require 'json/add/time'
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
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)
The objspace library extends the ObjectSpace
module and adds several methods to get internal statistic information about object/memory management.
You need to require 'objspace'
to use this extension module.
Generally, you SHOULD NOT use this library if you do not know about the MRI implementation. Mainly, this library is for (memory) profiler developers and MRI developers who need to know about MRI memory usage.
The ObjectSpace
module contains a number of routines that interact with the garbage collection facility and allow you to traverse all living objects with an iterator.
ObjectSpace
also provides support for object finalizers, procs that will be called after a specific object was destroyed by garbage collection. See the documentation for ObjectSpace.define_finalizer
for important information on how to use this method correctly.
a = "A" b = "B" ObjectSpace.define_finalizer(a, proc {|id| puts "Finalizer one on #{id}" }) ObjectSpace.define_finalizer(b, proc {|id| puts "Finalizer two on #{id}" }) a = nil b = nil
produces:
Finalizer two on 537763470 Finalizer one on 537763480
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 "a"*1_000_000_000
:
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)
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 @records
. You could provide the lookup method record_number(), which simply calls [] on the @records
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!"
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
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
.
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.
The Singleton
module implements the Singleton
pattern.
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.
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
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.
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 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')
‘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.
Tarjan, “Depth First Search and Linear Graph Algorithms”,
SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972.
The marshaling library converts collections of Ruby objects into a byte stream, allowing them to be stored outside the currently active script. This data may subsequently be read and the original objects reconstituted.
Marshaled data has major and minor version numbers stored along with the object information. In normal use, marshaling can only load data written with the same major version number and an equal or lower minor version number. If Ruby’s “verbose” flag is set (normally using -d, -v, -w, or –verbose) the major and minor numbers must match exactly. Marshal
versioning is independent of Ruby’s version numbers. You can extract the version by reading the first two bytes of marshaled data.
str = Marshal.dump("thing") RUBY_VERSION #=> "1.9.0" str[0].ord #=> 4 str[1].ord #=> 8
Some objects cannot be dumped: if the objects to be dumped include bindings, procedure or method objects, instances of class IO
, or singleton objects, a TypeError
will be raised.
If your class has special serialization needs (for example, if you want to serialize in some specific format), or if it contains objects that would otherwise not be serializable, you can implement your own serialization strategy.
There are two methods of doing this, your object can define either marshal_dump and marshal_load or _dump and _load. marshal_dump will take precedence over _dump if both are defined. marshal_dump may result in smaller Marshal
strings.
By design, Marshal.load
can deserialize almost any class loaded into the Ruby process. In many cases this can lead to remote code execution if the Marshal
data is loaded from an untrusted source.
As a result, Marshal.load
is not suitable as a general purpose serialization format and you should never unmarshal user supplied input or other untrusted data.
If you need to deserialize untrusted data, use JSON
or another serialization format that is only able to load simple, ‘primitive’ types such as String
, Array
, Hash
, etc. Never allow user input to specify arbitrary types to deserialize into.
When dumping an object the method marshal_dump will be called. marshal_dump must return a result containing the information necessary for marshal_load to reconstitute the object. The result can be any object.
When loading an object dumped using marshal_dump the object is first allocated then marshal_load is called with the result from marshal_dump. marshal_load must recreate the object from the information in the result.
Example:
class MyObj def initialize name, version, data @name = name @version = version @data = data end def marshal_dump [@name, @version] end def marshal_load array @name, @version = array end end
Use _dump and _load when you need to allocate the object you’re restoring yourself.
When dumping an object the instance method _dump is called with an Integer
which indicates the maximum depth of objects to dump (a value of -1 implies that you should disable depth checking). _dump must return a String
containing the information necessary to reconstitute the object.
The class method _load should take a String
and use it to return an object of the same class.
Example:
class MyObj def initialize name, version, data @name = name @version = version @data = data end def _dump level [@name, @version].join ':' end def self._load args new(*args.split(':')) end end
Since Marshal.dump
outputs a string you can have _dump return a Marshal
string which is Marshal.loaded in _load for complex objects.
A base class for objects representing a C union
This exception is raised if a parser error occurs.
This exception is raised if the nesting of parsed data structures is too deep.
YAML
event parser class. This class parses a YAML
document and calls events on the handler that is passed to the constructor. The events can be used for things such as constructing a YAML
AST or deserializing YAML
documents. It can even be fed back to Psych::Emitter
to emit the same document that was parsed.
See Psych::Handler
for documentation on the events that Psych::Parser
emits.
Here is an example that prints out ever scalar found in a YAML
document:
# Handler for detecting scalar values class ScalarHandler < Psych::Handler def scalar value, anchor, tag, plain, quoted, style puts value end end parser = Psych::Parser.new(ScalarHandler.new) parser.parse(yaml_document)
Here is an example that feeds the parser back in to Psych::Emitter
. The YAML
document is read from STDIN and written back out to STDERR:
parser = Psych::Parser.new(Psych::Emitter.new($stderr)) parser.parse($stdin)
Psych
uses Psych::Parser
in combination with Psych::TreeBuilder
to construct an AST of the parsed YAML
document.
Raised when OLE processing failed.
EX:
obj = WIN32OLE.new("NonExistProgID")
raises the exception:
WIN32OLE::RuntimeError: unknown OLE server: `NonExistProgID' HRESULT error code:0x800401f3 Invalid class string
WIN32OLE::Param
objects represent param information of the OLE method.
Subclass of Zlib::Error
When zlib returns a Z_VERSION_ERROR, usually if the zlib library version is incompatible with the version assumed by the caller.
Can be raised by IO
operations when IO#timeout=
is set.
exception to wait for writing by EAGAIN. see IO.select
.
exception to wait for writing by EWOULDBLOCK. see IO.select
.
exception to wait for writing by EINPROGRESS. see IO.select
.